<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0"><channel><title>蓝特智联-知识库</title><link>https://wiki.dgfygk.com/</link><atom:link href="https://wiki.dgfygk.com/rss.xml" rel="self" type="application/rss+xml"/><description>东莞目前最专业的 IT 外包服务商</description><generator>Halo v2.25.4</generator><language>zh-cn</language><image><url>https://wiki.dgfygk.com/upload/0-2%E8%93%9D%E6%A0%87l%E9%BB%91%E5%AD%97%E7%99%BD%E5%BA%95.png</url><title>蓝特智联-知识库</title><link>https://wiki.dgfygk.com/</link></image><lastBuildDate>Sun, 2 Aug 2026 07:19:18 GMT</lastBuildDate><item><title><![CDATA[小程序客服知识库]]></title><link>https://wiki.dgfygk.com/archives/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AE%A2%E6%9C%8D%E7%9F%A5%E8%AF%86%E5%BA%93</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AE%A2%E6%9C%8D%E7%9F%A5%E8%AF%86%E5%BA%93&amp;url=/archives/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AE%A2%E6%9C%8D%E7%9F%A5%E8%AF%86%E5%BA%93" width="1" height="1" alt="" style="opacity:0;">
<h1 id="%E8%93%9D%E7%89%B9%E6%99%BA%E5%85%85%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AE%A2%E6%9C%8D%E7%9F%A5%E8%AF%86%E5%BA%93" tabindex="-1">蓝特智充小程序客服知识库</h1>
<blockquote>
 <p>本文档依据小程序内现行页面文案与业务规则整理，供 AI 客服知识库使用。
  <br>
  回答时优先依据本文；涉及具体站点的免费时长、单价、封顶、投诉电话等以用户当前订单/站点展示为准，不要编造数字。
  <br>
  若上下文出现【当前咨询订单】摘要，金额、电量、状态等以摘要为准。</p>
</blockquote>
<hr>
<h2 id="%E4%B8%80%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F%E8%83%BD%E5%81%9A%E4%BB%80%E4%B9%88-%2F-%E5%85%A5%E5%8F%A3%E5%9C%A8%E5%93%AA" tabindex="-1">一、小程序能做什么 / 入口在哪</h2>
<h3 id="q1%EF%BC%9A%E8%BF%99%E4%B8%AA%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%B8%BB%E8%A6%81%E8%83%BD%E5%B9%B2%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1">Q1：这个小程序主要能干什么？</h3>
<p>支持查找充电站、扫码充电、余额充值与退款、查看订单、申请开票、查看占位订单，以及通过 AI 客服 / 在线客服 / 客服热线咨询问题。</p>
<h3 id="q2%EF%BC%9A%E5%B8%B8%E8%A7%81%E5%8A%9F%E8%83%BD%E5%85%A5%E5%8F%A3%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F" tabindex="-1">Q2：常见功能入口在哪里？</h3>
<ul>
 <li><strong>余额充值</strong>：我的 → 余额充值</li>
 <li><strong>充值记录</strong>：我的 → 充值记录</li>
 <li><strong>退款</strong>：我的 → 退款中心；帮助中心也可进退款</li>
 <li><strong>订单</strong>：我的 → 订单 / 帮助中心 → 订单</li>
 <li><strong>占位订单</strong>：我的 → 占位订单</li>
 <li><strong>申请开票</strong>：个人中心 → 申请开票</li>
 <li><strong>帮助中心</strong>：常见问题（充值与退款、充电相关、发票相关、故障解决）</li>
 <li><strong>客服</strong>：帮助中心底部「在线客服」；地图「联系客服」；站点详情「在线客服」；订单详情 / 充电中也可进入。客服会话默认由智能助手接待，需要时可转人工。</li>
</ul>
<h3 id="q3%EF%BC%9A%E8%81%94%E7%B3%BB%E4%BA%BA%E5%B7%A5%E5%AE%A2%E6%9C%8D%E6%80%8E%E4%B9%88%E5%81%9A%EF%BC%9F" tabindex="-1">Q3：联系人工客服怎么做？</h3>
<ol>
 <li>先进入<strong>客服</strong>会话（不是单独的「AI 客服」入口）。</li>
 <li>在会话中直接说「转人工」，按提示点击「转人工」进入人工客服。</li>
 <li>也可在帮助中心拨打<strong>客服热线</strong>。</li>
 <li>部分站点占位规则中另有<strong>本站投诉电话</strong>，与平台客服热线可能不同，以站点展示为准。</li>
</ol>
<hr>
<h2 id="%E4%BA%8C%E3%80%81%E6%89%AB%E7%A0%81%E5%85%85%E7%94%B5-%2F-%E5%90%AF%E5%8A%A8-%2F-%E7%BB%93%E6%9D%9F%E5%85%85%E7%94%B5" tabindex="-1">二、扫码充电 / 启动 / 结束充电</h2>
<h3 id="q4%EF%BC%9A%E6%80%8E%E4%B9%88%E5%BC%80%E5%A7%8B%E5%85%85%E7%94%B5%EF%BC%9F" tabindex="-1">Q4：怎么开始充电？</h3>
<ol>
 <li>将充电枪插好，扫描充电桩/枪上的二维码（或从站点进入选枪）。</li>
 <li>登录后确认使用<strong>个人账户</strong>或<strong>集团余额</strong>。</li>
 <li>确认余额充足后开始充电，进入「充电启动中」等待启动成功。</li>
 <li>启动成功后进入充电中页面，可查看电量、费用、SOC 等信息。</li>
</ol>
<h3 id="q5%EF%BC%9A%E5%BC%80%E5%A7%8B%E5%85%85%E7%94%B5%E5%89%8D%E8%A6%81%E6%B3%A8%E6%84%8F%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1">Q5：开始充电前要注意什么？</h3>
<ul>
 <li>开始充电前请<strong>熄火关门</strong>；若有强停按钮请检查按钮状态。</li>
 <li>启动过程中<strong>不要直接拔枪</strong>。</li>
 <li>若倒计时结束仍未启动成功，视为本次充电失败。</li>
 <li>余额不足时，系统会<strong>自动停止充电</strong>。</li>
</ul>
<h3 id="q6%EF%BC%9A%E6%80%8E%E4%B9%88%E7%BB%93%E6%9D%9F%E5%85%85%E7%94%B5%EF%BC%9F%E5%85%A5%E5%8F%A3%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F" tabindex="-1">Q6：怎么结束充电？入口在哪里？</h3>
<p>在<strong>充电中</strong>页面点击「结束充电」。
 <br>
 若长时间无法结束，可尝试<strong>拔枪结束</strong>（以页面提示为准）。
 <br>
 中途想停止充电，在充电页面点击「停止充电 / 结束充电」即可结束。</p>
<h3 id="q7%EF%BC%9A%E5%90%AF%E5%8A%A8%E5%A4%B1%E8%B4%A5%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F" tabindex="-1">Q7：启动失败怎么办？</h3>
<p>提示一般为：充电启动失败，请重试或更换电桩；如要重试，请<strong>拔枪后再操作</strong>。
 <br>
 也可检查二维码是否正确、枪是否插好、余额是否足够，或换枪重试。二维码错误时可提示「二维码错误请重试或换枪」。</p>
<h3 id="q8%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E7%82%B9%E4%BA%86%E5%BC%80%E5%A7%8B%E5%85%85%E7%94%B5%E5%8D%B4%E4%B8%8D%E8%83%BD%E5%85%85%EF%BC%9F" tabindex="-1">Q8：为什么点了开始充电却不能充？</h3>
<p>常见原因：</p>
<ol>
 <li><strong>未登录</strong>或登录失效。</li>
 <li><strong>余额不足</strong>：账户余额不足约 <strong>1 元</strong>无法充电（以页面校验为准）。</li>
 <li><strong>正在退款中</strong>：有未处理退款时无法开始充电。</li>
 <li><strong>余额与站点不匹配</strong>：当前钱包余额无法在该站点使用，需先退款再按站点重新充值。</li>
 <li>启动超时或设备故障，需拔枪后重试或换桩。</li>
</ol>
<h3 id="q9%EF%BC%9A%E5%85%85%E7%94%B5%E4%B8%AD%E4%BD%99%E9%A2%9D%E4%B8%8D%E5%A4%9F%E4%BC%9A%E6%80%8E%E6%A0%B7%EF%BC%9F" tabindex="-1">Q9：充电中余额不够会怎样？</h3>
<p>充电余额不足时，系统将<strong>自动停止充电</strong>。
 <br>
 集团充电场景下也会提示：余额不足时会自动结束订单，请保持余额充足。</p>
<h3 id="q10%EF%BC%9A%E5%85%85%E7%94%B5%E6%9C%AA%E4%BA%A7%E7%94%9F%E8%B4%B9%E7%94%A8%E8%BF%98%E8%A6%81%E6%94%AF%E4%BB%98%E5%90%97%EF%BC%9F" tabindex="-1">Q10：充电未产生费用还要支付吗？</h3>
<p>若当前充电未产生任何费用，一般<strong>无需支付</strong>。</p>
<h3 id="q11%EF%BC%9A%E4%B8%AA%E4%BA%BA%E8%B4%A6%E6%88%B7%E5%92%8C%E9%9B%86%E5%9B%A2%E4%BD%99%E9%A2%9D%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F" tabindex="-1">Q11：个人账户和集团余额有什么区别？</h3>
<p>开始充电前可选择使用个人账户或集团余额；确认弹窗会提示将使用哪一种余额。集团余额相关规则以集团账户权限与页面展示为准。</p>
<hr>
<h2 id="%E4%B8%89%E3%80%81%E7%94%B5%E4%BB%B7%E4%B8%8E%E8%B4%B9%E7%94%A8%EF%BC%88%E7%94%B5%E8%B4%B9-%2F-%E6%9C%8D%E5%8A%A1%E8%B4%B9%EF%BC%89" tabindex="-1">三、电价与费用（电费 / 服务费）</h2>
<h3 id="q12%EF%BC%9A%E5%85%85%E7%94%B5%E8%B4%B9%E7%94%A8%E6%80%8E%E4%B9%88%E7%AE%97%EF%BC%9F" tabindex="-1">Q12：充电费用怎么算？</h3>
<p>费用一般由<strong>电费 + 服务费</strong>构成，展示单位多为<strong>元/度</strong>。
 <br>
 订单总金额、实付金额、电量、时长等以订单详情或【当前咨询订单】摘要为准。</p>
<h3 id="q13%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B4%B9%E5%92%8C%E6%80%BB%E9%87%91%E9%A2%9D%E6%98%AF%E4%BB%80%E4%B9%88%E5%85%B3%E7%B3%BB%EF%BC%9F" tabindex="-1">Q13：服务费和总金额是什么关系？</h3>
<p>总金额通常包含电费与服务费等合计；是否已含服务费、服务费具体多少，请查看订单明细中的「电费」「服务费」「订单总金额」「实付金额」。</p>
<h3 id="q14%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%89%E4%BC%9A%E5%91%98%E6%8A%98%E6%89%A3-%2F-%E5%9C%BA%E5%9C%B0%E6%8A%98%E6%89%A3%EF%BC%9F" tabindex="-1">Q14：为什么有会员折扣 / 场地折扣？</h3>
<ul>
 <li><strong>会员折扣</strong>一般作用于<strong>服务费</strong>（页面常展示「会员折扣：X折（服务费）」）。</li>
 <li>部分站点还有<strong>场地服务费折扣</strong>，可能与会员折扣叠加展示。</li>
 <li>具体折扣以当前站点/订单展示为准，不要编造折扣数字。</li>
</ul>
<h3 id="q15%EF%BC%9A%E7%94%B5%E4%BB%B7%E5%88%86%E6%97%B6%E6%AE%B5%E5%90%97%EF%BC%9F" tabindex="-1">Q15：电价分时段吗？</h3>
<p>站点可能按时段展示不同电价（峰平谷等），以枪页/充电中「电价」或时段说明为准。</p>
<h3 id="q16%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E6%84%9F%E8%A7%89%E8%BF%99%E7%AC%94%E8%AE%A2%E5%8D%95%E8%B4%B5%EF%BC%9F" tabindex="-1">Q16：为什么感觉这笔订单贵？</h3>
<p>可引导用户核对：</p>
<ol>
 <li>电量（度）与充电时长；</li>
 <li>电费、服务费分别是多少；</li>
 <li>是否含占位费（占位为另一类订单）；</li>
 <li>是否有优惠抵扣（抵扣后实付可能变化）；</li>
 <li>结合【当前咨询订单】摘要中的金额字段说明，不要编造。</li>
</ol>
<hr>
<h2 id="%E5%9B%9B%E3%80%81%E5%8D%A0%E4%BD%8D%E8%B4%B9" tabindex="-1">四、占位费</h2>
<h3 id="q17%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E5%8D%A0%E4%BD%8D%E8%B4%B9%EF%BC%9F" tabindex="-1">Q17：什么是占位费？</h3>
<p>部分场站启用<strong>占位收费</strong>：充电完成后若车辆继续占用车位/枪位，超过免费时长后按规则计费。充电完成后请及时拔枪挪车。</p>
<h3 id="q18%EF%BC%9A%E5%8D%A0%E4%BD%8D%E8%B4%B9%E6%80%8E%E4%B9%88%E7%AE%97%EF%BC%9F%E5%85%8D%E5%A4%9A%E4%B9%85%EF%BC%9F" tabindex="-1">Q18：占位费怎么算？免多久？</h3>
<p>以站点「占位费说明」为准，通常包含：</p>
<ul>
 <li><strong>免占位费时长</strong>（分钟）</li>
 <li><strong>收费单价</strong>（元/分钟）</li>
 <li><strong>封顶金额</strong>（元）</li>
 <li>直流桩 / 交流桩可能规则不同</li>
 <li>站点自定义说明与<strong>本站投诉电话</strong></li>
</ul>
<p>具体数字以当前站点配置为准，知识库无法给出统一固定值。</p>
<h3 id="q19%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E5%85%85%E7%94%B5%E6%97%B6%E8%A6%81%E9%A2%84%E7%95%99%E4%BD%99%E9%A2%9D%EF%BC%9F" tabindex="-1">Q19：为什么充电时要预留余额？</h3>
<p>部分站点要求账户预留资金用于可能产生的占位费：</p>
<ul>
 <li>固定金额：至少保留 N 元；或</li>
 <li>按比例：按当前余额的 P% 预留；
  <br>
  直流/交流可能分别配置。余额不足时可能<strong>提前结束充电</strong>。</li>
</ul>
<h3 id="q20%EF%BC%9A%E5%8D%A0%E4%BD%8D%E8%B4%B9%E5%8F%AF%E4%BB%A5%E9%80%80%E5%90%97%EF%BC%9F" tabindex="-1">Q20：占位费可以退吗？</h3>
<p>占位费支付相关页面提示：支付占位费代表认可本次占位行为并愿意承担费用，<strong>占位费一经支付不予退款</strong>。
 <br>
 占位中显示金额可能为估算，以实际支付为准。</p>
<h3 id="q21%EF%BC%9A%E5%9C%A8%E5%93%AA%E9%87%8C%E7%9C%8B%E5%8D%A0%E4%BD%8D%E8%AE%A2%E5%8D%95%EF%BC%9F" tabindex="-1">Q21：在哪里看占位订单？</h3>
<p>我的 → <strong>占位订单</strong>。</p>
<hr>
<h2 id="%E4%BA%94%E3%80%81%E5%85%85%E5%80%BC" tabindex="-1">五、充值</h2>
<h3 id="q22%EF%BC%9A%E6%80%8E%E4%B9%88%E5%85%85%E5%80%BC%EF%BC%9F" tabindex="-1">Q22：怎么充值？</h3>
<p>入口：<strong>我的 → 余额充值</strong>；充电前也可在半屏充值组件充值。</p>
<p>两种常用方式：</p>
<ol>
 <li><strong>【推荐】扫码充值</strong>：选择金额 → 扫描充电桩二维码 → 完成支付</li>
 <li><strong>选站充值</strong>：选择金额 → 点击「选站充值」→ 选择充电站并确认 → 完成支付</li>
</ol>
<h3 id="q23%EF%BC%9A%E6%9C%80%E4%BD%8E%E5%85%85%E5%A4%9A%E5%B0%91%EF%BC%9F" tabindex="-1">Q23：最低充多少？</h3>
<p>自定义金额：<strong>最低充值 50 元，且必须是整数</strong>。也可选择页面提供的固定面额。</p>
<h3 id="q24%EF%BC%9A%E5%85%85%E5%80%BC%E5%90%8E%E6%9C%89%E4%BB%80%E4%B9%88%E9%99%90%E5%88%B6%EF%BC%9F" tabindex="-1">Q24：充值后有什么限制？</h3>
<ol>
 <li>充值后，请在<strong>对应站点</strong>使用充电服务。</li>
 <li>若需在<strong>其他站点</strong>充电，一般需先将当前余额<strong>退款</strong>后，再按目标站点重新充值。</li>
 <li>提示：充电余额不足时，系统将自动停止充电。</li>
</ol>
<h3 id="q25%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%9B%E4%B8%8D%E4%BA%86%E5%85%85%E5%80%BC%E9%A1%B5%EF%BC%9F" tabindex="-1">Q25：为什么进不了充值页？</h3>
<p>若账户处于<strong>退款中</strong>，不能前往充值（页面提示「退款中，不能前往充值」）。需等退款处理完成后再充值。</p>
<h3 id="q26%EF%BC%9A%E5%9C%A8%E5%93%AA%E9%87%8C%E7%9C%8B%E5%85%85%E5%80%BC%E8%AE%B0%E5%BD%95%EF%BC%9F" tabindex="-1">Q26：在哪里看充值记录？</h3>
<p>我的 → <strong>充值记录</strong>。</p>
<hr>
<h2 id="%E5%85%AD%E3%80%81%E9%80%80%E6%AC%BE" tabindex="-1">六、退款</h2>
<h3 id="q27%EF%BC%9A%E6%80%8E%E4%B9%88%E7%94%B3%E8%AF%B7%E9%80%80%E6%AC%BE%EF%BC%9F" tabindex="-1">Q27：怎么申请退款？</h3>
<p>入口：<strong>我的 → 退款中心</strong>（或帮助中心 → 退款）。
 <br>
 在「申请退款」页查看钱包余额，阅读注意事项后点击「申请退款」。</p>
<h3 id="q28%EF%BC%9A%E9%80%80%E6%AC%BE%E6%9C%89%E5%93%AA%E4%BA%9B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%EF%BC%9F" tabindex="-1">Q28：退款有哪些注意事项？</h3>
<ol>
 <li>申请退款后不能再进行充电</li>
 <li>不能重复申请退款</li>
 <li>发起退款后将不能充值</li>
 <li>有未处理支付时不能发起退款</li>
 <li>有未处理订单时不能发起退款</li>
 <li>退款后请等待 <strong>1～3 个工作日</strong></li>
 <li>如遇问题请联系客服或致电平台客服电话</li>
</ol>
<h3 id="q29%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E7%94%B3%E8%AF%B7%E4%B8%8D%E4%BA%86%E9%80%80%E6%AC%BE%EF%BC%9F" tabindex="-1">Q29：为什么申请不了退款？</h3>
<p>常见原因：</p>
<ul>
 <li>当前余额不足以退款（余额 ≤ 0）</li>
 <li><strong>正在充电订单</strong>未结束</li>
 <li><strong>未支付订单</strong>未处理</li>
 <li>已有退款在处理中，不能重复申请</li>
 <li>有未处理支付</li>
</ul>
<p>有充电中/未支付订单时，可引导用户先去订单页处理。</p>
<h3 id="q30%EF%BC%9A%E9%80%80%E6%AC%BE%E9%80%80%E5%88%B0%E5%93%AA%E9%87%8C%EF%BC%9F%E5%A4%9A%E4%B9%85%E5%88%B0%E8%B4%A6%EF%BC%9F" tabindex="-1">Q30：退款退到哪里？多久到账？</h3>
<p>退回方式一般为<strong>原路退回</strong>。到账请等待 <strong>1～3 个工作日</strong>。
 <br>
 退款记录状态可能为：处理中 / 完成 / 已拒绝。可在退款中心「退款记录」查看。</p>
<h3 id="q31%EF%BC%9A%E9%80%80%E6%AC%BE%E4%B8%AD%E8%BF%98%E8%83%BD%E5%85%85%E7%94%B5%E6%88%96%E5%85%85%E5%80%BC%E5%90%97%EF%BC%9F" tabindex="-1">Q31：退款中还能充电或充值吗？</h3>
<p>不能。退款处理期间：无法开始充电；也无法前往充值。</p>
<hr>
<h2 id="%E4%B8%83%E3%80%81%E5%8F%91%E7%A5%A8" tabindex="-1">七、发票</h2>
<h3 id="q32%EF%BC%9A%E6%80%8E%E4%B9%88%E7%94%B3%E8%AF%B7%E5%BC%80%E7%A5%A8%EF%BC%9F" tabindex="-1">Q32：怎么申请开票？</h3>
<p>路径示例：</p>
<ul>
 <li>微信搜索小程序 → <strong>个人中心 → 申请开票</strong></li>
 <li>或：选取所需开票订单 → 申请开票 → 等待开票 → 电子发票以<strong>邮件</strong>发送至填写的邮箱</li>
</ul>
<h3 id="q33%EF%BC%9A%E5%BC%80%E7%A5%A8%E6%9C%89%E4%BB%80%E4%B9%88%E9%97%A8%E6%A7%9B%EF%BC%9F" tabindex="-1">Q33：开票有什么门槛？</h3>
<ul>
 <li>勾选金额一般需 <strong>100 元以上</strong>才可申请开票（以页面校验为准）。</li>
 <li>多笔订单合并开票需选择<strong>同一开票方/同一开票主体</strong>。</li>
 <li>需正确填写邮箱等开票信息。</li>
</ul>
<h3 id="q34%EF%BC%9A%E5%8F%91%E7%A5%A8%E7%94%B1%E8%B0%81%E5%BC%80%EF%BC%9F%E8%83%BD%E5%BC%80%E5%A4%9A%E5%B0%91%E9%87%91%E9%A2%9D%EF%BC%9F" tabindex="-1">Q34：发票由谁开？能开多少金额？</h3>
<p>充电服务由充电站公司提供，发票由<strong>实际提供服务的充电站公司</strong>开具。平台主要转达开票需求与订单信息。
 <br>
 开票金额以订单<strong>实际支付金额</strong>为准；<strong>电子券、积分、折扣等抵扣部分不可开票</strong>。</p>
<h3 id="q35%EF%BC%9A%E4%BC%81%E4%B8%9A%E6%8A%AC%E5%A4%B4%E8%A6%81%E5%A1%AB%E7%A8%8E%E5%8F%B7%E5%90%97%EF%BC%9F" tabindex="-1">Q35：企业抬头要填税号吗？</h3>
<p>如用于企业报销，一般需填写纳税人识别号。
 <br>
 若选择「个人/非企业单位」抬头，通常无需填写纳税人识别号。</p>
<h3 id="q36%EF%BC%9A%E5%8F%91%E7%A5%A8%E5%BC%80%E9%94%99%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F" tabindex="-1">Q36：发票开错了怎么办？</h3>
<p>建议提交前仔细核对。若错填需修改，请<strong>联系客服</strong>处理。</p>
<h3 id="q37%EF%BC%9A%E5%8F%91%E7%A5%A8%E5%8F%91%E5%88%B0%E5%93%AA%E9%87%8C%EF%BC%9F" tabindex="-1">Q37：发票发到哪里？</h3>
<p>开票成功后，一般以<strong>电子邮件</strong>发送电子发票至用户填写的邮箱。</p>
<hr>
<h2 id="%E5%85%AB%E3%80%81%E4%BC%9A%E5%91%98%E4%B8%8E%E4%BC%98%E6%83%A0" tabindex="-1">八、会员与优惠</h2>
<h3 id="q38%EF%BC%9A%E4%BC%9A%E5%91%98%E6%9C%89%E4%BB%80%E4%B9%88%E4%BC%98%E6%83%A0%EF%BC%9F" tabindex="-1">Q38：会员有什么优惠？</h3>
<p>会员折扣通常体现在<strong>服务费</strong>上（如「会员折扣：X折（服务费）」）。
 <br>
 等级与累计充电量等规则以「会员等级」页面及接口配置为准；启用会员等级的时间点可能导致累计电量与历史总和存在差异。</p>
<h3 id="q39%EF%BC%9A%E4%BC%98%E6%83%A0%E5%92%8C%E5%9C%BA%E5%9C%B0%E6%8A%98%E6%89%A3%E8%83%BD%E4%B8%80%E8%B5%B7%E7%94%A8%E5%90%97%EF%BC%9F" tabindex="-1">Q39：优惠和场地折扣能一起用吗？</h3>
<p>页面可能同时展示场地服务费折扣与会员折扣的叠加说明，具体以当前订单/站点展示为准。</p>
<hr>
<h2 id="%E4%B9%9D%E3%80%81%E7%99%BB%E5%BD%95%E3%80%81%E6%94%AF%E4%BB%98%E4%B8%8E%E8%B4%A6%E6%88%B7" tabindex="-1">九、登录、支付与账户</h2>
<h3 id="q40%EF%BC%9A%E6%8F%90%E7%A4%BA%E8%AF%B7%E5%85%88%E7%99%BB%E5%BD%95%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F" tabindex="-1">Q40：提示请先登录怎么办？</h3>
<p>请先完成微信授权登录后再操作充电、充值、退款、开票等功能。</p>
<h3 id="q41%EF%BC%9A%E6%8F%90%E7%A4%BA%E7%99%BB%E5%BD%95%E5%A4%B1%E6%95%88-%2F-token-%E8%BF%87%E6%9C%9F%EF%BC%9F" tabindex="-1">Q41：提示登录失效 / token 过期？</h3>
<p>请退出后重新进入小程序完成登录，再重试操作。</p>
<h3 id="q42%EF%BC%9A%E6%94%AF%E4%BB%98%E5%A4%B1%E8%B4%A5%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F" tabindex="-1">Q42：支付失败怎么办？</h3>
<p>可提示「支付失败，请重试」。检查网络与微信支付状态后再次发起支付；仍失败可转人工或拨打客服热线。</p>
<h3 id="q43%EF%BC%9A%E6%9C%89%E6%9C%AA%E6%94%AF%E4%BB%98%E8%AE%A2%E5%8D%95%E4%BC%9A%E6%9C%89%E4%BB%80%E4%B9%88%E5%BD%B1%E5%93%8D%EF%BC%9F" tabindex="-1">Q43：有未支付订单会有什么影响？</h3>
<p>可能无法申请退款；需先到订单页处理未支付订单。</p>
<hr>
<h2 id="%E5%8D%81%E3%80%81%E6%95%85%E9%9A%9C%E4%B8%8E%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86" tabindex="-1">十、故障与异常处理</h2>
<h3 id="q44%EF%BC%9A%E6%9E%AA%E6%8F%92%E4%B8%8D%E4%B8%8A-%2F-%E8%AF%86%E5%88%AB%E4%B8%8D%E5%88%B0%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F" tabindex="-1">Q44：枪插不上 / 识别不到怎么办？</h3>
<p>检查枪是否插到位、桩是否空闲/故障、二维码是否对应本枪；可换枪或换桩重试；仍不行请联系客服或本站投诉电话，并尽量提供站点名、枪号、订单号。</p>
<h3 id="q45%EF%BC%9A%E5%85%85%E7%94%B5%E4%B8%AD%E9%80%94%E8%87%AA%E5%8A%A8%E5%81%9C%E4%BA%86%EF%BC%9F" tabindex="-1">Q45：充电中途自动停了？</h3>
<p>常见原因：<strong>余额不足自动停充</strong>；或设备/网络异常。可到订单详情查看结束原因与费用；需要时可转人工并提供订单号。</p>
<h3 id="q46%EF%BC%9A%E7%BB%93%E6%9D%9F%E4%B8%8D%E4%BA%86%E5%85%85%E7%94%B5%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F" tabindex="-1">Q46：结束不了充电怎么办？</h3>
<p>先在充电中页点击「结束充电」；若长时间无法结束，按页面提示尝试<strong>拔枪结束</strong>；仍异常请转人工，并提供订单号、站点、枪号。</p>
<h3 id="q47%EF%BC%9A%E5%90%AF%E5%8A%A8%E4%B8%80%E7%9B%B4%E8%BD%AC%E5%9C%88-%2F-%E5%90%AF%E5%8A%A8%E5%A4%B1%E8%B4%A5%EF%BC%9F" tabindex="-1">Q47：启动一直转圈 / 启动失败？</h3>
<p>不要拔枪强行操作至倒计时结束前；失败后按提示拔枪再重试或换桩；可咨询 AI 或转人工。</p>
<h3 id="q48%EF%BC%9A%E5%B8%AE%E5%8A%A9%E4%B8%AD%E5%BF%83%E9%87%8C%E3%80%8C%E6%95%85%E9%9A%9C%E8%A7%A3%E5%86%B3%E3%80%8D%E5%9C%A8%E5%93%AA%EF%BC%9F" tabindex="-1">Q48：帮助中心里「故障解决」在哪？</h3>
<p>帮助中心 Tab：<strong>故障解决</strong>。具体问答由运营后台配置，若知识库未覆盖具体条目，可引导用户打开帮助中心或转人工。</p>
<hr>
<h2 id="%E5%8D%81%E4%B8%80%E3%80%81%E8%AE%A2%E5%8D%95%E7%9B%B8%E5%85%B3" tabindex="-1">十一、订单相关</h2>
<h3 id="q49%EF%BC%9A%E5%9C%A8%E5%93%AA%E9%87%8C%E7%9C%8B%E8%AE%A2%E5%8D%95%EF%BC%9F" tabindex="-1">Q49：在哪里看订单？</h3>
<p>我的 → 订单；帮助中心也有订单入口。订单详情可查看站点、电量、金额、状态等。</p>
<h3 id="q50%EF%BC%9Aai-%E5%AE%A2%E6%9C%8D%E8%AF%B4%E7%9A%84%E3%80%8C%E5%BD%93%E5%89%8D%E5%92%A8%E8%AF%A2%E8%AE%A2%E5%8D%95%E3%80%8D%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1">Q50：AI 客服说的「当前咨询订单」是什么？</h3>
<p>从订单详情/充电中等入口进入 AI 客服时，系统可能带入该笔订单摘要（订单号、站点、金额、电量、状态等）。回答费用类问题应优先依据摘要，不要编造摘要没有的数据。</p>
<h3 id="q51%EF%BC%9A%E5%A6%82%E4%BD%95%E6%9F%A5%E8%AF%A2%E6%9F%90%E7%AC%94%E8%AE%A2%E5%8D%95%E5%85%85%E4%BA%86%E5%A4%9A%E5%B0%91%E7%94%B5%E3%80%81%E8%8A%B1%E4%BA%86%E5%A4%9A%E5%B0%91%E9%92%B1%EF%BC%9F" tabindex="-1">Q51：如何查询某笔订单充了多少电、花了多少钱？</h3>
<p>引导查看订单详情，或依据【当前咨询订单】中的充电电量、电费、服务费、总金额、实付金额等字段说明。</p>
<hr>
<h2 id="%E5%8D%81%E4%BA%8C%E3%80%81%E8%BD%AC%E4%BA%BA%E5%B7%A5%E4%B8%8E-ai-%E5%AE%A2%E6%9C%8D%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E" tabindex="-1">十二、转人工与 AI 客服使用说明</h2>
<h3 id="q52%EF%BC%9A%E4%BB%80%E4%B9%88%E6%83%85%E5%86%B5%E5%BB%BA%E8%AE%AE%E8%BD%AC%E4%BA%BA%E5%B7%A5%EF%BC%9F" tabindex="-1">Q52：什么情况建议转人工？</h3>
<p>退款争议、无法结束充电、设备故障、开票信息修改、反复无法解决的问题，或用户明确要求「转人工 / 人工客服」。</p>
<h3 id="q53%EF%BC%9A%E5%AF%B9%E5%AE%A2%E6%9C%8D%E8%AF%B4%E3%80%8C%E8%BD%AC%E4%BA%BA%E5%B7%A5%E3%80%8D%E4%B9%8B%E5%90%8E%E5%81%9A%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1">Q53：对客服说「转人工」之后做什么？</h3>
<p>智能助手会提示转接；请用户点击回复中的「转人工」链接，进入人工客服会话。智能助手本身不是人工坐席，不能假装已接通。</p>
<h3 id="q54%EF%BC%9A%E5%AE%A2%E6%9C%8D%E5%9B%9E%E7%AD%94%E4%B8%8D%E4%BA%86%E6%88%96%E7%AD%94%E9%94%99%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F" tabindex="-1">Q54：客服回答不了或答错了怎么办？</h3>
<p>可引导用户补充订单号/站点名，或说「转人工」、拨打客服热线。</p>
<hr>
<h2 id="%E5%8D%81%E4%B8%89%E3%80%81%E5%9B%9E%E7%AD%94%E8%A7%84%E8%8C%83%EF%BC%88%E7%BB%99-ai%EF%BC%89" tabindex="-1">十三、回答规范（给 AI）</h2>
<ol>
 <li><strong>只依据本文与【当前咨询订单】摘要</strong>，不编造金额、折扣、免费时长、到账时间以外的承诺。</li>
 <li>站点差异大的规则（占位免费时长、单价、封顶、投诉电话、电价时段）→ 引导用户看当前页说明或提供订单号。</li>
 <li>操作类问题给出<strong>路径步骤</strong>（我的 → xxx）。</li>
 <li>用户情绪激动或涉及资金争议 → 简短安抚并引导转人工。</li>
 <li>与充电业务无关的闲聊、违法违规内容 → 礼貌拒绝并引导回业务问题。</li>
</ol>
<hr>
<h2 id="%E9%99%84%E5%BD%95%EF%BC%9A%E5%B8%AE%E5%8A%A9%E4%B8%AD%E5%BF%83%E5%88%86%E7%B1%BB%E5%AF%B9%E7%85%A7" tabindex="-1">附录：帮助中心分类对照</h2>
<table>
 <thead>
  <tr>
   <th>分类名称</th>
   <th>典型问题范围</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>充值与退款</td>
   <td>充值方式、最低额、退款条件与时效</td>
  </tr>
  <tr>
   <td>充电相关</td>
   <td>扫码、启动、结束、费用、余额不足</td>
  </tr>
  <tr>
   <td>发票相关</td>
   <td>开票门槛、主体、邮箱、抵扣不可开</td>
  </tr>
  <tr>
   <td>故障解决</td>
   <td>启动失败、拔枪、结束异常等</td>
  </tr>
 </tbody>
</table>
<hr>
<p><em>文档版本：依据小程序页面规则整理，若产品文案变更请同步更新本知识库。</em></p>]]></description><guid isPermaLink="false">/archives/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AE%A2%E6%9C%8D%E7%9F%A5%E8%AF%86%E5%BA%93</guid><dc:creator>邹知兑</dc:creator><category>蓝特智充小程序常见问题</category><pubDate>Sat, 1 Aug 2026 03:21:03 GMT</pubDate></item><item><title><![CDATA[单车部署文档]]></title><link>https://wiki.dgfygk.com/archives/dan-che-bu-shu-wen-dang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8D%95%E8%BD%A6%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3&amp;url=/archives/dan-che-bu-shu-wen-dang" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E4%B8%80%E3%80%81%E7%8E%AF%E5%A2%83%E8%AF%B4%E6%98%8E"><strong>一、环境说明</strong></h2>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 820px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 720px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">项目</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="720" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">版本 / 要求</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">操作系统</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="720" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">CentOS 7.x</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">宝塔面板</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="720" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">海外版 7.0.20</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">Docker</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="720" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">最新稳定版</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">面板语言</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="720" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">繁体中文</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">端口映射</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="720" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">2181（ZooKeeper）、5672/15672（RabbitMQ）、12375（Docker Remote API）、7999、8000、30002、7998（Java 服务）</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h2 style="" id="%E4%BA%8C%E3%80%81%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%88%9D%E5%A7%8B%E5%8C%96"><strong>二、服务器初始化</strong></h2>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 631px">
  <colgroup>
   <col style="width: 631px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="631" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style=""># 更新系统软件包</p>
     <p style="">yum update -y</p>
     <p style=""># 安装常用工具</p>
     <p style="">yum install -y wget curl vim net-tools</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h2 style="" id="%E4%B8%89%E3%80%81%E5%AE%89%E8%A3%85%E6%B5%B7%E5%A4%96%E7%89%88%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF-7.0.20"><strong>三、安装海外版宝塔面板 7.0.20</strong></h2>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 100px">
  <colgroup>
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style=""># 执行安装命令</p>
     <p style="">wget -O install.sh http://download.bt.cn/install/install-ubuntu.sh &amp;&amp; bash install.sh ed848d</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h3 style="" id="%E5%AE%89%E8%A3%85%E5%90%8E%E6%93%8D%E4%BD%9C"><strong>安装后操作</strong></h3>
<p style="margin-left: 14px!important">1. 记录面板登录地址、用户名和密码（安装完成后会显示）</p>
<p style="margin-left: 14px!important">2. 登录面板后，按照提示完成初始化设置</p>
<h2 style="" id="%E5%9B%9B%E3%80%81%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E9%85%8D%E7%BD%AE"><strong>四、宝塔面板配置</strong></h2>
<h3 style="" id="1.-%E5%88%87%E6%8D%A2%E8%AF%AD%E8%A8%80%E4%B8%BA%E7%B9%81%E4%BD%93%E4%B8%AD%E6%96%87"><strong>1. 切换语言为繁体中文</strong></h3>
<p style="margin-left: 14px!important">• 登录宝塔面板后，点击顶部导航栏的「系统」</p>
<p style="margin-left: 14px!important">• 在下拉菜单中选择「语言」</p>
<p style="margin-left: 14px!important">• 在语言选择列表中点击「繁體中文」，完成切换</p>
<h3 style="" id="2.-%E5%AE%89%E8%A3%85%E5%9F%BA%E7%A1%80%E8%BD%AF%E4%BB%B6"><strong>2. 安装基础软件</strong></h3>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 654px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 454px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">软件</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">安装路径</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="454" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">版本说明</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">MySQL（AliSQL）</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">资料库 → 安装 → 选择 AliSQL</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="454" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">默认版本</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">Nginx</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">网站 → 安装</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="454" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">最新稳定版</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">Redis</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">应用商店 → 搜索「Redis」→ 安装</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="454" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">默认版本</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">Docker</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">应用商店 → 搜索「Docker」→ 安装</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="454" style="width:138.0000pt;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:none;
mso-border-left-alt:none;border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:none;mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);">
     <p style="">最新稳定版</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h3 style="" id="3.-mysql-%E9%85%8D%E7%BD%AE%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C"><strong>3. MySQL 配置与数据库操作</strong></h3>
<p style="margin-left: 14px!important"><strong>1. 创建数据库</strong></p>
<p style="margin-left: 36px!important">◦ 登录宝塔面板，进入「资料库」→ 选择已安装的 MySQL</p>
<p style="margin-left: 36px!important">◦ 点击「数据库」→「添加数据库」</p>
<p style="margin-left: 36px!important">◦ 数据库名填写 ebike，用户名和密码自定义（建议记录下来，用于后续配置），点击「创建」</p>
<p style="margin-left: 14px!important"><strong>1. 导入数据库</strong></p>
<p style="margin-left: 36px!important">◦ 找到刚创建的 ebike&nbsp;数据库，点击「导入」</p>
<p style="margin-left: 36px!important">◦ 选择本地的数据库备份文件（如 .sql&nbsp;格式），点击「开始导入」</p>
<p style="margin-left: 36px!important">◦ 等待导入完成，确认数据库表结构及数据无误</p>
<h2 style="" id="%E4%BA%94%E3%80%81%E9%85%8D%E7%BD%AE-docker-%E8%BF%9C%E7%A8%8B%E8%BF%9E%E6%8E%A5-api"><strong>五、配置 Docker 远程连接 API</strong></h2>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 655px">
  <colgroup>
   <col style="width: 655px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="655" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style=""># 备份原有 Docker 服务配置文件</p>
     <p style="">cp /usr/lib/systemd/system/docker.service /usr/lib/systemd/system/docker.service.bak</p>
     <p style=""># 配置远程连接 API</p>
     <p style="">echo 'ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:12375 -H unix:///var/run/docker.sock &nbsp;-H fd:// --containerd=/run/containerd/containerd.sock' &gt; /usr/lib/systemd/system/docker.service</p>
     <p style=""># 重新加载系统服务并重启 Docker</p>
     <p style="">systemctl daemon-reload</p>
     <p style="">systemctl restart docker</p>
     <p style="">systemctl enable docker</p>
     <p style=""># 开放 12375 端口（宝塔面板和服务器防火墙均需设置）</p>
     <p style=""># 宝塔面板：安全 → 防火墙 → 添加端口规则，端口填写 12375</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h2 style="" id="%E5%85%AD%E3%80%81%E9%83%A8%E7%BD%B2-zookeeper-%E5%AE%B9%E5%99%A8"><strong>六、部署 ZooKeeper 容器</strong></h2>
<h3 style="" id="1.-%E6%8B%89%E5%8F%96-zookeeper-%E9%95%9C%E5%83%8F"><strong>1. 拉取 ZooKeeper 镜像</strong></h3>
<p style="margin-left: 14px!important">• 登录宝塔面板，进入「Docker」模块</p>
<p style="margin-left: 14px!important">• 点击「镜像管理」→「拉取镜像」</p>
<p style="margin-left: 14px!important">• 镜像名称填写 zookeeper，选择最新版本（默认标签 latest），点击「确定」</p>
<h3 style="" id="2.-%E5%88%9B%E5%BB%BA%E5%B9%B6%E8%BF%90%E8%A1%8C-zookeeper-%E5%AE%B9%E5%99%A8"><strong>2. 创建并运行 ZooKeeper 容器</strong></h3>
<p style="margin-left: 14px!important">• 进入「容器列表」→「创建容器」</p>
<p style="margin-left: 14px!important">• 选择已拉取的 ZooKeeper 镜像</p>
<p style="margin-left: 14px!important">• 容器名称：自定义（如 zookeeper）</p>
<p style="margin-left: 14px!important">• 端口映射：添加规则「容器端口 2181 → 主机端口 2181」</p>
<p style="margin-left: 14px!important">• 点击「提交」完成创建</p>
<h2 style="" id="%E4%B8%83%E3%80%81%E9%83%A8%E7%BD%B2-rabbitmq-%E5%AE%B9%E5%99%A8"><strong>七、部署 RabbitMQ 容器</strong></h2>
<h3 style="" id="1.-%E6%8B%89%E5%8F%96-rabbitmq-%E9%95%9C%E5%83%8F"><strong>1. 拉取 RabbitMQ 镜像</strong></h3>
<p style="margin-left: 14px!important">• 进入宝塔面板「Docker」模块 →「镜像管理」→「拉取镜像」</p>
<p style="margin-left: 14px!important">• 镜像名称填写 rabbitmq，标签选择 management（带管理界面版本），点击「确定」</p>
<h3 style="" id="2.-%E5%88%9B%E5%BB%BA%E5%B9%B6%E8%BF%90%E8%A1%8C-rabbitmq-%E5%AE%B9%E5%99%A8"><strong>2. 创建并运行 RabbitMQ 容器</strong></h3>
<p style="margin-left: 14px!important">• 进入「容器列表」→「创建容器」</p>
<p style="margin-left: 14px!important">• 选择已拉取的 RabbitMQ 镜像（带 management&nbsp;标签）</p>
<p style="margin-left: 14px!important">• 容器名称：自定义（如 rabbitmq）</p>
<p style="margin-left: 14px!important">• 端口映射：</p>
<p style="margin-left: 36px!important">◦ 添加规则「容器端口 5672 → 主机端口 5672」</p>
<p style="margin-left: 36px!important">◦ 添加规则「容器端口 15672 → 主机端口 15672」</p>
<p style="margin-left: 14px!important">• 点击「提交」完成创建</p>
<h3 style="" id="3.-%E5%BC%80%E5%90%AF-rabbitmq-%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AE%E6%8F%92%E4%BB%B6"><strong>3. 开启 RabbitMQ 远程访问插件</strong></h3>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 587px">
  <colgroup>
   <col style="width: 587px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="587" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style=""># 进入 RabbitMQ 容器</p>
     <p style="">docker exec -it [容器ID或容器名称] /bin/bash</p>
     <p style=""># 开启远程访问插件</p>
     <p style="">rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_stomp</p>
     <p style=""># 退出容器</p>
     <p style="">exit</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h2 style="" id="%E5%85%AB%E3%80%81%E6%9C%8D%E5%8A%A1%E9%AA%8C%E8%AF%81"><strong>八、服务验证</strong></h2>
<h3 style="" id="1.-%E9%AA%8C%E8%AF%81-docker-%E8%BF%9C%E7%A8%8B-api"><strong>1. 验证 Docker 远程 API</strong></h3>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 288px">
  <colgroup>
   <col style="width: 288px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="288" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style=""># 执行以下命令，返回版本信息即表示配置成功</p>
     <p style="">curl http://localhost:12375/version</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h3 style="" id="2.-%E9%AA%8C%E8%AF%81-zookeeper"><strong>2. 验证 ZooKeeper</strong></h3>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 542px">
  <colgroup>
   <col style="width: 542px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="542" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style=""># 进入 ZooKeeper 容器</p>
     <p style="">docker exec -it [容器ID或容器名称] /bin/bash</p>
     <p style=""># 连接 ZooKeeper 服务</p>
     <p style="">zkCli.sh -server localhost:2181</p>
     <p style=""># 若出现 "Connected to localhost:2181" 提示，则表示运行正常</p>
     <p style=""># 输入 quit 退出连接</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h3 style="" id="3.-%E9%AA%8C%E8%AF%81-rabbitmq"><strong>3. 验证 RabbitMQ</strong></h3>
<p style="margin-left: 14px!important">• 访问管理界面：在浏览器中输入 http://服务器IP:15672</p>
<p style="margin-left: 14px!important">• 使用默认账号密码登录（初始账号密码均为 guest，首次登录建议修改）</p>
<p style="margin-left: 14px!important">• 若能成功登录并看到管理界面，则表示运行正常</p>
<h2 style="" id="%E4%B9%9D%E3%80%81java-%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2"><strong>九、Java 服务部署</strong></h2>
<h3 style="" id="1.-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%BF%AE%E6%94%B9"><strong>1. 配置文件修改</strong></h3>
<p style="margin-left: 14px!important"><strong>1. yml 配置文件调整</strong></p>
<p style="margin-left: 36px!important">◦ 打开项目的 application.yml（或 application-prod.yml&nbsp;等环境配置文件）</p>
<p style="margin-left: 36px!important">◦ 配置 MySQL 连接信息：修改 spring.datasource.url&nbsp;为服务器 IP 及 ebike&nbsp;数据库名，如 jdbc:mysql://服务器IP:3306/ebike?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai</p>
<p style="margin-left: 36px!important">◦ 配置 Redis、ZooKeeper、RabbitMQ 等服务地址，确保与服务器实际部署地址一致</p>
<p style="margin-left: 36px!important">◦ 保存并确认配置文件无误</p>
<p style="margin-left: 14px!important"><strong>1. pom.xml 配置修改</strong></p>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 818px">
  <colgroup>
   <col style="width: 818px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="818" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style="">&lt;configuration&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- 镜像名称 --&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;imageName&gt;ebike-${project.artifactId}-${build.time}&lt;/imageName&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- 指定标签 --&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;imageTags&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;imageTag&gt;latest&lt;/imageTag&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;/imageTags&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- 基础镜像--&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;baseImage&gt;openjdk:8-jdk-alpine&lt;/baseImage&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- 切换到容器工作目录--&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;workdir&gt;/opt/&lt;/workdir&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;entryPoint&gt;["java","-jar","${project.build.finalName}.jar"]&lt;/entryPoint&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- 指定远程 Docker API地址 &nbsp;--&gt;</p>
     <p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;dockerHost&gt;http://你的服务器ip:12375&lt;/dockerHost&gt;</p>
     <p style="">&lt;/configuration&gt;</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="margin-left: 36px!important">◦ 找到 pom.xml&nbsp;中 Docker 相关的 configuration&nbsp;配置，修改为以下内容：</p>
<h3 style="" id="2.-%E5%A4%9A%E6%A8%A1%E5%9D%97%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E9%A1%BA%E5%BA%8F"><strong>2. 多模块项目打包顺序</strong></h3>
<p style="margin-left: 14px!important"><strong>1. 打包顺序说明</strong></p>
<p style="margin-left: 36px!important">◦ 先打包 api&nbsp;模块：该模块为基础接口模块，需优先构建以确保其他依赖模块可正常引用</p>
<p style="margin-left: 36px!important">◦ 再打包 service&nbsp;模块：该模块依赖 api&nbsp;模块，需在 api&nbsp;模块打包完成后进行</p>
<p style="margin-left: 36px!important">◦ 其余模块（如 gateway、interface、admin&nbsp;等）不分先后顺序，可在 service&nbsp;模块打包完成后依次或并行打包</p>
<p style="margin-left: 14px!important"><strong>1. 打包命令示例</strong></p>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 576px">
  <colgroup>
   <col style="width: 576px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="576" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style=""># 进入项目根目录</p>
     <p style="">cd /path/to/project/root</p>
     <p style=""># 打包 api 模块</p>
     <p style="">mvn clean package -pl api -am -Dmaven.test.skip=true</p>
     <p style=""># 打包 service 模块</p>
     <p style="">mvn clean package -pl service -am -Dmaven.test.skip=true</p>
     <p style=""># 打包其余模块（以 gateway 为例，其他模块同理）</p>
     <p style="">mvn clean package -pl gateway -am -Dmaven.test.skip=true</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="margin-left: 36px!important">◦ 说明：-pl&nbsp;指定要构建的模块，-am&nbsp;表示同时构建所选模块的依赖模块，-Dmaven.test.skip=true&nbsp;跳过测试用例</p>
<h3 style="" id="3.-%E6%9E%84%E5%BB%BA%E5%B9%B6%E4%B8%8A%E4%BC%A0%E9%95%9C%E5%83%8F"><strong>3. 构建并上传镜像</strong></h3>
<p style="margin-left: 14px!important">1. 按照上述打包顺序执行构建命令，生成各模块的 Docker 镜像并上传至服务器的 Docker 中</p>
<p style="margin-left: 14px!important">2. 登录服务器，通过宝塔面板的「Docker」→「镜像管理」确认各模块镜像已成功上传</p>
<h3 style="" id="4.-%E5%90%AF%E5%8A%A8-java-%E6%9C%8D%E5%8A%A1%E5%AE%B9%E5%99%A8"><strong>4. 启动 Java 服务容器</strong></h3>
<p style="margin-left: 14px!important"><strong>1. 单车服务启动</strong></p>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 790px">
  <colgroup>
   <col style="width: 790px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="790" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style="">docker run -it --memory 1500m -e TZ=Asia/Shanghai -p 7999:7999 -p 10882:10882 --name ebike-service-20230508_180218 --link naughty_perlman --link fervent_black &nbsp;-v /opt/ebike/logs:/opt/logs/ -d ebike-service-20230508_180218</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="margin-left: 14px!important"><strong>1. 网关服务启动</strong></p>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 777px">
  <colgroup>
   <col style="width: 777px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="777" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style="">docker run -it --memory 1500m -e TZ=Asia/Shanghai -p 8000:8000 -p 22222:22222/udp -p 22223:22223 --name ebike-gateway-20230427_160535 --link naughty_perlman --link fervent_black &nbsp;-v /opt/ebike/logs:/opt/logs/ -d ebike-gateway-20230427_160535</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="margin-left: 14px!important"><strong>1. 接口服务启动</strong></p>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 775px">
  <colgroup>
   <col style="width: 775px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="775" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style="">docker run -e -it --memory 512m -e TZ=Asia/Shanghai -p 30002:30002 --name ebike-interface-20230508_180230 --link naughty_perlman --link fervent_black &nbsp;-v /opt/ebike/logs:/opt/logs/ -d ebike-interface-20230508_180230</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="margin-left: 14px!important"><strong>1. 管理服务启动</strong></p>
<p style="">&nbsp;</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 769px">
  <colgroup>
   <col style="width: 769px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="769" style="width:100.0000%;padding:3.0000pt 6.0000pt 1.5000pt 6.0000pt ;border-left:1.0000pt solid rgb(222,224,227);
mso-border-left-alt:0.0000pt solid rgb(222,224,227);border-right:1.0000pt solid rgb(222,224,227);mso-border-right-alt:0.0000pt solid rgb(222,224,227);
border-top:1.0000pt solid rgb(222,224,227);mso-border-top-alt:0.0000pt solid rgb(222,224,227);border-bottom:1.0000pt solid rgb(222,224,227);
mso-border-bottom-alt:0.0000pt solid rgb(222,224,227);background:rgb(245,246,247);">
     <p style="">docker run -e -it --memory 512m -e TZ=Asia/Shanghai -p 7998:7998 --name ebike-admin-20230508_180235 --link naughty_perlman --link fervent_black &nbsp;-v /opt/ebike/logs:/opt/logs/ -d ebike-admin-20230508_180235</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h3 style="" id="5.-%E6%9C%8D%E5%8A%A1%E5%90%AF%E5%8A%A8%E9%AA%8C%E8%AF%81"><strong>5. 服务启动验证</strong></h3>
<p style="margin-left: 14px!important">• 启动完成后，通过 docker ps&nbsp;命令查看容器运行状态</p>
<p style="margin-left: 14px!important">• 访问各服务端口（如网关服务的 8000 端口），确认服务正常响应</p>
<p style="margin-left: 14px!important">• 查看挂载的日志目录 /opt/ebike/logs，确认无错误日志输出</p>
<h2 style="" id="%E5%8D%81%E3%80%81%E9%83%A8%E7%BD%B2%E7%BD%91%E7%AB%99"><strong>十、部署网站</strong></h2>
<h3 style="" id="1.-%E6%B7%BB%E5%8A%A0%E7%AB%99%E7%82%B9"><strong>1. 添加站点</strong></h3>
<p style="margin-left: 14px!important">• 登录宝塔控制面板，点击左侧导航栏的「网站」。</p>
<p style="margin-left: 14px!important">• 在网站页面中，点击「添加站点」按钮。</p>
<p style="margin-left: 14px!important">• 在弹出的添加站点窗口中，填写域名，其他选项保持默认，点击「确定」。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-HQRI.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="2.-%E9%85%8D%E7%BD%AE%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86"><strong>2. 配置反向代理</strong></h3>
<p style="margin-left: 14px!important">• 站点添加完成后，找到刚创建的站点，点击其对应的「配置」按钮。</p>
<p style="margin-left: 14px!important">• <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-tOml.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 14px!important">• 在配置页面中，点击「反向代理」选项。</p>
<p style="margin-left: 14px!important">• 点击「添加反向代理」按钮，打开反向代理配置窗口。</p>
<p style="margin-left: 14px!important">• 点击「高级功能」，在代理名称处填写自定义名称（例如「admin-proxy」）。</p>
<p style="margin-left: 14px!important">• 在目标 URL 处填写 Docker 运行的服务端口，即 127.0.0.1:7998，点击「确定」。</p>
<p style="margin-left: 14px!important">• <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-XxyM.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="3.-%E8%B0%83%E6%95%B4%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E9%85%8D%E7%BD%AE"><strong>3. 调整反向代理配置</strong></h3>
<p style="margin-left: 14px!important">• 回到反向代理配置页面，找到刚添加的代理规则，点击其对应的「配置」按钮。</p>
<p style="margin-left: 14px!important">• 在配置文件中，找到 http://127.0.0.1:7998;&nbsp;这一行，删除末尾的斜杠，确保内容为 http://127.0.0.1:7998;（无斜杠），保存配置。</p>
<p style=""><strong>小程序后端，配置反向代理http://127.0.0.1:30002,就不需要开高级模式，不需要删除斜杠</strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-DpGJ.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="4.-%E4%B8%8A%E4%BC%A0%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%E4%BB%A3%E7%A0%81"><strong>4. 上传管理后台代码</strong></h3>
<p style="margin-left: 14px!important">• 回到网站列表页面，找到目标站点，点击其对应的「文件夹」图标，进入站点根目录。</p>
<p style="margin-left: 14px!important">• <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-wsRk.png&amp;size=m" width="977px" height="83px" style="display: inline-block"></p>
<p style="margin-left: 14px!important">• 提前在本地配置好管理后台代码的 API 地址，格式为添加站点时配置的域名加上反向代理的代理目录，例如 http://service.ebike.rhinogo.com.cn/api。</p>
<p style="margin-left: 14px!important">• 将配置好的管理后台代码上传到当前文件夹中。</p>
<h3 style="" id="5.-%E8%AE%BF%E9%97%AE%E9%AA%8C%E8%AF%81"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-bnkK.png&amp;size=m" width="100%" height="100%" style="display: inline-block"><strong>5. 访问验证</strong></h3>
<p style="margin-left: 14px!important">• 完成上述操作后，在浏览器中输入域名 https://service.ebike.rhinogo.com.cn，即可访问到管理后台。</p>
<p style="margin-left: 14px!important">• <a href="https://service.ebike.rhinogo.com.cn/api"><span fontsize="" color="rgb(5, 99, 193)" style="color: rgb(5, 99, 193)"><u>https://service.ebike.rhinogo.com.cn/api</u></span></a> ,就是后端api地址</p>]]></description><guid isPermaLink="false">/archives/dan-che-bu-shu-wen-dang</guid><dc:creator>黄健</dc:creator><pubDate>Sat, 12 Jul 2025 09:07:00 GMT</pubDate></item><item><title><![CDATA[搭建glpi（IT资产管理系统）]]></title><link>https://wiki.dgfygk.com/archives/dde38d21-86e9-4a37-976f-08524efd8be7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%90%AD%E5%BB%BAglpi%EF%BC%88IT%E8%B5%84%E4%BA%A7%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F%EF%BC%89&amp;url=/archives/dde38d21-86e9-4a37-976f-08524efd8be7" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#1.%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87">1.环境准备</a></p>
<p><a href="https://wiki.dgfygk.com#2.%E5%AE%89%E8%A3%85%E7%8E%AF%E5%A2%83%E4%BE%9D%E8%B5%96">2.安装环境依赖</a></p>
<p><a href="https://wiki.dgfygk.com#3.%E5%AE%89%E8%A3%85mariadb">3.安装mariadb</a></p>
<p><a href="https://wiki.dgfygk.com#4.%E5%AE%89%E8%A3%85httpd">4.安装httpd</a></p>
<p><a href="https://wiki.dgfygk.com#5.%E6%BA%90%E7%A0%81%EF%BC%9Ahttps%3A%2F%2Fgithub.com%2Fglpi-project%2Fglpi%2Freleases">5.源码：glpi-project</a></p>
<p><a href="https://wiki.dgfygk.com#6.%E4%BF%AE%E6%94%B9%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E6%97%B6%E9%97%B4%EF%BC%9A">6.修改服务器的时间：</a></p>
<p><a href="https://wiki.dgfygk.com#7.%E5%AE%89%E8%A3%85fusioninventory%E6%8F%92%E4%BB%B6%EF%BC%88fusioninventory%EF%BC%89">7.安装fusioninventory插件（fusioninventory）</a></p>
<p><a href="https://wiki.dgfygk.com#8.Client%EF%BC%88Windows%20agent%EF%BC%89%EF%BC%9Afusioninventory-agent_windows-x64_2.6.exe">8.Client（Windows agent）：fusioninventory-agent_windows-x64_2.6.exe</a></p>
<p><a href="https://wiki.dgfygk.com#9.Client%EF%BC%88Linux%C2%A0agent%EF%BC%89%EF%BC%9A">9.Client（Linux agent）</a></p>
<hr>
<table>
 <thead>
  <tr>
   <th>IP</th>
   <th>主机名</th>
   <th>系统</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>172.25.250.103</td>
   <td>服务器</td>
   <td>Linux</td>
  </tr>
  <tr>
   <td>172.25.250.1</td>
   <td>Client01</td>
   <td>Windows</td>
  </tr>
  <tr>
   <td>172.25.250.101</td>
   <td>Client02</td>
   <td>Linux</td>
  </tr>
 </tbody>
</table>
<br>
<h3 id="1环境准备">1.环境准备</h3>
<p>系统环境：centos7
 <br>
 mariadb：10.5.18
 <br>
 php：7.4.33
 <br>
 apache：2.4.6</p>
<pre><code class="language-bash">#关闭selinux
setenforce 0
#关闭防火墙
systemctl stop firewalld
#拉取cento7的阿里云网络源
curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo 
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo 
sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo 
yum install epel-release -y 
rpm -Uvh http://rpms.remirepo.net/enterprise/remi-release-7.rpm
</code></pre>
<h3 id="2安装环境依赖"><strong>2.安装环境依赖</strong></h3>
<pre><code class="language-bash">yum -y install --enablerepo=remi --enablerepo=remi-php74 php php-opcache php-devel php-mbstring php-mcrypt php-mysqlnd php-pecl-xdebug php-pecl-xhprof php-imap php-gd php-ldap php-intl php-simplexml php-zip php-apcu php-xmlrpc php-pear-CAS php-zip
yum -y install gcc gcc-c++ flex bison autoconf automake bzip2-devel zlib-devel ncurses-devel libjpeg-devel libpng-devel libtiff-devel freetype-devel pam-devel openssl-devel libxml2-devel gettext-devel pcre-devel
</code></pre>
<h3 id="3安装mariadb">3.安装mariadb</h3>
<pre><code class="language-bash">#配置mariadb的yum源
cat &gt;&gt; /etc/yum.repos.d/mariadb.repo &lt;&lt; EOF
[mariadb]
name = MariaDB
baseurl = https://mirrors.aliyun.com/mariadb/yum/10.5/centos7-amd64
gpgkey = https://mirrors.aliyun.com/mariadb/yum/RPM-GPG-KEY-MariaDB
gpgcheck = 1
EOF
</code></pre>
<pre><code class="language-bash">yum makecache
yum -y install MariaDB-server MariaDB-client
systemctl start mariadb
</code></pre>
<h4 id="配置mariadb">配置mariadb</h4>
<pre><code class="language-bash">#初始化数据库
mysql_secure_installation 
#登录数据库
mysql -uroot -p
use mysql
#创建数据库用户，并给予远程数据库的权限
create user'glpi'@'%' IDENTIFIED BY 'redhat';
grant all privileges ON *.* TO 'glpi'@'%' IDENTIFIED BY 'redhat';
create database glpi;
flush privileges;
</code></pre>
<h3 id="4安装httpd">4.安装httpd</h3>
<pre><code class="language-bash">yum install -y httpd httpd-devel
systemctl start  httpd
systemctl enable httpd
</code></pre>
<h3 id="5源码glpi-project">5.源码：<a href="https://github.com/glpi-project/glpi/releases" title="glpi-project">glpi-project</a></h3>
<pre><code class="language-bash">#下载源码：https://github.com/glpi-project/glpi/releases
wget https://github.com/glpi-project/glpi/releases/download/10.0.1/glpi-10.0.1.tgz
tar -zxf glpi-10.0.1.tgz 
cp glpi/* /var/www/html/. -R
chown -R apache:apache /var/www/html
chmod 755 -R /var/www/html/config/
chmod 755 -R /var/www/html/files/
</code></pre>
<p><strong>网页访问：<a href="http://%E6%9C%8D%E5%8A%A1%E5%99%A8IP/glpi/">http://服务器IP/glpi/</a></strong></p>
<pre><code class="language-bash">http://服务器IP/glpi/
</code></pre>
<h3 id="6修改服务器的时间">6.修改服务器的时间：</h3>
<pre><code class="language-bash">vim /etc/php.ini
    date.timezone = Asia/Shanghai
#重启httpd服务
systemctl restart httpd
</code></pre>
<h3 id="7安装fusioninventory插件fusioninventory">7.安装fusioninventory插件（<a href="https://github.com/fusioninventory/fusioninventory-for-glpi/releases" title="fusioninventory">fusioninventory</a>）</h3>
<pre><code class="language-bash">cd /var/www/html/plugins/
wget https://github.com/fusioninventory/fusioninventory-for-glpi/releases/download/glpi10.0.1%2B1.0/fusioninventory-10.0.1+1.0.zip
yum install -y unzip
unzip fusioninventory10.0.1+1.0.zip

</code></pre>
<p>网页打开安装并启用
 <br>
 <a href="http://%E6%9C%8D%E5%8A%A1%E5%99%A8IP/front/marketplace.php">http://服务器IP/front/marketplace.php</a></p>
<pre><code class="language-bash">http://服务器IP/front/marketplace.php
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1745045917876.png&amp;size=m" alt=""></p>
<br>
<h3 id="8clientwindows-agent">8.Client（Windows agent）：</h3>
<p><a href="https://github.com/fusioninventory/fusioninventory-agent/releases/download/2.6/fusioninventory-agent_windows-x64_2.6.exe" title="fusioninventory-agent_windows-x64_2.6.exe">fusioninventory-agent_windows-x64_2.6.exe</a></p>
<p>编写bat自动安装的脚本（与fusioninventory-agent_windows-x64_2.6.exe同一目录下）：</p>
<pre><code class="language-bash">:x64
echo "_____________________________________________"
echo "install fusioninventory-agent_windows-x64_2.6.exe"
%~dp0\fusioninventory-agent_windows-x64_2.6.exe /acceptlicense /add-firewall-exception /execmode=Service /no-start-menu /runnow /server='http://172.25.250.103/plugins/fusioninventory/' /installtasks=Deploy,ESX,Inventory,NetDiscovery,NetInventory,WakeOnLan /S
echo "install session"
ping -n 3 127.0.0.1&gt;nul
echo "send computer information ti GLPI Server"
call "D:\迅雷下载\fusioninventory-agent.bat"
echo "_____________________________________________"
ping -n 3 127.0.0.1&gt;nul
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1745045918175.png&amp;size=m" alt=""></p>
<p>右键管理员运行bat文件</p>
<p>网页查看：<a href="http://127.0.0.1:62354">http://127.0.0.1:62354</a></p>
<pre><code class="language-bash">http://127.0.0.1:62354
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1745045918431.png&amp;size=m" alt=""></p>
<br>
<p>生成的内容返回到服务器上</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1745045918715.png&amp;size=m" alt=""></p>
<h3 id="9clientlinux-agent">9.Client（Linux agent）：</h3>
<pre><code class="language-bash">yum install -y fusioninventory-agent fusioninventory-agent-task-inventory
sed -i "s@#server = http://server.domain.com/glpi/plugins/fusioninventory/@server = http://服务器IP/plugins/fusioninventory/@g" /etc/fusioninventory/agent.cfg
#开启服务并设置开机自启
systemctl start fusioninventory-agent
systemctl enable fusioninventory-agent
#防火墙放行62354端口
firewall-cmd --permanent --zone=public --add-port=62354/tcp
firewall-cmd --reload
#后台运行
fusioninventory-agent --server http://服务器IP/plugins/fusioninventory/ --daemon
</code></pre>
<br>
<p>原文链接: <a href="https://blog.csdn.net/vincefans/article/details/128419779">https://blog.csdn.net/vincefans/article/details/128419779</a></p>]]></description><guid isPermaLink="false">/archives/dde38d21-86e9-4a37-976f-08524efd8be7</guid><dc:creator>黄健</dc:creator><category>网络运维</category><pubDate>Sat, 19 Apr 2025 07:01:42 GMT</pubDate></item><item><title><![CDATA[修改注册表停用 Windows 资讯和兴趣服务教程]]></title><link>https://wiki.dgfygk.com/archives/xiu-gai-zhu-ce-biao-ting-yong-windows-zi-xun-he-xing-qu-fu-wu-jiao-cheng</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8%E5%81%9C%E7%94%A8%20Windows%20%E8%B5%84%E8%AE%AF%E5%92%8C%E5%85%B4%E8%B6%A3%E6%9C%8D%E5%8A%A1%E6%95%99%E7%A8%8B&amp;url=/archives/xiu-gai-zhu-ce-biao-ting-yong-windows-zi-xun-he-xing-qu-fu-wu-jiao-cheng" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8%E5%81%9C%E7%94%A8-windows-%E8%B5%84%E8%AE%AF%E5%92%8C%E5%85%B4%E8%B6%A3%E6%9C%8D%E5%8A%A1%E6%95%99%E7%A8%8B"><strong>修改注册表停用 Windows 资讯和兴趣服务教程</strong></h1>
<h2 style="" id="%E4%B8%80%E3%80%81%E8%87%AA%E5%8A%A8%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8%EF%BC%88%E9%80%82%E5%90%88%E4%B8%8D%E6%83%B3%E6%89%8B%E5%8A%A8%E6%93%8D%E4%BD%9C%E7%9A%84%E7%94%A8%E6%88%B7%EF%BC%89"><strong>一、自动修改注册表（适合不想手动操作的用户）</strong></h2>
<p style=""></p>
<ol>
 <li>
  <p style="">下载已修改好的注册表文件：</p>
  <ul>
   <li>
    <p style="">下载链接：<a href="http://ct.dujin.org/f/5210373-501511740-a7c079" target="_blank" class="link-ZNPgAX">http://ct.dujin.org/f/5210373-501511740-a7c079</a></p>
   </li>
   <li>
    <p style="">文件名称：注册表禁用 Windows 10 资讯和兴趣服务.zip（无解压密码，解压后直接运行即可）。</p>
   </li>
  </ul>
 </li>
</ol>
<h2 style="" id="%E4%BA%8C%E3%80%81%E6%89%8B%E5%8A%A8%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8%EF%BC%88%E9%80%82%E5%90%88%E5%B8%8C%E6%9C%9B%E8%87%AA%E4%B8%BB%E6%93%8D%E4%BD%9C%E7%9A%84%E7%94%A8%E6%88%B7%EF%BC%89"><strong>二、手动修改注册表（适合希望自主操作的用户）</strong></h2>
<h3 style="" id="%E6%AD%A5%E9%AA%A4-1%EF%BC%9A%E6%89%93%E5%BC%80%E6%B3%A8%E5%86%8C%E8%A1%A8%E7%BC%96%E8%BE%91%E5%99%A8"><strong>步骤 1：打开注册表编辑器</strong></h3>
<p style="">
 <br>
</p>
<ol>
 <li>
  <p style="">按下 <code>Win+R</code> 组合键，打开 “运行” 窗口。</p>
 </li>
 <li>
  <p style="">输入 <code>regedit</code>，点击 “确定” 打开注册表编辑器。</p>
 </li>
</ol>
<h3 style="" id="%E6%AD%A5%E9%AA%A4-2%EF%BC%9A%E4%BF%AE%E6%94%B9%E7%B3%BB%E7%BB%9F%E6%B3%A8%E5%86%8C%E8%A1%A8%E9%A1%B9"><strong>步骤 2：修改系统注册表项</strong></h3>
<p style="">
 <br>
</p>
<ol>
 <li>
  <p style="">定位路径：
   <br>
   <code>HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows</code></p>
 </li>
 <li>
  <p style="">在右侧空白处右键，选择【新建】→【项】，命名为 <code>Windows Feeds</code>。</p>
 </li>
 <li>
  <p style="">选中 <code>Windows Feeds</code> 项，在右侧空白处右键，选择【新建】→【DWORD（32 位）值】，命名为 <code>EnableFeeds</code>。</p>
 </li>
 <li>
  <p style="">双击 <code>EnableFeeds</code>，将 “数值数据” 设置为 <code>0</code>，点击 “确定” 保存。</p>
 </li>
</ol>
<h3 style="" id="%E6%AD%A5%E9%AA%A4-3%EF%BC%9A%E4%BF%AE%E6%94%B9%E7%94%A8%E6%88%B7%E6%B3%A8%E5%86%8C%E8%A1%A8%E9%A1%B9"><strong>步骤 3：修改用户注册表项</strong></h3>
<p style="">
 <br>
</p>
<ol>
 <li>
  <p style="">定位路径：
   <br>
   <code>HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Feeds</code></p>
 </li>
 <li>
  <p style="">在右侧窗口找到 <code>ShellFeedsTaskbarViewMode</code> 项（若没有则新建 DWORD 值并命名）。</p>
 </li>
 <li>
  <p style="">双击 <code>ShellFeedsTaskbarViewMode</code>，将 “数值数据” 设置为 <code>2</code>，点击 “确定” 保存。</p>
 </li>
</ol>
<h3 style="" id="%E6%AD%A5%E9%AA%A4-4%EF%BC%9A%E5%AE%8C%E6%88%90%E8%AE%BE%E7%BD%AE"><strong>步骤 4：完成设置</strong></h3>
<p style="">关闭注册表编辑器，重启电脑，即可彻底禁用<span fontsize="" color="initial" style="color: initial">微软</span>推送的 “资讯和兴趣” 服务。</p>
<p style="">
 <br>
</p>]]></description><guid isPermaLink="false">/archives/xiu-gai-zhu-ce-biao-ting-yong-windows-zi-xun-he-xing-qu-fu-wu-jiao-cheng</guid><dc:creator>Administrator</dc:creator><category>IT运维</category><category>网络运维</category><category>计算机基础</category><pubDate>Fri, 14 Mar 2025 10:02:51 GMT</pubDate></item><item><title><![CDATA[shell脚本一键部署Zabbix的实现步骤]]></title><link>https://wiki.dgfygk.com/archives/091336af-367f-4b50-8241-6593c874e056</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=shell%E8%84%9A%E6%9C%AC%E4%B8%80%E9%94%AE%E9%83%A8%E7%BD%B2Zabbix%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%AD%A5%E9%AA%A4&amp;url=/archives/091336af-367f-4b50-8241-6593c874e056" width="1" height="1" alt="" style="opacity:0;">
<p>本文主要介绍了shell一键部署Zabbix的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧！</p>
<ul>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li>
  <h1>!/bin/sh#zabbix install ip=<code>ip addr |grep inet |egrep -v "inet6|127.0.0.1" |awk '{print $2}' |awk -F "/" '{print $1}'</code>release=<code>cat /etc/redhat-release | awk '{print $4}' | awk -F '.' '{print $1}'</code>printf “######################################################################## 欢迎使用Zabbix离线一键部署脚本 ## 脚本适配环境CentOS7/8、Zabbix5.0/5.2 ## 避免软件包产生冲突建议使用纯净的操作系统进行安装！########################################################################“echo “#######################################################################“echo “# #“echo “# 正在关闭SElinux策略及防火墙 请稍等~ #“echo “# #“echo “#######################################################################“sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/configsetenforce 0systemctl stop firewalld &amp;&amp; systemctl disable firewalldecho “#######################################################################“echo “# #“echo “# 正在判断系统环境并安装相对应的yum源和软件 请稍等~ #“echo “# (如果是CentOS 8 就安装5.2，如果是7则安装5.0) #“echo “# #“echo “#######################################################################“if [ $release -eq '7' ];thencat «EOF &gt; /etc/yum.repos.d/zabbix.repo[zabbix]name=Zabbix Official Repository - \$basearchbaseurl=https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/\$basearch/enabled=1gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591</h1>
  [zabbix-frontend]name=Zabbix Official Repository frontend - $basearchbaseurl=https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/\$basearch/frontendenabled=1gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591
  <br>
  [zabbix-non-supported]name=Zabbix Official Repository non-supported - \$basearchbaseurl=https://mirrors.aliyun.com/zabbix/non-supported/rhel/7/\$basearch/enabled=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIXgpgcheck=1EOFecho “添加源gpgkey"curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX-A14FE591 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIXcurl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX-A14FE591 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIXyum -y install mariadb-server centos-release-scl epel-releaseyum -y install zabbix-server-mysql zabbix-web-mysql zabbix-web-mysql-scl zabbix-apache-conf-scl zabbix-agent vim elif [ $release -eq '8' ];thenecho “检测您的机器为CentOS 8，可以安装Zabbix 5.2"cat «EOF &gt; /etc/yum.repos.d/zabbix.repo[zabbix]name=Zabbix Official Repository - \$basearchbaseurl=https://mirrors.aliyun.com/zabbix/zabbix/5.2/rhel/8/\$basearch/enabled=1gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591[zabbix-non-supported]name=Zabbix Official Repository non-supported - \$basearchbaseurl=https://mirrors.aliyun.com/zabbix/non-supported/rhel/8/\$basearch/enabled=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIXgpgcheck=1EOFecho “添加源gpgkey"curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX-A14FE591 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIXcurl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX-A14FE591 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIXdnf -y install mariadb-server zabbix-server-mysql zabbix-web-mysql zabbix-apache-conf zabbix-agent vimfiecho “#######################################################################“echo “# #“echo “# 正在启动及配置Mariadb数据库 请稍等~ #“echo “# #“echo “#######################################################################“service mariadb start &amp;&amp; service mariadb enablewhile :; do echo read -p “设置Mysql数据库root密码（建议使用字母+数字）: " Database_Password [ -n “$Database_Password” ] &amp;&amp; breakdone
  <br>
  mysqladmin -u root password “$Database_Password"echo “create database zabbix default charset utf8 COLLATE utf8_bin;” | mysql -uroot -p$Database_Passwordecho “grant all privileges on zabbix.<em> to zabbix@'localhost' identified by '$Database_Password';” | mysql -uroot -p$Database_Passwordecho “flush privileges;” | mysql -uroot -p$Database_Password 
   <br>echo “#######################################################################“echo “# #“echo “# 正在导入zabbix数据库架构文件，请稍等~ #“echo “# #“echo “#######################################################################“zcat /usr/share/doc/zabbix-server-mysql</em>/create.sql.gz | mysql -uzabbix -p$Database_Password zabbix
  <br>
  echo “#######################################################################“echo “# #“echo “# 正在修改Zabbix配置文件，请稍等~ #“echo “# #“echo “#######################################################################“sed -i 's/# DBPassword=/DBPassword=$Database_Password/' /etc/zabbix/zabbix_server.confif [ $release -eq '7' ];thenecho “php_value[date.timezone] = Asia/Shanghai” » /etc/opt/rh/rh-php72/php-fpm.d/zabbix.confsystemctl restart zabbix-server zabbix-agent httpd rh-php72-php-fpmsystemctl enable zabbix-server zabbix-agent httpd rh-php72-php-fpmelif [ $release -eq '8' ];thensystemctl restart zabbix-server zabbix-agent httpd php-fpmsystemctl enable zabbix-server zabbix-agent httpd php-fpmfi
  <br>
  echo “#######################################################################“echo “# 安装已经完成 请移步浏览器，进行下一步操作 #“echo “# 登录地址为http://$ip/zabbix #“echo “# 数据库密码为$Database_Password，尽情享用吧！#“echo “#######################################################################”
 </li>
</ul>
<p>使用方法:</p>
<ul>
 <li></li>
 <li></li>
 <li>
  <p>chmod +x install_zabbix.sh
   <br>
   ./install_zabbix.sh</p>
 </li>
</ul>
<p>以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们下次再见。</p>
<p>原文链接: https://mp.weixin.qq.com/s/mrZy6e7YFSa_pklBrsbE8w</p>]]></description><guid isPermaLink="false">/archives/091336af-367f-4b50-8241-6593c874e056</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 02:00:44 GMT</pubDate></item><item><title><![CDATA[docker快速安装zabbix7.0]]></title><link>https://wiki.dgfygk.com/archives/04d21b3a-a9d3-4603-b4c7-f1634d971cea</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=docker%E5%BF%AB%E9%80%9F%E5%AE%89%E8%A3%85zabbix7.0&amp;url=/archives/04d21b3a-a9d3-4603-b4c7-f1634d971cea" width="1" height="1" alt="" style="opacity:0;">
<p>1、安装docker</p>
<p>(1)卸载旧版本</p>
<p>sudo apt-get remove docker docker-engine docker.io containerd runc</p>
<p>
 <br>
</p>
<p>(2)更新apt包索引并安装包以允许apt在HTTPS上使用存储库</p>
<p>sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common</p>
<p>
 <br>
</p>
<p>(3)添加Docker官方GPG密钥 # -fsSL</p>
<p>apt update</p>
<p>apt-get install ca-certificates curl gnupg lsb-release</p>
<p>
 <br>
</p>
<p>(4)写入docker镜像源信息</p>
<p>sudo add-apt-repository “deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable”</p>
<p>
 <br>
</p>
<p>(5)安装特定版本的Docker引擎,请在repo中列出可用的版本</p>
<p>sudo apt-get update \&amp;\&amp; sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</p>
<p>
 <br>
</p>
<p>(6)修改docker启动项</p>
<p>sudo mkdir -p /etc/docker</p>
<p>sudo tee /etc/docker/daemon.json &lt;&lt;-'EOF'</p>
<p>{</p>
<p>“registry-mirrors”: [</p>
<p>“https://docker.credclouds.com”,</p>
<p>“https://k8s.credclouds.com”,</p>
<p>“https://quay.credclouds.com”,</p>
<p>“https://gcr.credclouds.com”,</p>
<p>“https://k8s-gcr.credclouds.com”,</p>
<p>“https://ghcr.credclouds.com”,</p>
<p>“https://do.nark.eu.org”,</p>
<p>“https://docker.m.daocloud.io”,</p>
<p>“https://docker.nju.edu.cn”,</p>
<p>“https://docker.mirrors.sjtug.sjtu.edu.cn”</p>
<p>]</p>
<p>}</p>
<p>EOF</p>
<p>
 <br>
</p>
<p>(7)保存退出,更新设置</p>
<p>sudo systemctl daemon-reload</p>
<p>sudo systemctl start docker</p>
<p>sudo systemctl enable docker</p>
<p>
 <br>
</p>
<p>2、安装mysql</p>
<p>sudo docker run –name mysql-server -t \</p>
<p>-e MYSQL_DATABASE=“zabbix” \</p>
<p>-e MYSQL_USER=“zabbix” \</p>
<p>-e MYSQL_PASSWORD=“123.com” \</p>
<p>-e MYSQL_ROOT_PASSWORD=“123.com” \</p>
<p>-p 3306:3306 \</p>
<p>-d mysql:5.7</p>
<p>
 <br>
</p>
<p>3、安装zabbix_server</p>
<p>sudo docker run –name zabbix-server-mysql -t \</p>
<p>-e DB_SERVER_HOST=“mysql-server” \</p>
<p>-e MYSQL_DATABASE=“zabbix” \</p>
<p>-e MYSQL_USER=“zabbix” \</p>
<p>-e MYSQL_PASSWORD=“123.com” \</p>
<p>-e MYSQL_ROOT_PASSWORD=“123.com” \</p>
<p>–link mysql-server:mysql \</p>
<p>-d zabbix/zabbix-server-mysql:latest</p>
<p>
 <br>
</p>
<p>4、zabbix前端</p>
<p>sudo docker run –name zabbix-web-nginx-mysql -t \</p>
<p>-e DB_SERVER_HOST=“mysql-server” \</p>
<p>-e MYSQL_DATABASE=“zabbix” \</p>
<p>-e MYSQL_USER=“zabbix” \</p>
<p>-e MYSQL_PASSWORD=“123.com” \</p>
<p>–link zabbix-server-mysql:zabbix-server \</p>
<p>–link mysql-server:mysql \</p>
<p>-p 80:80 \</p>
<p>-d zabbix/zabbix-web-nginx-mysql:latest</p>
<p>
 <br>
</p>
<p>5、zabbix agentd</p>
<p>sudo docker run –name zabbix-agent -t \</p>
<p>-e ZBX_SERVER_HOST=“zabbix-server-mysql” \</p>
<p>-p 10050:10050 \</p>
<p>-d zabbix/zabbix-agent:latest</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312893413.png&amp;size=m" alt=""></p>
<p>以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们下次再见。</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/XiyNqAKCKf7yJUMOjXkIKw</p>]]></description><guid isPermaLink="false">/archives/04d21b3a-a9d3-4603-b4c7-f1634d971cea</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 02:00:33 GMT</pubDate></item><item><title><![CDATA[轻松搞定Zabbix监控部署，打造高效运维利器！]]></title><link>https://wiki.dgfygk.com/archives/8d353d0d-6875-416d-99e3-977388d32817</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%BD%BB%E6%9D%BE%E6%90%9E%E5%AE%9AZabbix%E7%9B%91%E6%8E%A7%E9%83%A8%E7%BD%B2%EF%BC%8C%E6%89%93%E9%80%A0%E9%AB%98%E6%95%88%E8%BF%90%E7%BB%B4%E5%88%A9%E5%99%A8%EF%BC%81&amp;url=/archives/8d353d0d-6875-416d-99e3-977388d32817" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946449.png&amp;size=m" alt=""></p>
<p>安装环境：</p>
<p>系统：centos7.5</p>
<p>nginx版本：nginx-1.15.3.tar.gz</p>
<p>php版本：php-5.6.36.tar.gz</p>
<p>mysql版本：mysql-5.7</p>
<p>zabbix版本：zabbix-4.0.0.tar</p>
<h1>1、部署Nginx</h1>
<h2>1.1、安装依赖包</h2>
<p>wget http://nginx.org/download/nginx-1.15.3.tar.gz</p>
<p>yum install gcc pcre-devel openssl-devel -y</p>
<p>useradd -M -s /sbin/nologin nginx</p>
<h2>1.2、安装nginx</h2>
<p>tar -xzf nginx-1.15.3.tar.gz</p>
<p>cd nginx-1.15.3</p>
<p>./configure –prefix=/usr/local/nginx –user=nginx –group=nginx –with-http_ssl_module –with-http_stub_status_module</p>
<p>make \&amp;\&amp; make install</p>
<p>chown nobody -R /usr/local/nginx/</p>
<h1>vi /usr/local/nginx/conf/nginx.conf</h1>
<p>pid /var/run/nginx.pid;</p>
<h1>vi /usr/lib/systemd/system/nginx.service</h1>
<p>[Unit]</p>
<p>Description=The NGINX HTTP and reverse proxy server</p>
<p>After=syslog.target network.target remote-fs.target nss-lookup.target</p>
<p>[Service]</p>
<p>Type=forking</p>
<p>PIDFile=/var/run/nginx.pid</p>
<p>ExecStartPre=/usr/local/nginx/sbin/nginx -t</p>
<p>ExecStart=/usr/local/nginx/sbin/nginx</p>
<p>ExecReload=/usr/local/nginx/sbin/nginx -s reload</p>
<p>ExecStop=/bin/kill -s QUIT $MAINPID</p>
<p>PrivateTmp=true</p>
<p>[Install]</p>
<p>WantedBy=multi-user.target</p>
<h1>2、部署mysql</h1>
<h2>2.1、安装</h2>
<p>yum -y install yum-utils</p>
<p>配置yum源：</p>
<p>cat /etc/yum.repos.d/mysql.repo</p>
<h1>Enable to use MySQL 5.7</h1>
<p>[mysql57-community]</p>
<p>name=MySQL 5.7 Community Server</p>
<p>baseurl=http://repo.mysql.com/yum/mysql-5.7-community/el/7/$basearch/</p>
<p>enabled=1</p>
<p>gpgcheck=0</p>
<p>gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql</p>
<p>查看可安装的mysql版本:</p>
<p>yum repolist all\|grep mysql</p>
<p>安装MySQL，默认最新版本：</p>
<h1>yum install mysql-community-server -y</h1>
<p>启动MySQL服务：</p>
<h1>systemctl start mysqld</h1>
<h1>systemctl status mysqld</h1>
<p>root账户默认密码存储在错误日志中：</p>
<h1>grep 'temporary password' /var/log/mysqld.log</h1>
<h1>mysql -uroot -p</h1>
<p>mysql&gt; ALTER USER 'root'@'localhost' IDENTIFIED BY 'Zabbix2018!';</p>
<p>注意：密码要求包含一个大写字母，一个小写字母，一位数字和一个特殊字符，并且密码长度至少为8个字符。</p>
<h2>2.2、MySQL服务器配置</h2>
<p>vi /etc/my.cnf</p>
<p>[mysql]</p>
<p>socket = /var/lib/mysql/mysql.sock</p>
<p>[mysqld]</p>
<p>user = mysql</p>
<p>port = 3306</p>
<p>datadir = /var/lib/mysql</p>
<p>socket = /var/lib/mysql/mysql.sock</p>
<p>bind-address = 0.0.0.0</p>
<p>pid-file = /var/run/mysqld/mysqld.pid</p>
<p>character-set-server = utf8</p>
<p>collation-server = utf8_general_ci</p>
<p>log-error = /var/log/mysqld.log</p>
<p>max_connections = 10240</p>
<p>open_files_limit = 65535</p>
<p>innodb_buffer_pool_size = 1G</p>
<p>innodb_flush_log_at_trx_commit = 2</p>
<p>innodb_log_file_size = 256M</p>
<h1>systemctl restart mysqld</h1>
<h1>systemctl enable mysqld</h1>
<h1>mysql -uroot -p</h1>
<p>mysql&gt; create database zabbix;</p>
<p>mysql&gt; grant all on zabbix.* to zabbix@'192.168.0.%' identified by 'Zabbix2018!';</p>
<h1>3、部署php</h1>
<h2>3.1、安装依赖包</h2>
<p>yum install -y gcc gcc-c++ make gd-devel libxml2-devel \</p>
<p>libcurl-devel libjpeg-devel libpng-devel openssl-devel \</p>
<p>libxslt-devel</p>
<h2>3.2、安装PHP</h2>
<h1>wget http://docs.php.net/distributions/php-5.6.36.tar.gz</h1>
<h1>tar zxf php-5.6.36.tar.gz</h1>
<h1>cd php-5.6.36</h1>
<h1>./configure –prefix=/usr/local/php \</h1>
<p>–with-config-file-path=/usr/local/php/etc \</p>
<p>–enable-fpm –enable-opcache \</p>
<p>–with-mysql –with-mysqli \</p>
<p>–enable-session –with-zlib –with-curl –with-gd \</p>
<p>–with-jpeg-dir –with-png-dir –with-freetype-dir \</p>
<p>–enable-mbstring –enable-xmlwriter –enable-xmlreader \</p>
<p>–enable-xml –enable-sockets –enable-bcmath –with-gettext</p>
<h1>make -j 8 \&amp;\&amp; make install</h1>
<h1>cp php.ini-production /usr/local/php/etc/php.ini</h1>
<h1>cp sapi/fpm/php-fpm.conf /usr/local/php/etc/php-fpm.conf</h1>
<h1>cp sapi/fpm/php-fpm.service /usr/lib/systemd/system/</h1>
<h1>vi /usr/lib/systemd/system/php-fpm.service</h1>
<p>[Unit]</p>
<p>Description=The PHP FastCGI Process Manager</p>
<p>After=syslog.target network.target</p>
<p>[Service]</p>
<p>Type=simple</p>
<p>PIDFile=/usr/local/php/var/run/php-fpm.pid</p>
<p>ExecStart=/usr/local/php/sbin/php-fpm –nodaemonize –fpm-config /usr/local/php/etc/php-fpm.conf</p>
<p>ExecReload=/bin/kill -USR2 $MAINPID</p>
<p>[Install]</p>
<p>WantedBy=multi-user.target</p>
<h1>systemctl daemon-reload</h1>
<h1>systemctl start php-fpm</h1>
<h1>systemctl enable php-fpm</h1>
<h1>4、部署Zabbix Server</h1>
<p>https://www.zabbix.com/download_sources</p>
<h2>4.1、安装依赖包:</h2>
<h1>yum install libxml2-devel libcurl-devel libevent-devel net-snmp-devel mysql-community-devel -y</h1>
<h1>yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel -y</h1>
<h2>4.2、安装zabbix server</h2>
<h1>tar -zxf zabbix-4.0.0.tar.gz</h1>
<h1>groupadd zabbix</h1>
<h1>useradd -g zabbix zabbix -s /sbin/nologin</h1>
<h1>cd zabbix-4.0.0</h1>
<h1>./configure –prefix=/usr/local/zabbix –enable-server –enable-agent –enable-java –with-mysql –enable-ipv6 –with-net-snmp –with-libcurl –with-libxml2</h1>
<h1>make \&amp;\&amp; make install</h1>
<h2>4.3、导入表结构</h2>
<p>mysql -h 192.168.0.13 -uzabbix -pZabbix2018! zabbix &lt; schema.sql</p>
<h1>cd database/mysql</h1>
<h1>mysql -uzabbix -p zabbix &lt; schema.sql</h1>
<h1>mysql -uzabbix -p zabbix &lt; images.sql</h1>
<h1>mysql -uzabbix -p zabbix &lt; data.sql</h1>
<h2>4.4、配置zabbix server</h2>
<h1>vi /usr/local/zabbix/etc/zabbix_server.conf</h1>
<p>DBHost=192.168.0.13</p>
<p>DBName=zabbix</p>
<p>DBUser=zabbix</p>
<p>DBPassword=Zabbix2018!</p>
<h1>vi /usr/lib/systemd/system/zabbix_server.service</h1>
<p>[Unit]</p>
<p>Description=Zabbix Server</p>
<p>After=syslog.target</p>
<p>After=network.target</p>
<p>[Service]</p>
<p>Environment=“CONFFILE=/usr/local/zabbix/etc/zabbix_server.conf”</p>
<p>EnvironmentFile=-/etc/sysconfig/zabbix-server</p>
<p>Type=forking</p>
<p>Restart=on-failure</p>
<p>PIDFile=/tmp/zabbix_server.pid</p>
<p>KillMode=control-group</p>
<p>ExecStart=/usr/local/zabbix/sbin/zabbix_server -c $CONFFILE</p>
<p>ExecStop=/bin/kill -SIGTERM $MAINPID</p>
<p>RestartSec=10s</p>
<p>TimeoutSec=0</p>
<p>[Install]</p>
<p>WantedBy=multi-user.target</p>
<p>启动Agent：</p>
<h1>/usr/local/zabbix/sbin/zabbix_agentd</h1>
<h1>5、部署Zabbix Web界面</h1>
<h3>5.1、修改php配置文件</h3>
<p>Zabbix前端使用PHP写的，所以必须运行在PHP支持的Web服务器上。</p>
<h1>cd zabbix-4.0.0</h1>
<h1>cp frontends/php/* /usr/local/nginx/html/ -rf</h1>
<h1>vi /usr/local/php/etc/php.ini</h1>
<p>max_execution_time = 300</p>
<p>memory_limit = 128M</p>
<p>post_max_size = 16M</p>
<p>upload_max_filesize = 2M</p>
<p>max_input_time = 300</p>
<p>always_populate_raw_post_data = -1</p>
<p>date.timezone = Asia/Shanghai</p>
<h1>systemctl restart php-fpm</h1>
<h1>systemctl restart zabbix_server</h1>
<h1>ps -ef \|grep zabbix</h1>
<h3>5.2、修改nginx配置文件</h3>
<h1>cd /usr/local/nginx/conf</h1>
<h1>cat nginx.conf</h1>
<p>server {</p>
<p>listen 80;</p>
<p>server_name localhost;</p>
<p>access_log logs/zabbix.access.log main;</p>
<p>location / {</p>
<p>root html;</p>
<p>index index.php index.html index.htm;</p>
<p>}</p>
<p>location \~ \.php$ {</p>
<p>root html;</p>
<p>fastcgi_pass 127.0.0.1:9000;</p>
<p>fastcgi_index index.php;</p>
<p>fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;</p>
<p>include fastcgi_params;</p>
<p>}</p>
<p>}</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946482.png&amp;size=m" alt=""></p>
<h1>/usr/local/nginx/sbin/nginx -t</h1>
<h1>/usr/local/nginx/sbin/nginx</h1>
<h3>5.3、通过web设置zabbix</h3>
<p>访问: http://192.168.0.13</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946537.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946592.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946638.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946722.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946774.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946835.png&amp;size=m" alt=""></p>
<h1>vim /usr/local/nginx/html/conf/zabbix.conf.php</h1>
<p>// Zabbix GUI configuration file.</p>
<p>global $DB;</p>
<p>$DB['TYPE'] = 'MYSQL';</p>
<p>$DB['SERVER'] = '192.168.0.13';</p>
<p>$DB['PORT'] = '3306';</p>
<p>$DB['DATABASE'] = 'zabbix';</p>
<p>$DB['USER'] = 'zabbix';</p>
<p>$DB['PASSWORD'] = 'Zabbix2018!';</p>
<p>// Schema name. Used for IBM DB2 and PostgreSQL.</p>
<p>$DB['SCHEMA'] = '';</p>
<p>$ZBX_SERVER = 'localhost';</p>
<p>$ZBX_SERVER_PORT = '10051';</p>
<p>$ZBX_SERVER_NAME = 'zabbix';</p>
<p>$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;</p>
<h3>5.4、zabbix登陆</h3>
<p>访问: http://192.168.0.13</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946870.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946919.png&amp;size=m" alt=""></p>
<h1>6、调整zabbix语言</h1>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312946968.png&amp;size=m" alt=""></p>
<h1>7、部署Zabbix Agent</h1>
<h3>7.1、yum安装zabbix agent</h3>
<h1>rpm -ivh http://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm</h1>
<h1>yum install zabbix-agent -y</h1>
<h1>vi /etc/zabbix/zabbix_agentd.conf</h1>
<p>PidFile=/var/run/zabbix/zabbix_agentd.pid</p>
<p>LogFile=/var/log/zabbix/zabbix_agentd.log</p>
<p>DebugLevel=3</p>
<p>Server=192.168.0.13</p>
<p>ListenPort=10050</p>
<p>ListenIP=192.168.0.14</p>
<p>ServerActive=192.168.0.13</p>
<p>Hostname=192.168.0.14</p>
<p>Include=/etc/zabbix/zabbix_agentd.d/*.conf</p>
<h1>UserParameter</h1>
<h1>systemctl start zabbix-agent</h1>
<h1>ps -ef \|grep zabbix</h1>
<p>在zabbix server测试：</p>
<h1>/usr/local/zabbix/bin/zabbix_get -s 192.168.0.14 -p 10050 -k “system.cpu.load[all,avg1]”</h1>
<h1>/usr/local/zabbix/bin/zabbix_get -s 192.168.0.14 -p 10050 -k “system.hostname”</h1>
<h1>/usr/local/zabbix/bin/zabbix_get -s 192.168.0.14 -p 10050 -k “system.uname”</h1>
<h3>7.2、源码包安装zabbix agent</h3>
<h1>yum install libxml2-devel libcurl-devel libevent-devel net-snmp-devel mysql-community-devel -y</h1>
<h1>tar -xzf zabbix-4.0.0.tar.gz</h1>
<h1>groupadd zabbix</h1>
<h1>useradd -g zabbix zabbix -s /sbin/nologin</h1>
<h1>cd zabbix-4.0.0</h1>
<h1>./configure –prefix=/usr/local/zabbix –enable-agent</h1>
<h1>make \&amp;\&amp; make install</h1>
<h1>vim /usr/local/zabbix/etc/zabbix_agentd.conf</h1>
<p>PidFile=/var/run/zabbix/zabbix_agentd.pid</p>
<p>LogFile=/var/log/zabbix/zabbix_agentd.log</p>
<p>DebugLevel=3</p>
<p>Server=192.168.0.13</p>
<p>ListenPort=10050</p>
<p>ListenIP=192.168.0.14</p>
<p>ServerActive=192.168.0.13</p>
<p>Hostname=192.168.0.14</p>
<p>Include=/usr/local/zabbix/etc/zabbix_agentd.conf.d/*.conf</p>
<h1>UserParameter</h1>
<h1>cp /opt/zabbix-4.0.0/conf/zabbix_agentd/userparameter_mysql.conf /usr/local/zabbix/etc/zabbix_agentd.conf.d/</h1>
<h1>mkdir -p /var/log/zabbix</h1>
<h1>mkdir -p /var/run/zabbix</h1>
<h1>chown zabbix:zabbix /var/log/zabbix</h1>
<h1>chown zabbix:zabbix /var/run/zabbix</h1>
<h1>/usr/local/zabbix/sbin/zabbix_agentd</h1>
<h1>ps -ef \|grep zabbix</h1>
<h1>ln -s /usr/local/zabbix/bin/* /usr/bin/</h1>
<h1>ln -s /usr/local/zabbix/sbin/* /usr/sbin/</h1>
<h1>cp /opt/zabbix-4.0.0/misc/init.d/fedora/core/zabbix_* /etc/init.d/</h1>
<h1>chmod 755 /etc/init.d/zabbix_*</h1>
<h1>sed -i “s#BASEDIR=/usr/local#BASEDIR=/usr/local/zabbix#g” /etc/init.d/zabbix_agentd</h1>
<h1>/etc/init.d/zabbix_agentd start</h1>
<h1>/etc/init.d/zabbix_agentd stop</h1>
<h1>systemctl start zabbix_agentd</h1>
<h1>ps -ef \|grep Zabbix</h1>
<h3>7.3、zabbix agent客户端主动模式</h3>
<h1>cat /usr/local/zabbix/etc/zabbix_agentd.conf</h1>
<p>PidFile=/var/run/zabbix/zabbix_agentd.pid</p>
<p>LogFile=/var/log/zabbix/zabbix_agentd.log</p>
<p>DebugLevel=3</p>
<p>ListenPort=10050</p>
<p>ListenIP=10.3.3.81 #zabbix agent端ip</p>
<p>StartAgents=0</p>
<p>ServerActive=45.114.170.162 #zabbix server端ip</p>
<p>Hostname=10.3.3.81</p>
<p>Include=/usr/local/zabbix/etc/zabbix_agentd.conf.d/*.conf</p>
<h1>nginx进程状态</h1>
<p>UserParameter=nginx.process,ps -ef\|grep nginx\|grep -v grep\|wc –l</p>
<p>监控项改为主动模式：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312947141.png&amp;size=m" alt=""></p>
<h3>7.4、zabbix_agentd监控项配置</h3>
<h4>7.4.1、自定义监控项1</h4>
<h1>nginx进程状态</h1>
<p>UserParameter=nginx.process,ps -ef\|grep nginx\|grep -v grep\|wc -l</p>
<h1>redis进程状态</h1>
<p>UserParameter=redis.process,ps -ef\|grep redis\|grep -v grep\|wc -l</p>
<h1>php-fpm进程状态</h1>
<p>UserParameter=php-fpm.process,ps -ef\|grep php-fpm\|grep -v grep\|wc -l</p>
<h1>rabbitmq进程状态</h1>
<p>UserParameter=rabbitmq.process,ps -ef\|grep rabbitmq\|grep -v grep\|wc -l</p>
<h1>进程占用cpu，内存使用率</h1>
<p>UserParameter=discovery.process,/usr/local/zabbix/scripts/discovery_process.sh</p>
<p>UserParameter=process.check[*],/usr/local/zabbix/scripts/process_check.sh $1 $2 $3</p>
<h1>node进程状态</h1>
<p>UserParameter=node.process,ps -ef\|grep node\|grep -v grep\|wc -l</p>
<h1>fdfs进程状态</h1>
<p>UserParameter=fdfs.process,ps -ef\|grep fdfs\|grep -v grep\|wc -l</p>
<h1>tomcat_front进程状态</h1>
<p>UserParameter=tomcat_front.process,ps -ef \|grep tomcat8_8080_front \|grep -v grep \|wc -l</p>
<h1>tomcat_manage进程状态</h1>
<p>UserParameter=tomcat_manage.process,ps -ef \|grep tomcat8_8083_manage \|grep -v grep \|wc -l</p>
<h1>tomcat_task进程状态</h1>
<p>UserParameter=tomcat_task.process,ps -ef \|grep tomcat8_8084_task \|grep -v grep \|wc -l</p>
<h1>tomcat_app进程状态</h1>
<p>UserParameter=tomcat_app.process,ps -ef \|grep tomcat8_8085_app \|grep -v grep \|wc -l</p>
<h1>tomcat_redis进程状态</h1>
<p>UserParameter=tomcat_redis.process,ps -ef \|grep tomcat8_8088_redis_sp \|grep -v grep \|wc -l</p>
<h1>mysql进程状态</h1>
<p>UserParameter=mysql.process,ps -ef\|grep mysql\|grep -v grep\|wc -l</p>
<h1>tcp连接状态</h1>
<p>UserParameter=tcp.status[*],ss -antp \|awk '{a[$$1]++}END{print a[“$1”]}'</p>
<h4>7.4.2、自定义监控项2</h4>
<p>下面自定义监控项在zabbix agent配置文件添加：</p>
<p><strong>#异常IP登录</strong></p>
<p><strong>UserParameter=check.ssh, who \| grep -v 103.25.18.121 \| wc -l</strong></p>
<p><strong>#异常用户登录</strong></p>
<p><strong>UserParameter=check.user, who \| grep -v root \| wc -l</strong></p>
<p><strong>#密码尝试失败次数</strong></p>
<p><strong>UserParameter=check.fail, grep “Failed password” /var/log/secure \| wc -l</strong></p>
<p><strong>#检查用户数量</strong></p>
<p><strong>UserParameter=get_user_num,wc -l /etc/passwd \| awk '{print $1}'</strong></p>
<p><strong>#可以登陆的用户</strong></p>
<p><strong>UserParameter=get_login_users,awk -F”:” '$7==“/bin/bash”{j++}END{print j}' /etc/passwd</strong></p>
<p><strong>#不可以登陆的用户</strong></p>
<p><strong>UserParameter=get_nologin_users,awk -F”:” '$7!=“/bin/bash”{i++}END{print i}' /etc/passwd</strong></p>
<p><strong>#检查nginx状态</strong></p>
<p><strong>UserParameter=check.nginx, ps -ef \|grep nginx \|grep -v grep\|wc -l</strong></p>
<p><strong>#检查mysql状态</strong></p>
<p><strong>UserParameter=check.mysql, ps -ef \|grep mysqld \|grep -v grep\|wc -l</strong></p>
<p><strong>#检查php状态</strong></p>
<p><strong>UserParameter=check.php-fpm, ps -ef \|grep php-fpm \|grep -v grep\|wc -l</strong></p>
<p>自定义监控模板</p>
<p>配置–模板–创建模板</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312947191.png&amp;size=m" alt=""></p>
<p>应用集—创建应用集</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312947216.png&amp;size=m" alt="">监控项—创建监控项</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312947251.png&amp;size=m" alt=""></p>
<p>触发器—创建触发器</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312947281.png&amp;size=m" alt=""></p>
<p>图形—创建图形</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312947324.png&amp;size=m" alt=""></p>
<p>主机应用模板</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312947351.png&amp;size=m" alt=""></p>
<h1>8、zabbix语言设置成中文后，有乱码解决如下</h1>
<p>1.从windows下控制面板-&gt;字体-&gt;选择一种中文字库例如"楷体”</p>
<p>2.将字体上传至/usr/local/nginx/html/fonts目录下</p>
<p>3.替换之前的字体</p>
<p>rm -rf DejaVuSans.ttf</p>
<p>mv simkai.ttf DejaVuSans.ttf
 <br>
 以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见。</p>
<p>原文链接: https://mp.weixin.qq.com/s/SaNrxrA-dL4K-36fA2b_JQ</p>]]></description><guid isPermaLink="false">/archives/8d353d0d-6875-416d-99e3-977388d32817</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 02:00:21 GMT</pubDate></item><item><title><![CDATA[zabbix监控域名到期时间]]></title><link>https://wiki.dgfygk.com/archives/fc575b1a-eb0c-4edb-bd4c-c6334c3c552e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7%E5%9F%9F%E5%90%8D%E5%88%B0%E6%9C%9F%E6%97%B6%E9%97%B4&amp;url=/archives/fc575b1a-eb0c-4edb-bd4c-c6334c3c552e" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312957830.png&amp;size=m" alt=""></p>
<h3>1、自定义监控项</h3>
<p>在zabbix server端这里面创建一个监控项</p>
<pre><code>cd /etc/zabbix/zabbix_agentd.d
</code></pre>
<pre><code>```
# vi domain_certificate_check.conf
</code></pre>
<pre><code>```
UserParameter=https.remaining[*],/etc/zabbix/scripts/domain_certificate_check.sh $1
</code></pre>
<pre><code>```
![](/upload/1734312957870.png)
</code></pre>
<h3>2、编写脚本</h3>
<pre><code>mkdir -p /etc/zabbix/scripts
</code></pre>
<pre><code>```
# vi domain_certificate_check.sh
</code></pre>
<pre><code>```
#/bin/bash
</code></pre>
<pre><code>```
host=$1
</code></pre>
<pre><code>```
port=443
</code></pre>
<pre><code>```
end_date=`openssl s_client -host $host -port $port -showcerts /dev/null |
</code></pre>
<pre><code>```
        sed -n '/BEGIN CERTIFICATE/,/END CERT/p' |
</code></pre>
<pre><code>```
        openssl x509 -text 2&gt;/dev/null |
</code></pre>
<pre><code>```
        sed -n 's/ *Not After : *//p'`
</code></pre>
<pre><code>```
if [ -n "$end_date" ]
</code></pre>
<pre><code>```
then
</code></pre>
<pre><code>```
        end_date_seconds=`date '+%s' --date "$end_date"`
</code></pre>
<pre><code>```
        # date指令format字符串时间。
</code></pre>
<pre><code>```
        now_seconds=`date '+%s'`
</code></pre>
<pre><code>```
        echo "($end_date_seconds-$now_seconds)/24/3600" | bc
</code></pre>
<pre><code>```
fi
</code></pre>
<h3>3、重启zabbix_agent</h3>
<pre><code>systemctl restart zabbix-agent
</code></pre>
<h3>4、zabbix_server端测试</h3>
<p>安装zabbix-get</p>
<pre><code>yum -y install zabbix-get
</code></pre>
<pre><code>```
</code></pre>
<p>给脚本赋予执行权限，然后测试</p>
<pre><code>chmod 755 /etc/zabbix/scripts/domain_certificate_check.sh
</code></pre>
<pre><code>```
yum -y install bc
</code></pre>
<pre><code>```
zabbix_get -s 127.0.0.1 -p10050 -k https.remaining[www.jd.com] 
</code></pre>
<pre><code>```
99
</code></pre>
<pre><code>```
</code></pre>
<p>解决zabbix"ZBX_NOTSUPPORTED: Timeout while executing a shell script"报错</p>
<p>解决方案</p>
<p>（1）修改zabbix_server的zabbix_server.conf：</p>
<h1>vi /etc/zabbix/zabbix_server.conf</h1>
<p>修改以下参数：</p>
<p>Timeout=30</p>
<p>注：超时时间为30秒</p>
<p>（2）修改zabbix_agent的zabbix_agentd.conf：</p>
<h1>vi /etc/zabbix/zabbix_agentd.conf</h1>
<p>修改以下参数：</p>
<p>Timeout=30</p>
<p>注：超时时间为30秒</p>
<p>(3) 重启zabbix服务端和客户端</p>
<p>systemctl restart zabbix-server</p>
<p>systemctl restart zabbix-agent</p>
<h3>5、zabbix的web界面添加监控项</h3>
<p>以本地化商户掌门1对1为例，域名证书到期时间还有109天过期</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312957896.png&amp;size=m" alt=""></p>
<p>配置——模板——Template App check_domain</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312957943.png&amp;size=m" alt=""></p>
<p>创建监控项</p>
<p>名称：域名到期时间_掌门1对1</p>
<p>键值：https.remaining[wgj.wgj.zhangmen.com]</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312957976.png&amp;size=m" alt=""></p>
<h3>6、创建触发器</h3>
<p>触发器——新建触发器</p>
<p>名称：掌门1对1域名到期时间</p>
<p>表达式：{Template App check_domain:https.remaining[wgj.wgj.zhangmen.com].last()}&lt;7</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312958004.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312958047.png&amp;size=m" alt=""></p>
<h3>7、创建图形</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312958083.png&amp;size=m" alt=""></p>
<h3>8、把这个模板应用到zabbix_server机器上</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312958118.png&amp;size=m" alt=""></p>
<h3>9、查看图形监控阀值,少于7天报警通知</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312958145.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见。</p>
<p>原文链接: https://mp.weixin.qq.com/s/lZo7bxZpYK4pYwvZ7yuO7A</p>]]></description><guid isPermaLink="false">/archives/fc575b1a-eb0c-4edb-bd4c-c6334c3c552e</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 02:00:08 GMT</pubDate></item><item><title><![CDATA[zabbix监控rabbitmq消息队列服务]]></title><link>https://wiki.dgfygk.com/archives/4730f063-3cb6-42d8-a674-c46584f0645e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7rabbitmq%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E6%9C%8D%E5%8A%A1&amp;url=/archives/4730f063-3cb6-42d8-a674-c46584f0645e" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966351.png&amp;size=m" alt=""></p>
<h3>1、脚本文件</h3>
<h1>cat rabbitmq_queues.sh</h1>
<h1>!/bin/bash</h1>
<p>source /etc/profile</p>
<p>rabbitmqctl list_connections name state \|grep -Ev 'List\|name' &gt;/tmp/rabbitmq_connections.txt</p>
<h1>cat zabbix_rabbitmq.sh</h1>
<h1>!/bin/bash</h1>
<p>RMQ_COMMAND=$1</p>
<p>QUEUE=$2</p>
<h1>rabbitmq进程</h1>
<p>function rabbitmq_process {</p>
<p>ps -ef \|grep rabbitmq \|grep -v grep \|wc -l</p>
<p>}</p>
<h1>rabbitmq健康检查</h1>
<p>function health_check {</p>
<p>rabbitmqctl node_health_check 2&gt;\&amp;1 \|grep “Health check passed” 2&gt;\&amp;1 &gt;/dev/null</p>
<p>if [ $? -ne 0 ];then</p>
<p>echo “1”</p>
<p>else</p>
<p>echo “0”</p>
<p>fi</p>
<p>}</p>
<p>case $RMQ_COMMAND in</p>
<p>check_process)</p>
<p>rabbitmq_process ;;</p>
<p>check_health)</p>
<p>health_check ;;</p>
<p>*)</p>
<p>echo -e “Usage: $0 [check_process\|check_health] "</p>
<p>esac</p>
<h1>cat mq_stat_discovery.sh</h1>
<h1>!/bin/bash</h1>
<h1>===============================================================================</h1>
<h1>MAIN SCRIPT</h1>
<h1>===============================================================================</h1>
<p>echo “{”</p>
<p>echo '“data”:['</p>
<p>echo “{”</p>
<p>echo '““:“Rabbit_MQ”'</p>
<p>echo “}”</p>
<p>echo “]”</p>
<p>echo “}”</p>
<h1>cat rabbitmq_discovery.sh</h1>
<h1>!/bin/bash</h1>
<h1>===============================================================================</h1>
<h1>MAIN SCRIPT</h1>
<h1>===============================================================================</h1>
<h1>AGENTD_BIN为脚本存放路径</h1>
<p>AGENTD_BIN=“/scripts”</p>
<p>cd “${AGENTD_BIN}”</p>
<p>for queue_name in $(sudo rabbitmqctl -q list_queues -p apollo_vhost \| sed -n '2,$p' \|awk '{print $1}')</p>
<p>do</p>
<p>queue_list=“$queue_list,“'{““:“'${queue_name}'“}'</p>
<p>done</p>
<p>echo '{“data”:['${queue_list#,}']}'</p>
<h1>cat rabbitmq_queues_num.sh #该脚本host_name要修改为服务器主机名称</h1>
<h1>!/bin/bash</h1>
<h1>===============================================================================</h1>
<h1>MAIN SCRIPT</h1>
<h1>===============================================================================</h1>
<h1>AGENTD_BIN为脚本存放路径</h1>
<h1>zabbix安装目录</h1>
<h1>host_name zabbix配制文件中的主机名</h1>
<p>AGENTD_BIN=“/scripts”</p>
<p>AGENTD_HOME=“/etc/zabbix”</p>
<p>host_name=“cache_test_150.39”</p>
<p>cd “${AGENTD_BIN}”</p>
<p>queue_file=${AGENTD_HOME}“/queue_list.txt”</p>
<p>if [ -f “$queue_file” ]; then</p>
<p>sudo chown zabbix.zabbix $queue_file</p>
<p>&gt;$queue_file</p>
<p>else</p>
<p>sudo touch $AGENTD_HOME/queue_list.txt</p>
<p>sudo chown zabbix.zabbix $queue_file</p>
<p>fi</p>
<p>AGENTD_CONF=“${AGENTD_HOME}““/zabbix_agentd.conf”</p>
<p>sudo rabbitmqctl -q list_queues -p apollo_vhost \| sed -n '2,$p' \|awk '{print “\“'$host_name'\““” rabbit.queue.num[“$1”] “$2}' &gt;&gt;${queue_file}</p>
<p>zabbix_sender -c ${AGENTD_CONF} -v -i ${queue_file}</p>
<p>上传以下文件到/scripts目录下</p>
<p>zabbix-get-4.0.27-1.el7.x86_64.rpm</p>
<p>zabbix-sender-4.0.26-1.el7.x86_64.rpm</p>
<p>然后赋权：</p>
<p>chmod 755 rabbitmq_queues.sh</p>
<p>chmod 755 zabbix_rabbitmq.sh</p>
<p>chmod 755 mq_stat_discovery.sh</p>
<p>chmod 755 rabbitmq_discovery.sh</p>
<p>chmod 755 rabbitmq_queues_num.sh</p>
<p>chown zabbix.zabbix mq_stat_discovery.sh</p>
<p>chown zabbix.zabbix rabbitmq_discovery.sh</p>
<p>chown zabbix.zabbix rabbitmq_queues_num.sh</p>
<p>安装依赖包：</p>
<p>rpm -ivh zabbix-get-4.0.27-1.el7.x86_64.rpm</p>
<p>rpm -ivh zabbix-sender-4.0.26-1.el7.x86_64.rpm</p>
<h3>2、zabbix_agentd 配置文件增加用户参数</h3>
<h1>vi /etc/zabbix/zabbix_agentd.conf</h1>
<p>UserParameter=rabbitmq[*],/scripts/zabbix_rabbitmq.sh “$1”</p>
<p>UserParameter=rabbitmq.connections,cat /tmp/rabbitmq_connections.txt \|wc -l</p>
<p>UserParameter=rabbitmq.queue.discovery[*],“/scripts/rabbitmq_discovery.sh”</p>
<p>UserParameter=rabbitmq.get.stat_num[*],“/scripts/rabbitmq_queues_num.sh”</p>
<p>UserParameter=rabbitmq.stat.discovery[*],“/scripts/mq_stat_discovery.sh”</p>
<h3>3、添加脚本定时任务</h3>
<p>crontab -e</p>
<p>*/1 * * * * bash /scripts/rabbitmq_queues.sh</p>
<h3>4、编辑sudo权限</h3>
<h1>visudo</h1>
<h1>在root ALL=(ALL) ALL下面加上下面两行</h1>
<p>zabbix ALL=(ALL) NOPASSWD: ALL</p>
<p>Defaults:zabbix !requiretty</p>
<h3>5、重启 zabbix_agent</h3>
<p>systemctl restart zabbix-agent</p>
<h3>6、自定义模板</h3>
<p>zabbix服务端web页面设置</p>
<p>l创建模板</p>
<p>lTemplate RabbitMQ for Linux</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966386.png&amp;size=m" alt=""></p>
<p>l创建宏</p>
<p>l宏:{$MQ_QUEUE_NUM_ALARM__THRESHOLD} 值:1000</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966425.png&amp;size=m" alt=""></p>
<p>l创建自动发现规则1</p>
<p>l名称： Rabbitmq Queue</p>
<p>l类型： zabbix 客户端(主动式)</p>
<p>l键值： rabbitmq.queue.discovery</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966473.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966535.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966632.png&amp;size=m" alt=""></p>
<p>l创建【监控项原型】</p>
<p>l名称： [] current_num</p>
<p>l类型： zabbix 采集器</p>
<p>l键值： rabbit.queue.num[]</p>
<p>l信息类型： 数字(无正负)</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966678.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966715.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966747.png&amp;size=m" alt=""></p>
<p>注：如果这里推送不到据，需要在允许主机这里加白名单</p>
<p>l创建自动发现规则2(按上面的操作方法)</p>
<p>l名称： Rabbitmq.stat.discovery</p>
<p>l类型： zabbix 客户端(主动式)</p>
<p>l键值： rabbitmq.stat.discovery</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966803.png&amp;size=m" alt=""></p>
<p>l创建监控项原型(按上面的操作方法)</p>
<p>l名称： [] rabbit.get.stat_num</p>
<p>l类型： zabbix 客户端(主动式)</p>
<p>l键值： rabbitmq.get.stat_num[]</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966845.png&amp;size=m" alt=""></p>
<p>l创建触发器</p>
<p>l在【Rabbitmq Queue】 自动发现规则下创建</p>
<p>l名称： 队列【】 当前未消费消息数量过多</p>
<p>l严重度： 警告</p>
<p>l表 达 式 ： {Template RabbitMQ for Linux:rabbit.queue.num[].last()}&gt;{$MQ_QUEUE_NUM_ALARM__THRESHOLD}</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966879.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966914.png&amp;size=m" alt=""></p>
<h3>7、添加rabbitmq监控模板</h3>
<p>监控主机添加模板：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312966972.png&amp;size=m" alt=""></p>
<h3>8、查看监控图形效果</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312967002.png&amp;size=m" alt=""></p>
<p>以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见。</p>
<p>原文链接: https://mp.weixin.qq.com/s/84cYOL3vXUOw67GMWM_R3g</p>]]></description><guid isPermaLink="false">/archives/4730f063-3cb6-42d8-a674-c46584f0645e</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:59:55 GMT</pubDate></item><item><title><![CDATA[zabbix监控mysql服务器]]></title><link>https://wiki.dgfygk.com/archives/ede7cd81-4735-4ed6-8231-5533f26be945</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7mysql%E6%9C%8D%E5%8A%A1%E5%99%A8&amp;url=/archives/ede7cd81-4735-4ed6-8231-5533f26be945" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312976718.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>监控指标：</p>
<p>实例状态</p>
<p>连接数</p>
<p>流量</p>
<p>增删改查</p>
<p>缓冲池大小与利用率</p>
<h3>1、编写监控数据采集脚本</h3>
<h1>cat /etc/my.cnf #创建一个普通用户用于免交互</h1>
<p>[mysql]</p>
<p>host=192.168.0.14</p>
<p>user=wp</p>
<p>password=MyNewPass4!</p>
<p>[mysqladmin]</p>
<p>host=192.168.0.14</p>
<p>user=wp</p>
<p>password=MyNewPass4!</p>
<p>[mysqld]</p>
<p>user = mysql</p>
<p>port = 3306</p>
<p>datadir = /var/lib/mysql</p>
<p>socket = /var/lib/mysql/mysql.sock</p>
<p>bind-address = 0.0.0.0</p>
<p>pid-file = /var/run/mysqld/mysqld.pid</p>
<p>character-set-server = utf8</p>
<p>collation-server = utf8_general_ci</p>
<p>log-error = /var/log/mysqld.log</p>
<p>max_connections = 10240</p>
<p>open_files_limit = 65535</p>
<p>innodb_buffer_pool_size = 1G</p>
<p>innodb_flush_log_at_trx_commit = 2</p>
<p>innodb_log_file_size = 256M</p>
<p>innodb_flush_method = O_DIRECT</p>
<p>interactive_timeout = 1800</p>
<p>wait_timeout = 1800</p>
<p>slave-parallel-type = LOGICAL_CLOCK</p>
<p>slave-parallel-workers = 8</p>
<p>master-info-repository = TABLE</p>
<p>relay-log-info-repository = TABLE</p>
<p>测试：</p>
<h1>echo “show variables where Variable_name='max_connections';” \|mysql -N \|awk '{print $2}'</h1>
<p>监控数据采集脚本：</p>
<h1>cat userparameter_mysql.conf</h1>
<h1>连接数</h1>
<p>UserParameter=mysql.max_connections,echo “show variables where Variable_name='max_connections';” \| mysql -N \| awk '{print $2}'</p>
<p>UserParameter=mysql.current_connections,echo “show global status where Variable_name='Threads_connected';” \| mysql -N \| awk '{print $2}'</p>
<h1>缓冲池</h1>
<p>UserParameter=mysql.buffer_pool_size,echo “show variables where Variable_name='innodb_buffer_pool_size';” \| mysql -N \|awk '{printf “%.2f”,$2/1024/1024/1024}'</p>
<p>UserParameter=mysql.buffer_pool_usage_percent,echo “show global status where Variable_name='Innodb_buffer_pool_pages_free' or Variable_name='Innodb_buffer_pool_pages_total';” \| mysql -N \| awk '{a[NR]=$2}END{printf “%.1f”,100-((a[1]/a[2])*100)}'</p>
<h1>增删改查</h1>
<p>UserParameter=mysql.status[*],echo “show global status where Variable_name='$1';” \| mysql -N \| awk '{print $$2}'</p>
<h1>实例状态</h1>
<p>UserParameter=mysql.ping,mysqladmin ping \| grep -c alive</p>
<p>UserParameter=mysql.version,mysql -V</p>
<h1>mv userparameter_mysql.conf /etc/zabbix/zabbix_agentd.d/</h1>
<h1>systemctl restart zabbix-agent</h1>
<h1>在zabbix server测试是否能获取到值</h1>
<h1>/usr/local/zabbix/bin/zabbix_get -s 192.168.0.14 -p 10050 -k “mysql.status[ status[Com_select]”</h1>
<h3>2、创建模板</h3>
<p>测试主机关联zabbix自带模板：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312976787.png&amp;size=m" alt=""></p>
<h3>3、将运行MySQL的主机关联模板</h3>
<p>删除之前模板，导入做好的模板mysql_templates：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312976827.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312976915.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312976986.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312977029.png&amp;size=m" alt=""></p>
<h3>4、监控mysql主从同步</h3>
<p>（1）在Zabbix Agent端/data/sh目录创建Shell脚本：mysql_ab_check.sh，写入如下代码：</p>
<h1>!/bin/bash</h1>
<p>/usr/local/mysql/bin/mysql -uroot -e 'show slave status\G' \|grep -E “Slave_IO_Running\|Slave_SQL_Running”\|awk '{print $2}'\|grep -c Yes</p>
<p>（2）在客户端Zabbix_agentd.conf配置文件中加入如下代码：</p>
<p>UserParameter=mysql.replication,sh /data/sh/mysql_ab_check.sh</p>
<p>（3） Zabbix服务器端获取监控数据，如果返回值为2，则证明丛库I/O、SQL线程均为YES，表示主从同步成功:</p>
<p>/usr/local/zabbix/bin/zabbix_get -s 192.168.149.129 -k mysql.replication</p>
<p>（4） Zabbix WEB平台，在192.168.149.129 hosts中创建item监控项，如图13-24（a）、13-24（b）所示，单击右上角create item，Key填写Zabbix Agentd配置文件中的mysql.replication即可：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312977094.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312977154.png&amp;size=m" alt=""></p>
<p>MYSQL主从监控项创建Graph图像，如图所示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312977209.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312977248.png&amp;size=m" alt=""></p>
<p>MYSQL主从监控项创建触发器，如图所示，MYSQL主从状态监控，设置触发器条件为key值不等于2即可，不等于2即表示MYSQL主从同步状态异常，匹配触发器，执行Actions：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312977284.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312977315.png&amp;size=m" alt=""></p>
<p>以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见。</p>
<p>原文链接: https://mp.weixin.qq.com/s/ix1UL-mKRC-TX9mQlXXGPQ</p>]]></description><guid isPermaLink="false">/archives/ede7cd81-4735-4ed6-8231-5533f26be945</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:59:43 GMT</pubDate></item><item><title><![CDATA[zabbix监控tomcat服务器]]></title><link>https://wiki.dgfygk.com/archives/a9524498-37e8-4074-93f3-5b5379438bc6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7tomcat%E6%9C%8D%E5%8A%A1%E5%99%A8&amp;url=/archives/a9524498-37e8-4074-93f3-5b5379438bc6" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312986842.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>监控指标：</p>
<p>堆内存</p>
<p>类加载</p>
<p>线程</p>
<p>GC</p>
<h3>1、Tomcat开启JMX远程连接(zabbix agentd)</h3>
<h1>vim /usr/local/tomcat8/bin/catalina.sh</h1>
<p>JAVA_OPTS=“-Dcom.sun.management.jmxremote</p>
<p>-Djava.rmi.server.hostname=192.168.0.15</p>
<p>-Dcom.sun.management.jmxremote.port=12345</p>
<p>-Dcom.sun.management.jmxremote.ssl=false</p>
<p>-Dcom.sun.management.jmxremote.authenticate=false”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312986870.png&amp;size=m" alt=""></p>
<h1>./shutdown.sh</h1>
<h1>./startup.sh</h1>
<h1>ps -ef \|grep tomcat</h1>
<h1>netstat -ntpl \|grep 12345</h1>
<h1>tail -f catalina.out</h1>
<h3>2、安装Zabbix Java gateway(zabbix server)</h3>
<h1>yum install zabbix-java-gateway #zabbix server没安装前提下</h1>
<h1>cd /usr/local/zabbix/sbin/zabbix_java</h1>
<h1>./startup.sh</h1>
<h1>ps -ef \|grep java</h1>
<h1>netstat -ntpl \|grep 10052</h1>
<h1>vim /usr/local/zabbix/etc/zabbix_server.conf</h1>
<p>JavaGateway=127.0.0.1</p>
<p>JavaGatewayPort=10052</p>
<p>StartJavaPollers=5</p>
<h1>systemctl stop zabbix_server</h1>
<h1>systemctl start zabbix_server</h1>
<h3>3、将主机关联模板</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312986915.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312986973.png&amp;size=m" alt="">
 <br>
 以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见。</p>
<p>原文链接: https://mp.weixin.qq.com/s/1Rus61_i-T1YfopLZtpzXQ</p>]]></description><guid isPermaLink="false">/archives/a9524498-37e8-4074-93f3-5b5379438bc6</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:59:25 GMT</pubDate></item><item><title><![CDATA[zabbix监控nginx服务器]]></title><link>https://wiki.dgfygk.com/archives/c364694c-d0ba-44d5-8373-0fec7d31bbc1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7nginx%E6%9C%8D%E5%8A%A1%E5%99%A8&amp;url=/archives/c364694c-d0ba-44d5-8373-0fec7d31bbc1" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312996788.png&amp;size=m" alt=""></p>
<p>监控指标：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312996833.png&amp;size=m" alt=""></p>
<h3>1、编写监控数据采集脚本</h3>
<h1>vim /usr/local/nginx/conf/nginx.conf #加入虚拟主机</h1>
<p>server {</p>
<p>listen 82;</p>
<p>server_name localhost;</p>
<p>location /nginx_status {</p>
<p>stub_status on;</p>
<p>access_log off;</p>
<p>allow 127.0.0.1;</p>
<p>deny all;</p>
<p>}</p>
<h1>/usr/local/nginx/sbin/nginx -t</h1>
<h1>/usr/local/nginx/sbin/nginx</h1>
<h1>ps -ef \|grep nginx</h1>
<h1>curl http://127.0.0.1:82/nginx_status</h1>
<p>监控数据采集脚本：</p>
<p>vim /usr/local/zabbix_scripts/nginx_status.sh</p>
<h1>!/bin/bash</h1>
<h1>active 当前活动的客户端连接数，包括waiting连接数</h1>
<h1>accepts 已接受的客户端连接数</h1>
<h1>handled 已处理的连接总数</h1>
<h1>requests 客户端请求的总数</h1>
<h1>reading 正在读取请求头的当前连接数</h1>
<h1>writing 将响应写回客户端的当前连接数</h1>
<h1>waiting 等待请求空闲客户端的当前连接数</h1>
<p>result=“/usr/bin/curl -s http://127.0.0.1:82/nginx_status”</p>
<p>case $1 in</p>
<p>active)</p>
<p>$result \|awk '/Active/ {print $NF}'</p>
<p>;;</p>
<p>accepts)</p>
<p>$result \|awk 'NR==3 {print $1}'</p>
<p>;;</p>
<p>handled)</p>
<p>$result \|awk 'NR==3 {print $2}'</p>
<p>;;</p>
<p>requests)</p>
<p>$result \|awk 'NR==3 {print $3}'</p>
<p>;;</p>
<p>reading)</p>
<p>$result \|awk '/Reading/ {print $2}'</p>
<p>;;</p>
<p>writing)</p>
<p>$result \|awk '/Writing/ {print $4}'</p>
<p>;;</p>
<p>waiting)</p>
<p>$result \|awk '/Waiting/ {print $6}'</p>
<p>;;</p>
<p>*)</p>
<p>echo “USAGE:$0 {active\|reading\|writing\|waiting\|accepts\|handled\|requests}”</p>
<p>esac</p>
<p>chmod +x /usr/local/zabbix_scripts/nginx_status.sh</p>
<h1>vim /etc/zabbix/zabbix_agentd.conf</h1>
<p>UserParameter=nginx.status[*],/usr/local/zabbix_scripts/nginx_status.sh $1</p>
<h1>systemctl start zabbix-agent</h1>
<p>zabbix server端测试：</p>
<h1>/usr/local/zabbix/bin/zabbix_get -s 192.168.0.14 -p 10050 -k “nginx.status[active]”</h1>
<h1>usr/local/zabbix/bin/zabbix_get -s 192.168.0.14 -p 10050 -k “nginx.status[accepts]”</h1>
<h3>2、创建模板</h3>
<p>测试是否可以收集：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312996872.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312996900.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312996931.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312996954.png&amp;size=m" alt=""></p>
<h3>3、将运行的主机关联模板</h3>
<p>删除之前模板，导入做好的模板nginx_status_templates：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312996975.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312997010.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734312997072.png&amp;size=m" alt="">
 <br>
 以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见</p>
<p>原文链接: https://mp.weixin.qq.com/s/DXfF34dOJM8yU0-Z8FJOQQ</p>]]></description><guid isPermaLink="false">/archives/c364694c-d0ba-44d5-8373-0fec7d31bbc1</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:59:16 GMT</pubDate></item><item><title><![CDATA[zabbix监控tcp连接]]></title><link>https://wiki.dgfygk.com/archives/34ce7811-f35f-4045-8b1b-ba754abf8092</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7tcp%E8%BF%9E%E6%8E%A5&amp;url=/archives/34ce7811-f35f-4045-8b1b-ba754abf8092" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313004895.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>监控指标：
 <br>
 • LISTEN
 <br>
 • ESTABLISHED
 <br>
 • SYN-SENT
 <br>
 • SYN-RECV
 <br>
 • TIME-WAIT
 <br>
 • CLOSE-WAIT
 <br>
 • FIN-WAIT-1
 <br>
 • FIN-WAIT-2
 <br>
 • CLOSE
 <br>
 • LAST-ACK
 <br>
 • CLOSING</p>
<h3>1、编写监控数据采集脚本</h3>
<h1>ss -antp \|awk '{a[$1]++}END{print a[“ESTAB”]}'</h1>
<h1>vim /usr/local/zabbix/etc/zabbix_agentd.conf</h1>
<p>UserParameter=tcp.status[*],ss -antp \|awk '{a[$$1]++}END{print a[“$1”]}'</p>
<p>zabbix server端测试是否获取到值：</p>
<h1>/usr/local/zabbix/bin/zabbix_get -s 192.168.0.15 -p 10050 -k 'tcp.status[LISTEN]'</h1>
<h3>2、将主机关联模板</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313004935.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313004957.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313004973.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313005028.png&amp;size=m" alt="">
 <br>
 以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见</p>
<p>原文链接: https://mp.weixin.qq.com/s/2KaDhpJDmo9lSnFyoKAkPA</p>]]></description><guid isPermaLink="false">/archives/34ce7811-f35f-4045-8b1b-ba754abf8092</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:59:02 GMT</pubDate></item><item><title><![CDATA[zabbix监控进程占cpu、内存、磁盘RAID情况]]></title><link>https://wiki.dgfygk.com/archives/2e7a9b50-2700-4f7c-b952-697583e5ff71</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7%E8%BF%9B%E7%A8%8B%E5%8D%A0cpu%E3%80%81%E5%86%85%E5%AD%98%E3%80%81%E7%A3%81%E7%9B%98RAID%E6%83%85%E5%86%B5&amp;url=/archives/2e7a9b50-2700-4f7c-b952-697583e5ff71" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015221.png&amp;size=m" alt=""></p>
<h3>1、cpu达到90%时报警</h3>
<p>名称: cpu user percent gt 90%</p>
<p>表达式：{Template OS Linux:system.cpu.util[,idle].avg(1m)}&lt;10</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015259.png&amp;size=m" alt=""></p>
<h3>2、内存达到80%时报警</h3>
<p>配置—主机(选择监控主机)—监控项—创建监控项</p>
<p>1、创建监控项</p>
<p>名称：内存使用率</p>
<p>类型：可计算的</p>
<p>键值：vm.memory.size[usedpercent]</p>
<p>公式：</p>
<p>100*(last(“vm.memory.size[total]“)-last(“vm.memory.size[available]“))/last(“vm.memory.size[total]“)</p>
<p>信息类型：数字（无正负）</p>
<p>更新间隔：5s</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015296.png&amp;size=m" alt=""></p>
<p>2、创建触发器</p>
<p>配置—主机(选择监控主机)—触发器—创建触发器</p>
<p>名称: Memory used percent more than 80%</p>
<p>表达式: {192.168.88.16:vm.memory.size[usedpercent].avg(1,60)}&gt;80</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015314.png&amp;size=m" alt=""></p>
<p>3、创建图形</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015388.png&amp;size=m" alt=""></p>
<h3>3、内存达到80%时报警</h3>
<p>1、增加硬盘使用量的监控项</p>
<p>名称：监控/磁盘空间</p>
<p>键值：vfs.fs.size[/,pfree]</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015417.png&amp;size=m" alt=""></p>
<p>2、增加硬盘使用量的触发器</p>
<p>名称: 空闲硬盘大小 小于20%</p>
<p>表达式：{192.168.88.16:vfs.fs.size[/,pfree].last(0)}&lt;20</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015467.png&amp;size=m" alt=""></p>
<h3>4、监控进程占cpu、内存情况</h3>
<p>mkdir -p /usr/local/zabbix/scripts</p>
<p>cd /usr/local/zabbix/scripts</p>
<p>vi top.sh</p>
<h1>!/bin/sh</h1>
<p>top -b -n 1 -d 1 &gt; /tmp/top.txt</p>
<p>chmod +x top.sh</p>
<p>./top.sh</p>
<p>chown zabbix /tmp/top.txt</p>
<p>crontab -e</p>
<p>*/1 * * * * /usr/local/zabbix/scripts/top.sh &gt; /dev/null 2&gt;\&amp;1</p>
<pre><code>vi discovery_process.sh
</code></pre>
<h1>!/bin/bash</h1>
<h1>获取占用高的10个进程</h1>
<h1>system process discovery script</h1>
<p>proc_array=(`tail -n +8 /tmp/top.txt \| awk '{a[$NF]+=$10}END{for(k in a)print a[k],k}'\|sort -gr\|head -10\|cut -d” " -f2`)</p>
<p>length=$</p>
<p>printf “{\n”</p>
<p>printf '\t'“\“data\“:[”</p>
<p>for ((i=0;i&lt;$length;i++))</p>
<p>do</p>
<p>printf “\n\t\t{”</p>
<p>printf “\“\“:\“${proc_array[$i]}\“}”</p>
<p>if [ $i -lt $[$length-1] ];then</p>
<p>printf “,”</p>
<p>fi</p>
<p>done</p>
<p>printf “\n\t]\n”</p>
<p>printf “}\n”</p>
<pre><code>chmod +x discovery_process.sh
</code></pre>
<p>测试获取的10个进程名</p>
<pre><code>./discovery_process.sh    json格式
</code></pre>
<p>vi process_check.sh</p>
<h1>!/bin/bash</h1>
<h1>system process CPU\&amp;MEM use information</h1>
<p>mode=$1</p>
<p>name=$2</p>
<p>process=$3</p>
<p>mem_total=$(cat /proc/meminfo \| grep “MemTotal” \| awk '{printf “%.f”,$2/1024}')</p>
<p>cpu_total=$(( $(cat /proc/cpuinfo \| grep “processor” \| wc -l) * 100 ))</p>
<p>function mempre {</p>
<p>mem_pre=`tail -n +7 /tmp/top.txt \| awk '{a[$NF]+=$10}END{for(k in a)print a[k],k}' \| grep “\b${process}\b” \| cut -d” " -f1`</p>
<p>echo “$mem_pre”</p>
<p>}</p>
<p>function memuse {</p>
<p>mem_use=`tail -n +7 /tmp/top.txt \| awk '{a[$NF]+=$10}END{for(k in a)print a[k]/100*'''${mem_total}''',k}' \| grep “\b${process}\b” \| cut -d” " -f1`</p>
<p>echo “$mem_use” \| awk '{printf “%.f”,$1*1024*1024}'</p>
<p>}</p>
<p>function cpuuse {</p>
<p>cpu_use=`tail -n +7 /tmp/top.txt \| awk '{a[$NF]+=$9}END{for(k in a)print a[k],k}' \| grep “\b${process}\b” \| cut -d” " -f1`</p>
<p>echo “$cpu_use”</p>
<p>}</p>
<p>function cpupre {</p>
<p>cpu_pre=`tail -n +7 /tmp/top.txt\|awk '{a[$NF]+=$9}END{for(k in a)print a[k],k}'\|grep “${process}“\|cut -d” " -f1`</p>
<p>if [ “$cpu_pre” = “” ]</p>
<p>then</p>
<p>echo “0.0”</p>
<p>else</p>
<p>echo “$cpu_pre.0”</p>
<p>fi</p>
<p>}</p>
<p>case $name in</p>
<p>mem)</p>
<p>if [ “$mode” = “pre” ];then</p>
<p>mempre</p>
<p>elif [ “$mode” = “avg” ];then</p>
<p>memuse</p>
<p>fi</p>
<p>;;</p>
<p>cpu)</p>
<p>if [ “$mode” = “pre” ];then</p>
<p>cpupre</p>
<p>elif [ “$mode” = “avg” ];then</p>
<p>cpuuse</p>
<p>fi</p>
<p>;;</p>
<p>*)</p>
<p>echo -e “Usage: $0 [mode : pre\|avg] [mem\|cpu] [process]”</p>
<p>esac</p>
<p>chmod +x process_check.sh</p>
<p>zabbix agentd配置收集数据:</p>
<p>cat zabbix_agentd.conf</p>
<h1>monitor process</h1>
<p>UserParameter=discovery.process,/usr/local/zabbix/scripts/discovery_process.sh</p>
<p>UserParameter=process.check[*],/usr/local/zabbix/scripts/process_check.sh $1 $2 $3</p>
<p>systemctl restart zabbix_agentd</p>
<p>在web界面导入模板：discovery-cpu\&amp;memory.xml</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015522.png&amp;size=m" alt=""></p>
<p>在主机添加整合图形：</p>
<p>内存使用率：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015574.png&amp;size=m" alt=""></p>
<p>内存使用大小：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015616.png&amp;size=m" alt=""></p>
<p>cpu使用率：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015635.png&amp;size=m" alt=""></p>
<p>图形效果图：</p>
<p>内存使用率：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015694.png&amp;size=m" alt=""></p>
<p>内存使用大小：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015724.png&amp;size=m" alt=""></p>
<p>cpu使用率：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015792.png&amp;size=m" alt=""></p>
<h3>5、监控磁盘RAID的discover模板</h3>
<p>通常，我们对硬盘当前的状态不太好确定，一般通过机房人员巡检来完成，有没有通过软件的方式来检查确定这个问题呢。MegaCli就可以做到，一般通过 MegaCli 的"Media Error Count"和"Other Error Count"这两个数值来确定阵列中磁盘是否有问题。</p>
<p>Medai Error Count 表示磁盘可能错误，可能是磁盘有坏道，这个值不为0值得注意，数值越大，危险系数越高；</p>
<p>Other Error Count 表示磁盘可能存在松动，可能需要重新再插入；</p>
<p>发现脚本：</p>
<h1>!/bin/bash</h1>
<h3>raid_id_discover.sh</h3>
<h3>wuhf</h3>
<p>num=0</p>
<p>RAID_stats(){</p>
<p>DISK=($(sudo /usr/local/MegaCli/MegaCli64 -pdlist -aALL \|grep"Slot Number”\|awk -F”:“'{print $2}'))</p>
<p>printf'{\n\t"data”:[\n'</p>
<p>for key in${DISK[@]};do</p>
<p>if[[“$” -gt “$num”\&amp;\&amp;“$num” -ne “$(($-1))“]];then</p>
<p>printf”\t\t{\“\“:\“$key\“},\n”</p>
<p>let"num++”</p>
<p>elif[[“$(($-1))” -eq “$num”]];then</p>
<p>printf”\t\t{\“\“:\“$key\“}\n”</p>
<p>fi</p>
<p>done</p>
<p>printf'\t]\n}\n'</p>
<p>}</p>
<p>RAID_stats</p>
<p>键值设置：</p>
<h1>raid.conf</h1>
<p>UserParameter=raid_discover,bash /usr/local/zabbix/libexec/raid_id_discover.sh</p>
<p>UserParameter=raid_degraded,sudo /usr/local/MegaCli/MegaCli64 -AdpAllInfo -aALL -NoLog \|grep"Degraded”\|awk'{print $NF}'</p>
<p>UserParameter=raid_failed_disks,sudo /usr/local/MegaCli/MegaCli64 -AdpAllInfo -aALL -NoLog \|grep"Failed Disks”\|awk'{print $NF}'</p>
<p>UserParameter=raid_MEC[*],sudo /usr/local/MegaCli/MegaCli64 -PDList -aAll -NoLog \|grep -A 8 “Slot Number: $1”\|grep"Media Error Count”\|awk'{print $NF}'</p>
<p>UserParameter=raid_OEC[*],sudo /usr/local/MegaCli/MegaCli64 -PDList -aAll -NoLog \|grep -A 8 “Slot Number: $1”\|grep"Other Error Count”\|awk'{print $NF}'</p>
<p>权限设置：</p>
<p>chmod 755 /usr/local/zabbix/libexec/raid_id_discover.sh</p>
<p>chown zabbix.zabbix /usr/local/zabbix/libexec/raid_id_discover.sh</p>
<p>chown zabbix.zabbix /usr/local/zabbix/etc/zabbix_agentd.conf.d/raid.conf</p>
<p>echo"zabbix ALL=(root) NOPASSWD:ALL”&gt;&gt; /etc/sudoers</p>
<p>sed -i 's/^Defaults.*.requiretty/#Defaults requiretty/' /etc/sudoers</p>
<p>模板导入：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313015838.png&amp;size=m" alt=""></p>
<p>说明：</p>
<p>要理解模板首先要了解MegaCLI命令的详情，这个百度教程有很多；我提供的模板是在zabbix-3.0的环境上运行的，低版本可能不兼容，只要理解了键值的意义自己可以自定义模板；
 <br>
 以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们，下次再见</p>
<p>原文链接: https://mp.weixin.qq.com/s/Yerr4Rn8KSWTSA3WL2EIKw</p>]]></description><guid isPermaLink="false">/archives/2e7a9b50-2700-4f7c-b952-697583e5ff71</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:58:53 GMT</pubDate></item><item><title><![CDATA[zabbix监控主机安全]]></title><link>https://wiki.dgfygk.com/archives/2debedb7-741c-4095-ad93-428253f14d16</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7%E4%B8%BB%E6%9C%BA%E5%AE%89%E5%85%A8&amp;url=/archives/2debedb7-741c-4095-ad93-428253f14d16" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313029192.png&amp;size=m" alt=""></p>
<h3>1、添加自定义安全监控项</h3>
<h1>vi /usr/local/zabbix/etc/zabbix_agentd.conf</h1>
<p><strong>#异常IP登录</strong></p>
<p><strong>UserParameter=check.ssh, who \| grep -v 103.25.18.121 \| wc -l</strong></p>
<p><strong>#异常用户登录</strong></p>
<p><strong>UserParameter=check.user, who \| grep -v root \| wc -l</strong></p>
<p><strong>#密码尝试失败次数</strong></p>
<p><strong>UserParameter=check.fail, grep “Failed password” /var/log/secure \| wc -l</strong></p>
<p><strong>#检查用户数量</strong></p>
<p><strong>UserParameter=get_user_num,wc -l /etc/passwd \| awk '{print $1}'</strong></p>
<p><strong>#可以登陆的用户</strong></p>
<p><strong>UserParameter=get_login_users,awk -F”:” '$7==“/bin/bash”{j++}END{print j}' /etc/passwd</strong></p>
<p><strong>#不可以登陆的用户</strong></p>
<p><strong>UserParameter=get_nologin_users,awk -F”:” '$7!=“/bin/bash”{i++}END{print i}' /etc/passwd</strong></p>
<h1>重启zabbix_agentd</h1>
<p>systemctl start zabbix_agentd</p>
<h3>2、zabbix server web界面配置</h3>
<h4>2.1、异常IP登录（其他监控项类似配置）</h4>
<p>自定义模板：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313029220.png&amp;size=m" alt=""></p>
<p>添加应用集：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313029248.png&amp;size=m" alt=""></p>
<p>添加监控项：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313029275.png&amp;size=m" alt=""></p>
<p>添加触发器：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313029312.png&amp;size=m" alt=""></p>
<p>添加图形：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313029339.png&amp;size=m" alt=""></p>
<h4>2.2、在主机上添加模板</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313029378.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们下次再见</p>
<p>原文链接: https://mp.weixin.qq.com/s/Cpi8eSlqeAXMYQag-YTc_w</p>]]></description><guid isPermaLink="false">/archives/2debedb7-741c-4095-ad93-428253f14d16</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:58:42 GMT</pubDate></item><item><title><![CDATA[zabbix监控交换机]]></title><link>https://wiki.dgfygk.com/archives/cc701da8-f866-4fee-bae4-bc5c0b6e0b3b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zabbix%E7%9B%91%E6%8E%A7%E4%BA%A4%E6%8D%A2%E6%9C%BA&amp;url=/archives/cc701da8-f866-4fee-bae4-bc5c0b6e0b3b" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击蓝字 关注我们</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313036407.png&amp;size=m" alt=""></p>
<h3>1、配置交换机</h3>
<p>先在要配置的交换机上,以下操作在交换机上进行(这里以华为交换机为例):</p>
<p>华为交换机:</p>
<h1>dis cu \| in snmp(检查配置)</h1>
<h1>system-view</h1>
<h1>snmp-agent #开启snmp</h1>
<h1>snmp-agent local-engineid 800007DB0378587070F161 #这个是版本号不用动,查看版本号的命令:dis snmp-agent local-engineid</h1>
<h1>snmp-agent community read cipher acl 2000 #交换机的访问控制列表,在这里放通server主机,给这个组创建一个叫momadeM321的密码.</h1>
<h1>snmp-agent sys-info location #交换机名称</h1>
<h1>snmp-agent sys-info version all #显示系统中运行的SNMP版本</h1>
<h1>snmp-agent community complexity-check disable #配置取消SNMP团体名密码复杂度检测</h1>
<h1>snmp-agent target-host trap address udp-domain 10.81.81.177(EQU:10.18.18.233) #配置通过udp协议发送到server主机</h1>
<h1>udp-port 5000 params securityname cipher momadeM321 #指定5000端口产生团队名为cipher的snmp主机日志,提取密码是momadeM321</h1>
<h3>2、配置Zabbix前端</h3>
<p>点击配置—主机–创建主机</p>
<p>把agent代理程序的接口这一项移除,这里用不上.群组就加到提前创建好的群组里面去.</p>
<p>在SNMP接口下面点击添加</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313036452.png&amp;size=m" alt=""></p>
<p>这里的端口默认是161</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313036497.png&amp;size=m" alt=""></p>
<p>在模板中选取一个链接模板,添加上.因为在交换机中配置的snmp版本为v2,所以这里用v2的模板.</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313036606.png&amp;size=m" alt=""></p>
<p>在宏这里把在交换机设置的密码添加上去,{$SNMP_COMMUNITY},点击添加和更新</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313036707.png&amp;size=m" alt=""></p>
<p>然后稍等片刻,就可以连接成功了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1734313036737.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～谢谢你看我的文章，我们下次再见</p>
<p>原文链接: https://mp.weixin.qq.com/s/YuE_IE8oSO95wYN8uScjWQ</p>]]></description><guid isPermaLink="false">/archives/cc701da8-f866-4fee-bae4-bc5c0b6e0b3b</guid><dc:creator>黄健</dc:creator><category>zabbix</category><pubDate>Mon, 16 Dec 2024 01:58:32 GMT</pubDate></item><item><title><![CDATA[综合布线系统是弱电重要组成部分，这是弱电人必需掌握的系统]]></title><link>https://wiki.dgfygk.com/archives/e3f2860b-9215-4261-8801-1f67c8f5d1dc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BB%BC%E5%90%88%E5%B8%83%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%98%AF%E5%BC%B1%E7%94%B5%E9%87%8D%E8%A6%81%E7%BB%84%E6%88%90%E9%83%A8%E5%88%86%EF%BC%8C%E8%BF%99%E6%98%AF%E5%BC%B1%E7%94%B5%E4%BA%BA%E5%BF%85%E9%9C%80%E6%8E%8C%E6%8F%A1%E7%9A%84%E7%B3%BB%E7%BB%9F&amp;url=/archives/e3f2860b-9215-4261-8801-1f67c8f5d1dc" width="1" height="1" alt="" style="opacity:0;">
<p>综合布线系统就像人体的血管一样，给各个终端输送养分。如今信息网络发达，综合布线一旦出现问题，就会牵一发而动全身，所以管理和保护好综合布线系统非常重要。延长综合布线系统的生命周期。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390255093.png&amp;size=m" alt=""></p>
<p><strong>一、综合布线组成</strong></p>
<p>1、工作区子系统</p>
<p>工作区子系统为用户提供一个满足高速数据传输的标准信息出口，并实现了信息口与设备终端的匹配、连接。该部分主要包括跳线、软线等非有源器件，其数据部分采用（ RJ45-RJ45）超五类压接跳线，每个信息插座应可支持如下的终端设备：数据通信、语音通信、图像传输等。</p>
<p>2、水平（配线）子系统</p>
<p>水平（配线）子系统由信息插座、信息插座至楼层配线设备的电缆或光缆、楼层配线设备和跳线组成。信息插座均为 RJ-45制式。每一条数据水平双绞线的长度均不应超过90米。水平双绞线布线从房间内的信息点引出并布到相应的楼宇配线柜内。整个布线为星型拓扑结构连接方式。其敷设采用PVC线槽安装。</p>
<p>3、管理子系统</p>
<p>管理应对设备间、交界间和工作区的配线设备、线缆、信息插座等设施，按一定的模式进行标识和记录，并符合以下规定：</p>
<p>1）规模较大的布线系统宜采用计算机进行管理，简单的布线系统宜按图纸资料进行管理，并应做到记录准确，及时更新、便于查阅。</p>
<p>2）每条电缆、光缆、配线设备、端接点、安装通道和安装空间均应给定唯一的标志。</p>
<p>3）配线设备、缆线、信息插座等硬件均应设置不易脱落和磨损的标识，并应有详细的书面记录和图纸资料。</p>
<p>4）电缆和光缆的两端均应标明相同的编号。</p>
<p>4、设备间子系统</p>
<p>整个大厦的主设备间（计算机中心机房），主要放置主配线机柜、网络服务器、交换机及其它网络设备等。考虑到大楼的高度及宽度，在楼层间分别设立分配线间。为了保证系统的安全可靠性，所有的配线设备及网络设备必须置于机柜内。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390255122.png&amp;size=m" alt=""></p>
<p><strong>二、综合布线系统施工关建点</strong></p>
<p>1、标签标识系统</p>
<p>标签标识是布线系统管理的基础，也是数据中心的基本要素。好的标签标识系统将帮助网管人员快速查找相关信息，缩短移动、添加和变更的时间。良好的标签标识在为拥有者增加附加价值、提高美观度的同时，还可使工作更加高效、灵活和可靠。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390255275.png&amp;size=m" alt=""></p>
<p>2、路径和空间</p>
<p>由于数据中心是一个高密度的计算环境，其中会存在大量的线缆。如果在设计之初不为这些线缆进行合理的路径和空间规划的话，随着数据中心的运行和扩展，线缆将失去控制，拥挤不堪。最终影响数据中心内的制冷能力，甚至无法维护，只好推倒重建。</p>
<p>理想方案为：合理利用数据中心内的有限空间，对铜缆、光缆和电力线缆进行良好的空间和路径规划，即将这三种类型线缆有效分离，便于实施和维护。而上走线还是下走线的选择，各有不同优缺点。建议最好是将长期固定不动的线缆，比如主干和水平线缆采用架空地板下方走线方式，而将跳线(铜缆及光纤跳线)采用在机架上方走线方式。</p>
<p>不同的线缆，其线径各不相同。在设计走线路径时，必须考虑线缆路径填充率的问题。建议在设计时，路径填充率以35%~40%为宜。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390255506.png&amp;size=m" alt=""></p>
<p>3、机柜和机架</p>
<p>为了实现良好的线缆管理，帮助维护人员减少移动、添加和更换布线系统的时间，数据中心内应该选用带有线缆管理设计的机柜和机架产品。在机柜和机架内考虑垂直和水平线缆管理器和冗余长度线缆的收纳，维持线缆弯曲半径的同时，让线缆更加整齐有序;避免线缆的缠绕和堆积阻挡机柜和机架上冷热空气的流动，使有源设备有效地实现散热冷却。</p>
<p>4、智能物理层管理系统</p>
<p>智能物理层管理系统帮助网管人员了解网络的连通性，通过实时监测连接状况，可迅速识别任何网络中断，并立即向系统管理员报告。这有助于快速排除故障和安全威胁，最大程度减少宕机时间。</p>
<p>此外，智能物理层管理系统数据库不断记录系统和物理层的资产移动和配置变更。这些信息可用于满足行业规定的报告要求或建立客户数据中心的服务水平协议(SLAs)，避免手工分析表可能存在的人工错误。</p>]]></description><guid isPermaLink="false">/archives/e3f2860b-9215-4261-8801-1f67c8f5d1dc</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:03:59 GMT</pubDate></item><item><title><![CDATA[弱电人一定要熟练掌握这7点网络技术，日常弱电网络问题基本都可以解决]]></title><link>https://wiki.dgfygk.com/archives/c335bb90-5c31-46f7-b7ed-3f8034210389</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BC%B1%E7%94%B5%E4%BA%BA%E4%B8%80%E5%AE%9A%E8%A6%81%E7%86%9F%E7%BB%83%E6%8E%8C%E6%8F%A1%E8%BF%997%E7%82%B9%E7%BD%91%E7%BB%9C%E6%8A%80%E6%9C%AF%EF%BC%8C%E6%97%A5%E5%B8%B8%E5%BC%B1%E7%94%B5%E7%BD%91%E7%BB%9C%E9%97%AE%E9%A2%98%E5%9F%BA%E6%9C%AC%E9%83%BD%E5%8F%AF%E4%BB%A5%E8%A7%A3%E5%86%B3&amp;url=/archives/c335bb90-5c31-46f7-b7ed-3f8034210389" width="1" height="1" alt="" style="opacity:0;">
<p>弱电人有个共性，对于网络技术方面的知识大部分朋友都不太熟，而大部分的弱电项目都跟网络技术是密不可分，经常有一些朋友会问到关于网络技术方面的问题，我们就从基础的开始，这七点弱电人不得不知。</p>
<p><strong>一、ip段/数字-如192.168.0.1/24是什么意思?</strong></p>
<p>后面这个24表示了我们的网络号的位数，也就是子网掩码中前24位为1。</p>
<p>192.168.1.1 /24 这个24就是网络号是24位。</p>
<p>也就相当于告诉我们了。</p>
<p>子网掩码是：11111111 11111111 11111111 00000000</p>
<p>即：255.255.255.0</p>
<p>又例如：</p>
<p>172.16.10.33/27 中的/27</p>
<p>也就是说子网掩码是255.255.255.224 即前27位全是1。</p>
<p>11111111 11111111 11111111 11100000</p>
<p>即子网掩码是：255.255.255.224</p>
<br>
<p><strong>二、一个C类的ip段中，有多少个可用的ip地址?</strong></p>
<p>C类的ip地址在实际项目中是用的最多的，一个C类地址能容纳下254台主机，为什么只有254呢，不是256呢?</p>
<p>例如：</p>
<p>192.168.1.0-192.168.1.255，子网掩码为24位，从0-255总共有256个ip地址，其中其中0是网络ID(网段)，255是广播地址，可用的ip地址只有：</p>
<p>192.168.1.1-192.168.1.254，共254个</p>
<p>也就是：</p>
<p>192.160.1.0是网络地址，192.168.1.255是广播地址，他们不能用于分配。</p>
<p>那么你可能会有疑问?网络id与广播地址有什么用途呢?</p>
<p>网络地址：是识别网络ID用的，告诉你ip地址的网段。</p>
<p>广播地址的作用：是专门用于同时向网络中所有工作站进行发送的一个地址，不能被路由转发。</p>
<p><strong>三、什么是公网ip?什么是内网ip?为什么ip地址通常以192.168开头?</strong></p>
<p>我们平台在内部组网时用的比较多，比如监控项目与公司内部电脑ip设置，用的多的就是192.168.xx。</p>
<p>为什么使用这个?</p>
<p>有朋友多次问到弱电行业网这个问题， 网络中的主机需要通信，需要使用一个IP地址。在IPv4地址中预留了3个IP地址段，作为私有地址，供家庭、企业、学校等内部组网使用。</p>
<p>私网地址</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389789425.png&amp;size=m" alt=""></p>
<p>除了这三个ip地址段为私有ip地址外，其它的都为公网ip。这些地址已被声明私有化，任何内网中的设备可以任意使用这些地址，但是在这三个范围内的IP地址不允许出现在Internet(外网)上。所以我们平时在做监控项目或内部组网时，就可以把ip地址设为192.168.x.x。</p>
<p>通常情况下，按照需要容纳的主机数选择私有地址段。家庭网络规模比较小，一个C类地址，192.168.1.x可以容纳254个终端，足够使用。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389789804.png&amp;size=m" alt=""></p>
<p><strong>四、如何用私网地址访问公网?</strong></p>
<p>我们知道，ipv4地址是有限的，作为普通用户，很少拥有公网ip地址，私网地址是不能出现在互联网上的。那么私网地址如何才能访问互联网呢?这就需要用到NAT地址转换，将内网地址映射到外网地址。</p>
<p>而NAT地址转换它实现内网的IP地址与公网的地址之间的相互转换，将大量的内网IP地址转换为一个或少量的公网IP地址，减少对公网IP地址的占用。</p>
<p>一般普遍使用是端口映射的方式，NAT的核心是一张映射表(源IP地址，源端口，目的IP地址，目的端口)，将内网源IP地址和端口映射到同一个公网地址的不同端口，如下图所示。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389789999.png&amp;size=m" alt=""></p>
<p>端口映射可以在路由器中设置，这个在远程网络或远程监控项目中是有应用的。</p>
<p><strong>五、交换机如何连接，进入配置界面?</strong></p>
<p>这是个基础的问题，很多朋友交换机拿到后，不知道该怎么操作?</p>
<p>在进行交换机配置之前，需要对交换机进行登陆与连接。</p>
<p>其实这个过程有两个步骤：</p>
<ul>
 <li>
  <p>第一步就是用线连接交换机与pc。</p>
 </li>
 <li>
  <p>第二步就是用超级终端软件登陆。</p>
 </li>
</ul>
<p><strong>1. 准备好Console通信线缆连接交换机与pc。</strong></p>
<p>如果你的PC是笔记本或者没有串口的电脑，那么就需要准备USB转串口的转接线，并按照说明书安装相关的驱动。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389790170.png&amp;size=m" alt=""></p>
<br>
<p>通过Console口连接设备</p>
<p><strong>2. 用PC终端仿真软件登陆交换机</strong></p>
<p>目前用的多的就两款，secureCRT，xshell使用用户都很多，前面的文章我们介绍过了，这次就不多说了。</p>
<p>这里弱电行业网就以secureCRT远程终端为例，在pc电脑上安装好SecureCRT，然后进行创建新的连接。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389790356.png&amp;size=m" alt=""></p>
<p>点击连接后，会提示用户输入用户名和密码。各牌子交换机的初始密码不一样，输入进去就行了，就会进入配置界面了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389790722.png&amp;size=m" alt=""></p>
<p><strong>六、同一vlan间的通信与不同vlan间的通信需要什么条件?</strong></p>
<p><strong>1. 同一个vlan的两台主机通信</strong></p>
<p>同一个vlan间的两台主机通信可以不需要路由器，用二层交换机可以完成。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389790895.png&amp;size=m" alt=""></p>
<p>同一个vlan中，A与B，这一过程完全可以在二层交换机进行，不需要路由器的参与。</p>
<p><strong>2. 不同vlan间的两台主机通信</strong></p>
<p>不同vlan间的两台主机通信是需要有路由功能。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389791124.png&amp;size=m" alt=""></p>
<p>所以我们在项目中，如果涉及到不同vlan间的通信，是需要有三层交换机，或者单臂路由。</p>
<p><strong>七、无线ap与无线路由器的区别</strong></p>
<p>很多朋友对他们的分不清，感觉用途都差不多。无线路由器也相当于是AP、路由功能和交换机的集合体，现在很多的无线路由器都拥有AP功能。</p>
<p>无线AP：我们称之为"无线访问节点"，AP的一个重要的功能就是中继，所谓中继就是在两个无线点间把无线信号放大一次，使得远端的客户端可以接受到更强的无线信号。</p>
<p>这里弱电行业网发个组网图，大家就知道ap的应用了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389791443.png&amp;size=m" alt=""></p>
<p>无线AP不能与ADSLMODE相连，要用一个交换机或是集线器或者路由器做为中介。而无线路由器带有宽带拨号功能，可以直接和ADSLMODEM相连拨号上网，实现无线覆盖，ap不具备处理数据的功能，只是把网络扩展，所有任务全部由主路由来完成。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/sYyFc_CdyJXSmtE7wSffVQ">https://mp.weixin.qq.com/s/sYyFc_CdyJXSmtE7wSffVQ</a></p>]]></description><guid isPermaLink="false">/archives/c335bb90-5c31-46f7-b7ed-3f8034210389</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:02:48 GMT</pubDate></item><item><title><![CDATA[交换机常用功能及应用]]></title><link>https://wiki.dgfygk.com/archives/6abb9d90-8a23-48db-901c-70793715bbdc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BA%A4%E6%8D%A2%E6%9C%BA%E5%B8%B8%E7%94%A8%E5%8A%9F%E8%83%BD%E5%8F%8A%E5%BA%94%E7%94%A8&amp;url=/archives/6abb9d90-8a23-48db-901c-70793715bbdc" width="1" height="1" alt="" style="opacity:0;">
<p>学习：以太网交换机了解每一端口相连设备的MAC地址，并将地址同相应的端口映射起来存放在交换机缓存中的MAC地址表中。</p>
<p>转发/过滤：当一个数据帧的目的地址在MAC地址表中有映射时，它被转发到连接目的节点的端口而不是所有端口（如该数据帧为广播/组播帧则转发至所有端口）。</p>
<p>消除回路：当交换机包括一个冗余回路时，以太网交换机通过生成树协议避免回路的产生，同时允许存在后备路径。</p>
<p>交换机除了能够连接同种类型的网络之外，还可以在不同类型的网络（如以太网和快速以太网）之间起到互连作用。如今许多交换机都能够提供支持快速以太网或FDDI等的高速连接端口，用于连接网络中的其它交换机或者为带宽占用量大的关键服务器提供附加带宽。</p>
<p>一般来说，交换机的每个端口都用来连接一个独立的网段，但是有时为了提供更快的接入速度，我们可以把一些重要的网络计算机直接连接到交换机的端口上。这样，网络的关键服务器和重要用户就拥有更快的接入速度，支持更大的信息流量。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389915939.png&amp;size=m" alt="">交换机方式</p>
<p>交换机通过以下三种方式进行交换：</p>
<ol>
 <li>直通式：</li>
</ol>
<p>直通方式的以太网交换机可以理解为在各端口间是纵横交叉的线路矩阵电话交换机。它在输入端口检测到一个数据包时，检查该包的包头，获取包的目的地址，启动内部的动态查找表转换成相应的输出端口，在输入与输出交叉处接通，把数据包直通到相应的端口，实现交换功能。由于不需要存储，延迟非常小、交换非常快，这是它的优点。它的缺点是，因为数据包内容并没有被以太网交换机保存下来，所以无法检查所传送的数据包是否有误，不能提供错误检测能力。由于没有缓存，不能将具有不同速率的输入/输出端口直接接通，而且容易丢包。</p>
<ol start="2">
 <li>存储转发：</li>
</ol>
<p>存储转发方式是计算机网络领域应用最为广泛的方式。它把输入端口的数据包先存储起来，然后进行CRC（循环冗余码校验）检查，在对错误包处理后才取出数据包的目的地址，通过查找表转换成输出端口送出包。正因如此，存储转发方式在数据处理时延时大，这是它的不足，但是它可以对进入交换机的数据包进行错误检测，有效地改善网络性能。尤其重要的是它可以支持不同速度的端口间的转换，保持高速端口与低速端口间的协同工作。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389916058.png&amp;size=m" alt=""></p>
<br>
<ol start="3">
 <li>碎片隔离：</li>
</ol>
<p>这是介于前两者之间的一种解决方案。它检查数据包的长度是否够64个字节，如果小于64字节，说明是假包，则丢弃该包；如果大于64字节，则发送该包。这种方式也不提供数据校验。它的数据处理速度比存储转发方式快，但比直通式慢。</p>
<p>交换机应用</p>
<p>作为局域网的主要连接设备，以太网交换机成为应用普及最快的网络设备之一。随着交换技术的不断发展，以太网交换机的价格急剧下降，交换到桌面已是大势所趋。</p>
<p>如果你的以太网络上拥有大量的用户、繁忙的应用程序和各式各样的服务器，而且你还未对网络结构做出任何调整，那么整个网络的性能可能会非常低。解决方法之一是在以太网上添加一个10/100Mbps的交换机，它不仅可以处理10Mbps的常规以太网数据流，而且还可以支持100Mbps的快速以太网连接。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731389916150.png&amp;size=m" alt=""></p>
<br>
<p>如果网络的利用率超过了40％，并且碰撞率大于10％，交换机可以帮你解决一点问题。带有100Mbps快速以太网和10Mbps以太网端口的交换机可以全双工方式运行，可以建立起专用的20Mbps到200Mbps连接。</p>
<p>不仅不同网络环境下交换机的作用各不相同，在同一网络环境下添加新的交换机和增加现有交换机的交换端口对网络的影响也不尽相同。充分了解和掌握网络的流量模式是能否发挥交换机作用的一个非常重要的因素。因为使用交换机的目的就是尽可能的减少和过滤网络中的数据流量，所以如果网络中的某台交换机由于安装位置设置不当，几乎需要转发接收到的所有数据包的话，交换机就无法发挥其优化网络性能的作用，反而降低了数据的传输速度，增加了网络延迟。</p>
<p>除安装位置之外，如果在那些负载较小，信息量较低的网络中也盲目添加交换机的话，同样也可能起到负面影响。受数据包的处理时间、交换机的缓冲区大小以及需要重新生成新数据包等因素的影响，在这种情况下使用简单的HUB要比交换机更为理想。因此，我们不能一概认为交换机就比HUB有优势，尤其当用户的网络并不拥挤，尚有很大的可利用空间时，使用HUB更能够充分利用网络的现有资源</p>]]></description><guid isPermaLink="false">/archives/6abb9d90-8a23-48db-901c-70793715bbdc</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:02:29 GMT</pubDate></item><item><title><![CDATA[如何区分：屏蔽网线、双绞线、超五类双屏蔽网线的作用]]></title><link>https://wiki.dgfygk.com/archives/eb354431-ed1f-4833-b19a-589e93855e8d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%8C%BA%E5%88%86%EF%BC%9A%E5%B1%8F%E8%94%BD%E7%BD%91%E7%BA%BF%E3%80%81%E5%8F%8C%E7%BB%9E%E7%BA%BF%E3%80%81%E8%B6%85%E4%BA%94%E7%B1%BB%E5%8F%8C%E5%B1%8F%E8%94%BD%E7%BD%91%E7%BA%BF%E7%9A%84%E4%BD%9C%E7%94%A8&amp;url=/archives/eb354431-ed1f-4833-b19a-589e93855e8d" width="1" height="1" alt="" style="opacity:0;">
<p>网线作为综合布线设计施工的一部分，是大部分信息传输系统的基础介质，其重要性不言而喻，本文将解析屏蔽网线的<strong>主要作用是什么？</strong> <strong>双绞线是什么？STP、SFTP、UTP、FTP、ASTP又分别是什么？什么的超五类双屏蔽网线？什么是超五类单屏蔽网线？怎么区分双屏网线和非屏蔽网线，两者有什么区别？如何分辨超五类线？如何分辨超六类线？</strong></p>
<p><strong>屏蔽网线的主要作用是什么？</strong></p>
<p>屏蔽网线主要是针对有强干扰的环境，有干扰的环境会造成网线传输信号断续，甚至中断，无法正常传输信号。而使用屏蔽网线能很好的抵抗干扰，保持传输正常，且有良好的保密性能，屏蔽布线的屏蔽结构对减低线缆间的相互干扰有先天优势，不仅可以屏蔽外界的电磁信号，铝箔也同时阻断了线缆本身的电磁泄露，不会发射干扰信号影响其他线缆的工作，可以防止信息被窃听。</p>
<p>在屏蔽网线中，都会有一条铜线，也就是地线，可以采用屏蔽型RJ45插座，使用时排流线一端接地。地线能更好的屏蔽外来的信号。屏蔽网线需要良好的接地，和屏蔽线路才能很好的降低强干扰对网络信号影响，在非必要的情况下，不要使用屏蔽网线，因为屏蔽网线的布线要求高，不仅要求系统中的配件都是带屏蔽的，且要有良好的接地才能起到作用。如果接地不好，那么效果会非常的差。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264353.png&amp;size=m" alt=""></p>
<p>如果接地点安排不正确、接地电阻过大或者接地电位不均衡时，会引起接地噪声，即在传输通道的某两点产生电位差，从而使金属屏蔽层上产生干扰电流，这时屏蔽层本身就形成了一个最大的干扰源，屏蔽网线的布线要求比较严格，全部配件都是需要具有屏蔽的性能，并且需要有良好的地线系统。在实际的施工时，很难全部完美接地，从而使屏蔽层本身成为最大的干扰源，导致性能甚至可能还不如非屏蔽双绞线。</p>
<p><strong>双绞线是什么？</strong> <strong>为什么要双绞？</strong></p>
<p>(1)双绞线：作为一种传输介质它是由二根包着绝缘材料的细铜线按一定的比率相互缠绕而成。</p>
<p>(2)为什么要把二根线双绞缠绕：因为这种相互缠绕改变了电缆原有的电子特性，这样不但可以减少自身的串扰，也可以最大程度上防止其它电缆上的信号对这对线缆上的干扰。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264387.png&amp;size=m" alt=""></p>
<p>图为超五类双绞线，由四对相互缠绕的线对构成，共八根线。双绞线是一种综合布线工程中最常用的传输介质，由两条相互绝缘的导线按照一定的规格互相缠绕在一起而制成的一种通用配线，属于信息通信网络传输介质。</p>
<p><strong>STP、SFTP、UTP、FTP、ASTP区分</strong></p>
<p><strong>STP：</strong> 即独立屏蔽双绞线，在芯线外增加铝箔层，每对线都有一个铝箔屏蔽层，目的在于减少信号的衰减。每4对线合包还有一个金属编织屏蔽层，这已经达到七类网线的结构标准。适用于高速网络和高保密性传输，可支撑未来应用更新，可以统一布线平台，多种媒体信息共同传输。</p>
<p><strong>SFTP：</strong> 即双屏蔽双绞线，在STP的铝箔基础上增加编织网，通常用铝镁丝编织网、镀锡铜网，外层是PVC外被，总体说则是有两层屏蔽层、且两层屏蔽层之间必须有绝缘隔离。它的优势在于有效减少环境磁场、信号干扰，内部信号衰减程度低。缺点是柔软性差且昂贵，这种具抗干扰及高度保密传输的线材,适用特殊环境的专业布线工程中。</p>
<p><strong>UTP：</strong> 即非屏蔽双绞线，这类是目前普通市场应用最多的布线系统线材，用于传输带宽在250MHz以下，没有特殊性能要求的普通网络应用，整体性能符合要求、价格便宜、施工和维护方便。但此类型的性能最高极限仅支持六类网线的布线系统。</p>
<p><strong>FTP：</strong> 即铝箔屏蔽的双绞线，其具有使用带宽较大、有一定抗干扰能力、减少信号衰减，回收低烟无卤、环保的优点。</p>
<p><strong>ASTP：</strong> 即铠装型双绞屏蔽网线(电缆)则是较为特殊的双屏蔽网线，在以上双屏蔽线的基础上，外层为钢带铠装层，使用时，钢带铠装层两端接地，最内层屏蔽一端接地。具有双屏蔽线的效果外，强度较高，可用于鼠害频繁或有防爆要求的布线系统，一般屏蔽线比非屏蔽线在成本和安装上都会贵一些，弯曲性能稍差。但适用于一些特殊环境和屏敝系统综合布线。大家可以在了解自身情况后合理选择，优化成本及布线效果。</p>
<p><strong>超五类单屏蔽网线是什么？</strong></p>
<p>超五类单屏蔽网线需求整个体系全部是屏蔽器材，包括网线、模块、水晶头等。比起超五类非屏蔽网线，除了对绞线和填充绳，外层是由铝箔包裹，能够减少辐射。使用的环境如电磁辐射严重、对数据传输要求严格，便可采用超五类单屏蔽网线值得信赖。</p>
<p><strong>超五类双屏蔽网线是什么？</strong></p>
<p>双屏蔽网线在对绞线外包裹着一层铝箔，并有一层金属编织网，适用于高速网络和高保密性传输，以减少辐射。这种屏蔽线适用于高频信号弦波信号之应用领域，应用于住宅综合布线系统的用户通信引出端到配线架之间的布线。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264431.png&amp;size=m" alt=""></p>
<p><strong>怎么区分双屏网线和非屏蔽网线？</strong></p>
<p>屏蔽网线指的是内部有屏蔽层的网线，屏蔽层指的是铝箔和编织网。单屏蔽网线的屏蔽层只有一层铝箔纸，双屏蔽网线在对绞线外包裹着一层铝箔，并有一层金属编织网，双屏网线和非屏蔽网线是根据的使用的环境不同来区分的，双屏蔽网线主要是用有强干扰的环境中，非屏蔽网线是使用在普通日常的网络环境中。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264483.png&amp;size=m" alt=""></p>
<p>(1)双屏蔽：网线和非屏蔽网线从内部结构上就非常容易区分的，双屏蔽网线内部是有一层金属屏蔽网和一层铝箔，双屏蔽网线也称为双屏带网网线。而非屏蔽网线内部只有4对网线。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264523.png&amp;size=m" alt=""></p>
<p>(2)网线：内部的线对是按逆时针方向紧密的缠绕在一起的，两两相互缠绕的做法有利于减低内部的串扰和减少外部的干扰，在普通的网络环境下，微弱的电磁干扰对非屏蔽网线并不造成影响，网线内部的线芯采用两两缠绕的方式，网线绞距指的是内部线对双绞扭绕一节的长度就是绞距，用来表示每对线对相互缠绕的紧密程度。</p>
<p>网线内部每一对线对都是按逆时针方向双绞，并且每一对的绞距都是不同的，这种相互缠绕改变了电缆原有的电子特性，可以抵御一部分外界的电磁干扰，还可以为了减低自身信号带来的串扰问题。</p>
<p>当布线环境存在强干扰影响的时候，就会影响到网络的传输效果，严重的会导致传输信号中断，布线环境有高压电线或者音响设备的强电流线时，网络传输信号不稳定的现象更加明显，像这种情况就要使用双屏蔽网线来屏蔽强干扰的信号，来保持网络传输信号的正常。</p>
<p>(3)双屏蔽网线：在布线的过程中是比非屏蔽网线的要复杂，要求布线系统中，全部配件都是需要具有屏蔽的性能，并且需要有良好的地线系统，如果接地不好，使屏蔽层反而成为最大的干扰源，导致性能甚至可能还不如非屏蔽双绞线，所以在必要时才使用屏蔽网线。</p>
<p><strong>如何认识超五类线呢？</strong></p>
<p><strong>1、看</strong></p>
<p>(1)超五类的线身都会印着CAT 5e。</p>
<p>(2)线皮内有4对双绞线的颜色分别为：蓝、蓝白，橙、橙白，绿、绿白，棕、棕白。</p>
<p>(3)室内网线在线皮内与双绞线同在的还有一根绳(絮状物)，简称抗拉绳，是用来提升网线的抗拉强度。</p>
<p><strong>2、辨别</strong></p>
<p>超五类网线分为非屏蔽网线和屏蔽网线，区别在于：屏蔽网线又分为单屏蔽网线(包裹双绞线一层铝箔纸)和双屏蔽网线(在铝箔纸的基础上加了一层编网)，屏蔽网线可以有效组织其他大功率电线以及强电源的电磁辐射干扰，大幅降低信号传输的误码率，起屏蔽干扰的作用。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264576.png&amp;size=m" alt=""></p>
<p><strong>3、分类</strong></p>
<p>超五类网线的材料分为多种：无氧铜、青铜、铜包铝、铜包银、四铁四铝等。其中四铁四铝是其中质量最差的。铜包铝和铜包铝一遇火就马上烧焦，烧焦后一折马上就断了青铜也统称铜包铜，外层包裹着一层无氧铜，刮的时候里面的颜色和表面的不同。无氧铜是真正的无氧铜无论刮多少层表面和铜芯的颜色也不会发生变化。</p>
<p>其实超五类网线有4对双绞线和填充绳、外皮组合而成，室外比室内多了一层PE防水外层，主要就是起到防水防潮的作用，超五类网线的寿命大概是10年左右，室内网线如果用在室外的话，一般寿命是1年多，短期使用便可。</p>
<p><strong>如何分辨超六类线？</strong></p>
<p><strong>1、看结构</strong></p>
<p>十字骨架，六类线，相比于超五类线中间多了一个绝缘的十字骨架，将网线的4对双绞线分别置于四个凹槽内，这样的结构能提高电缆的平衡特性，比起五类线，六类线性能快稳好。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264613.png&amp;size=m" alt=""></p>
<p><strong>2、外观看</strong></p>
<p>六类线的线身都印有CAT 6的标识，而超五类线的线身是印CAT 5,而且六类线的铜芯更粗，比五类线的线身更大一些。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390264728.png&amp;size=m" alt=""></p>
<p><strong>3、性能</strong></p>
<p>六类与超五类的一个重要的不同点在于：改善了在串扰以及回波损耗方面的性能，其实五类线和超五类线最大的差别就是超五类线在性能方面有较大的提升，五类线并没有抗拉绳。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/">https://mp.weixin.qq.com/s/</a></p>]]></description><guid isPermaLink="false">/archives/eb354431-ed1f-4833-b19a-589e93855e8d</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:02:05 GMT</pubDate></item><item><title><![CDATA[图文介绍OM1，OM2，OM3和OM4多模光纤的区别]]></title><link>https://wiki.dgfygk.com/archives/8afa55a4-bb8a-441c-a172-77503ae1256d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9B%BE%E6%96%87%E4%BB%8B%E7%BB%8DOM1%EF%BC%8COM2%EF%BC%8COM3%E5%92%8COM4%E5%A4%9A%E6%A8%A1%E5%85%89%E7%BA%A4%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/8afa55a4-bb8a-441c-a172-77503ae1256d" width="1" height="1" alt="" style="opacity:0;">
<p><em>1</em>
 <br>
 OM1</p>
<ul>
 <li>
  <p>颜色为<strong>橙色</strong></p>
 </li>
 <li>
  <p>核心尺寸 - 62.5um</p>
 </li>
 <li>
  <p>数据速率 - 1GB @ 850nm</p>
 </li>
 <li>
  <p>距离 - 高达300米</p>
 </li>
 <li>
  <p>应用 - 短程网络，局域网（LAN）和专用网络</p>
  <br>
 </li>
</ul>
<br>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390271213.png&amp;size=m" alt=""></p>
<br>
<p><em>2</em>
 <br>
 OM2</p>
<ul>
 <li>
  <p>颜色为橙色</p>
 </li>
 <li>
  <p>核心尺寸 - 50um</p>
 </li>
 <li>
  <p>数据速率 - 1GB @ 850nm</p>
 </li>
 <li>
  <p>距离 - 高达600米</p>
 </li>
 <li>
  <p>通常用于较短距离•OM1的2倍距离容量</p>
 </li>
 <li>
  <p>应用 - 短程网络，局域网（LAN）和专用网络</p>
 </li>
</ul>
<br>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390271244.png&amp;size=m" alt=""></p>
<br>
<br>
<p><em>3</em>
 <br>
 OM3</p>
<ul>
 <li>
  <p>颜色 - Aqua</p>
 </li>
 <li>
  <p>核心尺寸 - 50um</p>
 </li>
 <li>
  <p>日期速率 - 10GB @ 850nm</p>
 </li>
 <li>
  <p>距离 - 高达300米</p>
 </li>
 <li>
  <p>使用较少的光模式，提高速度</p>
 </li>
 <li>
  <p>使用MPO连接器可以运行40GB或100GB到100米</p>
 </li>
 <li>
  <p>应用 - 更大的专用网络</p>
 </li>
</ul>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390271284.png&amp;size=m" alt="">
 <br>
 <em><strong><strong>4</strong></strong></em>
 <br>
 <strong>OM4</strong></p>
<ul>
 <li>
  <p>颜色 - Aqua</p>
 </li>
 <li>
  <p>核心尺寸 - 50um</p>
 </li>
 <li>
  <p>数据速率 - 10GB @ 850nm</p>
 </li>
 <li>
  <p>距离 - 高达550米</p>
 </li>
 <li>
  <p>使用MPO连接器可以运行100GB到150米</p>
 </li>
 <li>
  <p>应用 - 高速网络 - 数据中心，金融中心和企业园区</p>
 </li>
</ul>
<br>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390271320.png&amp;size=m" alt=""></p>
<br>
<p><em><strong>5</strong></em>
 <br>
 <strong>什么是OM5</strong></p>
<p>根据ISO / IEC 11801，OM5光纤规定了850nm和953nm之间更宽的波长范围。它的创建是为了支持短波分复用（SWDM），这是为传输40Gb / s和100Gb / s而开发的众多新技术之一。2016年6月，新的宽带多模光纤标准ANSI / TIA-492AAAE被批准出版。2016年10月，OM5光纤被宣布为ISO / IEC 11801的包含WBMMF（宽带多模光纤）的布线的正式名称。从那时起，OM5可能成为需要更大链路距离的数据中心的潜在新选择。更高的速度。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390271386.png&amp;size=m" alt=""></p>
<br>
<br>
<br>
<p><strong>OM4与OM5：有什么区别？</strong></p>
<br>
<p>由于OM1和OM2光纤不能支持25Gbps和40Gbps的数据传输速度，OM3和OM4是支持25G，40G和100G以太网的多模光纤的主要选择。然而，随着带宽需求的增加，光纤电缆支持下一代以太网速度迁移的成本也越来越高。在这样的背景下，OM5光纤的诞生是为了扩展数据中心多模光纤的优势。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/qikGBsT_wR0z_pk-p61IPw">https://mp.weixin.qq.com/s/qikGBsT_wR0z_pk-p61IPw</a></p>]]></description><guid isPermaLink="false">/archives/8afa55a4-bb8a-441c-a172-77503ae1256d</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:01:36 GMT</pubDate></item><item><title><![CDATA[好网线与坏网线的区别，教你几招鉴别技巧]]></title><link>https://wiki.dgfygk.com/archives/fb4f0947-e16d-46f7-ab00-1838848efc81</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A5%BD%E7%BD%91%E7%BA%BF%E4%B8%8E%E5%9D%8F%E7%BD%91%E7%BA%BF%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%8C%E6%95%99%E4%BD%A0%E5%87%A0%E6%8B%9B%E9%89%B4%E5%88%AB%E6%8A%80%E5%B7%A7&amp;url=/archives/fb4f0947-e16d-46f7-ab00-1838848efc81" width="1" height="1" alt="" style="opacity:0;">
<p>不少朋友问到关于<strong>如何识别好坏网线？</strong> 今天我们可以一起来了解下。</p>
<p>一、劣质的网线差在哪里？</p>
<p>材质：铜包铁、铜包铝、杂铜（国标六类超五类是无氧纯铜）</p>
<p>直径：0.35mm~0.48mm(六类0.565mm；超五类0.51mm)</p>
<p>外皮材质：回收废料（VS. 环保PE+阻燃PVC）</p>
<p>整箱长度：150~280米（VS. 305米）</p>
<p>价格也非常便宜，50-300元（VS.六类 450-800元；超五类350-600元）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391152940.png&amp;size=m" alt=""></p>
<br>
<p>二、通常检测方法：</p>
<p>包装标识：超5类网线标有CAT5E字样每箱应该305米(1000英尺)，标有CAT6的是6类线。线上面的标识：正规厂家的产品标识上定标有长度，做工程时方便识别1箱线用了多少米，还剩多少米线。</p>
<p>绕对：网线也是采用4个绕对和1条抗拉线，分别为白橙、橙、白绿、绿、白蓝、蓝、白棕和棕，劣质网线的双绞的那根花色线芯颜色不清甚至为纯白。</p>
<p>绞合：双绞线绞的密度应该接近1CM绞一下。</p>
<p>线径：国标无氧铜网线线径为六类0.565mm；超五类0.51mm，材质坚韧，外护套的填充度高，不会松松垮垮，双手拿着绕一绕拉伸一下也有韧性，不会软踏踏的。</p>
<p>用万用表测试：国标超5类网线305米单芯电阻为9欧姆/百米左右，6类网线为7欧姆/百米左右，市场上的假超5类网线电阻有的甚至达50欧姆，传输距离缩短差不多1半。</p>
<p>称重：国标超五类网线一箱重10Kg左右，国标六类网线一箱重13Kg左右。</p>
<p>用磁铁吸（线芯含铁质较多），用刀刮（线芯外面黄里面白是铜包铝），用火烧（外护套不阻燃）</p>
<p>福禄克测试：测试费用比较昂贵，所以如果不是业主在合同中约定或者真的捅了大篓子的话，一般不会用。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391153049.png&amp;size=m" alt=""></p>
<br>
<p>三、对于最重要的铜芯，以材质分类，可以分为：</p>
<p>**四铁四铝网线：**传输质量较差，很容易出现衰减情况。100米电阻大概是50Ω，最多能传输 60米左右。</p>
<p>**辨别方法：**用磁铁吸引网线，如果被吸住，代表含铁。</p>
<p>**铜包铜网线：**又称青铜网线，青铜网线的导体属于二次回炉的铜。100米电阻大概是40Ω左右，大概能传输80米左右，抗氧化性强，使用寿命长。</p>
<p>**辨别方法：**因为表面镀了一层无氧铜，表面看不出有什么不同，但用火烧，全铜表面的无氧铜层很快会融化掉。</p>
<p>**铜包铝网线：**铜包网线的材质是在铝或者铝/钢合金芯表面铜芯包覆上一层无氧铜。100米的电阻大概是28Ω左右，便宜好卖，大致可以传输100米，抗氧性差，使用寿命不长。即抗拉强度偏低。</p>
<p>**辨别方法：**用火烧，如果网线线芯迅速软化（这个识别方法很实用），那就证明网线铜芯是含有铝质的。</p>
<p>**高导铝网线：**一种铜铝银合金的高性能网线。电阻较小，传输远，电阻100米大概是20Ω左右，可以传输120米，但抗氧性差，寿命不长。这种网线还细分为三种：铜包铝（中间是铝，表面是薄的无氧铜层）、高导铝网线（进口铝表面镀铜）、铜包银（进口铝，饱满镀铜层）。</p>
<p>**辨别方法：**同样是用火烧</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391153082.png&amp;size=m" alt=""></p>
<br>
<p>**铜包银网线：**主要材质是进口铝，内外线皮都采用全新料。电阻100米大概是15Ω左右，可以传输150-180米，但 抗氧性差，寿命不长。铜包银因为可以传输得比较远，我们一般可以用在ADSL，机房，超市，仓库这种场合，一般网络传输是没有问题的。</p>
<p>**辨别方法：**铜包银网线其中也有少量铝的成分，而铝是容易被氧化的。用个四、五年大概就会被氧化。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391153110.png&amp;size=m" alt=""></p>
<br>
<p>**无氧铜网线：**内外都是新料，光泽比较亮的是新料，能拉细的是新料。无氧铜网线100米电阻大概是 10Ω以内，大概能传输120-150米左右，信号传输距离远，丢包率低，因此在网络工程、网络高清监控工程中建议用无氧铜，网络稳定性有保障，抗氧化性强，寿命长。</p>
<p><strong>辨别方法</strong>：在烧的过程中，只是表面氧化了，用纸擦拭后， 还是恢复黄色。选择无氧铜网线的重要一点是：无氧铜网线的电阻最小，且耐氧化。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/slOO1SmWeQJQJVr61F6I-w">https://mp.weixin.qq.com/s/slOO1SmWeQJQJVr61F6I-w</a></p>]]></description><guid isPermaLink="false">/archives/fb4f0947-e16d-46f7-ab00-1838848efc81</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:00:53 GMT</pubDate></item><item><title><![CDATA[服务器有哪些作用？看完就明白]]></title><link>https://wiki.dgfygk.com/archives/ea7b3f9f-bd90-4fe2-ba5d-2fb42689180c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%9C%89%E5%93%AA%E4%BA%9B%E4%BD%9C%E7%94%A8%EF%BC%9F%E7%9C%8B%E5%AE%8C%E5%B0%B1%E6%98%8E%E7%99%BD&amp;url=/archives/ea7b3f9f-bd90-4fe2-ba5d-2fb42689180c" width="1" height="1" alt="" style="opacity:0;">
<p>如何保证服务器可以支持百万用户访问？服务器品牌有哪些？如何选购服务器？对于这些问题，今天我们就一起来看下关于服务器的相关知识。</p>
<p>假如你开发了一个网站或者一个app把他放到服务器上，之后你把它发布到了网上，运行良好，每天有几百人的访问量，用户量不大，能快速地响应用户的请求。</p>
<p>但是随着你的运营，你的网站或app出名了，服务每天要承担着成千上万的人来访问，这个时候对你来讲，这是个好消息，但是对你的服务器来说这是个坏消息，因为它变得慢了，用户多了，打开速度就慢了，服务器就很卡，所以它需要扩展了，你的服务器需要为成千上万的用户提供 7*24 不宕机服务，那么如何扩展，才能保持服务器不宕机呢？</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391065301.png&amp;size=m" alt=""></p>
<p>一、服务器如何才能保证成千上万的人访问呢？</p>
<p>1、引入负载均衡器</p>
<p>负载均衡器的使用使得我们可以在多个服务器之间分配负载。</p>
<p>例如：</p>
<p>想象下有一万个用户在一分钟内来访问你的服务器。</p>
<p>遗憾的是，你的服务器在一分钟内只能保证5千个用户的流畅访问。这怎么办呢？同时运行两个服务器就行了，就是增加了服务器来分配负载。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391065478.png&amp;size=m" alt=""></p>
<br>
<p>负载均衡器的功能就是把访问请求分发到两台服务器上。用户 1 往左，用户 2 往右，用户 3 再往左。。。以此类推。</p>
<p>如果一次有 100万个用户同时访问服务器，这该怎么解决呢？确切地说，你可以扩展到200台付款服务器，之后让负载均衡器分发请求到这200服务器上。</p>
<p>2、扩展数据库</p>
<p>虽然我们上面提到，关于大量的用户，我们可以用成百上千台服务器处理请求，但是他们都是用同一个数据库存储和检索数据。</p>
<br>
<p>那么，我们不能以同样的方式来扩展数据库吗？稍有不同。</p>
<p>我们需要做的第一件事是把数据库分成多个部分。一部分专门负责接收并存储数据，其他部分负责检索数据。</p>
<p>3、缓存和内容分发网络(CDN)</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391065553.png&amp;size=m" alt=""></p>
<br>
<p>有什么方式能使服务更高效?</p>
<p>我们使用缓存而不是对每个请求都重新处理，缓存用于记住最后一次的结果并交由其他服务或者客户端，这样就不用每次都请求后端服务了。</p>
<p>缓存的加强版叫内容分发网络（CDN），遍布全球的大量缓存。将这些缓存服务器分布到用户访问相对集中的地区或网络中，在用户访问网站时，利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上，由缓存服务器直接响应用户请求。</p>
<p>这使得用户可以从物理上靠近他们的地方来获取网页内容，而不是每次都把数据从源头搬到用户那里。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391065610.png&amp;size=m" alt=""></p>
<p>4、分片处理</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391065788.png&amp;size=m" alt=""></p>
<br>
<p>分片究竟是什么意思呢？其实也很简单：Facebook 上需要为 20 亿用户提供个人资料, 可以把你的应用架构分解为 26 个 mini-Facebook。</p>
<p>用户名如果以 A 开头，会被 mini-facebook A服务器 处理， 用户名如果以 B 开头，会被 mini-facebook B服务器 来处理......</p>
<p>分片不一定按字母顺序，根据业务需要，你可以基于任何数量的因素，比如位置、使用频率(特权用户被路由到好的硬件)等等。你可以根据需要以这种方式切分服务器、数据库或其他方面。</p>
<p>二、服务器品牌</p>
<p>服务器的品牌非常多，应用在各个领域中，我们来看下服务器用的多品牌有哪些。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391065881.png&amp;size=m" alt=""></p>
<br>
<p>不止这些，还有中兴、清华同方、富士通、海康等等，也是大家用的多的品牌。</p>
<p>三、服务器选购主要参数有哪些？</p>
<h1 id="1带宽服务器5m带宽在线多少人">1、带宽：服务器5M带宽在线多少人?</h1>
<p>我这里详细的给你介绍下，带宽和同时在线人数的计算，也方便后面有想了解的朋友在你这个提问上寻找答案。</p>
<p>首先，服务器带宽说的5M带宽，实际上是5Mbps/=625KB，需要是独享带宽，共享的话因为他人的因素无法计算。</p>
<p>然后计算你的网站大小，普通大小的网站页面（图片少，压缩过，代码优化）只有几K，我们姑且按照50K计算。</p>
<p>所以 625kb/50k=12.5人，大概同时在线12.5人访问这个50KB的页面是没有问题的。</p>
<p>要值得注意，这个是同时，也就是传统意义上的同一秒，只要有先后发送请求的顺序就可以错开，所以5M带宽严格意义上是很大的，如果你的页面小，几乎可以满足千人在线，因为大部分的人都是点击后浏览页面的，不会说一直给你发送请求。</p>
<p>2、CPU</p>
<p>中央处理器（CPU，Central Processing Unit）是是一台计算机的运算核心和控制核心。</p>
<p>计算机的性能在很大程度上由CPU的性能决定，而CPU的性能主要体现在其运行程序的速度上。影响运行速度的性能指标包括CPU的工作频率、Cache容量、指令系统和逻辑结构等参数。</p>
<p>主频：主频也叫时钟频率，单位是兆赫（MHz）或千兆赫（GHz），用来表示CPU的运算、处理数据的速度。通常，主频越高，CPU处理数据的速度就越快；</p>
<p>缓存（Cache）：实际工作时，CPU往往需要重复读取同样的数据块，而缓存容量的增大，可以大幅度提升CPU内部读取数据的命中率，而不用再到内存或者硬盘上寻找，以此提高系统性能。但是由于CPU芯片面积和成本的因素来考虑，缓存都很小；</p>
<p>核心数：般情况下每个核心都有一个线程，几核心就有几线程，但是intel发明了超线程技术，可以让单核模拟多核心工作，intel的超线程可以让单核心具有两个线程，双核四线程 ；</p>
<p>线程数 ：线程数多当然速度就快,但功耗就大 ，从英特尔品牌来看，主要有酷睿、至强、奔腾、凌动、赛扬、安腾和应用在物联网领域的几大品类。PC多以酷睿系列为主，至强则是服务器级处理器的唯一选择。在真实的场景中，确实有玩家将至强E3处理器应用在PC之上，这主要是因为服务器级CPU会比一般PC能支持更大的缓存和多处理（安装了多个物理CPU）。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391065944.png&amp;size=m" alt=""></p>
<p>3、芯片组</p>
<p>这里说的芯片组，是X86系统独有的，一般RISC处理器都是SoC，芯片即为系统；X86比较独特，以前是由CPU、南桥、北桥组成一个系统，现在是由CPU+PCH形成一个系统。因为接口和总线太多，太复杂，又由于X86系统一直传承着继承性，兼容性等特点，所以多个处理器可以匹配不同主板，同一个主板可以适配多种处理器，所以这样做了功能拆分。</p>
<p>4、内存</p>
<p>　服务器采用专用的ECC校验内存，并且应当与不同的CPU搭配使用。通常情况下，内存数量越大，服务器的性能越高。特别是对于数据库服务、代理服务、Web服务等网络服务而言，内存数量显得尤其重要。通常情况下，入门级服务器的内存不应该小于2GB，工作组级的内存不小于4GB，部门级的内存不小于8GB。</p>
<p>5、硬盘</p>
<p>SATA：Serial ATA接口，即串行ATA，采用串行技术以获得更高的传输速度及可靠性。目前是第二代即SATAII。</p>
<p>SCSI：全称为"SmallComputer System Interface"（小型计算机系统接口），具有应用范围广、多任务、带宽大、CPU占用率低，以及热插拔等优点，主要应用于中、高端服务器和高档工作站</p>
<p>SAS：Serial Attached SCSI接口，即串行SCSI， 采用串行技术以获得更高的传输速度。目前仍然是第一代</p>
<p>SSD：固态存储硬盘(Solid State Disk)其特别之处在于没有机械结构，以区块写入和抹除的方式作读写的功能，与目前的传统硬盘相较，具有低耗电、耐震、稳定性高、耐低温等优点。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391066101.png&amp;size=m" alt=""></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391066250.png&amp;size=m" alt=""></p>
<br>
<p>　另外，为了扩充数据存储空间，保证数据存储的安全性，成倍提高数据读取速度，部门级和企业级服务器还往往采用SAS RAID卡，将若干硬盘组建为磁盘阵列。入门级服务器可采用廉价的SATA RAID卡，以实现相似的功能。</p>
<p>　6、网卡</p>
<p>　　既然服务器要为网络中其他计算机提供服务，自然就要实现与其他计算机之间的通讯。即使服务器的处理能力很高，如果无法快速响应客户端的请求，那么，就会给网络传输造成瓶颈。因此，服务器应当连接在传输速率最快的端口上，并最少配置一块千兆网卡。对于某些有特殊应用的服务器（如FTP服务器、文件服务器或视频点播服务器），还应当配置两块千兆网卡。需要注意的是，千兆网卡通常需要安装在64位PCI插槽中。</p>
<p>7、冗余</p>
<p>　　可靠性是服务器最重要的指标。既然服务器在网络中的作用如此重要，那就要求服务器必须非常稳定，以便能随时为客户端能提供服务，也就是说，服务器需要不间断地工作。另外，所有重要数据都存储在服务器上，一旦硬盘损坏，数据将全部丢失。为了保证系统的可靠性，服务器采用了专门的技术。</p>
<p>　　磁盘冗余。磁盘冗余采用两块或多块硬盘来实现磁盘阵列，即使一块硬盘损坏，也不会丢失数据。</p>
<p>　　部件冗余。由于所有硬件设备都有发生故障的可能，因此，许多重要硬件设备都不止一个，例如，网卡、电源、风扇，这样可以保证部分硬件损坏之后，服务器仍然能够正常运行。</p>
<p>　　热插拔。所谓热插拔，是指带电进行硬盘或板卡的插拔操作，实现故障恢复和系统扩容。既然服务器是7×24小时工作的，那么，即使在更换或添加硬盘，甚至在插拔板卡时也不能停机。因此，热插拔对于服务器则言，就显得非常重要。</p>
<p>8、可扩展性</p>
<p>　　服务器的可扩展性既被用于部件冗余以保证运行的稳定性，同时，也被用于提升系统配置、增加功能。因此，服务器除了有较多的硬盘位置、内存插槽、CPU插座外，还拥有丰富的板卡插槽。如果硬盘数量较多，还应当能够扩充电源模块。</p>]]></description><guid isPermaLink="false">/archives/ea7b3f9f-bd90-4fe2-ba5d-2fb42689180c</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:00:33 GMT</pubDate></item><item><title><![CDATA[三层交换机如何配置，才算最大发挥其功能？]]></title><link>https://wiki.dgfygk.com/archives/f5c509cf-2401-44e9-8252-ad9e0fff0834</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%89%E5%B1%82%E4%BA%A4%E6%8D%A2%E6%9C%BA%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%EF%BC%8C%E6%89%8D%E7%AE%97%E6%9C%80%E5%A4%A7%E5%8F%91%E6%8C%A5%E5%85%B6%E5%8A%9F%E8%83%BD%EF%BC%9F&amp;url=/archives/f5c509cf-2401-44e9-8252-ad9e0fff0834" width="1" height="1" alt="" style="opacity:0;">
<p>对于弱电工程从业者来说，"交换机"一直都是关注的焦点。</p>
<br>
<p>广义的交换机（switch）就是一种在通信系统中完成信息交换功能的设备，它像是"开关"一样的存在。</p>
<br>
<p>交换机没出现之前，我们常用的就是HUB集线器。用它的话，同一时刻网络上只能传输一组数据帧的通讯，如果发生碰撞还得重试，很不高效。</p>
<p>但交换机不同，它不仅有网桥、集线器和集线器的所有功能，还提供了更先进的功能，比如如虚拟局域网（VLAN）和更高的性能。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365011.png&amp;size=m" alt=""></p>
<br>
<p>根据功能的不同，交换机分为二层交换机和三层交换机。</p>
<br>
<p>今天的文章给你科普的是"三层交换机"，它处在OSI七层模型的第三层，所以叫三层交换机。</p>
<br>
<ul>
 <li>
  <p>三层交换机你需要了解的机制有哪些？</p>
 </li>
 <li>
  <p>没有办法实操，该怎么练手掌握？</p>
 </li>
 <li>
  <p>在企业内具体部署时，又该如何操作？</p>
 </li>
</ul>
<br>
<br>
<p><strong>01</strong></p>
<p><strong>三层交换机的转发机制</strong></p>
<p><strong>你必须了解</strong></p>
<br>
<p>三层以太网交换机的转发机制主要分为两个部分：二层转发和三层交换。</p>
<br>
<p><strong>二层转发包含</strong>MAC 地址和<strong>VLAN二层转发；三层转发主要</strong>**** 涉及到两个关键的线程：地址学习线程、报文转发线程，这个和二层的线程是类似的。</p>
<br>
<br>
<p><strong>02</strong></p>
<p><strong>三层交换机配置</strong></p>
<p><strong>在模拟器上该怎么练习？</strong></p>
<br>
<p>首先，需要你了解vlan、了解基本的路由原理、了解客户端设置网关的作用。</p>
<br>
<p>并且你安装好了模拟器，这里用的是Cisco Packet Tracer。</p>
<br>
<p><strong>实验步骤如下：</strong></p>
<br>
<p><strong>01****二层交换机配置 （划a为主干链路接口）</strong></p>
<br>
<br>
<p>Switch(config)#vlan 10</p>
<p>Switch(config)#vlan 20</p>
<p>Switch(config)#int range f0/1-10</p>
<p>Switch(config-if-range)#switchport access vlan 10</p>
<p>Switch(config)#int range f0/11-20</p>
<p>Switch(config-if-range)#switchport access vlan 20</p>
<p>Switch(config)#int f0/24</p>
<p>Switch(config-if)#switchport mode trunk</p>
<br>
<p><strong>02****三层交换机配置配置</strong> <strong>（创建vlan、设置主干链路接口、设置SVI接口地址、启用路由功能）</strong></p>
<br>
<br>
<p>Switch(config)#ip routing#启用三层交换机的路由功能</p>
<p>Switch(config)#vlan 10</p>
<p>Switch(config)#vlan 20</p>
<p>Switch(config)#int f0/24</p>
<p>Switch(config-if)#switchport trunk encapsulation dot1q</p>
<p>Switch(config-if)#switchport mode trunk</p>
<p>Switch(config)#int vlan 10#创建svi10</p>
<p>Switch(config-if)#ip address 192.168.10.254 255.255.255.0#设置SVI10的ip，该地址是vlan10的网关</p>
<p>Switch(config)#int vlan 20#创建svi20</p>
<p>Switch(config-if)#ip address 192.168.20.254 255.255.255.0#设置SVI20的ip，该地址是vlan20的网关</p>
<br>
<p><strong>03****配置PC</strong> <strong>（设置IP、设置网关）</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365084.png&amp;size=m" alt=""></p>
<p>PC0地址设置，该主机属于vlan10</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365216.png&amp;size=m" alt=""></p>
<p>PC1地址设置，该主机属于vlan20</p>
<br>
<p><strong>04****测试连通性</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365371.png&amp;size=m" alt=""></p>
<br>
<br>
<br>
<p><strong>03</strong></p>
<p><strong>企业应用配置实例</strong></p>
<br>
<p>在企业中，不同部门可能需要区分管理，设置不同的网络权限，同时也需要一定的安全防护，这时你就会需要用到三层网管交换机作为核心交换机。</p>
<br>
<p>本文以TL-SG5428PE作为核心交换机为例，介绍在企业网络中配置三层交换机的方法。<strong>网络拓扑如下：</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365531.png&amp;size=m" alt=""></p>
<br>
<p><strong>出现的问题</strong></p>
<br>
<ul>
 <li>
  <p>访客网络可以访问互联网，但不能访问内部其他网络</p>
 </li>
 <li>
  <p>不同部门之间不能互相访问</p>
 </li>
 <li>
  <p>产品部可以访问互联网和服务器，研发部不能访问互联网，只能访问服务器</p>
 </li>
 <li>
  <p>服务器网段不能访问外网</p>
 </li>
</ul>
<br>
<p><strong>问题分析</strong></p>
<br>
<ul>
 <li>
  <p>每个网络设置VLAN，通过设置访问控制限制不同网络的访问权限</p>
 </li>
 <li>
  <p>开启ARP防护、DHCP侦听保障网络安全</p>
 </li>
</ul>
<br>
<p><strong>配置步骤</strong></p>
<br>
<p><strong>01****网络规划</strong></p>
<br>
<p>为方便设备管理，需要将路由器、交换机、AC、AP等设备划分到一个VLAN中，同时需要保证每个网络都划分VLAN。</p>
<br>
<p>本例中三层网管交换机的端口1连接路由器，端口2连接AC，具体VLAN划分和端口规划情况如下所示：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365674.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365730.png&amp;size=m" alt=""></p>
<br>
<p>注：网络地址的大小请根据企业规模灵活配置，本例中网络掩码配置为24位。</p>
<br>
<p><strong>02****设置端口类型</strong></p>
<br>
<p>根据规划表格，在"VLAN-&gt;802.1Q VLAN-&gt;端口配置"中，选中1-18口，端口类型下拉选择GENERAL，点击提交。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365784.png&amp;size=m" alt=""></p>
<br>
<p><strong>03****划分VLAN</strong></p>
<br>
<p>在"VLAN-&gt;802.1Q VLAN-&gt;VLAN配置"中，创建VLAN10，Tagged端口列表中选择对应的3-6号端口，点击提交。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365849.png&amp;size=m" alt=""></p>
<br>
<p>其余VLAN重复步骤即可，完成后VLAN列表如下：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390365914.png&amp;size=m" alt=""></p>
<br>
<p><strong>04****设置接口参数</strong></p>
<br>
<p>在"路由功能-&gt;接口"中，输入VLAN ID号，IP地址模式选择Static，输入网络参数如下图所示，点击创建。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366008.png&amp;size=m" alt=""></p>
<br>
<p>其余VLAN重复步骤即可，完成后接口列表如下：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366185.png&amp;size=m" alt=""></p>
<br>
<p><strong>05****设置DHCP服务器</strong></p>
<br>
<p>在"路由功能-&gt;DHCP服务器-&gt;DHCP服务器"中，启用DHCP服务。注意因为需要AC管理AP，所以DHCP服务器中需要填写option字段，如下option 60填写"TP-LINK"，option 138填写AC的IP地址，本例为192.168.23.253。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366363.png&amp;size=m" alt=""></p>
<br>
<p>在"路由功能-&gt;DHCP服务器-&gt;地址池设置"中，输入相应的网络参数如下图所示，点击添加。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366519.png&amp;size=m" alt=""></p>
<br>
<p>其余VLAN重复步骤即可，完成后DHCP地址池列表如下：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366607.png&amp;size=m" alt=""></p>
<br>
<p><strong>06****设置路由参数</strong></p>
<br>
<p>由于产品部、员工无线网络、访客网络需要连接互联网，所以需要设置相应路由使数据能转发出去。</p>
<br>
<p>在"路由功能-&gt;静态路由-&gt;IPv4静态路由"中，设置相应参数如下图所示，注意下一跳为路由器地址，本例为192.168.23.1。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366679.png&amp;size=m" alt=""></p>
<br>
<p><strong>07****网络权限设置</strong></p>
<br>
<p>在交换机中主要通过ACL来控制访问权限，本例使用其中的标准IP ACL进行配置，其余的MAC ACL等原理类似。</p>
<br>
<p>由于交换机默认规则是转发所有数据，ACL控制是逐条匹配的，所以各网络所需规则如下：</p>
<br>
<ul>
 <li>
  <p>产品部：禁止访问研发部网络。</p>
 </li>
 <li>
  <p>研发部：只允许访问服务器，禁止访问其余网络。</p>
 </li>
 <li>
  <p>员工无线网络：禁止访问产品部、研发部、服务器网络。</p>
 </li>
 <li>
  <p>访客网络：禁止访问产品部、研发部、员工无线网络、服务器网络。</p>
 </li>
</ul>
<p><strong>以研发部为例，具体设置如下：</strong></p>
<br>
<p><strong>✅新建一个ACL ID</strong></p>
<br>
<p>标准IP ACL的ID号范围是500-1499，本例使用520。在"访问控制-&gt;ACL配置-&gt;新建ACL"中，输入520，点击创建即可。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366821.png&amp;size=m" alt=""></p>
<br>
<p><strong>✅再根据需求创建ACL规则</strong></p>
<br>
<p>在"访问控制-&gt;ACL配置-&gt;标准IP ACL"中，下拉选择创建的ACL 520，输入规则ID 21，安全操作选择允许，源IP为研发部IP，目的IP为服务器IP。如下图所示，完成后点击提交。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390366936.png&amp;size=m" alt=""></p>
<br>
<p>禁止访问其余网络的规则，如下所示：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367039.png&amp;size=m" alt=""></p>
<br>
<p>完成后ACL 520列表，如下图所示：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367109.png&amp;size=m" alt=""></p>
<br>
<p><strong>✅最后绑定至相应VLAN中</strong></p>
<br>
<p>在"访问控制-&gt;ACL绑定配置-&gt;VLAN绑定"中，下拉选择ACL 520，输入VLAN ID号20，点击添加。如下图所示：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367195.png&amp;size=m" alt=""></p>
<br>
<p>其余网络重复上述三个步骤即可，注意每个网络都需要创建一个ACL ID号以进行VLAN的绑定。</p>
<br>
<p>其余网络创建后的ACL列表如下：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367265.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367378.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367426.png&amp;size=m" alt=""></p>
<br>
<p><strong>08****网络安全设置</strong></p>
<br>
<p>为保障内网的网络安全，在三层交换机中建议开启ARP防护、DHCP侦听。</p>
<br>
<p><strong>✅ARP防护</strong></p>
<br>
<p>防护功能需要先进行四元绑定。在"网络安全-&gt;四元绑定"中有手动绑定和扫描绑定，手动绑定输入相应参数即可，扫描绑定设置如下图所示。</p>
<br>
<p>绑定后可以在防护范围内进行防护选择。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367514.png&amp;size=m" alt=""></p>
<br>
<p><strong>✅防ARP欺骗</strong></p>
<br>
<p>在"网络安全-&gt;ARP防护-&gt;防ARP欺骗"中，选择启用源MAC、目的MAC和IP验证，填入作用的VLAN ID号，点击启用。如下如所示：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367630.png&amp;size=m" alt=""></p>
<br>
<p><strong>✅DHCP侦听</strong></p>
<br>
<p>DHCP主要作用是集中分配和管理IP地址，通常我们是通过路由器或三层网管交换机充当DHCP服务器的角色，但如果网络中有其他能够分配DHCP的非法服务器，也会给客户端分配不正确的IP，导致终端无法上网，网络结构紊乱。</p>
<br>
<p>而开启"DHCP侦听"功能，添加授信端口，可以让终端和服务器只能从授信端口接收发送DHCP Offer报文，从而能正确的进行网络通信。</p>
<br>
<p><strong>设置方法：</strong></p>
<br>
<p>在"网络安全-&gt;DHCP侦听-&gt;全局配置"中，启用DHCP侦听，输入作用的VLAN ID，点击提交，如下图所示：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367790.png&amp;size=m" alt=""></p>
<br>
<p>若交换机连接有合法DHCP服务器如路由器或AC或其他服务器，则需要进行端口配置，将DHCP服务器所在端口设置为授信端口。</p>
<br>
<p>在"网络安全-&gt;DHCP侦听-&gt;端口配置"中设置为授信端口，如下图所示。</p>
<br>
<p>本例中路由器和AC均无需开启DHCP服务，故无需做设置。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367871.png&amp;size=m" alt=""></p>
<br>
<p>通过以上设置，即完成了企业组网中三层网管交换机的设置，且实现了相应的访问控制和网络安全需求。注意保存配置以免掉电导致配置丢失。</p>
<br>
<p>以下简要介绍下此例中ER系列路由器、Web网管交换机中的重要设置。路由器、AC、Web网管交换机中的一些基本管理设置、上网设置、无线设置再此不做介绍。</p>
<br>
<p><strong>09****路由器设置</strong></p>
<br>
<p>数据转发到路由器后需要设置NAPT规则才能将数据转发出去，也需要设置到核心交换机的静态路由以将互联网数据转发到内网中。</p>
<br>
<p>在此以TL-ER6220G为例简单介绍ER系列路由器的设置方法。在"传输控制-&gt;NAT设置-&gt;NAPT"中，点击新增，输入相应参数如下图，点击确定。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390367984.png&amp;size=m" alt=""></p>
<br>
<p>其余VLAN重复步骤即可，完成后NAPT规则列表如下：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390368212.png&amp;size=m" alt=""></p>
<br>
<p>**注意：**由于研发部和服务器网段不能访问互联网，所以不做NAPT设置。</p>
<br>
<p>在"传输控制-&gt;路由设置-&gt;静态路由"中，点击新增，输入相应参数如下图，点击确定。注意此处的下一跳地址为三层网管交换机地址，本例为192.168.23.2。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390368378.png&amp;size=m" alt=""></p>
<br>
<p>在二层交换机中同样需要进行VLAN划分以对接三层交换机。</p>
<p>本文以员工网络所在交换机为例进行VLAN 30的设置。其余网络所在交换机设置同样。</p>
<br>
<p><strong>✅在"VLAN-&gt;802.1Q VLAN"中选中启用并点击应用</strong></p>
<br>
<p>在输入框中输入30，选择对应的端口，选为Tagged，完成后点击添加。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390368687.png&amp;size=m" alt=""></p>
<p>添加完成后，VLAN列表如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390368826.png&amp;size=m" alt=""></p>
<br>
<p><strong>✅设置端口PVID</strong></p>
<br>
<p>在"VLAN-&gt;802.1Q VLAN PVID设置"中，选中VLAN30中Untagged的端口，PVID框输入30，点击应用进行保存。</p>
<br>
<p>端口类型为Tagged的16口作为级联口，保持默认PVID值为1即可。设置后如下如所示：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390368895.png&amp;size=m" alt=""></p>
<br>
<p>至此，就完成所有设置了！</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/FjiThEU229XmbVVSauCsMg">https://mp.weixin.qq.com/s/FjiThEU229XmbVVSauCsMg</a></p>]]></description><guid isPermaLink="false">/archives/f5c509cf-2401-44e9-8252-ad9e0fff0834</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 05:00:08 GMT</pubDate></item><item><title><![CDATA[计算机网络知识点详细图文说明！弱电人必收藏！]]></title><link>https://wiki.dgfygk.com/archives/e8581074-02b2-4442-841f-50ac20acc475</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%9F%A5%E8%AF%86%E7%82%B9%E8%AF%A6%E7%BB%86%E5%9B%BE%E6%96%87%E8%AF%B4%E6%98%8E%EF%BC%81%E5%BC%B1%E7%94%B5%E4%BA%BA%E5%BF%85%E6%94%B6%E8%97%8F%EF%BC%81&amp;url=/archives/e8581074-02b2-4442-841f-50ac20acc475" width="1" height="1" alt="" style="opacity:0;">
<p>一、计算机网络概述
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390354355.png&amp;size=m" alt=""></p>
<h2 id="11-计算机网络的分类">1.1 计算机网络的分类</h2>
<p>按照网络的作用范围：广域网（WAN）、城域网（MAN）、局域网（LAN）；</p>
<p>按照网络使用者：公用网络、专用网络。</p>
<h2 id="12-计算机网络的层次结构">1.2 计算机网络的层次结构</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390354765.png&amp;size=m" alt=""></p>
<p>TCP/IP四层模型与OSI体系结构对比：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390355020.png&amp;size=m" alt=""></p>
<br>
<br>
<br>
<h2 id="13-层次结构设计的基本原则">1.3 层次结构设计的基本原则</h2>
<ul>
 <li>各层之间是相互独立的；</li>
 <li>每一层需要有足够的灵活性；</li>
 <li>各层之间完全解耦。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390355301.png&amp;size=m" alt=""></p>
<h3 id="14-计算机网络的性能指标">1.4 计算机网络的性能指标</h3>
<p>速率：bps=bit/s 时延：发送时延、传播时延、排队时延、处理时延 往返时间RTT：数据报文在端到端通信中的来回一次的时间。</p>
<h2 id="二物理层">二、物理层</h2>
<p>物理层的作用：连接不同的物理设备，传输比特流。该层为上层协议提供了一个传输数据的可靠的物理媒体。简单的说，物理层确保原始的数据可在各种物理媒体上传输。</p>
<p>物理层设备：</p>
<ul>
 <li>中继器【Repeater，也叫放大器】：同一局域网的再生信号；两端口的网段必须同一协议；5-4-3规程：10BASE-5以太网中，最多串联4个中继器，5段中只能有3个连接主机；</li>
 <li>集线器：同一局域网的再生、放大信号（多端口的中继器）；半双工，不能隔离冲突域也不能隔离广播域。</li>
</ul>
<p>信道的基本概念：信道是往一个方向传输信息的媒体，一条通信电路包含一个发送信道和一个接受信道。</p>
<ol>
 <li>单工通信信道：只能一个方向通信，没有反方向反馈的信道；</li>
 <li>半双工通信信道：双方都可以发送和接受信息，但不能同时发送也不能同时接收；</li>
 <li>全双工通信信道：双方都可以同时发送和接收。</li>
</ol>
<h2 id="三数据链路层">三、数据链路层</h2>
<h3 id="31-数据链路层概述">3.1 数据链路层概述</h3>
<p>数据链路层在物理层提供的服务的基础上向网络层提供服务，其最基本的服务是将源自网络层来的数据可靠地传输到相邻节点的目标机网络层。数据链路层在不可靠的物理介质上提供可靠的传输。</p>
<p>该层的作用包括：<strong>物理地址寻址、数据的成帧、流量控制、数据的检错、重发</strong>等。</p>
<p>有关数据链路层的重要知识点：</p>
<ol>
 <li>数据链路层为网络层提供可靠的数据传输；</li>
 <li>基本数据单位为帧；</li>
 <li>主要的协议：以太网协议；</li>
 <li>两个重要设备名称：网桥和交换机。</li>
</ol>
<p>封装成帧："帧"是<strong>数据链路层</strong>数据的基本单位：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390355516.png&amp;size=m" alt=""></p>
<p>透明传输："透明"是指即使控制字符在帧数据中，但是要当做不存在去处理。即在控制字符前加上转义字符ESC。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390355783.png&amp;size=m" alt=""></p>
<h3 id="32-数据链路层的差错监测">3.2 数据链路层的差错监测</h3>
<p>差错检测：奇偶校验码、循环冗余校验码CRC</p>
<ol>
 <li>奇偶校验码--局限性：当出错两位时，检测不到错误。</li>
 <li>循环冗余检验码：根据传输或保存的数据而产生固定位数校验码。</li>
</ol>
<h3 id="33-最大传输单元mtu">3.3 最大传输单元MTU</h3>
<p>最大传输单元MTU(Maximum Transmission Unit)，数据链路层的数据帧不是无限大的，数据帧长度受MTU限制.</p>
<p>路径MTU：由链路中MTU的最小值决定。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390355989.png&amp;size=m" alt=""></p>
<h3 id="34-以太网协议详解">3.4 以太网协议详解</h3>
<p>MAC地址：每一个设备都拥有唯一的MAC地址，共48位，使用十六进制表示。</p>
<p>以太网协议：是一种使用广泛的局域网技术，是一种应用于数据链路层的协议，使用以太网可以完成相邻设备的数据帧传输：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390356205.png&amp;size=m" alt=""></p>
<h4 id="局域网分类">局域网分类：</h4>
<p>Ethernet以太网IEEE802.3：</p>
<ol>
 <li>以太网第一个广泛部署的高速局域网</li>
 <li>以太网数据速率快</li>
 <li>以太网硬件价格便宜，网络造价成本低</li>
</ol>
<h4 id="以太网帧结构">以太网帧结构：</h4>
<ol>
 <li>类型：标识上层协议（2字节）</li>
 <li>目的地址和源地址：MAC地址（每个6字节）</li>
 <li>数据：封装的上层协议的分组（46~1500字节）</li>
 <li>CRC：循环冗余码（4字节）</li>
 <li>以太网最短帧：以太网帧最短64字节；以太网帧除了数据部分18字节；数据最短46字节；</li>
</ol>
<h4 id="mac地址物理地址局域网地址">MAC地址（物理地址、局域网地址）</h4>
<ol>
 <li>MAC地址长度为6字节，48位；</li>
 <li>MAC地址具有唯一性，每个网络适配器对应一个MAC地址；</li>
 <li>通常采用十六进制表示法，每个字节表示一个十六进制数，用 - 或 : 连接起来；</li>
 <li>MAC广播地址：FF-FF-FF-FF-FF-FF。</li>
</ol>
<h2 id="四网络层">四、网络层</h2>
<p>网络层的目的是实现两个端系统之间的数据透明传送，具体功能包括寻址和路由选择、连接的建立、保持和终止等。数据交换技术是报文交换（基本上被分组所替代）：采用储存转发方式，数据交换单位是报文。</p>
<p>网络层中涉及众多的协议，其中包括最重要的协议，也是TCP/IP的核心协议------IP协议。IP协议非常简单，仅仅提供不可靠、无连接的传送服务。IP协议的主要功能有：无连接数据报传输、数据报路由选择和差错控制。</p>
<p>与IP协议配套使用实现其功能的还有地址解析协议ARP、逆地址解析协议RARP、因特网报文协议ICMP、因特网组管理协议IGMP。具体的协议我们会在接下来的部分进行总结，有关网络层的重点为：</p>
<ol>
 <li>网络层负责对子网间的数据包进行路由选择。此外，网络层还可以实现拥塞控制、网际互连等功能；</li>
 <li>基本数据单位为IP数据包；</li>
 <li>包含的主要协议：</li>
</ol>
<ul>
 <li>IP协议（Internet Protocol，因特网互联协议）;</li>
 <li>ICMP协议（Internet Control Message Protocol，因特网控制报文协议）;</li>
 <li>ARP协议（Address Resolution Protocol，地址解析协议）;</li>
 <li>RARP协议（Reverse Address Resolution Protocol，逆地址解析协议）。</li>
</ul>
<ol start="4">
 <li>重要的设备：路由器。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390356340.png&amp;size=m" alt=""></p>
<p>路由器相关协议<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390356539.png&amp;size=m" alt=""></p>
<h3 id="41-ip协议详解">4.1 IP协议详解</h3>
<p>IP网际协议是 Internet 网络层最核心的协议。虚拟互联网络的产生：实际的计算机网络错综复杂；物理设备通过使用IP协议，屏蔽了物理网络之间的差异；当网络中主机使用IP协议连接时，无需关注网络细节，于是形成了虚拟网络。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390356739.png&amp;size=m" alt=""></p>
<p>IP协议使得复杂的实际网络变为一个虚拟互联的网络；并且解决了在虚拟网络中数据报传输路径的问题。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390356808.png&amp;size=m" alt=""></p>
<p>其中，版本指IP协议的版本，占4位，如IPv4和IPv6；首部位长度表示IP首部长度，占4位，最大数值位15；（欢迎关注公众号：网络工程师阿龙）总长度表示IP数据报总长度，占16位，最大数值位65535；TTL表示IP数据报文在网络中的寿命，占8位；协议表明IP数据所携带的具体数据是什么协议的，如TCP、UDP。</p>
<h3 id="42-ip协议的转发流程">4.2 IP协议的转发流程</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390356883.png&amp;size=m" alt=""></p>
<h3 id="43-ip地址的子网划分">4.3 IP地址的子网划分</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390357100.png&amp;size=m" alt=""></p>
<p>A类（8网络号+24主机号）、B类（16网络号+16主机号）、C类（24网络号+8主机号）可以用于标识网络中的主机或路由器，D类地址作为组广播地址，E类是地址保留。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390357368.png&amp;size=m" alt=""></p>
<h3 id="44-网络地址转换nat技术">4.4 网络地址转换NAT技术</h3>
<p>用于多个主机通过一个公有IP访问访问互联网的私有网络中，减缓了IP地址的消耗，但是增加了网络通信的复杂度。</p>
<h4 id="nat-工作原理">NAT 工作原理：</h4>
<p>从内网出去的IP数据报，将其IP地址替换为NAT服务器拥有的合法的公共IP地址，并将替换关系记录到NAT转换表中；</p>
<p>从公共互联网返回的IP数据报，依据其目的的IP地址检索NAT转换表，并利用检索到的内部私有IP地址替换目的IP地址，然后将IP数据报转发到内部网络。</p>
<h4 id="45-arp协议与rarp协议">4.5 ARP协议与RARP协议</h4>
<p>地址解析协议 ARP（Address Resolution Protocol）：为网卡（网络适配器）的IP地址到对应的硬件地址提供动态映射。可以把网络层32位地址转化为数据链路层MAC48位地址。</p>
<p>ARP 是即插即用的，一个ARP表是自动建立的，不需要系统管理员来配置。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390357566.png&amp;size=m" alt=""></p>
<p>RARP(Reverse Address Resolution Protocol)协议指逆地址解析协议，可以把数据链路层MAC48位地址转化为网络层32位地址。</p>
<h4 id="46-icmp协议详解">4.6 ICMP协议详解</h4>
<p>网际控制报文协议（Internet Control Message Protocol），可以报告错误信息或者异常情况，ICMP报文封装在IP数据报当中。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390357657.png&amp;size=m" alt=""></p>
<h4 id="icmp协议的应用">ICMP协议的应用：</h4>
<ul>
 <li>Ping应用：网络故障的排查；</li>
 <li>Traceroute应用：可以探测IP数据包在网络中走过的路径。</li>
</ul>
<h3 id="47网络层的路由概述">4.7网络层的路由概述</h3>
<p>关于路由算法的要求：正确的完整的、在计算上应该尽可能是简单的、可以适应网络中的变化、稳定的公平的。</p>
<p><strong>自治系统AS</strong>：指处于一个管理机构下的网络设备群，AS内部网络自治管理，对外提供一个或多个出入口，其中自治系统内部的路由协议为内部网关协议，如RIP、OSPF等；自治系统外部的路由协议为外部网关协议，如BGP。</p>
<p><strong>静态路由</strong>：人工配置，难度和复杂度高；</p>
<p>动态路由：</p>
<ol>
 <li>链路状态路由选择算法LS：向所有隔壁路由发送信息收敛快；全局式路由选择算法，每个路由器计算路由时，需构建整个网络拓扑图；利用Dijkstra算法求源端到目的端网络的（欢迎关注公众号：网络工程师阿龙）最短路径；Dijkstra(迪杰斯特拉)算法</li>
 <li>距离-向量路由选择算法DV：向所有隔壁路由发送信息收敛慢、会存在回路；基础是Bellman-Ford方程（简称B-F方程）；</li>
</ol>
<h3 id="48-内部网关路由协议之rip协议">4.8 内部网关路由协议之RIP协议</h3>
<p>路由信息协议 RIP(Routing Information Protocol)【<strong>应用层</strong> 】，基于距离-向量的路由选择算法，较小的<strong>AS（自治系统）</strong>，适合小型网络；RIP报文，封装进UDP数据包。</p>
<h4 id="rip协议特性">RIP协议特性：</h4>
<ol>
 <li>RIP在度量路径时采用的是跳数（每个路由器维护自身到其他每个路由器的距离记录）；</li>
 <li>RIP的费用定义在源路由器和目的子网之间；</li>
 <li>RIP被限制的网络直径不超过15跳；</li>
 <li>和隔壁交换所有的信息，30主动一次（广播）。</li>
</ol>
<h3 id="49-内部网关路由协议之ospf协议">4.9 内部网关路由协议之OSPF协议</h3>
<p>开放最短路径优先协议 OSPF(Open Shortest Path First)【网络层】，基于链路状态的路由选择算法（即Dijkstra算法），较大规模的AS ，适合大型网络，直接封装在IP数据包传输。</p>
<h4 id="ospf协议优点">OSPF协议优点：</h4>
<ol>
 <li>安全；</li>
 <li>支持多条相同费用路径；</li>
 <li>支持区别化费用度量；</li>
 <li>支持单播路由和多播路由；</li>
 <li>分层路由。</li>
</ol>
<p>RIP与OSPF的对比（路由算法决定其性质）：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390357778.png&amp;size=m" alt=""></p>
<h3 id="410外部网关路由协议之bgp协议">4.10外部网关路由协议之BGP协议</h3>
<p>BGP（Border Gateway Protocol）边际网关协议【应用层】：是运行在AS之间的一种协议,寻找一条好路由：首次交换全部信息，以后只交换变化的部分,BGP封装进TCP报文段.</p>
<h2 id="五传输层">五、传输层</h2>
<p>第一个端到端，即主机到主机的层次。传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输。此外，传输层还要处理端到端的差错控制和流量控制问题。</p>
<p>传输层的任务是根据通信子网的特性，最佳的利用网络资源，为两个端系统的会话层之间，提供建立、（欢迎关注公众号：网络工程师阿龙）维护和取消传输连接的功能，负责端到端的可靠数据传输。在这一层，信息传送的协议数据单元称为段或报文。</p>
<p>网络层只是根据网络地址将源结点发出的数据包传送到目的结点，而传输层则负责将数据可靠地传送到相应的端口。</p>
<br>
<h4 id="有关网络层的重点">有关网络层的重点：</h4>
<ol>
 <li>传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输以及端到端的差错控制和流量控制问题；</li>
 <li>包含的主要协议：TCP协议（Transmission Control Protocol，传输控制协议）、UDP协议（User Datagram Protocol，用户数据报协议）；</li>
 <li>重要设备：网关。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390357921.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390358057.png&amp;size=m" alt=""></p>
<h3 id="51-udp协议详解">5.1 UDP协议详解</h3>
<p>UDP(User Datagram Protocol: 用户数据报协议)，是一个非常简单的协议，
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390358183.png&amp;size=m" alt=""></p>
<p>UDP协议的特点：</p>
<ul>
 <li>UDP是无连接协议；</li>
 <li>UDP不能保证可靠的交付数据；</li>
 <li>UDP是面向报文传输的；</li>
 <li>UDP没有拥塞控制；</li>
 <li>UDP首部开销很小。</li>
</ul>
<h4 id="udp数据报结构">UDP数据报结构：</h4>
<p>首部:8B，四字段/2B【源端口 | 目的端口 | UDP长度 | 校验和】 数据字段：应用数据<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390358288.png&amp;size=m" alt=""></p>
<h3 id="52-tcp协议详解">5.2 TCP协议详解</h3>
<p>TCP(Transmission Control Protocol: 传输控制协议)，是计算机网络中非常复杂的一个协议。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390358351.png&amp;size=m" alt=""></p>
<h4 id="tcp协议的功能">TCP协议的功能：</h4>
<ol>
 <li>对应用层报文进行分段和重组；</li>
 <li>面向应用层实现复用与分解；</li>
 <li>实现端到端的流量控制；</li>
 <li>拥塞控制；</li>
 <li>传输层寻址；</li>
 <li>对收到的报文进行差错检测（首部和数据部分都检错）；</li>
 <li>实现进程间的端到端可靠数据传输控制。</li>
</ol>
<h4 id="tcp协议的特点">TCP协议的特点：</h4>
<ul>
 <li>TCP是面向连接的协议；</li>
 <li>TCP是面向字节流的协议；</li>
 <li>TCP的一个连接有两端，即点对点通信；</li>
 <li>TCP提供可靠的传输服务；</li>
 <li>TCP协议提供全双工通信（每条TCP连接只能一对一）；</li>
</ul>
<h4 id="521-tcp报文段结构">5.2.1 TCP报文段结构：</h4>
<p>最大报文段长度：报文段中封装的应用层数据的最大长度。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390358543.png&amp;size=m" alt=""></p>
<p><strong>TCP首部：</strong></p>
<ul>
 <li>序号字段：TCP的序号是<a href="http://mp.weixin.qq.com/s?__biz=MjM5ODY3MTgxNw==&amp;mid=2653963378&amp;idx=1&amp;sn=4963c0c5aa67cca4506f0449e6fbfd2d&amp;chksm=bd03c0d48a7449c23d0706d30416e2d8994bffe53e8ca9847c4098f6c557e22f68470242268d&amp;scene=21#wechat_redirect">对每个应用层数据</a>的每个字节进行编号</li>
 <li>确认序号字段：期望从对方接收数据的字节序号，即该序号对应的字节尚未收到。用ack_seq标识；</li>
 <li>TCP段的首部长度最短是20B ，最长为60字节。但是长度必须为4B的整数倍</li>
</ul>
<br>
<br>
<h3 id="tcp标记的作用">TCP标记的作用：</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390358705.png&amp;size=m" alt=""></p>
<h3 id="53-可靠传输的基本原理">5.3 可靠传输的基本原理</h3>
<p>基本原理：</p>
<ul>
 <li>不可靠传输信道在数据传输中可能发生的情况：比特差错、乱序、重传、丢失</li>
 <li>基于不可靠信道实现可靠数据传输采取的措施：</li>
</ul>
<blockquote>
 <p>差错检测：利用编码实现数据包传输过程中的比特差错检测 确认：接收方向发送方反馈接收状态 重传：发送方重新发送接收方没有正确接收的数据 序号：确保数据按序提交 计时器：解决数据丢失问题；</p>
</blockquote>
<p>停止等待协议：是最简单的可靠传输协议，但是该协议对信道的利用率不高。</p>
<p>连续ARQ(Automatic Repeat reQuest：自动重传请求)协议：滑动窗口+累计确认，大幅提高了信道的利用率。</p>
<h3 id="531tcp协议的可靠传输">5.3.1TCP协议的可靠传输</h3>
<p>基于连续ARQ协议，在某些情况下，重传的效率并不高，会重复传输部分已经成功接收的字节。</p>
<h3 id="532-tcp协议的流量控制">5.3.2 TCP协议的流量控制</h3>
<p>流量控制：让发送方发送速率不要太快，TCP协议使用滑动窗口实现流量控制。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390358894.png&amp;size=m" alt=""></p>
<h3 id="54-tcp协议的拥塞控制">5.4 TCP协议的拥塞控制</h3>
<p>拥塞控制与流量控制的区别：流量控制考虑点对点的通信量的控制，而拥塞控制考虑整个网络，是全局性的考虑。拥塞控制的方法：慢启动算法+拥塞避免算法。</p>
<p>慢开始和拥塞避免：</p>
<ol>
 <li>【慢开始】拥塞窗口从1指数增长；</li>
 <li>到达阈值时进入【拥塞避免】，变成+1增长；</li>
 <li>【超时】，阈值变为当前cwnd的一半（不能&lt;2）；</li>
 <li>再从【慢开始】，拥塞窗口从1指数增长。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359013.png&amp;size=m" alt=""></p>
<p>快重传和快恢复：</p>
<ol>
 <li>发送方连续收到3个冗余ACK，执行【快重传】，不必等计时器超时；</li>
 <li>执行【快恢复】，阈值变为当前cwnd的一半（不能&lt;2），并从此新的ssthresh点进入【拥塞避免】。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359159.png&amp;size=m" alt=""></p>
<h2 id="55-tcp连接的三次握手重要">5.5 TCP连接的三次握手（重要）</h2>
<p>TCP三次握手使用指令：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359230.png&amp;size=m" alt=""></p>
<p>面试常客：为什么需要三次握手？</p>
<ol>
 <li>第一次握手：客户发送请求，此时服务器知道客户能发；</li>
 <li>第二次握手：服务器发送确认，此时客户知道服务器能发能收；</li>
 <li>第三次握手：客户发送确认，此时服务器知道客户能收。</li>
</ol>
<p>建立连接（三次握手）：</p>
<p>第一次：客户向服务器发送连接请求段，建立连接请求控制段（SYN=1），表示传输的报文段的第一个数据字节的序列号是x，此序列号代表整个报文段的序号（seq=x）；客户端进入 SYN_SEND （同步发送状态）；</p>
<p>第二次：服务器发回确认报文段，同意建立新连接的确认段（SYN=1），确认序号字段有效（ACK=1），服务器告诉客户端报文段序号是y（seq=y），表示服务器已经收到客户端序号为x的报文段，准备接受客户端序列号为x+1的报文段（ack_seq=x+1）；服务器由LISTEN进入SYN_RCVD （同步收到状态）;</p>
<p>第三次:客户对服务器的同一连接进行确认.确认序号字段有效(ACK=1),客户此次的报文段的序列号是x+1(seq=x+1),客户期望接受服务器序列号为y+1的报文段(ack_seq=y+1);当客户发送ack时，客户端进入ESTABLISHED 状态;当服务收到客户发送的ack后，也进入ESTABLISHED状态;第三次握手可携带数据;
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359370.png&amp;size=m" alt=""></p>
<h3 id="56-tcp连接的四次挥手重要">5.6 TCP连接的四次挥手（重要）</h3>
<p>释放连接（四次挥手）</p>
<p>第一次：客户向服务器发<a href="http://mp.weixin.qq.com/s?__biz=MjM5ODY3MTgxNw==&amp;mid=2653963378&amp;idx=1&amp;sn=4963c0c5aa67cca4506f0449e6fbfd2d&amp;chksm=bd03c0d48a7449c23d0706d30416e2d8994bffe53e8ca9847c4098f6c557e22f68470242268d&amp;scene=21#wechat_redirect">送释放连接报文段</a>，发送端数据发送完毕，请求释放连接（FIN=1），传输的第一个数据字节的序号是x（seq=x）；客户端状态由ESTABLISHED进入FIN_WAIT_1（终止等待1状态）；</p>
<p>第二次：服务器向客户发送确认段，确认字号段有效（ACK=1），服务器传输的数据序号是y（seq=y），服务器期望接收客户数据序号为x+1（ack_seq=x+1）;服务器状态由ESTABLISHED进入CLOSE_WAIT（关闭等待）；客户端收到ACK段后，由FIN_WAIT_1进入FIN_WAIT_2；</p>
<p>第三次:服务器向客户发送释放连接报文段，请求释放连接（FIN=1），确认字号段有效（ACK=1），表示服务器期望接收客户数据序号为x+1（ack_seq=x+1）;表示自己传输的第一个字节序号是y+1（seq=y+1）；服务器状态由CLOSE_WAIT 进入 LAST_ACK （最后确认状态）；</p>
<p>第四次：客户向服务器发送确认段，确认字号段有效（ACK=1），表示客户传输的数据序号是x+1（seq=x+1），表示客户期望接收服务器数据序号为y+1+1（ack_seq=y+1+1）；客户端状态由FIN_WAIT_2进入TIME_WAIT，等待2MSL时间，进入CLOSED状态；服务器在收到最后一次ACK后，由LAST_ACK进入CLOSED；
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359453.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359555.png&amp;size=m" alt=""></p>
<p><strong>为什么需要等待2MSL?</strong></p>
<ol>
 <li>最后一个报文没有确认；</li>
 <li>确保发送方的ACK可以到达接收方；</li>
 <li>2MSL时间内没有收到，则接收方会重发；</li>
 <li>确保当前连接的所有报文都已经过期。</li>
</ol>
<h2 id="六应用层">六、应用层</h2>
<p>为操作系统或网络应用程序提供访问网络服务的接口。<strong>应用层重点：</strong></p>
<ol>
 <li>数据传输基本单位为报文；</li>
 <li>包含的主要协议： <a href="http://mp.weixin.qq.com/s?__biz=MjM5ODY3MTgxNw==&amp;mid=2653963428&amp;idx=2&amp;sn=d972768cded73935519f37af2e6abc38&amp;chksm=bd03c0028a744914aff967f3b40ae5c8d4ec4460359ea67c0886ed66b0dcff1c977f156cd2a8&amp;scene=21#wechat_redirect">FTP（文件传送协议）</a> 、Telnet（远程登录协议）、DNS（域名解析协议）、SMTP（邮件传送协议），POP3协议（邮局协议），HTTP协议（Hyper Text Transfer Protocol）。</li>
</ol>
<h3 id="61-dns详解">6.1 DNS详解</h3>
<p>DNS（Domain Name System:域名系统）【C/S，UDP，端口53】：解决IP地址复杂难以记忆的问题,存储并完成自己所管辖范围内主机的 域名 到 IP 地址的映射。</p>
<p>域名解析的顺序：</p>
<ul>
 <li>【1】浏览器缓存，</li>
 <li>【2】找本机的hosts文件，</li>
 <li>【3】路由缓存，</li>
 <li>【4】找DNS服务器（本地域名、顶级域名、根域名）-&gt;迭代解析、递归查询。</li>
</ul>
<ol>
 <li>IP---&gt;DNS服务---&gt;便于记忆的域名</li>
 <li>域名由点、字母和数字组成，分为顶级域（com，cn，net，gov，org）、二级域（baidu,taobao,qq,alibaba）、三级域（www）(12-2-0852)</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359656.png&amp;size=m" alt=""></p>
<h3 id="62-dhcp协议详解">6.2 DHCP协议详解</h3>
<p>DHCP（<a href="http://mp.weixin.qq.com/s?__biz=MjM5ODY3MTgxNw==&amp;mid=2653963466&amp;idx=1&amp;sn=888251688128da5d4088fc5847733982&amp;chksm=bd03c06c8a74497a6a5c024a48c5400a91c29056ae1e87bbf713276f2ad3fd6f2531b71ee2b5&amp;scene=21#wechat_redirect">Dynamic Configuration Protocol:动态主机设置协议</a>）：是一个局域网协议，是应用UDP协议的应用层协议。作用：为临时接入局域网的用户自动分配IP地址。</p>
<h3 id="63-http协议详解">6.3 HTTP协议详解</h3>
<p>文件传输协议（FTP）：控制连接（端口21）：传输控制信息（连接、传输请求），以7位ASCII码的格式。整个会话期间一直打开。</p>
<p>HTTP（HyperText Transfer Protocol:超文本传输协议）【TCP，端口80】：是可靠的数据传输协议，浏览器向服务器发收报文前，先建立TCP连接，HTTP使用TCP连接方式（HTTP自身无连接）。</p>
<p>HTTP请求报文方式：</p>
<ol>
 <li>GET：请求指定的页面信息，并返回实体主体；</li>
 <li>POST：向指定资源提交数据进行处理请求；</li>
 <li>DELETE：请求服务器删除指定的页面；</li>
 <li>HEAD：请求读取URL标识的信息的首部，只返回报文头；</li>
 <li>OPETION：请求一些选项的信息；</li>
 <li>PUT：在指明的URL下存储一个文档。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390359811.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390360032.png&amp;size=m" alt=""></p>
<h3 id="631-http工作的结构">6.3.1 HTTP工作的结构</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390360251.png&amp;size=m" alt=""></p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/vYeI8JEYFnAi41kST3ezjA">https://mp.weixin.qq.com/s/vYeI8JEYFnAi41kST3ezjA</a></p>]]></description><guid isPermaLink="false">/archives/e8581074-02b2-4442-841f-50ac20acc475</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:59:25 GMT</pubDate></item><item><title><![CDATA[视频监控中的上行、下行带宽、比特率是什么，怎么计算传输带宽？]]></title><link>https://wiki.dgfygk.com/archives/8224e6b7-7adb-4865-9b12-c04b3afeb354</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%A7%86%E9%A2%91%E7%9B%91%E6%8E%A7%E4%B8%AD%E7%9A%84%E4%B8%8A%E8%A1%8C%E3%80%81%E4%B8%8B%E8%A1%8C%E5%B8%A6%E5%AE%BD%E3%80%81%E6%AF%94%E7%89%B9%E7%8E%87%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%8C%E6%80%8E%E4%B9%88%E8%AE%A1%E7%AE%97%E4%BC%A0%E8%BE%93%E5%B8%A6%E5%AE%BD%EF%BC%9F&amp;url=/archives/8224e6b7-7adb-4865-9b12-c04b3afeb354" width="1" height="1" alt="" style="opacity:0;">
<p>视频监控传输带宽："上行带宽""下行带宽"</p>
<p>上行带宽</p>
<p>上行带宽就是本地上传信息到网络上的带宽。上行速率是指用户电脑向网络发送信息时的数据传输速率，比如用FTP上传文件到网上去，影响上传速度的就是"上行速率"。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390419578.png&amp;size=m" alt=""></p>
<p>下行带宽</p>
<p>下行带宽就是从网络上下载信息的带宽。下行速率是指用户电脑从网络下载信息时的数据传输速率，比如从FTP服务器上文件下载到用户电脑，影响下传速度的就是"下行速率"。</p>
<p>视频监控传输带宽："比特率"</p>
<p>比特率</p>
<p>比特率是指每秒传送的比特(bit)数。单位为bps(BitPerSecond)，比特率越高，传送的数据越大。比特率表示经过编码(压缩)后的音、视频数据每秒钟需要用多少个比特来表示，而比特就是二进制里面最小的单位，要么是0，要么是1。比特率与音、视频压缩的关系，简单的说就是比特率越高，音、视频的质量就越好，但编码后的文件就越大;如果比特率越少则情况刚好相反。</p>
<p><strong>码流</strong></p>
<p>码流(DataRate)是指视频文件在单位时间内使用的数据流量，也叫码率，是视频编码中画面质量控制中最重要的部分。同样分辨率下，视频文件的码流越大，压缩比就越小，画面质量就越高。</p>
<p>视频监控传输带宽计算</p>
<p>比特率大小×摄像机的路数=网络带宽至少大小</p>
<p>【注：监控点的带宽是要求上行的最小限度带宽(监控点将视频信息上传到监控中心)；监控中心的带宽是要求下行的最小限度带宽(将监控点的视频信息下载到监控中心)；例：电信2Mbps的ADSL宽带，理论上其上行带宽是512Kbps=64KB/s，其下行带宽是2Mbps=256KB/s】</p>
<p>例：监控分布在5个不同的地方，各地方的摄像机的路数：n=10，1个监控中心，远程监看及存储视频信息，不同视频格式的带宽计算如下：</p>
<p>（1）地方监控点：</p>
<p>上行带宽=视频格式的比特率×摄像机的路数</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390419777.png&amp;size=m" alt=""></p>
<br>
<p>即：理论上采用D1视频格式各地方监控所需的网络上行带宽至少为15Mbps；720P至少为20Mbps；1080P至少为40Mbps。</p>
<p>（2）监控中心：</p>
<p>下行带宽=视频格式的比特率×监控点的摄像机的总路数之和</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390419916.png&amp;size=m" alt=""></p>
<br>
<p>即：理论上采用D1视频格式监控中心所需的网络下行带宽至少75Mbps，720P至少为100Mbps；1080P至少为200Mbps。</p>
<p>注意：按照当前市场上的网络带宽利用率来计算，80%左右基本是极限。</p>
<p>例如：手机远程访问设备，打开一个通道，720P的分辨率，码流值为2048kbps，上行和下行带宽都需要2Mbps，那么在保持画面流畅的状态下， ADSL宽带的带宽至少需要8Mbps。当尝试同时打开多路图像的时候，会出现卡顿或者无法打开的情况，建议使用副码流访问。相同环境下，副码流占用的带宽小，具体可进入设备的编码设置中调节。</p>
<p>传输带宽计算：</p>
<p>比特率大小×摄像机的路数=网络带宽至少大小</p>
<p>【注：监控点的带宽是要求上行的最小限度带宽(监控点将视频信息上传到监控中心)；监控中心的带宽是要求下行的最小限度带宽(将监控点的视频信息下载到监控中心)；例：电信2Mbps的ADSL宽带，理论上其上行带宽是512Kbps=64KB/s，其下行带宽是2Mbps=256KB/s】</p>
<p>例：监控分布在5个不同的地方，各地方的摄像机的路数：n=10，1个监控中心，远程监看及存储视频信息，不同视频格式的带宽计算如下：</p>
<p>（1）地方监控点</p>
<p>上行带宽=视频格式的比特率×摄像机的路数</p>
<p>分辨率</p>
<p>每路所需传输带宽(上行带宽)</p>
<p>10路所需传输带宽(上行带宽)</p>
<p>D1</p>
<p>1.5Mbps</p>
<p>15Mbps</p>
<p>720P</p>
<p>(100W像素)</p>
<p>2Mbps</p>
<p>20Mbps</p>
<p>1080P</p>
<p>(200W像素)</p>
<p>4Mbps</p>
<p>40Mbps</p>
<p>即：理论上采用D1视频格式各地方监控所需的网络上行带宽至少为15Mbps；720P至少为20Mbps；1080P至少为40Mbps。</p>
<p>（2）监控中心</p>
<p>下行带宽=视频格式的比特率×监控点的摄像机的总路数之和</p>
<p>分辨率</p>
<p>每路所需传输带宽(下行带宽)</p>
<p>50路所需传输带宽(下行带宽)</p>
<p>D1</p>
<p>1.5Mbps</p>
<p>75Mbps</p>
<p>720P</p>
<p>(100W像素)</p>
<p>2Mbps</p>
<p>100Mbps</p>
<p>1080P</p>
<p>(200W像素)</p>
<p>4Mbps</p>
<p>200Mbps</p>
<p>即：理论上采用D1视频格式监控中心所需的网络下行带宽至少75Mbps，720P至少为100Mbps；1080P至少为200Mbps。</p>
<p>注意：</p>
<p>按照当前市场上的网络带宽利用率来计算，80%左右基本是极限。</p>
<p>例</p>
<p>手机远程访问设备，打开一个通道，720P的分辨率，码流值为2048kbps，上行和下行带宽都需要2Mbps，那么在保持画面流畅的状态下， ADSL宽带的带宽至少需要8Mbps。当尝试同时打开多路图像的时候，会出现卡顿或者无法打开的情况，建议使用副码流访问。相同环境下，副码流占用的带宽小，具体可进入设备的编码设置中调节。（树上鸟教育电气设计视频教程）</p>
<p>（3）网络摄像机常见故障解决方法</p>
<p>1、红外高清网络摄像机白天正常，夜晚没信号</p>
<p>　原因：电源供电不足，由于夜晚会开启红外灯，红外灯会消耗一定的电流，如果电源质量不好，就会导致供电不足，从而导致高清网络摄像机无法正常启动。</p>
<p>　解决方法：更换好一些的电源</p>
<p>2、高清网络摄像机不确定性的经常掉线</p>
<p>原因1：采用了集中供电方式，高清网络摄像机对电源适配器的要求比模拟机相对较高，需要较为稳定的电压和电流，而由于集中供电会造成电压和电流的不稳，所有会造成部分高清网络摄像机的重启或者启动不了。</p>
<p>解决方法：改成单独供电方式，采用好一些的电源</p>
<p>原因2:网线质量差，高清视频对网络传输速率的要求相对较高，所以网络丢包会表现的非常明显，比如经常掉线，严重的根本不出图像。</p>
<p>解决方法：采用国标纯铜网线</p>
<p>原因3：高清网络摄像机和交换机之间/或者交换机和交换机之间/或者交换机和PC/NVR之间的网线距离超过了100米。</p>
<p>解决方法：可以在中间不超过100米的地方增加一个带中继功能的交换机，或者采用光纤传输。</p>
<p>原因4：高清网络摄像机和局域网内的其他网络设备的IP地址有冲突。（树上鸟教育电气设计视频教程）</p>
<p>解决方法：修改高清网络摄像机或者其他网络设备的IP地址保证不要冲突。</p>
<p>原因5：网络交换机的带宽不足</p>
<p>解决方法：如果高清网络摄像机数量较，建议采用H3C的全千兆交换机，注意：不仅仅是中心交换机采用千兆的，就是说前端的交换机如果介入的摄像机数量超过了7台也建议采用千兆的。</p>
<p>网络视频监控系统重要知识点</p>
<p>1、什么是网络高清视频监控？</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390420003.png&amp;size=m" alt=""></p>
<p>网络视频系统通常指的是安全监视和远程监控领域内用于特定应用的IP监视系统，该系统使用户能够通过IP网络（LAN/WAN/Internet）实现视频监控及视频图像的录像、以及相关的报警管理。与模拟视频系统不同的是，网络视频系统采用网络，而不是点对点的模拟视频电缆，来传输视频及其他与监控相关的各类信息。</p>
<p>2、网络高清视频监控系统的哪些功能？</p>
<p>网络高清视频监控系统主要功能包括远程图像控制、录像、存储、回放、实时语音、图像广播、报警联动、电子地图、云台控制、数据转发、拍照、图像识别等。</p>
<p>3、网络高清视频监控可以应用于哪些方面？</p>
<p>主要应用于道路监控、小区监控、网吧监控、平安城市等行业，目前已经应用到各行各业。</p>
<p>4、目前主流的网络高清视频监控的产品有哪几种？</p>
<p>有两种，网络视频服务器（DVS）、网络摄像机（IPCAM）。</p>
<p>5、网络高清视频监控系统与传统视频监控系统有什么区别？</p>
<p>传统的闭路监控系统（包括以DVR为主的区域监控系统）采用视频线缆或者光纤传输模拟视频信号的方式，对距离十分敏感，且跨地域长距离传输不够经济便利（相比网络高清视频监控系统－网络高清视频监控），一般以局部的区域进行集中监控，远距离的传输一般采用点对点的方式进行组网，整个系统的布线工程大，结构复杂，功耗高，费用高，需要多人值守；整个系统管理的开放型和智能化程度较低。</p>
<p>网络高清视频监控系统采用灵活的租用方式（主要采用IP宽带网），多个用户可以共用一套中心控制平台，用户投入、使用简便，用户能远程进行浏览与控制，原则上任何可以上网的地方都可以进行浏览与控制。它还引入了许多新的数字化技术成果（如图像识别技术），弥补了传统视频监控系统的不足，提供了增值业务能力，扩展了功能和范围，提高了系统的性能和智能化。</p>
<p>6、网络高清视频监控系统与模拟闭路系统相比有什么优势？</p>
<p>网络高清视频监控系统系统的信息流和媒体流全程数字化并且相互独立，硬件和软件采用标准化、模块化和规模化的设计理念，系统设备的配置具有通用性强、开放性好、系统组态灵活、控制功能完善、数据处理方便、使用方便以及系统安装、调试和维修简单化的优点。同时，IP化、数字化产品的规模化将带来成本优势。</p>
<p>7、网络高清视频监控系统软件平台可实现的功能？</p>
<p>网络高清视频监控系统平台可实现集中监控管理、视频分发、报警联动管理、录像存储管理、系统日志管理、远程录像回放与查看等等。</p>
<p>8、模拟监控升级数字监控投入的成本需要多大？</p>
<p>原来有模拟监控系统可以通过开通接入网络线路及安装视频服务器等相关设备进行改造，实现网络高清视频监控系统的相关功能。这种情况下，用户需要投入的成本是其所负担的设备投入，主要是视频服务器的成本，由于视频服务器的种类与规格不同，具体的承担成本需与当地的客户经理协商。</p>
<p>9、使用网络高清视频监控系统，原有的设备如何处理？不增加使用成本吗？</p>
<p>原有设备可以利旧改造，但需要增加视频服务器、铺设接入线路和网络设备等相关设备。利旧改造仅仅涉及一次性费用，不会增加日常的使用成本。</p>
<p>10、安装客户端软件，还是使用浏览器就可以访问系统全部功能？</p>
<p>有CS/BS两种使用方式，即安装专用客户端以后使用或直接通过IE浏览器（需要下载相关插件）就可直接使用全部功能。</p>
<p>11、网络高清视频监控系统如何支持报警联动，并同时进行录像？</p>
<p>报警联动总的而言可以分成前端报警联动、平台报警联动、客户端报警联动。报警事件触发后通过声、光、电等报警输出设备在前端直接输出报警信息同时可以触发前端的报警录像；平台收到报警信息后可触发平台的报警联动：发送报警短信息和客户邮件、联动中心录像；客户端接到报警信号后可以立即反映在客户端的界面上向客户提示有报警事件发生。</p>
<p>12、网络高清视频监控系统视频图像存储有哪几种方式？</p>
<p>网络高清视频监控系统提供前端、中心、客户端三种存储方式。一般确定存储地点的原则如下：</p>
<p>前端存储：如果用户对存储图像实时性要求较高同时前端设备的可靠性能够得到保证；</p>
<p>中心存储：如果前端没有存储功能一般采用中心存储，另外中心存储可以做为前端存储的备份；</p>
<p>客户端存储：一般做为临时性的视频图像的存储：如抓拍、手动录像。</p>
<p>13、宽带用户新增网络高清视频监控系统是否要重新申请新的线路？能否使用已有的物理线路？</p>
<p>一般情况下网络高清视频监控系统可以和其它业务共享同一接入网络线路。申请新增网络高清视频监控系统不需要申请新的线路，利用原有的线路可以使用网络高清视频监控系统。但是如果原来是VPN等专用线路，需根据当地的组网情况做具体分析，但是从纯粹技术而言，只要在网络增加一定的资源配置，新增用户还是可以做到不增加线路而使用网络高清视频监控系统的业务。</p>
<p>14、网络高清视频监控系统提供地存储类型有哪些？</p>
<p>网络高清视频监控系统地存储功能按照存储位置特点分为：前端存储、中心存储、客户存储；前端存储就是将视频录像存储在DVR自带的硬盘中；中心存储将视频录像存储在中心平台的录像服务器所支持的硬盘陈列中或者是网络存储所支持的磁盘陈列中；客户端存储将视频录像存储在客户端浏览地监控机器中的磁盘。</p>
<p>15、视频监控行业对存储需求的特点？</p>
<p>视频监控对存储需求有如下几个特点：</p>
<p>1、对存储的容量需求弹性比较大，存储容量的多少随着画面质量的要求、画面尺寸的大小、画面的帧率、存储时间的长短等因素有关，这些因素的变化都会使存储容量产生较大变化。</p>
<p>2、对存储的性能要求不高，但是需要能够满足长时间的连续数据读写，数据流量大、同时访问请求的数量少。</p>
<p>3、数据保存周期长短不一，超过使用时间后可以重复使用。一般的监控场所数据保存一定时间（如10天），以后便可以删除或覆盖。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390420129.png&amp;size=m" alt=""></p>
<p>16、如何估算存储空间大小？</p>
<p>为视频监控方案选择合适的存储设备，首先需要估算存储容量需求。根据现在主流的压缩方式计算如下：</p>
<p>H.264压缩方式：D1清晰度，实时25帧， 500M/小时/路；</p>
<p>H.264压缩方式：HAFL D1清晰度，实时25帧，300M/小时/路；</p>
<p>H.264压缩方式：CIF清晰度，实时25帧，200M/小时/路；</p>
<p>17、网络高清视频监控系统数据传输的特点是什么？</p>
<p>网络高清视频监控系统数据流量大，实时性要求高。一般单路25帧/秒的CIF格式图像码流大小约384Kbps；单路25帧/秒的DI格式图像码流大小为384-1500Kbps，平均码流为900Kbps；由于在不同行业中，用户对于所需图像的实时性、流畅性要求不同，应根据实际情况具体问题具体分析。</p>
<p>18、如何对网络高清视频监控系统进行网络宽带规划？</p>
<p>可以把网络高清视频监控系统网络按照前端、中心服务平台和客户端划分为三部分：</p>
<p>1、 前端带宽：所需宽带进行估算；如一个前端只有一个视频服务器传输一路25帧/秒的DIF-图像，则所需宽带为384kbps。</p>
<p>2、平台：中心服务平台宽带主要有两部分，一部分为前端设备上传的视频流；另一部分为中心服务平台向客户端分发的视频流。</p>
<p>公网运营的中心服务平台宽带规划可以根据一个平台的满负荷前端数量50％的视频进行同时监看和并发的规模进行规划。</p>
<p>3、客户端：时间需要监看得视频路数决定。由于每路视频所需宽带可以估算，如一个视频服务器传输一路25帧/秒得CIP图像（静止图像），则所需宽带约为384kbps左右（如果考虑到图像的一般运动的，码流可能会提高到512kbps）。如果同时监看的路数为n，则客户端侧的宽带规划如下：n*512（kbps）。</p>
<p>19、网络高清视频监控系统对传输网络性能有哪些要求？</p>
<p>网络高清视频监控系统技术规范中对于传输网络的要求如下：</p>
<p>IP承载网络端到端通信质量要求如下：</p>
<p>承载协议 丢包率上限 网络延时上限</p>
<p>TCP 2/100 200ms</p>
<p>UDP 1/100 300ms</p>
<p>公安部《3111通用技术要求》规定监控中心内部及监控中心间互联的网络性能指标满足通信行业标准YD/T1171-2001中所规定的，其内容如下;</p>
<p>对于传输网络的质量（Qos）等级要求如下：</p>
<p>1、 网络时延上限应小于400ms。</p>
<p>2、 时延抖动上限应小于50ms。</p>
<p>3、 丢包率上限应小于1*10-3</p>
<p>20、专线接入的优点及缺点是什么？</p>
<p>专线接入方式主要有E1,SDH,ATM,FR等，专线接入优点主要有线路稳定，高带宽，高安全等；</p>
<p>缺点主要是客户群受限，一般客户很难接受专线昂贵的价格。</p>
<p>21、什么是组播技术？</p>
<p>IP组播（也称多址广播或多播）技术，是一种允许一台或多台主机（组播源）发送单以数据包到多台主机（一次的，同时的）的TCP/IP网络技术。组播作为一点对多点的通信，是节省网络带宽的有效方法之一。在网络音频广播的应用中，当需要将一个节点的信号传送到多个节点时，无论是采用重复点对点通信方式，还是采用广播方式，都会严重浪费网络带宽，只有组播才是最好的选择。组播能使一个或多个组播源只把数据包发送给特定的组播组，而只有加入该组播组的主机才能接收到数据包。目前，IP组播技术被广泛应用在网络音频/视频广播、AOD/VOD、网络视频会议、多媒体远程教育、"push"技术（如股票行情等）和虚拟现实游戏等方面。</p>
<p>22、什么是NAT技术？</p>
<p>NAT（Network Address Translation）的功能，就是指在一个网络内部，根据需要可以随着自定义IP地址，而不需要经过申请。在网络内部，各计算机间通过内部的IP地址进行通讯。而当内部的计算机要与外部internet网络进行通讯时，具有NAT功能的设备（比如：路由器）负责将其内部的IP地址转换为合法的IP地址（即经过申请的INTERNET IP地址）进行通信。</p>
<p>23、为什么视频数据一般都用UDP协议进行传输？</p>
<p>TCP（Transmission Control Protocol,传输控制协议）是基于连接的协议，在正式收发数据前，必须和对方建立可靠的连接。一个TCP连接必须要经过三次"对话"才能建立起来。</p>
<p>UDP（User Data Protocol,用户数据报协议）是与TCP相对应的协议。它是面向非连接的协议，它不与对方建立连接，而是直接就把数据包发送过去。</p>
<p>从上述描述我们可以看出TCP是面向连接的可靠的协议，而UDP是面向非连接的不可靠的协议，两者从传送速度上相比较，UDP的传输速度较TCP更高，更加符合网络高清视频监控系统对于数据传输实时性的要求。</p>
<p>24、前端设备的作用是什么？</p>
<p>在网络高清视频监控系统系统中，前端子系统的作用主要实现对监控点的音、视频数据、开关量报警数据的采集以及音视频编码压缩和网络传输。前端设备主要包括：</p>
<p>1、图像编码设备（数字视频录象机DVR、嵌入式视频服务器、网络摄像机）；</p>
<p>2、音视频采集设备（摄像机、拾音器）；</p>
<p>3、云台/云台解码器；</p>
<p>4、报警输入/输出设备（开关量设备、报警器等）；</p>
<p>25、什么是IP－CAMERA、视频服务器、数字视频录像机？</p>
<p>1、IP-CAMERA</p>
<p>IP-CAMERA是集成视频服务器和摄像机的功能为一体的数字视频设备；IP-CAMERA网络摄像机一般有内置Web服务的数字摄像机和录音设备，直接与以太网（有线、无线）相连。用户可通过标准Web浏览器观看和收听网络摄像机传送过来的视频和声音。</p>
<p>2、视频服务器（DVS）</p>
<p>采用数字压缩算法，实现音视频信息的数字压缩和网络传输与控制设备。视频服务器一般为嵌入式视频服务器设备，采用嵌入式CPU的硬件设计，体积小，结构方便，一般有为1路、2路和4路音视频输入，有些可以实现双向对讲功能。目前有些DVS设备也可以内置小容量的硬盘。</p>
<p>3、数字视频录像机（DVR）;</p>
<p>DVR即是Digital Video Recorder,数字视频录像机或数字硬盘录像机，我们习惯上称为硬盘录像机。结合标准接口的数字存储介质，采用数字压缩算法，实现音视频信息的数字记录、监视与回放，并可带有系统控制功能的视频设备或视频网络传输与监控的设备（一般带有独有的功能操控面板）。目前韩国、台湾的DVR产品在全球市场上占有很高的市场份额，有PC工控机式的，也有嵌入式的，高路数方面PC的多以些，低路数上嵌入式的多一些。韩国以PC为主，台湾以低路数的嵌入式为主。中国大陆的DVR也有PC式和嵌入式的。以前的DVR是不带联网功能的，以本地监控为主。根据用户的要求和技术的发展，目前不少厂家的DVR设备也具有了联网能力，根据用户的要求，带联网功能的DVR一般在本地观看和存储的是D1格式的图像，上传到中心的图像是CIF格式的。一般DVR输入不超过16路，尽量不要让DVR同时工作在不间断录像和网络传输应用之中。</p>
<p>26、端设备按产品形态可分为：</p>
<p>1、IPC（一体化网络摄像机）</p>
<p>2、DVS（不带存储接口的视频服务器）</p>
<p>嵌入式</p>
<p>3、 DVR（带存储接口的视频服务器）</p>
<p>嵌入式 PC架构+板卡终端设备按照编码方式可分为：</p>
<p>H．264\MPEG4\MPEG2\M-JPEG</p>
<p>27、网络高清视频监控系统前端设备如何避免雷击？</p>
<p>对于室外安装的网络高清视频监控系统前端设备，包括视频编码设备、摄像机、云台以及云台解码器等，室外的设备还必须遵循：</p>
<p>1、防雷接地与安全防护设计应符合现行国家标准《工业企业通信接地设计规范》，《建筑物防雷设计规范》和《30MHZ-1GHZ声音和电视信号的电缆分配系统》的规定；</p>
<p>2、室外摄像机头防雷要求：室外型摄像枪（带云台）在编码器前端需安装防雷器（如三合一型DXS220AC或DXS24DC，包含电源、视频接口、控制信号的防雷器），作为室外型前端设备的保护；</p>
<p>3、视频服务器：视频服务器的每路视频输入端口安装10f/BNC信号SPD; 在信号控制线上每路安装24DCP信号SPD; 在雷击高发地区，可选用安装三合一型防雷器作为保护</p>
<p>28、网络高清视频监控系统前端设备如何安装？</p>
<p>网络高清视频监控系统系统前端设备的安装需要针对不同的设备具体对待，在安装前要仔细阅读产品的说明书以及安装要求。对于编码终端设备安装的标准步骤如下：</p>
<p>1、 开箱确认产品说明中列举的设备完备性。</p>
<p>2、 检查电源电压，防止出现电压不匹配导致设备损坏。</p>
<p>3、 确认设备工作环境是否符合产品说明书中对设备的工作温度、电源等环境要求。</p>
<p>4、 对照设备说明书加电测试，确认各种工作状态指示灯能够正常运行。</p>
<p>29、网络高清视频监控系统的客户端以及监控中心设备的区别是什么？</p>
<p>网络高清视频监控系统的客户端一般从系统结构角度泛指用户侧的视频显示和组成。网络高清视频监控系统的客户端可以简单到就是一台可以上网的计算机，也可以复杂到一套监控中心子系统，主要有计算机、显示器、大屏幕控制器、存储设备、稳压电源、网络设备、视频矩阵、手控键盘灯组成。</p>
<p>30、网络高清视频监控系统监控中心设备的大屏如何控制？</p>
<p>高端用户对监控中心大部分都需要电视墙显示，在网络高清视频监控系统监控中心通过数字解码矩阵进行数字信号向模拟信号的转换，最终实现对前端设备的选择切换到电视墙。</p>
<p>31、如何安装视频服务器以及云台、报警设备？</p>
<p>云台的作用是使一般固定的摄像头产生转动；而报警设备一般起安全防范作用，一旦发生异常情况就产生电子开关的短路和开路信号，这个信号被捕捉到后，视频服务器可以产生本地的报警输出信号可以进行各种目标的控制；如异地的联动报警，报警信号远程传输给监控人员，引起人员对事件的警示并可以根据实际情况做出对应的接警处理。</p>
<p>32、摄像机分类及特点？</p>
<p>摄像机按功能大致分为以下几类：枪机、半球型摄像机、一体化摄像机、红外日夜两用摄像机、高速球摄像机、网络摄像机。</p>
<p>1、枪机：价格便宜，但不具备变焦和旋转功能，只能完成一个角度固定距离的监视、隐蔽性较差；</p>
<p>2、半球型摄像机：具有一定的隐蔽*，同时外形小巧、美观，可以吊装在天花板上，弥补了枪机的某些不足；</p>
<p>3、一体化摄像机：内置镜头、可以自动聚焦，对镜头控制方便，安装和调试简单；</p>
<p>4、红外日夜两用摄像机：将摄像机、防护罩、红外灯、供电散热单元综合成为一体的摄像设备，具有夜视距离远、隐蔽性强、性能稳定等优点；</p>
<p>5、高速球摄像机：集一体机化摄像机和云台于一身的设备，另外具有快速跟踪、360°水平旋转、无监视盲区和隐私区域遮蔽等特点和功能；</p>
<p>6、网络摄像机：又称IP camera。它可以将图像采集后进行数字处理并加以压缩，通过IP网络将压缩的视频信号送入到服务器侧或客户端侧；在服务器端或客户端侧，通过软件就可以实时查看远端的图像。</p>
<p>33、摄像机选择时需要关注哪些指标？</p>
<p>1、摄像机的芯片时CCD还是CMOS？目前监控摄像机大多数情况下采用CCD芯片；</p>
<p>2、CCD芯片的大小：目前市场上主要有1英寸、2/3英寸、1/2英寸、1/3英寸、1/4英寸，其中1/3英寸和1/4英寸的CCD芯片居多，芯片越大图像质量越好，价格越贵；</p>
<p>3、CCD摄像头的像素：像素越大，图像越清晰；</p>
<p>4、CCD的水平分辨率：分辨率是用电视线（简称线tv lines）来表示的。彩色摄像机的典型分辨率是在320到500电视线之间，主要有330线、380线、420线、460线、500线等不同档次；</p>
<p>5、镜头：根据物距、成像大小计算镜头焦距，选择合适的焦距镜头如标准镜头、长焦距镜头、短焦距镜头，同时还需要考虑镜头的成像尺寸应与摄像机CCD靶面尺寸相一致，另外还有镜头分辨率、光圈等因素；</p>
<p>6、最低照度：也称为灵敏度，是CCD对环境光线的敏感程度，或者说是CCD正常成像时所需要的最暗光线。照度的单位是勒克斯（LUX）,数值越小，表示需要的光线越小，摄像头也越灵敏。月光级和星光级等高增感度摄像机可工作在很暗条件，1～3lux属一般照度。</p>
<p>月光型：正常工作所需照度0.1lux左右；</p>
<p>星光型：正常工作所需照度0.01lux以下；</p>
<p>红外型：采用红外灯照明，在没有光线的情况下也可以成像（黑白）；</p>
<p>7、信噪比：典型值为48db。若为50db,则图像有少量噪声，但图像质量良好；若为60db,则图像质量优良，不出现噪声；</p>
<p>8、其他因素：如自动增益和白平衡等。</p>
<p>34、红外摄像机该如何选择？</p>
<p>红外摄像机主要用于目标需要日夜监视，特别强调夜间需要监视，而目标周围环境又没有太强光线的场景条件下。具体选择主要根据摄像机和观察目标之前的距离，一般的红外摄像机的距离范围20-100米，距离越远价格越贵，同时功耗也大。</p>
<p>另外，选择红外摄像机时应该考虑红外灯电源的开关控制问题。这个问题可以采用手动和自动两种控制方式。</p>
<p>35、什么情况下网络高清视频监控系统设备需考虑集成防雷设备？</p>
<p>严格意义上来说，监控系统都需要安装防雷设备，无论是室内监控还是室外监控，但因每个行业乃至每个单位经济状况不同以及监控需求的安全等级不一样，防雷设备的集成因情况而异，通常情况下需要考虑如下的防雷因素：</p>
<p>1、前端设备的防雷</p>
<p>前端设备有室外和室内安装两种情况，安装在室内的设备一般不会遭受直击雷击，但需考虑防止雷电过电压对设备的侵害，而室外的设备则同时需考虑防止直击雷击，如前端设备摄像头应置于接闪器（避雷针或其它接闪导体）有效保护范围之内。为防止电磁感应，沿杆引上摄像机的电源线和信号线应穿过金属管屏蔽。信号线传输距离长，耐压水平低，极易感应雷电流而损坏设备，为了将雷电流从信号传输线传导入地，信号过电压保护器须快速响应，在设计信号传输线的保护时必须考虑信号的传输速率、信号电平、启动电压以及雷电通量等参数。</p>
<p>2、传输线路的防雷</p>
<p>监控系统的传输线路主要是传输信号线的电源线。控制信号传输线和报警信号传输线一般选用铜芯屏蔽软线，架设（或敷设）在前端与终端之间。从防雷角度看，直埋敷设方式防护效果最佳，架空线最容易遭受雷击，并且破坏性大，波及范围广，为避免首尾端设备损坏，架空线传输时应在每一电杆上做接地处理，架空线缆的吊线和线路中的金属管道均应接地。中间放大器输入端的信号源和电源均应分别接入合适的避雷器。</p>
<p>3、终端设备的防雷</p>
<p>在监控系统中，监控室的防雷最为重要，应从直击雷防护、雷电波侵入、等电位连接和电涌保护多方面进行。由于有80%雷击高电位是从电源线侵入的，为保证设备安全，一般电源上应设置三级避雷保护。在视频传输线、信号控制线，入侵报警信号线进入前端设备之前或进入中心控制台前应加装相应的避雷保护器。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/EF0ZxB7lomWDrjEfFV1IgA">https://mp.weixin.qq.com/s/EF0ZxB7lomWDrjEfFV1IgA</a></p>]]></description><guid isPermaLink="false">/archives/8224e6b7-7adb-4865-9b12-c04b3afeb354</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:58:18 GMT</pubDate></item><item><title><![CDATA[100米、300米远距离HDMI信号如何实现？]]></title><link>https://wiki.dgfygk.com/archives/b66addf4-67ea-4052-a1c3-a3b5166cc490</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=100%E7%B1%B3%E3%80%81300%E7%B1%B3%E8%BF%9C%E8%B7%9D%E7%A6%BBHDMI%E4%BF%A1%E5%8F%B7%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%EF%BC%9F&amp;url=/archives/b66addf4-67ea-4052-a1c3-a3b5166cc490" width="1" height="1" alt="" style="opacity:0;">
<p>很多朋友提到如何实现HDMI信号远距离传输，这个是可以的，今天我们一起来看下。</p>
<p>首先我们了解下最新的HDMI2.0b的特点：</p>
<p>1.支持（HDR）高动态范围视频传输</p>
<p>2.高达18Gbps宽带</p>
<p>3.分辨率增至4K，3840*2160分辨率和50FPS，60fps帧率。</p>
<p>4.高达32个音频通道，畅想身临其境的多维音频体验。</p>
<p>5.高达1536khz的音频采样率，提供最高的音频保真度。</p>
<p>6.同步传输双视频流，让不同用户在同一屏幕上观看不同视频。</p>
<p>7.同步传输多个音频流至多个用户（最高可达4个）</p>
<p>8.提供广角影院21:9宽高比的全新体验</p>
<p>9.动态自动声画同步。</p>
<br>
<p>一般的HDMI信号传输<strong>30米</strong> 以下，那么对于<strong>质量一般的线材大约传输的最大距离15米至20米左右</strong> ，对于质量好的线比如秋叶原的HDMI线可以传输25米以上，甚至30米，如果只是单纯的传输HDMI信号到显示设备上，HDMI信号不经过其他设备的话，总长度在30米以内的话，建议距离越长需要选择越好的线材。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390347045.png&amp;size=m" alt=""></p>
<br>
<p><strong>一、HDMI延长器</strong></p>
<p><strong>1、HDMI网络转换延长器</strong></p>
<p><strong>1.1、HDMI信号不经过其他设备（传输120米)</strong></p>
<p>HDMI信号一般无法直接用HDMI线材传输到30米以上，市场上也几乎没有30米以上的HDMI成品线，然而我们可以用网线延长信号的话，这种情况下如果只是单纯的传输HDMI信号到显示设备上，HDMI信号不经过其他设备的话，我们可以采用网线延长信号的方法，用HDMI网线延长器来将信号延长，HDMI网线延长器分为发送端（HDMI输入，网线输出）和接收端（网线输入，HDMI输出），可以延长100米（5类网线）、120米（6类网线）。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390347052.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390347261.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390347480.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390348091.png&amp;size=m" alt=""></p>
<p><strong>1.2、HDMI信号中间需要经过其他设备（传输60米）</strong></p>
<p>如果从信号源到显示屏中间需要经过HDMI图像处理设备的话（如HDMI分配器，HDMI切换器，HDMI矩阵等等）。</p>
<br>
<p>一般情况下，建议将HDMI图像处理设备放在中间，使得信号源到HDMI图像处理设备的距离和HDMI图像处理设备到显示屏之间的距离尽量相等，如果是HDMI图像处理设备具有信号增益功能的话，可以随意改变输入和输出HDMI图像处理设备的线材长短，可以延长信号传输的距离，设备支持输入30米的HDMI线，支持输出30米的HDMI线，一共可以传输60米，产品本身带有增强和放大HDMI信号，能够保证图像高质量和长距离的传输。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390348396.png&amp;size=m" alt=""></p>
<br>
<p><strong>2、HDMI IP延长器</strong></p>
<p>HDMI IP延长器带ip协议的多宽带的线材在里面，可以实现ip拨号功能，免除网线的使用。</p>
<p>HDMI IP延长器一发多收，通过路由器或者交换机最多可以做一个发射TX，254接收端RX的信号同时传输。</p>
<p>一对一传输链接方式</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390348552.png&amp;size=m" alt=""></p>
<br>
<p>一对多传输链接方式</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390348621.png&amp;size=m" alt=""></p>
<br>
<p>多对多传输方式</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390348688.png&amp;size=m" alt=""></p>
<br>
<h3 id="hdmi-ip延长器可广泛应用于">HDMI IP延长器可广泛应用于</h3>
<ul>
 <li>
  <p>1、将一台HDMI切换器与远程设备（如蓝光和媒体播放器）连接，打造您自己的无线家庭影院。</p>
 </li>
 <li>
  <p>2、会议室的理想选择，可分享简报内容和视频会议到四组不同的地点，单机传输距离达120米，分级传输更远。</p>
 </li>
 <li>
  <p>3、可成为数字标牌架构的一部分，在展厅、精品店或汽车经销店无线广播广告或促销视频。</p>
 </li>
</ul>
<br>
<p><strong>3、HDMI 光纤延长器</strong></p>
<p>光纤延长器为了解决HDMI信号较长距离的传输时，发生的图像马赛克，颜色失真，无法传输等问题，是专为网络平板显示及大屏幕幕墙显示广告工程、工业自动化控制、医疗设备、安防监控、多媒体教学等显示系统工程中高质量HDMI视频信号长距离传输的需求而专业设计的集HDMI信号转换、驱动、接收和还原功能为一身的信号传输系统，传输距离最远可达到20公里。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390348742.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390348964.png&amp;size=m" alt=""></p>
<br>
<p><strong>二、HDMI光纤线</strong></p>
<p><strong>HDMI光纤线(100米-300米）</strong></p>
<p>光纤HDMI高清线因为采用了光纤传输媒介，长距离的信号传输衰减基本可以忽略，最长传输距离可达300m以上。对于HIFI、投影仪、视频监控系统来说，传统铜缆的长距离传输信号失真，直接导致设备的不可用或者糟糕的体验。而用户使用光纤HDMI线，布置这些设备时完全不用担心传输信号的衰减或失真，可以完全按照用户实际使用需求随心布置系统设备。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390349104.png&amp;size=m" alt=""></p>
<br>
<p>HDMI线的优势在于即使面临较长距离的埋线、布线连接，仍可以保证无损地传输4k图像及高保证的音频，而HDMI高清线超过25米以上规格就需要添加信号放大器，受限长度影响，无法保证无损传输影音信号。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/xwUfpYxnBRHwIU8JKclgGg">https://mp.weixin.qq.com/s/xwUfpYxnBRHwIU8JKclgGg</a></p>]]></description><guid isPermaLink="false">/archives/b66addf4-67ea-4052-a1c3-a3b5166cc490</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:57:57 GMT</pubDate></item><item><title><![CDATA[用网线当电源线这个事，你也做过吗？网线8芯线各自作用是什么？]]></title><link>https://wiki.dgfygk.com/archives/1a119fd9-201f-4d88-988b-cdfa7a94c4e0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%94%A8%E7%BD%91%E7%BA%BF%E5%BD%93%E7%94%B5%E6%BA%90%E7%BA%BF%E8%BF%99%E4%B8%AA%E4%BA%8B%EF%BC%8C%E4%BD%A0%E4%B9%9F%E5%81%9A%E8%BF%87%E5%90%97%EF%BC%9F%E7%BD%91%E7%BA%BF8%E8%8A%AF%E7%BA%BF%E5%90%84%E8%87%AA%E4%BD%9C%E7%94%A8%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;url=/archives/1a119fd9-201f-4d88-988b-cdfa7a94c4e0" width="1" height="1" alt="" style="opacity:0;">
<p>在实际小型监控系统中，经常会有人把网线的8芯分4芯，一半做水晶头作为摄像机的网络传输，一半做12V作为电源传输，这个事情相信大部分人都做过吧。</p>
<p>既然使用的人不少，这样的传输肯定是有效的。但是这样做不影响使用，但是肯定是不提倡的，因为这么安装摄像机端的电源电压容易导致供电不足的问题，有时候会影响到红外灯这样夜视效果会打折扣。但是似乎很多人这样做，大家都见怪不怪了。</p>
<p>当然这种方法只能用在小型的监控项目上，但是越往后可能会越正规了，因为POE供电方式越来越普及了。如果非要这么用，网线传输的电源也是直流12V，千万不要接交流220V电，那样真的坑自己也是坑同行了！</p>
<p><strong>网线8芯线各自作用是什么？</strong></p>
<p>网线里面的8芯线是由4对不同颜色对绞在一起的传输线组成的，所以也被称为双绞线。相信你也会好奇，为何要扭绞在一起呢。如果你观察仔细的话，会发现每一对缠绕的紧密程度是不一样的，每一对的绞距也是不一样的。</p>
<p>这么做的目的是改变线缆原有的电子特性，抵御外界电磁干扰，降低自身信号串扰，可以达到一个最佳的传输效果。</p>
<br>
<p>现在用到的网线有超五类网线和六类网线。超五类是传输百兆网络的；六类网线是传输千兆网络的。双绞线的一个最大的缺点就是传输距离不超过100米。关于双绞线的线序和制作方法。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390327188.png&amp;size=m" alt=""></p>
<p>网线里8芯线各自作用是什么：
 <br>
 1 输出数据 (+) 2 输出数据 (-) 3 输入数据 (+) 4 保留网络传输 5 保留网络传输 6 输入数据 (-) 7 保留网络传输 8 保留网络传输</p>
<p>所以从上面就可以看出，1和2是用来发送数据，3和6是用来接收数据的。而且网线除了在做水晶头位置外，中间其余位置都必须呈双绞状态，双绞的作用上面已经说了，这里不再赘述。所以那些说"只要两头做成一样就可以的"说法是不对的。</p>]]></description><guid isPermaLink="false">/archives/1a119fd9-201f-4d88-988b-cdfa7a94c4e0</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:57:36 GMT</pubDate></item><item><title><![CDATA[什么是配线架？为什么要使用配线架？]]></title><link>https://wiki.dgfygk.com/archives/0170b571-a714-47af-b277-41596498e77b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AF%E9%85%8D%E7%BA%BF%E6%9E%B6%EF%BC%9F%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%BD%BF%E7%94%A8%E9%85%8D%E7%BA%BF%E6%9E%B6%EF%BC%9F&amp;url=/archives/0170b571-a714-47af-b277-41596498e77b" width="1" height="1" alt="" style="opacity:0;">
<p>说到弱电工程施工，自然免不了布线二字。在弱电布线当中，除了需掌握布线技巧之外，对于配件我们也需要清楚了解。比如说什么是配线架？为什么要使用配线架？下面我们一同前去了解下。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390279986.png&amp;size=m" alt=""></p>
<br>
<p>什么是配线架</p>
<p>用于终端用户线或中继线，并能对它们进行调配连接的设备。配线架是管理子系统中最重要的组件，是实现垂直干线和水平布线两个子系统交叉连接的枢纽。配线架通常安装在机柜或墙上。通过安装附件，配线架可以全线满足UTP、STP、同轴电缆、光纤、音视频的需要。在网络工程中常用的配线架有双绞线配线架和光纤配线架。根据使用地点、用途的不同，分为总配线架和中间配线架两大类。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390280064.png&amp;size=m" alt=""></p>
<p>用途</p>
<p>配线架的定位是在局端对前端信息点进行管理的模块化的设备。前端的信息点线缆(超5类或者6类线)进入设备间后首先进入配线架，将线打在配线架的模块上，然后用跳线(RJ45接口)连接配线架与交换机。</p>
<p>总体来说，配线架是用来管理的设备，比如说如果没有配线架，前端的信息点直接接入到交换机上，那么如果线缆一旦出现问题，就面临要重新布线。此外，管理上也比较混乱，多次插拔可能引起交换机端口的损坏。配线架的存在就解决了这个问题，可以通过更换跳线来实现较好的管理。</p>
<p>用法和用量主要是根据总体网络点的数量或者该楼层(以及相近楼层，这要看系统图是怎么设计的)的网络点数量来配置的。不同的建筑，不同系统设计，主设备间的配线架都会不同。</p>
<p>如，一栋建筑只有四层，主设备间设置在一层，所有楼层的网络点均进入该设备间，那么配线架的数量就等于该建筑所有的网络点/配线架端口数(24口、48口等)，并加上一定得余量;如果，一栋建筑有9层，主设备间设置在4层，那么为了避免线缆超长，就可能每层均设有分设备间，且有交换设备。那么主设备间的配线架就等于4层的网络点数量/配线架端口数(24口、48口等)。</p>
<p>配线架是管理子系统中最重要的组件，是实现垂直干线和水平布线两个子系统交叉连接的枢纽。配线架通常安装在机柜或墙上。通过安装附件，配线架可以全线满足UTP、STP、同轴电缆、光纤、音视频的需要。在网络工程中常用的配线架有双绞线配线架和光配线架。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390280188.png&amp;size=m" alt=""></p>
<br>
<p>为什么要使用配线架</p>
<p>1、从设计角度出发</p>
<p>综合布线有一点要求不知道大家注意过没有，就是信息点要求数据、语音点在必要的时候可进行互换，也就是说同一个语音点，在不改变布线的情况下通过跳线的选择来确定是语音还是数据。这样在布线的时候语音点和数据点是一样的，设计很方便，不需要分别考虑，采用统一设备即可。</p>
<p>如果成本不是重要的考虑因素，把全部的信息点都接到rj45配线架上，然后通过跳线决定是跳到交换机上还是语音配线架上(可以是110的或者是stg以及其他类型的语音模块上)。当然这是不考虑成本的一种设计，也是一种彻底的不考虑终端是语音还是数据的设计。实际应用的情况还是非常少的。大多数的情况还是考虑语音和数据的区别的，具体的办法也有很多种.</p>
<p>2、从施工角度出发</p>
<p>大型的工程信息点数非常多，在交换设备和布线系统之间采用跳线连接可以是施工管理变得更加方便，机柜内也会非常的整洁，毕竟跳线要短的多。检测也方便了很多。</p>
<p>3、管理维护方面</p>
<p>这方面其实大型用户都很关心，配线架可以有效的提高工作效率，减少查错的时间。对于更换终端的使用方式也提供的便利快捷的方法。</p>
<p>配线架有什么优势</p>
<p>1、传统方法测试用户线路要通过手工操作完成，采取集中上线方式后可利用现有自动测试系统对远端局用户线路进行测试，从而实现无人值守或少人值守。</p>
<p>2、采取集中上线方式微机操作可与计算机综合管理系统合起来，实现操作自动化。</p>
<p>3、集中上线可选择最短路径就近跳线，只需2------3m即可，节省大量跳线。传统方法跳线一般得十几米，甚至几十米。</p>
<p>4、遇移机、改号和工程倒线，传统方法需拆动大量跳线，工作繁重，配线架卡接点经多次卡接，会造成卡点松弛。采取集中上线方式只需微机操作就可完成，减少了卡接点的磨损，延长了配线架寿命，同时也减少了障碍的发生。</p>
<p>5、集中上线方式配线架跳线一次完成，避免了传统方法多次无规则跳线造成的混乱，使配线架外观美观整齐。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390280306.png&amp;size=m" alt=""></p>
<p>以上就是什么是配线架以及为什么要使用配线架的全部内容了，其实配线架通俗点来说就是用来规范网络布线的一种设备，一般在网络安装(如局域网等)中用到，和交换机配合使用，安装在机柜内的。</p>
<p>首先需要考虑的是光缆余留量：应当保留一定量的光缆以保证在配线架底盘上产生纤维强度的膜。这样就能防止光纤被扯出配线架，同时避免电缆承受过高的应力。</p>
<p>其次是其保护性能：光纤配线架应当为连接器提供足够的保护以防止偶然接触。</p>
<p>最后是其适用性：光纤配线架应当能够允许除去现有的连接器并添加新的连接器或光纤，它也必须能够存储并识别来自水平或主干电缆中的非连接器光纤。</p>]]></description><guid isPermaLink="false">/archives/0170b571-a714-47af-b277-41596498e77b</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:57:21 GMT</pubDate></item><item><title><![CDATA[网管型和非网管型交换机有什么区别]]></title><link>https://wiki.dgfygk.com/archives/a6a36331-1743-48c6-bd44-e2eabccf913e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%AE%A1%E5%9E%8B%E5%92%8C%E9%9D%9E%E7%BD%91%E7%AE%A1%E5%9E%8B%E4%BA%A4%E6%8D%A2%E6%9C%BA%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB&amp;url=/archives/a6a36331-1743-48c6-bd44-e2eabccf913e" width="1" height="1" alt="" style="opacity:0;">
<p>网管型和非网管型交换机有什么不同，这个在选用交换机的时候经常会遇到，网管型交换机比非网管型交换机的功能更强大。它通常为监视连接状态、STP、其它类似QOS的高级性能以及VLAN等提供简单网络管理协议(SNMP)。这些特征随厂商和模式的不同而不同。网管型交换机比非网管型交换机贵很多，配置起来也比非网管型交换机耗时要多。下面详细介绍网管型和非网管型以太网交换机的不同之处。</p>
<p><strong>一、非网管型交换机</strong></p>
<p>非网管型交换机的缺点：</p>
<p>1、非网管型工业交换机功能有限，适合小型网络中。</p>
<p>2、不支持ARP防护，ARP攻击不是病毒因而几乎所有的杀毒软件对之都无可奈何；但它却胜似病毒---因为它轻可造成通信变慢、网络瘫痪，重会造成信息的泄密。</p>
<p>3、不支持mac地址的绑定。</p>
<p>4、不支持vlan的划分，在非网管的交换机上连接的终端用户处于同一广播域中，会爆发广播风暴，不能对其进行防护和抑制。使整个网络出现拥塞、阻断、泛洪，导致整个网络瘫痪。</p>
<p>5、不支持基于流量的控制。</p>
<p>6、数据传输的可靠性差，出现丢包现象严重。</p>
<p>7、组装单一，不能应用在大中型网络中，对网络升级、扩展存在大的局限。</p>
<p>8、管理不便，硬件故障率比较大。</p>
<br>
<p>非网管型交换机优点：</p>
<p>1、价格便宜，节省开支。</p>
<p>2、端口数量密集。</p>
<p>3、用户使用灵活。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390293585.png&amp;size=m" alt=""></p>
<p><strong>二、网管型以太网交换机</strong></p>
<p>网管型以太网交换机的优点</p>
<p>1、背板带宽大，数据转发速度更快。</p>
<p>2、组网灵活，应用大中型网络的接入层。</p>
<p>3、提供的端口灵活，根据网络的应用选择不同的接口形式如：SFP、GE、快速以太口、以太口等。</p>
<p>4、支持vlan的划分，用户可以针对不同的应用进行区域划分，有效的对网络进行控制和管理。进步抑制广播风暴。5、可网管交换机的数据吞吐量(Throughput)大、包丢失率(PacketLoss)小、延迟(Latency)低。</p>
<p>6、可以基于源、目的、网段进行数据信息流的控制。</p>
<p>7、链路聚合可以让交换机和交换机以及交换机和服务器之间通过多个以太网端口绑定在一起，实现负载均衡。</p>
<p>8、具有ARP的防护功能，进步减少网络的ARP欺骗。</p>
<p>9、具有MAC地址的绑定。</p>
<p>10、端口镜像功能可以将一个端口的流量和状态复制到交换机的另一个端口，用于监管。</p>
<p>11、支持DHCP的功能。</p>
<p>12、访问控制列表它可以对IP数据包进行控制，比如限制它的流量、出入以及提供QoS等等。</p>
<p>13、具有较好的安全性能：交换机都可以进行MAC地址的过滤、MAC地址锁定，并可以构建静态的MAC转发表。</p>
<p>14、能够支持IEEE802.1Q和基于端口技术的VLAN。而IEEE802.1QVLAN中涉及的GVRP(GARP，VLAN注册协议)和GMRP(GARP组播注册协议)也被广泛地支持。</p>
<p>15、具有SNMP功能，更能对网络实现很好的管理和控制。</p>
<p>16、易于扩展，灵活应用，可以通过网络管理软件进行管理，也可以通过其本身的访问控制对其进行远程访问。增加网络的安全性和可控制性。</p>
<br>
<p>非网管型交换机使用简单，但没法配置，网管型有web管理页面，可以通过ip登录进web管理页面对交换机做配置(分配端口vlan、设置端口速率等等)。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/xUm-8o1K5CLE5zaeT9gpAg">https://mp.weixin.qq.com/s/xUm-8o1K5CLE5zaeT9gpAg</a></p>]]></description><guid isPermaLink="false">/archives/a6a36331-1743-48c6-bd44-e2eabccf913e</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:56:51 GMT</pubDate></item><item><title><![CDATA[常用网络命令ping、arp、tracert、route的详细用法，新手进阶必看！]]></title><link>https://wiki.dgfygk.com/archives/fd48f3cc-9520-4264-aa58-c6bda9cf3073</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B8%B8%E7%94%A8%E7%BD%91%E7%BB%9C%E5%91%BD%E4%BB%A4ping%E3%80%81arp%E3%80%81tracert%E3%80%81route%E7%9A%84%E8%AF%A6%E7%BB%86%E7%94%A8%E6%B3%95%EF%BC%8C%E6%96%B0%E6%89%8B%E8%BF%9B%E9%98%B6%E5%BF%85%E7%9C%8B%EF%BC%81&amp;url=/archives/fd48f3cc-9520-4264-aa58-c6bda9cf3073" width="1" height="1" alt="" style="opacity:0;">
<p>网络相关的从业人员，都需要面对检测和解决网络故障的各种问题，实际案例中因为网络导致的故障也是最多的，今天我们和大家一起来学习一下解决网络故障时使用最多的四个网络命令。希望对大家以后的实际工作中的故障排除起到作用。</p>
<p><strong>1、Ping命令的用法：</strong></p>
<p>ping命令是Windows系统自带的一个用于验证计算机之间连接的可执行命令。它是基于TCP/IP协议、通过发送和接收数据包来检测两台计算机间的连接状况。</p>
<p>所以当出现网络故障的时候，我们就可以用ping命令来预估故障点，如果ping命令没有成功，那么就该考虑硬件连接是否出错，比如网卡设置是否正确、比如网线是否连通，再考虑IP地址设置是否正确，具体使用方法就是调出命令提示符窗口，然后Ping IP地址。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286152.png&amp;size=m" alt=""></p>
<p>调出命令提示符窗口的方法，可以在开始---运行中输入CMD，（或者Win键+R，再输入CMD）就可以调出了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286306.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286345.png&amp;size=m" alt=""></p>
<p>我们可以看到上图中，返回的数值包括字节、时间和TTL。时间是表示和这个地址连接的时间长短，数值越小表示时间越快。TTL值表示生存时间（Time To Live）,指数据被路由器丢弃之前允许通过的网段数量。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286449.png&amp;size=m" alt=""></p>
<p>我们从上图也可以看出默认情况下只会发送4个数据包，如果要返回多个数据包，可以通过ping -n来实现。如下图：</p>
<p>如果要不间断地返回数据包，那么就用ping -t来实现，这样就不断返回数据，直到管理员手动关闭，关闭的终止快捷键是Ctrl+C。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286488.png&amp;size=m" alt=""></p>
<p>更多ping命令参数的应用，我们可以在命令行里输入：ping/? 能够查看到。</p>
<br>
<p><strong>2、ARP命令的用法：</strong></p>
<p>ARP命令是地址解析协议，它也是一种TCP/IP协议，作用是根据IP地址来获取设备的MAC地址。经常用到的包括查询、添加和删除记录。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286543.png&amp;size=m" alt=""></p>
<p>我们在局域网中的一台电脑的命令提示符中输入：arp -a</p>
<p>我们就可以读取IP地址和物理MAC地址的一一对应关系。（我们这里演示的是家用电脑，并没有局域网）。</p>
<p>当你想绑定或添加一条ARP记录可以用arp -s 命令实现。格式为：ARP+空格+-s+IP地址+MAC地址</p>
<p>当你想删除一条APR记录，直接用arp -d来实现删除。</p>
<p><strong>3、tracert命令的用法：</strong></p>
<p>该命令是一个路由跟踪命令，用来确定网络中的IP数据包访问主机时所采取的路径。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286669.png&amp;size=m" alt=""></p>
<p>它的用法也并不难，我们可以在命令提示符中输入：tracert</p>
<p>就能看到它常用的命令和解析了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286676.png&amp;size=m" alt=""></p>
<p>比如我们还是跟踪什么的IP地址，我们可以看到具体的跟踪路径地址。当网络反应慢的时候，返回的数值就是请求超时。通常10个节点左右跟踪完成的IP地址或者网站，访问速度都是较好的。</p>
<p><strong>4、route命令的用法：</strong></p>
<p>该命令用法是在数据包在不能有效传输的情况下，用来管理和查看本机的路由表。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390286757.png&amp;size=m" alt=""></p>
<p>我们可以对本机的路由表查看和修改。查看路由表的命令为：route print</p>
<p>其他命令：route后面有效命令还包括：add 添加路由 change 更改现存路由 delete 删除路由。具体用法可以输入 route/？查看</p>
<p>以上四个网络命令都是属于使用频率较高的，你用过几个？</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/9p4qEw5JoSDaVbdTYXJ6PA">https://mp.weixin.qq.com/s/9p4qEw5JoSDaVbdTYXJ6PA</a></p>]]></description><guid isPermaLink="false">/archives/fd48f3cc-9520-4264-aa58-c6bda9cf3073</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:56:34 GMT</pubDate></item><item><title><![CDATA[史上最全的ONU、机顶盒及路由器常见网络问题及处理方法，90%的网络高手都不会！建议永久收藏]]></title><link>https://wiki.dgfygk.com/archives/6a2eb335-01cf-4c60-b88f-8620e37479fe</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8F%B2%E4%B8%8A%E6%9C%80%E5%85%A8%E7%9A%84ONU%E3%80%81%E6%9C%BA%E9%A1%B6%E7%9B%92%E5%8F%8A%E8%B7%AF%E7%94%B1%E5%99%A8%E5%B8%B8%E8%A7%81%E7%BD%91%E7%BB%9C%E9%97%AE%E9%A2%98%E5%8F%8A%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95%EF%BC%8C90%25%E7%9A%84%E7%BD%91%E7%BB%9C%E9%AB%98%E6%89%8B%E9%83%BD%E4%B8%8D%E4%BC%9A%EF%BC%81%E5%BB%BA%E8%AE%AE%E6%B0%B8%E4%B9%85%E6%94%B6%E8%97%8F&amp;url=/archives/6a2eb335-01cf-4c60-b88f-8620e37479fe" width="1" height="1" alt="" style="opacity:0;">
<p>无论是在项目中还是在家用网络中，我们总是会遇到一些奇怪的网络故障，网络突然变得很慢或者掉线，但又检查不出来什么原因，这样的问题相信很多弱电朋友都会遇到过，也是有不少朋友在项目中经常求助这样的问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390299374.png&amp;size=m" alt=""></p>
<br>
<br>
<p>本期将和大家一起来总结下这方面的问题。</p>
<p>一、ONU</p>
<p><strong>1、</strong> <strong>终端（ONU）</strong></p>
<p><strong>用户使用无线连接的终端上网反应慢、掉线</strong></p>
<p><strong>原因：</strong> 客户端无线WIFI型号使用环境复杂造成无线信号串扰掉线故障**解决办法：**1)进路由器内部页面更改无线信道，避开默认的1、6、11信道；2)也可使用WIFI分析仪寻找一个比较空闲的信道使用</p>
<p><strong>2、</strong> <strong>入户线下不能接交换机和路由</strong></p>
<p><strong>原因：</strong> 因光猫放在弱电井里，开发商的入户线质量问题**解决办法：**从光猫牵根网线，通过交换机分线。</p>
<p><strong>3、</strong> <strong>当用户纤没有贴标签，需要快速找到该户光纤</strong></p>
<p>**解决办法：**可采用FTTH绕圈快速找纤法。在用户端接入PON光功率计(调到1490nm)，或带光功率检测的ONU，让同事或用户观察光功率的变化，自己去分光器处依次将用户光纤绕一小圈，通过电话联络找到绕圈引起值变动的目标光纤，可以不用断其他用户的网络。</p>
<p><strong>4、</strong> <strong>用户光猫语音接口指示灯有时会反复闪烁跳动</strong></p>
<p><strong>无法正常固定，此时用户固话出现障碍现象</strong></p>
<p><strong>原因：</strong> 供电负荷过大，导致光猫供电不足引起**解决办法：**换个插家用电器少的插座，在查修过程中不能只考虑光衰、线路、光网终端这3方面，还应该考虑细小的东西，如：电源。</p>
<p><strong>5、</strong> <strong>线路不通，光衰值大</strong></p>
<p><strong>原因：</strong> 皮线光缆比较脆弱比不了其他网线、有线的耐拉力**解决办法：**尽量使皮线光缆单独走一根管，尽量不要和其他线路同走一条管，不要使皮线光缆打结和打扭。</p>
<p><strong>6、</strong> <strong>LOS灯亮红灯</strong></p>
<p><strong>原因：</strong> 光路不通、终端光猫问题、皮线光缆问题、接头问题、分光器单个口问题、主光纤问题、OLT问题**解决办法：**先用光功率测试一下光损耗值多大，如光损耗大或无光请检查接头和分线箱接头是否干净，尾纤是否跳错，以上无误请先测一下主光纤有没有光，如有请更换分光器口试一下。如在规定范围内请用别的终端光猫试一下。</p>
<p><strong>7、</strong> <strong>PON灯不亮</strong></p>
<p><strong>原因：</strong> 终端光猫上联POS口与工单所配置POS口不符、单一工单数据的丢失、终端Password输入问题、修改Password之前先接通的尾纤。**解决办法：**如果是新开小区首先请从新设置Password，如果不行请与督导联系给看一下所安装工单的Password数据是否正常，在那个POS口下。</p>
<p><strong>8、</strong> <strong>PON灯长亮连接宽带提示691用户名密码错误</strong></p>
<p><strong>原因：</strong> Password输入错误、用户名密码输入错误、密码错误、端口绑定问题、用户电脑问题。**解决办法：**首先从新输入用户名密码试一下如不行，检查password输入有没有问题检查无误后，联系综合员验证用户名密码查看拨号信息、解绑。</p>
<p><strong>9、</strong> <strong>PON灯长亮连接宽带提示678远程计算机无响应</strong></p>
<p><strong>原因：</strong> 网线连接LAN口错误、终端光猫问题、用户电脑问题、上层骨干网络问题。**解决办法：**目前的4口终端只有LAN1口能用，确认无误后用自带笔记本测试连接正常，证明用户电脑有问题。如果不行请与督导联系查看设备有无问题，如果没问题请和督导说一下更换终端请他修改一下数据。</p>
<br>
<p><strong>10、</strong> <strong>光猫注册30%以下提示注册OLT不成功</strong></p>
<p><strong>原因：</strong> 机房、板卡**解决办法：**机房光纤端口重启，不行就板卡复位(板卡复位后该pon口下全部用户会掉线请慎用)</p>
<p><strong>11、</strong> <strong>光猫注册50%以下提示连接平台超时</strong></p>
<p><strong>原因：</strong> 光猫数据配置问题**解决办法：**更换光猫</p>
<p><strong>12、</strong> <strong>光猫注册80%以后提示注册ITMS平台成功，业务下发失败</strong></p>
<p><strong>原因：</strong> 光猫无问题**解决办法：**与机房联系，光猫有绑定解绑；没绑就复位</p>
<p><strong>13、</strong> <strong>测速达不到200M</strong></p>
<p>**原因：**网线、电脑网卡、路由器</p>
<p>**解决办法：**检查网线是否为超5类或6类线；电脑网卡是否是千兆网卡；路由器是否是千兆路由器。</p>
<p><strong>14、</strong> <strong>普通高清机顶盒故障</strong></p>
<p>加载到30%以下为网线问题，50%为光猫业务问题，85%为接入账号问题，能到100%报错为业务账号问题。</p>
<p><strong>15、安卓内核4k机顶盒</strong></p>
<p>50%以下网线，接入账号，光猫数据均有可能问题，50%以后为业务账号问题。</p>
<p><strong>16、4K高清机顶盒</strong></p>
<p>7%，局域网连接故障，检测网线是否接好，检查设置里"有线连接"是否勾选。52%，互联网连接故障，检测所连接的网络是否可以正常访问Internet。83%，平台认证故障，检测所连接的网络是否可以正常访问Internet。身份验证出现问题，检查WIFI密码是否输入正确，注意区分字母大小写</p>
<p><strong>17、机顶盒卡屏，花屏：直播，点播，回看均卡屏花屏，有声音无图像</strong></p>
<p>**解决办法：**1)查光衰 2)查账号配置 3)查iptv总流量超负荷</p>
<p><strong>二、路 由 器</strong></p>
<p><strong>1、光纤100M不达标</strong></p>
<p>**原因：**主纤与尾纤、网线、光猫、路由器</p>
<p>**解决办法：**检查光纤线，测光衰值</p>
<p><strong>2、</strong> <strong>光纤200M不达标</strong></p>
<p><strong>原因：</strong> 主纤与尾纤、网线、光猫、路由器**解决办法：**检查光纤线，测光衰值是否达标(---26db)；网线是否是超5类线；光猫是否为千兆猫；路由器是否是千兆路由器(华为路由A1 WS832、华为荣耀Pro、华硕AC66U、腾达AC9、中兴E8820)。</p>
<p><strong>3、</strong> <strong>WIFI信号不稳定、网速慢、易掉线</strong></p>
<p>**解决办法：**重启用户路由器，仍然不好的进行单机测试</p>
<p><strong>4、</strong> <strong>WIFI信号小于---60dbm</strong></p>
<p>**原因：**路由器</p>
<p>**解决办法：**WIFI信号分析仪检查，达不到---60dbm，更换为推荐路由器。</p>
<p><strong>5、</strong> <strong>WIFI信号差</strong></p>
<p><strong>原因：</strong> WIFI同频干扰**解决办法：**使用WIFI分析仪检测用户信道是否受干扰，有干扰的连入路由器WiFi，浏览器输入网关IP地址，进入无线基本设置，信道下拉栏更改信道即可。</p>]]></description><guid isPermaLink="false">/archives/6a2eb335-01cf-4c60-b88f-8620e37479fe</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:56:17 GMT</pubDate></item><item><title><![CDATA[POE交换机：在弱电行业中的各种应用大全！收藏这一篇就够了！]]></title><link>https://wiki.dgfygk.com/archives/be7fa5f5-3698-4c41-9cd0-4bda1986d976</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=POE%E4%BA%A4%E6%8D%A2%E6%9C%BA%EF%BC%9A%E5%9C%A8%E5%BC%B1%E7%94%B5%E8%A1%8C%E4%B8%9A%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E5%BA%94%E7%94%A8%E5%A4%A7%E5%85%A8%EF%BC%81%E6%94%B6%E8%97%8F%E8%BF%99%E4%B8%80%E7%AF%87%E5%B0%B1%E5%A4%9F%E4%BA%86%EF%BC%81&amp;url=/archives/be7fa5f5-3698-4c41-9cd0-4bda1986d976" width="1" height="1" alt="" style="opacity:0;">
<p>POE交换机在安防行业的应用，给视频监控系统带来了质的改变，POE交换机。可通过网线为无线AP、网路摄像头等PoE终端设备供电，传送距离可达100m，安装简单，即插即用。非常适合无线城市、安防监控等行业使用。</p>
<p>① POE的由来什么是POE
 <br>
 POE(PowerOverEthernet)指的是在现有的以太网Cat.5布线基础架构不作任何改动的情况下，在为一poe供电些基于IP的终端（如IP电话机、无线局域网接入点AP、网络摄像机等）传输数据信号的同时，还能为此类设备提供直流供电的技术。POE也被称为基于局域网的供电系统(POL,PoweroverLAN)或有源以太网(ActiveEthernet)，有时也被简称为以太网供电，这是利用现存标准以太网传输电缆的同时传送数据和电功率的最新标准规范，并保持了与现存以太网系统和用户的兼容性。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390341989.png&amp;size=m" alt=""> POE的两种供电方案------POE标准为使用以太网的传输电缆输送直流电到POE兼容的设备定义了两种方法：</p>
<br>
<p>一种称作"中间跨接法"(Mid-Span),使用以太网电缆中没有被使用的空闲线对来传输直流电（即1236线序走数据，4578线序走电力),相应的EndpointPSE支持POE功能的以太网交换机、路由器、集线器或其他网络交换设备。</p>
<br>
<p>另一种方法是"末端跨接法"(End-Span),是在传输数据所用的芯线上同时传输直流电,其输电采用与以太网数据信号不同的频率（即1236线序同时走数据和电力）。MidspanPSE是一个专门的电源管理设备,通常和交换机放在一起。它对应每个端口有两个RJ45插孔,一个用短线连接至交换机,另一个连接远端设备。可以预见,End-Span会迅速得到推广,这是由于以太网数据与输电采用公用线对,因而省去了需要设置独立输电的专用线,这对于仅有8芯的电缆和相配套的标准RJ-45插座意义特别重大。</p>
<br>
<p>② POE供电的工作过程
 <br>
 当在一个网络中布置PSE供电端设备时,POE以太网供电工作过程如下所示。</p>
<br>
<p>1.检测：一开始,PSE设备在端口输出很小的电压,直到其检测到线缆终端的连接为一个支持IEEE802.3af标准的受电端设备。</p>
<br>
<p>2.PD端设备分类：当检测到受电端设备PD之后,PSE设备可能会为PD设备进行分类,并且评估此PD设备所需的功率损耗。</p>
<br>
<p>3.开始供电：在一个可配置时间(一般小于15μs)的启动期内,PSE设备开始从低电压向PD设备供电,直至提供48V的直流电源。</p>
<br>
<p>4.供电：为PD设备提供稳定可靠48V的直流电,满足PD设备不越过15.4W的功率消耗。</p>
<br>
<p>5.断电：若PD设备从网络上断开时,PSE就会快速地(一般在300～400ms之内)停止为PD设备供电,并重复检测过程以检测线缆的终端是否连接PD设备。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390341997.png&amp;size=m" alt="">
 <br>
 ③ 使用以太网供电的优势
 <br>
 1.POE只需要安装和支持一条电缆，简单而且节省空间，并且设备可随意移动。（简单，方便）</p>
<br>
<p>2.节约成本。许多带电设备，例如视频监视摄像机等，都需要安装在难以部署AC电源的地方，POE使其不再需要昂贵电源和安装电源所耗费的时间，节省了费用和时间。</p>
<br>
<p>3.像数据传输一样，POE可以通过使用简单网管协议(SNMP)来监督和控制该设备。</p>
<br>
<p>4.POE供电端设备只会为需要供电的设备供电，只有连接了需要供电的设备，以太网电缆才会有电压存在，因而消除了线路上漏电的风险。（安全性）</p>
<br>
<p>5.一个单一的UPS就可以提供相关所有设备在断电时的供电。（集中供电）</p>
<br>
<p>6.用户可以自动、安全地在网络上混用原有设备和POE设备，这些设备能够与现有以太网电缆共存。</p>
<br>
<p>7.使网络设备便于管理。因为当远端设备与网络相连后，才能够远程控制、重配或重设。</p>
<br>
<p>8.在无线局域网中，POE可以简化射频测试任务，接入点能够被轻松地移动和接入。</p>
<br>
<p>9.减少电力线的物料成本和人工布线成本。</p>
<br>
<p>10.减少后期的维护和维修成本。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342006.png&amp;size=m" alt="">
 <br>
 <strong>POE不能供电时，如何排查</strong></p>
<p>① 首先确定受电设备是否支持POE供电
 <br>
 不是所有的设备都支持POE供电的，曾经就接到过一起这样的售后，一个客户说他的监控摄像头在没接电源的情况下通过网线接在我们的8口POE上灯不亮，摄像头也不工作。接了摄像头电源后就一切正常。听到客户反映的情况，我首先就让客户查下他的摄像头是否支持POE供电，果不其然，他的设备不支持POE供电。</p>
<br>
<p>② 检查受电设备的功率是否超出了POE交换机端口供电的最大功率
 <br>
 POE交换机的端口供电功率有两种，一种是15.4W，一种是30W。如果用的是15.4W的POE交换机，受电设备的功率却大于15.4W，就会出现供电不稳，或者供不了电的。</p>
<br>
<p>③ 检测POE交换机在满负载的工作状态下，供电功率是否超出了供电总功率
 <br>
 POE交换机除了单个的端口供电功率，还有个总供电功率。市面上有的8口POE交换机总供电功率是90W，单个端口供电功率是15.4W。如果每个端口的供电功率都使用到顶峰，8个端口就是8×15.4=123.2W。这样就超出了本台交换机的总功率，供电自然就会出现问题。所以使用POE交换机要有个妥善的搭配，功率大的受电设备和功率小的搭配起来使用。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342033.png&amp;size=m" alt=""> <strong>POE供电距离到底怎么算</strong></p>
<br>
<p>只针对符合IEEE802.3af/at 标准PoE设备
 <br>
 ① 网线对供电距离的影响
 <br>
 首先我们先来看下表IEEE802.af和IEEE802.3at标准中对Cat5e网线要求：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342042.png&amp;size=m" alt="">
 <br>
 说明：Type 1 value和Type 2 value 分别指IEEE802.3af和IEEE802.3at的要求。</p>
<p>从表中可以看出：100米cat5e网线的等效阻抗IEEE802.3at须小于12.5欧姆，而IEEE802.3af则需小于20欧姆。</p>
<br>
<p>结论一：PoE供电距离与网线是息息相关的，网线的阻抗越低传输距离越远，一切抛开网线谈PoE供电距离都是耍流氓！要想供电足够远，网线首先要买正品。</p>
<br>
<p>② IEEE 802.3af/at标准对PoE供电电压要求
 <br>
 PoE输出电压范围44-57V，PD受电电压范围36-57V，802.3af/at标准；参考如下：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342051.png&amp;size=m" alt="">
 <br>
 结论二：PoE供电距离要求远，输出的电压就必须高。</p>
<br>
<p>总结：要想PoE供电距离够远，网线必须符合标准，PoE供电产品的输出电压在标准之内（44-57V）尽量高。</p>
<br>
<p>例如：巨烽光电PoE产品采用输出电压是55V, 在符合标准的Cat5e 200米网线等效阻抗为25欧姆，AF标准电流是300mA，U=0.3*25=7.5V， 55V（PoE输出）-7.5V（衰减电压）=47.5V（200米网线负载端电压），根据P=UI, 最终得出200米网线负载端功率P=负载端电压U*AF标准电流I=47V*0.3A= 14.1W，足够枪机或无线AP工作了。</p>
<br>
<p>当然一个健壮的网络不单单只看足够长的PoE供电距离，网络传输质量要有保障。根据IEEE规定网线传输超过100米，就不保证传输质量，但是在有的情况下（成本压力下），例如安装摄像头时候，我们会超过了十几米，这里教您一个小窍门：对超过不是太多的情况下，将摄像头的连接速率手动设置成10M，对于1个200万像素的枪机，10M速率足够了，非常大的几率解决问题。
 <br>
 <strong>POE交换机功率</strong></p>
<p>PoE交换机的总功率是一个非常重要的指标，直接关系到可以带多少摄像头。以电源功率是400W的、24端口的PoE交换机来说，除去损耗后PoE交换机总功率大概为370W。</p>
<br>
<p>在IEEE802.3af标准下，它能够供满24个端口（370/15.4=24），即可以同时给24个摄像头供电，也就是满载供电。但如果是按照IEEE802.3at标准的单口最大供电功率30W计算，同时最多就只能给12个端口供电了（370/30=12）。</p>
<br>
<p>实际使用中，很多普通网络摄像头的最大功耗较低，基本不超过15W，如果这时每个PoE端口按照最大功率（比如30W）去预留供电功率的话，就会出现有的端口PoE功率用不完，而有的端口却分不到功率的情况。比如，有些PoE交换机均支持动态功率分配，可以避免这种情况。在选购PoE交换机时，要看交换机是否支持动态功率分配，这样每个端口只分配实际使用的功率，这样就能更高效地利用PoE交换机的供电功率。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342060.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342073.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342085.png&amp;size=m" alt="">
 <br>
 <strong>POE供电稳定性</strong></p>
<p>从技术角度来讲，PoE的技术发展多年，目前已经处于非常成熟的阶段。但由于目前监控市场迫于成本压力，选用的PoE交换机或者线材品质过于低劣，再或者方案设计本身就不合理，导致采用PoE供电的项目维护的工作量特别大，所以工程商普遍存在PoE供电不稳定的观点。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342099.png&amp;size=m" alt="">
 <br>
 网络监控项目，不同于普通的网络综合布线，数据传输量非常大，功率高，且要求全天候不间断工作，采用有品质保证的PoE设备和线材是整个系统稳定的保证。
 <br>
 <strong>POE供电在安防系统中的应用</strong></p>
<p>安防行业应用PoE组网主要有简化布线、节能灵活、安全方便等优势。众所周知，一般的网络摄像机除了需要通过网线来传输视频信号外，还必须全天候为其提供足够的电力。而在现实施工中，经常会出现因为无法提供稳定的电源，而不得不重新设计摄像头的安装地点的情况。如此一来，重新设计安装地点额可能会导致出现监控死角，使监控失去意义。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342120.png&amp;size=m" alt="">
 <br>
 1、灵活性，PoE只需要安装和支持一条电缆，简单而且节省空间，并且设备可随意移动；</p>
<br>
<p>2、节约成本，视频监控摄像机等，都需要安装在难以部署AC电源的地方，PoE使其不再需要昂贵电源和安装电源所耗费的时间，节省了费用和时间；</p>
<br>
<p>3、供电智能化，DC电源像数据传输一样，PoE可以通过使用简单网管协议(SNMP)来监督和控制设备；</p>
<br>
<p>4、消除供电安全隐患，PoE供电端设备只会为需要供电的设备供电，只有连接了需要供电的设备，以太网电缆才会有电压存在，因而消除了线路上漏电的风险；</p>
<br>
<p>5、集中供电优势，一个单一的UPS就可以提供相关所有设备在断电时的供电；</p>
<br>
<p>6、设备兼容性优势，用户可以自动、安全地在网络上混用原有设备和PoE设备，这些设备能够与现有以太网电缆共存；</p>
<br>
<p>7、使网络设备便于管理，当远端设备与网络相连后，能够远程控制、重配或重设；</p>
<br>
<p>8、在网络摄像机局域网中，PoE可以简化测试任务，接入点能够被轻松地移动和接入。
 <br>
 <strong>PoE供电在无线网络中的应用</strong></p>
<br>
<p>PoE交换机可以部署在汇聚交换机和无线AP之间，或者PON网络中ONU和无线AP之间的位置。通过选择8口、16口、24口不同端口密度的PoE交换机，可以部署在不同用户密度的热点区域。其中汇聚交换机+PoE交换机的组网模式非常适合部署在用户密集的区域，目前在高校的WLAN项目建设中，多采用此方式。PoE交换机解决方案应用范围广、部署数量大，是目前很多工程商的主流选择。被广泛运用在无线AP和网络摄像机组网中，方案采用AP+AC的方式便于集中管理，并使用PoE供电节省人力成本。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342134.png&amp;size=m" alt=""> <strong>PoE供电线缆选择</strong></p>
<p>POE供电的安全传输距离100米，建议使用超五类全铜网线以上线缆。</p>
<br>
<p>POE供电网线要求这个问题只在中国等假货便宜货横行的国家是个问题，在很多发达国家不是问题。POE IEEE 802.3af标准要求PSE输出端口的输出功率为15.4W或者15.5W， 传输100米后的PD设备接受功率必须不小于12.95W，按照802.3af典型电流值为350ma计算，100米网线的电阻必须为(15.4-12.95W)/350ma = 7欧姆或者(15.5-12.95)/350ma = 7.29欧姆。</p>
<br>
<p>而标准网线是天然就满足这个要求的，IEEE 802.3af poe供电标准本身就是以标准网线测定的。而只所以会产生POE供电网线要求这个问题，是因为市面上的很多网线都是非标准网线，不是严格按照标准网线的要求来生产的。市面的非标准网线材质主要有铜包钢、铜包铝、铜包铁等，这些网线的阻值大，都不适合POE供电。POE供电必须使用无氧铜材质的网线，即标准网线。</p>
<br>
<p>PoE供电技术对线材的要求高，建议在监控项目中，千万不要在线材上省成本，得不偿失。
 <br>
 <strong>如何选择POE交换机</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390342149.png&amp;size=m" alt=""> ① 需要给多大功耗的设备供电：
 <br>
 PoE交换机采用的标准不同，输出功率也会不同，比如：IEEE802.3af最大不超过15.4W，由于传输线材的损耗，能给最大功耗不超过12.95W的设备供电。遵循IEEE802.3at标准的PoE交换机，能给最大功耗不超过25W的设备供电。</p>
<br>
<p>② 最多能给多少个设备供电：
 <br>
 PoE交换机的一个重要指标，就是PoE供电的总功率。在IEEE802.3af标准下，如果一台24口PoE交换机的PoE供电总功率达到了370W，那么就能够供满24个端口(370/15.4=24)，但如果是按照IEEE802.3at标准的单口最大供电功率30W计算，同时最多就只能给12个端口供电了(370/30=12)。</p>
<br>
<p>③ 需要接口数：
 <br>
 是否带光纤口、带不带网管、速率选择(10/100/1000M)。
 <br>
 <strong>POE交换机可以二级交换吗</strong></p>
<p>PoE只是解决供电问题，不会影响信号的，（一般供电是用的4、5、7、8四根线，而信号是1、2、3、6四根线），如果要使用多层交换机，只需要让离摄像机最近的交换机给摄像机供电就好了，后面的交换机则无需跟PoE有任何关系。</p>]]></description><guid isPermaLink="false">/archives/be7fa5f5-3698-4c41-9cd0-4bda1986d976</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:55:54 GMT</pubDate></item><item><title><![CDATA[光模块如何与交换机搭配使用]]></title><link>https://wiki.dgfygk.com/archives/4a242063-acec-4005-a720-90e3c630a75d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%89%E6%A8%A1%E5%9D%97%E5%A6%82%E4%BD%95%E4%B8%8E%E4%BA%A4%E6%8D%A2%E6%9C%BA%E6%90%AD%E9%85%8D%E4%BD%BF%E7%94%A8&amp;url=/archives/4a242063-acec-4005-a720-90e3c630a75d" width="1" height="1" alt="" style="opacity:0;">
<p>在常见的网络项目中，会用到光模块，也简称SFP模块或者SFP+模块。</p>
<p>关于<strong>SFP+光模块如何使用？</strong></p>
<p>在企业网络部署、数据中心建设都离不开<strong>光模块与交换机</strong> 。光模块主要是用来<strong>将电信号与光信号进行转换</strong> ，而交换机则是<strong>对光电信号起到转发作用</strong> 。在众多光模块中，<strong>SFP+光模块</strong> 是目前被<strong>应用的最多的光模块之一</strong>，在与交换机搭配使用时采用不同的连接方式可实现不同的网络需求。</p>
<p><strong>一、SFP+光模块是什么</strong></p>
<p>SFP+光模块是属于SFP光模块中的一种<strong>10G光纤模块，它独立于通信协议</strong>。一般与交换机、光纤路由器、光纤网卡等相连接，被应用在10G bps以太网以及8.5G bps光纤通道系统中，能满足数据中心更高的速率需求，实现数据中心的网络扩展与转换。</p>
<p>SFP+光模块线卡密度高、体积小，可<strong>与其他类型的10G模块互通</strong>，为数据中心提供更高的安装密度，节约成本。也因此而成为市场上主流的可插拔光模块。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390305482.png&amp;size=m" alt=""></p>
<p><strong>二、SFP+光模块的种类</strong></p>
<p>常规情况下，SFP+光模块是按照实际应用来进行分类的，常见的有<strong>10G SFP+、BIDI SFP+、CWDM SFP+、DWDM SFP+这几种类型。</strong></p>
<p><strong>10G SFP+光模块</strong></p>
<p>该种类型的光模块即为普通SFP+光模块，也可视作10G SFP光模块的升级版，是目前市场上的主流设计。</p>
<p><strong>BIDI SFP+光模块</strong></p>
<p>该种类型的光模块采用波分复用技术，速率可达到11.1G bps，功耗低。拥有两个光纤插孔，一般成对使用，在数据中心进行网络建设时，可减少光纤的使用量，降低建设成本。</p>
<p>CWDM SFP+光模块</p>
<p>该种光模块采用粗波分复用技术，常与单模光纤搭配使用，可节省光纤资源，在组网中更加灵活、可靠，且功耗小。</p>
<p>DWDM SFP+光模块</p>
<p>该种光模块采用密波分复用技术，多用于长距离的数据传输中，传输距离最大可达80km，具有高速率、大容量、扩展性强等特点。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390305528.png&amp;size=m" alt=""></p>
<p><strong>三、SFP+光模块与交换机如何搭配使用</strong></p>
<p>不同类型的光模块与交换机连接，可应用于不同的组网方案中，下面为大家介绍几种<strong>SFP+光模块与交换机的实际搭配应用方案。</strong></p>
<p>方案一：10G SFP+万兆光模块与交换机之间的连接</p>
<p>依次将4块10G SFP+光模块插入一台交换机的10-Gbps SFP+端口中，再将一块<strong>40G QSFP+光模块插入另一台交换机的40-Gbps QSFP+端口中</strong>，最后在中间以一根分支光纤跳线进行连接。</p>
<p>该连接方式主要<strong>实现了网络从10G向40G的扩展</strong>，可快速、简便的满足数据中心的网络升级需求。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390305656.png&amp;size=m" alt=""></p>
<p>方案二：BIDI SFP+万兆单纤双向光模块与交换机之间的连接</p>
<p>将光模块分别<strong>插入两台交换机的SFP+端口中</strong>，再用与光模块连接口对应的LC光纤跳线将两台交换机上的光模块进行连接。</p>
<br>
<p>该连接方式有效的实现了最简单经济的数据连接，可应用于数据中心、企业布线以及电信运营传输的以太网连接中。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390305733.png&amp;size=m" alt=""></p>
<p>方案三：CWDM SFP+万兆光模块与交换机之间的连接</p>
<p>该连接方式用了<strong>中继箱、光纤收发器、CWDM粗波分复用器等来将光模块与交换机进行连接</strong>，实现了将10G万兆以太网交换机上的RJ45电口转换为CWDM粗波分复用器需要的CWDM波长。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390305841.png&amp;size=m" alt=""></p>
<p>方案四：DWDM SFP+万兆光模块与交换机之间的连接</p>
<p>将光模块插入交换机SFP+端口中，再用铠装光纤跳线将其与DWDM密波分复用器进行连接。</p>
<p>该连接方式实现了长途传输中对光信号的保护，能最大程度的降低光波损耗，适用于长距离的光信号传输</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390305956.png&amp;size=m" alt=""></p>
<p><strong>四、SFP+光模块与交换机连接注意事项</strong></p>
<p>1、注意<strong>两端交换机所使用的光模块的波长、传输距离是否相同</strong>，以及单纤双纤、单模多模问题，若出现两端不对等的情况时，应使用相对应的转换器；</p>
<p>2、光模块在使用时要尽量避免静电与磕碰，若出现磕碰，则不建议继续使用该光模块；</p>
<p>3、注意光模块插入的正反，拉环与标签应朝上；</p>
<p>4、在将光模块插入交换机时，尽量用力将其推到底部，一般会有轻微的震动感，插入之后可轻拔光模块**，检查是否安装到位；**</p>
<p>5、在进行光模块拆卸时，应先将手环拉到与光口呈90°的位置，再将光模块拔出。</p>
<br>
<p><strong>五、光模块的使用与安装</strong></p>
<p>这里面为了更加直观，这里面弱电君引用一个视频来详细了解光模块的使用与安装，对这块不熟的朋友可以看下。</p>
<p>最后我们可以思考一个问题，我们在使用光模块时，经常会想到光纤收发器，关于**光模块与光纤收发器的区别在哪里？**我们在使用到两种设备时，就会有答案。</p>]]></description><guid isPermaLink="false">/archives/4a242063-acec-4005-a720-90e3c630a75d</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:55:14 GMT</pubDate></item><item><title><![CDATA[智能化停车场道闸系统常见故障与排除方法]]></title><link>https://wiki.dgfygk.com/archives/bbeffcea-4911-4855-8ee2-96af91b71234</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%99%BA%E8%83%BD%E5%8C%96%E5%81%9C%E8%BD%A6%E5%9C%BA%E9%81%93%E9%97%B8%E7%B3%BB%E7%BB%9F%E5%B8%B8%E8%A7%81%E6%95%85%E9%9A%9C%E4%B8%8E%E6%8E%92%E9%99%A4%E6%96%B9%E6%B3%95&amp;url=/archives/bbeffcea-4911-4855-8ee2-96af91b71234" width="1" height="1" alt="" style="opacity:0;">
<p>随着停车问题日益增多，很多住宅小区都基本安装了智能化的停车场系统，智能停车场系统一般由入口控制机、出口控制机、智能道闸、地感线圈、余位显示屏、图象识别设备、系统软件、管理工作站等组成。下面是停车场系统常见故障及解决方案仅供大家维护的时候参考。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390318734.png&amp;size=m" alt=""></p>
<p><strong>1、入口控制机：车压入感应区内，通话键及按票键灯不亮，且无语音提示</strong></p>
<p>A、检查单片机是否在工作；</p>
<p>B、感应器是否感应；</p>
<p>C、检查光藕是否有问题；</p>
<p>D、票灯接线是否有问题。</p>
<p><strong>2 车牌识别：记录了的车牌，识别不了</strong></p>
<p>A、图像拍摄不清楚</p>
<p>车牌区域定位的困难主要是来自于采集的图像，由于采集的车牌图像的多样性，并且采集图像时受到许多因素的影响，如雨天、大雾、光线等，使得有一些车牌图像质量出现不同程度的差异。</p>
<p>这种情况一般只有在图像聚焦清晰的情况下，识别结果才能达到比较满意的效果。那这对停车场系统的感光部件就有一定的要求了，视频的摄像机也就要提高一些配置啦，看看视频设置不正常(设置正常)，显示器分辨率、颜色桌面大小调节。</p>
<p>B、车牌受损或有污渍</p>
<p>在公路和城市内的实际应用过程中，很难保证所涉及到的车牌都是没有污损的，车牌在使用几年后，难免会出现污染和磨损等现象。</p>
<p>对于这种情况，很多智能的停车场系统也渐渐解决了这样的问题，但如果过于严重，这还得靠人工了。</p>
<br>
<p><strong>3 道闸不开：车牌识别通过，道闸门不开</strong></p>
<p>A、检查开闸接线端子是否接牢；</p>
<p>B、检查开闸接线端子是否有开关信号输出，若无，则道闸控制继电器坏，更换控制机控制板；</p>
<p>C、若仍不能开闸，检查道闸是否死机。</p>
<br>
<p><strong>4 显示屏的LED不显示</strong></p>
<p>A、检查LED电源是否正常；B、检查LED芯片是否正常。</p>
<br>
<p><strong>5 无语音提示声</strong></p>
<p>A、检查语音芯片；B、检查电位器。</p>
<br>
<p><strong>6 道闸上下运动或经常转过头</strong></p>
<p>A、检查限位开关；B、检查CPU；C、检查限位开关；D、检查断电开关。</p>
<br>
<p><strong>7、车过不下闸</strong></p>
<p>可能原因：</p>
<p>①地感线圈埋设不正确或线圈损坏、折断;</p>
<p>②车辆检测器电源功率不足(Max250mA)或感应灵敏度调节不当(过高或太低);</p>
<p>③地感检测器的"COM"端与道闸控制器的"GND"公共地端断开或接触不良。</p>
<br>
<p><strong>8 点击软件监控界面中〖开闸〗按钮，道闸不开启，但用手动按钮可以开闸。</strong></p>
<p>可能原因：</p>
<p>①读卡机内"控制开关"没有短接(出厂时均为断开的，处于脱机工作状态，与电脑联网控制时，须短接处于联机状态);</p>
<p>②电脑与读卡机不能正常通讯，控制器接收不到开闸命令。(检查电脑串口的联接是否正常，参数设置是否正确)</p>
<br>
<p><strong>9、电脑不稳定，系统经常出错，操作软件时易死机</strong></p>
<p>A.重装操作系统。</p>
<p>B.CPU风扇是否坏、机箱内温度是否正常。</p>
<p>C.查看电脑内的车辆记录是否太多。</p>
<br>
<p><strong>10、电脑网络不稳定，时通时断开</strong></p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/8tAD2h56x2uM9oLIcXC9CA">https://mp.weixin.qq.com/s/8tAD2h56x2uM9oLIcXC9CA</a></p>]]></description><guid isPermaLink="false">/archives/bbeffcea-4911-4855-8ee2-96af91b71234</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:54:56 GMT</pubDate></item><item><title><![CDATA[网络知识：路由器常见故障分析及处理方法，值得收藏！]]></title><link>https://wiki.dgfygk.com/archives/f5719526-212f-4774-8d59-e2dd593e7e19</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%BB%9C%E7%9F%A5%E8%AF%86%EF%BC%9A%E8%B7%AF%E7%94%B1%E5%99%A8%E5%B8%B8%E8%A7%81%E6%95%85%E9%9A%9C%E5%88%86%E6%9E%90%E5%8F%8A%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95%EF%BC%8C%E5%80%BC%E5%BE%97%E6%94%B6%E8%97%8F%EF%BC%81&amp;url=/archives/f5719526-212f-4774-8d59-e2dd593e7e19" width="1" height="1" alt="" style="opacity:0;">
<p>对当前的大多数网络来说，无论是实现网络互连还是访问Internet，路由器是不可或缺的。由于路由器的重要性，对它的管理就成了网络维护人员的日常工作中重要的一部分，而路由器的故障分析和排除也是令许多网络维护人员极为困扰的问题之一。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390311043.png&amp;size=m" alt=""></p>
<p>路由器出现的故障中，大体可以分为两类：一类是硬件故障，一类是软件故障。常见的硬件故障通常有这么几种：</p>
<p>系统不能正常加电或是部件损坏。遇到硬件故障时，要重点检查供电是否正常，连接是否牢靠，遇到不能解决的物理问题，最好还是送修或更换新的设备。</p>
<p>软件故障的种类很多，通常有系统软件损坏、配置问题、功能无法实现等。这时最好按照路由器的使用手册，检查某些特定的配置，反复排除确认配置是否正确，看一看是不是有重复使用的网段，网络掩码的计算是否正确等等。只要认真细致地查找，问题总会解决。</p>
<p>下面针对一些路由器常见故障引发的问题为大家详细列举出来。</p>
<p><strong>1.路由器的部分功能无法实现</strong></p>
<p>故障现象：路由器配置完全正确，但是有些功能却不能实现。</p>
<p>故障原因：如果是在确保路由器配置正确的前提下，那么问题应该就在路由器的软件系统上。</p>
<p>解决方法：升级软件系统。因为路由器的系统软件往往有许多版本，每个版本支持的功能有所不同，出现这种情况最大的可能就是当前的软件系统版本不支持某些功能而导致路由器部分功能的丧失，所以如无意外进行相应的软件升级就可以解决问题了。此类型的问题，对于企业用户来说影响比较大，但却很容易被网络管理人员所忽视，因为频繁使用网络的企业用户在功能使用上的需要比较多，如果缺了其中某一两个常用的功能，将会为企业用户带来许多不必要的麻烦。</p>
<p><strong>2.网络频繁掉线</strong></p>
<p>故障现象：刚打开路由器的时候，网络运行正常，但是上网一段时间后就经常掉线，关闭路由器后再重启又可以连通。</p>
<p>故障原因：引起这种故障的原因比较复杂，有可能是由于路由器设备温度过高导致死机，也有可能是局域网内经常有人使用BT软件下载资料，严重地影响网速而造成网络性能变低，不过大多数的情况都是由设置不当造成的，具体分为以下几种状况：</p>
<p>（1）BT下载拖慢网速而导致掉线。在共享网络中BT下载是影响网速的一个重大问题，所以在遇到上网频繁掉线的问题时，用户应该先检查局域网内是否经常有人使用BT软件下载资料，排除了使用不当这一因素，才继续往下寻找病根。</p>
<p>解决方法：关掉BT，重启路由器。</p>
<p>（2）多台DHCP服务器引起IP地址混乱。当网络上有多台DHCP服务器的时候，会造成网络上的IP地址冲突，从而导致频繁的不定时掉线问题。</p>
<p>解决方法是：将网络中的所有DHCP服务器关闭，使用手动方式指定IP地址。</p>
<p>（3）对应连接模式设置有误。</p>
<p>解决方法：在路由器的管理界面中，选择左侧菜单里"网络参数"中的"WAN口设置"选项。选择"对应连接模式"中的"按需连接"选项，把"自动断线等待时间"设为"0分钟"，则路由器就不会自动断线。</p>
<p>（4）遭受木马病毒攻击。这是除设置有误外所造成网络频繁掉线的另一个重要原因。</p>
<p>解决方法：如果怀疑是受到了黑客木马的攻击，那么首先就是查看所有连接的计算机是否都感染了病毒或者木马，使用正版的杀毒软件或木马专杀工具，扫描清除掉计算机内的病毒或者木马然后再上网。</p>
<p><strong>3.无法浏览网页</strong></p>
<p>故障现象：网页以不能正常打开，但是QQ之类的程序却可以正常运行。</p>
<p>故障原因：这种情况大多是路由器上的DNS未配置导致。</p>
<p>解决方法：要解决这个问题，建议在路由器和计算机网卡上手动设置DNS服务器地址（(ISP局端提供的地址），打开路由器设置界面，找到"网络参数"中的"WAN口参数"的字段，然后在下面手动设置DNS服务器地址。</p>
<p><strong>4.某些应用无法使用</strong></p>
<p>故障现象：网页可以正常打开，但是某些应用无法使用，例如：迅雷下载、暴风影音等。</p>
<p>故障原因：这种情况大多是路由器上的应用限制功能导致。</p>
<p>解决方法：以TP-LINKR478为例，要解决这个问题，首先要登录路由器管理界面，找到"上网行为管理"中的设置界面，根据需要取消相应的限制即可。</p>
<p><strong>5．网络带宽达不到合同带宽或相差甚远</strong></p>
<p>故障现象：网络使用速度很低，远远低于合同带宽。</p>
<p>故障原因：这种情况一般适用于路由器上进行了带宽限制或局域网内剩余使用带宽不足导致。</p>
<p>解决方法：1、登录路由器管理界面，进入系统工具里面的流量统计界面，查看当前局域网内共使用带宽，确认是否由于局域网内其他终端下载或其它原因占用大量带宽导致；</p>
<p>2、登录路由器管理界面，点击QOS--QOS设置，查看路由器上是否左右带宽限制策略；</p>
<p><strong>6.局域网内存在多个路由器，因人为原因出现二级路由</strong></p>
<p>故障现象：局域网内计算机无任何规律性的出现有某些电脑上不了网的情况。</p>
<p>故障原因：这种情况绝大多是是由于用户局域网内，某某用户想接入一个路由器，又不懂设置与接法，将上行网线误解在LAN口且DHCP未关闭导致。</p>
<p>解决方法：在无法上网的计算机上根据其所获IP地址中的网管登录路由器。例如：网管为192.168.2.1，在该计算机浏览器网址栏输入192.168.2.1（无端口号情况下）进入路由器设置界面，下面有两种方法解决：</p>
<p>1、在该路由器设置界面将DHCP服务器关闭，然后将无法上网的计算机网卡重新获取IP即可解决；</p>
<p>2、进入该路由器设置界面查看该路由器型号，根据型号，在用户配合下找到该路由器安装位置，重新设置数据，并将上行网线插入WAN口，然后将无法上网的计算机网卡重新获取IP即可解决。</p>]]></description><guid isPermaLink="false">/archives/f5719526-212f-4774-8d59-e2dd593e7e19</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:54:40 GMT</pubDate></item><item><title><![CDATA[史上最全的ups的空开、电缆及电池的配置计算，90%的弱电人都收藏了]]></title><link>https://wiki.dgfygk.com/archives/ca5c90ea-fb80-4f9e-92d5-0986de06c39b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8F%B2%E4%B8%8A%E6%9C%80%E5%85%A8%E7%9A%84ups%E7%9A%84%E7%A9%BA%E5%BC%80%E3%80%81%E7%94%B5%E7%BC%86%E5%8F%8A%E7%94%B5%E6%B1%A0%E7%9A%84%E9%85%8D%E7%BD%AE%E8%AE%A1%E7%AE%97%EF%BC%8C90%25%E7%9A%84%E5%BC%B1%E7%94%B5%E4%BA%BA%E9%83%BD%E6%94%B6%E8%97%8F%E4%BA%86&amp;url=/archives/ca5c90ea-fb80-4f9e-92d5-0986de06c39b" width="1" height="1" alt="" style="opacity:0;">
<p>很多朋友问到关于ups相关的计算，其实关于ups方面的计算还是挺多，ups无论是接空开，还是连接电缆，以及选择电池，都可能需要计算它的电流或功率等，那么今天我们来详细了解下ups的相关计算。</p>
<br>
<p><strong>一、ups空开及电缆的配置</strong></p>
<br>
<br>
<br>
<p><strong>一、高频ups与工频ups的区别</strong></p>
<p>ups从分类来说，分为<strong>高频ups</strong> 与<strong>工频ups</strong>，高频机体积小、重量轻，价格低适合单个工作点的小功率设备保护，对干扰不敏感的设备和可靠性要求不很高的场合。</p>
<p>而工频机适合所有设备保护，无论是网点设备还是IDC（数据中心），可靠性较高，但工频机有体积大、重量重、价格高等缺点。</p>
<p>对电力品质和可靠性要求<strong>较高</strong> 的地方，应使用<strong>工频机</strong>。反之，则可使用高频机。他们空开输入电流和输出电流的计算方法，都是一样的。</p>
<br>
<p>我们首先要看下，ups如何计算空开。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390425931.png&amp;size=m" alt="">
 <br>
 <strong>二、ups输入电流的计算方法</strong> 我们首先要看下输入电流的计算公式：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390426116.png&amp;size=m" alt=""></p>
<p>其中：S：<strong>三相UPS=3，单相UPS=1</strong></p>
<br>
<p><strong>例1：</strong></p>
<p>已知：UPS功率：100KVA，输入临界电压：176V，输入功率因数：0.99(高频机都可以近似取1，工频机取值0.85),整机效率：0.95，S=3，输出功率因数：0.8。</p>
<p>计算结果如下：</p>
<p>100000×0.8</p>
<p>输入电流（A）＝ ------------------ =160A</p>
<p>176×0.95×3*1</p>
<p>这里以工频机输入功率因数作为计算。</p>
<br>
<p><strong>例2：</strong></p>
<p>已知：UPS功率：6KVA，输入临界电压：176V，输入功率因数：0.99(高频机都可以近似取1，工频机取值0.85),整机效率：0.90，S=1，输出功率因数：0.7。</p>
<p>计算结果如下：</p>
<p>6000×0.8</p>
<p>输入电流（A）＝------------------ =30A</p>
<p>176×0.9×1</p>
<br>
<p><strong>三、ups输出电流的计算方法</strong> 我们先来看下输出电流的计算公式：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390426238.png&amp;size=m" alt=""></p>
<p>S：<strong>三相UPS=3，单相UPS=1</strong> 。</p>
<br>
<p><strong>举例：</strong></p>
<p>已知：UPS功率：100KVA，输出电压：220V，S=3（三相ups）。</p>
<p>计算结果如下：</p>
<p>100000</p>
<p>输出电流（A）＝------------=152A</p>
<p>220×3</p>
<br>
<p>这里面补充下，什么是<strong>标称功率</strong>？</p>
<p>标称功率是厂家标称的功率,，最大功率是产品的实际最大功率，产品的实际功率一般不是整数, 比如说是511.3瓦, 厂家不可能把这个小数都标出来的, 很有可能就标500瓦，那么标称功率就是500瓦，也就是就近取整。</p>
<br>
<p><strong>四、电缆大小计算方法</strong></p>
<p>交流电流一般按<strong>3-5A/mm2</strong>计算，直流电流一般按<strong>2-4A/mm2</strong> 计算；</p>
<p>例如：100KVA 输入电流：160A，输出电流：152A，电池电流200A。</p>
<p>输入线缆（mm2）＝160/5=32mm2(实际可采用35 mm2)</p>
<p>输出线缆（mm2）＝152/5=30mm2(实际可采用35 mm2)</p>
<p>电池线缆（mm2）＝200/4=50mm2(实际可采用60 mm2)</p>
<br>
<br>
<br>
<p><strong>二、ups电池的配置</strong></p>
<br>
<br>
<br>
<p>这里面就看下ups常用的两种计算方法。
 <br>
 <strong>一、恒电流法</strong></p>
<p><strong>1、计算蓄电池的最大放电电流值：</strong></p>
<p><strong>I最大</strong> =Pcosф/（η*E临界*N）</p>
<p><strong>注：</strong></p>
<p><strong>P →</strong> UPS电源的标称输出功率</p>
<p><strong>cosф →</strong> UPS电源的输出功率因数</p>
<p><strong>η →</strong> UPS逆变器的效率，一般为0.88~0.96（根据具体型号\功率取值）</p>
<p><strong>E临界 →</strong> 蓄电池组的临界放电电压（12V电池约为10.5V，2V电池约为1.75V）</p>
<p><strong>N →</strong> 每组电池的数量</p>
<br>
<p><strong>2、根据所选的蓄电池组的后备时间，查出所需的电池组的放电速率值C，</strong></p>
<p><strong>然后根据：</strong></p>
<p>电池组的标称容量= I最大/C</p>
<p>算出电池的标称容量。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390426374.png&amp;size=m" alt="">
 <br>
 <strong>举例</strong>
 <br>
 <strong>型号：</strong> 三进三出高频系列 100KVA，输出功率因数Cosф：0.8 直流电压：480V（电池低压保护：420V） 效率：93% 后备时间：2小时，每一电池组额定节数N:40节。</p>
<p><strong>电流法</strong></p>
<p><strong>I最大</strong> =Pcosф/（η*E临界*N）= 100*1000*0.8/0.93*10.5*40 =204.8A</p>
<p><strong>电池组的标称容量AH</strong>= I最大/C = 204.8/<strong>0.42</strong>= 487.6AH</p>
<p>(C值取至于蓄电池的放电时间与放电速率C对应表，0.42对应的是2h，可以查看上表)</p>
<p>因此， <strong>电池组的标称容量AH</strong> 487.6AH，需要选用12V100AH 40节 <strong>并联5组</strong>。</p>
<br>
<br>
<p><strong>二、恒功率法</strong></p>
<p>根据蓄电池<strong>功率</strong> 可以准确地选出蓄电池的<strong>型号</strong>，首先计算在后备时间内，每个电池至少向<strong>UPS提供的恒功率</strong>。</p>
<p>恒功率法计算公式如下：</p>
<p><strong>电池组提供的功率W</strong> ＝UPS的负载KVA×功率因数 / 逆变器的效率</p>
<p><strong>需要每节电池提供的功</strong> <strong>率</strong> ＝电池组提供的功率W / 每组电池额定节数</p>
<p><strong>即：</strong></p>
<p>W＝{P（VA)×1000×cosØ}/（ŋ×N）</p>
<br>
<p><strong>其中：</strong></p>
<p>W----电池组提供的功率</p>
<p>P-----UPS额定功率</p>
<p>cosØ--功率因数</p>
<p>ŋ-----UPS满载时逆变器效率</p>
<p>N--- -UPS每电池组额定节数
 <br>
 <strong>举例</strong>
 <br>
 台达NT系列80KVA UPS 后备时间30min，选用DCF126-</p>
<p>12系列电池。计算电池容量？</p>
<p>　　台达NT80KVA UPS直流终止电压为300V及U临界=300V，直流电压为348V电池组选用29只12V电池，故N=29，n=6， U终压=1.75v UPS的功率因子Pf =0.8，逆变器转换效率η=0.95，DCF126-12系列电池放电30min终止电压1.75V/CELL是放电速率？=0.98（以上数据均应用台达UPSDCF126电池性能技术参数）</p>
<br>
<p><strong>恒功率法</strong>　　</p>
<p>P（W）={P（VA）*Pf}/η
 <br>
 ={80*1000*0.8}/0.95=67368.4（W）</p>
<p>87.2／217=1.78组。
 <br>
 即：选用2组120AH，共58节120AH电池。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/rpK8Ewh5nL7uEKL2-WRdEA">https://mp.weixin.qq.com/s/rpK8Ewh5nL7uEKL2-WRdEA</a></p>]]></description><guid isPermaLink="false">/archives/ca5c90ea-fb80-4f9e-92d5-0986de06c39b</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:54:15 GMT</pubDate></item><item><title><![CDATA[如何叠加多条宽带，使局域网速度更快？]]></title><link>https://wiki.dgfygk.com/archives/016586d4-0550-421f-87b8-ed37a7ddd99b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%8F%A0%E5%8A%A0%E5%A4%9A%E6%9D%A1%E5%AE%BD%E5%B8%A6%EF%BC%8C%E4%BD%BF%E5%B1%80%E5%9F%9F%E7%BD%91%E9%80%9F%E5%BA%A6%E6%9B%B4%E5%BF%AB%EF%BC%9F&amp;url=/archives/016586d4-0550-421f-87b8-ed37a7ddd99b" width="1" height="1" alt="" style="opacity:0;">
<p>有朋友多次问到，关于多条宽带如何合并，使局域网内带宽更快？通常我们在企业网络或实际项目中，随着用户的增加，一条或者几条带宽不能满足正常使用，便可以对带宽进行叠加，便于网络带度更快；</p>
<br>
<p>这个内容我们之前有提到过，网络用久就可能会面临这个问题，我们一起来了解下合并组建局域网的方法。
 <br>
 <strong>一、为什么要用多WAN口</strong></p>
<p>多wan口路由器， 一般是有2个wan口，分别为wan1与wan2。当然也有更多的wan口的。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390440657.png&amp;size=m" alt=""></p>
<p><strong>为什么要有多WAN口？</strong></p>
<p>路由器连接多条宽带的目的主要有以下三个：</p>
<p>**增加带宽：**上网主机可以通过任意宽带上网，从应用角度讲，相当于一条更高速的带宽。</p>
<p>**冗余备份：**如果其中一条宽带出现故障，可以使用其他宽带上网，保证网络畅通无中断。</p>
<p>**ISP选路：**即所谓的"访问联通走联通，访问电信走电信"，使得访问目的走正确路径。</p>
<p>同时双wan口路由器它可以<strong>均衡负载，减小延迟</strong>。</p>
<br>
<p><strong>二、带宽叠加</strong></p>
<p>通常家里有可能有两条带宽的，联通或移动的，可能都是充话费送的，但网速都不怎么样，不用可惜，用之委屈，那怎么办呢？这个时候可以使用多WAN口路由器。</p>
<br>
<p><strong>1、将两条带宽同时接入WAN1和WAN2口。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390440739.png&amp;size=m" alt=""></p>
<p>2个wan口可以分别设置相应的WAN口网络参数。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390440844.png&amp;size=m" alt=""></p>
<p>设置成功后就可以实现2条带宽叠加。</p>
<br>
<p><strong>2、那是不是所有情况下都可以叠加带宽？</strong></p>
<p>答：从两个方面考虑：</p>
<p>[1] 对整个局域网来说，出口带宽数量多了，相当于总带宽上升，这样是可以起到叠加效果。</p>
<p>[2] 针对特定主机来说，多条宽带能否叠加，与主机的应用有关系。</p>
<br>
<p><strong>我们以一个列子来解答：</strong></p>
<p>主机A所在网络的接口路由器拉了两条宽带，如下：</p>
<p>|----------|----------|----------|----------|
 <br>
 | <strong>接口</strong> | <strong>宽带类型</strong> | <strong>下行带宽</strong> | <strong>上行带宽</strong> |
 <br>
 | <strong>WAN1</strong> | 光纤 | 100M | 100M |
 <br>
 | <strong>WAN2</strong> | 光纤 | 20M | 20M |</p>
<br>
<p>不考虑其他主机占用带宽时，内网主机A的上网带宽是多少呢？</p>
<p><strong>上传：</strong></p>
<p>主机向外网某服务器上传最大速度为12.5MBps（100Mbps）或者2.5MBps（20Mbps）。原因是给同一个服务器上传时只能走某一个WAN口。比如QQ上传邮件附件、百度网盘上传等。如果是多链接上传，上传速度可以叠加。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/rIOFmPbFBFB1Q60LJUt6DA">https://mp.weixin.qq.com/s/rIOFmPbFBFB1Q60LJUt6DA</a></p>]]></description><guid isPermaLink="false">/archives/016586d4-0550-421f-87b8-ed37a7ddd99b</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:53:43 GMT</pubDate></item><item><title><![CDATA[弱电网工们必看的网络基础知识大合集]]></title><link>https://wiki.dgfygk.com/archives/262fc815-2b59-4f77-b33c-34f399284401</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BC%B1%E7%94%B5%E7%BD%91%E5%B7%A5%E4%BB%AC%E5%BF%85%E7%9C%8B%E7%9A%84%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E5%A4%A7%E5%90%88%E9%9B%86&amp;url=/archives/262fc815-2b59-4f77-b33c-34f399284401" width="1" height="1" alt="" style="opacity:0;">
<p>在这个信息互联的时代，虽然不需要人人都学习网络技术，但是还是应该掌握一点网络的基础知识，关键时刻能够自助，尤其对于想要从事网络行业的大家来说，掌握这些知识就显得尤为重要，今天就为大家盘点一些常见的网络基础知识大全！</p>
<br>
<p>0 1 网络的基本概念</p>
<br>
<br>
<br>
<p>客户端:应用 C/S（客户端/服务器） B/S（浏览器/服务器）</p>
<p>服务器：为客户端提供服务、数据、资源的机器</p>
<p>请求：客户端向服务器索取数据</p>
<p>响应：服务器对客户端请求作出反应，一般是返回给客户端数据</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448113.png&amp;size=m" alt=""></p>
<br>
<p>0 2 IP 、子网掩码 、路由器 、DNS</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448218.png&amp;size=m" alt=""></p>
<br>
<p><strong>IP地址：</strong></p>
<br>
<p>IP地址是IP协议提供的一种统一的地址格式，它为互联网上的每一个网络和每一台主机分配一个逻辑地址，以此来屏蔽物理地址（每个机器都有一个编码，如MAC上就有一个叫MAC地址的东西）的差异。是32位二进制数据，通常以十进制表示，并以"."分隔。IP地址是一种逻辑地地址，用来标识网络中一个个主机，在本地局域网上是惟一的。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448373.png&amp;size=m" alt=""></p>
<br>
<p><strong>IP：</strong></p>
<p>IP（网络之间互连的协议）它是能使连接到网上的所有计算机网络实现相互通信的一套规则，规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统，只要遵守IP协议就可以与因特网互连互通。IP地址有唯一性，即每台机器的IP地址在全世界是唯一的。这里指的是网络上的真实IP它是通过本机IP地址和子网掩码的"与"运算然后再通过各种处理算出来的（要遵守TCP协议还要加报文及端口什么的，我没有细追究，现在还用不上，反正暂时知道被处理过的就行了），顺便教大家查自己真实IP的方法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448437.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448481.png&amp;size=m" alt=""></p>
<br>
<p><strong>子网掩码：</strong></p>
<p>要想理解什么是子网掩码，就不能不了解IP地址的构成。互联网是由许多小型网络构成的，每个网络上都有许多主机，这样便构成了一个有层次的结构。IP地址在设计时就考虑到地址分配的层次特点，将每个IP地址都分割成网络号和主机号两部分，以便于IP地址的寻址操作。</p>
<br>
<p>IP地址的网络号和主机号各是多少位呢？如果不指定，就不知道哪些位是网络号、哪些是主机号，这就需要通过子网掩码来实现。什么是子网掩码子网掩码不能单独存在，它必须结合IP地址一起使用。子网掩码只有一个作用，就是将某个IP地址划分成网络地址和主机地址两部分子网掩码的设定必须遵循一定的规则。</p>
<br>
<p>与IP地址相同，子网掩码的长度也是32位，左边是网络位，用二进制数字"1"表示；右边是主机位，用二进制数字"0"表示。假设IP地址为"192.168.1.1"子网掩码为"255.255.255.0"。其中，"1"有24个，代表与此相对应的IP地址左边24位是网络号；"0"有8个，代表与此相对应的IP地址右边8位是主机号。</p>
<br>
<p>这样，子网掩码就确定了一个IP地址的32位二进制数字中哪些是网络号、哪些是主机号。这对于采用TCP/IP协议的网络来说非常重要，只有通过子网掩码，才能表明一台主机所在的子网与其他子网的关系，使网络正常工作。</p>
<p>常用的子网掩码有数百种，这里只介绍最常用的两种子网掩码。</p>
<br>
<p>子网掩码是"255.255.255.0"的网络：</p>
<br>
<p>最后面一个数字可以在0~255范围内任意变化，因此可以提供256个IP地址。但是实际可用的IP地址数量是256-2，即254个，因为主机号不能全是"0"或全是"1"。</p>
<p>子网掩码是"255.255.0.0"的网络：</p>
<br>
<p>后面两个数字可以在0~255范围内任意变化，可以提供255²个IP地址。但是实际可用的IP地址数量是255²-2，即65023个。</p>
<br>
<p>IP地址的子网掩码设置不是任意的。如果将子网掩码设置过大，也就是说子网范围扩大，那么，根据子网寻径规则，很可能发往和本地主机不在同一子网内的目标主机的数据，会因为错误的判断而认为目标主机是在同一子网内，那么，数据包将在本子网内循环，直到超时并抛弃，使数据不能正确到达目标主机，导致网络传输错误；如果将子网掩码设置得过小，那么就会将本来属于同一子网内的机器之间的通信当做是跨子网传输，数据包都交给缺省网关处理，这样势必增加缺省网关(文章下方有解释)的负担，造成网络效率下降。因此，子网掩码应该根据网络的规模进行设置。如果一个网络的规模不超过254台电脑，采用"255.255.255.0"作为子网掩码就可以了，现在大多数局域网都不会超过这个数字，因此"255.255.255.0"是最常用的IP地址子网掩码；假如在一所大学具有1500多台电脑，这种规模的局域网可以使用"255.255.0.0"。</p>
<br>
<p><strong>网关：</strong></p>
<br>
<p>网关实质上是一个网络通向其他网络的IP地址。比如有网络A和网络B，网络A的IP地址范围为"192.168.1.1~192.168.1.254"，子网掩码为255.255.255.0；网络B的IP地址范围为"192.168.2.1~192.168.2.254"，子网掩码为255.255.255.0。在没有路由器的情况下，两个网络之间是不能进行TCP/IP通信的，即使是两个网络连接在同一台交换机（或集线器）上，TCP/IP协议也会根据子网掩码（255.255.255.0）判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信，则必须通过网关。如果网络A中的主机发现数据包的目标主机不在本地网络中，就把数据包转发给它自己的网关，再由网关转发给网络B的网关，网络B的网关再转发给网络B的某个主机。网络B向网络A转发数据包的过程也是如此 所以说，只有设置好网关的IP地址，TCP/IP协议才能实现不同网络之间的相互通信。那么这个IP地址是哪台机器的IP地址呢？网关的IP地址是具有路由功能的设备的IP地址，具有路由功能的设备有路由器、启用了路由协议的服务器（实质上相当于一台路由器）、代理服务器（也相当于一台路由器）。</p>
<br>
<p><strong>路由器（Windows下叫默认网关，网关就是路由，路由就是网关不要蒙）</strong></p>
<br>
<p>如果搞清了什么是网关，默认网关也就好理解了。就好像一个房间可以有多扇门一样，一台主机可以有多个网关。默认网关的意思是一台主机如果找不到可用的网关，就把数据包发给默认指定的网关，由这个网关来处理数据包。现在主机使用的网关，一般指的是默认网关。</p>
<br>
<p>下方是百度百科给出的解释</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448599.png&amp;size=m" alt=""></p>
<p>如何设置默认网关 一台电脑的默认网关是不可以随随便便指定的，必须正确地指定，否则一台电脑就会将数据包发给不是网关的电脑，从而无法与其他网络的电脑通信。默认网关的设定有手动设置和自动设置两种方式。</p>
<br>
<p><strong>手动设置：</strong> 手动设置适用于电脑数量比较少、TCP/IP参数基本不变的情况，比如只有几台到十几台电脑。因为这种方法需要在联入网络的每台电脑上设置"默认网关"，非常费劲，一旦因为迁移等原因导致必须修改默认网关的IP地址，就会给网管带来很大的麻烦，所以不推荐使用。需要特别注意的是：默认网关必须是电脑自己所在的网段中的IP地址，而不能填写其他网段中的IP地址。</p>
<br>
<p><strong>自动设置：</strong> 自动设置就是利用DHCP服务器来自动给网络中的电脑分配IP地址、子网掩码和默认网关。这样做的好处是一旦网络的默认网关发生了变化时，只要更改了DHCP服务器中默认网关的设置，那么网络中所有的电脑均获得了新的默认网关的IP地址。这种方法适用于网络规模较大、TCP/IP参数有可能变动的网络。另外一种自动获得网关的办法是通过安装代理服务器软件（如MS Proxy）的客户端程序来自动获得，其原理和方法和DHCP有相似之处。由于篇幅所限，就不再详述了。</p>
<br>
<p><strong>缺省网关：</strong></p>
<p>缺省网关（Default Gateway）是计算机网络中一个如何将数据包转发到其他网络中的节点。在一个典型的TCP / IP网络，节点（如服务器、工作站和网络设备）都有一个定义的默认路由设置（指向默认网关）。可以在没有特定路由的情况下，明确出发送数据包的下一跳IP地址。</p>
<br>
<p>下方是百度百科给出的解释：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448733.png&amp;size=m" alt=""></p>
<p>可以看出缺省网关就是默认网关，那么有人会说既然有一样为什么又凭空多出来一个缺省网关，我的理解是这样的，应该说默认网关是缺省网关的一个子集。缺省网关有一个定义的默认路由设置（指向默认网关），缺省网关就相当于一个代理服务器暂时管理发送的数据包，当发送到目标主机时先由目标主机的缺省网关接收再找到对应的默认网关，就相当于缺省网关是父类，默认网关是子类~~</p>
<br>
<p><strong>DNS服务器：</strong></p>
<br>
<p>域名服务器(Domain Name Server)。在Internet上域名与IP地址之间是一一对应的，域名虽然便于人们记忆，但机器之间只能互相认识IP地址，它们之间的转换工作称为域名解析，域名解析需要由专门的域名解析服务器来完成，DNS就是进行域名解析的服务器 。</p>
<br>
<p><strong>DHCP服务器：</strong></p>
<br>
<p>DHCP指的是由服务器控制一段IP地址范围，客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码。提升地址的使用率。</p>
<br>
<p><strong>MAC地址：</strong></p>
<p>MAC地址就如同我们身份证上的身份证号码，具有全球唯一性。(知道这个就行了，不用往下看了)</p>
<br>
<p>MAC（Media Access Control，介质访问控制）地址</p>
<p>前24位叫做组织唯一标志符（Organizationally Unique Identifier，即OUI），是由IEEE的注册管理机构给不同厂家分配的代码，区分了不同的厂家。</p>
<br>
<p>后24位是由厂家自己分配的，称为扩展标识符。同一个厂家生产的网卡中MAC地址后24位是不同的。</p>
<br>
<p>网卡的物理地址通常是由网卡生产厂家烧入网卡的EPROM（一种闪存芯片，通常可以通过程序擦写），它存储的是传输数据时真正赖以标识发出数据的电脑和接收数据的主机的地址。</p>
<br>
<p>也就是说，在网络底层的物理传输过程中，是通过物理地址来识别主机的，它一定是全球唯一的。比如，著名的以太网卡，其物理地址是48bit（比特位）的整数，如：44-45-53-54-00-00,以机器可读的方式存入主机接口中。以太网地址管理机构(除了管这个外还管别的)（IEEE）（IEEE：电气和电子工程师协会）将以太网地址，也就是48比特的不同组合，分为若干独立的连续地址组，生产以太网网卡的厂家就购买其中一组，具体生产时，逐个将唯一地址赋予以太网卡。</p>
<br>
<p>在一个稳定的网络中，IP地址和MAC地址是成对出现的。如果一台计算机要和网络中另一外计算机通信，那么要配置这两台计算机的IP地址，MAC地址是网卡出厂时设定的，这样配置的IP地址就和MAC地址形成了一种对应关系。在数据通信时，IP地址负责表示计算机的网络层地址，网络层设备（如路由器）根据IP地址来进行操作；MAC地址负责表示计算机的数据链路层地址，数据链路层设备（如交换机）根据MAC地址来进行操作。IP和MAC地址这种映射关系由ARP（Address Resolution Protocol，地址解析协议）协议完成。</p>
<br>
<p>0 3 服务器</p>
<br>
<br>
<p><strong>服务器的分类：</strong></p>
<br>
<p>按照软件开发阶段来分，服务器可以大致分为2种</p>
<br>
<p>（1）远程服务器</p>
<br>
<p>别名：外网服务器、正式服务器</p>
<br>
<p>使用阶段：应用上线后使用的服务器</p>
<br>
<p>使用人群：供全体用户使用</p>
<br>
<p>速度：服务器的性能、用户的网速</p>
<br>
<p>（2）本地服务器</p>
<br>
<p>别名：内网服务器、测试服务器</p>
<br>
<p>使用阶段：应用处于开发、测试阶段使用的服务器</p>
<br>
<p>使用人群：仅供公司内部的开发人员、测试人员使用</p>
<br>
<p>速度：由于是局域网，所以速度飞快，有助于提高开发测试效率</p>
<p>本地服务器的选择</p>
<br>
<p>远程服务器就是本地内网服务器开放外网访问而已</p>
<br>
<p>如果处于学习、开发阶段，自己搭建一个本地服务器即可</p>
<br>
<p>0 4 端口号</p>
<br>
<p>端口包括物理端口和逻辑端口。物理端口是用于连接物理设备之间的接口，逻辑端口是逻辑上用于区分服务的端口。TCP/IP协议中的端口就是逻辑端口，通过不同的逻辑端口来区分不同的服务。</p>
<br>
<p>端口有什么用呢？我们知道，一台拥有IP地址的主机可以提供许多服务，比如Web服务、FTP服务、SMTP服务等，这些服务完全可以通过1个IP地址来实现。那么，主机是怎样区分不同的网络服务呢？显然不能只靠IP地址，因为IP 地址与网络服务的关系是一对多的关系。实际上是通过"IP地址+端口号"来区 分不同的服务的。</p>
<p>公认端口（Well-Known Ports）</p>
<br>
<p>这类端口也常称之为"常用端口"。这类端口的端口号从0到1023，它们紧密绑定于一些特定的服务。通常这些端口的通信明确表明了某种服务的协议，这种端口是不可再重新定义它的作用对象。80端口实际上总是HTTP通信所使用的，而23号端口则是Telnet服务专用的。</p>
<p>注册端口（Registered Ports）</p>
<br>
<p>端口号从1025到49151。分配给用户进程或应用程序。这些进程主要是用户选择安装的一些应用程序，而不是分配好的公认端口的常用程序。</p>
<p>动态和/或私有端口（Dynamic and/or Private Ports）</p>
<br>
<p>之所以称为动态端口，因为它一般不固定分配某种服务，而是动态分配。</p>
<br>
<br>
<p>「网」是什么？</p>
<br>
<br>
<p>在今天这个时代，网络和我们息息相关，别说断网了，网络卡顿一会儿都会影响我们的生活，这时候大家会不会很想知道网络出现问题的原因是什么呢？是不是很想了解一下网络呢？其实这个网络有个名字叫做计算机网络，顾名思义就是计算机组成的网络。下图就是一个简单的计算机网络的拓扑。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390448844.png&amp;size=m" alt="">
 <br>
 可以看到图中有交换机、路由器、AP 这些网络设备，还有电脑、手机这些终端。其中交换机、路由器、AP 是我们计算机网络通信线路的组成部分，计算机相信就不需要我多说了，大家都知道，简单和大家说说交换机、路由器和 AP 分别是什么------这三个设备都是帮助大家将计算机连接在一起的设备，当然还有网线、光纤这种的通信线缆------而关于路由器、交换机和 AP 到底是不是计算机尚有争议，我们可以认为它们是嵌入式计算机的一种。 1 家里是如何上网的？ 以咱们家里的网络为例，路由器用来将家里所有设备的访问需求送到运营商的网络上去，接入到互联网，最终送达目的地。如果家里设备多那路由器的口就不太够了，全部都接路由器上，走线也不是很方便，我们就可以用一台交换机来解决这个问题。 随着网络的发展，有线已经不能够满足我们的需求，这时候 AP 和无线终端就出现了，AP 本质上是无线侧的交换机，它同交换机一样帮我们把无线终端接入到网络中，以电磁波为媒介。这里注意区分家用无线路由器和我们所说的商用路由器，家用无线路由器其实是一个集路由器、交换机、AP 与一体的设备，一般长这样：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390449060.png&amp;size=m" alt="">
 <br>
 普通家用无线路由器（图片来自与网络） 上面有几对天线，一个 WAN 口，多个 LAN 口，WAN 用来接光猫，LAN 口用来连有线终端，天线用来连无线终端。这种设备对家庭用户或者小白用户来说十分友好，按照上面的提示一步步设置就可以上网啦，但是对商用场景来说就不太够了，商用场景需要设备性能更强大，可以自定义的东西更多，所以我们会有家庭无线路由器和商用路由器之分。 2 LAN、MAN、WAN 是什么意思？ 最开始的网络就是将许多终端连在一起（严格意义上来说最开始只能算作联机），然后逐步壮大，并且这些终端也发展的更加智能，功能更大多样化，形成了今天的计算机网络。
 <br>
 计算机网络还分为局域网（LAN）、城域网（MAN）和广域网（WAN）。生活中常见的「LAN」就是指局域网，公司内网就是一个局域网，家里从光猫接入往后也是局域网。城域网生活中我们接触的不多，这里就不详细介绍了。广域网就是常见的"WAN" 标识了，我们可以简单的认为，运营商给拉进来的宽带连接的就是广域网，以光猫为分界点，光猫以外就是 WAN，光猫以内就是 LAN。 3 数据通讯和资源共享</p>
<p>除此之外，计算机网络也拥有着强大的功能：
 <br>
 数据通信就是计算机网络最主要的功能之一了，一些其他的重要功能也都需要依赖于数据通信来完成，计算机网络也是对传统通信方式的革新，是继电报、电话业务之后的第三种最大的通信业务。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390449209.png&amp;size=m" alt=""></p>
<p>信息传递（图片来自于网络）
 <br>
 资源共享是建立计算机网络最主要的目的，可以共享的不仅仅是数据资源，还有软件资源和硬件资源。信息检索，看视频，刷微博共享的就是数据资源；我们使用的各种应用软件，就是典型的软件共享；而云服务器，云桌面等就是硬件资源共享了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390449406.png&amp;size=m" alt=""></p>
<p>资源共享（图片来自于网络）
 <br>
 说到这里，大家可能对计算机网络有些概念了，总结一下就是：一些相互连接的、以共享资源为目的的、自治的计算机的集合。 4 指挥中心：操作系统、管理软件、通信协议 早期的一些终端不算是自治的计算机，因此那时候的终端互联只能称作「联机」。简单来说，两台用网线连在一起并且可以互通的电脑，就是一个最小的计算机网络。计算机网络和交通系统很像，道路就是用户连接不同计算机的通信线路，房屋、楼宇是进行不同运算和提供各种资源的计算机，人们通过道路在建筑之间穿梭，互通有无。当然，计算机网络并不是简单的物理上的互联就可以实现这些功能的，还需要操作系统、管理软件、通信协议从中协调，也就是说，想要运行下去，还需要一个「指挥中心」。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390449552.png&amp;size=m" alt=""></p>
<p>交通网络系统（图片来自于网络）
 <br>
 讲到这里，大家有没有觉得计算机网络其实比我们过去认为的要复杂许多呢？了解计算机网络一些基本原理也是一件重要的事情，理解好网络原理对平时学习、工作场景有着大大的提升，也可以有效避免我们在组建自己的网络设备时候掉坑的几率。</p>
<br>
<br>
<p>传输介质和网络设备</p>
<br>
<br>
<p>还记得文章开头的那张图吗？除了前面提到的网络设备和终端以外，还有负责连接发送方和接受方的"线"，构成发送方和接收方之间的物理通路。这些看得见和看不见的"线"就是网络传输介质。</p>
<p>常见的网络传输介质有同轴电缆、网线、光纤还有电磁波。
 <br>
 1 网线和同轴电缆 同轴电缆一般用于早期以太网，传输速度通常在 10Mbps 以内。当然粗缆是可以到 1-2Gbps 的速度，不过现在几乎被我们接下来要说到的网线给取代了。 网线即为双绞线，市面上的网线通常为八芯双绞线，传输距离建议不超过一百米。常见的网线标准有五类线、超五类线、六类线、超六类线和七类线这五种标准。不同标准传输速率不同，但是都不建议超过 100 米。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390449878.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390449925.png&amp;size=m" alt=""></p>
<p>Ubiquiti 的网线，是使用频率较高的超五类屏蔽线。（图片来自于 Ubiquiti 官网）
 <br>
 细心的小伙伴可能注意到，双绞线的绞合度是不一样的，而且八根芯的颜色也不一样，常见网线标准之间的区分就是绞合度、铜芯粗细、有无龙骨、有无屏蔽层等。其实这八根芯的颜色排列也是有讲究的，在我们线缆制作中会有线序的说法，下面就是两种线序标准：</p>
<table>
 <thead>
  <tr>
   <th>标准</th>
   <th>1</th>
   <th>2</th>
   <th>3</th>
   <th>5</th>
   <th>6</th>
   <th>7</th>
   <th>8</th>
   <th>9</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>T568-A</td>
   <td>绿白</td>
   <td>绿</td>
   <td>橙白</td>
   <td>蓝</td>
   <td>蓝白</td>
   <td>橙</td>
   <td>棕白</td>
   <td>棕</td>
  </tr>
  <tr>
   <td>T568-B</td>
   <td>橙白</td>
   <td>橙</td>
   <td>绿白</td>
   <td>蓝</td>
   <td>蓝白</td>
   <td>绿</td>
   <td>棕白</td>
   <td>棕</td>
  </tr>
 </tbody>
</table>
<p>2 光纤：远距传输理想之选 现在在中大型商用场景下网线已经远远不能满足我们当前的网络规模了，即便是加上中继，传输质量和范围都无法满足我们现在的需求，所以有了光纤的出现。光纤全称光导纤维，通常由玻璃或塑料制成，最外层会涂一层隔绝光线的材料。 光纤具有绝缘性能好、信号衰减小、传输速度快、传输距离大的优势，通常应用于主干网的连接，不受外界电磁场的影响，带宽几乎无限制，可以实现每秒万兆位的数据传送，传输距离可达数百公里。光纤有单模光纤和多模光纤之分，单模光纤传输距离长，可达 20-120 公里的距离。多模光纤适用于低速短距离的场景，比如说一个局域网里的交换机之间的互联，交换机和路由器之间的互联都可以用多模光纤，传输距离在 2 公里以内。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390450079.png&amp;size=m" alt=""></p>
<p>Ubiquiti 的多模光纤，主要用于路由器和交换机之间的连接（图片来自于 Ubiquiti 官网）
 <br>
 3 电磁波：突破网线限制</p>
<p>但随着终端的多样化，以及有线对设备移动范围的限制，我们开始使用电磁波作为对有线网络的扩展，电磁波的范围很广，适合作为信号传输的只有介于 3 Hz 和约 300 GHz 之间的无线电波，无线电波又被称为射频电波，我们的无线网络就是用的无线电波中超高频的一部分。国内最常用的频段是 2.4 GHz 和 5 GHz 。
 <br>
 4 路由器、交换机和 AP</p>
<p>到这里，我们常见的传输介质已经讲完了，接下来我们再来聊聊网络设备，前面有和大家简单提到过。我们主要讲路由器、交换机和 AP 这三种网络设备。</p>
<p>先来看看路由器，路由器是连接两个网络的硬件设备，是网络的大门，同时也承担寻路功能，因此，路由器又叫做网关设备。它帮助我们将数据运送到互联网上，然后互联网中的无数台路由器又帮助我们将信息运送到目的地。</p>
<p>交换机是用于数据转发交换的设备。通常用于路由器和各终端直接的连接，可将交换机视为路由器接口的拓展。随着网络的发展，也出现了三层交换机来承载部分内网寻路的工作，但是请大家记住，交换机的本职工作还是内网交换，在大型网络寻路中还是要通过路由器来实现；同样的，路由器的工作是不同网络之间的寻路，请不要将路由器作为交换机使用，尤其是终端较多的情况下。</p>
<p>AP 是用于无线网络的交换机，用来接入无线终端，是无线网络中的核心设备。</p>
<p>大家家里应该都会有无线路由器这种东西，AP 的功能和无线路由器上的天线是一样的，都用作无线终端的接入。咱们的无线路由器上通常还会标有 LAN 和 WAN，LAN 口一般都是交换口，也就是一个小交换机，WAN 承担的就是路由功能啦。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/-zndtalvBz1ZT-bFjMaeUw">https://mp.weixin.qq.com/s/-zndtalvBz1ZT-bFjMaeUw</a></p>]]></description><guid isPermaLink="false">/archives/262fc815-2b59-4f77-b33c-34f399284401</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:53:16 GMT</pubDate></item><item><title><![CDATA[视频存储为什么要选择监控专用硬盘？它有什么好处？]]></title><link>https://wiki.dgfygk.com/archives/8f9666db-7c7f-4216-8f1c-fad95c0012d0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%A7%86%E9%A2%91%E5%AD%98%E5%82%A8%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E9%80%89%E6%8B%A9%E7%9B%91%E6%8E%A7%E4%B8%93%E7%94%A8%E7%A1%AC%E7%9B%98%EF%BC%9F%E5%AE%83%E6%9C%89%E4%BB%80%E4%B9%88%E5%A5%BD%E5%A4%84%EF%BC%9F&amp;url=/archives/8f9666db-7c7f-4216-8f1c-fad95c0012d0" width="1" height="1" alt="" style="opacity:0;">
<p>智能化弱电业内总流传着一句硬盘录像机这个要用监控专用硬盘的！！！这个是为什么呀？下文我们一起来唠唠监控硬盘和普通电脑PC硬盘盘的区别。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390513766.png&amp;size=m" alt=""></p>
<p><strong>1.硬盘的寿命</strong></p>
<p>普通电脑硬盘在上电启动的时候会全速启动，瞬间电流可能达到2安，甚至更高。（故有部分客户使用普通电脑硬盘开机读取不到硬盘，换个高A数的电源适配器之后正常 此解决方案客户可在实际使用中参考）</p>
<p>监控硬盘启动的时候会缓慢加速，启动电流会控制在2安以下。</p>
<p>因为监控系统中通常会安装多个硬盘，这样在启动的瞬间会产生很大的启动电流，如果是普通电脑硬盘的话，电源会难以承受，甚至烧毁。另外，监控系统对硬盘的传输速度要求一般不高，但是会频繁的小数据量的读写。所以需要在磁头读写机构上针对监控系统的读写特点做结构优化设计，以延长磁头寿命。</p>
<p>监控硬盘的理论平均无故障运行时间比普通电脑硬盘要长的多，稳定性、可靠性要更高。</p>
<p><strong>2.连续工作时间差别</strong></p>
<p>普通电脑硬盘的设计以8×5为基础。8×5指的是普通普通电脑硬盘每天工作8个小时，每周工作5天(硬盘工作是指硬盘处于读写状态。需要特别注意的是硬盘加电后有两个状态：工作状态和等待状态，其中工作状态是硬盘进行读写工作，等待状态是硬盘没有进行读写工作但处于待命状态。)</p>
<p>监控硬盘是按24×7的企业级环境要求进行设计开发的。24×7就是每天工作24个小时，每周工作7天(就是连续不断的进行工作)。</p>
<p>所以普通电脑硬盘并不适合长时间连续读写，如果要求普通电脑硬盘长时间连续读写就会极大的损伤硬盘，使硬盘出现异常声音、读写错误、工作中止等多种问题并由此导致硬盘最终损坏。</p>
<p>硬盘录像机是常年不间断运行的，因此要求硬盘必须可以长时间连续工作，能满足这个要求的就是监控硬盘。</p>
<p><strong>3.启动差别</strong></p>
<p>所有的3.5寸硬盘都包含电机系统，因此硬盘加电启动的时候与电机加电启动的情况相类似：就是在加电的初始时间会出现较大的启动电流以完成硬盘启动。如果加电启动的时候硬盘外部供电系统不能保障足够的电流支持，则硬盘会启动失败，导致硬盘不可用。并且如果多次出现这种情况，容易造成硬盘的损坏。由于硬盘电机的供电是由外部电源系统直流12V满足的，因此在评价硬盘启动电流的时候都以直流12V的电流为标准。</p>
<p>普通电脑硬盘启动电流一般在2.8A～3.2A之间。监控硬盘的启动电流最高为2.0A。数字硬盘录像机经常采用多硬盘进行数据存储，以安装8片硬盘为例：普通电脑硬盘启动电流2.8×8=22.4A，最低的功耗为22.4A×12V=268.8W。DVR专用硬盘启动电流2.0A×8=16A，最高的功耗为16A×12V=192W。</p>
<p>可以看出，在相同硬盘数量的情况下监控硬盘对外部电源系统的要求更低(在同样输出功率的外部电源支持下，可以安装的DVR专用硬盘的数量会更多)。</p>
<p>说明：在上例的情况中，如果外部电源系统供电只能保障250W，则采用普通PC硬盘的时候会出现某些硬盘可以识别，某些硬盘不可以识别，并且没有规律性。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390513855.png&amp;size=m" alt=""></p>
<p><strong>4.运行功耗及散热差别</strong></p>
<p>这里运行功耗是指硬盘在正常读写状态时的功耗。PC硬盘的运行功耗一般为14.5瓦左右，监控硬盘的运行功耗一般为8瓦左右。</p>
<p>可以看出，监控硬盘运行功耗仅相当于普通电脑硬盘功耗的55%，低的运行功耗不仅对电源系统有重要意义，而且对数字硬盘录像机系统的散热也有重要的意义(运行功耗中大概75%会转变为热能)。在数字硬盘录像机中，如果安装8片硬盘，那普通PC硬盘的运行总功耗为14.5W×8=116W，DVR专用硬盘的运行总功耗为8W×8=64W。如果采用普通PC硬盘，可以想像在数字硬盘录像机这么小的空间内有这么高的发热源，对系统的散热要求是很高的，为了保障硬盘有一个合理的环境温度(0～60℃)，必须对硬盘系统进行有效的散热。而采用DVR专用硬盘，发热情况就好得多----降低了系统散热要求使系统对环境的适应性更强。</p>
<p><strong>5.传输差别</strong></p>
<p>监控硬盘除了采用传统电脑硬盘的传输模式，还引入了一个更新的传输模式----不间断传输模式，该传输模式最大为65MB/S。</p>
<p>通过引入不间断传输模式，使硬盘对流媒体的支持更加可靠，充分保障数字硬盘录像机在录入的同时进行回放的流畅性和稳定性。这是其它硬盘所不具备的特性。</p>
<p><strong>6.价格差别</strong></p>
<p>相比普通电脑硬盘，监控硬盘在价格上与其相差无几，只是在特殊技术上的支持有所不同，前者更注重性能后者则注重稳定读写。只有企业级硬盘才相对贵一些，毕竟要求更加苛刻一些。</p>]]></description><guid isPermaLink="false">/archives/8f9666db-7c7f-4216-8f1c-fad95c0012d0</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:52:59 GMT</pubDate></item><item><title><![CDATA[弱电工程光纤收发器基础知识及常见故障解决方]]></title><link>https://wiki.dgfygk.com/archives/06f64241-403d-4cac-956c-2a8802578835</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BC%B1%E7%94%B5%E5%B7%A5%E7%A8%8B%E5%85%89%E7%BA%A4%E6%94%B6%E5%8F%91%E5%99%A8%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E5%8F%8A%E5%B8%B8%E8%A7%81%E6%95%85%E9%9A%9C%E8%A7%A3%E5%86%B3%E6%96%B9&amp;url=/archives/06f64241-403d-4cac-956c-2a8802578835" width="1" height="1" alt="" style="opacity:0;">
<p>我们常用的光纤收发器都有6个指示灯，那么每个指示灯都代表什么含义呢？是否所有指示灯都亮起才代表光纤收发器正常工作呢？我们一起来看看！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390539401.png&amp;size=m" alt=""></p>
<p>PWR：灯亮表示DC5V电源工作正常；</p>
<p>FDX：灯亮表示光纤以全双工方式传输数据；</p>
<p>FX 100：灯亮表示光纤传输速率为100Mbps；</p>
<p>TX 100：灯亮表示双绞线传输速率为100Mbps，灯不亮表示双绞线传输速率为10Mbps；</p>
<p>FX Link/Act：灯长亮表示光纤链路连接正确；灯闪亮表示光纤中有数据在传输；</p>
<p>TX Link/Act：灯长亮表示双绞线链路连接确；灯闪亮表示双绞线中有数据在传输10/100M。</p>
<p>若光纤收发器正常工作，PWR电源指示灯必须常亮，FX-LINK/ACT光纤链路指示灯、TX-LINK/ACT网络链路指示灯需常亮或闪烁，若LINK/ACT指示灯不亮，需检查相应链路是否连线正常；至于FDX工作模式指示灯、FX-100光纤速率指示灯、TX-100网络速率指示灯是否常亮对光纤收发器没有实质影响。</p>
<p>光纤收发器指标灯所标示的工作状态：</p>
<br>
<ol>
 <li>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390539426.png&amp;size=m" alt=""></p>
 </li>
 <li></li>
 <li></li>
</ol>
<p>光纤纤与光纤收发器连接示意图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390539466.png&amp;size=m" alt=""></p>
<p>另外，光纤收发器是需要成对使用：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390539508.png&amp;size=m" alt=""></p>
<p><strong>注意：</strong> 对于单纤的收发器必须要成对使用，而且需要将发射端放在信号源，接收端放在终端设备处。</p>
<p><strong>光收发器的指示灯的作用和故障判定方法</strong></p>
<p>1、 首先看光纤收发器或光模块的指示灯和双绞线端口指示灯是否已亮？</p>
<ul>
 <li>
  <p>A、 如收发器的光口（FX-LINK/ACT）指示灯不亮，请确定光纤链路是否正确的交叉链接，光纤插口TX-RX；RX-TX。</p>
 </li>
 <li>
  <p>B、 如A收发器的光口（FXFX-LINK/ACT）指示灯亮而B收发器的光口（FXFX-LINK/ACT）指示灯不亮，则故障在A收发器端：一种可能是：A收发器（TX）光发送口已坏，因为B收发器的光口（RX）接收不到光信号；另一种可能是：A收发器（TX）光发送口的这条光纤链路有问题（光缆或光纤跳线可能断了）。</p>
 </li>
 <li>
  <p>C、 双绞线（TXFX-LINK/ACT）指示灯不亮，请确定双绞线连线是否有错或连接有误？请用通断测试仪检测（不过有些收发器的双绞线指示灯须等光纤链路接通后才亮）。</p>
 </li>
 <li>
  <p>D、 有的收发器有两个RJ45端口:（To HUB）表示连接交换机的连接线是直通线;(To Node)表示连接交换机的连接线是交叉线。</p>
 </li>
 <li>
  <p>E、 有的收发器侧面有MPR开关:表示连接交换机的连接线是直通方式；DTE开关：连接交换机的连接线是交叉线方式。</p>
 </li>
</ul>
<p>2、 光缆、光纤跳线是否已断？</p>
<ul>
 <li>
  <p>A、 光缆通断检测：用激光手电、太阳光、发光体对着光缆接头或偶合器的一头照光；在另一头看是否有可见光？如有可见光则表明光缆没有断。</p>
 </li>
 <li>
  <p>B、 光纤连线通断检测：用激光手电、太阳光等对着光纤跳线的一头照光；在另一头看是否有可见光?如有可见光则表明光纤跳线没有断。</p>
 </li>
</ul>
<p>3、 半/全双工方式是否有误？</p>
<p>有的收发器侧面有FDX开关：表示全双工；HDX开关：表示半双工。</p>
<p>4、 用光功率计仪表检测</p>
<p>光纤收发器或光模块在正常情况下的发光功率：</p>
<p>多模2Km：-10db---18db之间；</p>
<p>单模20公里：-8db---15db之间；</p>
<p>单模60公里：-5db---12db之间；</p>
<p>假如在光纤收发器的发光功率在：-30db---45db之间，那么可以判断这个收发器有问题。</p>
<p><strong>光纤收发器常见故障及解决方法</strong></p>
<p>根据日常维护、用户出现的问题，总结起来，希望能给维护员工带来一定的帮助，达到根据故障现象来判断其原因，找准故障点，"对症下药"。</p>
<p>1、 收发器RJ45口与其他设备连接时，使用何种连线？</p>
<p>原因：收发器的RJ45口接PC机网卡（DTE数据终端设备）使用交叉双绞线，接HUB或SWITCH(DCE数据通信设备)使用平行线。</p>
<p>2、 TxLink灯不亮是什么原因？</p>
<p>答：</p>
<ul>
 <li>
  <p>1、接错双绞线</p>
 </li>
 <li>
  <p>2、双绞线水晶头与设备接触不良，或双绞线本身质量问题</p>
 </li>
 <li>
  <p>3、设备没有正常连</p>
 </li>
</ul>
<p>3、光纤正常连接后TxLink灯不闪烁却常亮是什么原因？</p>
<p>原因：</p>
<ul>
 <li>
  <p>1、引起该故障一般为传输距离太长；</p>
 </li>
 <li>
  <p>2、与网卡的兼容性问题（与PC机连接）</p>
 </li>
</ul>
<p>4、Fxlink灯不亮是什么原因？</p>
<p>原因：</p>
<ul>
 <li>
  <p>1、光纤线接错，正确接法为TX---RX,RX---TX或是光纤模式错了；</p>
 </li>
 <li>
  <p>2、传输距离太长或中间损耗太大，超过本产品的标称损耗，解决办法为：采取办法减小中间损耗或更换为传输距离更长的收发。</p>
 </li>
 <li>
  <p>3、光纤收发器的自身工作温度过高。</p>
 </li>
</ul>
<p>5、光纤正常连接后Fxlink灯不闪烁却常亮是什么原因？</p>
<p>原因：引起该故障一般为传输距离太长或中间损耗太大，超过本产品的标称损耗，解决办法为尽量减小中间损耗或是更换为传输距离更长的收发器。</p>
<p>6、五灯全亮或指示器正常但无法传输怎么办？</p>
<p>原因：一般关断电源重启一下即可恢复正常。</p>
<p>7、收发器环境温度是多少？</p>
<p>原因：光纤模块受环境温度的影响较大，虽然其本身内置自动增益电路，但温度超出一定范围之后，光模块的发射光功率受到影响而下降，从而削弱光网路信号的质量而使丢包率上升，甚至使光链路断开；（一般光纤模块工作温度可达70℃）</p>
<p>8、与外部设备协议的兼容性如何？</p>
<p>原因：</p>
<p>10/100M光纤收发器和10/100M交换机一样，对帧长都有一定限制，一般不超过1522B或1536B，当在局端连接的交换机支持一些比较 特别的协议（如：Ciss的ISL）而使包开销增大（Ciss的ISL的包开销为30Bytes），从而超过光纤收发器帧长的上限而被其丢弃，反映丢包率 高或不通，此时需要调整终端设备的MTU（MTU最大发送单元，一般IP封包的开销是18个字节，MTU为1500字节，现高端通信设备厂家存在内部网络 协议，一般采用另行封包的方式，将加重IP封包的开销，若数据为1500字节，IP封包后IP包的大小将超过18而被丢弃），使线上传输的包的大小满意网 络设备对帧长的限制。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390539559.png&amp;size=m" alt=""></p>
<p>9、机箱正常工作过一段时间后，为什么会出现部分卡不能正常工作的情况？</p>
<p>原因：早期机箱电源采用继电器方式。电源功率余量不足，线路损耗较大是主要问题。机箱正常工作过一段时间后，出现部分卡不能正常工作，当拔出部分插卡，剩 下的卡工作正常，机箱在长期工作后，接头氧化造成较大的接头损耗，这种电源跌落超出规定要求范围，可能造成机箱插卡不正常现象。现对机箱电源切换采用大功 率肖特基二极管进行隔离保护，改进接头的形式，减少控制电路及接头引起的电源跌落。同时加大电源的功率冗余，真正使备份电源方便、安全、使之更适应长期不 间断工作的要求。</p>
<p>10、收发器上提供链路告警具有何种功能？</p>
<p>原因：收发器具有链路告警功能（linkloss）,当某根光纤掉线时会自动回馈到电口（即电口上的指示灯也会随之灭），如果交换机有网管，则马上反映到交换机的网管软件。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390539616.png&amp;size=m" alt=""></p>
<p><strong>光纤收发器应注意的事项</strong></p>
<p>1、 光收发器本身是否支持全双工及半双工？</p>
<p>市面上有些芯片目前只能使用全双工环境，无法支持半双工，如果接到其他品牌的接换机（SWITCH）或集行器（HUB），而它又使用半双工模式，则一定会造成严重的冲突及丢包。</p>
<p>2、 是否与其他的光纤收发器做过连接测试？</p>
<p>目前市面上的光纤收发器愈来愈多，如不同品牌的收发器相互的兼容性事前没做过测试则也会产生丢包、传输时过长、忽快忽慢等现象。</p>
<p>3、 是否有防范丢包的安全装置？</p>
<p>有些厂商在制造光纤收发器时，为了降低成本，往外采用寄存器（Register）数据传输模式，这种方式最大的缺点就是传输时不稳定、丢包，而最好的就是采用缓冲线路设计，可安全避免数据丢包。</p>
<p>4、 温度适应能力？</p>
<p>光纤收发器本身使用时会产生高热，温度过高时（不能大于50℃），光纤收发器是否工作正常，是购买时非常值得考虑的因素！</p>]]></description><guid isPermaLink="false">/archives/06f64241-403d-4cac-956c-2a8802578835</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:52:20 GMT</pubDate></item><item><title><![CDATA[无线AP容量及网络带宽计算方法]]></title><link>https://wiki.dgfygk.com/archives/653f7f3f-e487-4626-9388-743ba027386b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%97%A0%E7%BA%BFAP%E5%AE%B9%E9%87%8F%E5%8F%8A%E7%BD%91%E7%BB%9C%E5%B8%A6%E5%AE%BD%E8%AE%A1%E7%AE%97%E6%96%B9%E6%B3%95&amp;url=/archives/653f7f3f-e487-4626-9388-743ba027386b" width="1" height="1" alt="" style="opacity:0;">
<p><strong>无线AP</strong> 是使用无线设备（手机等移动设备及笔记本电脑等无线设备）用户进入有线网络的接入点，主要用于宽带家庭、大楼内部、校园内部、园区内部以及仓库、工厂等需要无线监控的地方，典型距离覆盖几十米至上百米，也有可以用于远距离传送，目前最远的可以达到30KM左右，主要技术为IEEE802.11系列。大多数无线AP还带有接入点客户端模式（AP client），可以和其它AP进行无线连接，延展网络的覆盖范围。</p>
<p>在搭建无线网络的时候，肯定会遇到这样的问题：在考察环境之后怎样才能准确、科学、快速地预判出大概的无线AP需求量?带着这个问题，我们一起来探讨如何利用带宽估算无线AP的部署量。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390542369.png&amp;size=m" alt=""></p>
<p><strong>影响无线AP数量的因素</strong></p>
<p>在估算无线AP数量时，需要明确四个要点，分别是网络带宽、吞吐量、AP覆盖距离、接入人数，下面分别说下这些要点对无线AP数量的影响。</p>
<p>1、网络带宽</p>
<p>通常讨论链路的带宽时，是指链路上每秒所能传送的比特数，强调的是最大能达到的速率。比如百兆以太网的带宽是100Mbps，千兆以太网的带宽是1000Mbps。</p>
<p>2、吞吐量</p>
<p>吞吐量和带宽是很容易搞混的一个词。当讨论通信链路的带宽时，一般是指链路上每秒所能传送的比特数，它取决于链路时钟速率和信道编码在计算机网络中又称为线速，也就可以说百兆以太网的带宽是100Mbps。</p>
<p>带宽与吞吐量的区别：带宽强调的是最大能达到的速度，而吞吐量强调的是实际情况下的速度。由于现实受各种低效率因素的影响，通常更倾向于用"吞吐量"来表示网络的性能表现。</p>
<p>3、覆盖范围</p>
<p>在非高密区域的场景，考虑无线AP个数可以根据实际场景的面积以及推荐覆盖范围，大概确定AP的数量。一些情况下也可以算最大覆盖距离，同时可以根据功率适当的调整覆盖范围。</p>
<p>4、接入人数</p>
<p>在高密区域的场景，还需要考虑接入人数，不同型号的无线AP在不同场景下的推荐接入人数不同，可以通过并发接入用户数来确定无线AP数量(笔记本和手机稍有区别)。</p>
<p><strong>带宽计算无线AP数量</strong></p>
<p>很多朋友经常会被客户问到这样的问题：这个场景这个方案部署了这么多的AP，带宽支不支持，如果不支持的话那用户的网速是不是变成了龟速，或者是部署的AP需要多大的出口带宽等。</p>
<p>1、可以根据AP数量、最大并发用户数、每个用户分配的带宽，来得到出口带宽。WLAN容量带宽=最大并发用户数×每用户带宽。</p>
<p>2、最大并发用户数可以根据覆盖场景的人数进行50%~70%估计 。</p>
<p>3、为了让每个无线终端有足够的带宽可利用，一般建议一个无线AP接入10~15个无线(以RD-W25AP为例，根据上表中的推荐接入数量)。</p>
<p>4、AP数量=最大并发用户数÷15(单AP容纳用户数)。</p>
<p>5、每个用户分配的带宽 一般可以自行设置一个中间值，进行计算估计，如100kbps。(常见单位换算：1TB=1024GB，1G=1024MB，1MB=1024KB，1KB=1024B，1字节=8比特。)</p>
<p>举例：某高校在校用户人数为30000人，移动终端用户15000人，并发比例按50%~70%计算，每用户带宽为512Kbps，求大概WLAN容量和AP数量。</p>
<p><strong>解答：</strong></p>
<p>1、并发接入人数=15000x(50%~70%)=7500~10500人;</p>
<p>2、AP数量=最大并发用户数÷15=(7500~10500)÷15=500~700个;</p>
<p>3、WLAN容量要求=(7500~10500)×0.512=(3840~5376)M 。</p>
<p><strong>实际上网应用带宽计算</strong></p>
<br>
<p>很多人做项目演示的时候，都会直观的通过设备连接WiFi测试速度，不过通过下载的速度也可以简单的估计对应应用的理论宽带速率。</p>
<p>网速单位:</p>
<p>**bps：**位每秒，通常对于串行总线设备使用bps为单位，如串口，USB口，以太网总线等。</p>
<p>**Bps：**字节每秒，通常对于并行总线设备使用Bps为单位，如并口，IDE硬盘等。</p>
<p>换算关系：100M bps ≈ 10M Bps</p>
<p>一般来说，如果我们说网速为100兆，指的是100兆bps。如果从网上下载软件，显示的速度单位是Bps，就是每秒下载的字节数。所以一定要分清楚不同情况下网速的单位是不一样的，不能弄错，因为这两种表示方式速度相差10倍多。</p>
<p>在计算机网络、IDC机房中，其宽带速率的单位用bps(或b/s)表示；换算关系为：<strong>1Byte=8bit</strong></p>
<p><strong>1B=8b ---------- 1B/s=8b/s(或1Bps=8bps)</strong></p>
<p><strong>1KB=1024B ---------- 1KB/s=1024B/s</strong></p>
<p><strong>1MB=1024KB ---------- 1MB/s=1024KB/s</strong></p>
<br>
<p>宽带最高下载理论值:</p>
<p><strong>1.5 M =169 KB/s</strong></p>
<p><strong>3 M =338 KB/s</strong></p>
<p><strong>6 M =676 KB/s</strong></p>
<p><strong>10 M =1126 KB/s</strong></p>
<p>在实际上网应用中，下载软件时常常看到诸如下载速度显示为128KB（KB/s），103KB/s等等宽带速率大小字样，因为ISP提供的线路带宽使用的单位是比特，而一般下载软件显示的是字节（1字节＝8比特），所以要通过换算，才能得实际值。然而我们可以按照换算公式换算一下：</p>
<p><strong>128KB/s=128×8(Kb/s)=1024Kb/s=1Mb/s即：128KB/s=1Mb/s</strong></p>
<p><strong>理论上：</strong></p>
<p>2M（即2Mb/s）宽带理论速率是：256KB/s（即2048Kb/s），实际速率大约为80--200kB/s；(其原因是受用户计算机性能、网络设备质量、资源使用情况、网络高峰期、网站服务能力、线路衰耗，信号衰减等多因素的影响而造成的)。</p>
<p>4M（即4Mb/s）的宽带理论速率是：512KB/s，实际速率大约为200---440kB/s</p>
<p>上行速率是指用户电脑向网络发送信息时的数据传输速率，下行速率是指网络向用户电脑发送信息时的传输速率。比如用FTP上传文件到网上去，影响上传速度的就是"上行速率"；而从网上下载文件，影响下载速度的就是"下行速率"。当然，在实际上传下载过程中，线路、设备(含计算机及其他设备)等的质量也会对速度造成或多或少的影响。</p>
<p>造成上述数据主要是受用户计算机性能、网络设备质量、资源使用情况、网络高峰期、网站服务能力、线路衰耗，信号衰减等多因素的影响而造成的。</p>]]></description><guid isPermaLink="false">/archives/653f7f3f-e487-4626-9388-743ba027386b</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:51:41 GMT</pubDate></item><item><title><![CDATA[交换机的4种网络结构方式，看看你掌握了哪种？]]></title><link>https://wiki.dgfygk.com/archives/cb543123-b6d6-49dd-b9d5-242e8e6d083b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BA%A4%E6%8D%A2%E6%9C%BA%E7%9A%844%E7%A7%8D%E7%BD%91%E7%BB%9C%E7%BB%93%E6%9E%84%E6%96%B9%E5%BC%8F%EF%BC%8C%E7%9C%8B%E7%9C%8B%E4%BD%A0%E6%8E%8C%E6%8F%A1%E4%BA%86%E5%93%AA%E7%A7%8D%EF%BC%9F&amp;url=/archives/cb543123-b6d6-49dd-b9d5-242e8e6d083b" width="1" height="1" alt="" style="opacity:0;">
<p>交换机（Switch）意为"开关"是一种用于电（光）信号转发的网络设备。它可以为接入交换机的任意两个网络节点提供独享的电信号通路。最常见的交换机是以太网交换机。其他常见的还有电话语音交换机、光纤交换机等。</p>
<p>随着5G到来，新的物联网的到来，对交换机要求更高，同时需求量会便大。</p>
<p>交换机是使用非常广泛的网络设备，多台网络设备的局域网，交换机是必不可少的设备。</p>
<h1 id="交换机的4种网络结构方式"><strong>交换机的4种网络结构方式：</strong></h1>
<br>
<p>0 1 级联方式</p>
<h1 id="这是最常用的一种组网方式它通过交换机上的级联口uplink进行连接级联可以定义为两台或两台以上的交换机通过一定的方式相互连接根据需要-多台交换机可以以多种方式进行级联">这是最常用的一种组网方式，它通过交换机上的级联口（UpLink）进行连接。级联可以定义为两台或两台以上的交换机通过一定的方式相互连接。根据需要， 多台交换机可以以多种方式进行级联。</h1>
<br>
<p>在较大的局域网例如园区网 ( 校园网 ) 中，多台交换机按照性能和用途一般形成总线型、树型或星型的级联结构。</p>
<p>需要注意的是交换机不能无限制级联，超过一定数量的交换机进行级联，最终会引起广播风暴，导致网络性能严重下降。</p>
<p><strong>结构图：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390551849.png&amp;size=m" alt=""></p>
<p>0 2 端口聚合方式</p>
<p>端口聚合将两个设备间多条物理链路捆绑在一起组成一条逻辑链路，从而达到带宽倍增的目的(这条逻辑链路带宽相当于物理链路带宽之和)。</p>
<p>除了增加带宽外，端口聚合还可以在多条链路上均衡分配流量，起到负载分担的作用；当一条或多条链路故障时，只要还有链路正常，流量将转移到其它的链路上，整个过程在几毫秒内完成，从而起到冗余的作用，增强了网络的稳定性和安全性。</p>
<br>
<p><strong>结构图：</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390552073.png&amp;size=m" alt=""></p>
<br>
<br>
<p>0 3 堆叠方式</p>
<h1 id="堆叠是指将一台以上的交换机组合起来共同工作-以便在有限的空间内提供尽可能多的端口">堆叠是指将一台以上的交换机组合起来共同工作， 以便在有限的空间内提供尽可能多的端口。</h1>
<p>多台交换机经过堆叠形成一个堆叠单元。可堆叠的交换机性能指标中有一个 " 最大可堆叠数 " 的参数，它是指一个堆叠单元中所能堆叠的最大交换机数，代表一个堆叠单元中所能提供的最大端口密度。</p>
<p>一般来说， 不同厂家、不同型号的交换机可以互相级联，堆叠则不同 ，它必须在可堆叠的同类型交换机 ( 至少应该是同一厂家的交换机 ) 之间进行；级联仅仅是交换机之间的简单连接， 堆叠则是将整个堆叠单元作为一台交换机来使用， 这不但意味着端口密度的增加，而且意味着系统带宽的加宽。</p>
<p>堆叠可以大大提高交换机端口密度和性能。堆叠单元具有足以匹敌大型机架式交换机的端口密度和性能， 而投资却比机架式交换机便宜得多 ，实现起来也灵活得多。这就是堆叠的优势所在。</p>
<p>机架式交换机可以说是堆叠发展到更高阶段的产物。机架式交换机一般属于部门以上级别得交换机，它有多个插槽，端口密度大，支持多种网络类型，扩展性较好，处理能力强，但价格昂贵。</p>
<p><strong>结构图：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390552150.png&amp;size=m" alt=""></p>
<p>0 4 分层方式</p>
<h1 id="这种方式一般应用于比较复杂的交换机结构中按照功能可划分为接入层汇聚层核心层">这种方式一般应用于比较复杂的交换机结构中，按照功能可划分为：接入层、汇聚层、核心层。</h1>
<p>这三层网络架构采用层次化模型设计，将复杂的网络设计分成几个层次，每个层次着重于某些特定的功能，这样就能够使一个复杂的大问题变成许多简单的小问题。</p>
<p><strong>结构图：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390552290.png&amp;size=m" alt=""></p>
<br>
<p>随着局域网和城域网的发展，上述四种方式必将得到越来越广泛的应用。今天的分享就到这里，小伙伴都明白了吗？</p>]]></description><guid isPermaLink="false">/archives/cb543123-b6d6-49dd-b9d5-242e8e6d083b</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:51:02 GMT</pubDate></item><item><title><![CDATA[跟你一起学习两招：一台录像机如何添加两个不同网段的摄像头？]]></title><link>https://wiki.dgfygk.com/archives/f66cc1c6-12fb-423c-8750-edfeeeadd1ae</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%B7%9F%E4%BD%A0%E4%B8%80%E8%B5%B7%E5%AD%A6%E4%B9%A0%E4%B8%A4%E6%8B%9B%EF%BC%9A%E4%B8%80%E5%8F%B0%E5%BD%95%E5%83%8F%E6%9C%BA%E5%A6%82%E4%BD%95%E6%B7%BB%E5%8A%A0%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E7%BD%91%E6%AE%B5%E7%9A%84%E6%91%84%E5%83%8F%E5%A4%B4%EF%BC%9F&amp;url=/archives/f66cc1c6-12fb-423c-8750-edfeeeadd1ae" width="1" height="1" alt="" style="opacity:0;">
<p>录像主机怎样添加不同网段ip的摄像头？这个在实际项目有可能会遇到，也有不少的朋友在问起，之前发布的内容中我们有一个大概的介绍，本期我们来详细总结下具体如何实现录像机添加不同网段ip的摄像头？ 常用的有两种方法。
 <br>
 <strong>方法一、使用双网口录像机添加不同网段的摄像头</strong></p>
<p>我们可以使用双网口的录像主机，就能实现添加不同网段的摄像头，通常双网口录像机的有两种工作模式， 网络容错、多址设定。</p>
<br>
<p>而添加不同网段的摄像头，一般选用的就是多址设定模式，假设录像主机是192.168.1.x网段，而摄像头是192.168.0.x网段，这样添加摄像头。</p>
<br>
<p>**步骤一、**在录像机主菜单-【系统配置】-【网络配置】-【基本配置】将工作模式改成多址设定，网卡选择LAN1，设置录像主机IP为192.168.1.100，因为LAN1是连接路由器，外网访问的，因此默认选择LAN1。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390811697.png&amp;size=m" alt=""></p>
<p><strong>步骤二、</strong> 连接摄像头
 <br>
 像头的交换机接到录像主机的LAN2口，网卡选择LAN2，设置录像主机LAN2的IP地址为192.168.0.100。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390811801.png&amp;size=m" alt=""></p>
<p>录像主机有两个IP，LAN1是用来连接外网，远程访问；LAN2是用来添加同网段的摄像头，然后再通道管理-IP通道管理里面添加摄像头，这样就能实现添加不同网段的摄像头。</p>
<br>
<p><strong>方法二、修改子网掩码</strong></p>
<p>你可能会问弱电行业网，有没有更简单的方法，其实是有的，对于一般两个不同的ip地址，更简单的方法是可以通过修改子网掩码来完成的，然后直接添加，不用过多设置。</p>
<br>
<p><strong>举例：</strong></p>
<p>例如两个网段的ip地址:</p>
<p>ip地址一、192.168.1.x</p>
<p>ip地址二：192.168.0.x</p>
<p>如果不想设置，一般我们可以把子网掩码从255.255.255.0改成255.255.252.0，当然也有的朋友会直接改成255.255.0.0，不过还是建议子网掩码越精准越好。</p>
<br>
<p>原理其实就是把不同的网段，变成相同的网段，这里也说明了一点，子网掩码的作用是<strong>获取网络IP地址</strong> 和<strong>分割网络。</strong></p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/YJzWRTaNa8j3cg0Vek_HGQ">https://mp.weixin.qq.com/s/YJzWRTaNa8j3cg0Vek_HGQ</a></p>]]></description><guid isPermaLink="false">/archives/f66cc1c6-12fb-423c-8750-edfeeeadd1ae</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:50:23 GMT</pubDate></item><item><title><![CDATA[弱电工程设备、线缆、辅材等工程量计算方法]]></title><link>https://wiki.dgfygk.com/archives/e75df909-c483-4ec2-a415-7f51b3de68b8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BC%B1%E7%94%B5%E5%B7%A5%E7%A8%8B%E8%AE%BE%E5%A4%87%E3%80%81%E7%BA%BF%E7%BC%86%E3%80%81%E8%BE%85%E6%9D%90%E7%AD%89%E5%B7%A5%E7%A8%8B%E9%87%8F%E8%AE%A1%E7%AE%97%E6%96%B9%E6%B3%95&amp;url=/archives/e75df909-c483-4ec2-a415-7f51b3de68b8" width="1" height="1" alt="" style="opacity:0;">
<p><strong>一、线缆长度的估算</strong></p>
<p>线缆长度估算分为有图纸和没图纸两种情况：</p>
<p><strong>1、有图纸情况</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390910967.png&amp;size=m" alt=""></p>
<p>在图纸上找出线路走向，确定监控室位置;如果是大楼工程要确定监控室离线井距离及位置，如无法确认一定找业务了解清楚。</p>
<p>工程施工一般布线原则是就近走直线，尽量不绕弯。单支枪最远距离用线长度一般按建筑面积的长和宽再加监控室下线长度、前端预留线之和。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390911066.png&amp;size=m" alt=""></p>
<br>
<p>一条线标准计算长度为：前端枪预留1.5米，后端从天花板到地面下线长度也就是天花板到监控柜垂直距离，一般为3.5米，监控机柜预留2米(供机柜短距离移动)这样算下来一支枪预留线路长度为1.5+3.5+2=7米，再按图纸实际环境标注的长度尺寸算出用线总长，以下图为例：</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390911107.png&amp;size=m" alt=""></p>
<p>建筑长40米，宽30米，监控中心在室内中间位置，那么枪A用线长度为40米+15米(30米宽度的一半)+7米(预留线)=62米，但根据工程部实际工作经验在布线过程中还要经过线缆转角、转弯等，一般会在原长度基础上增加15%，也就是62*10%=10米，那么枪A的实际长度为62米+10米=72米。</p>
<p>枪B用线长度为10米+15米+7米=32米，再加15%，枪B用线总长度为37米，那么两支枪各准备110米75-3视频线、RVV2*0.5电源线、RVVP3×0.5监听线就足够了，管槽长度同线缆长度。</p>
<p><strong>2、无图纸情况</strong></p>
<p>在没有图纸情况下，需向业务了解到网点基本情况如楼高、面积、线井位置等基本信息。然后根据楼层高度(一般楼层高3.5米，1楼在7米高度)估算垂直长度，然后再根据机柜离线井的横向距离算出线缆大概总长度。</p>
<p><strong>3、电梯布线</strong></p>
<p>如果是大楼布线有电梯情况，线缆从哪一层进入电梯应按以下公式算出后接入：楼层÷2+1，计算出结果就是实际楼层数。如一栋16层楼为例，16÷2+1=9应该在9层位置接线进入电梯。</p>
<p><strong>二、线缆类型的选择</strong></p>
<p>视频线一般选用75-3和75-5线也有及少数电梯专用线等特种线，75-3视频传输距离一般为200米，75-5传输距离一般为400米。电源线则根据实际情况进行选型。</p>
<p>目前公司常用24V、12V变压器供电距离不能超过50米，单个变压器可供6支枪,最多不超过7支。单支的功耗一般在5-12W，选用RVV2*0.5两芯线已足用，见下表：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390911143.png&amp;size=m" alt=""></p>
<p>电源线径速查表</p>
<p>选用什么类型的线，首先要确定清楚前端器材主要集中在哪一层，如果布线距离超过50米且枪的数量较多，电源线可选择走总线的方式进行布线，把变压器统一放在前端设备箱内。</p>
<p>通过查询电源线径速查表得知3*.075线径最大电流载荷为4A，一支枪按10W功耗算，一条3*0.75线径的电源线可供枪数为88支，计算公式：4A*220V/10W=88支。反之也可根据此公式算出88支枪需要多粗的线10W*88/220V=4A，再查对照表4A，线径为3*0.75。</p>
<p><strong>三、管槽选择</strong></p>
<p>公司常用的25￠规格的PVC管可穿8条75-3的视频线，6条75-5线;混合穿线可走5条75-5视频线+1条电源线，如果是25￠铁管走线在原来基础上减一条(因铁管比PVC管壁厚要厚些)。</p>
<p>器材集中的地方可选择走主槽的方式，用多大的槽是根据主槽要放多少线来决定的。一般槽满率(槽满率是指线缆放入槽后占用槽内空间的比例)规范要求弱电的槽满率是40%,强电的槽满率是50%。</p>
<p>管槽线缆容量对照表(以五类线为例，其它型号线类似，可参考)</p>
<p>1、PVC槽(型号) 20*10 24*14 39*19 59*22 99*27 99*40100*100</p>
<p>2、五类线(根数) 2 4 9 16 32 48</p>
<p>3、PVC管(型号) ф16 ф20 ф25 ф32 ф40 ф50</p>
<p>4、五类线(根数) 2 3 6 9 15 24</p>
<p>5、度锌线槽(型号) 25*25 25*50 25*75 50*50 50*100 100*100</p>
<p>6、五类线(根数) 7 15 22 30 60 120</p>
<p>容量公式：管槽容量=INT(管槽面积*K) 对CAT5UTP来说K=0.012</p>
<p>例如：100*100的PVC槽</p>
<p>算法：100*100*0.012</p>
<p>结果：120根</p>
<p><strong>四、辅材的计算</strong></p>
<p>1、统计信息点数，包括各房间和机房，填入点位分布表中；</p>
<p>2、确定是否超长？如超长，应在何处设置子配线间，几个？如有子配线间，那么交换机的数量也相应有变化。</p>
<p>3、确定理由的走向；</p>
<p>4、确定各处桥架的型号和长度。计算方法：(长×宽)×0.4/28，结果为信息点数，常用标准桥架有：300×100，200×100，100×100，100×50，50×50，其它桥架都需要定做。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390911254.png&amp;size=m" alt=""></p>
<p>注：如果分支理由有相同的桥架型号，则分别计算其长度，最后才统计该桥架型号的总长度。</p>
<p>5、ø25和ø20管的计算(通常ø25可以布6根线，ø20可以布4根线)。计算时，以ø20为准，平均某一信息点从桥架到终端需要ø20的长度，如为A，那么就可以计算出所有信息点需要ø20的长度了，即B=A×(总点数/4)，而实际在工程中，ø20=2/3×B，ø25=1/3×B。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390911388.png&amp;size=m" alt=""></p>
<p>6、角钢(30×30)的计算。角钢的长度=30cm×(桥架的总长m/1.5m)，即每根角钢的平均长度为30cm，每隔1.5m的距离就需要一根角钢。</p>
<p>7、龙骨(75×45)的计算。龙骨的长度=70cm×(总点数/2)，即每根龙骨的长度为70cm，通常布置为双口面板。</p>
<p>8、龙骨卡子、管接、盒接、铆钉、钢锯条等辅料的计算。=总辅料价格×10%</p>
<p>9、底盒(86×86)的计算。底盒的数量=总点数/2</p>
<p><strong>五、设备材料的计算</strong></p>
<p>1、线缆的计算</p>
<p>(最远+最近)/2×点数×1.1/305</p>
<p>说明：</p>
<p>最远为从机房到信息点的最远点；</p>
<p>最近为机房内的信息点，一般为20米；</p>
<p>点数为从机房开始所覆盖的信息点，如果有子配线间，那么该点数就为从子配线间开始理由所覆盖的信息点数，1.1中的0.1为富裕量，即10%。305为每箱线的长度为305米。</p>
<p>如果有子配线间，则应该分别计算，公式是一致的。即：中心机房覆盖信息点所需的线缆数量+子配线间覆盖信息点所需的线缆数量+子配线间到中心机房级联线所需的线缆数量。</p>
<p>还有一点请注意网线的数量一般为300米左右，不到305米，如果这个工程线缆数量比较大的时候，这个也有考虑。比如穿线设备端预留的线缆长度，也要综合考虑，这个也会根据您的施工队伍的整体施工工艺来判断。</p>
<p>2、模块的计算。为信息点的数量；</p>
<p>3、双口面板的数量：总点数/2；</p>
<p>4、48口配线架的计算。总点数/48，如果有子配线间应分别计算，即各自覆盖的信息点数/48，然后相加，4U；</p>
<p>5、线管理器的计算。48口配线架不需要线管理器(自带)，主要是给交换机，如有子配线间应分别计算。1U；</p>
<p>6、机柜跳线(2m)。从配线架跳接到交换机的跳线+交换机之间的级联线。</p>
<p>7、工作站的跳线。总点数的数量；</p>
<p>8、RJ45头。(机柜跳线+工作站跳线)×2×1.1；</p>
<p>9、RJ45头护套。为RJ45头的数量；</p>
<p>10、三类大对数的数量。从弱电井通过桥架到机房的距离+富裕量(大一些，因为大对数不能连接的)；</p>
<p>11、110DW2-100FT配线架(2U)。一个为100对；</p>
<p>12、110过线槽。与110配线架的数量一致；</p>
<p>13、110背板(4U)。为：110DW2-100FT配线架数量/2；</p>
<p>14、110C4连接块(每包10个)。</p>
<p>110DW2-100FT配线架为100对，即100部电话，它由四部分组成，每一部分为25对，即由5个C4连接块和1个C5连接块组成(5×4+1×5=25)，也就是说，100对大对数需要20个C4连接块和4个C5连接块。</p>
<p>15、110C5连接块(每包10个)。同前；</p>
<p>16、电话跳线(盘/100米)。每根电话跳线需要1.5米。</p>
<p>17、RJ11头。有电话的数量决定，如200部电话(200对大对数)需要200个RJ11头(另一端直接打在RJ11配线架上)；</p>
<p>18、电话柜(200回)。根据电话点数决定；</p>
<p>19、机柜(42U，24U)。计算48口配线架、线管理器，RJ11配线架、交换机、服务器等的高度(U)；</p>
<p><strong>六、光纤的配置</strong></p>
<p>说明：二层有一中心机房，另外有一子配线间，是4和7层还各有一个子配线间，3个子配线间各放置6芯室内多模光纤。</p>
<p>模接方式：需要光纤耗材和ST多模光纤连接器；</p>
<p>熔接方式：需要单芯片多模ST尾纤，不需要光纤耗材和ST多模光纤连接器。</p>
<p><strong>七、如何更精准的计算线缆的数量</strong></p>
<p>首先计算线缆的数量应该根据施工图来计算，我们干工程按图施工，图纸核算才是整个工程预算的基本。拿到施工图纸了，一般要按照图纸对照现场的桥架走向，是不是符合图纸的设计，确定了桥架的走向，主线路基本确定。</p>
<p>关于垂直部分和水平部分的计算，尤其在是在弱电井内的线缆计算，首先要看楼层高度，再者就是弱电井设备安装位置距离桥架的距离，综合考虑，综合计算。</p>
<p>当然计算线缆的数量，一部分根据图纸，一部分根据现场，一部分根据经验，一部分根据施工队伍。</p>]]></description><guid isPermaLink="false">/archives/e75df909-c483-4ec2-a415-7f51b3de68b8</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:49:38 GMT</pubDate></item><item><title><![CDATA[监控中H264-H265技术硬盘容量计算的方法，一看便知]]></title><link>https://wiki.dgfygk.com/archives/a3a8c190-986f-4f6f-b4b2-9359e9d71241</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%9B%91%E6%8E%A7%E4%B8%ADH264-H265%E6%8A%80%E6%9C%AF%E7%A1%AC%E7%9B%98%E5%AE%B9%E9%87%8F%E8%AE%A1%E7%AE%97%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%8C%E4%B8%80%E7%9C%8B%E4%BE%BF%E7%9F%A5&amp;url=/archives/a3a8c190-986f-4f6f-b4b2-9359e9d71241" width="1" height="1" alt="" style="opacity:0;">
<p>一 <strong>、h.264技术硬盘容量的计算</strong></p>
<br>
<p>那我们先来说说H.264技术的计算!</p>
<p>说到计算，我们要拿什么来计算呢?</p>
<p>除了摄像头数目、需要录像的时间外，还有一个重要的数值：码率! 一般情况下，分辨率越大，码流呢也就越大，比如我们常用到的130W的摄像头，码流是2MB/S，也就是2048kbps。然后知道了码流、时间、通道数，我们就可以直接套用公式计算了。</p>
<p>公式如下： 码率×3600×24÷8÷1024÷1024=1D（一天）</p>
<p>码流表</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390987718.png&amp;size=m" alt=""></p>
<p>应为码流是以秒为计量的，所以我们的容量也要用秒来换算，3600呢，就是1小时，24就是一天24小时，8呢，就是字节，1024是应为码流是以MB为单位的，所以要换算G就要除以1024，换成T 还要再除1024。果就是1天。甲方需要存储多久，再乘以天数就OK了。</p>
<p>以上为H.264的计算方式，那么我们来看一下H.265技术的应用。
 <br>
 <strong>三、H.256技术</strong></p>
<br>
<p>a、什么是H.265?</p>
<p>首先，H.265不是摄像机!不是录像机！而是一种新型技术手段！H.265是一种存在于摄像机和录像机里的技术，随着视频监控的技术发展和社会需求，越来越高的像素带来的不仅仅是清晰度的提升，同时，高像素带来的同样有高硬盘成本和高带宽压力。此刻，H265来了，在保证清晰度的同时，降低了码流。差不多提升了一倍的效率，也就是说对于一般的监控系统来说，H.265可以节约近一半的存储空间，同时降低近一般的网络带宽。</p>
<p><strong>举例：</strong></p>
<p>H.264技术下，4个300W存储一个月大概是：60G*4*30=7.2T</p>
<p>H.265技术下，4个300W存储一个月大概是：30G*4*30=3.6T、</p>
<p>这还是存储的，我们再来看看带宽。以往H.264如果要远程观看1个130W像素的高清画面，就需要6M的上行带宽，而H.265下，4M网络就可以看一个300W的高清画面。2个200W画面。</p>
<p>所以说，降低的成本不只是硬盘存储的，当然还有交换机。</p>
<p>b，支持H.265的设备怎么用？</p>
<p>是不是我用老的摄像机接到H.265的录像机上，也能存储减半？不是的，对于网络摄像机和录像机来说，要双方都支持H.265技术才可以。</p>
<p>如果有一方支持，另一方不支持呢？放心，和其他技术设备一样的，向下兼容，也就是说除了存储减半不能实现，其他功能都是正常使用的。</p>
<p>c，H.265存储怎么计算？</p>
<p>普通的摄像机一天大概21G，是不是直接除以2就可以了呢？其实不然。更简洁的方法来了！</p>
<p>200W≈20G</p>
<p>300W≈30G</p>
<p>400W≈40G</p>
<p>其实H.265 就是压缩了码流。</p>]]></description><guid isPermaLink="false">/archives/a3a8c190-986f-4f6f-b4b2-9359e9d71241</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:49:16 GMT</pubDate></item><item><title><![CDATA[教你网络配线架及水晶头的制作，一看就会，还不会打配线架的弱电人来收藏了了！]]></title><link>https://wiki.dgfygk.com/archives/ff54bbce-0e82-458c-a01c-420eaa36066d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%99%E4%BD%A0%E7%BD%91%E7%BB%9C%E9%85%8D%E7%BA%BF%E6%9E%B6%E5%8F%8A%E6%B0%B4%E6%99%B6%E5%A4%B4%E7%9A%84%E5%88%B6%E4%BD%9C%EF%BC%8C%E4%B8%80%E7%9C%8B%E5%B0%B1%E4%BC%9A%EF%BC%8C%E8%BF%98%E4%B8%8D%E4%BC%9A%E6%89%93%E9%85%8D%E7%BA%BF%E6%9E%B6%E7%9A%84%E5%BC%B1%E7%94%B5%E4%BA%BA%E6%9D%A5%E6%94%B6%E8%97%8F%E4%BA%86%E4%BA%86%EF%BC%81&amp;url=/archives/ff54bbce-0e82-458c-a01c-420eaa36066d" width="1" height="1" alt="" style="opacity:0;">
<p><strong>一、<strong>RJ45与RJ11水晶头</strong></strong></p>
<p><strong>1、RJ45水晶头</strong></p>
<p>我们通常所说的网络水晶头都是指的RJ45水晶头，它是一种有8根针脚的接头，主要用在以太网中，"RJ"表示注册的插孔，"45"代表接口标准的序号。RJ45水晶头通常端接在以太网线上，用来连接各种网络设备，例如计算机、路由器、交换机等。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962108.png&amp;size=m" alt=""></p>
<p><strong>2、RJ11水晶头</strong></p>
<p>电话线用的那种四芯水晶头就是RJ11水晶头，它和RJ45水晶头很类似，但是只有4根针脚，它是由西部电子公司开发的一种接头，常用来连接电话和调制解调器。需要注意的是，RJ11通常指的是6个位置（6针）模块化的插孔或插头，但是只有4针被用到，此外，RJ11也用于4针版本的模块化接插件。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962218.png&amp;size=m" alt=""></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962234.png&amp;size=m" alt=""></p>
<br>
<p><strong>3、RJ45水晶头和RJ11水晶头的不同之处</strong></p>
<p><strong>结构不同</strong></p>
<p>RJ45水晶头是有8个凹槽和8个触点（8p8c）的接头，而RJ11水晶头通常只有6个凹槽和4个触点（6p4c），因此，RJ45水晶头的体积要比RJ11水晶头大。RJ11水晶头通常可以插入到RJ45接口中，反之则不行，但是这里强烈建议不要将RJ11水晶头用于RJ45接口。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962250.png&amp;size=m" alt=""></p>
<br>
<p><strong>线序标准不同</strong></p>
<p>由于RJ45水晶头和RJ11水晶头的构造不同，其接线时的线序标准也有所不同。RJ11水晶头一般只有6个凹槽和4个触点（6p4c），而且其线序是固定的。</p>
<br>
<p>RJ45水晶头接线时则有两种线序标准：T-568A和T-568B。通过采用不同的标准，最后制作成的网线有直通型和交叉型两种。现在所有的网线制作都采用的是568B，线序如下：</p>
<br>
<p>橙白 橙 绿白 蓝 蓝白 绿 棕白 棕</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962265.png&amp;size=m" alt=""></p>
<p><strong>二、<strong><strong>超5类水晶头与6类水晶头的区别</strong></strong></strong></p>
<p>了解超5类水晶头与6类水晶头区别，我们先来看下超5类线与6类线的区别。</p>
<p>超五类/五类芯径：0.511，截面：19.625mm2</p>
<p>六类线芯径：0.574 ，截面：30.1754mm2</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962281.png&amp;size=m" alt=""></p>
<br>
<p>六类网线的铜芯比超五类网线的铜芯要粗。因为两者的铜芯不同，导致了水晶头内部的洞口也有差异，进线孔径大小不一样，超五类1.02mm，正六类1.08mm。</p>
<br>
<p><strong>1、超5类水晶头</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962425.png&amp;size=m" alt=""></p>
<p>超5类水晶头正面，可以看出8根线芯一字排开</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962513.png&amp;size=m" alt=""></p>
<p>超5类水晶头侧面，压接触点有两个齿</p>
<p><strong>2、六类水晶头</strong></p>
<p>六类水晶头的简介六类水晶头不同于传统的超五类水晶头，它由两部分组成：内部的分线件和外壳。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962588.png&amp;size=m" alt=""></p>
<p>两件式（水晶头与分线模块)</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962644.png&amp;size=m" alt=""></p>
<p>6类水晶头正面，8根线芯交错排列</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962808.png&amp;size=m" alt=""></p>
<p>6类水晶头芯压接触点有三个齿</p>
<br>
<p><strong>3、六类水晶头接线方法</strong></p>
<p>正因为六类线水晶头的特性，所以接线方式也有区别，多个了分线模块（内塞）。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962875.png&amp;size=m" alt=""></p>
<p>6类水晶头具体接线步骤：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390962925.png&amp;size=m" alt=""></p>
<br>
<p>六类线8根线芯上下交错排布，排线孔较大，前部触点上部有三个分叉，压接进线芯后接触面积较大，常用于千兆网络，同时向下兼容百兆网络。</p>
<br>
<p><strong>三、<strong><strong><strong>网络模块如何制作？</strong></strong></strong></strong></p>
<p><strong>准备：</strong></p>
<p>网络配线架，网线，剥线刀，单对110打线刀，扎带。</p>
<br>
<p><strong>步骤：</strong></p>
<p><strong>1.将网线放入剥线刀口，预留40MM长度。</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390963015.png&amp;size=m" alt=""></p>
<br>
<p><strong>2.旋转剥线刀剪开外皮。</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390963134.png&amp;size=m" alt=""></p>
<br>
<p><strong>3.把网线按国际常规标准线序T568B卡入打线夹。</strong></p>
<br>
<p><strong>T568B线序见下图，一般在配线架上都有标注，注意和水晶头的B型打法是不一样的，不要搞混。</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390963174.png&amp;size=m" alt=""></p>
<br>
<p><strong>4.用打线刀把网线压接并剪切好。</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390963294.png&amp;size=m" alt=""></p>
<br>
<p><strong>5.完成打线如图。</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390963446.png&amp;size=m" alt=""></p>
<br>
<p><strong>6.再按如上顺序压制好另一条网线。</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390963559.png&amp;size=m" alt=""></p>
<br>
<br>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/9KQFQjtzcALJp77tl_iYQg">https://mp.weixin.qq.com/s/9KQFQjtzcALJp77tl_iYQg</a></p>]]></description><guid isPermaLink="false">/archives/ff54bbce-0e82-458c-a01c-420eaa36066d</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:48:54 GMT</pubDate></item><item><title><![CDATA[什么是端口映射？如何设置端口映射？]]></title><link>https://wiki.dgfygk.com/archives/4c2ca969-f66e-483c-8d38-4742e58c0d2b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AF%E7%AB%AF%E5%8F%A3%E6%98%A0%E5%B0%84%EF%BC%9F%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E7%AB%AF%E5%8F%A3%E6%98%A0%E5%B0%84%EF%BC%9F&amp;url=/archives/4c2ca969-f66e-483c-8d38-4742e58c0d2b" width="1" height="1" alt="" style="opacity:0;">
<p>不少朋友问到<strong>什么是端口端射？</strong> 在项目中我们经常会遇到，这个功能也是非常实用的，可以解决一些远程控制访问，那么**如何设置端口映射呢？**本期我们一起来看下。 <strong>一、什么端口映射？</strong></p>
<br>
<p><strong>端口映射：</strong> 端口映射就是将内网中的主机的一个端口映射到外网主机的一个端口，提供相应的服务。当用户访问外网IP的这个端口时，服务器自动将请求映射到对应局域网内部的机器上。</p>
<p><strong>比如：</strong></p>
<p>我们在内网中有一台Web服务器，但是外网中的用户是<strong>没有办法直接访问该服务器的</strong> 。于是我们可以在路由器上设置一个<strong>端口映射</strong>，只要外网用户访问路由器ip的80端口，那么路由器会把自动把流量转到内网Web服务器的80端口上。并且，在路由器上还存在一个Session，当内网服务器返回数据给路由器时，路由器能准确的将消息发送给外网请求用户的主机。在这过程中，路由器充当了一个反向代理的作用，他保护了内网中主机的安全。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390937126.png&amp;size=m" alt="">
 <br>
 <strong>二、如何使用路由进行设置端口映射，使外网访问本地服务器</strong></p>
<br>
<h1 id="映射实现外网访问本地服务器当你的内外的内容需要外网的用户进行访问时可以进行端口映射">映射实现外网访问本地服务器，当你的内外的内容需要外网的用户进行访问时，可以进行端口映射。</h1>
<p>端口映射又称虚拟服务器，当内网使用私有地址时：</p>
<p>（比如10.x.x.x/172.16.x.x/192.168.x.x）外部网络**无法直接访问内网中的服务器，**可通过在路由器上做端口映射，配置内网服务器的IP地址与端口，如此一来外部网络便可以访问内网服务器，从而使用内网提供的服务了。
 <br>
 <strong>一、如何通过路由器设置端口映射</strong></p>
<br>
<p>内网有100 台计算机，已经配置好一台FTP服务器，它的IP地址是 10.168.1.125，如果想让外网用户也可以访问此服务器，可以点击"<strong>添加新规则</strong>"，在出现的界面做如下图所示的配置操作：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390937198.png&amp;size=m" alt=""></p>
<p>如果你的公网ip地址是<strong>49.210.128.65</strong>，在映身好后，外网浏览器内输入公ip地址+端口，如外部主机输入<strong><a href="http://49.210.128.65:8080">http://49.210.128.65:8080</a></strong> ，即可访问到内网主机所搭建的服务应用。</p>
<p>这个无论是监控项目或办公中经常会用到，在外地访问本地的电脑与监控，非常方便。</p>
<br>
<p>当然，现在大部分路由器都会分配公网（广域网ip）的地址。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390937311.png&amp;size=m" alt=""></p>
<p><strong>当然：</strong></p>
<p>如果你的没有公网地址的话，也可以通过<strong>花生壳生成域名进行访问也行。</strong></p>
<p>下载花生壳，注册后然后会分配到一个<strong>二级域名</strong>，设置内外网端口，或内网主机。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390937451.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390937518.png&amp;size=m" alt=""></p>
<p>可以使用域名<strong>25c8898k47.zicp.vip:55694</strong> 进行访问内网服务器。</p>
<br>
<p>其中内网主机IP地址是你的<strong>内网同网段的ip地址</strong>，怎么查看自己的内网IP？</p>
<p>在win+R键搜索框中输入cmd，打开命令行输入<strong>ipconfig</strong> ，就可以查看到内网的ip段了。</p>
<br>
<p>有的路由器是直接在虚拟服务器中设置。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390937582.png&amp;size=m" alt=""></p>
<p>设置后，重启路由器，当然，如果端口被其它的占用了，可以使用其它的端口。</p>
<br>
<br>
<p><strong>三、设置了端口映射，但外网访问不了的原</strong>
 <br>
 1、首先检查您设置的端口映射<strong>是否正确映射到您内网的服务器，</strong> IP地址是否为您服务器的IP地址。</p>
<p>2、无法访问内网的服务器，请检查服务器是否开启了。您可以在局域网内使用其他的计算机访问一下您的服务器，<strong>确认服务器是否开启，并能成功访问。</strong></p>
<p>3、检查映射了服务器所需的所有端口，有可能没有完全设置访问服务器所需的端口，导致访问失败。可以通过开启"<strong>DMZ主机</strong> "来检测一下，是否端口没有映射完全。若开启DMZ主机后，能正常访问服务器，则可能是端口添加不完全。</p>
<br>
<p>4、网关上是否设置了"<strong>防火墙</strong> "，是否将服务器通信的端口过滤，导致访问失败。</p>
<p>5、**本机防火墙：**因本地网络为信任区域，防火墙并不会阻止本地网络的访问，若访问源地址为外网地址(非信任区域地址)，则可能会被防火墙拦截而无法访问。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390937656.png&amp;size=m" alt=""></p>
<p>6、服务商将相应端口屏蔽：在网关WAN口接一台电脑模拟内外网环境，若WAN口所接电脑可以正常访问内网服务器，但在Internet上无法访问，则可能是服务器将相应端口屏蔽导致虚拟服务器无法访问；</p>
<p><strong>最后补充下：</strong>
 <br>
 1、若是80端口的WEB服务器，一定要注意网关的管理端口是否更改，否则两者会冲突;</p>
<p>2、检查虚拟服务器是否映射成功一定要从<strong>外网进行访问测试</strong> ，不要从内网用WAN口IP去访问，这样在测试的时候是成功的，但实际外网并不能成功访问。</p>
<p>3、特殊的服务器，如网络摄像机及特殊的应用软件等，如果客户自己都不清楚通讯的端口，可先尝试<strong>DMZ主机</strong>以确认是否可以使用，同时建议客户咨询软件提供商。</p>]]></description><guid isPermaLink="false">/archives/4c2ca969-f66e-483c-8d38-4742e58c0d2b</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:48:25 GMT</pubDate></item><item><title><![CDATA[视频监控系统的存储空间的计算方法]]></title><link>https://wiki.dgfygk.com/archives/14a25ff4-9cad-4349-943a-ef2b8b055bea</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%A7%86%E9%A2%91%E7%9B%91%E6%8E%A7%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E7%9A%84%E8%AE%A1%E7%AE%97%E6%96%B9%E6%B3%95&amp;url=/archives/14a25ff4-9cad-4349-943a-ef2b8b055bea" width="1" height="1" alt="" style="opacity:0;">
<p>视频监控存储空间的计算方法是怎么计算的，对于一些工程师来说是经常遇到的问题，那么到底该怎么计算呢？</p>
<p>在介绍计算方法的时候，首先要先介绍下视频监控系统的一些有关概念。视频监控系统搭建的过程中，视频监控存储空间的计算是不可忽视的一个环节。存储空间的计算与前端摄像机、中端平台、视频传输带宽等关系慎密，如果存储这个环节解决不了，那整个系统的性能将大打折扣。</p>
<p>但存储空间的计算对于刚入行的新手来说还是一桩比较难以搞定的事情，我们先了解下相关的概念。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390897225.png&amp;size=m" alt=""></p>
<ol>
 <li>何为比特率？</li>
</ol>
<p>比特率就是每秒数据传送的比特数，单位是bps.比特率越高，数据量就越大，比特率就是音视频信息经过压缩编码之后，数据需要多少个比特来表示。比特率越高，比特率越高编码后的问价就越大，图像的质量就越好。比特率的bit在计算机世界里，bit表示一个位宽，bps表示每秒传输的比特位。而我们常常说的kb、M、G、T这个是按照字节来计算的，1kb就是1024个字节。1个字节=8bit。</p>
<ol start="2">
 <li>什么是码流？</li>
</ol>
<p>码流就是视频文件在单位时间传输所用的数据流量。同样，码流越大表示压越小，图像质量越高，需要的存储空间就越大。</p>
<p>3.什么是上行带宽？</p>
<p>上行带宽就是本地信息上传到网络带宽，通俗一点就是我们上传用的带宽，比如我们上传图片到空间，或者使用FTP服务等。影响上传速度的就是"上行速率</p>
<p>4.什么是下行带宽？</p>
<p>下行带宽就是本地从网络上下载信息的带宽。本地下载信息的速度就是"下行速率"</p>
<p>视频监控存储空间的计算方法：</p>
<p>1、 常见录像格式的码流大小：</p>
<p>QCIF：256Kbps CIF：512Kbps DCIF：768Kbps Half_D1：768Kbps 4CIF：1536Kbps Full_D1：2048Kbps 720P：3096Kbps1080P：10240Kbps</p>
<p>2、如何进行硬盘容量的计算</p>
<p>每小时录像文件大小计算公式：码流大小×3600÷8÷1024= MB/小时 硬盘录像机硬盘容量计算遵循以下公式： 每小时录像文件大小×每天录像时间×硬盘录像机路数×需要保存的天数 例如：8路硬盘录像机，视音频录像，采用512Kbps定码流，每天定时录像12小时，录像资料保留15天，计算公式如下： 每小时录像文件大小=512×3600÷8÷1024=225MB 硬盘录像机所需硬盘容量=225×8×12×15=324000MB≈320GB</p>
<p>3、传输带宽计算： 计算公式：网络带宽大小+比特率X 前端摄像机路数 监控点上传信息到监控中心说的是上行带宽，监控中心从监控点下载信息说的是下行带宽。如2Mbps的ADSL宽带，上行带宽是512kbps=64kb/s，下行带宽是2Mbps=256kb/s。 4、情景模拟： 监控点5个，每个监控点10个监控探头，1个远程监控中心，视频信息要求存储30天，以下是不同格式视频监控存储空间的计算方法：</p>
<p>监控点的上载带宽计算</p>
<p>监控中心的下载带宽计算：</p>
<p>存储空间计算:</p>
<p>计算公式：存储空间=比特率÷8（换算成字节数据量）x3600（每小时存储容量大小）x24（每天存储容量大小）x30（存储天数）x50（前端摄像机路数）÷0.9（磁盘格式化损失10%的空间）</p>]]></description><guid isPermaLink="false">/archives/14a25ff4-9cad-4349-943a-ef2b8b055bea</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:48:07 GMT</pubDate></item><item><title><![CDATA[光纤收发器最常见的一些故障分析和解决方法]]></title><link>https://wiki.dgfygk.com/archives/e21dbd11-cc2b-424a-af6b-f20deae8b0a9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%89%E7%BA%A4%E6%94%B6%E5%8F%91%E5%99%A8%E6%9C%80%E5%B8%B8%E8%A7%81%E7%9A%84%E4%B8%80%E4%BA%9B%E6%95%85%E9%9A%9C%E5%88%86%E6%9E%90%E5%92%8C%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95&amp;url=/archives/e21dbd11-cc2b-424a-af6b-f20deae8b0a9" width="1" height="1" alt="" style="opacity:0;">
<p>光纤收发器相关的问题我们一起来看下相关故障及解决方式。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390887586.png&amp;size=m" alt=""></p>
<p><strong>1.Power灯不亮</strong></p>
<p>电源故障，主要是光纤收发器适配器问题，坏了，或者插座故障，还有就是光纤收发器本身故障引起</p>
<br>
<p><strong>2.Link灯不亮</strong></p>
<p>故障可能有如下情况：</p>
<p>(a)检查光纤线路是否断路</p>
<p>(b)检查光纤线路是否损耗过大，超过设备接收范围</p>
<p>(c)检查光纤接口是否连接正确，本地的TX与远方的RX连接，远方的TX与本地的RX连接。(d)检查光纤连接器是否完好插入设备接口，跳线类型是否与设备接口匹配，设备类型是否与光纤匹配，设备传输长度是否与距离匹配。</p>
<br>
<p><strong>3.电路Link灯不亮</strong></p>
<p>故障可能有如下情况：</p>
<p>(a)检查网线是否断路</p>
<p>(b)检查连接类型是否匹配：网卡与路由器等设备使用交叉线，交换机，集线器等设备使用直通线。</p>
<p>(c)检查设备传输速率是否匹配</p>
<p><strong>4.网络丢包严重</strong></p>
<p>可能故障如下：</p>
<p>(1)收发器的电端口与网络设备接口，或两端设备接口的双工模式不匹配。</p>
<p>(2)双绞线与RJ-45头有问题，进行检测 。</p>
<p>(3)光纤连接问题，跳线是否对准设备接口，尾纤与跳线及耦合器类型是否匹配等。</p>
<p>(4)光纤线路损耗是否超出设备接受灵敏度。</p>
<p>(5)光纤收器的适配器太差，输出电流达不到2A，这个比较常见，可以更换质量好的适配器。</p>
<p><strong>5.光纤收发器连接后两端不能通信</strong></p>
<p>(1).光纤接反了，TX和RX所接光纤对调</p>
<p>(2).RJ45接口与外接设备连接不正确(注意直通与绞接)光纤接口(陶瓷插芯)不匹配，此故障主要体现在100M带光电互控功能的收发器上，如APC插芯的尾纤接到PC插芯的收发器上将不能正常通信，但接非光电互控收发器没有影响。</p>
<p><strong>6.时通时断现象：</strong></p>
<p>(1).可能为光路衰减太大，此时可用光功率计测量接收端的光功率，如果在接收灵敏度范围附近，1~2dB范围之内可基本判断为光路故障</p>
<p>(2).可能为与收发器连接的交换机故障，此时把交换机换成PC，即两台收发器直接与PC连接，两端对PING，如未出现时通时断现象可基本判断为交换机故障</p>
<p>(3).可能为收发器故障，此时可把收发器两端接PC(不要通过交换机)，两端对PING没问题后，从一端向另一端传送一个较大文件(100M)以上，观察它的速度，如速度很慢(200M以下的文件传送15分钟以上)，可基本判断为收发器故障。</p>
<p><strong>7.通信一段时间后死机，即不能通信，重起后恢复正常</strong></p>
<p>此现象一般由交换机引起，交换机会对所有接收到的数据进行CRC错误检测和长度校验，检查出有错误的包将丢弃，正确的包将转发出去。但这个过程中有些有错误的包在CRC错误检测和长度校验中都检测不出来，这样的包在转发过程中将不会被发送出去，也不会被丢弃，它们将会堆积在动态缓存(buffer)中，永远无法发送出去，等到buffer中堆积满了，就会造成交换机死机的现象。因为此时重起收发器或重起交换机都可以使通信恢复正常，所以用户通常都会认为是收发器的问题。</p>
<p><strong>8.收发器测试方法</strong></p>
<p>如果发现收发器连接有问题，请按以下方法进行测试，以便找出故障原因。</p>
<p>a)近端测试：两端电脑对PING，如可以PING通的话证明光纤收发器没有问题。如近端测试都不能通信则可判断为光纤收发器故障。</p>
<p>b)远端测试：两端电脑对PING，如PING不通则必须检查光路连接是否正常及光纤收发器的发射和接收功率是否在允许的范围内。如能PING通则证明光路连接正常。即可判断故障问题出在交换机上。</p>
<p>C)远端测试判断故障点：先把一端接交换机，两端对PING，如无故障则可判断为另一台交换机的故障。</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/09HapVvtfXcTDBJUHnS6Eg">https://mp.weixin.qq.com/s/09HapVvtfXcTDBJUHnS6Eg</a></p>]]></description><guid isPermaLink="false">/archives/e21dbd11-cc2b-424a-af6b-f20deae8b0a9</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:47:45 GMT</pubDate></item><item><title><![CDATA[一文看懂什么是TCP_IP协议]]></title><link>https://wiki.dgfygk.com/archives/8874f6bb-9c8a-4bda-b9f1-110847e3f4ec</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%80%E6%96%87%E7%9C%8B%E6%87%82%E4%BB%80%E4%B9%88%E6%98%AFTCP_IP%E5%8D%8F%E8%AE%AE&amp;url=/archives/8874f6bb-9c8a-4bda-b9f1-110847e3f4ec" width="1" height="1" alt="" style="opacity:0;">
<h3 id="一tcpip模型">一、TCP/IP模型</h3>
<p>TCP/IP协议模型（Transmission Control Protocol/Internet Protocol），包含了一系列构成互联网基础的网络协议，是Internet的核心协议。 基于TCP/IP的参考模型将协议分成四个层次，它们分别是链路层、网络层、传输层和应用层。下图表示TCP/IP模型与OSI模型各层的对照关系。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390873894.png&amp;size=m" alt="">
 <br>
 TCP/IP协议族按照层次由上到下，层层包装。最上面的是应用层，这里面有http，ftp 等等我们熟悉的协议。而第二层则是传输层，著名的TCP和UDP协议就在这个层次。第三层是网络层，IP协议就在这里，它负责对数据加上IP地址和其他的数据以确定传输的目标。第四层是数据链路层，这个层次为待传送的数据加入一个以太网协议头，并进行CRC编码，为最后的数据传输做准备。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390873965.png&amp;size=m" alt=""></p>
<br>
<p>上图清楚地表示了TCP/IP协议中每个层的作用，而TCP/IP协议通信的过程其实就对应着数据入栈与出栈的过程。入栈的过程，数据发送方每层不断地封装首部与尾部，添加一些传输的信息，确保能传输到目的地。出栈的过程，数据接收方每层不断地拆除首部与尾部，得到最终传输的数据。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390873978.png&amp;size=m" alt=""></p>
<p>上图以HTTP协议为例，具体说明。</p>
<h3 id="二数据链路层">二、数据链路层</h3>
<p>物理层负责0、1比特流与物理设备电压高低、光的闪灭之间的互换。数据链路层负责将0、1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的(MAC,物理地址，一个主机会有一个MAC地址)。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874008.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p>封装成帧: 把网络层数据报加头和尾，封装成帧,帧头中包括源MAC地址和目的MAC地址。</p>
 </li>
 <li>
  <p>透明传输:零比特填充、转义字符。</p>
 </li>
 <li>
  <p>可靠传输: 在出错率很低的链路上很少用，但是无线链路WLAN会保证可靠传输。</p>
 </li>
 <li>
  <p>差错检测(CRC):接收者检测错误,如果发现差错，丢弃该帧。</p>
 </li>
</ul>
<h3 id="三网络层">三、网络层</h3>
<h3 id="1ip协议">1、IP协议</h3>
<p>IP协议是TCP/IP协议的核心，所有的TCP，UDP，IMCP，IGMP的数据都以IP数据格式传输。要注意的是，IP不是可靠的协议，这是说，IP协议没有提供一种数据未传达以后的处理机制，这被认为是上层协议：TCP或UDP要做的事情。</p>
<h4 id="11-ip地址">1.1 IP地址</h4>
<p>在数据链路层中我们一般通过MAC地址来识别不同的节点，而在IP层我们也要有一个类似的地址标识，这就是IP地址。 32位IP地址分为网络位和地址位，这样做可以减少路由器中路由表记录的数目，有了网络地址，就可以限定拥有相同网络地址的终端都在同一个范围内，那么路由表只需要维护一条这个网络地址的方向，就可以找到相应的这些终端了。 A类IP地址: 0.0.0.0~127.0.0.0
 <br>
 B类IP地址:128.0.0.1~191.255.0.0
 <br>
 C类IP地址:192.168.0.0~239.255.255.0</p>
<h4 id="12-ip协议头">1.2 IP协议头</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874020.png&amp;size=m" alt="">
 <br>
 这里只介绍:八位的TTL字段。这个字段规定该数据包在穿过多少个路由之后才会被抛弃。某个IP数据包每穿过一个路由器，该数据包的TTL数值就会减少1，当该数据包的TTL成为零，它就会被自动抛弃。 这个字段的最大值也就是255，也就是说一个协议包也就在路由器里面穿行255次就会被抛弃了，根据系统的不同，这个数字也不一样，一般是32或者是64。</p>
<h3 id="2arp及rarp协议">2、ARP及RARP协议</h3>
<p>ARP 是根据IP地址获取MAC地址的一种协议。 ARP（地址解析）协议是一种解析协议，本来主机是完全不知道这个IP对应的是哪个主机的哪个接口，当主机要发送一个IP包的时候，会首先查一下自己的ARP高速缓存（就是一个IP-MAC地址对应表缓存）。 如果查询的IP－MAC值对不存在，那么主机就向网络发送一个ARP协议广播包，这个广播包里面就有待查询的IP地址，而直接收到这份广播的包的所有主机都会查询自己的IP地址，如果收到广播包的某一个主机发现自己符合条件，那么就准备好一个包含自己的MAC地址的ARP包传送给发送ARP广播的主机。 而广播主机拿到ARP包后会更新自己的ARP缓存（就是存放IP-MAC对应表的地方）。发送广播的主机就会用新的ARP缓存数据准备好数据链路层的的数据包发送工作。 RARP协议的工作与此相反，不做赘述。</p>
<h3 id="3icmp协议">3、ICMP协议</h3>
<p>IP协议并不是一个可靠的协议，它不保证数据被送达，那么，自然的，保证数据送达的工作应该由其他的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。ICMP不是高层协议，而是IP层的协议。</p>
<p>当传送IP数据包发生错误。比如主机不可达，路由不可达等等，ICMP协议将会把错误信息封包，然后传送回给主机。给主机一个处理错误的机会，这 也就是为什么说建立在IP层以上的协议是可能做到安全的原因。</p>
<h3 id="四ping">四、ping</h3>
<p>ping可以说是ICMP的最著名的应用，是TCP/IP协议的一部分。利用"ping"命令可以检查网络是否连通，可以很好地帮助我们分析和判定网络故障。</p>
<p>例如：当我们某一个网站上不去的时候。通常会ping一下这个网站。ping会回显出一些有用的信息。一般的信息如下:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874031.png&amp;size=m" alt="">
 <br>
 ping这个单词源自声纳定位，而这个程序的作用也确实如此，它利用ICMP协议包来侦测另一个主机是否可达。原理是用类型码为0的ICMP发请求，受到请求的主机则用类型码为8的ICMP回应。</p>
<h3 id="五traceroute">五、Traceroute</h3>
<p>Traceroute是用来侦测主机到目的主机之间所经路由情况的重要工具，也是最便利的工具。</p>
<p>Traceroute的原理是非常非常的有意思，它收到到目的主机的IP后，首先给目的主机发送一个TTL=1的UDP数据包，而经过的第一个路由器收到这个数据包以后，就自动把TTL减1，而TTL变为0以后，路由器就把这个包给抛弃了，并同时产生 一个主机不可达的ICMP数据报给主机。主机收到这个数据报以后再发一个TTL=2的UDP数据报给目的主机，然后刺激第二个路由器给主机发ICMP数据 报。如此往复直到到达目的主机。这样，traceroute就拿到了所有的路由器IP。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874042.png&amp;size=m" alt=""></p>
<h3 id="六tcpudp">六、TCP/UDP</h3>
<p>TCP/UDP都是是传输层协议，但是两者具有不同的特性，同时也具有不同的应用场景，下面以图表的形式对比分析。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874100.png&amp;size=m" alt=""></p>
<p><strong>面向报文</strong></p>
<p>面向报文的传输方式是应用层交给UDP多长的报文，UDP就照样发送，即一次发送一个报文。因此，应用程序必须选择合适大小的报文。若报文太长，则IP层需要分片，降低效率。若太短，会是IP太小。</p>
<p><strong>面向字节流</strong>
 <br>
 面向字节流的话，虽然应用程序和TCP的交互是一次一个数据块（大小不等），但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲，当应用程序传送的数据块太长，TCP就可以把它划分短一些再传送。 关于拥塞控制，流量控制，是TCP的重点，后面讲解。 TCP和UDP协议的一些应用</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874112.png&amp;size=m" alt=""></p>
<h5 id="什么时候应该使用tcp">什么时候应该使用TCP？</h5>
<p>当对网络通讯质量有要求的时候，比如：整个数据要准确无误的传递给对方，这往往用于一些要求可靠的应用，比如HTTP、HTTPS、FTP等传输文件的协议，POP、SMTP等邮件传输的协议。</p>
<h5 id="什么时候应该使用udp">什么时候应该使用UDP？</h5>
<p>当对网络通讯质量要求不高的时候，要求网络通讯速度能尽量的快，这时就可以使用UDP。</p>
<h3 id="七dns">七、DNS</h3>
<p>DNS（Domain Name System，域名系统），因特网上作为域名和IP地址相互映射的一个分布式数据库，能够使用户更方便的访问互联网，而不用去记住能够被机器直接读取的IP数串。通过主机名，最终得到该主机名对应的IP地址的过程叫做域名解析（或主机名解析）。DNS协议运行在UDP协议之上，使用端口号53。</p>
<h3 id="八tcp连接的建立与终止">八、TCP连接的建立与终止</h3>
<h3 id="1三次握手">1、三次握手</h3>
<p>TCP是面向连接的，无论哪一方向另一方发送数据之前，都必须先在双方之间建立一条连接。在TCP/IP协议中，TCP协议提供可靠的连接服务，连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP窗口大小信息。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874123.png&amp;size=m" alt="">
 <br>
 <strong>第一次握手</strong>：建立连接。客户端发送连接请求报文段，将SYN位置为1，Sequence Number为x；然后，客户端进入SYN_SEND状态，等待服务器的确认； <strong>第二次握手</strong>：服务器收到SYN报文段。服务器收到客户端的SYN报文段，需要对这个SYN报文段进行确认，设置Acknowledgment Number为x+1(Sequence Number+1)；同时，自己自己还要发送SYN请求信息，将SYN位置为1，Sequence Number为y；服务器端将上述所有信息放到一个报文段（即SYN+ACK报文段）中，一并发送给客户端，此时服务器进入SYN_RECV状态； <strong>第三次握手</strong>：客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1，向服务器发送ACK报文段，这个报文段发送完毕以后，客户端和服务器端都进入ESTABLISHED状态，完成TCP三次握手。 为什么要三次握手？
 <br>
 为了防止已失效的连接请求报文段突然又传送到了服务端，因而产生错误。 具体例子："已失效的连接请求报文段"的产生在这样一种情况下：client发出的第一个连接请求报文段并没有丢失，而是在某个网络结点长时间的滞留了，以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后，就误认为是client再次发出的一个新的连接请求。 于是就向client发出确认报文段，同意建立连接。假设不采用"三次握手"，那么只要server发出确认，新的连接就建立了。由于现在client并没有发出建立连接的请求，因此不会理睬server的确认，也不会向server发送数据。但server却以为新的运输连接已经建立，并一直等待client发来数据。这样，server的很多资源就白白浪费掉了。采用"三次握手"的办法可以防止上述现象发生。例如刚才那种情况，client不会向server的确认发出确认。server由于收不到确认，就知道client并没有要求建立连接。"</p>
<h5 id="2四次挥手">2、四次挥手</h5>
<p>当客户端和服务器通过三次握手建立了TCP连接以后，当数据传送完毕，肯定是要断开TCP连接的啊。那对于TCP的断开连接，这里就有了神秘的"四次分手"。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874247.png&amp;size=m" alt=""></p>
<p><strong>第一次分手</strong>：主机1（可以使客户端，也可以是服务器端），设置Sequence Number，向主机2发送一个FIN报文段；此时，主机1进入FIN_WAIT_1状态；这表示主机1没有数据要发送给主机2了；</p>
<p><strong>第二次分手</strong> ：主机2收到了主机1发送的FIN报文段，向主机1回一个ACK报文段，Acknowledgment Number为Sequence Number加1；主机1进入FIN_WAIT_2状态；主机2告诉主机1，我"同意"你的关闭请求；
 <br>
 <strong>第三次分手</strong>：主机2向主机1发送FIN报文段，请求关闭连接，同时主机2进入LAST_ACK状态；</p>
<p><strong>第四次分手</strong>：主机1收到主机2发送的FIN报文段，向主机2发送ACK报文段，然后主机1进入TIME_WAIT状态；主机2收到主机1的ACK报文段以后，就关闭连接；此时，主机1等待2MSL后依然没有收到回复，则证明Server端已正常关闭，那好，主机1也可以关闭连接了。</p>
<h4 id="为什么要四次分手">为什么要四次分手？</h4>
<p>TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式，这就意味着，当主机1发出FIN报文段时，只是表示主机1已经没有数据要发送了，主机1告诉主机2，它的数据已经全部发送完毕了；但是，这个时候主机1还是可以接受来自主机2的数据；当主机2返回ACK报文段时，表示它已经知道主机1没有数据发送了，但是主机2还是可以发送数据到主机1的；当主机2也发送了FIN报文段时，这个时候就表示主机2也没有数据要发送了，就会告诉主机1，我也没有数据要发送了，之后彼此就会愉快的中断这次TCP连接。</p>
<h5 id="为什么要等待2msl">为什么要等待2MSL？</h5>
<p>MSL：报文段最大生存时间，它是任何报文段被丢弃前在网络内的最长时间。原因有二：</p>
<ul>
 <li>
  <p>保证TCP协议的全双工连接能够可靠关闭</p>
 </li>
 <li>
  <p>保证这次连接的重复数据段从网络中消失</p>
 </li>
</ul>
<p>第一点：如果主机1直接CLOSED了，那么由于IP协议的不可靠性或者是其它网络原因，导致主机2没有收到主机1最后回复的ACK。那么主机2就会在超时之后继续发送FIN，此时由于主机1已经CLOSED了，就找不到与重发的FIN对应的连接。所以，主机1不是直接进入CLOSED，而是要保持TIME_WAIT，当再次收到FIN的时候，能够保证对方收到ACK，最后正确的关闭连接。</p>
<p>第二点：如果主机1直接CLOSED，然后又再向主机2发起一个新连接，我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题，但是还是有特殊情况出现：假设新连接和已经关闭的老连接端口号是一样的，如果前一次连接的某些数据仍然滞留在网络中，这些延迟数据在建立新连接之后才到达主机2，由于新连接和老连接的端口号是一样的，TCP协议就认为那个延迟的数据是属于新连接的，这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL，这样可以保证本次连接的所有数据都从网络中消失。</p>
<h3 id="九tcp流量控制">九、TCP流量控制</h3>
<p>如果发送方把数据发送得过快，接收方可能会来不及接收，这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快，要让接收方来得及接收。</p>
<p>利用<strong>滑动窗口机制</strong>可以很方便地在TCP连接上实现对发送方的流量控制。</p>
<p>设A向B发送数据。在连接建立时，B告诉了A："我的接收窗口是 rwnd = 400 "(这里的 rwnd 表示 receiver window) 。因此，发送方的发送窗口不能超过接收方给出的接收窗口的数值。请注意，TCP的窗口单位是字节，不是报文段。假设每一个报文段为100字节长，而数据报文段序号的初始值设为1。大写ACK表示首部中的确认位ACK，小写ack表示确认字段的值ack。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874359.png&amp;size=m" alt=""></p>
<p>从图中可以看出，B进行了三次流量控制。第一次把窗口减少到 rwnd = 300 ，第二次又减到了 rwnd = 100 ，最后减到 rwnd = 0 ，即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。B向A发送的三个报文段都设置了 ACK = 1 ，只有在ACK=1时确认号字段才有意义。</p>
<p>TCP为每一个连接设有一个持续计时器(persistence timer)。只要TCP连接的一方收到对方的零窗口通知，就启动持续计时器。若持续计时器设置的时间到期，就发送一个零窗口控测报文段（携1字节的数据），那么收到这个报文段的一方就重新设置持续计时器。</p>
<h3 id="十tcp拥塞控制">十、TCP拥塞控制</h3>
<p>发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度，并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。</p>
<p>发送方控制拥塞窗口的原则是：只要网络没有出现拥塞，拥塞窗口就再增大一些，以便把更多的分组发送出去。但只要网络出现拥塞，拥塞窗口就减小一些，以减少注入到网络中的分组数。</p>
<p>慢开始算法：</p>
<p>当主机开始发送数据时，如果立即所大量数据字节注入到网络，那么就有可能引起网络拥塞，因为现在并不清楚网络的负荷情况。因此，较好的方法是 先探测一下，即由小到大逐渐增大发送窗口，也就是说，由小到大逐渐增大拥塞窗口数值。</p>
<p>通常在刚刚开始发送报文段时，先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后，把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ，可以使分组注入到网络的速率更加合理。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874372.png&amp;size=m" alt=""></p>
<p>每经过一个传输轮次，拥塞窗口 cwnd 就加倍。一个传输轮次所经历的时间其实就是往返时间<strong>RTT</strong>。不过"传输轮次"更加强调：把拥塞窗口cwnd所允许发送的报文段都连续发送出去，并收到了对已发送的最后一个字节的确认。</p>
<p>另，慢开始的"慢"并不是指cwnd的增长速率慢，而是指在TCP开始发送报文段时先设置cwnd=1，使得发送方在开始时只发送一个报文段（目的是试探一下网络的拥塞情况），然后再逐渐增大cwnd。</p>
<p>为了防止拥塞窗口cwnd增长过大引起网络拥塞，还需要设置一个慢开始门限ssthresh状态变量。慢开始门限ssthresh的用法如下：</p>
<ul>
 <li>
  <p>当 cwnd &lt; ssthresh 时，使用上述的慢开始算法。</p>
 </li>
 <li>
  <p>当 cwnd &gt; ssthresh 时，停止使用慢开始算法而改用拥塞避免算法。</p>
 </li>
 <li>
  <p>当 cwnd = ssthresh 时，既可使用慢开始算法，也可使用拥塞控制避免算法。拥塞避免</p>
 </li>
</ul>
<p><strong>拥塞避免</strong></p>
<p>让拥塞窗口cwnd缓慢地增大，即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1，而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长，比慢开始算法的拥塞窗口增长速率缓慢得多。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874385.png&amp;size=m" alt=""></p>
<p>无论在慢开始阶段还是在拥塞避免阶段，只要发送方判断网络出现拥塞（其根据就是没有收到确认），就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半（但不能小于2）。然后把拥塞窗口cwnd重新设置为1，执行慢开始算法。</p>
<p>这样做的目的就是要迅速减少主机发送到网络中的分组数，使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。</p>
<p>如下图，用具体数值说明了上述拥塞控制的过程。现在发送窗口的大小和拥塞窗口一样大。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874397.png&amp;size=m" alt=""></p>
<h3 id="2快重传和快恢复">2、快重传和快恢复</h3>
<h4 id="快重传">快重传</h4>
<p>快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认（为的是使发送方及早知道有报文段没有到达对方）而不要等到自己发送数据时才进行捎带确认。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731390874517.png&amp;size=m" alt=""></p>
<p>接收方收到了M1和M2后都分别发出了确认。现在假定接收方没有收到M3但接着收到了M4。</p>
<p>显然，接收方不能确认M4，因为M4是收到的失序报文段。根据 可靠传输原理，接收方可以什么都不做，也可以在适当时机发送一次对M2的确认。</p>
<p>但按照快重传算法的规定，接收方应及时发送对M2的重复确认，这样做可以让 发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后，也还要再次发出对M2的重复确认。这样，发送方共收到了 接收方的四个对M2的确认，其中后三个都是重复确认。</p>
<p>快重传算法还规定，发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段M3，而不必 继续等待M3设置的重传计时器到期。</p>
<p>由于发送方尽早重传未被确认的报文段，因此采用快重传后可以使整个网络吞吐量提高约20%。</p>
<h4 id="快恢复">快恢复</h4>
<p>与快重传配合使用的还有快恢复算法，其过程有以下两个要点：</p>
<ul>
 <li>当发送方连续收到三个重复确认，就执行"乘法减小"算法，把慢开始门限ssthresh减半。</li>
 <li>与慢开始不同之处是现在不执行慢开始算法（即拥塞窗口cwnd现在不设置为1），而是把cwnd值设置为 慢开始门限ssthresh减半后的数值，然后开始执行拥塞避免算法（"加法增大"），使拥塞窗口缓慢地线性增大。</li>
 <li></li>
</ul>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/vn0gVJ5c0KUcihyUIf2ToA">https://mp.weixin.qq.com/s/vn0gVJ5c0KUcihyUIf2ToA</a></p>]]></description><guid isPermaLink="false">/archives/8874f6bb-9c8a-4bda-b9f1-110847e3f4ec</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:47:22 GMT</pubDate></item><item><title><![CDATA[关于ip地址的最详细明了的知识点，看完这篇就够了]]></title><link>https://wiki.dgfygk.com/archives/2d8512e5-a811-4631-bf99-dcb352be6557</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%B3%E4%BA%8Eip%E5%9C%B0%E5%9D%80%E7%9A%84%E6%9C%80%E8%AF%A6%E7%BB%86%E6%98%8E%E4%BA%86%E7%9A%84%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%8C%E7%9C%8B%E5%AE%8C%E8%BF%99%E7%AF%87%E5%B0%B1%E5%A4%9F%E4%BA%86&amp;url=/archives/2d8512e5-a811-4631-bf99-dcb352be6557" width="1" height="1" alt="" style="opacity:0;">
<p>我们发布过如何判断两个IP地址是否在同一个网段？什么是子网掩码？，有朋友反映有没有更基础的，那我们就从ip地址换算开始说起。</p>
<br>
<p><strong>一、特殊的IP地址</strong></p>
<p>1、0.0.0.0</p>
<p>严格说来，这个奇葩的地址0.0.0.0已经不是一个真正意义上的IP地址了。它表示的是这样一个集合：也就是说；所有不清楚的主机和目的网络。这里的"不清楚"是指在本机的路由表里没有特定条目指明如何到达。对本机来说，它就是一个"收容所"，所有不认识的"三无"人员，一律送进去。如果你在网络设置中设置了缺省网关，那么计算机系统会自动产生一个目的地址为0.0.0.0的缺省路由。</p>
<p>2、255.255.255.255 限制广播地址。</p>
<p>对本机来说，这个地址指本网段内(同一广播域)的所有主机。</p>
<p>然而它的意思很明确，使用人类语言来说意思就是"这里的所有计算机都注意了"这个地址不能被路由器所转发。</p>
<br>
<p>3、127.0.0.1 本机地址</p>
<p>主要用于测试。用汉语表示，就是"我自己"。在Windows系统中，这个地址有一个别名"Localhost"。寻址这样一个地址，是不能把它发到网络接口的。除非出错，否则在传输介质上永远不应该出现目的地址为"127.0.0.1"的数据包。</p>
<br>
<p>4、224.0.0.1 组播地址</p>
<p>注意它和广播的区别。从224.0.0.0到239.255.255.255都是这样的地址。224.0.0.1特指所有主机，224.0.0.2特指所有路由器。这样的地址多用于一些特定的程序以及多媒体程序。如果你的主机开启了IRDP(Internet路由发现协议），使用组播功能功能，那么你的主机路由表中应该有这样一条路由。</p>
<br>
<p>5、169.254.x.x</p>
<p>如果你的主机使用了DHCP功能自动获得一个IP地址，那么当你的DHCP服务器发生故障，或响应时间太长而超出了一个系统规定的时间，计算机操作系统会为你分配这样一个地址。如果你发现你的主机IP地址是一个诸如此类的地址，很不幸的是，现在你的网络不能正常运行了。</p>
<br>
<p>6、10.x.x.x；172.16.0.0---172.31.255.254；192.168.x.x；私有地址</p>
<p>这些地址被大量用于企业内部网络中。一些宽带路由器，也往往使用192.168.1.1作为缺省地址。私有网络由于不与外部互连，因而可能使用随意的IP地址。保留这样的地址供其使用是为了避免以后接入公网时引起地址混乱。使用私有地址的私有网络在接入Internet时，要使用地址翻译(NAT)，将私有地址翻译成公用合法地址。在Internet上，这类地址是不能出现的。对一台网络上的主机来说，它可以正常接收的合法目的网络地址有三种：本机的IP地址、广播地址以及组播地址。</p>
<br>
<p><strong>二、IP地址的分类</strong></p>
<p><strong>1、A类地址</strong></p>
<p><strong>A类IP地址：</strong> 由1个字节的网络地址和3个字节的主机地址，网络地址的最高位必须是"0"。</p>
<p>如：0XXXXXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX（X代表0或1）</p>
<p><strong>A类IP地址范围：</strong> 1.0.0.1---126.255.255.254</p>
<p><strong>A类IP地址中的私有地址和保留地址：</strong></p>
<p>① 10.X.X.X是私有地址（所谓的私有地址就是在互联网上不使用，而被用在局域网络中的地址）。</p>
<p>范围（10.0.0.1---10.255.255.254）</p>
<p>② 127.X.X.X是保留地址，用做循环测试用的。</p>
<br>
<br>
<h4 id="2b类地址"><strong>2、B类地址</strong></h4>
<p>**B类IP地址：**由2个字节的网络地址和2个字节的主机地址，网络地址的最高位必须是"10"。</p>
<p>如：10XXXXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX（X代表0或1）</p>
<br>
<p><strong>B类IP地址范围：</strong> 128.0.0.1---191.255.255.254。</p>
<br>
<p><strong>B类IP地址的私有地址和保留地址：</strong></p>
<p>① 172.16.0.0---172.31.255.254是私有地址</p>
<p>② 169.254.X.X是保留地址。如果你的IP地址是自动获取IP地址，</p>
<p>而你在网络上又没有找到可用的DHCP服务器。就会得到其中一个IP。</p>
<p>191.255.255.255是广播地址，不能分配。</p>
<br>
<h4 id="3c类地址"><strong>3、C类地址</strong></h4>
<p>**C类IP地址：**由3个字节的网络地址和1个字节的主机地址，网络地址的最高位必须是"110"。</p>
<p>如：110XXXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX（X代表0或1）</p>
<br>
<p><strong>类IP地址范围：</strong> 192.0.0.1---223.255.255.254。</p>
<br>
<p><strong>C类地址中的私有地址：</strong></p>
<p>192.168.X.X是私有地址。（192.168.0.1---192.168.255.255)</p>
<br>
<br>
<h4 id="4d类地址"><strong>4、D类地址</strong></h4>
<p><strong>D类地址：</strong> 不分网络地址和主机地址，它的第1个字节的前四位固定为1110。</p>
<p>如：1110XXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX（X代表0或1）</p>
<br>
<p><strong>D类地址范围：</strong> 224.0.0.1---239.255.255.254</p>
<br>
<h4 id="5e类地址"><strong>5、E类地址</strong></h4>
<p><strong>E类地址：</strong> 不分网络地址和主机地址，它的第1个字节的前四位固定为 1111。</p>
<p>如：1111XXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX（X代表0或1）</p>
<br>
<p><strong>E类地址范围：</strong> 240.0.0.1---255.255.255.254</p>
<br>
<p><strong>三、ip地址换算</strong></p>
<p>很多朋友可能在算ip地址的时候，对二进制转换成十进制，或十进制转换成二制比较不理解，弱电君就以自己理解的方式来换算进制之间的转换。</p>
<br>
<p><strong>例如：</strong></p>
<p>我们的现有ip地址为：<strong>192.10.1.1</strong> (ip地址是十进制)</p>
<p>我们现在把它转化为二进制：</p>
<br>
<p>1、先列出2进制每一位数的权值（即2的位数-1次方）</p>
<p>因为IP地址每组数最大是255，列到第8位。</p>
<br>
<p>2、把10进制数参照权值进行分解</p>
<p>如：192=128+64,那么对应的第8和第7位为1，其它位为0，转换成二进制：11000000</p>
<br>
<p>再如：10=8+2，那么对应的就是第4位与2位为1，其它位数为0。</p>
<br>
<p>转换示例如图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391026953.png&amp;size=m" alt=""></p>
<br>
<p><strong>结果就是：</strong></p>
<p>192=11000000</p>
<p>9元，就有一份弱电技术资料（300G），限免，前100名免费入弱电交流群!](<a href="http://mp.weixin.qq.com/s?__biz=MzU4Nzc2NDQ3OQ==&amp;mid=2247488427&amp;idx=1&amp;sn=a5525419b4d5ddc33a512500ff48e8f6&amp;chksm=fde6543bca91dd2dda4a070fffcd028e3230ad311eb40e383ba0bd953134d8e3027390491e32&amp;scene=21#wechat_redirect">http://mp.weixin.qq.com/s?__biz=MzU4Nzc2NDQ3OQ==&amp;mid=2247488427&amp;idx=1&amp;sn=a5525419b4d5ddc33a512500ff48e8f6&amp;chksm=fde6543bca91dd2dda4a070fffcd028e3230ad311eb40e383ba0bd953134d8e3027390491e32&amp;scene=21#wechat_redirect</a>)**</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/ZmlYktLN7WK7zBO3KENyUg">https://mp.weixin.qq.com/s/ZmlYktLN7WK7zBO3KENyUg</a></p>]]></description><guid isPermaLink="false">/archives/2d8512e5-a811-4631-bf99-dcb352be6557</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:46:59 GMT</pubDate></item><item><title><![CDATA[核心交换机参数概念详解，搞不清的弱电人进来看看！]]></title><link>https://wiki.dgfygk.com/archives/d36f264d-8f37-4b5e-81ba-db245756d469</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%A0%B8%E5%BF%83%E4%BA%A4%E6%8D%A2%E6%9C%BA%E5%8F%82%E6%95%B0%E6%A6%82%E5%BF%B5%E8%AF%A6%E8%A7%A3%EF%BC%8C%E6%90%9E%E4%B8%8D%E6%B8%85%E7%9A%84%E5%BC%B1%E7%94%B5%E4%BA%BA%E8%BF%9B%E6%9D%A5%E7%9C%8B%E7%9C%8B%EF%BC%81&amp;url=/archives/d36f264d-8f37-4b5e-81ba-db245756d469" width="1" height="1" alt="" style="opacity:0;">
<p><strong>核心交换机参数概念详解</strong></p>
<p><strong>一、背板带宽</strong></p>
<p>背板带宽也称交换容量，是交换机接口处理器或接口卡和数据总线间所能吞吐的最大数据量，就像是立交桥所拥有的车道的总和。由于所有端口间的通信都需要通过背板完成，所以背板所能提供的带宽，就成为端口间并发通信时的瓶颈。</p>
<p>带宽越大，提供给各端口的可用带宽越大，数据交换速度越大；带宽越小，给各端口提供的可用带宽越小，数据 交换速度也就越慢。也就是说，背板带宽决定着交换机的数据处理能力，背板带宽越高，所能处理数据的能力就越强。若欲实现网络的全双工无阻塞传输，必须满足最小背板带宽的要求。</p>
<p>计算公式如下</p>
<p>背板带宽=端口数量×端口速率×2</p>
<p>提示：对于三层交换机而言，只有转发速率和背板带宽都达到最低要求，才是合格的交换机，二者缺一不可。</p>
<p>例如，</p>
<p>如何一款交换机有24个端口，</p>
<p>背板带宽=24*1000*2/1000=48Gbps。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391037564.png&amp;size=m" alt=""></p>
<br>
<p><strong>二、二层三层的包转发率</strong></p>
<p>网络中的数据是由一个个数据包组成，对每个数据包的处理要消耗资源。转发速率（也称吞吐量）是指在不丢包的情况下，单位时间内通过的数据包数量。吞吐量就像是立交桥的车流量，是三层交换机最重要的一个参数，标志着交换机的具体性能。如果吞吐量太小，就会成为网络瓶颈，给整个网络的传输效率带来负面影响。交换机应当能够实现线速交换，即交换速率达到传输线上的数据传输速度，从而最大限度地消除交换瓶颈。对于三层核心交换机而言，若欲实现网络的无阻塞传输，这个速率能≤标称二层包转发速率和速率能≤标称三层包转发速率，那么交换机在做第二层和第三层交换的时候可以做到线速。</p>
<p>那么公式如下</p>
<p>吞吐量（Mpps）=万兆位端口数量×14.88 Mpps+千兆位端口数量×1.488 Mpps+百兆位端口数量×0.1488 Mpps。</p>
<p>算出的吞吐如果小于你交换机的吞吐量的话，那就可以做到线速。</p>
<p>这里面万兆位端口与百兆端口如果有就算上去，没有就可以不用算。</p>
<p>例如，对于一台拥有24个千兆位端口的交换机而言，其满配置吞吐量应达到24×1.488 Mpps=35.71 Mpps，才能够确保在所有端口均线速工作时，实现无阻塞的包交换。同样，如果一台交换机最多能够提供176个千兆位端口，那么其吞吐量至少应当为 261.8 Mpps（176×1.488 Mpps=261.8 Mpps），才是真正的无阻塞结构设计。</p>
<p>那么，1.488Mpps是怎么得到的呢?</p>
<p>包转发线速的衡量标准是以单位时间内发送64byte的数据包(最小包)的个数作为计算基准的。对于千兆以太网来说，计算方法如下：1，000，000，000bps/8bit/(64+8+12)byte=1,488,095pps 说明：当以太网帧为64byte时，需考虑 8byte的帧头和12byte的帧间隙的固定开销。故一个线速的千兆以太网端口在转发64byte包时的包转 发率为1.488Mpps。快速以太网的统速端口包转发率正好为千兆以太网的十分之一，为148.8kpps。</p>
<p>*对于万兆以太网，一个线速端口的包转发率为14.88Mpps。</p>
<p>*对于千兆以太网，一个线速端口的包转发率为1.488Mpps。</p>
<p>*对于快速以太网，一个线速端口的包转发率为0.1488Mpps。</p>
<p>这个数据我们能用就行。</p>
<p>所以说，如果能满足上面三个条件（背板带宽、包转发率）那么我们就说这款核心交换机真正做到了线性无阻塞。</p>
<p>一般是两者都满足的交换机才是合格的交换机。</p>
<p>背板相对大，吞吐量相对小的交换机，除了保留了升级扩展的能力外就是软件效率/专用芯片电路设计有问题;背板相对小。吞吐量相对大的交换机，整体性能比较高。不过背板带宽是可以相信厂家的宣传的，可吞吐量是无法相信厂家的宣传的，因为后者是个设计值，测试很困难的并且意义不是很大。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391037740.png&amp;size=m" alt=""></p>
<p><strong>三、可扩展性</strong></p>
<p>可扩展性应当包括两个方面：</p>
<p>1、插槽数量：插槽用于安装各种功能模块和接口模块。由于 每个接口模块所提供的端口数量是一定的，因此插槽数量也就从根本上决定着交换机所能容纳的端口数量。另外，所有功能模块(如超级引擎模块、IP语音模块、 扩展服务模块、网络监控模块、安全服务模块等)都需要占用一个插槽，因此插槽数量也就从根本上决定着交换机的可扩展性。</p>
<p>2、模块类型：毫无疑问，支持的模块类型(如LAN接口模块、WAN接口模块、ATM接口模块、 扩展功能模块等)越多，交换机的可扩展性越强。仅以局域网接口模块为例，就应当包括RJ-45模块、GBIC模块、SFP模块、10Gbps模块等，以适 应大中型网络中复杂环境和网络应用的需求。</p>
<p><strong>四、四层交换</strong></p>
<p>第四层交换用于实现对网络服务的快速访问。在四层交换中，决定传输的依据不仅仅是MAC地址(第二层网桥)或源/目标地址(第三层路由)，而且包括 TCP /UDP(第四层)应用端口号，被设计用于高速Intranet应用。四层交换除了负载均衡功能外，还支持基于应用类型和用户ID的传输流控制功能。此 外，四层交换机直接安放在服务器前端，它了解应用会话内容和用户权限，因而使它成为防止非授权访问服务器的理想平</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391037794.png&amp;size=m" alt=""></p>
<p><strong>五、模块冗余</strong></p>
<p>冗余能力是网络安全运行的保证。任何厂商都不能保证其产品在运行的过程中不发生故障。而故障发生时能否迅速切换就取决于设备的冗余能力。对于核心交换机而 言，重要部件都应当拥有冗余能力，比如管理模块冗余、电源冗余等，这样才可以在最大程度上保证网络稳定运行。</p>
<p><strong>六、路由冗余</strong></p>
<p>利用HSRP、VRRP协议保证核心设备的负荷分担和热备份，在核心交换机和双汇聚交换机中的某台交换机出现故障时，三层路由设备和虚拟网关能够快速切换，实现双线路的冗余备份，保证整网稳定性。</p>]]></description><guid isPermaLink="false">/archives/d36f264d-8f37-4b5e-81ba-db245756d469</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:45:11 GMT</pubDate></item><item><title><![CDATA[弱电工程公共广播系统有噪音、啸叫现象如何解决？]]></title><link>https://wiki.dgfygk.com/archives/a130a110-8b8a-47b1-b557-e05795f1d129</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BC%B1%E7%94%B5%E5%B7%A5%E7%A8%8B%E5%85%AC%E5%85%B1%E5%B9%BF%E6%92%AD%E7%B3%BB%E7%BB%9F%E6%9C%89%E5%99%AA%E9%9F%B3%E3%80%81%E5%95%B8%E5%8F%AB%E7%8E%B0%E8%B1%A1%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%EF%BC%9F&amp;url=/archives/a130a110-8b8a-47b1-b557-e05795f1d129" width="1" height="1" alt="" style="opacity:0;">
<p>公共广播系统目前是一个各行各业都会有需求的系统，酒店、商场、公园、景点等场所都会用到。现实使用中，我们会发现有的广播系统是电声悦耳、自然流畅，但是有一些则带有些噪音、啸叫。面对这种情况，我们该如何处理呢</p>
<p>1、选购质量优的广播设备</p>
<p>系统广播设备选用性能指标好、抗干扰能力强的产品。广播设备的抗扰能力决定于噪声的抑制能力，受广播设备的电路、制造工艺及所用材料的制约。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391060384.png&amp;size=m" alt=""></p>
<p>因此如果预算允许、又想要更好的效果，建议选购更高档次的广播设备，一分钱一分货，万古不变的道理。</p>
<p>2、正确连接设备间的信号线</p>
<p>设备质量过关，还要注意设备信号线之间的连接。如果连接不正确或不恰当，即使是性能良好的设备也难以发挥其优秀的品质。</p>
<p>接地是抑制噪声和防止干扰、提高可靠性的重要技术措施。正确的接地能抑制干扰对系统的影响，又能抑制广播设备向外辐射干扰；反之，错误的接地会引入严重的干扰，甚至使广播设备无法正常工作。</p>
<p>所有的电力电缆及信号电缆均穿金属管保护，这样能起到一定的屏蔽作用。</p>
<p>3、不要随意焊接屏蔽线</p>
<p>在机房设备安装过程中，不要图方便而随意焊接屏蔽线，否则易形成闭环回路，引起系统噪声问题。防止闭环回路形成的有效方法：(1)屏蔽层一端接地(一般在信号输入端)，另一端开路；(2)分清机壳接地点、信号接地点几种接地点。</p>
<p>4、净化电源</p>
<p>设计供电电源时，应设计单独电源变压器；无条件时可独立一组电源供电，与空调、照明灯光等设备分开，或采用隔离电源变压器，亦或在广播设备的电源输入端加装滤波器将噪声滤掉。</p>
<p>5、系统隔离</p>
<p>电台常常用电话线路传输音频广播直播信号，由于不同的子系统都有各自独立的接地系统，两个系统一旦地线相连，必然形成接地噪声。</p>
<p>另一方面，由于平内2个子系统互连后，即使用单端屏蔽接地、长线分段接地处理，也没有办法解决长距离传输造成的辐射干扰噪声，尤其用庞大的电话网络传输时。这时好的措施是采用隔离的办法，在2个系统间加装音频隔离变压器使之互相隔离。</p>
<p>6、日常维护</p>
<p>定期检查和测试背景音乐功能，确保系统功能能够正常使用；定期对扬声器的回路进行测试，保证扬声器回路处于正常状态，有效避免在出现紧急情况时能够正常使用；考虑到电子设备容易受到灰尘等的影响而出现故障，因此在设备使用过程中，需要定期进行清洁，清理灰尘的时候需要用专用的工具，且注意勿将灰尘调入设备内。</p>
<p>公共广播系统最终呈现效果与设备质量、安装调试及后期维护有关，为了防止出现噪音、啸音，以上6点一定要注意。</p>
<p>附：解决公共广播项目调试时出现噪音与啸叫问题的方法</p>
<p><strong>先排除外部因素：</strong></p>
<p>引起电流声或者噪声问题，外部原因无非，只有三个，外部音源设备、现场坏境噪声过大和系统供电有问题。我们一个个去排除，先把外部音源取下来，再把现场打开的麦克风全部关掉，隔断现场环境噪音的拾取，然后再去确认这一路电源有没有跟其他大型的用电系统共用一路电源。因为如果与其他大型的用电系统共用一路电源，它的使用会大大影响电流的变化，因此产生电流声。</p>
<p><strong>逐步排除内部系统各种问题：</strong></p>
<p>1、最小系统法</p>
<p>我们一套系统中，通常是由前级设备、周边设备、后级组合而成。我们先把周边设备统统去掉。</p>
<p>例如：调音台接功放，功放接音箱，看还有没有噪声，如果还有，我们把调音台也舍去了，如果还有，那就是功放本底噪声，属于质量问题，只能换了。</p>
<p>如果没有，那就是调音台的设置不正确或质量问题，看增益是不是调得过大。如果最小系统没有问题，那肯定是舍去的那一堆周边出问题了，这时候，我们要把周边的设备一件一件的往上添加，再详细检查是设备设置问题还是质量问题。譬如降噪器有没有调整好、均衡器增益有没有过大等等.......</p>
<p>2、替换法</p>
<p>在很多项目中，系统可能不止一个，同样的设备可能会有两台或者以上，我们把检查出来认为有毛病的设备换一台其他会议室调试好没问题的设备，同样的设置，如果问题解决了，那就是设备的问题了。</p>
<p>如果问题还是存在，那就是系统中还存在其他问题你还没有发现，我们需要重新细心地检查。</p>
<p>3、仪表测量法</p>
<p>通过万用表，电平表等测量仪器，测量音箱阻抗、逐级设备输出电平是否正常。</p>
<p>通过以上步骤和方法，我相信很快就把系统中存在的噪声问题解决掉了。</p>
<p><strong>解决啸叫问题；</strong></p>
<p>这个问题的产生是由于扬声器的声音传到麦克风形成了回路，经过多次放大后出现啸叫。最好的办法是将两者之间的通路进行物理上的隔断，比如我们选用麦克风的时候尽量选用指向性的而不选用无指向性的（全向mice）,使用的过程中，麦克风尽量不要正对着扬声器。</p>
<p>但是，由于有多支话筒，扬声器的摆放也固定下来了，无法变更，那我们只能借助周边设备去完成这个功能了，例如反馈抑制器，借助smartlive寻找啸叫点，在均衡器上把对应的频率点作衰减。</p>]]></description><guid isPermaLink="false">/archives/a130a110-8b8a-47b1-b557-e05795f1d129</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:44:29 GMT</pubDate></item><item><title><![CDATA[门禁用的电锁的种类及其各自的特性]]></title><link>https://wiki.dgfygk.com/archives/1c372775-9665-4693-bb21-f665bda9b640</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%97%A8%E7%A6%81%E7%94%A8%E7%9A%84%E7%94%B5%E9%94%81%E7%9A%84%E7%A7%8D%E7%B1%BB%E5%8F%8A%E5%85%B6%E5%90%84%E8%87%AA%E7%9A%84%E7%89%B9%E6%80%A7&amp;url=/archives/1c372775-9665-4693-bb21-f665bda9b640" width="1" height="1" alt="" style="opacity:0;">
<p>门禁常用的电锁有：电插锁，磁力锁，电锁口，电控锁等。（按使用普及率排序）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391094010.png&amp;size=m" alt=""></p>
<br>
<h2 id="1电插锁">(1)电插锁：</h2>
<p>有的人也叫他"阳极锁"，其实他只是"阳极锁"的一种，即 停电开门的电锁。因为按照消防要求，火灾时，大楼会自动切断电源，电锁应该打开，方便人员逃生，所以大部分电锁是断电开门的。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391094208.png&amp;size=m" alt=""></p>
<p>四线电插锁</p>
<p>电插锁看电线分为两线电插锁，四线电插锁，5线电插锁，8线电插锁。</p>
<p>两线电插锁：有两条电线，红色和黑色，红色接电源+12VDC 黑色接GND。断开任何一根线，锁头缩回，门打开。两线电锁，设计比较简单，没有单片机控制电路，锁体容 易发热烫手，冲击电流比较大，属于价格比较低的低档电插锁。 四线电插锁：有两条电线，红色和黑色，红色接电源+12VDC 黑色接GND。还有两条白色的线，是门磁信号线，反映门的开和关状态。它通过门磁，根据当前门的开还是关 状态，输出不同的开关信号给门禁控制器做判断，例如门禁的非法闯入报警、门长时间未关闭等功能都依赖这些信号做判断，如果不需要这些功能，门磁信号线可以不接。 四线电插锁采用单片机控制器，发热良性，带延时控制，带门磁信号输出，属于性价比好的常用型电锁。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391094372.png&amp;size=m" alt=""></p>
<p>电插锁上的关门延时设置</p>
<p>所谓带延时控制，就是锁体上有拨码开关，可以设置关门的延时时间。通常可以设置为 0秒，2.5秒，5秒，9秒。每个厂家的锁分几档延时略有不同。</p>
<p>请注意这个延时 和 门禁控制器和门禁软件设置的开门延时 两个概念不一样。
 <br>
 门禁控制器和门禁软件设置的是"开门延时"，或者叫"门延时"，是指电锁开门多少秒后自动合上。
 <br>
 电锁自带的延时，是关门延时，是指门到位多久后，锁头下来，锁住门。一般门禁系 统都是要求门一关到位，锁头就下来，把门关好。 所以，电锁延时缺省设置成0秒。 而有些门，地弹簧不好，门在关门位置前后晃荡个几下，门才定下来，这个时候如果 设置成0秒，锁头还没有来得及打中锁孔，门就晃荡过去了，门在晃荡回来会把已经 伸出来锁头撞歪，这种情况就可以设置一个关门延时，使门晃荡几下后，稳定下来， 锁头再下来，关闭门。
 <br>
 5线电插锁：
 <br>
 和四线电插锁的原理是一样的，只是多了一对门磁的相反信号，用于一些特殊场合， 正式场合反而麻烦，工程师要测试该用哪一对。 红黑两条线是电源。还有 COM NO NC 三条线，NO 和 NC 分别和 COM 组成两对 信号相反（一组闭合信号，一组开路信号）。门被打开后，闭合信号变成开路信号，开路 信号的一组变成闭合信号。
 <br>
 8线电插锁：
 <br>
 原理和5线电插锁一样。只是除了门磁状态输出外，还增加了锁头状态输出。即，锁 头是不是伸出来信号不一样。 电插锁：通常用于 玻璃门 ，木门等。 优点：隐藏式安全，外观美观，安全性好，不容易被撬开和拉开。
 <br>
 缺点：安装时要挖锁孔，比较辛苦。
 <br>
 备注：有些玻璃门没有门槛（即门框也是玻璃的），或者玻璃门面的顶部没有包边， 需要买无框玻璃门附件来辅助安装。 附件的费用由于产量不高，费用不低。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391094531.png&amp;size=m" alt=""></p>
<p>电插锁带无框玻璃门附件安装后 样图</p>
<h2 id="2-磁力锁-"><strong>(2)</strong> <strong>磁力锁</strong> <strong>：</strong></h2>
<p>又叫电磁锁，是一种依靠电磁铁和铁块之间产生的吸力来闭合门的电锁。</p>
<p>磁力锁也是一种断电开门的电锁。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391094601.png&amp;size=m" alt=""></p>
<br>
<p>磁力锁</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391094791.png&amp;size=m" alt=""></p>
<br>
<p>磁力锁的固定用配件和螺丝（锁里原配好了）</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391094837.png&amp;size=m" alt=""></p>
<p>磁力锁内的接线端子</p>
<p>有些磁力锁是带门状态（门磁状态）输出的，你仔细观察接线端，除电源 接线端子外，还有 COM NO NC 三个接线端子，这些接线端子的作用可以根据当前门是开着还是关着，输出不同的开关信号给门禁控制器做判断，例如 门禁的非法闯入报警 门长时间未关闭等功能都依赖这些信号做判断，如果不需要这些功能，门状态信号端子可以不接。</p>
<p>磁力锁通常用于木门，防火门。 优点：性能比较稳定，返修率会低于其他电锁。安装方便，不用挖锁孔，只用走线 槽，用螺钉固定锁体即可。 缺点：一般装在门外的门槛顶部，而且由于外露，美观性 和 安全性都不如隐藏式安 装的电插锁。 价格和电插锁差不多，有的会略高一些。 由于吸力有限，通常的型号是 280公斤力的，这种力度有可能被多人同时，或者力气 很大的人忽然用力拉开。 所以，磁力锁通常用于办公室内部等一些防君子不防小人的 非高安全级别的场合。 有些安全场合，例如"监狱"，如果用到磁力锁，会定做抗拉 力500公斤以上的磁力锁。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391095002.png&amp;size=m" alt=""></p>
<br>
<p>磁力锁安装好后 样图</p>
<h2 id="3-电锁口-电锁扣"><strong>(3)</strong> <strong>电锁口</strong> <strong>：（电锁扣）</strong></h2>
<p>也有的人叫他"阴极锁"，其实不对，他只是其中一个型号是阴极锁的一种。 它安装在门的侧面，必须配合机械锁使用。 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391095135.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391095179.png&amp;size=m" alt=""></p>
<p>优点：价格便宜。有停电开和停电关两种。</p>
<p>缺点：冲击电流比较大，对系统稳定性影响大，由于是安装在门的侧面，布线很不方便，因为侧门框中间有隔断，线不方便从门的顶部通过门框放下来。锁体要挖空埋入，安装比较吃力。不刷卡，通过球形机械锁也能开门，降低了电子门禁系统的安全性和可查询性。能承受的破坏力有限，可能一脚可以踹开，原则上，我不建议大家使用这种电锁。</p>
<h2 id="4-电控锁-"><strong>(4)</strong> <strong>电控锁</strong> <strong>：</strong></h2>
<p>主要用于小区单元门，银行储蓄所二道门等场合。也可以用门内锁上的旋钮或者钥 匙打开。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391095245.png&amp;size=m" alt=""></p>
<br>
<p>电控锁</p>
<br>
<p>缺点：冲击电流较大，对系统稳定性冲击大，开门时"啪"的一声，噪音比较大。安装不方便，经常需要专业的焊接设备，点焊到铁门上。施工时要注意，开门延时不能长，只能设置在1秒钟以内，如果时间长，有可能引起电控锁发热损坏。 针对这些缺点，新款的"静音电控锁"简称 "静音锁"，有的人又叫 "电机锁"， 被设计出来，他不再是利用电磁铁原理，而是驱动一个小马达来伸缩锁头。 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391095420.png&amp;size=m" alt=""></p>
<p>静音锁（电机锁）</p>
<p>其他电锁，例如玻璃门夹锁，磁力锁中的上下吸合的锁，等电锁由于用量不大，有的还面临淘汰，这里就不介绍了。</p>]]></description><guid isPermaLink="false">/archives/1c372775-9665-4693-bb21-f665bda9b640</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:43:48 GMT</pubDate></item><item><title><![CDATA[弱电工程交换机的三种vlan划分方法详解]]></title><link>https://wiki.dgfygk.com/archives/9e70e58e-6293-4902-9bd6-0ad52cf8f17b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BC%B1%E7%94%B5%E5%B7%A5%E7%A8%8B%E4%BA%A4%E6%8D%A2%E6%9C%BA%E7%9A%84%E4%B8%89%E7%A7%8Dvlan%E5%88%92%E5%88%86%E6%96%B9%E6%B3%95%E8%AF%A6%E8%A7%A3&amp;url=/archives/9e70e58e-6293-4902-9bd6-0ad52cf8f17b" width="1" height="1" alt="" style="opacity:0;">
<p>很多朋友在留言中也多次提到了华为交换机，华为也是在项目中用的比较多交换机，因此，我们今天以华为交换机为例，来讲解vlan的3种划分方法，360度来了解vlan。</p>
<p>如果你对vlan了解不多，那么这篇文章可以带你深入学习vlan，都有详细注释。</p>
<p><strong>端口类型</strong></p>
<p>在学习划分vlan前，必须要了解华为交换机的端口类型，以及他们的使用方法，因为端口的类型在实际配置中是必须会用到的，因为下面的vlan划分会用到。</p>
<p><strong>1、Access类型端口</strong></p>
<p>执行命令port default vlan vlan-id，将端口加入到指定的VLAN中。</p>
<p>Access类型端口只能属于1个VLAN，一般用于连接计算机端口;</p>
<p><strong>2、Trunk类型端口</strong></p>
<p>Trunk类型端口可以允许多个VLAN通过,可以接收和发送多个VLAN 报文,</p>
<p><strong>3、Hybrid类型端口</strong></p>
<p>-- 选择执行其中一个步骤配置Hybrid端口加入VLAN的方式:Hybrid类型端口:可以允许多个VLAN通过，可以接收和发送多个VLAN 报文。</p>
<p>我们前面曾说到，关于vlan的划分方法有很多，项目应用中较多的方法就是基于<strong>端口划分vlan</strong> 、<strong>基于mac地址划分vlan</strong> 、<strong>基于ip地址划分vlan</strong>，要真正对vlan了解，必须熟悉这三次划分方法，以便于根据实际项目进行应用。</p>
<p><strong>一、基于端口的vlan划分方法</strong></p>
<p>这里面为了方便大家理解，直接用实例讲解更清楚。</p>
<p><strong>1、组网需求</strong></p>
<p>如图1所示，某企业的交换机连接有很多用户，且相同业务用户通过不同的设备接入企业网络。为了通信的安全性，也为了避免广播报文泛滥，企业希望业务相同用户之间可以互相访问，业务不同用户不能直接访问。</p>
<p>可以在交换机上配置基于端口划分VLAN，把业务相同的用户连接的端口划分到同一VLAN。</p>
<p>也就是<strong>不同VLAN的用户不能直接进行二层通信</strong> ，<strong>同一VLAN内的用户可以直接互相通信。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391168832.png&amp;size=m" alt=""></p>
<p><strong>2、配置步骤</strong></p>
<p><strong>步骤1 在SwitchA创建VLAN2和VLAN3，并将连接用户的端口分别加入VLAN。SwitchB配置与SwitchA类似，不再赘述。</strong></p>
<p>[Quidway] system-view //进入配置视图</p>
<p>[Quidway] sysname SwitchA //给交换机命名</p>
<p>[SwitchA] vlan batch 2 3 //同时创建vlan2与vlan3</p>
<p>[SwitchA] interface ethernet 0/0/1 //进入端口0/0/1</p>
<p>[SwitchA-Ethernet0/0/1] port link-type access //设置端口模式为access</p>
<p>[SwitchA-Ethernet0/0/1] port default vlan 2 //将端口加入vlan2中</p>
<p>[SwitchA-Ethernet0/0/1] quit //退出</p>
<p>[SwitchA] interface ethernet 0/0/2 //进入端口0/0/2</p>
<p>[SwitchA-Ethernet0/0/2] port link-type access //端口模式为access</p>
<p>[SwitchA-Ethernet0/0/2] port default vlan 3 //将端口加入vlan3中</p>
<p>[SwitchA-Ethernet0/0/2] quit //退出</p>
<p><strong>步骤2 配置SwitchA上与SwitchB连接的端口类型及通过的VLAN。SwitchB配置与SwitchA类似，不再赘述。</strong></p>
<p>[SwitchA] interface ethernet 0/0/3 // 进入端口0/0/3</p>
<p>[SwitchA-Ethernet0/0/3] port link-type trunk //设置端口模式为trunk</p>
<p>[SwitchA-Ethernet0/0/3] port trunk allow-pass vlan 2 3 // trunk口允许通过vlan2与vlan3的数据包</p>
<p><strong>步骤3 验证配置结果将User1和User2配置在一个网段，比如192.168.100.0/24;将User3和User4配置在一个网段，比如192.168.200.0/24。</strong></p>
<p>User1和User2能够互相ping通，但是均不能ping通User3和User4。User3和User4能够互相ping通，但是均不能ping通User1和User2。</p>
<p><strong>二、基于MAC地址vlan划分方法</strong></p>
<p><strong>1、组网需求</strong></p>
<p>某个公司的网络中，网络管理者将同一部门的员工划分到同一VLAN。为了提高部门内的信息安全，要求只有本部门员工的PC才可以访问公司网络。</p>
<p>如图2所示，PC1、PC2、PC3为本部门员工的PC，要求这几台PC可以通过SwitchA、Switch访问公司网络，如换成其他PC则不能访问。可以配置基于MAC地址划分VLAN，将本部门员工PC的<strong>MAC地址与VLAN绑定</strong>，从而实现该需求。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391169120.png&amp;size=m" alt=""></p>
<p><strong>2、操作步骤</strong></p>
<p>这里面弱电君对上面重复的注释就不再解释了，和上面一样，大家也可以验证下，经过上面的理解，是否能认识下面的配置代码。</p>
<p><strong>步骤1 配置Switch</strong></p>
<p><strong>#创建VLAN</strong></p>
<p>[Quidway] system-view</p>
<p>[Quidway] vlan batch 10 100</p>
<p><strong>#配置接口的PVID和加入VLAN</strong></p>
<p>[Quidway] interface ethernet 0/0/1</p>
<p>[Quidway-Ethernet0/0/1] port hybrid pvid vlan 100 //将接口加入到vlan100</p>
<p>[Quidway-Ethernet0/0/1] port hybrid untagged vlan 10 //untagged是指当数据离开端口时脱掉标签，指定端口允许VLAN 10的数据帧通过，且发送时不带VLAN标签</p>
<p>[Quidway-Ethernet0/0/1] quit #退出</p>
<p>[Quidway] interface ethernet 0/0/2 #进入端口0/0/2</p>
<p>[Quidway-Ethernet0/0/2] port hybrid tagged vlan 10 //tagged是指当数据离开端口时带着标签，指定允许VLAN 10的数据帧通过，且发送时必须带有VLAN标签</p>
<p>[Quidway-Ethernet0/0/2] quit</p>
<p><strong>#PC的MAC地址与VLAN10关联</strong></p>
<p>[Quidway] vlan 10</p>
<p>[Quidway-Vlan10] mac-vlan mac-address 22-22-22</p>
<p>//关联pc1的mac地址</p>
<p>[Quidway-Vlan10] mac-vlan mac-address 33-33-33</p>
<p>//关联pc2的mac地址</p>
<p>[Quidway-Vlan10] mac-vlan mac-address 44-44-44</p>
<p>//关联pc3的mac地址</p>
<p>[Quidway-Vlan10] quit</p>
<p><strong>#使能接口的基于MAC地址划分VLAN功能</strong></p>
<p>[Quidway] interface ethernet 0/0/1</p>
<p>[Quidway-Ethernet0/0/1] mac-vlan enable</p>
<p>//启动端口的mac-vlan功能</p>
<p>[Quidway-Ethernet0/0/1] quit</p>
<p><strong>三、基于IP子网的vlan划分</strong></p>
<p><strong>1、组网需求</strong></p>
<p>某企业拥有多种业务，如IPTV、VoIP、Internet等，每种业务使用的IP地址网段各不相同。为了便于管理，现需要将同一种类型业务划分到同一VLAN中，不同类型的业务划分到不同VLAN中。</p>
<p>如图3所示，Switch接收到用户报文有数据、IPTV、语音等多种业务，用户设备的IP地址网段各不相同。<strong>现需要将不同类型的业务划分到不同的VLAN中</strong>，通过不同的VLANID分流到不同的远端服务器上以实现业务互通。</p>
<p>这类在实际应用中是比较多的。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391169282.png&amp;size=m" alt=""></p>
<p><strong>2、配置思路</strong></p>
<p>采用如下的思路配置基于IP子网划分VLAN:</p>
<p>a、创建VLAN，确定每种业务所属的VLAN。</p>
<p>b、关联IP子网和VLAN，实现根据报文中的源IP地址或指定网段确定VLAN。</p>
<p>c、配置端口加入VLAN，实现基于IP子网的VLAN通过当前端口。</p>
<p>d、配置VLAN划分方式的优先级，确保优先选择基于IP子网划分VLAN。</p>
<p>e、使能基于IP子网划分VLAN。</p>
<p><strong>3、操作步骤</strong></p>
<p><strong>步骤1 创建VLAN</strong></p>
<p>#在Switch上创建VLAN100、VLAN200和VLAN300。</p>
<p>[Quidway] system-view</p>
<p>[Quidway] vlan batch 100 200 300</p>
<p><strong>步骤2 配置接口</strong></p>
<p><strong>#在Switch上配置接口Eth0/0/1为Hybrid类型，并加入VLAN100、VLAN200和VLAN300。</strong></p>
<p>[Quidway] interface ethernet 0/0/1</p>
<p>[Quidway-Ethernet0/0/1] port link-type hybrid //设置端口为hybrid模式</p>
<p>[Quidway-Ethernet0/0/1] port hybrid untagged vlan100 200 300 //指定端口允许VLAN 100、vlan200、vlan300的数据帧通过，且发送时不带VLAN标签</p>
<p>[Quidway-Ethernet0/0/1] quit</p>
<p><strong>#在Switch上配置接口Eth0/0/2加入VLAN100。</strong></p>
<p>[Quidway] interface ethernet 0/0/2</p>
<p>[Quidway-Ethernet0/0/2] port link-type trunk</p>
<p>[Quidway-Ethernet0/0/2] port trunk allow-pass vlan 100 //接口0/0/2加入vlan100</p>
<p>[Quidway-Ethernet0/0/2] quit</p>
<p><strong>#在Switch上配置接口Eth0/0/3加入VLAN200。</strong></p>
<p>[Quidway] interface ethernet 0/0/3</p>
<p>[Quidway-Ethernet0/0/3] port link-type trunk</p>
<p>[Quidway-Ethernet0/0/3] port trunk allow-pass vlan 200 //接口0/0/3加入vlan200</p>
<p>[Quidway-Ethernet0/0/3] quit</p>
<p><strong>#在Switch上配置接口Eth0/0/4加入VLAN300。</strong></p>
<p>[Quidway] interface ethernet 0/0/4</p>
<p>[Quidway-Ethernet0/0/4] port link-type trunk</p>
<p>[Quidway-Ethernet0/0/4] port trunk allow-pass vlan 300 //接口0/0/4加入vlan300</p>
<p>[Quidway-Ethernet0/0/4] quit</p>
<p><strong>#在Switch上配置接口Eth0/0/1使能基于IP子网划分VLAN功能。</strong></p>
<p>[Quidway] interface ethernet 0/0/1</p>
<p>[Quidway-Ethernet0/0/1] ip-subnet-vlan enable</p>
<p>[Quidway-Ethernet0/0/1] quit</p>
<p><strong>步骤3 配置基于IP子网划分VLAN</strong></p>
<p>#在Switch上配置VLAN100与IP地址192.168.1.2/24关联，优先级为2。</p>
<p>[Quidway] vlan 100</p>
<p>[Quidway-vlan100] ip-subnet-vlan 100 ip 192.168.1.2 24 priority 2</p>
<p>[Quidway-vlan100] quit</p>
<p>#在Switch上配置VLAN200与IP地址192.168.2.2/24关联，优先级为3。</p>
<p>[Quidway] vlan 200</p>
<p>[Quidw ay-vlan200] ip-subnet-vlan 200 ip 192.168.2.2 24 priority 3</p>
<p>[Quidway-vlan200] quit</p>
<p>#在Switch上配置VLAN300与IP地址192.168.3.2/24关联，优先级为4。</p>
<p>[Quidway] vlan 300</p>
<p>[Quidway-vlan300] ip-subnet-vlan 300 ip 192.168.3.2 24 priority 4</p>
<p>[Quidway-vlan300] quit</p>
<p><strong>步骤4 验证配置结果</strong></p>
<p>在Switch上执行以下命令，显示信息如下:</p>
<p>300 1 192.168.3.2 255.255.255.0 4</p>
<p>ip-subnet-vlan count: 3 total count: 3</p>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/ZL3yAhhG7qhNgPsmdwU0uA">https://mp.weixin.qq.com/s/ZL3yAhhG7qhNgPsmdwU0uA</a></p>]]></description><guid isPermaLink="false">/archives/9e70e58e-6293-4902-9bd6-0ad52cf8f17b</guid><dc:creator>黄健</dc:creator><pubDate>Tue, 3 Dec 2024 04:37:33 GMT</pubDate></item><item><title><![CDATA[核心交换机和普通交换机有何区别？]]></title><link>https://wiki.dgfygk.com/archives/abb432ff-9823-4554-a95a-b221e7f2d130</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%A0%B8%E5%BF%83%E4%BA%A4%E6%8D%A2%E6%9C%BA%E5%92%8C%E6%99%AE%E9%80%9A%E4%BA%A4%E6%8D%A2%E6%9C%BA%E6%9C%89%E4%BD%95%E5%8C%BA%E5%88%AB%EF%BC%9F&amp;url=/archives/abb432ff-9823-4554-a95a-b221e7f2d130" width="1" height="1" alt="" style="opacity:0;">
<p>提起核心交换机与普通交换机有什么区别？相信很多朋友都有点迷惑，今天我们一起来了解下。</p>
<p>
 <br>
</p>
<p>核心交换机并不是交换机的一种类型，而是放在核心层（网络主干部分）的交换机叫核心交换机。</p>
<p>
 <br>
</p>
<p>一般大型企业网络和网吧需要购买核心交换机来实现强大的网络扩展能力，以保护原有的投资，电脑达到一定数量才会要用上核心交换机，而基本在50台以下无需用核心交换机，有个路由器即可，所谓的核心交换机是针对网络架构而言，如果是个几台电脑的小局域网，一个8口的小交换机就可以称之为核心交换机。</p>
<p>
 <br>
</p>
<p><strong>核心交换机与普通交换机区别</strong></p>
<p>
 <br>
</p>
<p><strong>1、端口的区别</strong></p>
<p>
 <br>
</p>
<p>普通交换机端口数量一般为24-48个，网口大部分为千兆以太网或者百兆以太网口，主要功能用于接入用户数据或者汇聚一些接入层的交换机数据，这种交换机最多可以配置Vlan简单路由协议和一些简单的SNMP等功能，背板带宽相对较小。</p>
<p>
 <br>
</p>
<p><strong>2、连接或访问网络区别</strong></p>
<p>
 <br>
</p>
<p>通常将网络中直接面向用户连接或访问网络的部分称为接入层，将位于接入层和核心层之间的部分称为分布层或汇聚层，接入层目的是允许终端用户连接到网络，因此接入层交换机具有低成本和高端口密度特性。</p>
<p>
 <br>
</p>
<p>汇聚层交换机是多台接入层交换机的汇聚点，它必须能够处理来自接入层设备的所有通信量，并提供到核心层的上行链路，因此汇聚层交换机具备更高的性能，更少的接口和更高的交换速率。</p>
<p>
 <br>
</p>
<p>而网络主干部分则称为核心层，核心层的主要目的在于通过高速转发通信，提供优化、可靠的骨干传输结构，因此核心层交换机应用有更高的可靠性、性能和吞吐量。</p>
<p>
 <br>
</p>
<p><strong>核心交换机的优势</strong></p>
<p>
 <br>
</p>
<p>相比较普通交换机而言，数据中心交换机需要具备以下特质：大缓存、高容量、虚拟化、FCOE、二层TRILL技术、可扩展性和模块冗余等方面的特征。</p>
<p>
 <br>
</p>
<p><strong>1、大缓存技术</strong></p>
<p>
 <br>
</p>
<p>数据中心交换机改变了传统交换机的出端口缓存方式，采用分布式缓存构架，缓存比普通交换机也大许多，缓存能力可达1G以上，而一般的交换机只能达到2-4m。对于每端口在万兆全线速条件下达到200ms的突发流量缓存能力，从而在突发流量的情况下，大缓存仍能保证网络转发零丢包，正好适应数据中心服务器量大，突发流量大的特点。</p>
<p>
 <br>
</p>
<p><strong>2、高容量设备</strong></p>
<p>
 <br>
</p>
<p>数据中心的网络流量具有高密度应用调度、浪涌式突发缓存的特点，而普通交换机以满足互联互通为目的，无法实现对业务精准识别与控制，在大业务情况下无法做到快速响应和零丢包，无法保证业务的连续性，系统的可靠性主要依赖设备的可靠性。</p>
<p>
 <br>
</p>
<p>所以普通交换机无法满足数据中心的需要，数据中心交换机需要具备高容量转发特点，数据中心交换机必须支持高密万兆板卡，也就是48口万兆板卡，为使48口万兆板卡具备权限转发，数据中心交换机只能采用CLOS分布式交换架构。</p>
<p>
 <br>
</p>
<p>除此之外，随着40G和100G的普及，支持8端口40G板卡和4端口的100G板卡也逐渐商用，数据中心交换机40G、100G的板卡早已出现进入市场，从而满足数据中心高密度应用的需求。</p>
<p>
 <br>
</p>
<p><strong>3、虚拟化技术</strong></p>
<p>
 <br>
</p>
<p>数据中心的网络设备需要具有高管理性和高安全可靠性的特点，因此数据中心的交换机也需要支持虚拟化，虚拟化就是把物力资源转变为逻辑上可以管理的资源，以打破物理结构之间的壁垒，网络设备的虚拟化包括多虚一，一虚多等技术。</p>
<p>
 <br>
</p>
<p>通过虚拟化技术，可以对多台网络设备统一管理，也可以对一台设备上的业务进行完全隔离，从而可以将数据中心管理成本减少40%，将IT利用率提高大约25%。</p>
<p>
 <br>
</p>
<p><strong>4、TRILL技术</strong></p>
<p>
 <br>
</p>
<p>数据中心在构建二层网络方面，原先的标准是FTP协议，但其固有的缺陷如：STP是通过端口阻止来工作的，所有冗余链路不进行数据转发，造成宽带资源的浪费，STP整网只有一颗生成树，数据报文都要经过根桥中转收才能到达，影响了整网的转发效率。</p>
<p>
 <br>
</p>
<p>所以STP将不再适合超大型数据中心的扩展，TRILL正是因为应了STP 的这些缺陷而产生，视为数据中心应用而生的技术，TRILL协议把二层配置和灵活性与三层融合和规模有效结合在一起 ，大二层不需要配置的情况下，就可以实现整网无环路转发。TRILL技术是数据中心交换机二层基本特性，这是普通交换机不具备的。</p>
<p>
 <br>
</p>
<p><strong>5、FCOE技术</strong></p>
<p>
 <br>
</p>
<p>传统的数据中心往往存在一张数据网和一张存储网络，而新一代的数据中心网络融合趣事越来越明显，FCOE技术的出现使网络融合成为可能，FCOE就是把存储网的数据帧封装在以太网帧内进行转发的技术。实现这一融合技术必然是在数据中心的交换机上，普通交换机一般不具备这些功能。</p>
<p>
 <br>
</p>
<p><strong>链路聚合、冗余、堆叠、热备份</strong>等这些功能也非常重要，决定了核心交换机在实际应用中的性能、效率、稳定性等。</p>
<p><strong>一、链路聚合</strong></p>
<p>
 <br>
</p>
<p>是将两个或更多数据信道结合成一个单个的信道，该信道以一个单个的更高带宽的逻辑链路出现。链路聚合一般用来连接一个或多个带宽需求大的设备，例如连接骨干网络的服务器或服务器群。它可以用于扩展链路带宽，提供更高的连接可靠性。</p>
<p>
 <br>
</p>
<p><strong>举例：</strong> 公司有2层楼，分别运行着不同的业务，本来两个楼层的网络是分开的，但都是一家公司难免会有业务往来，这时我们就可以打通两楼之前的网络，使具有相互联系的部门之间高速通信。如下图：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391178566.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>如上图所示，SwitchA和SwitchB通过以太链路分别都连接VLAN10和VLAN20的网络，且SwitchA和SwitchB之间有较大的数据流量。</p>
<p>用户希望SwitchA和SwitchB之间能够提供较大的链路带宽来使相同VLAN间互相通信。同时用户也希望能够提供一定的冗余度，保证数据传输和链路的可靠性。</p>
<p>创建Eth-Trunk接口并加入成员接口，实现增加链路带宽，2台交换机分别配置Eth-Trunk1 分别将需要通信的3条线路的端口加入Eth-Trunk1，设置端口trunk， 允许相应的vlan通过；这样两楼的网络就可以正常通信了。</p>
<p>
 <br>
</p>
<p><strong>二、链路冗余</strong></p>
<p>
 <br>
</p>
<p>为了保持网络的稳定性，在多台交换机组成的网络环境中，通常都使用一些备份连接，以提高网络的效率、稳定性，这里的备份连接也称为备份链路或者冗余链路。</p>
<p>
 <br>
</p>
<p><strong>三、交换机的堆叠</strong></p>
<p>
 <br>
</p>
<p>通过专有的堆叠电缆连接起来，可将多台交换机堆叠成一台逻辑交换机。该逻辑交换机中的所有交换机共享相同的配置信息和路由信息。当向逻辑交换机增加和减少单体交换机时不会影响其性能。</p>
<p>
 <br>
</p>
<p>叠加的交换机之间通过两条环路连接起来。交换机的硬件负责将数据包在双环路上做负载均衡。环路在这里充当了这个大的逻辑交换机的背板的角色，在双环路都正常工作时，数据包在这台逻辑交换机上的传输率为32Gbps。</p>
<p>
 <br>
</p>
<p>当一个数据帧需要传输时，交换机的软件会进行计算看哪条环路更可用，然后数据帧会被送到该环路上。如果一条堆叠电缆出故障，故障两端的交换机都会侦测到该故 障，并将受影响的环路断开，而逻辑交换机仍然可以以单环的状态工作，此时的数据包通过率为16Gbps。交换机的堆叠采用菊花链方式，连接的方式参考下图。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391178764.png&amp;size=m" alt=""></p>
<p>堆叠增加交换机端口与带宽的稳定性。</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><strong>四、热备份（HSRP)</strong></p>
<p>核心交换机是整个网络的核心和心脏，如果核心交换机发生致命性的故障，将导致本地网络的瘫痪，所造成的损失也是难以估计的。所以我们在选择核心交换机时，经常会看到有的核心交换机具有堆叠或热备份等功能。</p>
<p>
 <br>
</p>
<p>对核心交换机采用热备份是提高网络可靠性的必然选择。在一个核心交换机完全不能工作的情况下，它的全部功能便被系统中的另一个备份路由器完全接管，直至出现问题的路由器恢复正常，这就是热备份路由协议。</p>
<p>
 <br>
</p>
<p>实现HSRP的条件是系统中有多台核心交换机，它们组成一个"热备份组”，这个组形成一个虚拟路由器。在任意时刻，一个组内只有一个路由器是活动的，并由它来转发数据包，如果活动路由器发生了故障，将选择一个备份路由器来替代活动路由器，但是在本网络内的主机看来，虚拟路由器没有改变。所以主机仍然保持连接，没有受到故障的影响，这样就较好地解决了核心交换机切换的问题。</p>
<p>
 <br>
</p>
<p>为了减少网络的数据流量，在设置完活动核心交换机和备份核心交换机之后，只有活动核心交换机和备份核心交换机定时发送HSRP报文。如果活动核心交换机失效，备份核心交换机将接管成为活动核心交换机。如果备份核心交换机失效或者变成了活跃核心交换机，将由另外的核心交换机被选为备份核心交换机。</p>
<p>
 <br>
</p>
<p>当某台接入层交换机到主核心交换机的线路出现故障，切换至备机，数据流走向。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391178826.png&amp;size=m" alt=""></p>
<p>当接入层交换机1上联至核心交换机A的数据链路出现故障，导致接入层交换机1的数据链路切换至核心交换机B，但在切换期间接入层交换机1分丢6个数据包，如上图所示。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391178946.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>当服务器与核心交换机A之间主链路出现故障（如线路、网卡等），服务器主网卡切换至备用网卡上时，会丢6个数据包，但当主链路恢复以后，服务器会自动从备用网卡切换至主网卡，而这次切换时数据包不会丢失。</p>
<p>
 <br>
</p>
<p><strong><a href="http://mp.weixin.qq.com/s?__biz=MzU4Nzc2NDQ3OQ==&amp;mid=2247484556&amp;idx=1&amp;sn=d494b4cdb3a55bca959f68679a3c853f&amp;chksm=fde6431cca91ca0abb267da4476f540de473af2ed162314bfd4756131e9f658307cef9dc2a24&amp;scene=21#wechat_redirect">50元一份弱电技术资料（10G），限免100名免费入弱电交流群!</a></strong></p>
<p>
 <br>
</p>
<p><strong>谢谢关注，每天更新一篇弱电行业文章！！ !</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391179112.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/Us7HnCZy2iQq_lt1LiVMMw</p>]]></description><guid isPermaLink="false">/archives/abb432ff-9823-4554-a95a-b221e7f2d130</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 18 Nov 2024 06:44:54 GMT</pubDate></item><item><title><![CDATA[什么是公网ip？什么是内网ip？为什么ip地址通常以192.168开头？]]></title><link>https://wiki.dgfygk.com/archives/c4ccde0a-efb0-404a-abf9-df1c7c133824</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AF%E5%85%AC%E7%BD%91ip%EF%BC%9F%E4%BB%80%E4%B9%88%E6%98%AF%E5%86%85%E7%BD%91ip%EF%BC%9F%E4%B8%BA%E4%BB%80%E4%B9%88ip%E5%9C%B0%E5%9D%80%E9%80%9A%E5%B8%B8%E4%BB%A5192.168%E5%BC%80%E5%A4%B4%EF%BC%9F&amp;url=/archives/c4ccde0a-efb0-404a-abf9-df1c7c133824" width="1" height="1" alt="" style="opacity:0;">
<p>我们做弱电的，与ip地址接触最多，无论是弱电的哪方面，都需要跟ip地址打交道，通常我们也会经常听到公网、内网？那什么是公网ip地址呢？什么是私网ip地址呢？为什么我们常见到的ip地址以192.168开头呢？我们今天来了解下。</p>
<br>
<p>内、外网是相对于防火墙而言的，在防火墙内部叫做内网，反之就是外网。在一定程度上外网等同于公网，内网等同于私网。</p>
<br>
<p><strong>一、IP地址分类</strong></p>
<br>
<p>公网地址是指在因特网上直接可达的地址，如果你有一个公网地址，那就意味着你不但能访问别人，还能被人访问。公网地址是架设各种服务器必不可少的条件。</p>
<br>
<ul>
 <li>
  <p>A类：10.0.0.0到127.255.255.255主要分配给大量主机而局域网网络数量较少的大型网络</p>
 </li>
 <li>
  <p>B类：128.0.0.0到191.255.255.255一般用于国际性大公司和政府机构</p>
 </li>
 <li>
  <p>C类：192.0.0.0到223.255.255.255用于一般小公司校园网研究机构等</p>
 </li>
 <li>
  <p>D类：224.0.0.0到239.255.255.255用于特殊用途，又称作广播地址</p>
 </li>
 <li>
  <p>E类：240.0.0.0到255.255.255.255暂时保留</p>
 </li>
</ul>
<br>
<p><strong>二、私有地址</strong></p>
<br>
<p>网络中的主机需要通信，需要使用一个IP地址，目前我们普遍使用的IPv4的地址，分为A、B、C、D、E五类，在上面已经列出，其中A、B、C类是我们常见的IP地址段。在这三类地址中，大多数为公有地址，需要向国际互联网信息中心注册。在IPv4地址中预留了3个IP地址段，作为私有地址，共家庭、企业、学校等内部组网使用。</p>
<br>
<p><strong>私网地址</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391275414.png&amp;size=m" alt=""></p>
<p>除了这三个ip地址段为私有ip地址外，其它的都为公网ip。</p>
<p>这些地址已被声明私有化，任何内网中的设备可以任意使用这些地址，但是在这三个范围内的IP地址不允许出现在Internet（外网）上。所以我们平台在内部组网时用的比较多，比如监控项目与公司内部电脑ip设置，用的多的就是192.168.xx。</p>
<p>通常情况下，按照需要容纳的主机数选择私有地址段。家庭网络规模比较小，一个C类地址，192.168.1.x可以容纳254个终端，足够使用。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391275581.png&amp;size=m" alt=""></p>
<br>
<p>学校或者大型企业可能使用B类甚至A类作为内部网络的地址段。比如手机上网很可能获取的也是一个10开头的内网地址，而不是公网地址。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391275701.png&amp;size=m" alt=""></p>
<br>
<p><strong>三、NAT地址转换</strong></p>
<br>
<p>私网地址只能在局域网中使用，不同的局域网可以使用同样的私网地址，私网地址是不能出现在互联网上的。那么私网地址如何访问互联网呢？这就需要用到NAT地址转换，将内网地址映射到外网地址。</p>
<br>
<p>而NAT地址转换它实现内网的IP地址与公网的地址之间的相互转换，将大量的内网IP地址转换为一个或少量的公网IP地址，减少对公网IP地址的占用。</p>
<br>
<p>家庭网络普遍使用端口映射的方式，NAT的核心是一张映射表（源IP地址，源端口，目的IP地址，目的端口），将内网源IP地址和端口映射到同一个公网地址的不同端口，如下图所示。</p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391275768.png&amp;size=m" alt=""></p>
<br>
<p>总之，家庭网络由于网络规模比较小，通常使用了192.168开头的私网地址，光猫拨号获取了一个公网地址（或者运营商私网地址），通过NAT方式的映射到这个公网地址的不同端口，访问互联网。主要注意的NAT模式和路由模式是不同的。</p>]]></description><guid isPermaLink="false">/archives/c4ccde0a-efb0-404a-abf9-df1c7c133824</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 18 Nov 2024 06:44:39 GMT</pubDate></item><item><title><![CDATA[海康威视系列硬盘录像机常用设置与操作]]></title><link>https://wiki.dgfygk.com/archives/d4c3815f-41fe-4395-920f-d84ee620f2e7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%B7%E5%BA%B7%E5%A8%81%E8%A7%86%E7%B3%BB%E5%88%97%E7%A1%AC%E7%9B%98%E5%BD%95%E5%83%8F%E6%9C%BA%E5%B8%B8%E7%94%A8%E8%AE%BE%E7%BD%AE%E4%B8%8E%E6%93%8D%E4%BD%9C&amp;url=/archives/d4c3815f-41fe-4395-920f-d84ee620f2e7" width="1" height="1" alt="" style="opacity:0;">
<ol>
 <li>如何使用遥控？</li>
</ol>
<p>对准DVR面板，在遥控器上操作</p>
<ul>
 <li>
  <p>A、按设备键</p>
 </li>
 <li>
  <p>B、输入设备号88（默认设备号为88，可在主菜单-本地显示进行修改）</p>
 </li>
 <li>
  <p>C、按确认键</p>
 </li>
</ul>
<p>如果遥控器配置成功，硬盘录像机前面板上的状态灯变为绿色。再次按下设备键，停止遥控控制。每次DVR重新上电后，遥控器需再次配置方可使用。</p>
<ol start="2">
 <li>为什么新机器开机后会有"嘀－嘀－嘀－嘀嘀"的声音，如何清除？</li>
</ol>
<p>原因：</p>
<ul>
 <li>
  <p>1：机器没有装硬盘；</p>
 </li>
 <li>
  <p>2：机器装了硬盘但没有进行格式化；</p>
 </li>
 <li>
  <p>3：硬盘坏。</p>
 </li>
</ul>
<p>清除办法：</p>
<ul>
 <li>
  <p>1：如果不需要装硬盘，请到主菜单-异常处理中，把硬盘错这个异常类型的声音告警打"×"；</p>
 </li>
 <li>
  <p>2：如果装了硬盘，请到主菜单-管理工具-硬盘管理中，把硬盘格式化；</p>
 </li>
 <li>
  <p>3：仍未解决，可能是硬盘坏，请更换硬盘。</p>
 </li>
</ul>
<ol start="3">
 <li>
  <p>如何设置7×24小时自动录像？</p>
 </li>
 <li>
  <p>如何设置录像参数，如何计算录像文件大小？</p>
 </li>
</ol>
<p>常用的录像参数有码流类型、分辨率、视频帧率、位率上限和位率类型，使用中根据设备功能选定支持的码流类型与合适的分辨率（详见"各类型的对比"）；</p>
<p>视频帧率一般为全帧率；</p>
<p>位率类型有：</p>
<ul>
 <li>
  <p>定码流：按照设定的位率上限值进行压缩存储。</p>
 </li>
 <li>
  <p>变码流：根据不同场景，即时自动改变录像分辨率、图像质量、码流和帧率，节省硬盘空间和网络带宽。</p>
 </li>
</ul>
<p>录像文件大小和"录像设置"中的位率大小和采用何种位率类型有关。使用中根据所选择的分辨率来选择适当的位率大小：</p>
<p>分辨率 位率上限</p>
<p>CIF 384K~768Kbps</p>
<p>DCIF 512K~1Mbps</p>
<p>4CIF 768K~2Mbps</p>
<p>下表是在定码率下常见位率一个通道一个小时录像文件的大小。</p>
<p>码流大小/文件大小 码流大小/文件大小 码流大小/文件大小</p>
<p>96kb 42MB 320kb 140MB 896kb 393MB</p>
<p>128kb 56MB 384kb 168MB 1.00Mb 450MB</p>
<p>160kb 70MB 448kb 196MB 1.25Mb 562MB</p>
<p>192kb 84MB 512kb 225MB 1.50Mb 675MB</p>
<p>224kb 98MB 640kb 281MB 1.75Mb 787MB</p>
<p>256kb 112MB 768kb 337MB 2.00Mb 900MB</p>
<p>单通道录像文件大小（GByte）=码流(kbit/s)÷8×3600×每天录像时间(hour)÷1024÷1024</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731391336380.png&amp;size=m" alt=""></p>
<br>
<ol start="5">
 <li>如何设置移动侦测录像？</li>
</ol>
<p>在菜单录像设置界面中把开启录像状态选为"ü"，然后进入设置界面，把录像类型设置为"移动侦测"，设置好时间段并复制到不同的日期，点击"确定"保存设置。</p>
<p>如果要取消已保存的移动侦测区域及移动侦测处理方式，只需将"移动侦测"设成"关闭"。</p>
<p>注意：一个通道的移动侦测区域不能被复制到其他通道。</p>
<ol start="6">
 <li>如何设置移动侦测区域？</li>
</ol>
<p>进入主菜单-图像设置，选择一档灵敏度级别（0-5）以后，进入区域设置，</p>
<p>整个画面被分割成22*18（NTSC制式为22*15）个小方格。</p>
<ol>
 <li>鼠标操作</li>
</ol>
<p>选定区域：</p>
<ul>
 <li>
  <p>① 按下鼠标左键，拖动鼠标，放开鼠标。点击鼠标右键 确定 保存。</p>
 </li>
 <li>
  <p>② 点击鼠标右键全屏选项，点击鼠标右键 确定 保存全屏区域。</p>
 </li>
</ul>
<p>清除区域：</p>
<ol start="2">
 <li>面板/遥控操作</li>
</ol>
<p>选定区域：</p>
<ul>
 <li>
  <p>① 将小黄框移动到区域左上角，按【编辑】键，黄框变为红框，使用方向键划定区域，然后确定保存。</p>
 </li>
 <li>
  <p>② 点击云台控制键，选定全屏，然后确定保存。</p>
 </li>
</ul>
<p>清除区域：</p>
<ul>
 <li>
  <p>① 将小黄框移动到已划定区域左上角，按【编辑】键，红框变为黄框，使用方向键划定区域，然后确定清除区域。</p>
 </li>
 <li>
  <p>② 点击输入法键，清除所有区域。</p>
 </li>
</ul>
<p>以下为设置移动侦测区域时所涉及到的操作键。</p>
<ol start="7">
 <li>如何回放录像文件？</li>
</ol>
<p>1）按文件类型回放</p>
<p>录像文件以分页方式显示，每页最多显示8个文件。如果多于8个，可以使用"选择页号"来选择当前的页号，方法是在"选择页号"选项内，使用【数字键】直接输入页号或通过【↑】或【↓】键进行翻页。</p>
<p>2）按时间回放</p>
<ul>
 <li>
  <p>① 第一步：在回放操作界面中分别选择通道号、录像类型。</p>
 </li>
 <li>
  <p>② 输入起止时间。</p>
 </li>
 <li>
  <p>③ 将活动框移到"按时间播放"按钮处，再按前面板【确认】键，即播放起始时间（精确到"秒"）开始的录像资料。若起始时间点上无录像资料，则回放的起始时间往后顺延。</p>
 </li>
</ul>
<p>若整个时间段里无录像资料，则屏幕上会出现"操作失败"提示框（同上按文件播放"操作失败"提示框）。</p>
<p>3） 按卡号回放（针对ATM设备）</p>
<p>录像文件以分页方式显示，每页最多显示8个文件。如果多于8个，可以使用"选择页号"来选择当前的页号，方法是在"选择页号"选项内，使用【数字键】直接输入页号或通过【↑】或【↓】键进行翻页。</p>
<ol start="8">
 <li>如何开启预览和回放声音？</li>
</ol>
<p>1） 打开预览声音</p>
<p>先确认所购买产品是否支持音频（详见"各类型的对比"）；再确认音频输入已按要求接好；然后在预览设置菜单界面中，把音频预览开启即可。</p>
<p>2） 打开回放声音</p>
<ul>
 <li>
  <p>① 录像设置界面中把码流类型设置为复合流。</p>
 </li>
 <li>
  <p>② 在回放时，可以通过【回放】键来开启或关闭录像声音。</p>
 </li>
</ul>
<ol start="9">
 <li>目前支持哪几种备份设备？</li>
</ol>
<ul>
 <li>
  <p>U盘</p>
 </li>
 <li>
  <p>USB硬盘</p>
 </li>
 <li>
  <p>USB刻录机</p>
 </li>
 <li>
  <p>IDE刻录机或SATA刻录机</p>
 </li>
 <li>
  <p>SATA硬盘</p>
 </li>
</ul>
<ol start="10">
 <li>如何备份当天的所有录像文件？</li>
</ol>
<ul>
 <li>
  <p>① 进入回放操作界面，选择起始日期；</p>
 </li>
 <li>
  <p>② 插入备份设备；注：需要格式化FAT32格式；</p>
 </li>
 <li>
  <p>③ 选择存储设备；</p>
 </li>
 <li>
  <p>④ 移动活动框到备份当天录像按钮处，按【确认】键开始备份当天录像资料。</p>
 </li>
</ul>
<ol start="11">
 <li>如何备份指定条件的录像文件？</li>
</ol>
<p>备份指定某一通道、指定某一时间段内的录像资料。按以下步骤进行：</p>
<p>12.如何进行视频剪辑备份？</p>
<p>备份视频剪辑后的录像资料，按以下步骤进行：</p>
<ul>
 <li>
  <p>① 进入回放操作界面；</p>
 </li>
 <li>
  <p>② 插入备份设备；注：需要格式化FAT32格式</p>
 </li>
 <li>
  <p>③ 选择存储设备；</p>
 </li>
 <li>
  <p>④ 播放需要进行剪辑的录像文件；</p>
 </li>
 <li>
  <p>⑤ 按【编辑】键开始剪接，再按一次【编辑】键结束剪接；</p>
 </li>
 <li>
  <p>⑥ 若要剪辑多个片段（最多30个），重复步骤5；</p>
 </li>
 <li>
  <p>⑦ 退出回放，提示"复制的片段数为：X，是否进行复制？"，选择"是"开始复制。</p>
 </li>
 <li>
  <p>⑧ 确定完成复制备份。</p>
 </li>
</ul>
<ol start="13">
 <li>怎样播放备份的录像资料？</li>
</ol>
<p>采用专用海康专门的播放器播放；可到官方网站下载 <a href="http://www.hikvision.com">www.hikvision.com</a></p>
<ol start="14">
 <li>录像文件是否可以转成其它格式的文件？</li>
</ol>
<p>目前的录像文件都是H.264格式。</p>
<p>海康官方网站提供格式转换工具。</p>
<p>工具提供转换成标准AVI和WMV格式功能；</p>
<p>海康播放器也提供AVI转换功能。</p>
<ol start="15">
 <li>如何远程访问DVR？</li>
</ol>
<p>注 DVR/DVS的端口号不可与其他网络设备端口号冲突。如路由器的web管理端口号有的为80。这样的话可以通过修改路由器或者我们DVR/DVS的端口号来解决。</p>
<p>◆申请动态域名解析服务</p>
<p>推荐用希网免费DDNS服务 <a href="http://www.3322.org">www.3322.org</a> 或花生壳商业版DDNS服务（免费版不稳定，不推荐）,将申请好的DDNS服务的用户名和密码填入路由器的DDNS里面保存。</p>
<p>2）访问方式</p>
<p>① 通过IE来访问</p>
<p>◆IE浏览器菜单---工具--Internet选项--安全--自定义级别--ActiveX 控件和插件相关选项改为启用，确定保存。</p>
<p>◆如果DVR/DVS端是静态IP,则直接在地址栏中输入此IP并后加我们DVR/DVS的http端口号。如静态IP是192.168.1.2。设备 http端口号为81,则在地址栏中输入http：//192.168.1.2:81即可访问。如果DVR/DVS端是动态IP,则在地址栏中输入域名加http端口号。如
 <br>
 <a href="http://hikvision.3322.org:81%E3%80%82%E5%9B%9E%E8%BD%A6%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%87%BA%E7%8E%B0%E7%99%BB%E5%BD%95%E6%A1%86,%E7%84%B6%E5%90%8E%E8%BE%93%E5%85%A5DVR/DVS%E7%9A%84%E7%94%A8%E6%88%B7%E5%90%8D%E3%80%81%E5%AF%86%E7%A0%81%E5%8F%8A%E7%AB%AF%E5%8F%A3%E5%8F%B7(%E9%BB%98%E8%AE%A4%E4%B8%BA8000)%E3%80%82">http://hikvision.3322.org:81。回车就可以出现登录框,然后输入DVR/DVS的用户名、密码及端口号(默认为8000)。</a></p>
<p>② 通过客户端来访问（以海康威视网络视频监控软件v4.02为例）</p>
<p>◆进入配置-设备管理在左侧白框右键添加区域，自定义区域名称。</p>
<p>◆在区域上右键，点击添加设备，弹出添加设备对话框。自定义设备名称。</p>
<p>A. 如果DVR/DVS为固定IP，选择注册模式为普通IP，填写IP地址、设备用户名与密码（不是软件登录用户名密码）、通道数、设备端口号和设备类型（默认即可）。确定保存设备。</p>
<p>注：DVR/DVS主控版本为V2.0以上版本，可点击添加设备对话框左下角显示在线设备按钮，搜索局域网内同一网段在线设备。然后左键单击选定，输入设备用户名和密码即可。</p>
<p>B. 如果DVR/DVS使用动态域名解析，则注册模式选择普通域名，然后填写域名、设备用户名与密码（不是软件登录用户名密码）、通道数、设备端口号和设备类型（默认即可）。确定保存设备。</p>
<p>C. 如果DVR/DVS使用私有域名解析，设备名称要与设备本地一致，注册模式选择私有域名，然后填写设备用户名、密码、通道数、设备类型、DNS地址和设备序列号。</p>
<p>设备添加成功后，点击预览，双击通道即可。</p>
<ol start="16">
 <li>如何通过流媒体突破24路访问限制</li>
</ol>
<p>注：若该区域下有通道正在预览或录，该通道重新开始预览或录像设置才生效</p>
<ol start="17">
 <li>如何实现DVR/DVS控制云台？</li>
</ol>
<ul>
 <li>
  <p>① 将设备RS485的T+、T-线连接云台或球机解码器（RS485引脚定义见附录）；多球机连接建议使用总线结构。</p>
 </li>
 <li>
  <p>② 查看云台或快球说明书，查出产品解码器波特率、解码器类型（协议）和解码器地址三个参数。进入DVR/DVS主菜单-解码器界面，更波特率、协议、地址三个参数，使与前端匹配。</p>
 </li>
</ul>
<ol start="18">
 <li>如何设置巡航？</li>
</ol>
<p>备注：支持巡航的协议
 <br>
 E/YAAN 、DENNARD_DOME 、SAMSUNG、PANASONIC、CW860、TECHWIN</p>
<p>19.为什么DS-1002K、DS-1003K键盘不能控制DVR？</p>
<p>如果仍然无法控制，可通过长按"配置"，输入密码（默认为8888），进入 "系统设置"，选择"恢复默认设置"将键盘恢复出厂默认。然后重复步骤3。</p>
<br>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/OQK4GH2saA25WVlfi0bRFg">https://mp.weixin.qq.com/s/OQK4GH2saA25WVlfi0bRFg</a></p>]]></description><guid isPermaLink="false">/archives/d4c3815f-41fe-4395-920f-d84ee620f2e7</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 18 Nov 2024 06:44:00 GMT</pubDate></item><item><title><![CDATA[跳线、尾线区别在哪里？一张图弄清楚]]></title><link>https://wiki.dgfygk.com/archives/863bed7b-079a-4c30-826c-b46da152944e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%B7%B3%E7%BA%BF%E3%80%81%E5%B0%BE%E7%BA%BF%E5%8C%BA%E5%88%AB%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F%E4%B8%80%E5%BC%A0%E5%9B%BE%E5%BC%84%E6%B8%85%E6%A5%9A&amp;url=/archives/863bed7b-079a-4c30-826c-b46da152944e" width="1" height="1" alt="" style="opacity:0;">
<h4 id=""><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605193.png&amp;size=m" alt=""></strong></h4>
<p><strong>跳纤与尾纤有何区别？都各自如何应用？</strong> 用在哪里？这几天在vip群里有几位朋友提到。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605275.png&amp;size=m" alt=""></p>
<p>问到相关的内容，本期我们一起来总结这块。</p>
<p><strong>首先我们来看这张图：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605293.png&amp;size=m" alt=""></p>
<p>在终端盒中，他们的应用一目了然。</p>
<p>带着这张图，我们再来详解他们各自的<strong>用途与区别</strong>。</p>
<br>
<p>一、什么是光纤跳线？</p>
<p>光纤跳线又称光纤连接器，是指光缆<strong>两端都装上连接器插头</strong>，用来实现光路活动连接。与同轴电缆相似，只是没有网状屏蔽层，用来做从设备到光纤布线链路的跳接线**。一般用于光端机和终端盒之间的连接。**</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605338.png&amp;size=m" alt="">
 <br>
 一、光纤跳线分类
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605449.png&amp;size=m" alt=""> <strong>①FC型光纤跳线：</strong> 外部加强方式是采用金属套，紧固方式为螺丝扣。一般在ODF侧采用(配线架上用的最多)； <strong>②SC型光纤跳线：</strong> 连接GBIC光模块的连接器，它的外壳呈矩形，紧固方式是采用插拔销闩式，不须旋转。(路由器交换机收发器上用的最多)；
 <br>
 <strong>③ST型光纤跳线：</strong> 常用于ODF光纤配线架，外壳呈圆形，紧固方式为螺丝扣。(对于10Base-F连接来说，连接器通常是ST类型。常用于光纤配线架)；
 <br>
 <strong>④LC型光纤跳线：</strong> 连接SFP模块的连接器，它采用操作方便的模块化插孔(RJ)闩锁机理制成。(光模块常用)。 <strong>我们来看下下面各类跳线的图片，就一清二楚。</strong> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605461.png&amp;size=m" alt=""></p>
<br>
<p>二、什么是光缆尾纤
 <br>
 光缆尾纤，尾纤又叫猪尾线，只有<strong>一端有连接头</strong> ，而另一端是一根光缆纤芯的断头，通过<strong>熔接与其他光缆纤芯相连</strong> ，常出现在光纤终端盒内，<strong>用于连接光缆与光纤收发器（之间还用到耦合器、跳线等）。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605475.png&amp;size=m" alt=""></p>
<br>
<p>尾纤分为多模尾纤和单模尾纤： <strong>多模尾纤为橙色</strong> ，波长为850nm，传输距离为500m，用于短距离互联。 <strong>单模尾纤为黄色</strong> ，波长有两种，1310nm和1550nm，传输距离分别为10km和40km。</p>
<br>
<p>三、光纤跳线与尾纤有什么区别呢？</p>
<p>那尾纤与跳纤有什么区别呢？主要是在应用上。</p>
<p>**光纤尾纤只有一头是活动接头，**<strong>跳线两头都是活动接头</strong> ，接口有很多种，不同接口需要不同的耦合器，跳线一分为二还可以做为尾纤用，所以也可以理解尾纤为一个接头的跳线。</p>
<br>
<p><strong>对比下图就清楚：</strong></p>
<br>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605489.png&amp;size=m" alt=""></p>
<br>
<p>弱电 | 通信 | 标准 | 最新
 <br>
 公众号：弱电课堂 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605519.png&amp;size=m" alt=""></p>
<pre><code>  
   
    
     
      学习推荐：

      
VIP技术交流群：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247590782&amp;idx=1&amp;sn=8dae01671b4f144f4402a0745222b97c&amp;chksm=ce4d3733f93abe25fe822b6d021a28be86e80b1c69069565e3e005e399ecf16b7661c29dc43d&amp;scene=21#wechat_redirect

      
弱电方案及报价：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247585221&amp;idx=1&amp;sn=e679fcec1b61b7ea758642e83fdc047b&amp;chksm=ce4dd988f93a509ecd8710c39c32ff0b216a1a119e5ba86831bb3c43f96e93ebc630d3459930&amp;scene=21#wechat_redirect

      
弱电图纸大全：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247591326&amp;idx=1&amp;sn=8cfffbd2ebcf811f49a6a7a398f9ad80&amp;chksm=ce4d31d3f93ab8c50064f000c68bd8a55912a10fb1efe057598ca5e6593443f71cea647d1c13&amp;scene=21#wechat_redirect

     
    
   
  
</code></pre>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/6INRyAx_2iJO62dkKs-fzQ">https://mp.weixin.qq.com/s/6INRyAx_2iJO62dkKs-fzQ</a></p>]]></description><guid isPermaLink="false">/archives/863bed7b-079a-4c30-826c-b46da152944e</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731288605338.png&amp;size=m" type="image/jpeg" length="29562"/><category>IT运维</category><pubDate>Mon, 11 Nov 2024 02:14:38 GMT</pubDate></item><item><title><![CDATA[智能化施工常用工具总结]]></title><link>https://wiki.dgfygk.com/archives/b3f7dd33-6369-4b03-9547-55fd6859899b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%99%BA%E8%83%BD%E5%8C%96%E6%96%BD%E5%B7%A5%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7%E6%80%BB%E7%BB%93&amp;url=/archives/b3f7dd33-6369-4b03-9547-55fd6859899b" width="1" height="1" alt="" style="opacity:0;">
<h1>1 智能化施工工具</h1>
<p>一般维护运维建议手动工具套装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287885237.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287885509.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287885686.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287885871.png&amp;size=m" alt=""></p>
<p>施工现场需要的施工机械</p>
<ol>
 <li>
  <ol>
   <li>三级配电箱</li>
  </ol>
 </li>
</ol>
<p>2.</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886054.png&amp;size=m" alt=""></p>
<p>一级配电箱就是从变压器引入三相电源，地线，零线。二级配电箱是从一级配电箱电源线至临时用电（总包负责），智能化专业承包一般都是三级配电箱，三级配电箱是电器设备自身的控制柜。</p>
<p><strong>2</strong> <strong>．移动电缆盘</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886184.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886315.png&amp;size=m" alt=""></p>
<p>用于长距离的电源线盘接电，带过载保护，线盘长度有20米、30米、50米等型号。</p>
<p>2．<strong>电动螺丝刀</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886435.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886523.png&amp;size=m" alt=""></p>
<p>充电起子，既可当螺丝刀又能用作电钻，特殊情况下以充电电池作为动力电源。配合各式通用的六角工具头可以拆卸及锁入螺丝，钻洞等。可取代传统起子，拆卸锁入螺丝完全不费力，提高工效。</p>
<p>4．手电钻</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886602.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886715.png&amp;size=m" alt=""></p>
<p>手电钻钻孔，以及现在全部采用锂电池手电钻。</p>
<p>适用在金属型材、木材、塑料上钻孔，布线系统安装中经常用到的工具。手电钻由电动机、电源开关、电缆、钻孔头等组成。</p>
<p>5．冲击电钻、电锤</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287886944.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287887135.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287887352.png&amp;size=m" alt=""></p>
<p>现在一般采用锂电池二合一电钻电锤。</p>
<p>它是一种旋转带冲击的特殊用途的手提式电动工具，由电动机、减速箱、冲击头、辅助手柄、开关、电源线、插头及钻头夹等组成。</p>
<p>适用于在混凝土、预制板、瓷面砖、砖墙等建筑材料上进行钻孔或打洞。</p>
<p>电锤是以单相串激电动机为动力，适用于混凝土、岩石、砖石砌体等脆性材料上钻孔、开槽、凿毛等作业。</p>
<p>电锤钻孔速度快，而且成孔精度高，它与冲击电钻从功能看有相似的地方，但主要区分是电锤具有强烈的冲击力。</p>
<p>6．电镐</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287887542.png&amp;size=m" alt=""></p>
<p>电镐采用精确的重型电锤机械结构，具有极强的混凝土铲凿功能，比电锤功率大，更具冲击力、震动力，减震控制和可调控冲击能量，适合多种材料条件下的施工。</p>
<p>7．水钻机</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287887589.png&amp;size=m" alt=""></p>
<p>水钻是俗称，学名为带水源金刚石钻，简称金刚石钻。直径为Ф1mmФ25mm的孔可直接钻孔。工程：混凝土承重墙新开门洞，开窗。空调、油烟机、热水器等各种型号的预留孔钻孔。通风管道开洞，消防管道钻孔，线槽开孔，墙面拆除，楼板拆除，爆破钻孔，加固钻孔。Ф1mmФ25mm的孔可直接钻孔。根据需要也可连排式钻孔。可直接钻孔及各种规格大小的过墙孔工程。</p>
<p>8．线槽剪</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287887669.png&amp;size=m" alt=""></p>
<p>线槽剪是PVC线槽专用剪</p>
<p>9．台虎钳</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287887785.png&amp;size=m" alt=""></p>
<p>台虎钳是中小工件的锯割、凿削、锉削时的常用夹持工具之一。</p>
<p>10．梯子</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287887895.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888080.png&amp;size=m" alt=""></p>
<p>折叠梯和现场施工用的木梯。 。</p>
<p>11．管子台虎钳</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888220.png&amp;size=m" alt=""></p>
<p>管子台虎钳又名龙门钳，用于切割钢管、PVC塑料管等管形材料的夹持工具，管子台虎钳的钳座是固定在三脚铁板工作台上。</p>
<p>12．管子切割器</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888353.png&amp;size=m" alt=""></p>
<p>管子切割器又称管子割刀</p>
<p>13．管钳</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888486.png&amp;size=m" alt=""></p>
<p>管子钳又称管钳，用来安装钢管布线的工具，用它来装卸电线管上的管箍、锁紧螺母、管子活接头、防爆活接头等。</p>
<p>14．螺纹铰板</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888629.png&amp;size=m" alt=""></p>
<p>螺纹铰板又名管螺纹铰板，简称"铰板”。螺纹铰板是铰制钢管外螺纹的手动工具。俗称套丝。</p>
<p>15．简易弯管器</p>
<p>、<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888770.png&amp;size=m" alt=""></p>
<p>经热处理，具有超高强度，延长应力点的寿命，标记均铸造在弯管器两面，便于观察。锯齿形槽口，有效防止打滑，并最大程度实现省力。综合布线工程中常自制自用这种简易弯管器，用于25mm以下的管子弯管。</p>
<p>16．曲线锯</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888854.png&amp;size=m" alt=""></p>
<p>主要用于锯割直线和特殊的曲线切口的；能锯割木材、PVC和金属等材料。</p>
<p>17．角磨机</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287888974.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287889067.png&amp;size=m" alt=""></p>
<p>角磨机用于磨平金属槽、管切割后会留下锯齿形的毛边，防止毛边刺穿线缆的外套，保护线缆。</p>
<p>18．型材切割机</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287889155.png&amp;size=m" alt=""></p>
<p>由砂轮锯片、护罩，操纵手把、电动机、工件夹、工件夹调节手轮及底座、胶轮等组装而成、电动机一般是三相交流电动机。在布线管槽的安装中，常常需要加工角铁横担、割断管材。</p>
<p>19．台钻</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287889301.png&amp;size=m" alt=""></p>
<p>20．穿线器</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287889394.png&amp;size=m" alt=""></p>
<p>玻璃纤维穿线器，适用于管道较长的线缆敷设。</p>
<p>21．小型穿管器</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287889554.png&amp;size=m" alt=""></p>
<p>22．线轴支架</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287889683.png&amp;size=m" alt=""></p>
<p>23．剥线钳、压线工具</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890075.png&amp;size=m" alt=""></p>
<p>24．110打线工具</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890245.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890381.png&amp;size=m" alt=""></p>
<p>分为110单对打线工具和110五对打线工具，另外还有一种66型的，用于语音系统的交叉连接。</p>
<p>25．光纤剥离钳</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890449.png&amp;size=m" alt=""></p>
<p>26．光纤剪刀</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890567.png&amp;size=m" alt=""></p>
<p>27．光纤连接器压接钳</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890632.png&amp;size=m" alt=""></p>
<p>28．光纤接续盒</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890721.png&amp;size=m" alt=""></p>
<p>29．光纤熔接套装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287890846.png&amp;size=m" alt=""></p>
<p>30．网络测线仪</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891019.png&amp;size=m" alt=""></p>
<p>31．福禄克网络测试（太贵）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891101.png&amp;size=m" alt=""></p>
<p>33．巡线仪：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891236.png&amp;size=m" alt=""></p>
<p>34．数字万用表</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891389.png&amp;size=m" alt=""></p>
<p>35．接地电阻测量仪</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891551.png&amp;size=m" alt=""></p>
<p>36．对讲机</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891648.png&amp;size=m" alt=""></p>
<p>37．工程宝</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891708.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891797.png&amp;size=m" alt=""></p>
<p>38．场强仪</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287891933.png&amp;size=m" alt=""></p>
<p>用于测试有线电视信号强度</p>
<p>39．游标卡尺</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287892005.png&amp;size=m" alt=""></p>
<p>40．水平仪</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287892109.png&amp;size=m" alt=""></p>
<p>41.红外测距仪</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287892179.png&amp;size=m" alt=""></p>
<p>42.烙铁</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287892323.png&amp;size=m" alt=""></p>
<ol>
 <li>炮钉枪</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287892483.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/ce3EF7_xEwPO0cNQhn4aCQ</p>]]></description><guid isPermaLink="false">/archives/b3f7dd33-6369-4b03-9547-55fd6859899b</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287885237.png&amp;size=m" type="image/jpeg" length="69884"/><category>IT运维</category><pubDate>Mon, 11 Nov 2024 02:14:25 GMT</pubDate></item><item><title><![CDATA[裸纤、专线、SDH、MSTP、MSTP+、OTN、PTN弱电人必了解！]]></title><link>https://wiki.dgfygk.com/archives/baee646d-2a32-4ead-9e9e-7f391040514f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%A3%B8%E7%BA%A4%E3%80%81%E4%B8%93%E7%BA%BF%E3%80%81SDH%E3%80%81MSTP%E3%80%81MSTP%2B%E3%80%81OTN%E3%80%81PTN%E5%BC%B1%E7%94%B5%E4%BA%BA%E5%BF%85%E4%BA%86%E8%A7%A3%EF%BC%81&amp;url=/archives/baee646d-2a32-4ead-9e9e-7f391040514f" width="1" height="1" alt="" style="opacity:0;">
<p><strong>01 裸纤</strong> 裸纤也叫裸光纤，运营商提供一条纯净光纤线路，中间不经过任何交换机或路由器，只经过配线架或配线箱做光纤跳纤，可以理解成运营商仅仅提供一条物理线路。实际项目中，裸光纤应用较多，比如某大学两个校区，相隔大概20KM，租用运营商裸光纤实现两个校区互联。可以理解成通过一根很长的光纤连接两个校区，拓扑图也简单，直连即可，如图所示：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287900644.png&amp;size=m" alt=""></p>
<p>最早两个校区均有运营商互联网出口，采用两套认证系统，管理维护麻烦，后续升级改造，两个校区租用运营商裸光纤，将两个校区互联起来，相当于将老校区网络作为一个子模块，接入到新校区，与新校区有机融合到一起，实现统一运营和管理。</p>
<p>
 <br>
</p>
<p>租用运营商裸纤价格较高，一般按照公里收费，记得某项目租用20km裸纤，费用为20万/年，5年线路费用就是100万。有人可能会问，既然裸纤线路这么贵，为什么不自己拉一根光纤，连接两个校区，买光纤和工人布线施工的成本也用不了这么多呀！的确如此，但不是你想拉光纤就能拉的。国家法律明文规定，只有运营商、军队、市政等几个部门可以在公共区域破土施工，学校/医院/政府这类单位，在自己单位园区内部（也就是围墙内）随便怎么挖，没人会管，但到公共区域施工就不允许了，犯法！</p>
<p>
 <br>
</p>
<p>裸光纤应用场景还很多，比如教育城域网，会租用运营商裸光纤，实现教育局与辖区各学校互联，将网络出口统一到教育局，从而实现教育资源共享，统一审计等功能，某县教育网城域网拓扑如图，租用运营商裸光纤实现学校与教育局互联，最后通过教育局网络统一网络出口访问互联网。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287900754.png&amp;size=m" alt="">
 <br>
 另外，在平安城市/视频专网/雪亮工程等项目中，也经常租用运营商裸纤，实现前端摄像头视频流量回传，如下为公安部统计，全国视频专网链路类型，35%采用裸纤。（注：交警前端电子警察一般采用裸纤回传，有钱就任性，而平安城市摄像头采用PON链路较多）</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287900810.png&amp;size=m" alt=""></p>
<p><strong>02 专线</strong> 专线一般分三层专线和二层专线，三层专线一般是指MPLS V.P.N，这是CCIE RS方向考试的重点内容，说实话比较难，基本只在金融、电子政务等行业应用，一般人接触不上。我们日常说的专线基本指二层专线，二层专线相当于运营商给了你"一根线”，你两端连接PC，分别配置192.168.1.1/24和192.168.1.2/24，能直接ping通。效果看似跟裸纤差不多，但与裸纤的实质区别是：裸纤中间不经过任何路由器交换机设备，运营商给你的是真实的一根线，而专线中间经过运营商的各类路由器交换机设备，只是运营商给你模拟出来了一根线，让你感觉跟裸纤效果差不多，至于专线的底层技术，这个是CCIE ISP运营商方向研究的内容，大部分人无需搞得那么透。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287900855.png&amp;size=m" alt="">
 <br>
 如果租用运营商裸纤，只要光纤不被挖断，运营商内部交换机路由器等设备故障，不会影响业务。如果你租用的光纤被挖断了，那就没办法了，业务肯定受影响。如果租用运营商专线，运营商设备出问题，可能影响用户业务，反而光纤被挖断，不一定会影响业务，因为运营商骨干网采用环形设计，传统SDH和新的OTN底层都会有环网，所以论可靠性而言，专线可靠性稍高。笔者今年经历的一个大型雪亮工程项目，一个主数据中心，七个分中心，考虑使用什么链路互联。</p>
<p>最早规划使用裸纤互联，但后来考虑到可靠性，改成了租用运营商二层专线互联。当然，使用裸纤也有别的优势，比如带宽自主可控，如下图所示，你需要10G带宽，只需要在交换机上插10G光模块，需要40G带宽在交换机上插40G光模块，在交换机上插100G光模块，自然就有100G带宽。但是如果向运营商租用10G或40G专线，价格高得吓人！</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287900920.png&amp;size=m" alt="">
 <br>
 专线可以看成是运营商的一种产品，底层实现技术很多，有SDH、MSTP、PTN、OTN等。下面为大家介绍一下运营商网络常见术语。</p>
<p><strong>03 SDH和MSTP</strong>
 <br>
 要明白SDH，首先要说TDM，TDM是时分复用，就是将一个标准时长（1 秒）分成若干段小的时间段（8000），每一个小时间段（1/8000=125us）传输一路信号；SDH系统就是传统的电路调度，电路调度是以TDM为基础。</p>
<p>
 <br>
</p>
<p>早年的网络都是传输语音的，说白了电话网络的基础是TDM。在SDH大红大紫的时候，另一场战争以太网和ATM（不是取款机哟）大战中，以太网取得全面胜利，从而以太网大行其道，其中又以IP最为强势，导致今天很多业务侧都IP 化了，服务器、PC、摄像头都得配置IP地址吧！</p>
<p>
 <br>
</p>
<p>早年运营商骨干网SDH是大红人（运营商早期就是靠装电话挣钱），而园区网内部以太网是另一个大红人（上网数据通过以太网承载），能否合作一下？于是一拍即合，MSTP诞生，运营商骨干部分依旧使用SDH时分复用，但运营商甩给用户的链路接口采用园区网流行的以太网接口，也就是10M、100M、1G、10G这样的标准以太网接口，而不是155M、622M、2.5G这类POS/CPOS接口。
 <br>
 在合资公司MSTP中股份分配不太均匀：SDH 占股70%，以太网占股20%，其它包括ATM占股10%，掌权的还是SDH，内核还是TDM。以太网和ATM因为股权问题，都没有拿出像样的东西，只是须有其表（提供相应接口而已）毕竟早期网络，还是语音为主，数据为辅，但现在完全颠倒，以太网当然不干了，也就有了后来的WDM、OTN、IP-RAN。（就像一家公司，最牛逼的某销售贡献了90%的业绩，他还不出去自己创业，他是傻么？）</p>
<p><strong>04 波分WDM</strong> 随着互联网的大力普及，电脑、手机、电视等终端都能上网了，带宽的需求急剧增加，电信运营商们赚钱的机会来了，但挑战也来了，以前1*155M可以供好上千人打电话，现在人们需要打电话同时还要上网，家庭宽带都300M光纤入户了，带宽需求增长和现网资源出现矛盾，承载语音的TDM骨干网已经完全不能满足海量音视频传输要求。要解决这个矛盾，专家们思考良久，某天看到二环路高架桥，恍然大悟，如果将网络传输也划分多个通道，也能提升传输性能。</p>
<p>
 <br>
</p>
<p>于是波分产生，波分WDM 就是将多个波道的信号放到同一条光纤中进行传送。WDM得到重用后，各地纷纷仿效，现在的WDM不仅在城市主干道里使用（城域波分），还用在跨市、跨省道路上（长途波分）。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287900987.png&amp;size=m" alt="">
 <br>
 裸光纤+波分的效果对比如图使用传统裸光纤，目前商用较多的是100G光模块，那么链路最大100G。裸光纤两端部署波分设备，带宽惊人，线路带宽直接可以到4T甚至更高，单条光纤目前实验室最高波分速率是36T。当然，波分设备特别贵，不是一般单位买得起，波分设备厂商主要有华为、烽火等，客户为运营商、公安、BAT这类单位居多。</p>
<p><strong>05 OTN</strong> <strong>OTN</strong>（OpticalTransportNetwork，光传送网）</p>
<p>
 <br>
</p>
<p>WDM波分技术优势是带宽大，大得惊人。当然也存在一些问题，最突出的是实时流量监测和重点业务保障没有很好的措施。</p>
<p>
 <br>
</p>
<p>SDH 笑道：小样儿，容量那么大，你监测和管理上还是有问题吧？</p>
<p>
 <br>
</p>
<p>WDM 回应道：我容量确实比你大得多，但管理这方面的确没你们做得好；</p>
<p>
 <br>
</p>
<p>于是他们握手言欢，优势互补，一个全新的制度诞生了—OTN。所以简单的说：OTN=WDM+SDH</p>
<p>
 <br>
</p>
<p>我们对以前的红人SDH在江湖的发展做了详细的描述，现在的SDH也只相当于OTN掌门下的一个堂主而已了，那么另一位红人以太网它现在发展的如何呢？</p>
<p><strong>06 MSTP+</strong>
 <br>
 由于先前MSTP成立时，股权分配不均，有很多遗留问题，以太网仅占20%，导致现在以太网严重不满意，毕竟现在网络上IP流量占了90%以上，为了安抚这个"销量"占公司90%的"销售”，SDH 集团研究后推出MSTP+（也叫Hybrid MSTP），50/50股权分配，进一步提升以太网在公司的地位，也不为一种好的补偿措施。</p>
<p><strong>07 IP-RAN和PTN</strong>
 <br>
 以太网现在声势越来越大，再加上又有MPLS 助阵，逐渐有了可以抗衡SDH的实力，面对MSTP+50%的股份分配，以太网并不高兴，发誓要有所改观……</p>
<p>为了对抗SDH阵营，以太网大力发展自己的势力，走农村包围城市的策略，先将末端IP化（终端PC、摄像头、打印机都得配IP）。同时建立以IP+以太网主导的骨干网，相当于自己开公司，创业了。以太网阵营，自由散漫惯了，现在出现了两种大的分歧：</p>
<p>
 <br>
</p>
<p>第一种：认为我们自己成立的运输公司不让SDH的客户（TDM业务）上车，如果一定要进来，必须改头换面-伪装（仿真），同时我们没有时间上的保证（无时间同步），我们纯粹为我们以太网服务，我们的公司名叫IP-RAN；</p>
<p>
 <br>
</p>
<p>第二种：认为我们应该吸收一些SDH的客户，SDH经营了这么多年，它的客户还是很多的（还有很多TDM业务需求），同样进来后还是要改头换面-伪装（仿真），然后再我们的帮派里活动，出帮派后再去掉伪装还原成自己原来的模样，这个公司取名叫PTN。无论哪种方式，伪装-易容术总少不了，随后就开发了PWE3 易容术（CCIE ISP运营商方向重点讲的内容，一般人了解即可）。</p>
<p><strong>08 总结</strong> MSTP+（Hybrid MSTP）可以看做是SDH向以太网的妥协方案，不得已而为之。随着相互学习融合，IP-RAN和PTN现在已趋于一致，差别不大了，所谓：分久必合合久必分！它们是IP/以太网向SDH发起的全面挑战，现在看来它们是胜利了。</p>
<p>
 <br>
</p>
<p>说到这，IP-RAN/PTN战胜了SDH，也就是数通战胜了传统通信，运营商骨干网完全IP化，这也算我们网络人的胜利，所以我一直觉得网络还是有前途+钱途的。在你为待遇低而埋怨的时候，请自我检讨一下，你到底会多少呢？</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/R55-Qq0katUssTaGVPD4pw</p>]]></description><guid isPermaLink="false">/archives/baee646d-2a32-4ead-9e9e-7f391040514f</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1731287900644.png&amp;size=m" type="image/jpeg" length="288859"/><category>IT运维</category><pubDate>Mon, 11 Nov 2024 02:14:01 GMT</pubDate></item><item><title><![CDATA[争议 _ 在虚拟化平台上搭建 RAC，RDM 裸磁盘映射和 VMFS 哪种方式更好？]]></title><link>https://wiki.dgfygk.com/archives/ec923829-414b-4cbc-a837-c474eb4617a3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BA%89%E8%AE%AE%20_%20%E5%9C%A8%E8%99%9A%E6%8B%9F%E5%8C%96%E5%B9%B3%E5%8F%B0%E4%B8%8A%E6%90%AD%E5%BB%BA%20RAC%EF%BC%8CRDM%20%E8%A3%B8%E7%A3%81%E7%9B%98%E6%98%A0%E5%B0%84%E5%92%8C%20VMFS%20%E5%93%AA%E7%A7%8D%E6%96%B9%E5%BC%8F%E6%9B%B4%E5%A5%BD%EF%BC%9F&amp;url=/archives/ec923829-414b-4cbc-a837-c474eb4617a3" width="1" height="1" alt="" style="opacity:0;">
<p><em>来自twt社区同行交流，欢迎更多同行参与交流</em>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528810946.png&amp;size=m" alt=""> <strong>存储使用RDM裸磁盘映射方式还是使用VMFS方式，各自的优缺点有哪些？</strong></p>
<p>若使用共享存储的虚拟化平台上搭建数据库rac，存储使用RDM裸磁盘映射方式还是使用VMFS方式，各自的优缺点有哪些</p>
<p>问题来自社区会员@wangdd 河北省人民医院 系统工程师，下文来自twt社区众多同行实践经验分享，欢迎大家参与交流，各抒己见。
 <br>
 * “争议"栏目内容来自同行分享的一手体验和观察，仅代表个人观点</p>
<p>
 <br>
</p>
<p>@赵海 技术经理：</p>
<p>RDM: 裸磁盘方式，一般用于Oracle RAC的虚拟机场景。</p>
<p>VMFS：虚拟文件系统方式，可用于大部分的虚拟机磁盘场景。</p>
<p>在VMware的环境当中没什么要支持RDM方式呢？其实最主要的目的在于支持多虚拟机磁盘共享，而且对读写性能有要求的场景，例如Oracle RAC。如果用了这种方式，那么VMware本身的一些特性也就没了，比如说虚拟机的漂移，比如说对于磁盘IO的控制参数等。另外给云环境的运维带来很多麻烦，比如说批量的迁移碍于某台服务器上的RDM而没有办法批量执行迁移等等类似的事情。</p>
<p>一般情况下，我们在VMware平台上用虚拟机实现的一些业务场景，其实更多的还是考虑其灵活性及高可用性。如果从性能角度来考虑，就算我们用了RDM的方式，其实性能瓶颈还是有的，在于VM。所以如果是重量型OLTP业务，我们还是考虑物理机做资源池来支撑RAC的方式比较合理，哪怕是通过12C的方式去实现物理机组成的DB资源池。如果实在想把数据库搬运到VMware平台上的VM里，建议你选择一些不重要的，负载不是很高的，数据量不是很大的这类数据库，也不用做什么RAC。</p>
<p>如果是其他的业务场景，就更没必要用RDM方式去代替VMFS了。</p>
<p>
 <br>
</p>
<p>@张鹏 中国金融电子化公司-中小金融机构灾备服务中心 高级技术主管：</p>
<p>有人认为RDM是直接从SAN上的LUN到映射到虚拟机上面，在一定程度可以提高性能，进而可以部署相对高IO的Database服务。但是实际上差别并不大。</p>
<p>而实际上RDM还是有一些局限的。</p>
<p>有两种类型的RDM：虚拟兼容模式RDM和物理兼容模式RDM。尤其是物理兼容模式RDM有一些相当重要的局限：例如：不能使用VMware快照 ，不支持VCB，因为VCB需要VMware快照 ，使用物理模式RDM不能克隆虚拟机， 在模板里使用物理模式RDM不能转换虚拟机 ， 如果迁移包括复制磁盘，使用物理模式RDM不能迁移虚拟机 ，物理模式RDM不能使用VMotion功能 虚拟模式RDM能解决这样一些问题，允许将原始LUN看作虚拟磁盘，并能使用像VMotion、快照和克隆这样的功能。如果需要使用RDM，大多数情况下，虚拟模式RDM比较受欢迎。虚拟模式RDM能用于跨物理主机的V2V集群。不过注意，跨物理主机的P2V迁移需要使用物理模式RDM。</p>
<p>
 <br>
</p>
<p>@zyp8365 广东省中医院 数据库管理员：</p>
<p>首先，个人不太建议在虚拟化平台搭建数据库rac。因为虚拟化平台是为充分利用服务器的计算和内存资源而提供的一种一虚多的云计算解决方案，能很好解决信息IT的烟囱式发展方式，数据库RAC最主要是实现如下两个目标:双机高可靠和业务负载均衡。虚拟化平台本身就是多台服务器组成的，本身就有HA的机制，另外虚拟化平台本身的虚机保护的方式有很多，如VMware的Ft(虽然有CPU限制)，也有连续数据保护的机制如RP4VM，以及很多备份还原的途径和方式能实现高可靠，不需要依赖RAC的方式，如果想实实在在做容灾应该考虑dataguard的方式。</p>
<p>另外再说业务负载均衡，也更不应该在虚拟化平台来实现，RAC多虚一和虚拟化平台一虚多本身就是矛盾的，所以不建议这样操作，两台物理机去做更好。</p>
<p>而且还有一个比较重要的原因是Oracle数据库的RAC虽然理论上可以通过虚拟化平台来实现，但是不建议这样做，因为经过了一层虚拟化层，如果出问题不好排查，而且信息流非最优。回到楼主的问题，其实RDM和VMFS最重要的区别就是RDM是直通，而VMFS是经过虚拟化层，用的虚拟化平台的文件系统格式。</p>
<p>优缺点就是RDM的方式，数据直接到磁盘，路径最短速度更快，但是因为没有经过虚拟化平台，管理麻烦，用不到平台的一些高级功能，但是VMFS就是刚好相反，数据要通过虚拟化平台的解析后才到后端磁盘，所以路径更长速度相对慢，但是管理简单方便，能使用虚拟化平台对磁盘的一些高级功能。</p>
<p>
 <br>
</p>
<p>@wuliangy 浙江省肿瘤医院 信息工程部工程师：</p>
<p>个人支持对虚拟化层上搭建RAC这种新方式进行探索。</p>
<p>在2019年以前，数据库rac的普遍搭建方式是物理机+光交+存储（双活）的方式，原因有几方面：</p>
<p>1、Oracle并不承认虚拟化上搭建的RAC，一旦出现问题，Oracle不提供服务</p>
<p>2、该模式已经过时间的检验，稳定可靠</p>
<p>3、虚拟化上搭建毕竟经过了一层虚拟化层，开销是个疑问</p>
<p>4、许多医疗机构可能使用的是小型机RAC，对于X86的信任度并没有这么的高</p>
<p>但是在进入2020年后，至少在浙江区域有越来越多的机构开始尝试虚拟化上搭建数据库了，甚至采用RAC over vSphere来做，原因如下：</p>
<p>1、2019年9月开始Oracle和VMware互认，400可以直接形成case了，从商务上得到了原厂的认可。</p>
<p>2、X86主机的性能、稳定性已经提高到了一个较高的级别，单机的计算性能就完全能承载大型机构的数据库压力。</p>
<p>3、许多大型医疗机构在HIS DB之外还有诸如LIS、EMR、PACS、集成平台等核心业务数据库，若数据库采用虚拟化部署则可以更高效利用硬件资源。</p>
<p>4、全闪存储，全闪超融合硬件的价格下降带来了很多的可能性，实测虚机RAC的IO和延时完全满足要求。</p>
<p>而回到主问题，RDM和VMFS的方式，对于虚拟化来说，一种是裸设备映射，存储路径短，更高效，一种是虚拟化的文件格式。但是如今的全闪存储在性能上已经达到较高水平，实测下来已无大区别，如若想要使用VM的特性的，那建议都用VMFS挂载，或者折中，数据库虚机使用RDM，最大程度利用性能，剩余存储资源则VMFS丢给虚拟化，高效利用。</p>
<p>
 <br>
</p>
<p>@cpc1989 某保险公司 存储工程师：</p>
<p>个人看法是：大多数场景下，虚拟化平台的存储接入采用RDM和VMFS两种方式并没有明显的差异，但是VMFS与虚拟化平台更契合，而RDM也主要用于极个别需要做SAN裸盘映射的场景，而这种使用场景，本身的稳定性也欠佳。</p>
<p>在数据库RAC的部署的场景，从技术架构角度来看，物理机更适宜用于部署数据库rac，如果服务器计算资源富余，完全可以通过整合多个数据库的方式来达到资源有效利用的目的；虚拟化平台部署rac的方案，不契合虚拟化集群本身的高可用特性，从实践经验来看，在负载较高的情况下，系统稳定性还会下降 。如果负载不高，系统可用性要求不高，可以在虚拟化平台上部署单机+数据库备份或者单机+数据库复制，其他情况更推荐物理机部署方式。</p>
<p>
 <br>
</p>
<p>@王巧雷 sino-bridge 系统工程师：</p>
<p>VMware目前支持vmfs和rdm两种通用模式，RDM还分虚拟兼容和物理兼容模式。</p>
<p>这几种模式的特点如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811028.png&amp;size=m" alt="">
 <br>
 (点击图片可放大)</p>
<p>基于上面的说明以及一些项目经验，个人观点：</p>
<ol>
 <li>
  <p>VMFS偏向VMware的角度，其可以覆盖大多数的场景，尤其是当前硬件性能过剩的情况下，VMFS方式可以最大化的利用VMware的所有高级特性，比如快照、vmotion等，这些可以带来很大的便利。</p>
 </li>
 <li>
  <p>RDM更偏向存储的角度，也有其相应的适用场景，比如需要直接和存储交换的SAN管理主机，或者有特殊的SAN需求；需要使用基于存储的高级特性（快照、复制、克隆等）；使用了npiv功能的虚拟化环境等等。</p>
 </li>
</ol>
<p>当然也有人说都这样了不如直接使用物理机，但是有些应用对计算资源没那么大，虚拟化就比较低成本了，也更好管理。对于计算和存储需求都特别高的应用，比如Oracle RAC，个人还是比较倾向使用物理机。还是看需求吧。</p>
<p>
 <br>
</p>
<p>@hacmp 四川华信富恒 系统工程师：</p>
<p>我觉得看业务，实际上目前在虚拟化平台上运行的绝大部分都是轻量级的应用，就算是有不少数据库，IO也不会很高，VMFS完全可以满足，当然要合理规划好VMFS，数据库如果是核心的那么还是建议使用物理机来跑RAC，RDM确实有点尴尬，虽然相对VMFS来说性能会好，但是我接触到的用RDM的。</p>
<p>不多，做过存放图片的业务系统，几十TB，使用了RDM。</p>
<p>
 <br>
</p>
<p>@刘东 东软集团 IT技术咨询顾问：</p>
<p>使用共享存储搭建的虚拟机化平台上部署数据库RAC，从技术角度上看，完全没有问题。但是从性能和管理方面来讲，不推荐这样做。如果一定使用虚拟化平台部署Oracle，建议只部署单机或者Oracle DG即可，因为Oracle RAC是一种群集和高可用技术，而虚拟化平台本身就具备HA，VMtion等高可用技术，有些冲突。</p>
<p>虽然虚拟化平台的高可用技术做不到RAC的那种故障无感知切换，但是如果使用虚拟化平台实现RAC，其实效果也并不好。</p>
<p>首先，从性能上讲，中间多了一个虚拟化层，无法全部发挥物理机的全部硬件性能，包括CPU性能和I/O性能都会受影响。其次，如果选择的两台虚拟机发生了漂移，同时在一台物理服务器上，这样部署的Oracle RAC并没有任何意义。因为一旦这个物理机发生故障，两个 RAC 节点会同时宕机，所以还不如部署单机，然后利用虚拟化平台的高可用技术提供服务，或者直接将RAC 部署在物理服务器上。</p>
<p>再说 RDM裸磁盘和VMFS。如果一定坚持使用虚拟化平台做RAC，个人建议 VMFS或虚拟RDM是一个比较好的选择，因为可以使用虚拟化平台的一个磁盘管理特性，例如快照和迁移等。如果直接使用物理RDM还不如不用虚拟化平台，直接使用物理机+SAN存储就好了，完全没有在虚拟化平台上部署的必要。既然在虚拟化平台上部署RAC就要充分利用虚拟化平台的特性。</p>
<p>另外，有条件的话最好将部署RAC的虚拟化平台硬件配置高一些，例如配置SSD硬盘和万兆核心网络，因为RAC和单机部署模式不同，RAC的群集节点之间会产生大量的数据交换和同步，如果没有万兆网络和SSD做支撑，很容易出问题，而且还会影响部署在虚拟化平台上的其他业务系统。这一点需要特别注意。</p>
<blockquote>
 <p><strong>欢迎点击文末</strong> <strong>阅读原文</strong><strong>到社区阅读和讨论交流，发表您的看法</strong></p>
 <p><strong>觉得本文有用，请</strong> <strong>转发、点赞</strong><strong>或点击</strong> <strong>在看</strong><strong>，让更多同行看到</strong></p>
</blockquote>
<p>
 <br>
</p>
<p>资料/文章推荐：</p>
<ul>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650648175&amp;idx=1&amp;sn=584872aecf1765a99dca1737e5c6cef1&amp;chksm=bef9d0e9898e59ff450bcb6c130864ba391c16be8ab23527dd83c63935df381304d86693dca0&amp;scene=21#wechat_redirect"></a><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646107&amp;idx=1&amp;sn=53c3a0961c9df959795185e59a492ae9&amp;chksm=bef9d8dd898e51cb7362d6c0aae769480dbf30b3d02fafd9dc2dd16d86faa9ee1874525a4b8b&amp;scene=21#wechat_redirect">干货！存储相关精选文章 68 篇 \| 周末送资料</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646107&amp;idx=1&amp;sn=53c3a0961c9df959795185e59a492ae9&amp;chksm=bef9d8dd898e51cb7362d6c0aae769480dbf30b3d02fafd9dc2dd16d86faa9ee1874525a4b8b&amp;scene=21#wechat_redirect"></a><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633481&amp;idx=2&amp;sn=6e2ae2aaf5b72359042f50b1b380fa11&amp;chksm=bef90b8f898e8299c50b338d955446ef002b6c5bfdced5185d307a1b067af242490b581b8ee1&amp;scene=21#wechat_redirect">Oracle RAC 资料大全</a></p>
 </li>
</ul>
<p>
 <br>
</p>
<p>欢迎关注社区以下 <strong>“存储"技术主题</strong>，将会不断更新优质资料、文章。地址：</p>
<p>https://www.talkwithtrend.com/Channel/179</p>
<p>
 <br>
</p>
<p><strong>下载 twt 社区客户端 APP</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811167.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811193.png&amp;size=m" alt=""></p>
<p>长按识别二维码即可下载</p>
<p>或到应用商店搜索"twt”</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811221.png&amp;size=m" alt=""></p>
<p>*本公众号所发布内容仅代表作者观点，不代表社区立场</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650649487&amp;idx=1&amp;sn=104382f4d7329f77bd77fd40b56fd56c&amp;chksm=bef9cd09898e441fc1d9e3012bd19aa449f91f6690b0f514a7e90ef222cd3fcca4319af1207e&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/ec923829-414b-4cbc-a837-c474eb4617a3</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 01:27:05 GMT</pubDate></item><item><title><![CDATA[Oracle RAC_ADG_OGG 双活方案比较 _ 资料]]></title><link>https://wiki.dgfygk.com/archives/df358b6a-df74-4e12-82c2-940496b52c2e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Oracle%20RAC_ADG_OGG%20%E5%8F%8C%E6%B4%BB%E6%96%B9%E6%A1%88%E6%AF%94%E8%BE%83%20_%20%E8%B5%84%E6%96%99&amp;url=/archives/df358b6a-df74-4e12-82c2-940496b52c2e" width="1" height="1" alt="" style="opacity:0;">
<p><strong>一、基于 Oracle 数据库技术的容灾方案都有哪些？</strong></p>
<p>容灾向来是以RPO/RTO来定义其级别，所谓的双活只是业内对某种较高容灾级别的架构的俗称，根据不同的角度对其理解也有所偏差。基于此，暂且认为只要是两个数据中心同时能提供业务服务的就认为是所谓的双活。在这个前提条件下，从Oracle数据库本身的技术来讲，有这么几种方案。</p>
<p>■ 基于跨中心实现的远距离RAC架构。</p>
<p>1）基于ASM冗余设计实现。</p>
<p>2）基于存储集群化之后的分布式存储卷实现。</p>
<p>■ 基于Oracle ADG/OGG 实现的主备库架构。</p>
<p>我们先从大的方案比较：</p>
<p>1)从RPO角度来看，RAC方案可以做到理论上的绝对同步。ADG可以做到近似同步，但是一般用在异步场合。</p>
<p>2)从RTO角度来看，RAC方案可以做到理论上的秒级自动故障转移。ADG一般需要人工去实现备库切换，而且需要应用改变连接IP地址，重新启动。</p>
<p>3)从风险角度来看，RAC方案一旦实现距离拉伸，最大的风险在于远距离光纤条件下的节点之间的数据交互。而ADG方案就没有该风险存在。</p>
<p>4)从方案的复杂度来看，RAC方案理论上需要第三点的仲裁，需要双中心二层打通等复杂环境条件。而ADG和OGG方案只需要网络三层可达即可。</p>
<p>5)从投资成本来看，RAC方案实现距离的拉伸之后，需要的环境成本（网络条件、仲裁条件）等都需要较高的成本。ADG和OGG方案没有这些成本。</p>
<p>由此可以看出，实际上从容灾角度考虑（RTO/RPO）,那么RAC方案一定是比ADG方案能实现RTO和RPO的更高目标，但是从成本和风险角度考虑，ADG又是最佳的选择。</p>
<p>那么撇开成本和风险，只考虑容灾目标的话，我们再来比较一下对于RAC方案的两种不同存储架构的差异：</p>
<p>1)首先是实现难度及投资比较,ASM冗余设计架构的复杂度在于 ASM 层的设计。Oracle RAC 实例节点看到的共享盘是基于双中心存储实现的镜像策略，所有IO的读写分发是由ASM本身的冗余算法规则来决定的，DBA不仅仅要根据磁盘情况来设计合理的 Failure Group，而且需要结合第三方站点的网络存储卷来合理设计仲裁磁盘组的分配。更重要的是需要结合实际的网络环境指标（延时、稳定性等）进行复杂的性能、稳定性、灾难测试等来调整ASM的一些IO参数。基于分布式存储卷设计架构的复杂度在于整体架构的复杂度。</p>
<p>例如仲裁一致性问题，是指双中心之间的存储集群和数据库RAC集群的仲裁结果是否能保证一致性。存储集群是靠仲裁站点分别于两个站点之间的网络连通性来判定站点故障。而数据库集群是通过以太网心跳和OCR仲裁盘来做数据库仲裁。而数据库的OCR仲裁盘是存储集群提供的分布式共享卷。二者仲裁时的一致性如何保障是非常重要的一个问题。假设在发生站点级别故障时，数据库集群首先根据网络故障触发仲裁，判定站点A的节点存活。而存储随后再发生存储集群的仲裁，这个时候如果根据仲裁站点判定的结果恰恰仲裁委站点B的节点存活。那么数据库集群整体就会宕掉，这对于业务来讲就是一个灾难。</p>
<p>2)从实现的基本条件来看，两种架构的实现都会依赖双中心的二层打通。双中心的波分设备、以太转换设备、光纤链路租用就是必不可少的条件了。包括其购置成本和日后的运维成本等。这是非常可观的一项成本预算。从存储层的架构组成来看，ASM冗余设计架构不需要存储层增加任何其他设备成本及运维成本。但是分布式存储卷架构需要依赖存储层的虚拟化网关产品来实现存储虚拟化集群，无疑这需要增加相应的购置成本和相应的运维成本。尤其注意存储集群产品是否有容量许可成本问题。从第三点的仲裁站点成本来看，两种方案都需要第三点的仲裁，区别在于ASM冗余设计架构需要的是NAS存储，而分布式存储卷架构需要的基于以太网的计算资源来配置仲裁虚拟机。投入成本没有什么差异。</p>
<p>3)从Oracle运维成本来看，ASM冗余设计架构对DBA的要求非常苛刻，需要DBA不仅仅能够深知其中的原理，而且需要对性能的分析有较深的造诣，从而保障在复杂的双中心联动环境下各种复杂情况下的性能及稳定性变动有快速和准确的判断和处理能力。分布式存储卷架构对DBA的要求没有特殊的苛刻要求但是需要增加对存储集群的专业维护成本。</p>
<p>从银行业的角度来看，行业看中的更多的是RPO和RTO以及风险度本身这几个点。</p>
<p>从RPO和RTO角度来选的话，那么方案一定是RAC的方案。但是从风险角度来考虑的话，那么ADG会是比较好的选择。OGG主要用于异构平台之间的数据传输。但是这几种方案并不是只能选择其中一种而舍弃其他的方案。方案各有优劣，为了尽可能达到整体最优，可以考虑方案的组合。</p>
<p>比如说同城实现RAC、异地实现ADG。或者在RAC基础之上再增加一个ADG。成本上没有太大的改变，但是保险系数上却增加了很多。</p>
<p>OGG可以考虑到特殊业务场合，比如说为了搭建数据平台实现不同数据库数据的整合等。</p>
<p>
 <br>
</p>
<h2><strong>二、ADG 和 OGG 之比较</strong></h2>
<p>ADG 同构平台数据同步，OGG可以异构平台数据同步。</p>
<p>ADG 用在容灾场合，可以同城、可以异地。OGG一般用在异构平台迁移场合。</p>
<p>ADG 相对比较稳定，OGG 相比而言问题多一些。</p>
<p>ADG 架构可以灵活组合，OGG 架构相对会单一。</p>
<p>ADG 可以通过快照方式保留当前时刻点数据，OGG不能做到。</p>
<p>ADG：五级容灾目标场合下的数据库主备切换架构。</p>
<p>OGG：数据平台的建设或者是异构数据库之间的集中整合或者是同步。</p>
<p>在金融行业，常见的容灾架构为ADG，尤其是异地灾备。也有部分较高要求的采用 RAC + ADG，这里的RAC有的是基于存储集群虚拟出来的分布式卷之上做的RAC，有的是通过ASM冗余设计本身实现的。OGG在重大变更需要异构数据库同步数据的场合下或者是金融行业的数据集中平台上采用。</p>
<p>ADG，最常用的同城，异地灾备解决方案，物理级备份，备机不可写，传输数据为所有redo日志的更改，数据量稍大，不过从以往的使用经验来看，也不太会影响网络，除非应用对网络有很苛刻的要求，即使有，也可以通过vlan或者路由或者多网卡的方法特别建立网络通道，主备库完全一致，缺点是必须全库备份。OGG，DSG这两个是一个类型的，逻辑备份，主要采用特有的技术从联机日志中抽取更改项应用到备库，主备库为两个库，可以全库同步也可以同步单张表或数张表，同步速度较快，传输数据量很少，DML操作和DDL操作均支持。</p>
<p>
 <br>
</p>
<h2><strong>三、基于Oracle RAC/ADG 双活方案实施的难点和关键点是什么？</strong></h2>
<p>1）针对分布式存储卷架构的仲裁一致性问题</p>
<p>在这个问题上，风险发生的引发点有两个：数据库和集群的仲裁触发以及仲裁过程的时间顺序发生紊乱；资源被1:1割裂之后的默认仲裁策略不一致。也就是说，只要控制这两个引发点，那么这个问题从理论上也就避免了。对于第一个引发点来讲，实际上存储集群的默认仲裁触发时间会是15秒左右，而数据库仲裁触发的控制参数由misscount这个参数来决定，所以只要我们将misscount这个参数调整到45秒之后，也就是说理论上绝对保障存储集群仲裁在前，而数据库仲裁在后，那么第一个引发点就没有了。对于第二个引发点来讲，假设两站点节点资源对等，仲裁选票同样对等的情况下，存储集群会有一个默认的Winner策略，同样在这种情况下数据库集群也有一个默认仲裁策略：选择实例号小的集群存活。只要我们保证这两个策略结果的一致性，那么第二个引发点也就不存在了。</p>
<p>2）链路稳定状况不可控</p>
<p>这个问题是两种架构都面临的问题。主要表现为两个方面：链路稳定状况不可控；延时指标不可控。因为双中心之间的链路是通过租用运营商的裸光纤链路实现的，那么这其中会经历很多的中继设备及节点。无论从管理上还是从技术把控上都是金融企业自身不可控制的因素。假设双中心间链路延时指标不稳定，也就是说数据库节点之间私网传输的延时会经常出现长延时情况，这势必导致这种延时会加倍放大到数据库节点之间的读写热点竞争上。由于数据库集群之间的数据传输量非常大（缓存、锁、心跳等），在读写热点相对突出的业务上，轻则导致数据库读写性能灾难，重则导致数据库节点直接处于僵死状态。另外，链路的不稳定会导致存储链路频繁切换，甚至会导致集群仲裁频繁发生，这对于业务连续性更是一个灾难。</p>
<p>对于这个问题来讲，就目前金融行业的传统数据架构来讲，并没有一个十足的解决方案。我们只能通过以下措施来减少这种问题带给我们的风险。</p>
<p>i、业务层面需要进行拆分重组：按照IO特点进行合理拆分，将读写业务尽量分布于不同节点上，减少节点间的锁竞争。按照业务将数据库表进行分区，避免在数据库写上的数据热点块儿。例如，对于银行核心系统来讲，尤其是要将批量业务和联机业务区分对待，批量业务的热点以及数据量非常之巨大，所以一定要将批量业务的数据库读写放在单边实现。对于联机业务来讲可以根据热点状况以及链路质量评测结果可以尝试实现双中心同时读写，但是本文建议对于这种重量级的业务还是要从业务层尽量实现应用上的读写分离，或者在应用层双中心部署而在数据库层将数据引到单边来做。</p>
<p>ii、双中心间通讯的整体控制，具体包括对通讯带宽的优先级管理、对通讯的实时监控和控制、对跨中心数据传输的严格策略把控。例如：优先保障存储和数据库通讯的优先级和带宽，严格的规则算法和优先级限定VMOTION、DRS等行为的跨中心随意性，从LTM负载分发上尽可能保障正常情况下纵向IO的单中心效率策略，故障情况下保障跨中心访问的科学性。DWDM上设置双中心间通讯带宽的逻辑隔离以及实时可控。</p>
<p>3）存储网络故障泛滥</p>
<p>这是两种架构都会面临的问题，只是ASM冗余设计架构可能性相对高一些。</p>
<p>如果我们把两个中心的SAN环境整合为一张大网，物理上没有任何隔离的大网，那么可能会因为局部的存储网络故障而波及到整个存储网络。尽管我们通过SAN交换机上的逻辑隔离能够解决大部分的安全问题，但是这样的风险毕竟还是存在的。</p>
<p>所以我们可以通过对数据中心内部SAN环境前后物理隔离，双中心之间靠专一SAN交换机实现存储后端网络的联通来解决该问题。这样的话，单中心内前段SAN环境故障不会波及存储后端，更不会波及整个基础架构的存储网络。</p>
<p>4）串联深度带来的性能问题</p>
<p>这个问题是针对分布式存储卷架构的问题。</p>
<p>架构深度越深，那么IO的性能就会越差，因为IO每经过一层设备就会有一定的延时消耗，纵向深度越深经历的设备越多，那么IO的延时也就越高。如果我们的架构在纵向上越复杂，那么这个问题应该说从本质上是无法消除的，只能通过一定的方法来减少和优化。</p>
<p>从存储层来看，一般存储侧在对物理卷进行虚拟化的时候都会有几种策略。为了增加管理的灵活性及扩展性，虚拟化的时候可能会经过多层映射。另外一种策略是为了提高性能，在虚拟化的时候尽量较少映射。我们在规划存储卷的时候，尽量采用后一种策略。例如VPLEX就会有（1:1map、Raid等策略），我们可以选择1:1map这种策略，仅仅利用它的镜像聚合，而舍弃它的灵活伸缩特性。</p>
<p>
 <br>
</p>
<h2><strong>四、基于ASM冗余设计架构实现的数据库双活方案如何规划？</strong></h2>
<p>ASM使用独特的镜像算法：不镜像磁盘，而是镜像盘区。作为结果，为了在产生故障时提供连续的保护，只需要磁盘组中的空间容量，而不需要预备一个热备(hot spare)磁盘。不建议用户创建不同尺寸的故障组，因为这将会导致在分配辅助盘区时产生问题。ASM将文件的主盘区分配给磁盘组中的一个磁盘时，它会将该盘区的镜像副本分配给磁盘组中的另一个磁盘。给定磁盘上的主盘区将在磁盘组中的某个伙伴磁盘上具有各自的镜像盘区。ASM确保主盘区和其镜像副本不会驻留在相同的故障组中。</p>
<p>磁盘组的冗余可以有如下的形式：双向镜像文件(至少需要两个故障组)的普通冗余(默认冗余)和使用三向镜像(至少需要3个故障组)提供较高保护程度的高冗余。一旦创建磁盘组，就不可以改变它的冗余级别。为了改变磁盘组的冗余，必须创建具有适当冗余的另一个磁盘组，然后必须使用RMAN还原或DBMS_FILE_TRANSFER将数据文件移动到这个新创建的磁盘组。三种不同的冗余方式如下：</p>
<p>1、 外部冗余（external redundancy）：表示Oracle不帮你管理镜像，功能由外部存储系统实现，比如通过RAID技术；有效磁盘空间是所有磁盘设备空间的大小之和。</p>
<p>2、 默认冗余（normal redundancy）：表示Oracle提供2份镜像来保护数据，有效磁盘空间是所有磁盘设备大小之和的1/2 （使用最多）</p>
<p>3、 高度冗余（high redundancy）：表示Oracle提供3份镜像来保护数据，以提高性能和数据的安全，最少需要三块磁盘（三个failure group）；有效磁盘空间是所有磁盘设备大小之和的1/3，虽然冗余级别高了，但是硬件的代价也最高。</p>
<p>至于如何选择，不言而喻。</p>
<p>一个简单的案例：</p>
<p>A中心存储的LUN在系统上看到的设备文件名叫：hdisk1,hdisk2,100G hdisk3,hdisk4,20G, hdisk5,1G</p>
<p>B中心存储的LUN在系统上看到的设备文件名叫：hdisk6,hdisk7,100G hdisk8,hdisk9,20G, hdisk10,1G</p>
<p>仲裁中心的LUN在系统上看到的文件名叫：hdisk11,1G</p>
<p>DATA磁盘组：规划两个failuregroup：</p>
<p>failuregroup1=(hdisk1,hdisk2) failuregroup2=(hdisk6,hdisk7)</p>
<p>策略=normal</p>
<p>FRA磁盘组：规划两个failuregroup：</p>
<p>failuregroup1=(hdisk3,hdisk4) failuregroup2=(hdisk8,hdisk9)</p>
<p>策略=normal</p>
<p>OCR磁盘组：</p>
<p>hdisk5,hdisk10,hdisk11</p>
<p>
 <br>
</p>
<h2><strong>五、在 Oracle RAC 双活方案落地之前应该做哪些测试？</strong></h2>
<p>最重要的是压力测试，尤其要测试两个节点之间的热点可以承受到什么样的水平。然后就是故障测试，模拟单中心故障、链路故障、以太设备故障、节点故障、网卡故障、仲裁故障等各种条件下是否能如预期实现。</p>
<p>双活的最大故障就是一个中心完全不可用，模拟一个数据中心全部宕机，当然还有一些其他的单设备或者单应用的测试，虽然操作起来有一定的风险，不过还是值得的。</p>
<blockquote>
 <p>来自社区专家分享整理 <strong>觉得本文有用，请转发或点击"在看”，让更多同行看到</strong></p>
</blockquote>
<p>
 <br>
</p>
<p><strong>推荐资料/文章：</strong></p>
<ul>
 <li>
  <p>基于Oracle RAC实现双活方案的分析</p>
  <p>http://www.talkwithtrend.com/Article/216745</p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650636527&amp;idx=1&amp;sn=b8f4345646f14882cc99f90297fbe961&amp;chksm=bef9fe69898e777f0ea4c44a0c6de718eecd52c0f2ad343b6bd41fa17edcf71f80a8686ba93e&amp;scene=21#wechat_redirect">Oracle 资料大全 \| 19 个实用文档 46 篇文章</a></p>
 </li>
</ul>
<p>
 <br>
</p>
<p>欢迎关注社区<strong>“数据库双活"技术主题</strong> ，将会不断更新优质资料、文章。地址：</p>
<p>http://www.talkwithtrend.com/Topic/1809</p>
<p>
 <br>
</p>
<p><strong>下载 twt 社区客户端 APP</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528810351.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528810378.png&amp;size=m" alt=""></p>
<p>长按识别二维码即可下载</p>
<p>或到应用商店搜索"twt”</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528810406.png&amp;size=m" alt=""></p>
<p>*本公众号所发布内容仅代表作者观点，不代表社区立场</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646841&amp;idx=4&amp;sn=a5964a5b657325759ec2c042a2024576&amp;chksm=bef9d7bf898e5ea9edf8e936660de375d6173c945038ddfbe06c1372ec2dfd910194fbd60a72&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/df358b6a-df74-4e12-82c2-940496b52c2e</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 01:26:26 GMT</pubDate></item><item><title><![CDATA[Oracle19c RAC for Centos7.6 详细安装文档]]></title><link>https://wiki.dgfygk.com/archives/148f9c73-bb9d-445f-aab0-bbd98f17f5ce</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Oracle19c%20RAC%20for%20Centos7.6%20%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3&amp;url=/archives/148f9c73-bb9d-445f-aab0-bbd98f17f5ce" width="1" height="1" alt="" style="opacity:0;">
<p>目录</p>
<p>1 环境</p>
<p>1.1 系统版本</p>
<p>1.2 ASM 磁盘组规划</p>
<p>1.3 主机网络规划</p>
<p>1.4 操作系统配置部分</p>
<p>2 准备工作（rac1 与 rac2 同时配置）</p>
<p>2.1 配置本地 yum 源</p>
<p>2.2 安装 rpm 依赖包</p>
<p>2.3 创建用户</p>
<p>2.4 配置 host 表</p>
<p>2.5 禁用 NTP</p>
<p>2.6 创建所需要目录</p>
<p>2.7 其它配置</p>
<p>2.8 配置环境变量</p>
<p>2.9 配置共享磁盘权限</p>
<p>2.10 配置互信</p>
<p>2.11 在 grid 安装文件中安装 cvuqdisk</p>
<p>3 开始安装 grid</p>
<p>3.1 上传集群软件包</p>
<p>3.2 解压 grid 安装包</p>
<p>3.3 进入 grid 集群软件目录执行安装</p>
<p>3.4 GUI 安装步骤</p>
<p>3.5 查看状态</p>
<p>4 以 Oracle 用户登录图形化界面</p>
<p>4.1 执行安装</p>
<p>4.2 执行 root 脚本</p>
<p>5 创建 ASM 数据磁盘</p>
<p>5.1 grid 账户登录图形化界面，执行 asmca</p>
<p>6 建立数据库</p>
<p>6.1 执行建库 dbca</p>
<p>6.2 查看集群状态</p>
<p>6.3 查看数据库版本</p>
<p>
 <br>
</p>
<p><strong>1 环境</strong></p>
<p><strong>1.1 系统版本</strong></p>
<p>[root@rac19c1 Packages]# cat /etc/redhat-release</p>
<p>CentOS Linux release 7.6.1810 (Core)</p>
<p><strong>1.2 ASM 磁盘组规划</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528796517.png&amp;size=m" alt=""></p>
<p><strong>1.3 主机网络规划</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528796742.png&amp;size=m" alt=""></p>
<p><strong>1.4 操作系统配置部分</strong></p>
<ol>
 <li>
  <p>关闭防火墙
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528796869.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>关闭 selinux
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528796941.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>hosts 文件配置</p>
 </li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797075.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797130.png&amp;size=m" alt=""></p>
<p><strong>2 准备工作（rac1 与 rac2 同时配置）</strong></p>
<p><strong>2.1 配置本地 yum 源</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797218.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797353.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797428.png&amp;size=m" alt=""></p>
<p><strong>2.2 安装 rpm 依赖包</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797508.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797663.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797751.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797857.png&amp;size=m" alt=""></p>
<p><strong>2.3 创建用户</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797905.png&amp;size=m" alt=""></p>
<p><strong>2.4 配置 host 表</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528797979.png&amp;size=m" alt=""></p>
<p><strong>2.5 禁用 NTP</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798091.png&amp;size=m" alt=""></p>
<p><strong>2.6 创建所需要目录</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798211.png&amp;size=m" alt=""></p>
<p><strong>2.7 其它配置：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798284.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798370.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798428.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798590.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798648.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798704.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798778.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798862.png&amp;size=m" alt=""></p>
<p><strong>2.8 配置环境变量</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528798955.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799111.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799163.png&amp;size=m" alt=""></p>
<p><strong>2.9 配置共享磁盘权限</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799240.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799416.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799545.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799656.png&amp;size=m" alt=""></p>
<p><strong>2.10 配置互信</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799731.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799816.png&amp;size=m" alt=""></p>
<p><strong>2.11 在 在 d grid 安装文件中安 装 cvuqdisk</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799916.png&amp;size=m" alt=""></p>
<p><strong>3 开始安装 grid</strong></p>
<p><strong>3.1 上传集群软件包</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528799986.png&amp;size=m" alt=""></p>
<p><strong>3.2 解压 grid 安装包</strong></p>
<p>在 19C 中需要把 grid 包解压放到 grid 用户下 ORACLE_HOME 目录内</p>
<p>解压文件到/u01/app/19.0.0/grid
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800059.png&amp;size=m" alt=""></p>
<p><strong>3.3 进入 grid 集群软件目录执行安装</strong></p>
<p>[grid@rac1 grid]$ ./gridSetup.sh
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800110.png&amp;size=m" alt=""></p>
<p><strong>3.4 GUI 安装步骤</strong></p>
<ol>
 <li>
  <p>创建新的集群
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800244.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>配置集群名称以及 scan 名称
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800299.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>节点互信
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800472.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>公网、私网网段选择
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800640.png&amp;size=m" alt=""> 5. 选择 asm 存储
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800805.png&amp;size=m" alt=""> 6. 选择配置 GIMR <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528800887.png&amp;size=m" alt=""> 7. 这里选择 ocr、voting file 与 gimr 放在一起 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528801012.png&amp;size=m" alt=""> 8. 选择 asm 磁盘组 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528801181.png&amp;size=m" alt=""> 9. 输入密码 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528801300.png&amp;size=m" alt=""> 10. 保持默认 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528801514.png&amp;size=m" alt=""> 11. 保持默认 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528801611.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528801805.png&amp;size=m" alt=""> 12. 确认 base 目录 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528801945.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528802134.png&amp;size=m" alt=""> 13. 这里可以选择自动 root 执行脚本 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528802254.png&amp;size=m" alt=""> 14. 预安装检查 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528802375.png&amp;size=m" alt=""> 15. 解决相关依赖后，忽略如下报错 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528802548.png&amp;size=m" alt=""> 16. 如下警告可以忽略-警告是由于没有使用 DNS 解析造成可忽略 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528802723.png&amp;size=m" alt=""> 17. 执行 root 脚本 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528802907.png&amp;size=m" alt=""></p>
 </li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803007.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803175.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803285.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803422.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803589.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803702.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803834.png&amp;size=m" alt=""></p>
<p><strong>3.5 查看状态</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803891.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528803964.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804064.png&amp;size=m" alt=""></p>
<p><strong>4 以 Oracle 用户登录图形化界面</strong></p>
<p>将数据库软件解压至$ORACLE_HOME 安装 Oracle 数据库软件</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804151.png&amp;size=m" alt=""></p>
<p><strong>4.1 执行安装</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804222.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804380.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804462.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804565.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804678.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804759.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528804846.png&amp;size=m" alt=""></p>
<p>预安装前检查忽略如下警告
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528805024.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528805306.png&amp;size=m" alt=""></p>
<p><strong>4.2 执行 t root 脚本</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528805452.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528805553.png&amp;size=m" alt=""></p>
<p><strong>5 创建 ASM 数据磁盘</strong></p>
<p><strong>5.1 grid 账户登录图形化界面，执行 asmca</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528805662.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528805853.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528805992.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806077.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806173.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806326.png&amp;size=m" alt=""></p>
<p><strong>6 建立数据库</strong></p>
<p>以 Oracle 账户登录。</p>
<p><strong>6.1. 执行建库 dbca</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806438.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806578.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806678.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806884.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528806981.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807147.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807272.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807372.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807516.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807615.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807694.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807836.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528807937.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528808106.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528808242.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528808357.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528808496.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528808675.png&amp;size=m" alt=""></p>
<p><strong>6.2 查看集群状态</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528808776.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528808915.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528809055.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528809169.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528809245.png&amp;size=m" alt=""></p>
<p><strong>6.3 查看数据库版本</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528809331.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<blockquote>
 <p>来自社区会员上传分享，相关版权归原作者所有 点击阅读原文可下载原文档 <strong>觉得本文有用，请转发、点赞或点击"在看”，让更多同行看到</strong></p>
</blockquote>
<p>
 <br>
</p>
<p><strong>推荐资料/文章：</strong></p>
<ul>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646655&amp;idx=2&amp;sn=1189e6e14da9a661a15597134c7155a2&amp;chksm=bef9d6f9898e5fef5ec77c9866944d8c8cd1536a75724570dade53ee86e714e6e7789b2e1f53&amp;scene=21#wechat_redirect">经典实用：Oracle RAC 建设过程中各个层面的关键点和优化项总结 \| 周末送资料</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633481&amp;idx=2&amp;sn=6e2ae2aaf5b72359042f50b1b380fa11&amp;chksm=bef90b8f898e8299c50b338d955446ef002b6c5bfdced5185d307a1b067af242490b581b8ee1&amp;scene=21#wechat_redirect">Oracle RAC 资料大全</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650647096&amp;idx=1&amp;sn=d310f9738e8e1b1992d184940c21a63e&amp;chksm=bef9d4be898e5da8d1c84847a3e393d7c7da9c36e823de0e9b6d667a687d9c19fc366ba22bf9&amp;scene=21#wechat_redirect">经典实用高分文档：银行双活容灾建设方案技术手册 \| 周末送资料</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650645294&amp;idx=1&amp;sn=09a094bebad33563fe48ee9a0a8af453&amp;chksm=bef9dda8898e54bed262bab7f944ee531cd7844e2506b215480e5cdb76d5e34eb9b869c2d2e3&amp;scene=21#wechat_redirect">75 篇容灾\&amp;备份实用参考！请收好</a></p>
 </li>
</ul>
<p>
 <br>
</p>
<p>欢迎关注社区<strong>“数据库双活"技术主题</strong> ，将会不断更新优质资料、文章。地址：</p>
<p>http://www.talkwithtrend.com/Topic/1809</p>
<p>
 <br>
</p>
<p><strong>下载 twt 社区客户端 APP</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528809416.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528809446.png&amp;size=m" alt=""></p>
<p>长按识别二维码即可下载</p>
<p>或到应用商店搜索"twt”</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528809475.png&amp;size=m" alt=""></p>
<p>*本公众号所发布内容仅代表作者观点，不代表社区立场</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650647693&amp;idx=2&amp;sn=0f5654822da40f780ea88882fb7d7b54&amp;chksm=bef9d20b898e5b1d3d95729b6bc8fc29ee94872de699b6808030c43bf4899f8bf44ac32e370d&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/148f9c73-bb9d-445f-aab0-bbd98f17f5ce</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 01:20:24 GMT</pubDate></item><item><title><![CDATA[基于 Oracle RAC 实现双活的关键问题和解决方案]]></title><link>https://wiki.dgfygk.com/archives/9022a415-00ff-4eed-8d7e-1ee60267ee4f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8E%20Oracle%20RAC%20%E5%AE%9E%E7%8E%B0%E5%8F%8C%E6%B4%BB%E7%9A%84%E5%85%B3%E9%94%AE%E9%97%AE%E9%A2%98%E5%92%8C%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&amp;url=/archives/9022a415-00ff-4eed-8d7e-1ee60267ee4f" width="1" height="1" alt="" style="opacity:0;">
<p><strong>1.架构分析</strong></p>
<h3><strong>1.1基于ASM冗余设计架构</strong></h3>
<p>图1.1基于ASM冗余设计实现的Oracle RAC</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811747.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p>存储层实现SAN网络跨数据中心级联，使双数据中心能够实现整体SAN网络。</p>
 </li>
 <li>
  <p>网络层实现二层打通，SCAN IP可以跨数据中心浮动。</p>
 </li>
 <li>
  <p>应用层实现跨数据中心RAC，每一个数据中心分别有一个实例节点。</p>
 </li>
 <li>
  <p>Oracle ASM存储层，数据磁盘组需要实现基于双数据中心存储卷的双镜像冗余策略，OCR仲裁磁盘组需要实现基于双数据中心存储卷以及第三方站点网络存储卷的三块儿磁盘高可用策略。</p>
 </li>
</ul>
<h3><strong>1.2基于存储集群实现的架构</strong></h3>
<p>图1.2基于存储集群实现的架构
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811828.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p>存储层借助存储虚拟化产品实现双数据中心以及第三方仲裁站点组成的存储集群，使得存储可以提供给应用层分布式虚拟磁盘，最终让应用对存储层的逻辑映射没有任何感知。</p>
 </li>
 <li>
  <p>网络层实现二层打通，SCAN IP可以跨数据中心浮动。</p>
 </li>
 <li>
  <p>应用层实现跨数据中心RAC，每一个数据中心分别有一个实例节点。</p>
 </li>
 <li>
  <p>Oracle ASM存储层，磁盘组不需要做任何特殊冗余配置，只需要将存储层提供的分布式虚拟磁盘看做是本地共享磁盘进行安装配置即可。</p>
 </li>
</ul>
<p>
 <br>
</p>
<h2><strong>2.实现难度分析</strong></h2>
<h3><strong>2.1 架构复杂度</strong></h3>
<ul>
 <li>
  <p>架构一的复杂度在于ASM层的设计。ORACLE RAC实例节点看到的共享盘是基于双中心存储实现的镜像策略，所有IO的读写分发是由ASM本身的冗余算法规则来决定的，DBA不仅仅要根据磁盘情况来设计合理的Failure Group，而且需要结合第三方站点的网络存储卷来合理设计仲裁磁盘组的分配。更重要的是需要结合实际的网络环境指标（延时、稳定性等）进行复杂的性能、稳定性、灾难测试等来调整ASM的一些IO参数。</p>
 </li>
 <li>
  <p>架构一的复杂度在于整体架构的复杂度。例如仲裁一致性问题，是指双中心之间的存储集群和数据库RAC集群的仲裁结果是否能保证一致性。存储集群是靠仲裁站点分别于两个站点之间的网络连通性来判定站点故障。而数据库集群是通过以太网心跳和OCR仲裁盘来做数据库仲裁。而数据库的OCR仲裁盘是存储集群提供的分布式共享卷。二者仲裁时的一致性如何保障是非常重要的一个问题。假设在发生站点级别故障时，数据库集群首先根据网络故障触发仲裁，判定站点A的节点存活。而存储随后再发生存储集群的仲裁，这个时候如果根据仲裁站点判定的结果恰恰仲裁委站点B的节点存活。那么数据库集群整体就会宕掉，这对于业务来讲就是一个灾难。</p>
 </li>
</ul>
<h3><strong>2.2 落地成本</strong></h3>
<ul>
 <li>
  <p>从实现的基本条件来看，两种架构的实现都会依赖双中心的二层打通。双中心的波分设备、以太转换设备、光纤链路租用就是必不可少的条件了。包括其购置成本和日后的运维成本等。这是非常可观的一项成本预算。</p>
 </li>
 <li>
  <p>从存储层的架构组成来看，架构一不需要存储层增加任何其他设备成本及运维成本。但是架构二需要依赖存储层的虚拟化网关产品来实现存储虚拟化集群，无疑这需要增加相应的购置成本和相应的运维成本。尤其注意存储集群产品是否有容量许可成本问题。</p>
 </li>
 <li>
  <p>从第三点的仲裁站点成本来看，两种方案都需要第三点的仲裁，区别在于架构一需要的是NAS存储，而架构二需要的基于以太网的计算资源来配置仲裁虚拟机。投入成本没有什么差异。</p>
 </li>
 <li>
  <p>从Oracle运维成本来看，架构一对DBA的要求非常苛刻，需要DBA不仅仅能够深知其中的原理，而且需要对性能的分析有较深的造诣，从而保障在复杂的双中心联动环境下各种复杂情况下的性能及稳定性变动有快速和准确的判断和处理能力。架构二对DBA的要求没有特殊的苛刻要求但是需要增加对存储集群的专业维护成本。</p>
 </li>
</ul>
<h3><strong>2.3 关键问题及解决方案</strong></h3>
<h4><strong>2.3.1针对架构二的仲裁一致性问题</strong></h4>
<p>在这个问题上，风险发生的引发点有两个：数据库和集群的仲裁触发以及仲裁过程的时间顺序发生紊乱；资源被1:1割裂之后的默认仲裁策略不一致。也就是说，只要控制这两个引发点，那么这个问题从理论上也就避免了。对于第一个引发点来讲，实际上存储集群的默认仲裁触发时间会是15秒左右，而数据库仲裁触发的控制参数由misscount这个参数来决定，所以只要我们将misscount这个参数调整到45秒之后，也就是说理论上绝对保障存储集群仲裁在前，而数据库仲裁在后，那么第一个引发点就没有了。对于第二个引发点来讲，假设两站点节点资源对等，仲裁选票同样对等的情况下，存储集群会有一个默认的Winner策略，同样在这种情况下数据库集群也有一个默认仲裁策略：选择实例号小的集群存活。只要我们保证这两个策略结果的一致性，那么第二个引发点也就不存在了。</p>
<h4><strong>2.3.2链路稳定状况不可控</strong></h4>
<h4>这个问题是两种架构都面临的问题。主要表现为两个方面：链路稳定状况不可控；延时指标不可控。因为双中心之间的链路是通过租用运营商的裸光纤链路实现的，那么这其中会经历很多的中继设备及节点。无论从管理上还是从技术把控上都是金融企业自身不可控制的因素。假设双中心间链路延时指标不稳定，也就是说数据库节点之间私网传输的延时会经常出现长延时情况，这势必导致这种延时会加倍放大到数据库节点之间的读写热点竞争上。由于数据库集群之间的数据传输量非常大（缓存、锁、心跳等），在读写热点相对突出的业务上，轻则导致数据库读写性能灾难，重则导致数据库节点直接处于僵死状态。另外，链路的不稳定会导致存储链路频繁切换，甚至会导致集群仲裁频繁发生，这对于业务连续性更是一个灾难。</h4>
<h4>对于这个问题来讲，就目前金融行业的传统数据架构来讲，并没有一个十足的解决方案。我们只能通过以下措施来减少这种问题带给我们的风险。</h4>
<h4>一、业务层面需要进行拆分重组：按照IO特点进行合理拆分，将读写业务尽量分布于不同节点上，减少节点间的锁竞争。按照业务将数据库表进行分区，避免在数据库写上的数据热点块儿。例如，对于银行核心系统来讲，尤其是要将批量业务和联机业务区分对待，批量业务的热点以及数据量非常之巨大，所以一定要将批量业务的数据库读写放在单边实现。对于联机业务来讲可以根据热点状况以及链路质量评测结果可以尝试实现双中心同时读写，但是本文建议对于这种重量级的业务还是要从业务层尽量实现应用上的读写分离，或者在应用层双中心部署而在数据库层将数据引到单边来做。</h4>
<p>二、双中心间通讯的整体控制，具体包括对通讯带宽的优先级管理、对通讯的实时监控和控制、对跨中心数据传输的严格策略把控。例如：优先保障存储和数据库通讯的优先级和带宽，严格的规则算法和优先级限定VMOTION、DRS等行为的跨中心随意性，从LTM负载分发上尽可能保障正常情况下纵向IO的单中心效率策略，故障情况下保障跨中心访问的科学性。DWDM上设置双中心间通讯带宽的逻辑隔离以及实时可控。</p>
<h4><strong>2.3.3存储网络故障泛滥</strong></h4>
<h4>这是两种架构都会面临的问题，只是架构一可能性相对高一些。</h4>
<h4>如果我们把两个中心的SAN环境整合为一张大网，物理上没有任何隔离的大网，那么可能会因为局部的存储网络故障而波及到整个存储网络。尽管我们通过SAN交换机上的逻辑隔离能够解决大部分的安全问题，但是这样的风险毕竟还是存在的。</h4>
<p>所以我们可以通过对数据中心内部SAN环境前后物理隔离，双中心之间靠专一SAN交换机实现存储后端网络的联通来解决该问题。这样的话，单中心内前段SAN环境故障不会波及存储后端，更不会波及整个基础架构的存储网络。</p>
<h4><strong>2.3.4串联深度带来的性能问题</strong></h4>
<h4>这个问题是针对架构二的问题。</h4>
<p>架构深度越深，那么IO的性能就会越差，因为IO每经过一层设备就会有一定的延时消耗，纵向深度越深经历的设备越多，那么IO的延时也就越高。如果我们的架构在纵向上越复杂，那么这个问题应该说从本质上是无法消除的，只能通过一定的方法来减少和优化。</p>
<p>从存储层来看，一般存储侧在对物理卷进行虚拟化的时候都会有几种策略。为了增加管理的灵活性及扩展性，虚拟化的时候可能会经过多层映射。另外一种策略是为了提高性能，在虚拟化的时候尽量较少映射。我们在规划存储卷的时候，尽量采用后一种策略。例如VPLEX就会有（1:1map、Raid等策略），我们可以选择1:1map这种策略，仅仅利用它的镜像聚合，而舍弃它的灵活伸缩特性。</p>
<blockquote>
 <p>原题：基于Oracle RAC实现双活方案的分析 <strong>如有任何问题，可点击文末</strong> <strong>阅读原文</strong><strong>，到社区原文下评论交流</strong></p>
 <p><strong>觉得本文有用，请转发或点击"在看”，让更多同行看到</strong></p>
</blockquote>
<p>
 <br>
</p>
<p>资料/文章推荐：</p>
<ul>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650641006&amp;idx=1&amp;sn=c15f21de51d0b11ad5bd9b398c165344&amp;chksm=bef9ece8898e65fe456e64f19550b92345256425a582ef7eca72add4fe8c0e15e64deeb07eb0&amp;scene=21#wechat_redirect">Oracle RAC/ADG/OGG 双活方案比较 \| 资料</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650647693&amp;idx=2&amp;sn=0f5654822da40f780ea88882fb7d7b54&amp;chksm=bef9d20b898e5b1d3d95729b6bc8fc29ee94872de699b6808030c43bf4899f8bf44ac32e370d&amp;scene=21#wechat_redirect">Oracle19c RAC for Centos7.6 详细安装文档</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633481&amp;idx=2&amp;sn=6e2ae2aaf5b72359042f50b1b380fa11&amp;chksm=bef90b8f898e8299c50b338d955446ef002b6c5bfdced5185d307a1b067af242490b581b8ee1&amp;scene=21#wechat_redirect">Oracle RAC 资料大全</a></p>
 </li>
</ul>
<p>
 <br>
</p>
<p>欢迎关注社区 <strong>“双活”</strong> <strong>技术主题</strong> ，将会不断更新优质资料、文章。地址：</p>
<p>http://www.talkwithtrend.com/Topic/71</p>
<p>
 <br>
</p>
<p><strong>下载 twt 社区客户端 APP</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811965.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528811992.png&amp;size=m" alt=""></p>
<p>长按识别二维码即可下载</p>
<p>或到应用商店搜索"twt”</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528812017.png&amp;size=m" alt=""></strong></p>
<p>*本公众号所发布内容仅代表作者观点，不代表社区立场</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650649292&amp;idx=3&amp;sn=2dd2d6915b62d7de39dbdfd54a477336&amp;chksm=bef9cc4a898e455c8ed5c8c2d59732cea9cf94d74d860a5c70c1cd208c1285fd65be71e08357&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/9022a415-00ff-4eed-8d7e-1ee60267ee4f</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 01:20:01 GMT</pubDate></item><item><title><![CDATA[基于Oracle RAC 双活方案实施，如何规避脑裂风险？]]></title><link>https://wiki.dgfygk.com/archives/da8a20da-05a0-4664-b898-bf6ba3d607b9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8EOracle%20RAC%20%E5%8F%8C%E6%B4%BB%E6%96%B9%E6%A1%88%E5%AE%9E%E6%96%BD%EF%BC%8C%E5%A6%82%E4%BD%95%E8%A7%84%E9%81%BF%E8%84%91%E8%A3%82%E9%A3%8E%E9%99%A9%EF%BC%9F&amp;url=/archives/da8a20da-05a0-4664-b898-bf6ba3d607b9" width="1" height="1" alt="" style="opacity:0;">
<p><em>以下内容来自社区探讨，欢迎点击阅读原文到社区与同行交流本话题</em></p>
<p>
 <br>
</p>
<blockquote>
 <p><strong>基于Oracle RAC 双活方案实施，如何规避脑裂这个风险？</strong>
  <br>
  基于ORACLE RAC 双活方案实施，难点在于远距离光纤条件下的节点之间的数据交互，尤其因为仲裁的原因，导致出现的脑裂现象较多，我们应该如何规避这个风险？</p>
 <p>（问题来自@skey_deng 某农商行 系统运维工程师）</p>
</blockquote>
<p>
 <br>
</p>
<p>@asdf-asdf 研究学者：</p>
<p>磁盘心跳，ip心跳，共享NAS心跳盘做备份。</p>
<p>当然，网络连接速度和SAN链接速度必须保证。</p>
<p>
 <br>
</p>
<p>@wanggeng 某银行 系统运维工程师：</p>
<p>数据库的仲裁是通过参数missmount设置，要想不发生脑裂的现象，如何设置这个参数参数很重要，其中需要参考光纤速率，光纤稳定性，业务响应时间等诸多因素进行设定。</p>
<p>再次，数据库集群中可以设置优先写，优先读等关键参数，比如主中心注重写，备中心偏重读，这里面可以设置scanip的分配策略等。</p>
<p>
 <br>
</p>
<p>@Switcher 某银行 数据库管理员：</p>
<p>RAC尽量选择在同机房部署，同城机房或者异地机房尽量以ADG为主。</p>
<p>当然，对于某些企业有自己专用线路并且线路还是多条冗余，不依赖供应商线路的企业，做跨机房的RAC才会推荐。</p>
<p>
 <br>
</p>
<p>@jampg 某大型保险 系统运维工程师：</p>
<p>我们的RAC的确还没有异地机房部署的案例，根据经验也不是很建议。</p>
<p>
 <br>
</p>
<p>@03500408 gansubank 数据库管理员：</p>
<p>脑裂分两个层面，一个层面是共享存储的脑裂，另外一个层面是数据库RAC层面的脑裂，共享存储的脑裂通过第三方站点仲裁避免，数据库层面的脑裂通过磁盘心跳和网络心跳避免，由于发生心跳网络中断时可能同时导致存储和数据库的仲裁，所以就要优先进行存储的仲裁，存储仲裁完将会导致数据库的磁盘心跳的仲裁，这样就避免了存储和数据库的仲裁不一致的情况。当然存储仲裁的第三方站点是放在另外的机房的一般不会同时第三方站点损坏，存储心跳和数据库心跳也中断的情况。</p>
<p>
 <br>
</p>
<p>@Dennis_Cai 系统工程师：</p>
<p>对于RAC脑裂，建议心跳与磁盘仲裁都冗余。如果是多套存储或者异地，楼上采用第三方仲裁楼上兄弟也说了。这里我有个建议，就是加入为2个节点，尽量保证一个节点能支撑业务，个人曾经试过由于交换机问题，RAC出现脑裂，二节点直接被重启了，导致业务受影响，这使我甚至没有时间去仔细去排查故障就得立刻恢复节点。</p>
<p>
 <br>
</p>
<p>@韩成亮 某金融 数据库架构师：</p>
<p>脑裂的情况是一般是实例层或者集群层。</p>
<p>内网网络连接中断 2个实例都是正常的这个时候数据会不一致，这个时候就脑裂了， 集群层会把其中一个节点踢出去强制重启，修复数据，这个也是网络波动的时候一直有重启的原因。</p>
<p>当然脑裂发生的时候，总会有一个节点由于表决盘的心跳机制会存活，脑裂是为了解决数据的不一致<strong>。</strong></p>
<p>麻烦说下具体的双活架构图。Oracle RAC的双活 主要看你基于哪一种做的：</p>
<p>RAC</p>
<p>Extended RAC</p>
<p>Extended RAC +EMC VPLEX 等</p>
<p>
 <br>
</p>
<p>@梅志荣 某金融公司 系统架构师：</p>
<p>Oracle RAC在同城双活实践中业务数据同步非常重要，网络延时以及数据同步模式的选择直接影响到业务端响应速度的感受。用于业务数据同步的物理网络存在网络延迟或者网络抖动的情况，Oracle RAC环境下为了保证RAC节点间数据的可用性和一致性，引入仲裁盘（卷）的机制，能够有效避免由于业务数据网络问题引起的所谓"脑裂"的情况出现。</p>
<p>Oracle引入的仲裁盘机制有两个比较常用的模式，一个是利用共享存储（并发卷组）中的仲裁逻辑卷（LVs）（一般可以配置为奇数，3个或者5个），发生"脑裂"时，通过判断RAC节点获得仲裁卷的数量多少判决被宕机的RAC节点；另一种模式是在第三方机房内部署仲裁站点（服务器），双活站点与仲裁站点网络可达，通过iscsi或者NFS分配给不同RAC节点仲裁盘，判决原理同仲裁逻辑卷，只是仲裁卷变成仲裁盘（PVs）。</p>
<p>两种模式可以依据用户环境的实践情况做出选择，如果不考虑经济因素，建议采用第二种模式，理由是第三方站点受到同城业务网络影响不大，不会出现误判，而且第三方站点网络质量一般不会影响双活业务环境。</p>
<p>个人理解，仅供参考！</p>
<blockquote>
 <p><strong>欢迎点击文末</strong> <strong></strong>阅读原文<strong></strong> <strong>到社区讨论交流</strong> <strong>觉得本文有用，请</strong> <strong>转发</strong><strong>或点击</strong> <strong>“在看”</strong><strong>，让更多同行看到</strong></p>
</blockquote>
<p>
 <br>
</p>
<p>相关推荐：</p>
<ul>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633481&amp;idx=2&amp;sn=6e2ae2aaf5b72359042f50b1b380fa11&amp;chksm=bef90b8f898e8299c50b338d955446ef002b6c5bfdced5185d307a1b067af242490b581b8ee1&amp;scene=21#wechat_redirect">Oracle RAC 资料大全</a></p>
 </li>
 <li>
  <p>银行双活容灾建设方案技术手册——分析篇</p>
  <p>http://www.talkwithtrend.com/Article/243071</p>
 </li>
 <li>
  <p>银行双活容灾建设方案技术手册——规划篇</p>
  <p>http://www.talkwithtrend.com/Article/243091</p>
 </li>
 <li>
  <p>银行双活容灾建设方案技术手册——实施篇</p>
  <p>http://www.talkwithtrend.com/Article/243101</p>
 </li>
</ul>
<p>
 <br>
</p>
<p>欢迎关注社区 “双活"技术主题 ，将会不断更新优质资料、文章。地址：</p>
<p>http://www.talkwithtrend.com/Topic/71</p>
<p>
 <br>
</p>
<p><strong>下载 twt 社区客户端 APP</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528812533.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528812560.png&amp;size=m" alt=""></p>
<p>长按识别二维码即可下载</p>
<p>或到应用商店搜索"twt”</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528812587.png&amp;size=m" alt=""></p>
<p>*本公众号所发布内容仅代表作者观点，不代表社区立场</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642228&amp;idx=3&amp;sn=6524d5db3289704dd0b2996a286f08e4&amp;chksm=bef9e9b2898e60a4c76e4ed0ca0d0af6c72af980c19d7555dbffd30c62281090b112d0821f51&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/da8a20da-05a0-4664-b898-bf6ba3d607b9</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 01:19:46 GMT</pubDate></item><item><title><![CDATA[周末送资料 _ Oracle 高可用性（RAC）技术解决方案及实现过程]]></title><link>https://wiki.dgfygk.com/archives/a886ce80-d41f-44cc-bb69-20bbc5e5dedb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%91%A8%E6%9C%AB%E9%80%81%E8%B5%84%E6%96%99%20_%20Oracle%20%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7%EF%BC%88RAC%EF%BC%89%E6%8A%80%E6%9C%AF%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E5%8F%8A%E5%AE%9E%E7%8E%B0%E8%BF%87%E7%A8%8B&amp;url=/archives/a886ce80-d41f-44cc-bb69-20bbc5e5dedb" width="1" height="1" alt="" style="opacity:0;">
<p>目录</p>
<p>一、基础知识介绍</p>
<ol>
 <li>
  <p>RAC 是什么</p>
 </li>
 <li>
  <p>RAC 的优缺点</p>
 </li>
 <li>
  <p>Oracle RAC 原理</p>
 </li>
</ol>
<p>二、 RAID 存储技术介绍</p>
<p>三、 RAC 系统结构设计</p>
<ol>
 <li>
  <p>RAC 系统拓扑结构基本如下图所示：</p>
 </li>
 <li>
  <p>主机操作系统系统</p>
 </li>
</ol>
<p>1) 用户组规划</p>
<p>2) 用户规划</p>
<p>3) 主机文件系统规划</p>
<ol>
 <li>
  <p>存储规划</p>
 </li>
 <li>
  <p>数据库 RAC 环境数据库规划</p>
 </li>
</ol>
<p>1) RAC 环境信息</p>
<p>2) Oracle RAC 软件组件</p>
<p>3) Oracle RAC 网络规划</p>
<p>4) Oracle 用户组信息</p>
<p>5) 表空间规划</p>
<p>6) 数据库备份规划</p>
<p>四、 Oracle 数据库及 RAC 配置清单</p>
<ol>
 <li>
  <p>硬件清单</p>
 </li>
 <li>
  <p>软件清单</p>
 </li>
 <li>
  <p>网络硬件需求</p>
 </li>
 <li>
  <p>网络规划</p>
 </li>
 <li>
  <p>系统安装规划</p>
 </li>
 <li>
  <p>系统环境需求</p>
 </li>
 <li>
  <p>查所需软件包</p>
 </li>
</ol>
<p>五、 安装工作</p>
<ol>
 <li>
  <p>修改主机名称，在所有节点执行；</p>
 </li>
 <li>
  <p>创建用户和组</p>
 </li>
 <li>
  <p>修改用户密码</p>
 </li>
 <li>
  <p>修改 gird 用户的环境变量</p>
 </li>
 <li>
  <p>修改 oracle 用户的环境变量</p>
 </li>
 <li>
  <p>创建软件目录</p>
 </li>
 <li>
  <p>修改系统参数</p>
 </li>
 <li>
  <p>配置用户等效性</p>
 </li>
 <li>
  <p>配置 NTPD 服务</p>
 </li>
 <li>
  <p>安装并配置 ASM 驱动</p>
 </li>
 <li>
  <p>安装 cvuqdisk 软件包</p>
 </li>
 <li>
  <p>安装 Grid 前检查</p>
 </li>
 <li>
  <p>安装 Grid Infrastructure</p>
 </li>
 <li>
  <p>确认群集安装成功</p>
 </li>
 <li>
  <p>创建 asm 磁盘组</p>
 </li>
 <li>
  <p>安装 oracle 软件</p>
 </li>
 <li>
  <p>创建数据库</p>
 </li>
 <li>
  <p>数据库管理工作</p>
 </li>
</ol>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><strong>一、 基础知识介绍</strong></p>
<p><strong>1. RAC 是什么</strong></p>
<p>RAC，全称 real application clusters，译为"实时应用集群” ， 是 Oracle 新版数据库中采用的一项新技术，是高可用性的一种，也是 Oracle 数据库支持网格计算环境的核心技术。</p>
<p><strong>2. RAC 的优缺点</strong></p>
<ul>
 <li><strong>优点：</strong></li>
</ul>
<p>Oracle RAC 主要支持 Oracle9i、10g、11g 版本，可以支持 24 x 7 有效的数据库应用系统， 在低成本服务器上构建高可用性数据库系统， 并且自由部署应用，无需修改代码。在 Oracle RAC 环境下，Oracle 集成提供了集群软件和存储管理软件， 为用户降低了应用成本。 当应用规模需要扩充时， 用户可以按需扩展系统，以保证系统的性能。</p>
<p>(1)多节点负载均衡;</p>
<p>(2)提供高可用：故障容错和无缝切换功能，将硬件和软件错误造成的影响最小化;</p>
<p>(3)通过并行执行技术提高事务响应时间—-通常用于数据分析系统;</p>
<p>(4)通过横向扩展提高每秒交易数和连接数—-通常对于联机事务系统;</p>
<p>(5)节约硬件成本，可以用多个廉价 PC 服务器代替昂贵的小型机或大型机，同时节约相应维护成本;</p>
<p>(6)可扩展性好，可以方便添加删除节点，扩展硬件资源。</p>
<ul>
 <li><strong>缺点：</strong></li>
</ul>
<p>(1)相对单机，管理更复杂，要求更高;</p>
<p>(2)可能会增加软件成本(如果使用高配置的 pc 服务器， Oracle 一般按照 CPU个数收费)。</p>
<p><strong>3. Oracle RAC 原理</strong></p>
<p>在一个应用环境当中，所有的服务器使用和管理同一个数据库，目的是为了分散每一台服务器的工作量，硬件上至少需要两台以上的服务器，而且还需要一个共享存储设备。同时所有服务器上的 OS 都应该是同一类 OS,根据负载均衡的配置策略，当一个客户端发送请求到某一台服务的 listener 后，这台服务器根据我们的负载均衡策略，会把请求发送给本机的 RAC 组件处理也可能会发送给另外一台服务器的 RAC 组件处理，处理完请求后，RAC 会通过集群软件来访问共享存储设备.逻辑结构上看：</p>
<ul>
 <li>
  <p>每一个参加集群的节点有一个独立的 instance，这些 instance 访问同一个数据库。</p>
 </li>
 <li>
  <p>每一个节点的 linstance 都有自己的 SGA。</p>
 </li>
 <li>
  <p>每一个节点的 linstance 都有自己的 background process。</p>
 </li>
 <li>
  <p>每一个节点的 linstance 都有自己的 redo logs。</p>
 </li>
 <li>
  <p>每一个节点的 instance 都有自己的 undo 表空间。</p>
 </li>
 <li>
  <p>所有节点都共享一份 datafiles 和 controlfiles。</p>
 </li>
</ul>
<p>三类 Resource：</p>
<ul>
 <li>
  <p>VIP - 虚拟 IP 地址(Virtual IP)</p>
 </li>
 <li>
  <p>OCR - Oracle Cluster Registry(集群注册文件),记录每个节点的相关信息</p>
 </li>
 <li>
  <p>Voting Disk,仲裁机制用于仲裁多个节点向共享节点同时写的行为，这样做是为了避免发生冲突。</p>
 </li>
</ul>
<p>
 <br>
</p>
<p><strong>二、 RAID 存储技术介绍</strong></p>
<p>独立冗余磁盘阵列（Redundant Array of Independent Disk，RAID）是一种把多块独立的硬盘（物理硬盘）按不同的方式组合起来形成一个硬盘组（逻辑硬盘） ，从而提供比单个硬盘更高的存储性能与数据备份能力的技术。RAID 特色是 N 块硬盘同时读取速度加快及提供容错性。可以将 RAID 分为不同级别， 级别并不代表技术高低， 选择哪一种 RAID产品纯视用户的操作环境及应用而定，与级别高低没有必然关系。</p>
<p><strong>RAID 0 ：无差错控制的带区组</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813196.png&amp;size=m" alt=""></p>
<p>要实现 RAID0 必须要有两个以上硬盘驱动器，数据并不是保存在一个硬盘上，而是分成数据块保存在不同驱动器上。因为将数据分布在不同驱动器上，所以数据吞吐率大大提高，驱动器的负载也比较平衡。它的缺点是它没有数据差错控制，如果一个驱动器中的数据发生错误，即使其它盘上的数据正确也无济于事了。不应该将它用于对数据稳定性高的场合。在所有的级别中，RAID 0 的速度是最快的。但是 RAID0 没有冗余功能的，如果一个磁盘（物理）损坏，则所有的数据都无法使用。</p>
<p><strong>RAID 1 ：镜象结构</strong></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813373.png&amp;size=m" alt=""></strong></p>
<p>对于使用这种 RAID1 结构的设备来说， RAID 控制器必须能够同时对两个盘进行读操作和对两个镜象盘进行写操作。镜象结构是在一组盘出现问题时，可以使用镜象磁盘，提高系统的容错能力。每读一次盘只能读出一块数据，也就是说数据块传送速率与单独的盘的读取速率相同。 当您的系统需要极高的可靠性时， 如进行数据统计， 那么使用 RAID1比较合适。 而且 RAID1 技术支持"热替换”， 即不断电的情况下对故障磁盘进行更换， 更换完毕只要从镜像盘上恢复数据即可。 当主硬盘损坏时，镜像硬盘就可以代替主硬盘工作。镜像硬盘相当于一个备份盘，这种硬盘模式的安全性是非常高的，RAID 1 的数据安全性在所有的 RAID 级别上来说是最好的。但是其磁盘的利用率却只有 50%，是所有 RAID 级别中最低的。</p>
<p><strong>RAID5 ：分布式奇偶校验的独立磁盘结构</strong></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813449.png&amp;size=m" alt=""></strong></p>
<p>RAID 5 将数据分散存放于多个硬盘上面，同时使用一定的编码技术产生奇偶校验码来提供错误检查及恢复能力，数据段的校验位交互存放于各个硬盘上。 因为奇偶校验码在不同的磁盘上， 所以提高了可靠性，允许单个磁盘出错。任何一个硬盘损坏，都可以根据其它硬盘上的校验位来重建损坏的数据。硬盘的利用率为 n-1。优点是提供了冗余性（支持一块盘掉线后仍然正常运行） ，磁盘空间利用率较高（N-1/N） ，读写速度较快（N-1 倍） 。RAID5 是 RAID 级别中最常见的一个类型。</p>
<p><strong>RAID10 ：高可靠性与高效磁盘结构</strong></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813581.png&amp;size=m" alt=""></strong></p>
<p>这种结构是一个带区结构加一个镜象结构， 因为两种结构各有优缺点，因此可以相互补充，达到既高效又高速还可以互为镜像的目的。大家可以结合两种结构的优点和缺点来理解这种新结构。这种新结构的价格高，可扩充性不好。主要用于容量不大，但要求速度和差错控制的数据库中。</p>
<p>RAID 10 是先镜射再分区数据。是将所有硬盘分为两组，然后将这两组各自视为 RAID 1 运作。RAID 10 有着不错的读取速度，而且拥有比RAID 0 更高的数据保护性。</p>
<p>
 <br>
</p>
<p><strong>三、 RAC 系统结构设计</strong></p>
<p><strong>1. RAC 系统拓扑结构基本如下图所示：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813671.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>2. 主机操作系统系统</strong></p>
<p>RAC 支持常用的 linux、 AIX、 windows、 solaris 等操作系统， 本文以 linux操作为例。</p>
<ul>
 <li>数据库服务器 Redhat Linux 系统规划</li>
</ul>
<p>1) 用户组规划</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813760.png&amp;size=m" alt=""></p>
<p>2) 用户规划</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813887.png&amp;size=m" alt=""></p>
<p>3) 主机文件系统规划</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528813969.png&amp;size=m" alt=""></p>
<p><strong>3. 存储规划</strong></p>
<p>存储一般采用 RAID 5 存储方式。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814018.png&amp;size=m" alt=""></p>
<p><strong>4. 数据库 RAC 环境数据库规划</strong></p>
<p>1) RAC 环境信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814081.png&amp;size=m" alt=""></p>
<p>2) Oracle RAC 软件组件</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814138.png&amp;size=m" alt=""></p>
<p>3) Oracle RAC 网络规划</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814221.png&amp;size=m" alt=""></p>
<p>4) Oracle 用户组信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814276.png&amp;size=m" alt=""></p>
<p>5) 表空间规划</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814404.png&amp;size=m" alt=""></p>
<p>6) 数据库备份规划</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814479.png&amp;size=m" alt=""></p>
<p>根据平台业务特性及考虑到节约硬件成本，建议每周作一次完全备份，每天做一次增量备份。恢复时，首先恢复最近一次完全备份的数据，然后再依次恢复服务器硬盘上增量备份的数据，最后通过归档的日志文件将数据恢复到故障点处。</p>
<p>
 <br>
</p>
<p><strong>四、Oracle 数据库及 及 RAC 配置清单</strong></p>
<p><strong>1. 硬件清单</strong></p>
<ul>
 <li>
  <p>数据库服务器两 台 ， 主 要 配置如下：</p>
 </li>
 <li>
  <p>CPU 2*Intel Xeon</p>
 </li>
 <li>
  <p>内存 8*2GB</p>
 </li>
 <li>
  <p>硬盘 2*146GB</p>
 </li>
 <li>
  <p>数据库服务器磁盘阵列</p>
 </li>
 <li>
  <p>RAID 5</p>
 </li>
</ul>
<p><strong>2. 软件清单</strong></p>
<ul>
 <li>
  <p>Red Hat Enterprise Linux AS release 5</p>
 </li>
 <li>
  <p>Oracle Database 11g Release 2 (11.2.0.1.0) for Linux x86</p>
 </li>
 <li>
  <p>linux_11gR2_database_1of2.zip</p>
 </li>
 <li>
  <p>linux_11gR2_database_2of2.zip</p>
 </li>
 <li>
  <p>linux_11gR2_grid.zip</p>
 </li>
 <li>
  <p>oracleasmlib-2.0.4-1.el5.x86_64.rpm(11gR2 后非必须)</p>
 </li>
</ul>
<p><strong>3. 网络硬件需求</strong></p>
<ul>
 <li>
  <p>每个 Oracle RAC 节点必须至少有两个网络适配器（即网络接口卡 (NIC)）：一个用于公共网络接口，另一个用于专用网络接口（互连）。要对公共网络或专用网络使用多个 NIC，Oracle 建议采用 NIC 绑定。对公共网络和专用网络使用不同的绑定（即对公共网络使用 bond0，对专用网络使用 bond1），这是因为在安装过程中，每个接口要么定义为公共接口，要么定义为专用接口。本文不讨论 NIC 绑定。</p>
 </li>
 <li>
  <p>每个网络中网络适配器的相关公共接口名称在所有节点上都应相同，网络适配器的相关专用接口名称在所有节点上都应相同。</p>
 </li>
 <li>
  <p>对于公共网络，每个网络适配器必须支持 TCP/IP。</p>
 </li>
 <li>
  <p>对于专用网络，互连必须支持用户数据报协议 (UDP)，使用支持TCP/IP（最小要求为 1 Gb 以太网）的高速网络适配器和交换机。</p>
 </li>
 <li>
  <p>TCP 是 Oracle Clusterware 的互连协议。对于此互连，必须使用一个交换机。Oracle 建议您使用一个专用交换机。</p>
 </li>
 <li>
  <p>对于专用网络，所有指定互连接口的端点在网络上必须完全可达。每个节点均应连接到所有的专用网络接口。可以使用 ping命令测试一个互连接口是否可达。</p>
 </li>
 <li>
  <p>在 Oracle Grid Infrastructure 的安装过程中，系统都会要求您标识 OUI 在您的集群节点上检测到的每个网络接口计划使用方式 。 必 须 将 每 个 接 口 标 识 为 public interface 、 privateinterface 或 not used。</p>
 </li>
 <li>
  <p>可将若干独立的接口绑定为一个公共接口，这样可在某个 NIC出现故障时提供冗余性。但是，Oracle 建议您不要为 OracleClusterware 和 Oracle RAC 创建不同的接口。如果您对专用互连使用多个 NIC，Oracle 建议您使用 NIC 绑定。请注意，多个专用接口提供负载平衡，但不提供故障切换功能，只有将它们绑定在一起时才提供故障切换功能。</p>
 </li>
 <li>
  <p>从 Oracle Clusterware 11g 第 2 版开始，无需再为互连提供专用名称或 IP 地址。标识为专用的子网上的 IP 地址将被分配作为集群成员节点的专用 IP 地址。无需在 hosts 目录中手动配置这些地址。如果需要针对互连进行名称解析，可在 hosts 文件或 DNS 上配置专用 IP 名称。而 Oracle Clusterware 会为安装期间定义为专用接口的那个接口（如 eth1）分配互连地址，并将该地址分配给用作专用子网的那个子网。实际上，在本指南中，我会继续在每个节点包括一个专用名称和 IP 地址以用于RAC 互连。这在专用网络上提供了自我文档编制的方法和一组端点，可供故障排除之用。</p>
 </li>
</ul>
<p><strong>4. 网络规划</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814565.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814765.png&amp;size=m" alt=""></p>
<p><strong>5. 系统安装规划</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814908.png&amp;size=m" alt=""></p>
<p>注意：服务器的主机名必须全部都是小写,并且不要有下划线。</p>
<p><strong>6. 系统环境需求</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528814992.png&amp;size=m" alt=""></p>
<p><strong>7. 查所需软件包</strong></p>
<p>如果是 64bit,需要检查以下的 Packages。以下重复包名称的部分是 64bit,注明 32bit 的是 32bit packages。如果是 32bit OS,那么重复包名的只需要 32bit 部分的包。注意不同版本 Linux 系统后面的版本不太一样。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815072.png&amp;size=m" alt=""></p>
<p>检查方法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815128.png&amp;size=m" alt=""></p>
<p>然后输入：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815213.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815291.png&amp;size=m" alt=""></p>
<p>结果如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815372.png&amp;size=m" alt=""></p>
<p>查询一些文档，pdksh 这个包可以不用安装。</p>
<p>
 <br>
</p>
<p><strong>五、 安装工作</strong></p>
<p><strong>1. 修改主机名称，在所有节点执行</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815457.png&amp;size=m" alt=""></p>
<p><strong>2. 创建用户和组</strong></p>
<p>创建 RAC 相关用户和组，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815535.png&amp;size=m" alt=""></p>
<p><strong>3. 修改用户密码</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815642.png&amp;size=m" alt=""></p>
<p><strong>4. 修改 gird 用户的环境变量</strong></p>
<p>切换到 grid 用户， 准备安装 grid Infrastructure . 首先需要修改所有节点 grid 用户的环境变量.bash_profile</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815725.png&amp;size=m" alt=""></p>
<p>以下 grid 的 bash_profile, 仅供参考，还有一些参数可以自行加入。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815772.png&amp;size=m" alt=""></p>
<p>使配置文件生效， 同样在其他节点的 Grid 用户.bash_profile 中加入， 并注意修改 ORACLE_SID=+ASM2</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815876.png&amp;size=m" alt=""></p>
<p><strong>5. 修改 oracle 用户的环境变量</strong></p>
<p>Oracle 用户也需要设置.bash_profile</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528815958.png&amp;size=m" alt=""></p>
<p>以下 Oracle 的 bash_profile, 仅供参考</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816039.png&amp;size=m" alt=""></p>
<p>使配置文件生效, 同样在其他节点的 Oracle 用户.bash_profile 中加入，并注意修改 ORACLE_SID=orcl2</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816116.png&amp;size=m" alt=""></p>
<p><strong>6. 创建软件目录</strong></p>
<p>创建 Oracle RAC 所使用的目录，并修改相应的权限，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816195.png&amp;size=m" alt=""></p>
<p><strong>7. 修改系统参数</strong></p>
<p>修改操作系统相关参数，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816294.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816372.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816496.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816589.png&amp;size=m" alt=""></p>
<p>修改/etc/hosts 文件，在所有节点添加以下内容</p>
<p><strong>8. 配置用户等效性</strong></p>
<p>使用 Oracle 用户，配置 Oracle 用户等效性，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816660.png&amp;size=m" alt=""></p>
<p>使用 Oracle 用户，配置 Oracle 用户等效性，在节点 1 执行</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816714.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816795.png&amp;size=m" alt=""></p>
<p>验证 oracle 用户等效性，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816876.png&amp;size=m" alt=""></p>
<p>使用 Grid 用户，配置 Grid 用户等效性，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816933.png&amp;size=m" alt=""></p>
<p>配置 Grid 用户等效性，在节点 1 执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528816986.png&amp;size=m" alt=""></p>
<p>验证 Grid 用户等效性，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817068.png&amp;size=m" alt=""></p>
<p><strong>9. 配置 NTPD 服务</strong></p>
<p>各个节点时间同步 (所有节点设置，两个节点相互同步，不设置时间服务器)</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817140.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817219.png&amp;size=m" alt=""></p>
<p><strong>10. 安装并配置 ASM 驱动</strong></p>
<p>检查操作系统版本，确认安装与操作系统相匹配的 asmlib，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817322.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817427.png&amp;size=m" alt=""></p>
<p>Oracle ASMlib 下载地址：</p>
<p>http://www.oracle.com/technetwork/server-storage/linux/downloads/index.html</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817479.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817601.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817690.png&amp;size=m" alt=""></p>
<p>初始化 asmlib，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817769.png&amp;size=m" alt=""></p>
<p>ASMlib 加载到内核，在所有节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817834.png&amp;size=m" alt=""></p>
<p>磁盘分区</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817882.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528817963.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818094.png&amp;size=m" alt=""></p>
<p>创建 asm 磁盘，在节点 1 执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818171.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818223.png&amp;size=m" alt=""></p>
<p>在节点二执行扫描，识别 asm 盘</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818342.png&amp;size=m" alt=""></p>
<p><strong>11. 安装 cvuqdisk 软件包</strong></p>
<p>cvuqdisk 软件包用于发现共享存储，在 grid 安装软件的 rpm 目录中，在所有</p>
<p>节点执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818473.png&amp;size=m" alt=""></p>
<p><strong>12. 安装 Grid 前检查</strong></p>
<p>在所有节点执行，并且检查通过；</p>
<p>检查参数：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818544.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818673.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818798.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528818874.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819009.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819089.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819218.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819271.png&amp;size=m" alt=""></p>
<p><strong>13. 安装 Grid Infrastructure</strong></p>
<p>登陆到 grid 用户，执行安装文件，在节点 1 执行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819358.png&amp;size=m" alt="">
 <br>
 选择安装并配置一个集群</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819508.png&amp;size=m" alt=""></p>
<p>选择高级安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819593.png&amp;size=m" alt=""></p>
<p>将中文添加到右侧边框，添加中文可避免在使用 em 时乱码</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819719.png&amp;size=m" alt=""></p>
<p>填写集群名称和 scan 的解析名称及端口，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819836.png&amp;size=m" alt=""></p>
<p>在弹出的界面中，点击 add 填写节点二的信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528819955.png&amp;size=m" alt=""></p>
<p>添加完成后显示如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820103.png&amp;size=m" alt=""></p>
<p>在下图界面中选择各个网卡的用途</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820188.png&amp;size=m" alt=""></p>
<p>为 OCR 和表决盘选择 ASM 存储方式</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820319.png&amp;size=m" alt=""></p>
<p>选择 crsvol，并填写磁盘组名称，点击下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820385.png&amp;size=m" alt=""></p>
<p>配置 ASM System 口令</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820517.png&amp;size=m" alt=""></p>
<p>不配置 IPMI</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820650.png&amp;size=m" alt=""></p>
<p>为 asm 的各种角色分配属组</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820725.png&amp;size=m" alt=""></p>
<p>默认 grid 的目录，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820861.png&amp;size=m" alt=""></p>
<p>点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528820992.png&amp;size=m" alt=""></p>
<p>接下来，软件进行安装检测</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821119.png&amp;size=m" alt=""></p>
<p>检测通过后，点击 install，开始安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821225.png&amp;size=m" alt=""></p>
<p>安装进行中。。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821322.png&amp;size=m" alt=""></p>
<p>安装完成，弹出窗口，提示需要以 root 依次在两个节点执行脚本</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821459.png&amp;size=m" alt=""></p>
<p>脚本执行如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821548.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821668.png&amp;size=m" alt=""></p>
<p>执行完毕后，点击 ok，弹出如下界面，点击 close，grid 安装完成</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821754.png&amp;size=m" alt=""></p>
<p>出现以下错误时，是因为在 Hosts 配置文件里配置了 SCAN，未启用 DNS 解析，不影响 RAC 正常运行；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821850.png&amp;size=m" alt=""></p>
<p>点关闭完成安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528821997.png&amp;size=m" alt=""></p>
<p><strong>14. 确认群集安装成功</strong></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822157.png&amp;size=m" alt=""></strong></p>
<p><strong>15. 创建 asm 磁盘组</strong></p>
<p>本次任务将创建 3 个 asm 磁盘组，分别为：ARCH,DATA,FLASH。其中DATA 将存放数据库文件；FLASH 存放闪回文件；ARCH 磁盘组将创建 ACFS文件系统，分别挂载到两个节点，用来存放归档日志。</p>
<p>在 grid 用户下，执行 asmca，启动 asm 磁盘组创建向导</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822237.png&amp;size=m" alt=""></p>
<p>点击 create 按钮，在弹出的创建界面中填写磁盘组名称，选择外边存储方，并勾选成员，选择完毕后点击 ok；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822311.png&amp;size=m" alt=""></p>
<p>以同样的方法创建 BACKUP 磁盘组，创建完毕后如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822425.png&amp;size=m" alt=""></p>
<p><strong>16. 安装 oracle 软件</strong></p>
<p>以 oracle 用户登录到节点一，切换到软件安装目录，执行安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822562.png&amp;size=m" alt=""></p>
<p>在 弹 出 的 安 装 界 面 中 ， 点 击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822615.png&amp;size=m" alt=""></p>
<p>选择"install software only” ，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822735.png&amp;size=m" alt=""></p>
<p>选择两个节点，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822876.png&amp;size=m" alt=""></p>
<p>将简体中文添加到安装选项</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528822952.png&amp;size=m" alt=""></p>
<p>选择企业版，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823082.png&amp;size=m" alt=""></p>
<p>保持默认的目录，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823213.png&amp;size=m" alt=""></p>
<p>保持默认的属组信息，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823336.png&amp;size=m" alt=""></p>
<p>安装过程自动进行安装检查</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823405.png&amp;size=m" alt=""></p>
<p>检查通过后，点击 install</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823513.png&amp;size=m" alt=""></p>
<p>开始安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823660.png&amp;size=m" alt=""></p>
<p>安装完成，跳出一个界面，提示需要以 root 在两个节点上执行脚本</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823758.png&amp;size=m" alt=""></p>
<p>以 root 用户，在两个节点上执行脚本</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823890.png&amp;size=m" alt=""></p>
<p>执行完毕后，点击 ok，返回如下界面</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528823978.png&amp;size=m" alt=""></p>
<p>至此，oracle 数据库软件安装完毕。</p>
<p><strong>17. 创建数据库</strong></p>
<p>本节操作在节点 1 执行；</p>
<p>以 oracle 用户登录，执行 dbca 命令，选择 rac 数据库点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824109.png&amp;size=m" alt=""></p>
<p>选择通用类型，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824230.png&amp;size=m" alt=""></p>
<p>填写数据库名称，选择两个节点，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824349.png&amp;size=m" alt=""></p>
<p>默认，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824429.png&amp;size=m" alt=""></p>
<p>为所有用户设置统一密码，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824485.png&amp;size=m" alt=""></p>
<p>选择 ASM 存储方式，并选择相应的磁盘组，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824616.png&amp;size=m" alt=""></p>
<p>注：如果此处无法找到"ASM DISKGROUP” ，使用 root 用户，则执行以下操作，然后再次执行"dbca” ；</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824752.png&amp;size=m" alt=""></p>
<p>弹出 ASM 用户密码窗口，输入在安装 grid 时设置的密码</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824799.png&amp;size=m" alt=""></p>
<p>选择闪回磁盘组，勾选归档</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528824936.png&amp;size=m" alt=""></p>
<p>点击编辑归档模式参数，在归档路径出输入：+BACK/archive</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825009.png&amp;size=m" alt=""></p>
<p>下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825144.png&amp;size=m" alt=""></p>
<p>使用自动内存管理</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825222.png&amp;size=m" alt=""></p>
<p>点击字符集选项，选择 ZHS16GBK 类型</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825349.png&amp;size=m" alt=""></p>
<p>显示概览，默认不变，点击 next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825415.png&amp;size=m" alt=""></p>
<p>点击 finish</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825472.png&amp;size=m" alt=""></p>
<p>出现安装概要</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825686.png&amp;size=m" alt=""></p>
<p>点击 ok，开始安装过程</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825809.png&amp;size=m" alt=""></p>
<p>等待一段时间后，安装结束，弹出如下界面</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528825874.png&amp;size=m" alt=""></p>
<p>至此，数据库创建完毕。</p>
<p><strong>18. 数据库管理工作</strong></p>
<ul>
 <li>RAC 的启停</li>
</ul>
<p>oracle rac 默认会开机自启动，如需维护时可使用以下命令：</p>
<ul>
 <li>关闭：</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826040.png&amp;size=m" alt=""></p>
<ul>
 <li>开启</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826121.png&amp;size=m" alt=""></p>
<p>注：以上命令需以 root 用户执行</p>
<ul>
 <li>RAC 检查运行状况</li>
</ul>
<p>以 grid 用户运行</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826198.png&amp;size=m" alt=""></p>
<ul>
 <li>Database 检查例状态</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826276.png&amp;size=m" alt=""></p>
<ul>
 <li>检查节点应用状态及配置</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826356.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826432.png&amp;size=m" alt=""></p>
<ul>
 <li>查看数据库配置</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826568.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826710.png&amp;size=m" alt=""></p>
<ul>
 <li>检查 ASM 状态及配置</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826761.png&amp;size=m" alt=""></p>
<ul>
 <li>检查 TNS 的状态及配置</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826849.png&amp;size=m" alt=""></p>
<ul>
 <li>检查 SCAN 的状态及配置</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826898.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528826986.png&amp;size=m" alt=""></p>
<ul>
 <li>检查 VIP 的状态及配置</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528827041.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<blockquote>
 <p>本文档由社区会员分享，点击阅读原文可下载原文档</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528827165.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629925&amp;idx=2&amp;sn=6a85bfd25f3dd3cb3f7187d3a14b98ef&amp;chksm=bef919a3898e90b54ba9157182888170dae93a48cc98fecd50776c01e5edc3ab2ecb4c545528&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/a886ce80-d41f-44cc-bb69-20bbc5e5dedb</guid><dc:creator>黄健</dc:creator><category>周末送资料</category><pubDate>Wed, 6 Nov 2024 01:18:04 GMT</pubDate></item><item><title><![CDATA[RAC 群集配置及恢复手册]]></title><link>https://wiki.dgfygk.com/archives/2ab47688-08ea-4f68-9b49-f75eabfac4c6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=RAC%20%E7%BE%A4%E9%9B%86%E9%85%8D%E7%BD%AE%E5%8F%8A%E6%81%A2%E5%A4%8D%E6%89%8B%E5%86%8C&amp;url=/archives/2ab47688-08ea-4f68-9b49-f75eabfac4c6" width="1" height="1" alt="" style="opacity:0;">
<p>由社区专家王巧雷分享，王巧雷撰写有多篇优秀技术文章及分享多篇资料。欢迎访问专家个人主页：http://www.talkwithtrend.com/home/space.php?uid=8767</p>
<p>
 <br>
</p>
<p><strong>RAC配置及存储崩溃恢复示例</strong></p>
<p>
 <br>
</p>
<p>环境介绍</p>
<p>创建虚拟机</p>
<p>安装操作系统</p>
<p>创建共享存储</p>
<p>安装RAC前的准备工作</p>
<ol>
 <li>
  <p>创建用户组和目录</p>
 </li>
 <li>
  <p>修改oracle用户的环境变量</p>
 </li>
 <li>
  <p>修改系统参数</p>
 </li>
 <li>
  <p>配置两节点的时间同步</p>
 </li>
 <li>
  <p>配置用户等效性</p>
 </li>
 <li>
  <p>配置裸设备</p>
 </li>
 <li>
  <p>创建ASM磁盘</p>
 </li>
 <li>
  <p>为归档配置NFS</p>
 </li>
</ol>
<p>安装配置oracle clusterware</p>
<p>安装clusterware软件</p>
<p>执行脚本</p>
<p>安装数据库软件</p>
<p>安装10.2.0.4补丁</p>
<p>升级clusterware</p>
<p>升级database</p>
<p>创建侦听</p>
<p>创建数据库</p>
<p>创建数据表空间</p>
<ol>
 <li>
  <p>创建表空间</p>
 </li>
 <li>
  <p>创建用户</p>
 </li>
 <li>
  <p>创建表并插入数据</p>
 </li>
 <li>
  <p>强制产生归档</p>
 </li>
</ol>
<p>备份数据</p>
<p>关闭RAC集群</p>
<p>备份VOTEDISK</p>
<p>备份OCR</p>
<p>备份数据设备文件</p>
<p>删除oracle rac共享存储分区，执行恢复</p>
<p>启动crs和群集</p>
<p>检查数据</p>
<p>结论</p>
<p>
 <br>
</p>
<ul>
 <li><strong>环境介绍</strong></li>
</ul>
<p>
 <br>
</p>
<p>本次试验是以windows 7平台下的vmware8虚拟环境来实现的，信息如下表：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528829478.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ul>
 <li><strong>创建虚拟机</strong></li>
</ul>
<p>
 <br>
</p>
<p>创建rhel5类型的虚拟机，过程略，虚拟机配置如下</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528829627.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>*</p>
<p><strong>安装操作系统</strong></p>
<hr>
<p>
 <br>
</p>
<p>安装centos5.6操作系统，ip和主机名等信息按照规划表配置，过程略</p>
<p>*</p>
<p><strong>创建共享存储</strong></p>
<hr>
<p>
 <br>
</p>
<p>由于oracle rac环境需要共享存储，因此我们要开辟一个新的文件夹来方法共享磁盘文件（E:\vm\linux_serias\share5）。</p>
<p>
 <br>
</p>
<p>节点一：</p>
<p>
 <br>
</p>
<ol>
 <li>关闭虚拟机操作系统，打开虚拟机编辑页面，点击"add”</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528829749.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>在弹出的添加硬件向导的对话框中，选择harddisk</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528829863.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择创建一个新的磁盘</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528829972.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>按图中所示，勾选参数。</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528830076.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>磁盘大小选为1G，选择立即分配空间。</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528830213.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>共享磁盘文件的位置选择与系统文件夹不同的位置，点击完成。</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528830333.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择刚刚创建的磁盘，点击右侧的高级选项，在弹出的对话框中，将其该为1:0</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528830450.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>以相同的方法，添加其他的几块共享磁盘，添加完全后如下</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528830628.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>使用记事本打开虚拟机文件winsql1.vmx，将以下内容加入</li>
</ol>
<p>
 <br>
</p>
<p>disk.locking = “FALSE”</p>
<p>diskLib.dataCacheMaxSize = “0”</p>
<p>scsi1.sharedBus = “virtual”</p>
<p>scsi1:0.deviceType = “disk”</p>
<p>节点二：</p>
<p>
 <br>
</p>
<p>在创建磁盘时注意选择使用已存在的磁盘选项，其他操作和节点一相同。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528830756.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>*</p>
<p><strong>安装RAC前的准备工作</strong></p>
<hr>
<h3><strong>1.创建用户组和目录</strong></h3>
<p>
 <br>
</p>
<p>两个节点都执行</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528830873.png&amp;size=m" alt=""></p>
<h3><strong>2. 修改oracle用户的环境变量</strong></h3>
<p>
 <br>
</p>
<p>两个节点均执行，执行完毕后退出用户再次登录。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831031.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>3. 修改系统参数</strong></p>
<p>
 <br>
</p>
<p>以root用户修改，两个节点均执行</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831175.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831296.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831392.png&amp;size=m" alt=""></p>
<h3><strong>4. 配置两节点的时间同步</strong></h3>
<p>
 <br>
</p>
<p>A. 开启节点一的时间服务</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831520.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>B. 将同步脚本写入节点二的开机自启动</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831571.png&amp;size=m" alt=""></p>
<h3><strong>5. 配置用户等效性</strong></h3>
<p>
 <br>
</p>
<p>两节点均以oracle用户执行</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831672.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点一执行</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831804.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二执行</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831881.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>等效性测试</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528831944.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832068.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>6. 配置裸设备</strong></p>
<p>
 <br>
</p>
<p>6.1. 将识别到的共享磁盘使用fdisk命令进行分区，分区信息依照表二，过程略</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832147.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832256.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832392.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>6.2 修改/etc/udev/rules.d/60-raw.rules文件，来配置裸设备信息，两个节点都运行</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832468.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>6.3 重启服udev务</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832527.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>6.4查看裸设备</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832609.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>6.5 创建数据文件软连接，两个节点均执行</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832750.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>6.6. 查看软连接</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832835.png&amp;size=m" alt=""></p>
<h3><strong>7. 创建ASM磁盘</strong></h3>
<p>
 <br>
</p>
<p>A. 检查asm包是否安装</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832910.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>B. 两个节点都配置ASMlib</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528832990.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833065.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>C. 在节点一创建asm磁盘</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833203.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>D. 在节点二扫描并识别磁盘</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833347.png&amp;size=m" alt=""></p>
<h3><strong>8. 为归档配置NFS</strong></h3>
<p>
 <br>
</p>
<p>由于oracle rac环境下的归档不能放在裸设备上，为了方便备份，我们在每个节点上都配置了一个单独的归档目录，并在两个节点间将这两个目录互为NFS</p>
<p>
 <br>
</p>
<ol>
 <li>确认两个节点都安装了nfs的包</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833401.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>配置NFS</li>
</ol>
<p>
 <br>
</p>
<p>节点一配置导出</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833493.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二识别并挂载：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833616.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二配置导出</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833681.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点一配置并识别：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833758.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>写入/etc/fstab文件，配置开机自动挂载</li>
</ol>
<p>
 <br>
</p>
<p>节点一</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833856.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833907.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ul>
 <li><strong>安装配置oracle clusterware</strong></li>
</ul>
<h3><strong>安装clusterware软件</strong></h3>
<p>
 <br>
</p>
<ol>
 <li>编辑/etc/redhat-release文件，将如下内容写入，否则自检过不去</li>
</ol>
<p>redhat-4</p>
<p>
 <br>
</p>
<ol>
 <li>切换到clusterware安装目录，执行./runInstaller 开始安装</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528833990.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>安装清单目录和安装组默认，下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528834109.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>通过浏览按钮更正crs的目录，点击下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528834353.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>忽略系统警告，下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528834426.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>点击add添加节点二信息</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528834526.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528834692.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528834816.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>修改两个网卡的作用</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528834923.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择单路模式的集群注册信息，并填入路径</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835087.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>表决盘选择外部方式并写入路径</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835136.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>确认细心无误，点击安装</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835243.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835390.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3><strong>执行脚本</strong></h3>
<p>
 <br>
</p>
<ol>
 <li>安装过程中会出现运行脚本提示</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835525.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>以root用户依次执行所提示脚本</li>
</ol>
<p>
 <br>
</p>
<p>节点一执行orainstRoot.sh</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835667.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二执行orainstRoot.sh</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835714.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点一执行root.sh</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835791.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528835919.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二执行root.sh</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836081.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836155.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>修改vipca的脚本内容，在原有的基础上添加红色字体部分。</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836297.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>在节点二上以root身份手动执行vipca</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836376.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>解决方法：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836461.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836591.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836713.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836836.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528836934.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837065.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837202.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837274.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>点击exit完成集群软件的安装</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837408.png&amp;size=m" alt=""></p>
<p>*</p>
<p><strong>安装数据库软件</strong></p>
<hr>
<p>
 <br>
</p>
<p>如下软件包需要提前安装</p>
<p>
 <br>
</p>
<p>binutils-2.15.92.0.2-10.EL4</p>
<p>compat-db-4.1.25-9</p>
<p>control-center-2.8.0-12</p>
<p>gcc-3.4.3-9.EL4</p>
<p>gcc-c++-3.4.3-9.EL4</p>
<p>glibc-2.3.4-2</p>
<p>glibc-common-2.3.4-2</p>
<p>gnome-libs-1.4.1.2.90-44.1</p>
<p>libstdc++-3.4.3-9.EL4</p>
<p>libstdc++-devel-3.4.3-9.EL4</p>
<p>make-3.80-5</p>
<p>pdksh-5.2.14-30</p>
<p>sysstat-5.0.5-1</p>
<p>xscreensaver-4.18-5.rhel4.2</p>
<p>libaio-0.3.96</p>
<p>
 <br>
</p>
<ol>
 <li>切换到数据库安装目录，执行./runInstaller</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837470.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择企业版进行安装</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837535.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>安装清单目录和安装组默认</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837700.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择cluster方式安装，勾选第二个节点</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837791.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>只安装软件</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528837932.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>确认无误后，开始安装</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838056.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838173.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838301.png&amp;size=m" alt=""></p>
<ol>
 <li>执行脚本</li>
</ol>
<p>
 <br>
</p>
<p>节点一：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838434.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838574.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>点击退出完成安装</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838673.png&amp;size=m" alt=""></p>
<p>*</p>
<p><strong>安装10.2.0.4补丁</strong></p>
<hr>
<h3><strong>升级clusterware</strong></h3>
<ol>
 <li>切换到补丁目录，执行安装程序</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838788.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>修改为crs目录，下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528838881.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择升级节点</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839017.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>执行预安装检查通过</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839153.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>确认无误，点击install</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839304.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839448.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839591.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>执行脚本</li>
</ol>
<p>
 <br>
</p>
<p>节点1</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839710.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839839.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528839963.png&amp;size=m" alt=""></p>
<h3><strong>升级database</strong></h3>
<p>
 <br>
</p>
<ol>
 <li>切换到补丁目录，执行升级脚本</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528840090.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选数据库目录，点下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528840236.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>默认为群集方式，下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528840358.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>预安装检查通过，下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528840490.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>默认下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528840672.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>确认无误后，点击install</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528840823.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528840943.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>提示执行脚本</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841062.png&amp;size=m" alt=""></p>
<p>节点一</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841126.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点二：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841263.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>点击exit，完成升级</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841384.png&amp;size=m" alt=""></p>
<p>*</p>
<p><strong>创建侦听</strong></p>
<hr>
<p>
 <br>
</p>
<ol>
 <li>在一个节点上以oracle运行netca</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841457.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择两个节点</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841593.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择创建侦听</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841657.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择添加</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841714.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>填写侦听名称</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841841.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选tcp模式</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528841963.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择默认的1521端口</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842072.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>不继续创建侦听</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842202.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>创建完毕</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842256.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>点击finish退出安装</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842364.png&amp;size=m" alt=""></p>
<p>*</p>
<p><strong>创建数据库</strong></p>
<hr>
<p>
 <br>
</p>
<ol>
 <li>在任意一个节点以oracle用户执行dbca，选择集群数据库</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842485.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择创建数据库</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842616.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择两个节点</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842760.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择一般用途</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528842892.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>填写sid</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843034.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>默认配置即可，下一步</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843107.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>设置密码</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843236.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择asm</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843299.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>输入asm实例密码，并选择pfile</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843363.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择是，创建asm实例</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843485.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择创建新的磁盘组</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843576.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>创建两个新的磁盘组:FLASH和DATA</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843712.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843831.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528843968.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844097.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择数据文件磁盘组为DATA</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844198.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>选择闪存区磁盘组为FLASH，启用归档，编辑归档路径</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844331.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844456.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>不创建示例库</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844530.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>暂不配置数据库服务</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844687.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>可在此页面下调整内存参数、字符集和连接数等等</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844814.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>数据库存储信息概览</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528844905.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>点击finish，开始创建</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845034.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845170.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845303.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845375.png&amp;size=m" alt=""></p>
<p>创建完成后检查rac的各项服务，均正常</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845576.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>*</p>
<p><strong>创建数据表空间</strong></p>
<hr>
<h3><strong>1. 创建表空间</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845693.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3><strong>2. 创建用户</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845831.png&amp;size=m" alt=""></p>
<h3><strong>3. 创建表并插入数据</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528845902.png&amp;size=m" alt=""></p>
<h3><strong>4. 强制产生归档</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846001.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>*</p>
<p><strong>备份数据</strong></p>
<hr>
<p>
 <br>
</p>
<p>由于ocr和vote都是基于裸设备，oracle对ocr有自己的备份机制，votedisk使用dd备份，除此之外，我们还可以使用tsm的裸设备映像备份功能直接对ocr和vote进行备份。</p>
<h3><strong>关闭RAC集群</strong></h3>
<p>
 <br>
</p>
<p>关闭数据库相关资源：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846073.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846204.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846344.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>关闭crs</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846503.png&amp;size=m" alt=""></p>
<h3><strong>备份VOTEDISK</strong></h3>
<ol>
 <li>检查votedisk配置</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846576.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>使用dd备份</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846654.png&amp;size=m" alt=""></p>
<h3><strong>备份OCR</strong></h3>
<p>
 <br>
</p>
<p>备份操作都在root下执行，备份恢复时关闭crs服务</p>
<p>
 <br>
</p>
<p>备份：</p>
<p>ocrconfig -export /u01/ocrexp.exp</p>
<p>
 <br>
</p>
<p>恢复：</p>
<p>ocrconfig -import /u01/ocr.exp</p>
<p>Ocr每4个小时自动备份一次，也可以使用ocr的自动备份文件来进行恢复</p>
<h3><strong>备份数据设备文件</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846736.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846837.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528846967.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847051.png&amp;size=m" alt=""></p>
<h3><strong>删除oracle rac共享存储分区，执行恢复</strong></h3>
<p>
 <br>
</p>
<ol>
 <li>删除分区，重建分区，重建完成后如下</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847179.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>打开tsm client执行恢复</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847312.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847388.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847520.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847603.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3><strong>启动crs和群集</strong></h3>
<p>
 <br>
</p>
<p>以root用户启动crs：crsctl start crs</p>
<p>以oracle用户启动rac服务：crs_start -all</p>
<h3><strong>检查数据</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847685.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>*</p>
<p><strong>结论</strong></p>
<hr>
<p>
 <br>
</p>
<p>使用基于tsm裸设备映像的备份方式，可以直接备份oracle rac的裸设备和asm设备文件。</p>
<p>
 <br>
</p>
<p>好处是，在后端存储被破坏掉之后，只需要安装原先的存储规划进行划分，然后直接经映像恢复到新的分区上即可，映像会覆盖新的分区，恢复完成后直接启动rac的各项服务即可。</p>
<p>
 <br>
</p>
<p>坏处是，进行映像备份和恢复会对整个lun进行操作，比较浪费备份存储空间，需要更长的备份恢复时间，也就是说需要更长的停机时间。</p>
<blockquote>
 <p>点及阅读原文可下载原文档（word版，更方便收藏使用）</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注公众号AIX专家俱乐部</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528847769.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629168&amp;idx=1&amp;sn=c124c4f09d003cf9a8557590e9d48efb&amp;chksm=bef91ab6898e93a0e073f9c4c5cc6f6fa017826eed69047fea99c786b3d05b9fb4a8b3729bc5&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/2ab47688-08ea-4f68-9b49-f75eabfac4c6</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 01:16:44 GMT</pubDate></item><item><title><![CDATA[Oracle11GR2 RAC ASM for AIX6.1.4 完全安装手册]]></title><link>https://wiki.dgfygk.com/archives/6a125e23-de16-499a-84ff-f198d7824995</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Oracle11GR2%20RAC%20ASM%20for%20AIX6.1.4%20%E5%AE%8C%E5%85%A8%E5%AE%89%E8%A3%85%E6%89%8B%E5%86%8C&amp;url=/archives/6a125e23-de16-499a-84ff-f198d7824995" width="1" height="1" alt="" style="opacity:0;">
<p>目录</p>
<p>1 主机规划</p>
<p>1.1 硬件环境</p>
<p>1.2 软件环境</p>
<p>1.3 RAC拓朴图</p>
<p>1.4 网络规划</p>
<p>1.4.1 IP地址要求</p>
<p>1.4.2 DNS服务器（可选）</p>
<p>1.5 安装目录规划</p>
<p>1.6 存储规划</p>
<p>1.6.1 grid、ASM安装目录</p>
<p>1.6.2 Clusterware安装目录</p>
<p>1.6.3 OCR、VOTE存储规划</p>
<p>1.6.4 数据库数据文件存储规划</p>
<p>2 安装准备</p>
<p>2.1 系统硬件环境检查</p>
<p>2.1.1 Oracle11gR2 RAC最低安装要求：</p>
<p>2.1.2 验证方法：</p>
<p>2.1.2.1 确认内存大小</p>
<p>2.1.2.2 确认SWAP空间，最少4G</p>
<p>2.1.2.3 确认/tmp大于1.5GB和/oracle目录空间15GB</p>
<p>2.1.2.4 确认系统结构</p>
<p>2.1.2.5 确认系统CPU个数和主频</p>
<p>2.2 安装操作系统软件包</p>
<p>2.2.1 安装FILESETS</p>
<p>2.2.2 安装PATCHES</p>
<p>2.3 配置IP地址</p>
<p>2.4 修改/etc/hosts文件</p>
<p>2.5 文件系统准备</p>
<p>2.5.1 方法1：使用命令创建</p>
<p>2.5.2 方法2：使用smit创建</p>
<p>2.6 调整系统参数</p>
<p>2.6.1 打开异步IO</p>
<p>2.6.2 调整虚拟内存管理</p>
<p>2.6.3 调整maxuproc（系统最大进程数）和ncargs（系统块大小）参数</p>
<p>2.6.4 调整SHELL限制</p>
<p>2.6.5 确认网络配置参数</p>
<p>2.7 创建用户、组</p>
<p>2.7.1 要求</p>
<p>2.7.2 创建命令</p>
<p>2.7.3 修改用户属性</p>
<p>2.7.3.1 grid用户</p>
<p>2.7.3.2 oracle用户</p>
<p>2.7.3.3 root用户</p>
<p>2.8 用户环境变量设置</p>
<p>2.8.1 grid用户</p>
<p>2.8.2 oracle用户</p>
<p>2.9 用户shell限制</p>
<p>2.10 创建安装目录并修改权限</p>
<p>2.11 SSH信任关系设置（可选）</p>
<p>2.11.1 首先在两台机器上安装好OpenSSH软件；</p>
<p>2.11.2 然后在grid安装中选择自动配置SSH双机信任关系</p>
<p>2.11.2.1 方法1</p>
<p>2.11.2.2 方法2</p>
<p>2.12 配置磁盘设备</p>
<p>2.12.1 设置磁盘属性</p>
<p>2.12.2 修改用于数据库存储的PV的属主</p>
<p>2.12.3 修改用于数据库存储的PV的权限</p>
<p>2.12.4 清除PVID</p>
<p>2.13 配置NTP服务（可选）</p>
<p>3 安装Grid</p>
<p>3.1 Grid安装前检查</p>
<p>3.2 执行rootpre.sh</p>
<p>3.3 安装grid</p>
<p>3.4 Clusterware安装校验</p>
<p>4 安装Database</p>
<p>5 创建ASM磁盘组</p>
<p>6 DBCA创建数据库</p>
<p>7 安装完成后</p>
<p>7.1 备份root.sh</p>
<p>7.2 重新编译全部PL/SQL modules</p>
<p>7.3 备份spfile文件</p>
<p>7.4 RAC集群系统维护命令</p>
<p>7.5 启动和关闭集群系统命令</p>
<p>7.5.1 启动集群件和集群数据库</p>
<p>7.5.2 关闭RAC集群数据库和集群件</p>
<p>
 <br>
</p>
<p><strong>1 主机规划</strong></p>
<p><strong>1.1 硬件环境</strong></p>
<p>服务器：两台IBM P630 16C(Power6 CPU)/32GB；</p>
<p>存储：一台DS4000存储；</p>
<p><strong>1.2 软件环境</strong></p>
<p><strong>数据库：</strong></p>
<p>aix.ppc64_11gR2_database_1of2.zip</p>
<p>aix.ppc64_11gR2_database_2of2.zip、</p>
<p><strong>集群件（10G中的clusterware）：</strong></p>
<p>aix.ppc64_11gR2_grid.zip这三个包</p>
<p><strong>操作系统：</strong></p>
<p>AIX6.1</p>
<p>原来版本是6100-01</p>
<p>升级后为：6100-04-01-0944</p>
<p><strong>注意：</strong>解压时aix.ppc64_11gR2_database_1of2.zip、aix.ppc64_11gR2_database_2of2.zip这两个包要解到同一个目录下，aix.ppc64_11gR2_grid.zip包解到另一个不同的目录下。</p>
<p><strong>1.3 RAC拓朴图</strong>（略）</p>
<p><strong>1.4 网络规划</strong></p>
<p><strong>1.4.1 IP地址要求</strong></p>
<p> 每个节点一个public IP</p>
<p> 每个节点一个public VIP</p>
<p> 每个节点一个private IP</p>
<p> 一个用于集群的SCAN名称和一到三个和public IP同网段的IP，本处用一个</p>
<p> public IP,VIP和private IP写入/etc/hosts文件,scan名注册到DNS</p>
<p> 配置DNS搜索域和解析顺序</p>
<p>Oracle 11gR2 RAC中对网络的要求与10g稍有些不同，在Oracle 11gR2 RAC中引入了SCAN的概念。SCAN全称是Single Client Access Name ，它是Oracle RAC 11gR2版本的一个新特性。它为需要访问Oracle集群数据的客户端提供一个简单名字。它的好处是如果你使用SCAN当你增长或踢除集群中的节点时客户端不需要做任何改变，这一点在三层架构应用系统中（数据库-应用服务器-客户机）优势明显。使用单个名字去访问集群允计客户端使用EZConnect 客户端和简单JDBC thin URL去访问运行在集群中的任何数据库，而不用管在集群中那个或那些服务器（或叫实例）是Active的。</p>
<p>配置好固定IP（public），ORACLE的私有IP（priv），和VIP，尤其要注意的是：两台机器需配置对应关系一致的IP与网卡。如：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528887943.png&amp;size=m" alt=""></p>
<p>注：SCAN IP,有的资料说是1－3个，而有的资料说是必须要3个，未证实。</p>
<p>上规划表中7、8项二选一即可，如果没有DNS服务器，也可以使用/etc/hosts解析scan单IP</p>
<p>ASM规划</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888112.png&amp;size=m" alt=""></p>
<p>若是需要捆绑网卡做心跳，可以使用：</p>
<h1>smit etherchannel</h1>
<p><strong>1.4.2 DNS服务器（可选）</strong></p>
<p>前面说到在Oracle 11gR2 RAC中引入了SCAN的概念，而ORACLE推荐使用DNS解析的方法来实现SCAN，这就要求网络中有一台DNS服务器用来解析SCAN地址。</p>
<p>如果你网络中没有专用的DNS服务器，那么也可以用现有的数据库服务器兼作DNS服务器，但是不推荐这样做，因为一旦这台机器当机了，DNS就无法解析，可以导致监听的异步常。</p>
<p>在AIX中搭建DNS服务器的方法在后文中将讲到。</p>
<p>这里使用/etc/hosts来解析scan</p>
<p><strong>1.5 安装目录规划</strong></p>
<p>安装目录规划原则：建立/u01文件系统用来安装grid、datbase程序。程序都安装在/u01/app下面，对于grid与database分别建立不同的目录，分配不同的权限。其中grid的ORACLE_BASE和ORACLE_HOME建议安装在不同的目录下，具体规划如下：</p>
<p>新建30G lv：oralv</p>
<p>新建文件系统，挂载点：/u01</p>
<p>grid base目录：/u01/app/grid #grid用户的ORACLE_BASE</p>
<p>grid asm安装目录：/u01/app/11.2/grid #grid用户的ORACLE_HOME，也即是安装时的software location</p>
<p>Oracle base目录：/u01/app/oracle #oracle用户的ORACLE_BASE</p>
<p><strong>注：此规划为后来总结，本实验中与此略有出入。Grid用户的ORACLE_BASE、ORACLE_HOME都需要手工创建。Oracle用户只创建ORACLE_BASE目录即可。</strong></p>
<p><strong>1.6 存储规划</strong></p>
<p><strong>1.6.1 grid、ASM安装目录</strong></p>
<p>在Oracle 11gR2中grid和ASM是安装在同一目录下的，我们将grid和ASM安装在/u01/grid目录下，为JFS2文件系统，大小建立为30GB以上。本次示例25G</p>
<p><strong>1.6.2 Clusterware安装目录</strong></p>
<p>我们将Clusterware安装在/u01/db目录下。</p>
<p><strong>1.6.3 OCR、VOTE存储规划</strong></p>
<p>在我们的方案中规划如下：</p>
<p>OCR hdisk6 容量：5G</p>
<p>VOTE hdisk5 容量：4G</p>
<p>DATA hdisk2、4 容量：50G、40G</p>
<p><strong>1.6.4 数据库数据文件存储规划</strong></p>
<p>规划使用2个50GB和40G的阵列盘用来存放数据库数据文件文件，这2块盘在ASM中做成external磁盘组。</p>
<p>
 <br>
</p>
<p><strong>2 安装准备</strong></p>
<p><strong>2.1 系统硬件环境检查</strong></p>
<p><strong>2.1.1 Oracle11gR2 RAC最低安装要求：</strong></p>
<p>内存：</p>
<p>安装grid infrastructure最少需要1.5GB的内存，</p>
<p>安装grid infrastructure和RAC最少需要2.5GB的内存</p>
<p>swap：最少需要4GB,有的资料介绍最少1.5G，不过安装环境检查时显示需要4G。</p>
<p>temp：推荐10G，可以将安装文件上传至/tmp目录下，最少1GB</p>
<p>安装目录：grid infrastructure+ASM+Clusterware最少需要12GB</p>
<p>操作系统：</p>
<p>AIX 6.1 TL 01 SP1 (“6100-01-01), 64-bit kernel</p>
<p><strong>2.1.2 验证方法：</strong></p>
<p><strong>2.1.2.1 确认内存大小</strong></p>
<h1>lsattr -El sys0 -a realmem</h1>
<p>realmem 4194304 Amount of usable physical memory in Kbytes False</p>
<p><strong>2.1.2.2 确认SWAP空间，最少4G。</strong></p>
<h1>lsps -a</h1>
<p>若不够4G，需要增加PPS数。本系统一个PPS为128M，所以增加20个，使swap达到4G。PPS大小可以通过lsvg rootvg查看。</p>
<h1>chps -s 20 hd6</h1>
<h1>lsps -a</h1>
<p><strong>2.1.2.3 确认/tmp大于1.5GB和/oracle目录空间15GB</strong></p>
<p>推荐oracle安装目录在25GB左右，grid infrastructure+ASM安装在/oracle/grid目录下，其中Oracle11g Clusterware安装在/oracle/db目录下.</p>
<p>chfs –a size=5G /tmp</p>
<p><strong>2.1.2.4 确认系统结构</strong></p>
<h1>getconf HARDWARE_BITMODE</h1>
<p>64</p>
<p><strong>2.1.2.5 确认系统CPU个数和主频：</strong></p>
<h1>lsdev -C \| grep proc</h1>
<p>proc0 Available 00-00 Processor</p>
<p>proc1 Available 00-01 Processor</p>
<h1>lsattr -El proc0</h1>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888191.png&amp;size=m" alt=""></p>
<p><strong>2.2 安装操作系统软件包</strong></p>
<p><strong>2.2.1 安装FILESETS</strong></p>
<p><strong>AIX 6.1需要安装如下软件包：</strong></p>
<p>bos.adt.base</p>
<p>bos.adt.lib</p>
<p>bos.adt.libm</p>
<p>bos.perf.libperfstat 6.1.2.1 or later</p>
<p>bos.perf.perfstat</p>
<p>bos.perf.proctools</p>
<p>rsct.basic.rte</p>
<p>rsct.compat.clients.rte</p>
<p>xlC.aix61.rte 10.1.0.0 (or later)</p>
<p><strong>AIX 5.3需要安装如下软件包：</strong></p>
<p>bos.adt.base</p>
<p>bos.adt.lib</p>
<p>bos.adt.libm</p>
<p>bos.perf.libperfstat 5.3.9.0 or later</p>
<p>bos.perf.perfstat</p>
<p>bos.perf.proctools</p>
<p>rsct.basic.rte</p>
<p>rsct.compat.clients.rte</p>
<p>xlC.aix50.rte 10.1.0.0 (or later)</p>
<p>以上filesets安装与否可以用命令lslpp －l进行检查确认。默认安装是不全的，需要手工进行添加。同时系统盘的版本与上述也有差异，安装尝试。</p>
<p><strong>2.2.2 安装PATCHES</strong></p>
<p><strong>AIX版本要求如下</strong></p>
<p>AIX 5L V5.3 TL 09 SP1 (“5300-09-01”), 64 bit kernel或以上版本</p>
<p>AIX 6.1 TL 02 SP1 (“6100-02-01), 64-bit kernel或以上版本</p>
<p>检查方法：</p>
<h1>oslevel -r</h1>
<p>6100-03</p>
<p>其它单个补丁的要求如下：</p>
<p>AIX 6L installations All AIX 6L 6.1 installations Authorized Problem Analysis</p>
<p>Reports (APARs) for AIX 5L v. 5.3 ML06, and the following AIX</p>
<p>fixes:</p>
<p>IZ41855</p>
<p>IZ51456</p>
<p>IZ52319</p>
<p>AIX 5L installations All AIX 5L 5.3 installations Authorized Problem Analysis</p>
<p>Reports (APARs) for AIX 5L v. 5.3 ML06, and the following AIX</p>
<p>fixes:</p>
<p>IZ42940</p>
<p>IZ49516</p>
<p>IZ52331</p>
<p>验证：#/usr/sbin/instfix -i -k IZ41855</p>
<p><strong>安装补丁：</strong></p>
<p><strong>由于6100-04不需要任何补丁，所以我们将系统升级到6100-04（但是安装grid的时候还是出现3个包未安装提示）</strong></p>
<p>1、 从IBM官网上下载6100-04-00-0943</p>
<p>2、 将补丁文件上传至/tmp/tools下</p>
<p>3、 smit update_all</p>
<p>选择不提交，保存被覆盖的文件，可以回滚操作，接受许可协议</p>
<p>COMMIT software updates? No</p>
<p>SAVE replaced files? yes</p>
<p>ACCEPT new license agreements? Yes</p>
<p>升级完后查看：</p>
<h1>oslevel -s</h1>
<p>6100-04-01-0944</p>
<p><strong>2.3 配置IP地址</strong></p>
<p>根据前面的规划先配置要两台机器的IP地址，就是ibmdb1、ibmdb1-priv和ibmdb2、ibmdb2-priv这四个IP地址。</p>
<p>同样配置几块网卡的IP地址，基中服务地址要配置网关，网关地址为：192.168.17.254。</p>
<p><strong>2.4 修改/etc/hosts文件</strong></p>
<p>修改每个节点上的/etc/hosts文件。</p>
<p>127.0.0.1 loopback localhost # loopback (lo0) name/address</p>
<p>192.168.17.237 ibmdb1</p>
<p>192.168.17.236 ibmdb1-vip</p>
<p>10.10.10.237 ibmdb1-priv</p>
<p>192.168.17.239 ibmdb2</p>
<p>192.168.17.238 ibmdb2-vip</p>
<p>10.10.10.239 ibmdb2-priv</p>
<p>192.168.17.229 scan</p>
<p>注意：如果你的环境里不具备DNS服务器，那么SCAN地址解析可以通过/etc/hosts或GNS来实现，这需要在你的所有数据库主机的host文件里添加一个SCAN地址，并且这个地址的解析名不要超过16个字符，超过的话，oracle只会解析前16个字符。本例中我们通过/etc/hosts来解析scan。</p>
<p><strong>2.5 文件系统准备</strong></p>
<p>创建/u01文件系统</p>
<p><strong>2.5.1 方法1：使用命令创建</strong></p>
<p>1、mkdir /u01</p>
<p>2、建立一个lv　　　mklv –t jfs2 –y u01lv rootvg 200 #200个pps，可视需要而定</p>
<p>3、建立一个文件系统 crfs –v jfs2 -d /dev/u01lv -m /u01</p>
<p>4、mount文件系统　　mount /u01</p>
<p>5、使用smit chfs，修改/u01启动自动mount。</p>
<p>创建/oracle文件系统</p>
<p>1、mkdir /oracle</p>
<p>2、建立一个lv　　　mklv –t jfs2 –y u01lv rootvg 200 #200个pps，可视需要而定</p>
<p>3、建立一个文件系统 crfs –v jfs2 -d /dev/u01lv -m /u01</p>
<p>4、mount文件系统　　mount /u01</p>
<p>5、使用smit chfs，修改/u01启动自动mount。</p>
<p><strong>2.5.2 方法2：使用smit创建</strong></p>
<p>Smit mklv:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888331.png&amp;size=m" alt=""></p>
<p>Smit fs:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888475.png&amp;size=m" alt=""></p>
<p>在上图中，由于我们已经建议好了LV,所以选择如图。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888612.png&amp;size=m" alt=""></p>
<p>在上图中，我们设置挂载点、是否自启动。</p>
<p>完成后，mount /u01</p>
<p>挂载/u01文件系统。</p>
<p><strong>2.6 调整系统参数</strong></p>
<p><strong>2.6.1 打开异步IO</strong></p>
<p>AIX5.3中，在安装grid软件前执行rootpre.sh会自动打开异步IO服务，在AIX6.1中默认异步IO服务是打开的。推荐的aio_maxreqs值为 64k (65536)，检查方法如下：</p>
<p><strong>AIX 6.1</strong></p>
<h1>ioo -o aio_maxreqs</h1>
<p>aio_maxreqs = 65536</p>
<p><strong>AIX 5.3</strong></p>
<h1>lsattr -El aio0 -a maxreqs</h1>
<p>maxreqs 65536 Maximum number of REQUESTS True</p>
<p>查看当前启动了多少异步IO进程的方法：</p>
<h1>ps -ek\|grep -v grep\|grep –v posix_aioserver\|grep -c aioserver</h1>
<p><strong>调整aio_maxservers和aio_minservers为合适的值</strong></p>
<p>原则：</p>
<p>1、 调整maxservers的值为10倍的磁盘数量，但是不要超赤80</p>
<p>2、 在高i/o的时间周期监控AIO的性能情况，如果所有的AIO服务进程都已经启动，那么增大maxservers的值。但是，过多的AIO进程也会增加系统的负载</p>
<p>3、 监控AIO服务进程的使用情况：</p>
<h1>ps -ek\|grep -v grep\|grep –v posix_aioserver\|grep -c aioserver</h1>
<p>4、 调整参数</p>
<p>Ioo –po aio_maxservers=80 / ioo –po aio_minservers=40</p>
<p><strong>2.6.2 调整虚拟内存管理</strong></p>
<p>Oracle推存使用vmo命令去调优虚拟内存管理，参数推荐如下：</p>
<p>命令如下:</p>
<p>vmo -p -o minperm%=3</p>
<p>vmo -p -o maxperm%=90</p>
<p>vmo -p -o maxclient%=90</p>
<p>vmo -p -o lru_file_repage=0</p>
<p>vmo -p -o strict_maxclient=1</p>
<p>vmo -p -o strict_maxperm=0</p>
<p>You must restart the system for these changes to take effect.</p>
<p><strong>2.6.3 调整maxuproc（系统最大进程数）和ncargs（系统块大小）参数</strong></p>
<h1>chdev -l sys0 -a maxuproc=16384</h1>
<p>sys0 changed</p>
<p>也可以使用：smitty chgsys修改"Maximum number of PROCESSES allowed per user”</p>
<p>修改完成后查看：</p>
<h1>lsattr -El sys0 -a maxuproc</h1>
<p>maxuproc 16384 Maximum number of PROCESSES allowed per user True</p>
<h1>chdev -l sys0 -a ncargs=256</h1>
<p>sys0 changed</p>
<p><strong>2.6.4 调整SHELL限制</strong></p>
<ol>
 <li>在 /etc/security/limits文件中增加如下行:</li>
</ol>
<p>default:</p>
<p>fsize = -1</p>
<p>core = 2097151</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p><strong>2.6.5 确认网络配置参数</strong></p>
<p>用# /usr/sbin/no -a \| more来查看网络参数是否满足下面的要求</p>
<p> PARAMETER RECOMMENDED VALUE</p>
<p>ipqmaxlen 512</p>
<p>rfc1323 1</p>
<p>sb_max 1500000</p>
<p>tcp_recvspace 65536</p>
<p>tcp_sendspace 65536</p>
<p>udp_recvspace 1351680 这个值应该是udp_sendspace的10倍，但是必须小于sb_max</p>
<p>udp_sendspace 135168 这个值至少应该是4K＋（db_block_size*db_multiblock_read_count）的大小</p>
<p> 修改命令</p>
<p>no -r -o ipqmaxlen=521</p>
<p>no -p -o rfc1323=1</p>
<p>no -p -o sb_max=1500000</p>
<p>no -p -o tcp_recvspace=65536</p>
<p>no -p -o tcp_sendspace=65536</p>
<p>no -p -o udp_recvspace=1351680</p>
<p>no -p -o udp_sendspace=13516</p>
<p>在修改前需要先查看系统设置的兼容性：</p>
<h1>lsattr -El sys0 -a pre520tune</h1>
<p>1、输出如：</p>
<p>pre520tune disable Pre-520 tuning compatibility mode True</p>
<p>说明是不兼容方式，这样就可以用上面的方式调整：</p>
<p>2、如果是compatibility模式，则：</p>
<h1>/usr/sbin/no -o parameter_name=value</h1>
<p>并且编辑文件/etc/rc.net加入下面内容：</p>
<p>if [ -f /usr/sbin/no ] ; then</p>
<p>/usr/sbin/no -o udp_sendspace=65536</p>
<p>/usr/sbin/no -o udp_recvspace=655360</p>
<p>/usr/sbin/no -o tcp_sendspace=65536</p>
<p>/usr/sbin/no -o tcp_recvspace=65536</p>
<p>/usr/sbin/no -o rfc1323=1</p>
<p>/usr/sbin/no -o sb_max=2*655360</p>
<p>/usr/sbin/no -o ipqmaxlen=512</p>
<p>fi</p>
<p><strong>2.7 创建用户、组</strong></p>
<p><strong>2.7.1 要求</strong></p>
<p>本次安装我们就规划了一个组，但是规划两个用户，一个用于管理ASM实例，另一个用于管理DB实例。</p>
<p>创建一个组：</p>
<p>dba</p>
<p>创建二个用户：</p>
<p>grid：主要用与安装grid、ASM，管理ASM实例,grid用户主组为dba，属组为：dba。</p>
<p>oracle：要用与安装Clusterware，管理db实例，oracle用户主组为dba，属组为：dba。</p>
<p><strong>2.7.2 创建命令：</strong></p>
<h1>mkgroup -'A' id='1000' adms='root' dba</h1>
<h1>mkuser id='1000' pgrp='dba' groups='dba' home='/home/grid' grid</h1>
<h1>mkuser id='1100' pgrp='dba' groups='dba' home='/home/oracle' oracle</h1>
<p>！注意保持双机用户与组的ID的一致。</p>
<p><strong>2.7.3 修改用户属性</strong></p>
<p><strong>2.7.3.1 grid用户</strong></p>
<p>1、在两台主机上要保证grid用户具有以下能力：</p>
<p>CAP_NUMA_ATTACH, CAP_BYPASS_RAC_VMM, and CAP_PROPAGATE</p>
<p>查看grid用户能力：</p>
<h1>lsuser -a capabilities grid</h1>
<p>增加grid用户能力：</p>
<h1>chuser capabilities=CAP_NUMA_ATTACH,CAP_BYPASS_RAC_VMM,CAP_PROPAGATE grid</h1>
<p>2、设置grid用户密码：</p>
<h1>passwd grid</h1>
<p>3、在两台主机上最消grid用户shell限制</p>
<p>可以通过smit chuser去修改grid用户限制，或直接修改/etc/security/limits 文件，具体参数如下：</p>
<p>default:</p>
<p>fsize = -1</p>
<p>core = 2097151</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p><strong>2.7.3.2 oracle用户</strong></p>
<p>1、在两台主机上要保证oracle用户具有以下能力：</p>
<p>CAP_NUMA_ATTACH, CAP_BYPASS_RAC_VMM, and CAP_PROPAGATE</p>
<p>查看oracle用户能力：</p>
<h1>lsuser -a capabilities oracle</h1>
<p>增加oracle用户能力：</p>
<h1>chuser capabilities=CAP_NUMA_ATTACH,CAP_BYPASS_RAC_VMM,CAP_PROPAGATE oracle</h1>
<p>2、设置oracle用户密码：</p>
<h1>passwd oracle</h1>
<p>3、在两台主机上最消oracle用户shell限制</p>
<p>可以通过smit chuser去修改oracle用户限制，或直接修改/etc/security/limits 文件，具体参数如下：</p>
<p>default:</p>
<p>fsize = -1</p>
<p>core = 2097151</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p><strong>2.7.3.3 root用户</strong></p>
<p>在两台主机上最消root用户shell限制，因为CRS进程（crsd）需要以root用户运行。</p>
<p>可以通过smit chuser去修改root用户限制，或直接修改/etc/security/limits 文件，具体参数如下：</p>
<p>default:</p>
<p>fsize = -1</p>
<p>core = 2097151</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p><strong>2.8 用户环境变量设置</strong></p>
<p><strong>2.8.1 grid用户</strong></p>
<p>在两台服务器的的grid用户的.profile最后，增加如下内容：</p>
<p>umask 022</p>
<p>export ORACLE_BASE=/u01/app/grid #需手工创建</p>
<p>export ORACLE_HOME=/u01/app/11.2/grid #需手工创建</p>
<p>export ORACLE_SID=+ASM1 #节点2是+ASM2</p>
<p>export NLS_LANG=AMERICAN_AMERICA.UTF8</p>
<p>export NLS_DATE_FORMAT=“yyyy-mm-dd hh24:mi:ss”</p>
<p>export PATH=.:${PATH}:$HOME/bin:$ORACLE_HOME/bin</p>
<p>export PATH=${PATH}:/usr/bin:/bin:/usr/bin/X11:/usr/local/bin</p>
<p>export PATH=${PATH}:$ORACLE_BASE/common/oracle/bin</p>
<p>export ORACLE_PATH=${PATH}:$ORACLE_BASE/common/oracle/sql:.:$ORACLE_HOME/rdbms/admin</p>
<p>export ORACLE_TERM=xterm</p>
<p>export TNS_ADMIN=$ORACLE_HOME/network/admin</p>
<p>export ORA_NLS10=$ORACLE_HOME/nls/data</p>
<p>export LD_LIBRARY_PATH=$ORACLE_HOME/lib</p>
<p>export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$ORACLE_HOME/oracm/lib</p>
<p>export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/lib:/usr/lib:/usr/local/lib</p>
<p>export CLASSPATH=$ORACLE_HOME/JRE</p>
<p>export CLASSPATH=${CLASSPATH}:$ORACLE_HOME/jlib</p>
<p>export CLASSPATH=${CLASSPATH}:$ORACLE_HOME/rdbms/jlib</p>
<p>export CLASSPATH=${CLASSPATH}:$ORACLE_HOME/network/jlib</p>
<p>export THREADS_FLAG=native</p>
<p>export TEMP=/tmp</p>
<p>export TMPDIR=/tmp</p>
<p>export PS1='hostname':'$PWD'“$</p>
<p><strong>2.8.2 oracle用户</strong></p>
<p>在两台服务器的的oracle用户的.profile最后，增加如下内容：</p>
<p>umask 022</p>
<p>export ORACLE_BASE=/u01/app/oracle #需手工创建</p>
<p>export ORACLE_HOME=$ORACLE_BASE/product/11.2</p>
<p>export ORACLE_SID=ytdb1</p>
<p>export ORACLE_UNQNAME=testdb</p>
<p>export NLS_LANG=AMERICAN_AMERICA.UTF8</p>
<p>export NLS_DATE_FORMAT=“yyyy-mm-dd hh24:mi:ss”</p>
<p>export PATH=.:${PATH}:$HOME/bin:$ORACLE_HOME/bin</p>
<p>export PATH=${PATH}:/usr/bin:/bin:/usr/bin/X11:/usr/local/bin</p>
<p>export PATH=${PATH}:$ORACLE_BASE/common/oracle/bin</p>
<p>export ORACLE_PATH=${PATH}:$ORACLE_BASE/common/oracle/sql:.:$ORACLE_HOME/rdbms/admin</p>
<p>export ORACLE_TERM=xterm</p>
<p>export TNS_ADMIN=$ORACLE_HOME/network/admin</p>
<p>export ORA_NLS10=$ORACLE_HOME/nls/data</p>
<p>export LD_LIBRARY_PATH=$ORACLE_HOME/lib</p>
<p>export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$ORACLE_HOME/oracm/lib</p>
<p>export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/lib:/usr/lib:/usr/local/lib</p>
<p>export CLASSPATH=$ORACLE_HOME/JRE</p>
<p>export CLASSPATH=${CLASSPATH}:$ORACLE_HOME/jlib</p>
<p>export CLASSPATH=${CLASSPATH}:$ORACLE_HOME/rdbms/jlib</p>
<p>export CLASSPATH=${CLASSPATH}:$ORACLE_HOME/network/jlib</p>
<p>export THREADS_FLAG=native</p>
<p>export TEMP=/tmp</p>
<p>export TMPDIR=/tmp</p>
<p>export PS1='hostname':'$PWD'“$”</p>
<p><strong>2.9 用户shell限制</strong></p>
<p>更改root,grid,oracle用户的shell限制，要求如下：</p>
<p>fsize = -1</p>
<p>core = -1</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p>更改命令：smit chuser</p>
<p><strong>2.10 创建安装目录并修改权限</strong></p>
<p>mkdir -p /u01/app/grid #grid用户的ORACLE_BASE</p>
<p>mkdir -p /u01/app/11.2/grid #grid用户的ORACLE_HOME</p>
<p>chown -R grid:dba /u01</p>
<p>mkdir -p /u01/app/oracle #oracle用户的ORACLE_BASE</p>
<p>chown -R oracle:dba /u01/app/oracle</p>
<p>chmod -R 755 /u01/</p>
<p>注意：如果grid与orcle安装在同一文件系统下面，如上，在进行权限修改的时候要注意顺序，先进行/u01赋权，再进行/u01/db赋权，否则会被覆盖。</p>
<p><strong>2.11 SSH信任关系设置（可选）</strong></p>
<p>SSH信任关系也可在grid安装时选择自动配置。</p>
<p>注意：Oracle11g R2 grid在AIX上自动配置ssh时会报错，因为Oracle调用的命令路径和AIX系统上命令实际路径不符，可以修改oracle安装程序的sshsetup.sh脚本，或按照oracle调用路径添加程序软连接，具体路径安装过程中Oracle会提示。</p>
<p><strong>2.11.1 首先在两台机器上安装好OpenSSH软件；</strong></p>
<p>具体安装方法本处不详述，需要下载openssh、openssl，安装时需先安装openssl，然后再安装openssh。</p>
<p>也可以通过AIX系统光盘，执行smitty install，选择所有ssh包安装。</p>
<p>安装完毕后可以检查：</p>
<h1>lslpp –l \| grep ssh</h1>
<p><strong>2.11.2 然后在grid安装中选择自动配置SSH双机信任关系</strong></p>
<p><strong>2.11.2.1 方法1</strong></p>
<p> 修改/etc/ssh/sshd_config</p>
<p>将：</p>
<p>RSAAuthentication yes</p>
<p>PubkeyAuthentication yes</p>
<p>AuthorizedKeysFile .ssh/authorized_keys</p>
<p>前面的注释去掉。</p>
<p> 利用命令:ssh-keygen生成key</p>
<p>全部选择默认的就可以 , 生成的private key和public Key会保存在 \~/.ssh目录下 .</p>
<p>注: 为了后面的访问方便, passphrase一行密码一般设置为空.</p>
<p> 将2台机器的public key互相传给对方</p>
<p>可以有好几种方法: ftp , rcp , scp都可以 .这里我们通过FTP将两个节点的\~/.ssh下的id_rsa、id_rsa.pub两个文件分别拷下来传至对方。由于同名，分别将其更改为id_rsa239、id_rsa239.pub、id_rsa237、id_rsa237.pub，为了区分，后面加上其IP标识。</p>
<p> 建立authorized_keys文件</p>
<p>由于上面修改了sshd_config文件 , 其中一行为</p>
<p>AuthorizedKeysFile .ssh/authorized_keys</p>
<p>为认证读取文件的位置 .</p>
<p>我们采取默认的方式 , 在\~/.ssh下touch一个authorized_keys文件.</p>
<p>touch authorized_keys</p>
<p>将传输过来的对方主机的pub key内容 ,追加到authorized_keys文件上,</p>
<p>Node1(192.168.17.237):</p>
<p>bash-3.00# cat id_rsa239.pub &gt; authorized_keys</p>
<p>node2(192.168.17.239):</p>
<h1>cat id_rsa237.pub &gt; authorized_keys</h1>
<p>测试：</p>
<p>ssh 192.168.17.237</p>
<p>ssh 192.168.17.239</p>
<p>第一次登录会出现提示，输入yes后以后就不会了</p>
<p><strong>2.11.2.2 方法2</strong></p>
<p>以下两个节点都执行：</p>
<h1>su – grid</h1>
<p>$mkdir –p \~/.ssh</p>
<p>$chmod 700 \~/.ssh</p>
<p>$/usr/bin/ssh-keygen -t rsa</p>
<p>提示输入密码时，保持为空，直接回车即可。</p>
<p>以下只在节点1上执行：</p>
<p>$ touch \~/.ssh/authorized_keys</p>
<p>$ ssh ibmdb1 cat \~/.ssh/id_rsa.pub&gt;&gt;\~/.ssh/authorized_keys</p>
<p>$ ssh ibmdb2 cat \~/.ssh/id_rsa.pub&gt;&gt;\~/.ssh/authorized_keys</p>
<p>$ scp \~/.ssh/authorized_keys ibmdb2:.ssh/authorized_keys</p>
<p>以下只在节点2上执行：</p>
<p>$ chmod 600 \~/.ssh/authorized_keys</p>
<p>配置完成后按方法1中测试方法进行测试。</p>
<p><strong>2.12 配置磁盘设备</strong></p>
<p><strong>2.12.1 设置磁盘属性</strong></p>
<p>该步骤若不做，在grid安装完成建立ASM时，第二个节点会找不到硬盘，同时加入不了群集。</p>
<p>检测:</p>
<p>lsattr -El hdisk2 \| grep reserve_</p>
<p>查看一个即可</p>
<p>设置保持策略:</p>
<p>如果上面查看的结果是reserve_policy</p>
<p>chdev -l hdisk2 -a reserve_policy=no_reserve</p>
<p>chdev -l hdisk3 -a reserve_policy=no_reserve</p>
<p>chdev -l hdisk4 -a reserve_policy=no_reserve</p>
<p>chdev -l hdisk5 -a reserve_policy=no_reserve</p>
<p>chdev -l hdisk6 -a reserve_policy=no_reserve</p>
<p>如果上面查看的结果是reserve_lock</p>
<p>chdev -l hdisk2 -a reserve_lock=no</p>
<p>.。。</p>
<p>
 <br>
</p>
<p><strong>2.12.2 修改用于数据库存储的PV的属主</strong></p>
<p>chown grid:dba /dev/rhdisk2</p>
<p>chown grid:dba /dev/rhdisk3</p>
<p>chown grid:dba /dev/rhdisk4</p>
<p>chown grid:dba /dev/rhdisk5</p>
<p>chown grid:dba /dev/rhdisk6</p>
<p><strong>2.12.3 修改用于数据库存储的PV的权限</strong></p>
<p>chmod 777 /dev/rhdisk2</p>
<p>chmod 777 /dev/rhdisk3</p>
<p>chmod 777 /dev/rhdisk4</p>
<p>chmod 777 /dev/rhdisk5</p>
<p>chmod 777 /dev/rhdisk6</p>
<p>如果出现两边盘符不一致，可使用软连接实现两边盘符的对应关系一致，如：</p>
<p>Ln –s /dev/rhdisk2 /ShareDisk/grid_lun1</p>
<p>..</p>
<p>Ln –s /dev/rhdisk6 /ShareDisk/data_asm1</p>
<p>…</p>
<p><strong>2.12.4 清除PVID</strong></p>
<p>查看LUN，如果已经有了PVID的话，需要进行清除。</p>
<p>chdev -l hdisk2 -a pv=clear</p>
<p>重复同样的操作，清除2-6所有LUN的PVID</p>
<p><strong>2.13 配置NTP服务（可选）</strong></p>
<p>Oracle 11g R2提供Cluster Time Synchronization Service（CTSS）集群时间同步服务，在没有NTP服务时，该功能可以保证所有RAC节点的时间保持一致。ASM可以作为统一的存储把Oracle Cluster Registry（OCR）和Voting disks统一安装在ASM磁盘上，不再需要单独安装集群文件系统了，11g第二版也不再支持裸设备了（之前可以把集群件安装到裸设备上）。还有一个功能SCAN（Single Client Access Name）即单客户端访问名称而且该功能包括了Failover故障自动切换功能，在访问集群是只写一个SCAN名称就可以了，不需要象以前要把所有节点的VIP写在应用程序的配置文件里面了，这样就大大方便了客户端程序对RAC系统的访问，但该功能需要DNS服务器的支持。SCAN配置也可以采用hosts文件作解析。</p>
<p>如果系统配置了NTP服务，CTSS服务会处于观察者模式，配置NTP具体步骤可参考AIX服务配置。</p>
<p>
 <br>
</p>
<p><strong>3 安装Grid</strong></p>
<p><strong>3.1 Grid安装前检查</strong></p>
<p>切换到grid用户并执行安装前检查，进入grid安装目录：</p>
<p>$./runcluvfy.sh stage -pre crsinst -n ibmdb1,ibmdb2 -fixup -verbose</p>
<p>检查失败项并进行相应的调整。</p>
<p>安装提示过程中发现几个失败：</p>
<p>1、 swap需要4G以上。</p>
<p>2、 需要oinstall组</p>
<p>3、 有几个系统补丁未打</p>
<p><strong>3.2 执行rootpre.sh</strong></p>
<p>1、以root身份登录在所有节点执行：slibclean</p>
<p>2、以root身份登录在所有节点执行grid软件下的：rootpre.sh</p>
<p><strong>3.3 安装grid</strong></p>
<p>使用xmanager登录，</p>
<p>$ export DISPLAY=192.168.18.12:0.0 #安装终端IP</p>
<p>在上传的grid安装文件目录下，键入./runInstaller开始安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888709.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888840.png&amp;size=m" alt=""> 在这个界面下选择合适的安装类型，选择typical，将遇到更少的选择，并且在后续Oracle可以生成一个fixup脚本叫你执行，然后自动调整你没有按要求调整的系统参数，如果选择advanced，那么你将遇到更多的选择，并且所有环境变量要按要求事先准备，否则会报错,在这里我们选择advanced选项：</p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528888964.png&amp;size=m" alt=""></p>
<p>在上图选择产品语言时加上中文件语句，否则在使用EM时如果你的IE优先使用中文的话会出乱码的：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889024.png&amp;size=m" alt=""></p>
<p>注：在上图的SCAN name中，如果有DNS，则填写DNS中的scan name，如果没有配置DNS，则填写在/etc/hosts中定义的scan名字（本例中的抓图为第一次安装时图，当时建立了DNS服务器，第二次安装时就使用了/etc/hosts中的scan名字）。</p>
<p>在配置网络增强界面时，规划好的集群名、SCAN名，SCAN端口，不勾选Configure GNS（GNS需要DHCP服务器配合，本次部署无DHCP服务器）：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889113.png&amp;size=m" alt=""></p>
<p>本例中我们使用的是/etc/hosts解析，所以直接填写scan，即可。</p>
<p>若是有DNS解析，这里填上在DNS服务器中建立的SCAN.IBM.COM的名称。</p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889243.png&amp;size=m" alt=""></p>
<p>配置集群中的节点信息：</p>
<p>1、配置集群中的节点信息，</p>
<p>2、选择 SSH connectivity选项，输入grid用户的密码，然后点setup选项，oracle会自动配置ssh互信：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889365.png&amp;size=m" alt=""></p>
<p>在进行grid 的SSH配置时出过两个报错：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889544.png&amp;size=m" alt=""></p>
<p>1、 提示在ibmdb1上未找到/usr/local/bin/ssh-keygen，这个文件本身就不在这里，原因前面曾经提到，寻找路径与实际路径不符。既然提示要在这里找，建一个这个路径，然后将ssk-keygen拷贝至这里。</p>
<p>2、 提示ibmdb2没有/bin/bash，检查发现ibmdb2果然没有这个，将ibmdb1的拷贝过来。</p>
<p>bash-3.00# scp /bin/bash ibmdb2:/bin/</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889637.png&amp;size=m" alt=""></p>
<p>这里如果DNS没有配置好，会报scan vip出错。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889721.png&amp;size=m" alt=""></p>
<p>指定网络接口，public和private.</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528889858.png&amp;size=m" alt=""></p>
<p>这里选择ASM存储：</p>
<p>创建ASM磁盘组OCRVOTE，用于放置OCR、VOTE文件：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890001.png&amp;size=m" alt=""></p>
<p>磁盘冗余类型选择：Normal，提示最少需要3块，这里我们选择disk3、5、6三块。</p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890140.png&amp;size=m" alt=""></p>
<p>设置一致的密码，这里我们设置为：oracle。密码不要忘记，在建立RAC集群数据库时还需要。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890200.png&amp;size=m" alt=""></p>
<p>会提示密码过于简单，不用理会。Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890290.png&amp;size=m" alt=""></p>
<p>上图指定特权操作系统组：Next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890406.png&amp;size=m" alt=""></p>
<p>上图指定oracle base和software install目录，按我们的规划要求进行调整：</p>
<p>Oracle Base：/u01/grid</p>
<p>Software Location：/u01/app</p>
<p>安装不通过，将software location改为/u01/db后可以继续，/u01/db的属主是oracle:dba,不解</p>
<p>创建临时目录inventory：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890578.png&amp;size=m" alt=""></p>
<p>预安装检查，如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890707.png&amp;size=m" alt=""></p>
<p>检查结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890798.png&amp;size=m" alt=""></p>
<p>Swap空间不足，以及有几个补丁未打。</p>
<p>检查发现ibmdb2忘了增加swap，添加至3G。</p>
<p>其余补丁暂时未打，</p>
<p>其中第四个提示在ibmdb1上有0级别的用户不止root一个，检查发现在ibmdb1上有人建立了一个dylan的0级用户，在/etc/passwd中将其禁用后重新检查，此问题不在。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528890947.png&amp;size=m" alt=""></p>
<p>尝试忽略其它Failed继续安装。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528891091.png&amp;size=m" alt=""></p>
<p>点击：Finish 开始安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528891266.png&amp;size=m" alt=""></p>
<p>安装时间较长，达数十分钟。</p>
<p>在两个节点上分别按提示的顺序执行oraInstRoot.sh和root.sh，注意一定要按提示的顺序执行。若是在node1上安装，则执行顺序：</p>
<p>Node1:</p>
<h1>/../orainstRoot.sh</h1>
<p>Node2:</p>
<p>/../orainstRoot.sh</p>
<p>Node1:</p>
<p>/../root.sh</p>
<p>Node2:</p>
<p>/../root.sh</p>
<p>完成后点击OK：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528891422.png&amp;size=m" alt=""></p>
<p>又有一个报错，忽略：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528891563.png&amp;size=m" alt=""></p>
<p>完装完成后点击:close。</p>
<p>删除grid：</p>
<p>若安装出错，需要删除重新安装时，可以通过deinstall进行干净卸载。</p>
<p><strong>3.4 Clusterware安装校验</strong></p>
<p>查看集群启动了哪些服务，以grid用户执行crs_stat –t –v 命令：</p>
<p>如果看到启动的许多相应的服务，则显示正常。</p>
<p><strong>Ibmdb1:</strong></p>
<h1>./crs_stat -t</h1>
<p>Name Type Target State Host</p>
<hr>
<p>ora.DATA.dg ora….up.type ONLINE ONLINE ibmdb1</p>
<p>ora….N1.lsnr ora….er.type ONLINE ONLINE ibmdb1</p>
<p>ora.asm ora.asm.type ONLINE ONLINE ibmdb1</p>
<p>ora.eons ora.eons.type ONLINE ONLINE ibmdb1</p>
<p>ora.gsd ora.gsd.type OFFLINE OFFLINE</p>
<p>ora….SM1.asm application ONLINE ONLINE ibmdb1</p>
<p>ora.ibmdb1.gsd application OFFLINE OFFLINE</p>
<p>ora.ibmdb1.ons application ONLINE ONLINE ibmdb1</p>
<p>ora.ibmdb1.vip ora….t1.type ONLINE ONLINE ibmdb1</p>
<p>ora….SM2.asm application ONLINE ONLINE ibmdb2</p>
<p>ora.ibmdb2.gsd application OFFLINE OFFLINE</p>
<p>ora.ibmdb2.ons application ONLINE ONLINE ibmdb2</p>
<p>ora.ibmdb2.vip ora….t1.type ONLINE ONLINE ibmdb2</p>
<p>ora….network ora….rk.type ONLINE ONLINE ibmdb1</p>
<p>ora.oc4j ora.oc4j.type OFFLINE OFFLINE</p>
<p>ora.ons ora.ons.type ONLINE ONLINE ibmdb1</p>
<p>ora.scan1.vip ora….ip.type ONLINE ONLINE ibmdb1</p>
<h1></h1>
<p><strong>Ibmdb2:</strong></p>
<h1>./crs_stat -t</h1>
<p>Name Type Target State Host</p>
<hr>
<p>ora.DATA.dg ora….up.type ONLINE ONLINE ibmdb1</p>
<p>ora….N1.lsnr ora….er.type ONLINE ONLINE ibmdb1</p>
<p>ora.asm ora.asm.type ONLINE ONLINE ibmdb1</p>
<p>ora.eons ora.eons.type ONLINE ONLINE ibmdb1</p>
<p>ora.gsd ora.gsd.type OFFLINE OFFLINE</p>
<p>ora….SM1.asm application ONLINE ONLINE ibmdb1</p>
<p>ora.ibmdb1.gsd application OFFLINE OFFLINE</p>
<p>ora.ibmdb1.ons application ONLINE ONLINE ibmdb1</p>
<p>ora.ibmdb1.vip ora….t1.type ONLINE ONLINE ibmdb1</p>
<p>ora….SM2.asm application ONLINE ONLINE ibmdb2</p>
<p>ora.ibmdb2.gsd application OFFLINE OFFLINE</p>
<p>ora.ibmdb2.ons application ONLINE ONLINE ibmdb2</p>
<p>ora.ibmdb2.vip ora….t1.type ONLINE ONLINE ibmdb2</p>
<p>ora….network ora….rk.type ONLINE ONLINE ibmdb1</p>
<p>ora.oc4j ora.oc4j.type OFFLINE OFFLINE</p>
<p>ora.ons ora.ons.type ONLINE ONLINE ibmdb1</p>
<p>ora.scan1.vip ora….ip.type ONLINE ONLINE ibmdb1</p>
<h1></h1>
<p>
 <br>
</p>
<p><strong>4 安装Database</strong></p>
<p>这里只安装数据库软件，之后再用dbca命令建立集群数据库。安装完database软件后，还要建立ASM磁盘组。</p>
<p>使用oracle用户通过xmanager登录进行安装。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528891705.png&amp;size=m" alt=""></p>
<p>选择不接收更新，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528891838.png&amp;size=m" alt=""></p>
<p>选择只安装数据库软件，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528891963.png&amp;size=m" alt=""></p>
<p>选择real application cluster database installlation，然后选中所有的节点，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892100.png&amp;size=m" alt=""></p>
<p>在上图中，需要进行oracle用户的ssh信任配置，如果在前面没有配置好，可以在这里进行配置。Test一下是否正常，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892190.png&amp;size=m" alt=""></p>
<p>选择语言，添加Simplitied Chinese，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892314.png&amp;size=m" alt=""></p>
<p>选择数据库版本：Enterprise Edition，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892438.png&amp;size=m" alt=""></p>
<p>选择安装目录，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892582.png&amp;size=m" alt=""></p>
<p>如果环境变量设置正确，上面会自动填充，否则的话需要手工调整，这里的调整要和oracle用户的环境变量保持一致，Next：</p>
<p>注：这里原定使用的/u01/db用来进行grid程序的安装目录了，所以这里只有调整，但是/u01/db安装完grid后，目录权限自动变成了root:dba，不知道有没有什么影响。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892670.png&amp;size=m" alt=""></p>
<p>由于我们这里只建了一个dba组，所以，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892831.png&amp;size=m" alt=""></p>
<p>开始执行安装前检查，</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528892960.png&amp;size=m" alt=""></p>
<p>有几个报错，由于crs安装完成后有报错，现在尝试忽略继续安装。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893087.png&amp;size=m" alt=""></p>
<p>Next，开始进行安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893212.png&amp;size=m" alt=""></p>
<p>此安装过程较为耗时。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893305.png&amp;size=m" alt=""></p>
<p>安装进度完成后，提示需要运行相关脚本，按要求的顺序执行，正确执行完毕后点击：OK</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893407.png&amp;size=m" alt=""></p>
<p>关闭窗口，完成database的安装。</p>
<p>
 <br>
</p>
<p><strong>5 创建ASM磁盘组</strong></p>
<p>在建库前需要先创建存储数据文件的ASM磁盘组。以grid用户通过xmanager登录，执行asmca命令：</p>
<p>$asmca</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893508.png&amp;size=m" alt=""></p>
<p>切换到Disk Group界面，点击Create：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893652.png&amp;size=m" alt=""></p>
<p>按我们的规划，选择rhdisk2、rhdisk4组成ASMORACLE磁盘组。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893806.png&amp;size=m" alt=""></p>
<p>完成后如图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893869.png&amp;size=m" alt=""></p>
<p>其中DATA磁盘组是我们安装grid是建立的给OCR和VOTEDISK使用的，ASMORACLE是准备用来安装数据库的。</p>
<p>
 <br>
</p>
<p><strong>6 DBCA创建数据库</strong></p>
<p>使用oracle用户，通过xmanager登录运行dbca来建立RAC集群数据库。建库时可以先不配置企业管理器（Enterprise Manager），等建库完成后，执行脚本配置EM。原因是在安装过程中配置EM导致建库时间很长，如果RAC节点的主机性能不好，可能导致安装程序的界面停止响应。本次安装就是因为选择了配置Enterprise Manager，结果从早上11点一直安装到18点才结束。</p>
<p>$export DISPLAY=192.168.18.12:0.0</p>
<p>$dbca</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528893963.png&amp;size=m" alt=""></p>
<p>当然选择建立RAC数据库，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894078.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894212.png&amp;size=m" alt=""></p>
<p>选择自定议，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894282.png&amp;size=m" alt=""></p>
<p>输入我们oracle环境变量中配置的Databse Name，然后选中所有节点，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894430.png&amp;size=m" alt=""></p>
<p>默认选择EM：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894563.png&amp;size=m" alt=""></p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894755.png&amp;size=m" alt=""></p>
<p>所有用户配置一个密码：oracle 然后Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894864.png&amp;size=m" alt=""></p>
<p>选择刚才建立的ASMORACLE磁盘组：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528894979.png&amp;size=m" alt=""></p>
<p>指定ASMSNMP密码，暂时不知道有何用，配置密码：oracle，点击OK：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895107.png&amp;size=m" alt=""></p>
<p>配置闪回，默认选择OCR的ASM磁盘组，勾选启用归档，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895240.png&amp;size=m" alt=""></p>
<p>进入数据库初始化参数配置：</p>
<p>Memory：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895294.png&amp;size=m" alt=""></p>
<p>Block Size：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895419.png&amp;size=m" alt=""></p>
<p>字符集：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895574.png&amp;size=m" alt=""></p>
<p>Connection Mode：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895706.png&amp;size=m" alt=""></p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895823.png&amp;size=m" alt=""></p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528895990.png&amp;size=m" alt=""></p>
<p>Finish</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528896134.png&amp;size=m" alt=""></p>
<p>生成建库脚本。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528896237.png&amp;size=m" alt=""></p>
<p>开始一个较为漫长的建库过程。</p>
<p>
 <br>
</p>
<p><strong>7 安装完成后</strong></p>
<p><strong>7.1 备份root.sh</strong></p>
<p>官方建议在安装完成后要备份root.sh脚本文件。因为如果在oracle home目录中又安装其它oracle的产品，安装时OUI会升级已存在的root.sh文件。如果需要root.sh内的原始信息，就可以在备份中找到。</p>
<p>$ cd $ORACLE_HOME</p>
<p>$ cp root.sh root.sh.bak</p>
<p><strong>7.2 重新编译全部PL/SQL modules</strong></p>
<p>官方建议在建立或升级数据库后执行utlrp.sql脚本。这个脚本重新编译全部为不可用状态的PL/SQL module，包括包（packages）、存储过程（procedures）、类型（types）。这是一个可选操作，但oracle官方建议在安装后立即执行该脚本。</p>
<p>以oracle用户执行以下命令：</p>
<p>$ sqlplus “/as sysdba”</p>
<p>SQL&gt; @$ORACLE_HOME/rdbms/admin/utlrp.sql</p>
<p><strong>7.3 备份spfile文件</strong></p>
<p>$ sqlplus / as sysdba</p>
<p>SQL&gt; create pfile='/home/oracle/initorcl1.ora' from spfile='+OCRVOTE/orcl/spfileorcl.ora';</p>
<p><strong>7.4 RAC集群系统维护命令</strong></p>
<p>$ srvctl add –h #给集群添加对象，比较重要的如：service, srvpool等等</p>
<p>$ srvctl remove –h #删除集群的对象</p>
<p>$ srvctl modify –h #修改集群的对象</p>
<p>$ srvctl config –h #查看集群内对象的配置信息</p>
<p>$ srvctl status –h #查看集群内对象的状态信息</p>
<p>$ srvctl relocate –h #把集群的对象重定位到其它实例节点，如oc4j，gns只在一个实例节点上有效，使用该命令可以把对象转移到其它实例节点</p>
<p>$ srvctl enable –h #把集群内已经存在的对象置于可用状态</p>
<p>$ srvctl disable –h #把集群内已经存在的对象置于不可用状态</p>
<p>$ srvctl start –h #启动集群内已经存在的对象</p>
<p>$ srvctl stop –h #停止集群内已经存在的对象</p>
<p>$ srvctl config database #查看全部安装集群数据库名称</p>
<p>$ srvctl config database –d ytdb #查看指定的集群数据库的配置信息,以环境变量中为准</p>
<p>$ srvctl config nodeapps –a –g –s –e #显示节点应用程序的配置</p>
<p><strong>7.5 启动和关闭集群系统命令</strong></p>
<p><strong>7.5.1 启动集群件和集群数据库</strong></p>
<p>集群系统默认是自动启动的，手工启动命令如下：</p>
<h1>cd /u01/app/11.2/grid/bin #视安装路径而定,可查看grid环境变量</h1>
<h1>./crsctl start cluster</h1>
<p>也可以以grid用户执行以下命令（不过11.2版本中不建议使用）：</p>
<p>$ su - grid</p>
<p>$ crs_start –all</p>
<p>启动集群数据库</p>
<p>$ su – oracle</p>
<p>$ srvctl start database –d ytdb #启动全部实例</p>
<p>$ srvctl start instance –d ytdb –n ibmnode1 #节点1上启动数据库实例</p>
<p>$ srvctl start instance –d ytdb –n ibmnode2 #节点2上启动数据库实例</p>
<p><strong>7.5.2 关闭RAC集群数据库和集群件</strong></p>
<p>先关闭集群数据库</p>
<p>$ su – oracle</p>
<p>$ srvctl stop database –d ytdb</p>
<p>再关闭集群件（root用户）</p>
<h1>cd /u01/app/11.2/grid/bin #视安装路径而定,可查看grid环境变量</h1>
<h1>./crsctl stop cluster</h1>
<p>
 <br>
</p>
<hr>
<p>
 <br>
</p>
<p>安装完成后分别登录两个节点查看监听：</p>
<p>hostname:/u01/oracle/db/product/11.2/network/admin$lsnrctl status</p>
<p>
 <br>
</p>
<p>LSNRCTL for IBM/AIX RISC System/6000: Version 11.2.0.1.0 - Production on 12-NOV-2010 18:09:14</p>
<p>
 <br>
</p>
<p>Copyright (c) 1991, 2009, Oracle. All rights reserved.</p>
<p>
 <br>
</p>
<p>Connecting to (ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521))</p>
<p>STATUS of the LISTENER</p>
<hr>
<p>Alias LISTENER</p>
<p>Version TNSLSNR for IBM/AIX RISC System/6000: Version 11.2.0.1.0 - Production</p>
<p>Start Date 12-NOV-2010 11:20:55</p>
<p>Uptime 0 days 6 hr. 48 min. 19 sec</p>
<p>Trace Level off</p>
<p>Security ON: Local OS Authentication</p>
<p>SNMP ON</p>
<p>Listener Parameter File /u01/db/network/admin/listener.ora</p>
<p>Listener Log File /u01/grid/diag/tnslsnr/ibmdb1/listener/alert/log.xml</p>
<p>Listening Endpoints Summary…</p>
<p>(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=LISTENER)))</p>
<p>(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.17.237)(PORT=1521)))</p>
<p>(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.17.236)(PORT=1521)))</p>
<p>Services Summary…</p>
<p>Service “+ASM” has 1 instance(s).</p>
<p>Instance “+ASM1”, status READY, has 1 handler(s) for this service…</p>
<p>Service “ytdb” has 1 instance(s).</p>
<p>Instance “ytdb1”, status READY, has 1 handler(s) for this service…</p>
<p>Service “ytdbXDB” has 1 instance(s).</p>
<p>Instance “ytdb1”, status READY, has 1 handler(s) for this service…</p>
<p>The command completed successfully</p>
<p>
 <br>
</p>
<p>查看tnsnames.ora</p>
<p>YTDB =</p>
<p>(DESCRIPTION =</p>
<p>(ADDRESS = (PROTOCOL = TCP)(HOST = scan)(PORT = 1521))</p>
<p>(CONNECT_DATA =</p>
<p>(SERVER = DEDICATED)</p>
<p>(SERVICE_NAME = ytdb)</p>
<p>)</p>
<p>)</p>
<p>
 <br>
</p>
<blockquote>
 <p>社区会员分享，点击阅读原文可下载原文档</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注"AIX专家俱乐部"公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528896327.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627251&amp;idx=1&amp;sn=f77783bd6e95dc1aee25635e8e0839a6&amp;chksm=bef92235898eab233907cc48c7dfdbc861bc3eb88665193ef485e320ceeb480ab50e78f7f027&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/6a125e23-de16-499a-84ff-f198d7824995</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 01:01:40 GMT</pubDate></item><item><title><![CDATA[Linux 环境 Oracle 11g RAC 安装指南]]></title><link>https://wiki.dgfygk.com/archives/800e5b81-229c-4959-b0d8-40f6380ed2b8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%20%E7%8E%AF%E5%A2%83%20Oracle%2011g%20RAC%20%E5%AE%89%E8%A3%85%E6%8C%87%E5%8D%97&amp;url=/archives/800e5b81-229c-4959-b0d8-40f6380ed2b8" width="1" height="1" alt="" style="opacity:0;">
<p>目录</p>
<p>一、 安装前操作系统要求</p>
<p>1.1内存检查</p>
<p>1.2 Swap空间</p>
<p>1.3确认LINUX是64-Bit</p>
<p>1.4操作系统版本查看</p>
<p>1.5操作系统内核查看</p>
<p>1.6tmp空间</p>
<p>1.7磁盘空间需求</p>
<p>1.8安装软件包检查</p>
<p>1.9 创建用户和用户组</p>
<p>1.9.1 确认用户nobody存在</p>
<p>1.10 参数设置</p>
<p>1.10.1设置核心参数</p>
<p>1.10.2设置Shell Limits(系统资源限制)</p>
<p>1.11 设置用户环境变量</p>
<p>1.11.1设置grid用户环境变量</p>
<p>1.11.2设置oracle用户环境变量</p>
<p>1.12创建软件目录</p>
<p>1.13修改/etc/hosts文件</p>
<p>1.14关闭不必要的服务</p>
<p>1.14.1关闭防火墙</p>
<p>1.14.2关闭SELINUX</p>
<p>1.15配置裸设备</p>
<p>二、 安装集群软件</p>
<p>2.1 上传介质</p>
<p>2.2 开始安装</p>
<p>2.3 配置下载软件更新</p>
<p>2.4 选择软件类型</p>
<p>2.5 选择安装选项</p>
<p>2.6 选择产品语言</p>
<p>2.7 设置集群名称和SCAN名称</p>
<p>2.8 配置集群节点信息</p>
<p>2.9 配置SSH信任</p>
<p>2.10 配置网络接口使用情况</p>
<p>2.11指定OCR 和VotingDisk 存储介质</p>
<p>2.12 创建ASM磁盘组</p>
<p>2.13指定ASM密码</p>
<p>2.14指定ASM 实例操作系统组</p>
<p>2.15指定安装目录</p>
<p>2.16建立产品清单目录</p>
<p>2.17执行安装前检查</p>
<p>2.18安装前汇总</p>
<p>2.19开始安装</p>
<p>2.20执行配置脚本</p>
<p>2.21安装完成</p>
<p>三、 安装数据库软件</p>
<p>3.1 开始安装</p>
<p>3.2 配置安全更新</p>
<p>3.3 配置下载软件更新</p>
<p>3.4 选择软件类型</p>
<p>3.5选择安装选项</p>
<p>3.6 选择产品语言</p>
<p>3.7 选择数据库版本组件</p>
<p>3.8 指定安装目录</p>
<p>3.9设置系统组</p>
<p>3.10 执行安装前检查</p>
<p>3.11 安装前汇总</p>
<p>3.12 开始安装</p>
<p>3.13 执行配置脚本</p>
<p>3.14 安装完成</p>
<p>四、 创建数据库</p>
<p>4.1 创建磁盘组信息</p>
<p>4.2 运行dbca</p>
<p>4.3 选择执行操作选项</p>
<p>4.4 选择数据库模板</p>
<p>4.5 定义全局数据库名称和实例名</p>
<p>4.6 定义管理选项</p>
<p>4.7 设置数据库管理用户密码</p>
<p>4.8 指定数据库文件存储类型</p>
<p>4.9 选择恢复配置选项</p>
<p>4.10 选择数据库组件</p>
<p>4.11 配置数据库安装参数</p>
<p>4.12数据文件定义</p>
<p>4.13 选择数据库建立选项</p>
<p>4.14 数据库配置汇总查看</p>
<p>4.15 数据库开始创建</p>
<p>4.16 完成数据库的创建</p>
<p>五、安装完成后的配置</p>
<p>5.1 参数修改</p>
<p>5.2 关闭OCM功能</p>
<p>5.3 关闭数据文件自动扩展</p>
<p>5.4关闭集群自动启动功能</p>
<p>5.5 创建集群启停脚本</p>
<p>5.5.1 停集群脚本</p>
<p>5.5.2 起集群脚本</p>
<p>5.6 部署RMAN备份脚本</p>
<p>5.7 开启归档</p>
<p>5.7.1 创建目录</p>
<p>5.7.2 打开归档</p>
<p>六、 补丁更新</p>
<p>七、 卸载</p>
<p>7.1 卸载数据库</p>
<p>7.1.1 选择执行操作选项</p>
<p>7.1.2 选择要删除的数据库名称</p>
<p>7.1.3 确认删除</p>
<p>7.1.4 开始删除</p>
<p>7.1.5 完成删除</p>
<p>7.2 卸载软件</p>
<p>7.2.1卸载集群软件</p>
<p>7.2.2卸载数据库软件</p>
<p>八、日常查看命令</p>
<p>7.1查看数据库基本信息</p>
<p>7.2查看字符集</p>
<p>7.3查看用户信息</p>
<p>7.4查看表空间</p>
<p>7.5查看无效对象</p>
<p>
 <br>
</p>
<h1><strong>一、安装前操作系统要求</strong></h1>
<p>
 <br>
</p>
<h2><strong>1.1 内存检查</strong></h2>
<p>
 <br>
</p>
<p>至少4G的物理内存</p>
<h1>grepMemTotal /proc/meminfo</h1>
<p>MemTotal: 16336396 kB</p>
<p>
 <br>
</p>
<h2><strong>1.2 Swap空间</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849014.png&amp;size=m" alt=""></p>
<h1>grepSwapTotal /proc/meminfo</h1>
<p>SwapTotal: 8241144 kB</p>
<p>
 <br>
</p>
<p><strong>若交换空间不足,用以下方法增加:</strong></p>
<p>
 <br>
</p>
<p>假如分区空间要增加,可以增加新硬盘来加入VG内使其增加</p>
<p>先加上硬盘,用fdisk –l,可以看到新硬盘。</p>
<p>给新加的硬盘分区: fdisk /dev/sde</p>
<p>&gt;n</p>
<p>&gt;p</p>
<p>&gt;1</p>
<p>&gt;t</p>
<p>&gt;8e(linux LVM)</p>
<p>&gt;w</p>
<p>分成一个分区,格式为linux LVM.</p>
<p>
 <br>
</p>
<p>下面开始把分区加到LVM内去：</p>
<p>
 <br>
</p>
<p>1.建立物理卷</p>
<h1>pvcreate/dev/sde1</h1>
<p>
 <br>
</p>
<p>2.把新物理卷加入到卷组中去</p>
<h1>vgextendVolGroup /dev/sde1</h1>
<p>
 <br>
</p>
<p>3.关闭swap分区</p>
<h1>swapoff/dev/VolGroup/lv_swap</h1>
<p>
 <br>
</p>
<p>4.扩展 /dev/VolGroup/lv_swap,增加8336M</p>
<h1>lvextend -L+8336M /dev/VolGroup/lv_swap</h1>
<p>Extending logical volume lv_swap to 16.28 GiB</p>
<p>Logical volume lv_swap successfully resized</p>
<p>
 <br>
</p>
<p>5.将 /dev/VolGroup/lv_swap 设置为swap分区</p>
<h1>mkswap/dev/VolGroup/lv_swap</h1>
<p>mkswap: /dev/VolGroup/lv_swap: warning:don't erase bootbits sectors</p>
<p>on whole disk. Use -f to force.</p>
<p>Setting up swapspace version 1, size =17072124 KiB</p>
<p>no label,UUID=f5fa0e85-da9e-440c-8f1c-e3ab978c9d6b</p>
<p>
 <br>
</p>
<p>6.启动swap分区</p>
<h1>swapon/dev/VolGroup/lv_swap</h1>
<p>
 <br>
</p>
<p>7.查看结果</p>
<h1>free -m</h1>
<p>total used free shared buffers cached</p>
<p>Mem: 16081 14695 1386 0 83 13680</p>
<p>-/+ buffers/cache: 931 15150</p>
<p>Swap: 16671 0 16671</p>
<p>
 <br>
</p>
<h2><strong>1.3 确认LINUX是64-Bit</strong></h2>
<p>
 <br>
</p>
<h1>uname -m</h1>
<p>x86_64</p>
<p>
 <br>
</p>
<h2><strong>1.4 操作系统版本查看</strong></h2>
<p>
 <br>
</p>
<h1>cat/etc/redhat-release</h1>
<p>Red Hat Enterprise Linux Server release 6.4(Santiago)</p>
<p>至少或高于</p>
<ul>
 <li>
  <p>Red Hat Enterprise Linux 4 Update 7</p>
 </li>
 <li>
  <p>Red Hat Enterprise Linux 5 Update 2</p>
 </li>
 <li>
  <p>Red Hat Enterprise Linux 5 Update 5 (only if using Red Hat compatiblekernel)</p>
 </li>
</ul>
<h2><strong>1.5 操作系统内核查看</strong></h2>
<p>
 <br>
</p>
<h1>uname -a</h1>
<p>Linux localhost.localdomain2.6.32-358.el6.x86_64 #1 SMP Tue Jan 29 11:47:41 EST 2013 x86_64 x86_64 x86_64GNU/Linux</p>
<p>或</p>
<h1>uname -r</h1>
<p>2.6.32-358.el6.x86_64</p>
<p>至少或高于</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849088.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>1.6 tmp空间</strong></h2>
<p>
 <br>
</p>
<p>至少1G可用空间</p>
<h1>df –h /tmp</h1>
<h2><strong>1.7 磁盘空间需求</strong></h2>
<p>
 <br>
</p>
<p>磁盘空间按照数据库安装规范为50G。</p>
<h1>df –ah</h1>
<h2><strong>1.8 安装软件包检查</strong></h2>
<p>
 <br>
</p>
<p>检查下列包是否安装,若未安装则要先安装:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849163.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849262.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849393.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849511.png&amp;size=m" alt=""></p>
<p>检查命令</p>
<h1>rpm -qa \| greppackage_name</h1>
<p>安装缺少的包</p>
<h1>yum –y install package_name</h1>
<h2><strong>1.9 创建用户和用户组</strong></h2>
<p>
 <br>
</p>
<p>Oracle11g 需要以下用户和组</p>
<p>The Oracle Inventory group (dba)</p>
<p>The OSDBA group (dba)</p>
<p>The Oracle ASM Database Administrator group (dba)</p>
<p>The Oracle software owner (oracle)</p>
<p>Oracle Grid Infrastructure User (grid)</p>
<p>命令行执行：</p>
<h1>/usr/sbin/groupadd-g 300 dba</h1>
<h1>/usr/sbin/useradd-u 300 -g dba oracle</h1>
<h1>/usr/sbin/useradd-u 301 -g dba grid</h1>
<h1>id oracle</h1>
<p>uid=300(oracle) gid=300(dba)groups=300(dba)</p>
<h1>id grid</h1>
<p>uid=301(grid) gid=300(dba)groups=300(dba)</p>
<h1>passwd oracle</h1>
<h1>passwdgrid</h1>
<h3><strong>1.9.1 确认用户nobody存在</strong></h3>
<p>
 <br>
</p>
<h1>id nobody</h1>
<p>uid=99(nobody) gid=99(nobody) groups=99(nobody)</p>
<p>
 <br>
</p>
<h2><strong>1.10 参数设置</strong></h2>
<h3><strong>1.10.1 设置核心参数</strong></h3>
<p>
 <br>
</p>
<p>在/etc/sysctl.conf文件中加入下列行:</p>
<p>fs.aio-max-nr = 1048576</p>
<p>fs.file-max = 6815744</p>
<p>kernel.shmall = 2097152</p>
<p>kernel.shmmax = 536870912 (以字节为单位，物理内存数量*1024*1024*2，为内存的2倍)</p>
<p>kernel.shmmni = 4096</p>
<p>kernel.sem = 250 32000 100 128</p>
<p>net.ipv4.ip_local_port_range = 9000 65500</p>
<p>net.core.rmem_default = 262144</p>
<p>net.core.rmem_max = 4194304</p>
<p>net.core.wmem_default = 262144</p>
<p>net.core.wmem_max = 1048586</p>
<p>运行下列命令改变核心参数</p>
<h1>/sbin/sysctl –p</h1>
<p>注：kernel.shmmax，kernel.shmall两个参数,一般默认较大,取两者较大值。</p>
<h3><strong>1.10.2 设置Shell Limits(系统资源限制)</strong></h3>
<p>
 <br>
</p>
<p>在/etc/security/limits.conf文件中加入下列行：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849635.png&amp;size=m" alt=""></p>
<h2><strong>1.11设置用户环境变量</strong></h2>
<h3><strong>1.11.1 设置grid用户环境变量</strong></h3>
<p>
 <br>
</p>
<h1>su - grid–su到grid用户下，</h1>
<p>更改用户的一些配置</p>
<p>$vi .bash_profile</p>
<p>添加以下参数</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849766.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849842.png&amp;size=m" alt=""></p>
<p>注:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528849979.png&amp;size=m" alt=""></p>
<p>保存退出</p>
<p>–执行以下命令让配置马上生效或以grid用户登录使设置生效</p>
<p>$ source$HOME/.bash_profile</p>
<h3><strong>1.11.2 设置oracle用户环境变量</strong></h3>
<p>
 <br>
</p>
<h1>su - oracle –su到oracle用户下，</h1>
<p>更改用户的一些配置</p>
<p>$vi .bash_profile</p>
<p>添加以下参数，主要是配置oracle软件运环境参数</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528850056.png&amp;size=m" alt=""></p>
<p>注:</p>
<p>11g:ORA_NLS33=$ORACLE_HOME/nls/admin/data</p>
<p>10g:ORA_NLS33=$ORACLE_HOME/ocommon/nls/admin/data</p>
<p>9i：ORA_NLS33=/oracle/app/ora92/ocommon/nls/admin/data</p>
<p>保存退出</p>
<p>–执行以下命令让配置马上生效或以oracle用户登录使设置生效</p>
<p>$ source$HOME/.bash_profile</p>
<h2><strong>1.12 创建软件目录</strong></h2>
<p>
 <br>
</p>
<p>Oracle Base Directory ：/oracle/db</p>
<p>Grid Base Directory：/oracle/grid，</p>
<p>Grid Home Directory：/oracle/asm</p>
<p>/oracle目录大小要求不低于50G</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528850182.png&amp;size=m" alt=""></p>
<h2><strong>1.13 修改/etc/hosts文件</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528850367.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528850507.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>1.14 关闭不必要的服务</strong></h2>
<h3><strong>1.14.1 关闭防火墙</strong></h3>
<h1>serviceiptablesstop</h1>
<h3><strong>1.14.2 关闭SELINUX</strong></h3>
<p>修改/etc/sysconfig/selinux或/etc/selinux/config文件:</p>
<p>SELINUX=disable</p>
<p>或</p>
<h1>sed -i's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config</h1>
<h1>cat/etc/selinux/config</h1>
<p>注：/etc/sysconfig/selinux、/etc/selinux/config是同一个文件，修改任一个即可。</p>
<p>
 <br>
</p>
<h2><strong>1.15 配置裸设备</strong></h2>
<p>
 <br>
</p>
<p>查看磁盘信息</p>
<h1>fdisk–l</h1>
<p>编辑/etc/udev/rules.d/60-raw.rules文件，添加以下内容：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528850605.png&amp;size=m" alt=""></p>
<p>运行以下命令使裸设备可用</p>
<h1>start_udev</h1>
<p>
 <br>
</p>
<h1><strong>二、 安装集群软件</strong></h1>
<h2><strong>2.1 上传介质</strong></h2>
<p>
 <br>
</p>
<p>建立软件存放目录</p>
<h1>mkdir -p /pkg/oracle/11g</h1>
<p>ftp上传到指定目录并赋权</p>
<h1>chmod –R 777/pkg/oracle/11g</h1>
<p>用grid用户解压</p>
<p>$unzip p13390677_112040_Linux-x86-64_3of7.zip</p>
<p>用oracle用户解压</p>
<p>$unzipp13390677_112040_Linux-x86-64_1of7.zip</p>
<p>$unzipp13390677_112040_Linux-x86-64_2of7.zip</p>
<p>或</p>
<p>$jar -xvf p13390677_112040_Linux-x86-64_1of7.zip</p>
<p>$jar -xvf p13390677_112040_Linux-x86-64_2of7.zip</p>
<p>
 <br>
</p>
<h2><strong>2.2 开始安装</strong></h2>
<p>
 <br>
</p>
<p>注意以下命名规则:</p>
<p>cluster name: hostname-cluster</p>
<p>scan name:hostname-scan</p>
<p>磁盘组名字为:ASMCRS</p>
<p>磁盘冗余类型选择:外部冗余</p>
<p>进入到相应目录，以grid用户运行runInstaller</p>
<p>$ ./runInstaller</p>
<p>
 <br>
</p>
<h2><strong>2.3 配置下载软件更新</strong></h2>
<p>
 <br>
</p>
<p>–跳过软件更新</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528850750.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.4 选择软件类型</strong></h2>
<p>
 <br>
</p>
<p>–在安装设置界面选择第一项(默认)：Install and ConfigureGrid Infrastructure for a Cluster，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528850894.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.5 选择安装选项</strong></h2>
<p>
 <br>
</p>
<p>–在"安装类型界面"选择"高级安装”：Advanced Installation，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851022.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.6 选择产品语言</strong></h2>
<p>
 <br>
</p>
<p>–在"语言界面"添加"简体中文”：Simplified Chinese，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851167.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.7 设置集群名称和SCAN名称</strong></h2>
<p>
 <br>
</p>
<p>–在"定义集群界面"按照"安装规范"中的3小项指定 “集群名称”，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851277.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.8 配置集群节点信息</strong></h2>
<p>
 <br>
</p>
<p>–在"集群节点信息"窗口添加节点2的信息,格式按照节点1的信息添加,HOSTNAME和Virtual IP Name是在/etc/hosts文件中已经定义好的</p>
<p>完成添加以后点击"OK"按钮返回"集群节点信息"窗口</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851396.png&amp;size=m" alt=""></p>
<p>–在"集群节点信息"窗口中点击"SSH Connectivity"按钮输入grid密码"grid”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851538.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.9 配置SSH信任</strong></h2>
<p>
 <br>
</p>
<p>–点击"setup"安装SSH认证</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851657.png&amp;size=m" alt=""></p>
<p>–点击"Test"检查SSH认证是否正常,点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851789.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.10 配置网络接口使用情况</strong></h2>
<p>
 <br>
</p>
<p>–在"网络接口使用配置"界面指定网络接口的使用情况</p>
<p>私有网址选择"私有”</p>
<p>公共网址选择"Public”</p>
<p>管理网址选择"Do Not Use”</p>
<p>完成后点击 “Next"按钮</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528851882.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.11 指定OCR 和VotingDisk 存储介质</strong></h2>
<p>
 <br>
</p>
<p>–在"存储设置界面"选择第一项:Oracle Automatic Management(OracleASM)，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852007.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.12 创建ASM磁盘组</strong></h2>
<p>
 <br>
</p>
<p>–在"创建ASM 磁盘组"界面选择"ocr"和"voting"文件的存放位置，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852139.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.13 指定ASM密码</strong></h2>
<p>
 <br>
</p>
<p>–在"ASM 密码"界面输入ASM密码"oracle”,出现"红色提示"不用管,点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852327.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852462.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.14 指定ASM 实例操作系统组</strong></h2>
<p>
 <br>
</p>
<p>–在"系统组设置"界面所有的工作组均选择"dba"用户组</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852563.png&amp;size=m" alt=""></p>
<p>当出现该告警提示时选择"Yes"忽略掉该提示信息</p>
<h2><strong>2.15 指定安装目录</strong></h2>
<p>
 <br>
</p>
<p>–在"安装位置"界面不需要指定路径，默认是读取/home/grid/.profile文件中的路径设置，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852697.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.16 建立产品清单目录</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852760.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.17 执行安装前检查</strong></h2>
<p>
 <br>
</p>
<p>–“需求检查"界面检查安装grid软件时的要求是是否都已经满足</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852898.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528852993.png&amp;size=m" alt=""></p>
<p>出现上述提示可忽略'Ignore All'，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853069.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.18 安装前汇总</strong></h2>
<p>
 <br>
</p>
<p>–“安装预览界面"核对配置的基本信息是否都正确，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853232.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.19 开始安装</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853307.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.20 执行配置脚本</strong></h2>
<p>
 <br>
</p>
<p>–切换到"root"用户执行弹出窗口中的两个sh脚本</p>
<p>root 用户：</p>
<p>在node1 上执行：/oracle/oraInventory/orainstRoot.sh</p>
<p>在node2 上执行：/oracle/oraInventory/orainstRoot.sh;</p>
<p>在node1 上执行: /oracle/asm/root.sh</p>
<p>在node2上执行: /oracle/asm/root.sh</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853432.png&amp;size=m" alt=""></p>
<p>节点1</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853517.png&amp;size=m" alt=""></p>
<p>节点2</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853608.png&amp;size=m" alt=""></p>
<p>节点1</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853717.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853790.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528853952.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854091.png&amp;size=m" alt=""></p>
<p>节点2</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854270.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854392.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854512.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>2.21 安装完成</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854639.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h1><strong>三、 安装数据库软件</strong></h1>
<h2><strong>3.1 开始安装</strong></h2>
<p>
 <br>
</p>
<p>进入到相应目录，以oracle用户运行runInstaller</p>
<p>$ ./runInstaller</p>
<h2><strong>3.2 配置安全更新</strong></h2>
<p>
 <br>
</p>
<p>–去掉技术支持选择</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854768.png&amp;size=m" alt=""></p>
<p>不接收软件更新，点击下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854832.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.3 配置下载软件更新</strong></h2>
<p>
 <br>
</p>
<p>–跳过软件更新</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528854895.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.4 选择软件类型</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855021.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.5选择安装选项</strong></h2>
<p>
 <br>
</p>
<p>–在"Grid Installation Options"界面选择"Oracle Real Application Clusters database installation"选项</p>
<p>–点击"SSH Connectivity"按钮，输入在OS Password文本框数据密码"oracle”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855175.png&amp;size=m" alt=""></p>
<p>–点击"setup"安装SSH认证</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855301.png&amp;size=m" alt=""></p>
<p>点击"Test"测试SSH是否正常，如果正常点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855439.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.6 选择产品语言</strong></h2>
<p>
 <br>
</p>
<p>–语言支持选择英语和简体中文</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855512.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.7 选择数据库版本组件</strong></h2>
<p>
 <br>
</p>
<p>–选择企业版</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855644.png&amp;size=m" alt=""></p>
<h2><strong>3.8 指定安装目录</strong></h2>
<p>
 <br>
</p>
<p>–和环境变量里设置的目录保持一致</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855758.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.9设置系统组</strong></h2>
<p>
 <br>
</p>
<p>–在"Operating System Groups"界面中，全部选择dba工作组</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855831.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.10 执行安装前检查</strong></h2>
<p>
 <br>
</p>
<p>–检测系统补丁及各项参数</p>
<p>pdksh包不是官方文档要求的包，可以忽略。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528855955.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856087.png&amp;size=m" alt=""></p>
<p>点击yes，下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856179.png&amp;size=m" alt=""></p>
<h2><strong>3.11 安装前汇总</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856402.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>3.12 开始安装</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856554.png&amp;size=m" alt=""></h2>
<p>
 <br>
</p>
<h2><strong>3.13 执行配置脚本</strong></h2>
<p>
 <br>
</p>
<p>–再开一窗口，以root用户运行以下脚本</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856619.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点1：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856751.png&amp;size=m" alt=""></p>
<p>节点2：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856826.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856904.png&amp;size=m" alt=""></p>
<h2><strong>3.14 安装完成</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528856984.png&amp;size=m" alt=""></p>
<h1><strong>四、 创建数据库</strong></h1>
<h2><strong>4.1 创建磁盘组信息</strong></h2>
<p>
 <br>
</p>
<p>–在"终端命令"窗口下切换到"grid"用户，输入"asmca"命令打开"ASM Configure Assistant：Configure ASM:DiskGroup"选择"Disk Groups"选项，点击"Create"按钮创建ASM磁盘组，磁盘组名为ASMVG1,完成后关闭该窗口</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857099.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857229.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857324.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857451.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.2 运行dbca</strong></h2>
<p>
 <br>
</p>
<p>–以oracle用户运行dbca</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857597.png&amp;size=m" alt=""></p>
<h2><strong>4.3 选择执行操作选项</strong></h2>
<p>
 <br>
</p>
<p>–选择建立一个数据库</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857689.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.4 选择数据库模板</strong></h2>
<p>
 <br>
</p>
<p>–自定义数据库</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857827.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.5 定义全局数据库名称和实例名</strong></h2>
<p>
 <br>
</p>
<p>–输入数据库名和实例名</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528857892.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.6 定义管理选项</strong></h2>
<p>
 <br>
</p>
<p>–不安装EM组件，如果有需要可以以后建立</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858024.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.7 设置数据库管理用户密码</strong></h2>
<p>
 <br>
</p>
<p>–密码一致</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858087.png&amp;size=m" alt=""></p>
<p>点击yes，下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858211.png&amp;size=m" alt=""></p>
<h2><strong>4.8 指定数据库文件存储类型</strong></h2>
<p>
 <br>
</p>
<p>–选择 file system方式</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858343.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>4.9 选择恢复配置选项</strong></p>
<p>
 <br>
</p>
<p>–不开启flash功能，如有需要可以以后更改</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858430.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.10 选择数据库组件</strong></h2>
<p>
 <br>
</p>
<p>–以下默认应用组件没有特殊要求不安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858562.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.11 配置数据库安装参数</strong></h2>
<p>
 <br>
</p>
<p>–以下所有参数配置参考《生产环境ORACLE数据库安装配置操作规程》（下图以物理内存16G为例）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858703.png&amp;size=m" alt=""></p>
<p>–块大小和进程数参考,块大小为8192，进程数一般为1500</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858703.png&amp;size=m" alt=""></p>
<p>–生产数据库字符集须依据应用系统特性来定义，推荐的数据库字符集为ZHS16GBK。国家语言字符集为AL16UTF16。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858817.png&amp;size=m" alt=""></p>
<h2><strong>4.12数据文件定义</strong></h2>
<p>
 <br>
</p>
<p>–在数据文件定义界面可以修改DataFile、RedoLog、Controlfile的大小和路径，按照数据库安装规范定义修改，点击"Next”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528858952.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>4.13 选择数据库建立选项</strong></p>
<p>
 <br>
</p>
<p>–点击"Finish”</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859028.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.14 数据库配置汇总查看</strong></h2>
<p>
 <br>
</p>
<p>–点击OK开始创建数据库</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859167.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>4.15 数据库开始创建</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859250.png&amp;size=m" alt=""></h2>
<p>
 <br>
</p>
<h2><strong>4.16 完成数据库的创建</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859372.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h1><strong>五、安装完成后的配置</strong></h1>
<h2><strong>5.1 参数修改</strong></h2>
<p>
 <br>
</p>
<p>修改空表EXP导致bug参数</p>
<p>SQL&gt;alter system set deferred_segment_creation =FALSE;</p>
<p>修改用户限制参数</p>
<p>SQL&gt;alter profile default limit PASSWORD_LIFE_TIME UNLIMITED;</p>
<p>SQL&gt;alter profile default limit FAILED_LOGIN_ATTEMPTSUNLIMITED;</p>
<p>SQL&gt;alter profile default limit PASSWORD_LOCK_TIME UNLIMITED;</p>
<p>SQL&gt;alter profile default limitPASSWORD_GRACE_TIME UNLIMITED;</p>
<p>关闭审计功能</p>
<p>SQL&gt;alter system set audit_trail=none scope=spfile;</p>
<p>修改游标参数</p>
<p>SQL&gt;alter system set open_cursors=1500 scope=both;</p>
<p>修改进程参数</p>
<p>SQL&gt;alter system set processes=1500 scope=spfile;</p>
<p>调大数据文件个数参数</p>
<p>SQL&gt;alter system set db_files=600 scope=spfile;</p>
<h2><strong>5.2 关闭OCM功能</strong></h2>
<p>
 <br>
</p>
<p>SQL&gt;execdbms_scheduler.disable('ORACLE_OCM.MGMT_CONFIG_JOB');</p>
<p>SQL&gt;execdbms_scheduler.disable('ORACLE_OCM.MGMT_STATS_CONFIG_JOB');</p>
<h2><strong>5.3 关闭数据文件自动扩展</strong></h2>
<p>
 <br>
</p>
<p>SQL&gt;declare</p>
<p>v_dfvarchar2(400);</p>
<p>v_tfvarchar2(400);</p>
<p>cursor c1 isselect name from v\$datafile;</p>
<p>cursor c2 isselect name from v\$tempfile;</p>
<p>begin</p>
<p>open c1;</p>
<p>loop</p>
<p>fetch c1 intov_df;</p>
<p>if c1%found then</p>
<p>execute immediate'alter database datafile '''\|\|v_df\|\| ''' autoextend off';</p>
<p>else</p>
<p>exit;</p>
<p>end if;</p>
<p>end loop;</p>
<p>close c1;</p>
<p>open c2;</p>
<p>loop</p>
<p>fetch c2 intov_tf;</p>
<p>if c2%found then</p>
<p>executeimmediate 'alter database tempfile '''\|\|v_tf\|\| ''' autoextend off';</p>
<p>else</p>
<p>exit;</p>
<p>end if;</p>
<p>end loop;</p>
<p>close c2;</p>
<p>end;</p>
<p>/</p>
<h2><strong>5.4关闭集群自动启动功能</strong></h2>
<p>
 <br>
</p>
<p>两个节点分别root用户运行</p>
<h1>/oracle/asm/bin/crsctl disable crs</h1>
<p>
 <br>
</p>
<h2><strong>5.5 创建集群启停脚本</strong></h2>
<p>
 <br>
</p>
<p>创建存放目录</p>
<p>
 <br>
</p>
<h1>cd /</h1>
<h1>mkdir /hacmp</h1>
<h1>cd /hacmp</h1>
<h3><strong>5.5.1 停集群脚本</strong></h3>
<p>
 <br>
</p>
<p>变量$ORACLE_SID根据各数据库而定</p>
<h1>more $ORACLE_SIDstopdb.sh</h1>
<p>su - oracle -c"srvctl stop instance -d orcl -i orcl1”</p>
<p>su - root -c”/oracle/asm/bin/crsctl stop crs”</p>
<h3><strong>5.5.2 起集群脚本</strong></h3>
<p>
 <br>
</p>
<h1>more $ORACLE_SIDstartdb.sh</h1>
<p>su - root -c”/oracle/asm/bin/crsctl start crs”</p>
<p>sleep 120</p>
<p>su - oracle -c"srvctl start instance -d orcl -i orcl2”</p>
<p>
 <br>
</p>
<h2><strong>5.6 部署RMAN备份脚本</strong></h2>
<p>
 <br>
</p>
<p>在2个节点分别创建备份目录</p>
<h1>/usr/sbin/mklv -y'lv_rmanbak ' -t'jfs2' -a'c' datavg 100</h1>
<h1>/usr/sbin/crfs -v jfs2 -d 'lv_rmanbak' -m '/rmanbak' -Ayes -p rw</h1>
<h1>mount /rmanbak</h1>
<h1>chown -R oracle.dba /rmanbak</h1>
<p>
 <br>
</p>
<p>只在一个节点部署，脚本存放位置及命名</p>
<p>$/home/oracle/scripts/rman_full_db.sh</p>
<p>脚本内容</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859502.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859582.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859765.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>crontab 内容</p>
<p>10 2 * * */home/oracle/scripts/rman_full_db.sh &gt;&gt;/home/oracle/scripts/rman_full_db.log 2&gt;\&amp;1</p>
<p>脚本权限</p>
<h1>chmod u+x rmanbak.sh</h1>
<p>
 <br>
</p>
<h2><strong>5.7 开启归档</strong></h2>
<h3><strong>5.7.1 创建目录</strong></h3>
<p>
 <br>
</p>
<p>大小根据业务或者生产环境而定，新环境默认不小于20G</p>
<h1>mkdir /archivelog</h1>
<h1>chown -R oracle.dba /archivelog</h1>
<h3><strong>5.7.2 打开归档</strong></h3>
<p>
 <br>
</p>
<p>SQL&gt;alter system setlog_archive_dest_1='location=/archivelog';</p>
<p>SQL&gt;shutdown immediate;</p>
<p>SQL&gt;startup mount</p>
<p>SQL&gt;alter database archivelog;</p>
<p>SQL&gt;alter database open;</p>
<p>
 <br>
</p>
<h1><strong>六、 补丁更新</strong></h1>
<p>
 <br>
</p>
<p>根据《数据库安全规范》更新最新的opatch</p>
<p>除非造成比较严重的bug，一般情况下直接更新PSU补丁。</p>
<p>根据《数据库安全规范》更新最新的PSU补丁</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528859888.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>其他建议补丁：</p>
<p>根据在测试，生产环境遇到的类似问题补丁，在同版本的数据库下一起更新。</p>
<p>11.2.0.1版本需要打</p>
<p>
 <br>
</p>
<h1><strong>七、 卸载</strong></h1>
<h2><strong>7.1 卸载数据库</strong></h2>
<p>Oracle用户运行dbca</p>
<h3><strong>7.1.1 选择执行操作选项</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860039.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860157.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3><strong>7.1.2 选择要删除的数据库名称</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860277.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3><strong>7.1.3 确认删除</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860399.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3><strong>7.1.4 开始删除</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860554.png&amp;size=m" alt=""></p>
<h3><strong>7.1.5 完成删除</strong></h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860655.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>7.2 卸载软件</strong></h2>
<h3><strong>7.2.1卸载集群软件</strong></h3>
<h4><strong>7.2.1.1 手工卸载</strong></h4>
<p>
 <br>
</p>
<p>a、以root用户在所有的节点上，除了最后一个节点执行</p>
<h1>$GRID_HOME/crs/install/rootcrs.pl-verbose -deconfig -force</h1>
<p>b、以root用户在最后一个节点执行</p>
<h1>$GRID_HOME/crs/install/rootcrs.pl -verbose -deconfig -force-lastnode</h1>
<p>c、用grid用户在任意节点执行</p>
<h1>$GRID_HOME/deinstall/deinstall，在执行过程中，还需要根据提示运行两个在/TMP目录下的脚本。</h1>
<p>
 <br>
</p>
<p>d、root用户执行下列脚本删除grid相关配置文件</p>
<h1>rm -rf/var/tmp/.oracle</h1>
<h1>rm -rf/usr/tmp/.oracle</h1>
<h1>rm -rf/tmp/.oracle</h1>
<h1>rm -rf/etc/ora*</h1>
<h1>rm -rf/var/tmp/.oracle</h1>
<h1>rm/etc/init.cssd</h1>
<h1>rm/etc/init.crs</h1>
<h1>rm/etc/init.crsd</h1>
<h1>rm/etc/init.evmd</h1>
<h1>rm/etc/rc.d/rc2.d/K96init.crs</h1>
<h1>rm/etc/rc.d/rc2.d/S96init.crs</h1>
<h1>rm -rf/etc/oracle/scls_scr</h1>
<h1>rm -rf/etc/oracle/oprocd</h1>
<h1>rm/etc/inittab.crs</h1>
<h1>cp/etc/inittab.orig /etc/inittab</h1>
<h1>rm-rf/opt/ORCL*</h1>
<p>最后删除grid软件所在目录</p>
<h1>rm -rf/oracle/*</h1>
<p>
 <br>
</p>
<p>e、清空ocr 和votedisk磁盘</p>
<h1>ddif=/dev/zero of=/dev/rhdisk2 bs=1024 count=2560</h1>
<h1>ddif=/dev/zero of=/dev/rhdisk3 bs=1024 count=2560</h1>
<h4><strong>7.2.1.2使用deinstall工具卸载</strong></h4>
<p>
 <br>
</p>
<p>Oracle 11g之后提供了卸载grid和database的脚本，可以卸载的比较干净，不需要手动删除crs等。</p>
<p>
 <br>
</p>
<p>a、从oracle官方网站上下载的deinstall工具</p>
<p>11GR2有7个下载包，deinstall放在第7个下载包，如11.2.0.2的下载包为p10098816_112020_AIX64-5L_7of7.zip</p>
<p>
 <br>
</p>
<p>b、解压到本地目录,并执行deinstall</p>
<p>需要输入$ORACLE_HOME</p>
<p>bash-3.00$./deinstall -home/oracle/grid/product/11.2.0/grid</p>
<p>Location of logs/ora11g/server_install/deinstall/./logs/</p>
<h6>ORACLE DEINSTALL \&amp; DECONFIG TOOL START</h6>
<p>
 <br>
</p>
<p>c、这里为RAC双节点的grid，选择3后按回车开始卸载流程</p>
<p>–后面根据自己情况进行选择，输入LISTENER的地方可以直接回车</p>
<p>–注卸载选项说明 1 为卸载单实例数据库,2 卸载rac数据库 ,3 卸载集群grid ,4 卸载单节点grid ,5卸载oracle客户端 ,6 卸载oracle网关</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860786.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860883.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528860974.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528861140.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528861325.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528861491.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528861638.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528861793.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>–显示执行成功，用root用户手工删除各节点/etc/oraInst.loc' 内容</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528861902.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3><strong>7.2.2卸载数据库软件</strong></h3>
<p>
 <br>
</p>
<p>通过操作系统命令删除相关目录即可</p>
<h1>rm –rf /etc/ora*</h1>
<h1>rm –rf /usr/local/bin/coraenv</h1>
<h1>rm –rf /usr/local/bin/dbhome</h1>
<h1>rm –rf /usr/local/bin/oraenv</h1>
<h1>rm –rf /oracle/</h1>
<p>
 <br>
</p>
<h1><strong>八、日常查看命令</strong></h1>
<h2><strong>8.1查看数据库基本信息</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528861982.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528862075.png&amp;size=m" alt=""></p>
<h2><strong>8.2查看字符集</strong></h2>
<p>
 <br>
</p>
<p>SQL&gt;select name,value$ from props$ where namein('NLS_CHARACTERSET','NLS_NCHAR_CHARACTERSET');</p>
<p>
 <br>
</p>
<h2><strong>8.3查看用户信息</strong></h2>
<p>
 <br>
</p>
<p>用户基本信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528862147.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>用户权限信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528862210.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528862332.png&amp;size=m" alt=""></p>
<h2><strong>8.4查看表空间</strong></h2>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528862410.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528862484.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h2><strong>8.5查看无效对象</strong></h2>
<p>
 <br>
</p>
<p>SQL&gt;select'invalidobt',owner,object_name,object_type from dba_objects wherestatus='INVALID' and owner in (select username from dba_users where ACCOUNT_STATUS='OPEN') ;</p>
<p>
 <br>
</p>
<blockquote>
 <p>由社区会员岳彩波分享，点击阅读原文可下载原文档</p>
</blockquote>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号AIX专家俱乐部</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528862566.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628830&amp;idx=1&amp;sn=f895c0ae5fd8c6ffd30c0f4182f0fa60&amp;chksm=bef91c58898e954e8a0cba96626de41b72a7bf1b985cf558c2a6cf0c2b1f9742619d4a683d9a&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/800e5b81-229c-4959-b0d8-40f6380ed2b8</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 00:48:07 GMT</pubDate></item><item><title><![CDATA[AIX7.1 + Oracle 11g RAC 安装配置文档]]></title><link>https://wiki.dgfygk.com/archives/701f8a68-0ad8-47a0-8dfe-92fdd8aad219</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AIX7.1%20%2B%20Oracle%2011g%20RAC%20%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E6%96%87%E6%A1%A3&amp;url=/archives/701f8a68-0ad8-47a0-8dfe-92fdd8aad219" width="1" height="1" alt="" style="opacity:0;">
<p>目录</p>
<p>1 主机规划</p>
<p>1.1 硬件环境</p>
<p>1.2 软件环境</p>
<p>1.3 网络规划</p>
<p>1.31 ip 地址规划</p>
<p>1.32 DNS 服务器</p>
<p>1.4 安装目录规划</p>
<p>1.5 存储规划</p>
<p>1.5.1 grid、ASM 安装目录</p>
<p>1.5.2 Clusterware 安装目录</p>
<p>1.5.3 OCR、VOTE存储规划</p>
<p>2 安装准备</p>
<p>2.1 系统硬件环境检查</p>
<p>2.11 Oracle11gR2 RAC 最低安装要求</p>
<p>2.12 验证方法</p>
<p>2.2 安装操作系统软件包</p>
<p>2.21 安装 FILESETS</p>
<p>2.22 配置 IP 地址</p>
<p>2.23 修改/etc/hosts 文件</p>
<p>2.3 文件系统准备</p>
<p>2.31 方法 1 ：使用命令创建</p>
<p>2.4 调整系统参数</p>
<p>2.41 打开异步 IO</p>
<p>2.42 调整虚拟内存管理</p>
<p>2.43 调整 maxuproc （系统最大进程数）和 ncargs （系统块大小）参数</p>
<p>2.44 调整 SHELL 限制</p>
<p>2.45 确认网络配置参数</p>
<p>2.5 创建用户、组</p>
<p>2.51 要求</p>
<p>2.52 创建命令</p>
<p>2.53 修改用户属性</p>
<p>2.6 用户环境变量设置</p>
<p>2.61 grid 用户</p>
<p>2.62 oracle 用户</p>
<p>2.7 创建安装目录并修改权限</p>
<p>2.8 SSH 信任关系设置</p>
<p>2.81 首先在两台机器上安装好 OpenSSH 软件</p>
<p>2.82 在 在 grid 安装中选择自动配置 SSH 双机信任关系</p>
<p>2.9 配置磁盘设备</p>
<p>2.91 设置磁盘属性</p>
<p>2.92 修改用于数据库存储的 PV 的属主</p>
<p>2.93 修改用于数据库存储的 PV 的权限</p>
<p>2.94 清除 PVID</p>
<p>3 安装 Grid</p>
<p>3.1 Grid 安装前检查</p>
<p>3.11 执行rootpre.sh</p>
<p>3.12 安装grid</p>
<p>3.13 Clusterware 安装校验</p>
<p>4 安装 Database</p>
<p>5 创建 ASM 磁盘组</p>
<p>6 DBCA 创建数据库</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><strong>1 主机规划</strong></p>
<p><strong>1.1 硬件环境</strong></p>
<p>服务器：两台 IBM P740</p>
<p>存储：DS5020 存储；</p>
<p><strong>1.2 软件环境</strong></p>
<p>数据库：</p>
<p>p10404530_112030_AIX64-5L_1of7.zip</p>
<p>p10404530_112030_AIX64-5L_2of7.zip</p>
<p>集群软件:</p>
<p>p10404530_112030_AIX64-5L_3of7.zip</p>
<p>这三个包</p>
<p>操作 系统:</p>
<p>AIX 7.1,Oracle 11gR2</p>
<p><strong>1.3 网络规划</strong></p>
<p><strong>1.31 ip地址规划</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528878735.png&amp;size=m" alt=""></p>
<p><strong>1.32 DNS服务器</strong></p>
<p>使用/etc/hosts 来解析 scan</p>
<p><strong>1.4 安装目录规划</strong></p>
<p>安装目录规划原则：建立/u01 文件系统用来安装 grid、 datbase 程序。 程序都安装在/u01/app下面， 对于 grid 与 database分别建立不同的目录， 分配不同的权限。 其中grid 的 ORACLE_BASE和 ORACLE_HOME 建议安装在不同的目录下，具体规划如下：</p>
<p>新建 50g lv ：fslv</p>
<p>新建文件系统，挂载点：/u01</p>
<p>gridbase 目录：/u01/app/grid #grid 用户的 ORACLE_BASE</p>
<p>grid asm 安装目录：/u01/app/11.2/grid #grid 用户的 ORACLE_HOME，也即是安装时的</p>
<p>software location</p>
<p>Oracle base 目录：/u01/app/oracle #oracle 用户的 ORACLE_BASE</p>
<p><strong>1.5 存储规划</strong></p>
<p><strong>1.5.1grid 、ASM 安装目录</strong></p>
<p>在 Oracle 11gR2 中 grid 和 ASM 是安装在同一目录下的，我们将 grid 和 ASM安装在/u01/app/grid 目录下，为 JFS2 文件系统，大小为 50G。</p>
<p><strong>1.5.2Clusterware 安装目录</strong></p>
<p>我们将 Clusterware 安装在/u01/app目录下。</p>
<p><strong>1.5.3OCR 、VOTE 存储规划</strong></p>
<p>在我们的方案中规划如下：</p>
<p>OCR、 VOTE hdisk2 容量：3x1G</p>
<p>DATAhdisk3 容量：500G</p>
<p>FLASH hdisk8 容量：200G</p>
<p>
 <br>
</p>
<p><strong>2 安装准备</strong></p>
<p><strong>2.1 系统硬件环境检查</strong></p>
<p><strong>2.11 Oracle11gR2 RAC 最低安装要求：</strong></p>
<p>内存：</p>
<p>安装 grid infrastructure最少需要 1.5GB 的内存，</p>
<p>安装 grid infrastructure和 RAC 最少需要 2.5GB 的内存</p>
<p>swap ：最少需要 4GB,有的资料介绍最少 1.5G，不过安装环境检查时显示需要16G。</p>
<p>temp ：推荐 10G，可以将安装文件上传至/tmp 目录下，8GB 合适</p>
<p>安装目录：grid infrastructure+ASM+Clusterware 最少需要 12GB</p>
<p>操作系统：</p>
<p>AIX 7.1, 64-bitkernel</p>
<p><strong>2.12 验证方法：</strong></p>
<p>2.121 确认内存大小</p>
<h1>r lsattr l -El 0 sys0 a -a realmem</h1>
<p>2.122 确认 SWAP 空间，最少 4G。 。</p>
<h1>s lsps -a</h1>
<p>若不够 4G，需要增加 PPS 数。本系统一个 PPS 为 128M，所以增加 20 个，使 swap 达到 4G。</p>
<p>PPS 大小可以通过lsvg rootvg 查看。</p>
<h1>s chps s -s 2 32 hd6</h1>
<h1>s lsps -a</h1>
<p>2.123 确认/tmp 大于 1.5GB 和/oracle 目录空间于 大于 15GB</p>
<p>oracle 安装目录在 50GB左右，grid infrastructure+ASM 安装在/u01/app/grid目录下，其中 Oracle11g 安装在/u01/app/oracle/目录下.</p>
<p>s chfs –a a G size=10G /tmp</p>
<p>2.124 确认系统结构</p>
<h1>f getconf HARDWARE_BITMODE</h1>
<p>64</p>
<p>2.125 确认系统 CPU 个数和主频：</p>
<h1>v lsdev C -C \| \| p grep proc</h1>
<h1>r lsattr l -El proc0</h1>
<p><strong>2.2 安装操作系统软件包</strong></p>
<p><strong>2.21 安装 FILESETS</strong></p>
<p>AIX 7 71 .1 需要安装如下软件包：</p>
<p>The following operatingsystem filesetsare required for X AIX 7.1:</p>
<p> bos.adt.base</p>
<p> bos.adt.lib</p>
<p> bos.adt.libm</p>
<p> bos.perf.libperfstat</p>
<p> bos.perf.perfstat</p>
<p> bos.perf.proctools</p>
<p> xlC.aix61.rte:10.1.0.0 or later</p>
<p> xlC.rte.10.1.0.0 or later</p>
<p> gpfs.base 3.3.0.11 or later</p>
<p>以上 filesets 安装与否可以用命令 lslpp －l 进行检查确认。默认安装都是全的，不需要手工进行添加。其中 gpfs.base 不需要安装。验证如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528878844.png&amp;size=m" alt=""></p>
<p><strong>2.22 配置 IP 地址</strong></p>
<p>根据前面的规划先配置要两台机器的 IP 地址，就是 interlib1、interlib1-priv 和</p>
<p>interlib2、interlib2-priv 这四个IP 地址。</p>
<p>同样配置几块网卡的 IP 地址，en0 和 en1 做冗余绑定（en4） ，其中服务地址要配置网关，</p>
<p>网关地址为：80.10.1.254。</p>
<p><strong>2.23 修改/etc/hosts 文件</strong></p>
<p>修改每个节点上的/etc/hosts 文件。</p>
<p>127.0.0.1 loopback localhost # loopback (lo0) name/address</p>
<p>80.10.1.1 interlib1</p>
<p>80.10.1.3 interlib1-vip</p>
<p>192.168.0.2 interlib1-priv</p>
<p>80.10.1.2 interlib2</p>
<p>80.10.1.4 interlib2-vip</p>
<p>192.168.0.3 interlib2-priv</p>
<p>80.10.1.5 interlib-scan</p>
<p>注意：如果你的环境里不具备 DNS 服务器，那么 SCAN 地址解析可以通过/etc/hosts 或 GNS来实现， 这需要在你的所有数据库主机的 host 文件里添加一个 SCAN 地址， 并且这个地址的解析名不要超过 16 个字符，超过的话，oracle 只会解析前 16 个字符。本例中我们通过/etc/hosts 来解析 scan。</p>
<p><strong>2.3 文件系统准备</strong></p>
<p>创建1 /u01 文件系统</p>
<p><strong>2.31 方法 1 ：使用命令创建</strong></p>
<p>1、r mkdir /u01</p>
<p>2、建立一个lv v mklv –t t 2 jfs2 –y y 0 fslv00 g rootvg 100 #100个pps，可视需要而定</p>
<p>3、建立一个文件系统 s crfs –v v 2 jfs2 d -d 0 /dev/fslv00 m -m /u01</p>
<p>4、mount文件系统 t mount /u01</p>
<p>5、使用smitchfs，修改/u01启动自动mount。</p>
<p>创建文件系统</p>
<p>1、r mkdir /u01</p>
<p>2、建立一个lv v mklv –t t 2 jfs2 –y y 0 fslv00 g rootvg 100 #100个pps，可视需要而定</p>
<p>3、建立一个文件系统 s crfs –v v 2 jfs2 d -d 0 /dev/fslv00 m -m /u01</p>
<p>4、mount文件系统 t mount /u01</p>
<p>5、使用smitchfs，修改/u01启动自动mount。</p>
<p><strong>2.4 调整系统参数</strong></p>
<p><strong>2.41 打开异步 IO</strong></p>
<p>AIX5.3 中，在安装grid 软件前执行 rootpre.sh 会自动打开异步 IO 服务，在AIX7.1 中默认异步 IO 服务是打开的。推荐的 aio_maxreqs 值为 64k (65536)，检查方法如下：</p>
<p>AIX 7.1</p>
<h1>o ioo o -o aio_maxreqs</h1>
<p>aio_maxreqs = 65536</p>
<p><strong>2.42 调整虚拟内存管理</strong></p>
<p>Oracle推存使用vmo命令去调优虚拟内存管理，参数推荐如下：</p>
<p>命令如下:</p>
<p>o vmo p -p o -o minperm%=3</p>
<p>o vmo p -p o -o maxperm%=90</p>
<p>o vmo p -p o -o maxclient%=90</p>
<p>o vmo p -p o -o lru_file_repage=0</p>
<p>o vmo p -p o -o strict_maxclient=1</p>
<p>o vmo p -p o -o strict_maxperm=0</p>
<p>You must restart the system forthese changes to take effect.</p>
<p><strong>2.43 调整 maxuproc （系统最大进程数）和 ncargs （系统块大小）参数</strong></p>
<h1>v chdev l -l 0 sys0 a -a maxuproc=16384</h1>
<p>sys0 changed</p>
<p>也可以使用：smitty chgsys 修改"Maximum number of PROCESSES allowed per user”</p>
<p>修改完成后查看：</p>
<h1>r lsattr l -El 0 sys0 a -a maxuproc</h1>
<p>maxuproc 16384 Maximum number of PROCESSES allowed per user True</p>
<h1>v chdev l -l 0 sys0 a -a ncargs=256</h1>
<p>sys0 changed</p>
<p><strong>2.44 调整 SHELL 限制</strong></p>
<ol>
 <li>在 /etc/security/limits文件中增加如下行:</li>
</ol>
<p>default:</p>
<p>e fsize = = -1</p>
<p>e core = = - -1 1</p>
<p>u cpu = = -1</p>
<p>a data = = -1</p>
<p>s rss= -1</p>
<p>k stack = = -1</p>
<p>s nofiles = = -1</p>
<p>2.45 确认网络配置参数</p>
<p>用# /usr/sbin/no -a \| more来查看网络参数是否满足下面的要求</p>
<ul>
 <li>PARAMETER RECOMMENDED VALUE</li>
</ul>
<p>ipqmaxlen 512</p>
<p>rfc1323 1</p>
<p>sb_max 1500000</p>
<p>tcp_recvspace 65536</p>
<p>tcp_sendspace 65536</p>
<p>udp_recvspace1351680 这个值应该是 udp_sendspace 的 10 倍，但是必须小于 sb_max</p>
<p>udp_sendspace 135168 这 个 值 至 少 应 该 是 4K ＋</p>
<p>（db_block_size*db_multiblock_read_count）的大小</p>
<ul>
 <li>修改命令</li>
</ul>
<p>no -r -o ipqmaxlen=521</p>
<p>no -p -o rfc1323=1</p>
<p>no -p -o sb_max=1500000</p>
<p>no -p -o tcp_recvspace=65536</p>
<p>no -p -o tcp_sendspace=65536</p>
<p>no -p -o udp_recvspace=1351680</p>
<p>no -p -o udp_sendspace=13516</p>
<p>在修改前需要先查看系统设置的兼容性：</p>
<h1>r lsattr l -El 0 sys0 a -a pre520tune</h1>
<p>1 、输出如：</p>
<p>pre520tune disable Pre-520 tuning compatibilitymode True</p>
<p>说明是不兼容方式，这样就可以用上面的方式调整：</p>
<p>2 、如果是 y compatibility 模式，则：</p>
<h1>/usr/sbin/no -o parameter_name=value</h1>
<p>并且编辑文件/etc/rc.net 加入下面内容：</p>
<p>if [ -f/usr/sbin/no ] ; then</p>
<p>/usr/sbin/no -oudp_sendspace=65536</p>
<p>/usr/sbin/no -oudp_recvspace=655360</p>
<p>/usr/sbin/no -otcp_sendspace=65536</p>
<p>/usr/sbin/no -otcp_recvspace=65536</p>
<p>/usr/sbin/no -orfc1323=1</p>
<p>/usr/sbin/no -osb_max=2*655360</p>
<p>/usr/sbin/no -oipqmaxlen=512</p>
<p>fi</p>
<p><strong>2.5 创建用户、组</strong></p>
<p><strong>2.51 要求</strong></p>
<p>本次安装我们就规划了多个组，但是规划两个用户，一个用于管理 ASM 实例，另一个用于管理 DB 实例。</p>
<p><strong>2.52 创建命令：</strong></p>
<h1>mkgroup -A id=500 adms='root' oinstall</h1>
<h1>mkgroup -A id=501 adms='root' dba</h1>
<h1>mkgroup -A id=502 adms='root' oper</h1>
<h1>mkgroup -A id=503 adms='root' asmadmin</h1>
<h1>mkgroup -A id=504 adms='root' asmdba</h1>
<h1>mkgroup -A id=505 adms='root' asmoper</h1>
<h1>mkuser id=500 pgrp=oinstall groups=asmadmin,asmdba,asmoper home=/home/grid grid</h1>
<h1>mkuserid=501 pgrp=oinstall groups=dba,asmdba,oper home=/home/oracle oracle</h1>
<p>！注意保持双机用户与组的 D ID 的一致。</p>
<p><strong>2.53 修改用户属性</strong></p>
<p><strong>2.531 grid 用户</strong></p>
<p>1、在两台主机上要保证 grid 用户具有以下能力：</p>
<p>CAP_NUMA_ATTACH, CAP_BYPASS_RAC_VMM, and CAP_PROPAGATE</p>
<p>查看grid用户能力：</p>
<h1>lsuser-a capabilitiesgrid</h1>
<p>增加grid用户能力：</p>
<h1>r chuser E capabilities=CAP_NUMA_ATTACH,CAP_BYPASS_RAC_VMM,CAP_PROPAGATE grid</h1>
<p>2、设置grid用户密码：</p>
<h1>d passwd grid</h1>
<p>3、在两台主机上最消grid用户shell限制</p>
<p>可以通过smit chuser去修改grid用户限制，或直接修改/etc/security/limits 文件，具体</p>
<p>参数如下：</p>
<p>default:</p>
<p>fsize = -1</p>
<p>core = 2097151</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p><strong>2.532 oracle 用户</strong></p>
<p>1、在两台主机上要保证 oracle 用户具有以下能力：</p>
<p>CAP_NUMA_ATTACH, CAP_BYPASS_RAC_VMM, and CAP_PROPAGATE</p>
<p>查看oracle用户能力：</p>
<h1>lsuser -a capabilities oracle</h1>
<p>增加oracle用户能力：</p>
<h1>r chuser E capabilities=CAP_NUMA_ATTACH,CAP_BYPASS_RAC_VMM,CAP_PROPAGATE oracle</h1>
<p>2、设置oracle用户密码：</p>
<h1>passwdoracle</h1>
<p>3、在两台主机上最消oracle用户shell限制</p>
<p>可以通过smit chuser去修改oracle用户限制，或直接修改/etc/security/limits 文件，具体参数如下：</p>
<p>oracle:</p>
<p>fsize = -1</p>
<p>core = 2097151</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p><strong>2.533 root 用户</strong></p>
<p>在两台主机上最消root用户shell限制，因为CRS进程（crsd）需要以root用户运行。可以通过smit chuser去修改root用户限制，或直接修改/etc/security/limits 文件，具体参数如下：</p>
<p>default:</p>
<p>fsize = -1</p>
<p>core = 2097151</p>
<p>cpu = -1</p>
<p>data = -1</p>
<p>rss = -1</p>
<p>stack = -1</p>
<p>nofiles = -1</p>
<p><strong>2.6 用户环境变量设置</strong></p>
<p><strong>2.61grid 用户</strong></p>
<p>在两台服务器的的 grid 用户的.profile 最后，增加如下内容：</p>
<p>umask 022</p>
<p>ORACLE_BASE=/u01/app/grid #需手工创建</p>
<p>ORACLE_HOME=/u01/app/11.2.0/grid #需手工创建</p>
<p>ORACLE_SID=+ASM1 #节点 2 是+ASM2</p>
<p>NLS_DATE_FORMAT=“yyyy-mm-dd hh24:mi:ss”</p>
<p>PAHT=$ORACLE_HOME/bin:$PATH</p>
<p>export ORACLE_SID ORACLE_BASE ORACLE_HOME PATH NLS_DATE_FORMAT</p>
<p><strong>2.62oracle 用户</strong></p>
<p>在两台服务器的的 oracle 用户的.profile 最后，增加如下内容：</p>
<p>umask 022</p>
<p>ORACLE_SID=interlib1</p>
<p>ORACLE_BASE=/u01/app/oracle</p>
<p>ORACLE_HOME=$ORACLE_BASE/product/11.2.0/db_1</p>
<p>ORACLE_UNQNAME=interlib</p>
<p>NLS_DATE_FORMAT=“yyyy-mm-dd hh24:mi:ss”</p>
<p>PATH=$ORACLE_HOME/bin:$PATH</p>
<p>export ORACLE_SID ORACLE_BASE ORACLE_HOME ORACLE_UNQNAME PATH NLS_DATE_FORMAT</p>
<p><strong>2.7 创建安装目录并修改权限</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879005.png&amp;size=m" alt=""></p>
<p>注意：如果 grid 与 orcle 安装在同一文件系统下面，如上，在进行权限修改的时候要注意顺序，先进行/u01 赋权，再进行/u01/db 赋权，否则会被覆盖。</p>
<p><strong>2.8 SSH 信任关系设置</strong></p>
<p><strong>2.81 首先在两台机器上安装好 OpenSSH 软件；</strong></p>
<p>具体安装方法本处不详述，需要下载 openssh、openssl，安装时需先安装 openssl，然后再安装 openssh。也可以通过 AIX 系统光盘，执行 smitty install，选择所有 ssh 包安装。</p>
<p>安装完毕后可以检查：</p>
<h1>lslpp –l \| grep ssh</h1>
<p><strong>2.82 在 grid 安装中选择自动配置 SSH 双机信任关系</strong></p>
<p>以下两个节点都执行：</p>
<h1>su – grid</h1>
<p>$mkdir –p \~/.ssh</p>
<p>$chmod 700 \~/.ssh</p>
<p>$/usr/bin/ssh-keygen -trsa</p>
<p>提示输入密码时，保持为空，直接回车即可。</p>
<p>以下只在节点 1 1 上执行：</p>
<p>$ touch\~/.ssh/authorized_keys</p>
<p>$ ssh interlib1cat \~/.ssh/id_rsa.pub&gt;&gt;\~/.ssh/authorized_keys</p>
<p>$ ssh interlib2cat \~/.ssh/id_rsa.pub&gt;&gt;\~/.ssh/authorized_keys</p>
<p>$ scp \~/.ssh/authorized_keys interlib2:.ssh/authorized_keys</p>
<p>以下只在节点 2 2 上执行：</p>
<p>$ chmod600 \~/.ssh/authorized_keys</p>
<p><strong>2.9 配置磁盘设备</strong></p>
<p><strong>2.91 设置磁盘属性</strong></p>
<p>该步骤若不做，在 grid 安装完成建立 ASM 时，第二个节点会找不到硬盘，同时加入不了群集。</p>
<p>检测:</p>
<p>lsattr -El hdisk3 \|grep reserve_</p>
<p>查看一个即可</p>
<p>设置保持策略:</p>
<p>如果上面查看的结果是 reserve_policy</p>
<p>chdev -l hdisk2 -a reserve_policy=no_reserve</p>
<p>chdev -l hdisk3 -a reserve_policy=no_reserve</p>
<p>chdev -l hdisk8 -a reserve_policy=no_reserve</p>
<p>chdev -l hdisk9 -a reserve_policy=no_reserve</p>
<p>如果上面查看的结果是 reserve_lock</p>
<p>chdev -l hdisk3 -a reserve_lock=no</p>
<p>……</p>
<p><strong>2.92 修改用于数据库存储的 PV 的属主</strong></p>
<p>chown grid:dba /dev/rhdisk2</p>
<p>chown grid:dba /dev/rhdisk3</p>
<p>chown grid:dba /dev/rhdisk8</p>
<p>chown grid:dba /dev/rhdisk9</p>
<p><strong>2.93 修改用于数据库存储的 PV 的权限</strong></p>
<p>chmod 777 /dev/rhdisk2</p>
<p>chmod 777 /dev/rhdisk3</p>
<p>chmod 777 /dev/rhdisk8</p>
<p>chmod 777 /dev/rhdisk9</p>
<p><strong>2.94 清除 PVID</strong></p>
<p>查看 LUN，如果已经有了 PVID 的话，需要进行清除。</p>
<p>chdev -l hdisk3 -a pv=clear</p>
<p>重复同样的操作，清除所有 LUN 的 PVID</p>
<p>
 <br>
</p>
<p><strong>3 安装 Grid</strong></p>
<p><strong>3.1 Grid 安装前检查</strong></p>
<p>切换到 grid 用户并执行安装前检查，进入 grid 安装目录：</p>
<p>$./runcluvfy.sh stage -pre crsinst -n interlib1,interlib2 -fixup -verbose</p>
<p>检查失败项并进行相应的调整。</p>
<p><strong>3.11 执行 rootpre.sh</strong></p>
<p>1、以 root 身份登录在所有节点执行：slibclean</p>
<p>2、以 root 身份登录在所有节点执行 grid 软件下的：rootpre.sh</p>
<p><strong>3.12 安装 grid</strong></p>
<p>使用 xmanager 登录，</p>
<p>$ export DISPLAY=80.10.254.126:0.0 #安装终端 IP</p>
<p>在上传的 grid 安装文件目录下，键入./runInstaller 开始安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879069.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879205.png&amp;size=m" alt=""></p>
<p>在这个界面下选择合适的安装类型，选择 typical，将遇到更少的选择，并且在后续Oracle 可以生成一个 fixup 脚本叫你执行，然后自动调整你没有按要求调整的系统参数，如果选择 advanced，那么你将遇到更多的选择，并且所有环境变量要按要求事先准备，否则会报错,在这里我们选择 advanced选项：</p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879301.png&amp;size=m" alt=""></p>
<p>在上图选择产品语言时加上中文件语句， 否则在使用 EM 时如果你的 IE 优先使用中文的话会出乱码的：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879481.png&amp;size=m" alt=""></p>
<p>本次实施中我们使用的是/etc/hosts 解析，直接填写 scan，即可。</p>
<p>若是有 DNS 解析，这里填上在 DNS 服务器中建立的 SCAN.IBM.COM 的名称。</p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879655.png&amp;size=m" alt=""></p>
<p>这里如果 DNS 没有配置好，会报 scan vip 出错。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879736.png&amp;size=m" alt=""></p>
<p>指定网络接口，public 和 private.</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528879933.png&amp;size=m" alt=""></p>
<p>这里选择 ASM 存储：</p>
<p>创建 ASM磁盘组 SYSTEMDG，用于放置 OCR、VOTE 文件：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528880037.png&amp;size=m" alt=""></p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528880208.png&amp;size=m" alt=""></p>
<p>设置一致的密码，这里我们设置为：interlib。密码不要忘记，在建立 RAC集群数据库时还需要。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528880384.png&amp;size=m" alt=""></p>
<p>会提示密码过于简单，不用理会。Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528880554.png&amp;size=m" alt=""></p>
<p>上图指定特权操作系统组：Next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528880721.png&amp;size=m" alt=""></p>
<p>上图指定 oracle base 和 software install 目录，按我们的规划要求进行调整：</p>
<p>Oracle Base：/u01/app/grid</p>
<p>Software Location：/u01/app/11.2.0/grid</p>
<p>安装不通过， 将software location改为/u01/db后可以继续， /u01/db的属主是oracle:dba,不解</p>
<p>创建临时目录 inventory：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528880818.png&amp;size=m" alt=""></p>
<p>预安装检查，如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881012.png&amp;size=m" alt=""></p>
<p>检查结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881310.png&amp;size=m" alt=""></p>
<p>尝试忽略其它 Failed 继续安装。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881451.png&amp;size=m" alt=""></p>
<p>点击：Finish 开始安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881533.png&amp;size=m" alt=""></p>
<p>安装时间较长，达数十分钟。</p>
<p>在两个节点上分别按提示的顺序执行 oraInstRoot.sh 和 root.sh，注意一定要按提示的顺序执行。若是在 node1 上安装，则执行顺序：</p>
<p>Node1:</p>
<h1>/../orainstRoot.sh</h1>
<p>Node2:</p>
<p>/../orainstRoot.sh</p>
<p>Node1:</p>
<p>/../root.sh</p>
<p>Node2:</p>
<p>/../root.sh</p>
<p>完成后点击 OK：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881617.png&amp;size=m" alt=""></p>
<p>完装完成后点击:close。</p>
<p><strong>3.13 Clusterware 安装校验</strong></p>
<p>查看集群启动了哪些服务，以 grid 用户执行 crs_stat –t –v 命令：</p>
<p>如果看到启动的许多相应的服务，则显示正常。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881787.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881866.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528881993.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882063.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882204.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>4 安装 Database</strong></p>
<p>这里只安装数据库软件，之后再用 dbca 命令建立集群数据库。安装完 database 软件后，还要建立 ASM 磁盘组。</p>
<p>使用 oracle 用户通过 xmanager 登录进行安装。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882311.png&amp;size=m" alt=""></p>
<p>选择不接收更新，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882427.png&amp;size=m" alt=""></p>
<p>选择只安装数据库软件，Next：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882500.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>选择 realapplication cluster database installlation，然后选中所有的节点，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882608.png&amp;size=m" alt=""></p>
<p>在上图中，需要进行 oracle 用户的 ssh 信任配置，如果在前面没有配置好，可以在这里进行配置。Test 一下是否正常，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882726.png&amp;size=m" alt=""></p>
<p>选择语言，添加 Simplitied Chinese，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528882820.png&amp;size=m" alt=""></p>
<p>选择数据库版本：Enterprise Edition，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528883006.png&amp;size=m" alt=""></p>
<p>选择安装目录，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528883103.png&amp;size=m" alt=""></p>
<p>如果环境变量设置正确，上面会自动填充，否则的话需要手工调整，这里的调整要和 oracle用户的环境变量保持一致，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528883274.png&amp;size=m" alt=""></p>
<p>由于我们这里只建了一个 dba 组，所以，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528883427.png&amp;size=m" alt=""></p>
<p>开始执行安装前检查，</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528883684.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528883788.png&amp;size=m" alt=""></p>
<p>Next，开始进行安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528883851.png&amp;size=m" alt=""></p>
<p>此安装过程较为耗时。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884021.png&amp;size=m" alt=""></p>
<p>安装进度完成后，提示需要运行相关脚本，按要求的顺序执行，正确执行完毕后点击：OK</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884115.png&amp;size=m" alt=""></p>
<p>关闭窗口，完成 database 的安装。</p>
<p>
 <br>
</p>
<p><strong>5 创建 ASM 磁盘组</strong></p>
<p>在建库前需要先创建存储数据文件的 ASM 磁盘组。以 grid 用户通过 xmanager 登录，执行 asmca 命令：</p>
<p>$asmca</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884242.png&amp;size=m" alt=""></p>
<p>切换到 Disk Group 界面，点击 Create：</p>
<p>按我们的规划，选择 rhdisk3 为 DATADG 磁盘组。</p>
<p>
 <br>
</p>
<p><strong>6 DBCA 创建数据库</strong></p>
<p>使用 oracle 用户，通过 xmanager 登录运行 dbca 来建立 RAC 集群数据库。建库时可以先不配置企业管理器（Enterprise Manager） ，等建库完成后，执行脚本配置 EM。原因是在安装过程中配置 EM 导致建库时间很长，如果 RAC 节点的主机性能不好，可能导致安装程序的界面停止响应。</p>
<p>$export DISPLAY=80.10.254.126:0.0</p>
<p>$dbca</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884399.png&amp;size=m" alt=""></p>
<p>当然选择建立 RAC 数据库，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884578.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884688.png&amp;size=m" alt=""></p>
<p>选择自定议，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884833.png&amp;size=m" alt=""></p>
<p>输入我们 oracle 环境变量中配置的 Databse Name，然后选中所有节点，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528884954.png&amp;size=m" alt=""></p>
<p>默认选择 EM：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528885093.png&amp;size=m" alt=""></p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528885198.png&amp;size=m" alt=""></p>
<p>所有用户配置一个密码：interlib 然后 Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528885335.png&amp;size=m" alt=""></p>
<p>选择刚才建立的 ASMORACLE 磁盘组：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528885508.png&amp;size=m" alt=""></p>
<p>指定 ASMSNMP 密码，暂时不知道有何用，配置密码：interlib，点击 OK：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528885634.png&amp;size=m" alt=""></p>
<p>配置闪回，默认选择 OCR 的 ASM 磁盘组，勾选启用归档，Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528885852.png&amp;size=m" alt=""></p>
<p>进入数据库初始化参数配置：</p>
<p>Memory：设置为 60%</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886026.png&amp;size=m" alt=""></p>
<p>Block Size：设置为 8192，Processes 设置为 2048</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886189.png&amp;size=m" alt=""></p>
<p>字符集：选择 UTF-8</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886363.png&amp;size=m" alt=""></p>
<p>Connection Mode：选择专用模式</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886496.png&amp;size=m" alt=""></p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886643.png&amp;size=m" alt=""></p>
<p>Next：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886723.png&amp;size=m" alt=""></p>
<p>Finish</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886862.png&amp;size=m" alt=""></p>
<p>生成建库脚本。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528886995.png&amp;size=m" alt=""></p>
<p>开始一个较为慢长的建库过程。</p>
<blockquote>
 <p>来自社区会员分享</p>
</blockquote>
<p>
 <br>
</p>
<p>相关资料</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627251&amp;idx=1&amp;sn=f77783bd6e95dc1aee25635e8e0839a6&amp;chksm=bef92235898eab233907cc48c7dfdbc861bc3eb88665193ef485e320ceeb480ab50e78f7f027&amp;scene=21#wechat_redirect">Oracle11GR2 RAC ASM for AIX6.1.4 完全安装手册</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650622630&amp;idx=1&amp;sn=c5e95c347b624c7397b3f74716ea6e74&amp;scene=21#wechat_redirect">AIX Oracle 11g RAC 安装</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650624504&amp;idx=1&amp;sn=3b707f1f76d246371e88df85915ad8e1&amp;chksm=bef92f7e898ea668eb7c7a8064cfe79a45c6693abee976f658ffd9c34b2d257e9dab7c99e967&amp;scene=21#wechat_redirect">AIX6下安装oracle11G全纪录</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650624397&amp;idx=1&amp;sn=d979dcff6c756df36c4e6135e015d7fe&amp;chksm=bef92f0b898ea61da7c7cf65fb6c0daad1ea3f10ba21f970ff845b75c3e3058df50ad7563aa7&amp;scene=21#wechat_redirect">ORACLE 11g R2 for SUSE 11集群双机部署</a></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号AIX专家俱乐部</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528887121.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628518&amp;idx=1&amp;sn=7e7cfe8ab2b1946d41fd0d5637aa61ea&amp;chksm=bef91f20898e96369a5ff96148ba629d59b6bd4b560ee4add86d972d99ea6fe35479eb67af3e&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/701f8a68-0ad8-47a0-8dfe-92fdd8aad219</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 00:47:52 GMT</pubDate></item><item><title><![CDATA[Centos6.5_64 Oracle 11g rac安装（免费资料）]]></title><link>https://wiki.dgfygk.com/archives/39a68883-7d83-4934-b31d-eef0c95b9e54</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Centos6.5_64%20Oracle%2011g%20rac%E5%AE%89%E8%A3%85%EF%BC%88%E5%85%8D%E8%B4%B9%E8%B5%84%E6%96%99%EF%BC%89&amp;url=/archives/39a68883-7d83-4934-b31d-eef0c95b9e54" width="1" height="1" alt="" style="opacity:0;">
<p><strong>1. 网络规划</strong></p>
<p>
 <br>
</p>
<p>Public用途网卡在所有RAC节点上必须有相同的名字；</p>
<p>Private（interconnect）用途网卡在所有RAC节点上必须有相同的名字；</p>
<p>Public node name 是由hostname命令返回的值</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528897377.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>2. 存储规划</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528897452.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>3. 软件</strong></p>
<p>
 <br>
</p>
<p>操作系统：Centos 6.5 x86_64</p>
<p>安装ORACE RAC 所需要的软件：</p>
<ul>
 <li>
  <p>linux.x64_11gR2_database_1of1.zip</p>
 </li>
 <li>
  <p>linux.x64_11gR2_database_1of2.zip</p>
 </li>
 <li>
  <p>linux.x64_11gR2_grid.zip</p>
 </li>
 <li>
  <p>cvuqdisk-1.0.7-1 在grid软件所内可以找到</p>
 </li>
 <li>
  <p>oracleasm-2.6.18</p>
 </li>
 <li>
  <p>rlwrap-0.37 下载tar软件包进行安装</p>
 </li>
 <li>
  <p>kmod-oracleasm-2.0.6 到网上下载支持2.6.32内核软件包</p>
 </li>
</ul>
<p>
 <br>
</p>
<p><strong>4. 网络配置</strong></p>
<p>
 <br>
</p>
<p>1、 主机文件hosts配置(两台分别如下)</p>
<p>主机名里以后最好用-中线</p>
<p>
 <br>
</p>
<p>vi /etc/hosts</p>
<p>160.99.10.220 wxsdb1</p>
<p>160.99.10.222 wxsdb2</p>
<p>160.99.10.221 wxsdb1-vip</p>
<p>160.99.10.223 wxsdb2-vip</p>
<p>192.168.192.1 wxsdb1-pri</p>
<p>192.168.192.2 wxsdb2-pri</p>
<p>160.99.10.224 wxsdb-scan</p>
<p>
 <br>
</p>
<p>hostname wxsdb1</p>
<p>hostname wxsdb2</p>
<p>
 <br>
</p>
<p>2、 设置ip地址wxsdb1</p>
<p>vi /etc/sysconfig/network-scripts/ifcfg-eth0</p>
<p>DEVICE=eth0</p>
<p>TYPE=Ethernet</p>
<p>ONBOOT=yes</p>
<p>BOOTPROTO=none</p>
<p>IPADDR=160.99.10.220</p>
<p>NETMASK=255.255.255.0</p>
<p>GATEWAY=160.99.10.254</p>
<p>
 <br>
</p>
<p>vi /etc/sysconfig/network-scripts/ifcfg-eth1</p>
<p>DEVICE=eth1</p>
<p>TYPE=Ethernet</p>
<p>ONBOOT=yes</p>
<p>BOOTPROTO=none</p>
<p>IPADDR=192.168.192.1</p>
<p>NETMASK=255.255.255.0</p>
<p>
 <br>
</p>
<p>Wxsdb2 IP地址</p>
<p>vi /etc/sysconfig/network-scripts/ifcfg-eth0</p>
<p>DEVICE=eth0</p>
<p>TYPE=Ethernet</p>
<p>ONBOOT=yes</p>
<p>BOOTPROTO=none</p>
<p>IPADDR=160.99.10.222</p>
<p>NETMASK=255.255.255.0</p>
<p>GATEWAY=160.99.10.254</p>
<p>
 <br>
</p>
<p>vi /etc/sysconfig/network-scripts/ifcfg-eth1</p>
<p>DEVICE=eth1</p>
<p>TYPE=Ethernet</p>
<p>ONBOOT=yes</p>
<p>BOOTPROTO=none</p>
<p>IPADDR=192.168.192.2</p>
<p>NETMASK=255.255.255.0</p>
<p>
 <br>
</p>
<p>3、 IP地址配置完成以后重起网络服务</p>
<p>Service network restart</p>
<p>
 <br>
</p>
<p>4、 查看网络配置</p>
<p>Wxsdb1 IP地址配置如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528897599.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528897755.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528897818.png&amp;size=m" alt=""></p>
<p>Wxsdb2 IP地址配置如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528897903.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898061.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>5. 关闭无用服务</strong></p>
<p>
 <br>
</p>
<p>[root@test1 \~]# ntsysv –level 2345</p>
<p>crond</p>
<p>haldaemon</p>
<p>iptables</p>
<p>irqbalance</p>
<p>lvm2-monitor</p>
<p>messagebus</p>
<p>network</p>
<p>rsyslog</p>
<p>sshd</p>
<p>
 <br>
</p>
<p>[root@test2 \~]# ntsysv –level 2345</p>
<p>crond</p>
<p>haldaemon</p>
<p>iptables</p>
<p>irqbalance</p>
<p>lvm2-monitor</p>
<p>messagebus</p>
<p>network</p>
<p>rsyslog</p>
<p>sshd</p>
<p>
 <br>
</p>
<p><strong>6. 关闭防火墙和SELinux</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898121.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898191.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>7. ssh禁止dns查找，加快ssh连接速度（两台都需要做）</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898296.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898398.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898538.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898682.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>8. 安装rpm包</strong></p>
<p>
 <br>
</p>
<p>配置本地Yum源</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898871.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528898987.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>9. Pdksh无法用yum 安装,需手动下载安装：</strong></p>
<p>
 <br>
</p>
<p>wget ftp://ftp.muug.mb.ca/mirror/centos/5.10/os/x86_64/CentOS/pdksh-5.2.14-37.el5_8.1.x86_64.rpm</p>
<p>如果上面地址无法使用、在百度网盘下载 http://pan.baidu.com/s/1gdoXKUR</p>
<p>http://rpm.pbone.net/index.php3?stat=3\&amp;search=pdksh</p>
<p>如果wget不能下载，则用迅雷下载后再用rz命令传上去</p>
<p>rpm -ivh pdksh*</p>
<p>安装该包可能会有冲突,事先将其它ksh卸载掉:</p>
<p>[root@ora11g tmp]# rpm -e ksh</p>
<p>[root@ora11g tmp]# rpm -ivh pdksh-5.2.14-36.el5.i386.rpm</p>
<p>warning: pdksh-5.2.14-36.el5.i386.rpm: Header V3 DSA signature: NOKEY, key ID e8562897</p>
<p>Preparing… ########################################### [100%]</p>
<p>1:pdksh ########################################### [100%]</p>
<p>安装完成</p>
<p>
 <br>
</p>
<p><strong>10. 安装rlwrap</strong></p>
<p>
 <br>
</p>
<p>rlwrap封装了readline，所以安装rlwrap之前需要安装readline库。</p>
<p>a. 安装</p>
<p>rpm -ivh epel-release-6-8.noarch.rpm</p>
<p>a. 安装readline</p>
<h1>wget ftp://ftp.gnu.org/gnu/readline/readline-6.2.tar.gz</h1>
<h1>tar zxvf readline-6.2.tar.gz</h1>
<h1>cd readline-6.2/</h1>
<h1>./configure</h1>
<h1>make</h1>
<h1>make install</h1>
<p>
 <br>
</p>
<p>rpm -ivh rlwrap-0.42-1.el6.x86_64.rpm</p>
<p>在尾部添加：</p>
<p>alias sqlplus='rlwrap sqlplus'</p>
<p>alias rman='rlwrap rman'</p>
<p>退出oracle用户再重新登录就OK了。现在Linux下的sqlplus用起来就像cmd中一样了。</p>
<p>
 <br>
</p>
<p>操作系统版本修改，安装后再改回来</p>
<p>
 <br>
</p>
<p>[root@wxsdb1 \~]# cat /etc/redhat-release</p>
<p>CentOS release 6.5 (Final)</p>
<p>[root@wxsdb1 \~]# vi /etc/redhat-release</p>
<p>Centos release 5 (Final)</p>
<h1>CentOS release 6.5 (Final)</h1>
<p>[root@wxsdb2 \~]# cat /etc/redhat-release</p>
<p>CentOS release 6.5 (Final)</p>
<p>[root@wxsdb2 \~]# vi /etc/redhat-release</p>
<p>Centos release 5 (Final)</p>
<h1>CentOS release 6.5 (Final)</h1>
<p>
 <br>
</p>
<p>时间修改及时间同步</p>
<p>
 <br>
</p>
<p>[root@wxsdb1 \~]# date</p>
<p>Wed Apr 27 17:33:35 CST 2016</p>
<p>[root@wxsdb2 \~]# date</p>
<p>Wed Apr 27 17:33:50 CST 2016</p>
<p>节点1</p>
<p>cp /etc/ntp.conf /etc/ntp<em>`date +%Y%m%d</em>%H%M%S`.conf</p>
<p>cat /dev/null &gt; /etc/ntp.conf</p>
<p>
 <br>
</p>
<p>cat &gt;&gt; /etc/ntp.conf &lt;&lt; EOF</p>
<p>server 127.127.1.0</p>
<p>fudge 127.127.1.0 stratum 11</p>
<p>driftfile /var/lib/ntp/drift</p>
<p>broadcastdelay 0.008</p>
<p>EOF</p>
<p>cat /etc/ntp.conf</p>
<p>/etc/init.d/ntpd restart</p>
<p>ntpq -p</p>
<p>chkconfig ntpd on</p>
<p>
 <br>
</p>
<p>节点2</p>
<p>cp /etc/ntp.conf /etc/ntp<em>`date +%Y%m%d</em>%H%M%S`.conf</p>
<p>cat /dev/null &gt; /etc/ntp.conf</p>
<p>
 <br>
</p>
<p>cat &gt;&gt; /etc/ntp.conf &lt;&lt; EOF</p>
<p>server 160.99.10.220 prefer</p>
<p>driftfile /var/lib/ntp/drift</p>
<p>broadcastdelay 0.008</p>
<p>EOF</p>
<p>cat /etc/ntp.conf</p>
<p>ntpdate 160.99.10.220</p>
<p>/etc/init.d/ntpd restart</p>
<p>ntpq -p</p>
<p>chkconfig ntpd on</p>
<p>
 <br>
</p>
<p>节点2手工同步一下</p>
<p>service ntpd stop</p>
<p>ntpdate 160.99.10.220</p>
<p>service ntpd start</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899057.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>两台更改以下配置要不在安装grid的时候会报时间同步错误</p>
<h1>vim /etc/sysconfig/ntpd</h1>
<h1>OPTIONS=“-u ntp:ntp -p /var/run/ntpd.pid -g”</h1>
<p>OPTIONS=“-x -u ntp:ntp -p /var/run/ntpd.pid”</p>
<p>重起服务</p>
<p>service ntpd restart</p>
<p>配置时间客户端主机</p>
<h1>vim /etc/sysconfig/ntpd</h1>
<p>OPTIONS=“-x -u ntp:ntp -p /var/run/ntpd.pid "</p>
<h1>OPTIONS=“-u ntp:ntp -p /var/run/ntpd.pid -g”</h1>
<p>重起服务</p>
<p>service ntpd restart</p>
<p>
 <br>
</p>
<p>创建用户和组(两台都要操作)</p>
<p>
 <br>
</p>
<p>groupadd -g 500 oinstall</p>
<p>groupadd -g 501 dba</p>
<p>groupadd -g 502 asmadmin</p>
<p>groupadd -g 503 asmdba</p>
<p>useradd -u 500 -g oinstall -G dba,asmadmin,asmdba oracle</p>
<p>echo oracle \|passwd –stdin oracle</p>
<p>id oracle</p>
<p>
 <br>
</p>
<p>创建安装目录</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899188.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>设置环境变量</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899279.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899352.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>设置 Oracle 用户 ssh</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899475.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899539.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899671.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899760.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899809.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528899946.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900075.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900209.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900370.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点1和2 检验认证命令</p>
<p>
 <br>
</p>
<p>ssh wxsdb2 date</p>
<p>ssh wxsdb2_pri date</p>
<p>ssh wxsdb1 date</p>
<p>ssh wxsdb1_pri date</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900509.png&amp;size=m" alt=""></p>
<p>每条命令执行1次后，再次执行，就不需要输入密码，说明信任配置成功；</p>
<p>
 <br>
</p>
<p>设置内核参数</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900596.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900729.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>两个系统都要执行，执行前看看内存（2GB内存大小为：2147483648）</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900804.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>查看配置1</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528900942.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901012.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>查看配置2</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901156.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901288.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901394.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>设置Oracle 进程及文件限制</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901525.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901643.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901702.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901832.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528901972.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902089.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902158.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902301.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902445.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>查看设置</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902498.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902640.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902723.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902808.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528902966.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903088.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903190.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903350.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903444.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>数据库软件解压缩及设置权限</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903527.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>安装cvuqdisk包</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903711.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903786.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>配置共享存储</p>
<p>
 <br>
</p>
<p>查看共享存储</p>
<p>
 <br>
</p>
<p>节点1</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528903911.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点2</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904030.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>节点1 共享存储分区</p>
<p>
 <br>
</p>
<p>节点1</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904165.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904303.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904428.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904529.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904622.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904702.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528904870.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905012.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905090.png&amp;size=m" alt=""></p>
<p>裸设备设置权限</p>
<p>
 <br>
</p>
<p>节点1</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905150.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905302.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905436.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905610.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>重启操作系统，再次检查配置信息</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905683.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905737.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528905872.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906000.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906120.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>安装clusterware</p>
<p>执行Installer 脚本开始图片化安装</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906246.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>选择集群模式</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906355.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>高级方式安装</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906489.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>添加支持中文</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906573.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>不使用GNS方式而采用本地解析，要与两节点的/etc/hosts文件内容一致</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906645.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>改成</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906770.png&amp;size=m" alt=""></p>
<p>添加第二个节点</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906831.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>ssh连接测试通过</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528906970.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907086.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>指定公网网卡及私网网卡</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907216.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>指定ocr文件及voting文件存储方式</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907344.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>创建ocr文件及voting文件所在的asm磁盘组</p>
<p>
 <br>
</p>
<p>磁盘冗余类型选择：Normal，提示最少需要3块1 2 3</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907482.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>如果磁盘已经被使用了请清除</p>
<p>dd if=/dev/zero of=/dev/raw/raw1 bs=1M count=1024</p>
<p>
 <br>
</p>
<p>设置sys用户和asmsnmp用户密码:oracle</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907535.png&amp;size=m" alt=""></p>
<p>会提示密码过于简单，点击YES继续</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907677.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>不使用IPMI</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907757.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>指定组</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907837.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>确保安装路径正确</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528907908.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908044.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>安装前检验</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908176.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908295.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>报以上错误</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908421.png&amp;size=m" alt=""></p>
<p>其它自己查看一下系统里是否已经有了，有的可能是版本高了</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908548.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>pdksh包不需要安装，光盘里也没有这个包</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908605.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>错误信息提示如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908739.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908826.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>ntp</p>
<p>
 <br>
</p>
<p>ntp已设置，忽略（/etc/sysconfig/ntpd配置文件已添加-x参数也一样报错：OPTIONS=“-x -u ntp:ntp -p /var/run/ntpd.pid -g”）</p>
<p>
 <br>
</p>
<p>错误信息提示如下：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528908956.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909137.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>不报错的解决办法：</p>
<p>报错的ntpd配置文件内容如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909197.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>报错的ntpd配置文件内容如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909325.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909396.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909474.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>开始安装</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909563.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909690.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>执行脚本</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909750.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>注意：安装完成时有两条要执行的脚本，必须按照以下顺序：</p>
<p>A. racdb01执行第一条，直到结束；</p>
<p>B. racdb02执行第一条，直到结束；</p>
<p>C. racdb01执行第二条，直到结束；</p>
<p>D. racdb02执行第二条，直到结束；</p>
<p>特别是第二条脚本，必须等待racdb01执行完毕才能在racdb02上执行。</p>
<p>如果执行脚本时出错，执行 /u01/app/11.2.0/grid/crs/install/rootcrs.pl -verbose -deconfig -force ，然后根据错误提示查找原因修复后，再执行 第二条脚本。</p>
<p>
 <br>
</p>
<p>第一个节点执行脚本</p>
<p>先在节点1和节点2执行：</p>
<p>/u01/app/oraInventory/orainstRoot.sh</p>
<p>其次：</p>
<p>//u01/app/11.2.0/grid/11.2.0/root.sh</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909866.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528909947.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910079.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>会出现这个报错</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910140.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>crs-4124:oraclehigh availability services startup failed</p>
<p>crs-4000:command start failed, or completed with errors</p>
<p>p wh 11.20.1的GUB</p>
<p>原因:/var/tmp/.oracle/npohasd 管道文件的权限不正确。</p>
<p>解决方法：</p>
<p>1、删除配置</p>
<p>/u01/app/11.2.0/grid/crs/install/roothas.pl -deconfig -force</p>
<p>2、在执行/u01/app/11.2.0/grid/root.sh的时候，一般出现Adding daemon to inittab这条信息的时候执行以下命令</p>
<p>/bin/dd if=/var/tmp/.oracle/npohasd of=/dev/null bs=1024 count=1</p>
<p>
 <br>
</p>
<p>查看两边的执行结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910282.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>继续安装ok</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910353.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910490.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>报错忽略，导致这个错误的原因是NTP设置问题，可以忽略</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910580.png&amp;size=m" alt=""></p>
<p>下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910779.png&amp;size=m" alt=""></p>
<p>完成</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910839.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>安装后检查</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528910961.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911092.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911183.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911262.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911406.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>安装database软件</p>
<p>
 <br>
</p>
<p>改变Oracle 环境变量</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911455.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911583.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911655.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>执行安装脚本Installer，进入图形化安装界面</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911791.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911870.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528911950.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>仅安装database软件</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912027.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>两节点全选</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912159.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>ssh连通性检查可忽略</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912219.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912348.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>选择中文支持</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912447.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>选择企业版</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912587.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>组件默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912640.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>确保安装路径正确</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912768.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>组默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528912884.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>安装前检查，报错信息与安装grid类似</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913010.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913134.png&amp;size=m" alt=""></p>
<p>开始安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913194.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913314.png&amp;size=m" alt=""></p>
<p>执行脚本</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913374.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>第一个节点执行脚本</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913512.png&amp;size=m" alt=""></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913604.png&amp;size=m" alt=""></strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913746.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>配置监听</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913827.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528913958.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>创建第二个磁盘组（+DATA），用于存储数据库相关内容</p>
<p>
 <br>
</p>
<p>执行asmca命令</p>
<p>
 <br>
</p>
<p>[oracle@wxsdb1 database]$ asmca</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914087.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914217.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>不知道为什么crs的磁盘组名会变成data，也有可能是在第二安装的时候没有改</p>
<h3>创建data磁盘组</h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914356.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914490.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914563.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>建库</p>
<p>
 <br>
</p>
<p>执行dbca命令</p>
<p>
 <br>
</p>
<p>[oracle@wxsdb1 database]$ dbca</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914692.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>选择创建数据库</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914765.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528914900.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>指定数据库名称，数据库节点全选上</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915017.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>不安装企业管理器</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915112.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915183.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>指定sys用户及system用户密码</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915319.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915439.png&amp;size=m" alt=""></p>
<p>数据库文件位置指定第二个磁盘组位置</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915521.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>默认File location vanlables</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915640.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>未指定，默认multiplex Redo logs and control file</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915772.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>启动归档开启recovery area</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915826.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>先创建需要的磁盘组</p>
<p>[oracle@wxsdb1 \~]$ asmca</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528915961.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916100.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916184.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916235.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916317.png&amp;size=m" alt=""></p>
<p>设置归档位置在第二个磁盘组位置（+DATA1）</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916449.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916512.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916641.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916774.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>SGA和PGA总大小设置为60%（默认40%）</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528916865.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>指定数据库连接客户的最大进程数为500个（默认150）</p>
<p>
 <br>
</p>
<p>这个看机器的性能吧</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917003.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>字符集选中文</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917139.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>连接模式默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917254.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917353.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>控制文件参数，默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917484.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>数据文件位置，默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917623.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>重做日志组需要修改，默认每个组一个成员，现修改成每个组2个成员，成员大小目前默认</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917693.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917868.png&amp;size=m" alt=""></p>
<p>都添加一个</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528917965.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528918036.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528918134.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528918269.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528918371.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>完成建库</p>
<p>
 <br>
</p>
<p><strong>Oracle集群状态查询</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528918509.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<ol>
 <li>crs_stat用法详解：</li>
</ol>
<p>http://duqiangatom.blog.163.com/blog/static/1150251820100188205246/</p>
<p>
 <br>
</p>
<ol>
 <li>查看ASM运行状态：</li>
</ol>
<p>[oracle@wxsdb1 \~]$ srvctl status asm -n wxsdb1</p>
<p>ASM is running on wxsdb1</p>
<p>[oracle@wxsdb1 \~]$ srvctl status asm -n wxsdb2</p>
<p>ASM is running on wxsdb2</p>
<p>[oracle@wxsdb1 \~]$</p>
<ol>
 <li>查看数据库运行状态：</li>
</ol>
<p>[oracle@wxsdb2 \~]$ srvctl status database -d wxs</p>
<p>Instance wxs2 is running on node wxsdb1</p>
<p>Instance wxs1 is running on node wxsdb2</p>
<ol>
 <li>查看实例运行状态</li>
</ol>
<p>[oracle@wxsdb1 \~]$ srvctl status instance -d wxs -n wxsdb1</p>
<p>Instance wxs2 is running on node wxsdb1</p>
<p>[oracle@wxsdb1 \~]$ srvctl status instance -d wxs -n wxsdb2</p>
<p>Instance wxs1 is running on node wxsdb2</p>
<ol>
 <li>查看服务状态</li>
</ol>
<p>[oracle@wxsdb1 \~]$ srvctl status nodeapps</p>
<p>VIP wxsdb1-vip is enabled</p>
<p>VIP wxsdb1-vip is running on node: wxsdb1</p>
<p>VIP wxsdb2-vip is enabled</p>
<p>VIP wxsdb2-vip is running on node: wxsdb2</p>
<p>Network is enabled</p>
<p>Network is running on node: wxsdb2</p>
<p>Network is running on node: wxsdb1</p>
<p>GSD is disabled</p>
<p>GSD is not running on node: wxsdb2</p>
<p>GSD is not running on node: wxsdb1</p>
<p>ONS is enabled</p>
<p>ONS daemon is running on node: wxsdb2</p>
<p>ONS daemon is running on node: wxsdb1</p>
<p>eONS is enabled</p>
<p>eONS daemon is running on node: wxsdb2</p>
<p>eONS daemon is running on node: wxsdb1</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>以上内容由社区会员上传分享</p>
<p>您也可以点击阅读原文到社区下载以上文档的word版（需要社区金币）</p>
<p>
 <br>
</p>
<p>长按下图二维码关注</p>
<p>也可以直接搜索公众号名称"AIX专家俱乐部"或微信号"AIXChina"关注</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528918666.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650623141&amp;idx=1&amp;sn=c80166a9fe513f5234401a177c83cf05&amp;chksm=bef93223898ebb3502255a5033141eb9f3020fa39d04492a38046d96d2a85cf798ba63326b3f&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/39a68883-7d83-4934-b31d-eef0c95b9e54</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 00:47:36 GMT</pubDate></item><item><title><![CDATA[基于 Oracle RAC_ADG_OGG 等设计数据库双活方案，必须掌握五方面知识点]]></title><link>https://wiki.dgfygk.com/archives/35fe440d-c389-4c3a-8cf8-7e28ba9b3541</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8E%20Oracle%20RAC_ADG_OGG%20%E7%AD%89%E8%AE%BE%E8%AE%A1%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8F%8C%E6%B4%BB%E6%96%B9%E6%A1%88%EF%BC%8C%E5%BF%85%E9%A1%BB%E6%8E%8C%E6%8F%A1%E4%BA%94%E6%96%B9%E9%9D%A2%E7%9F%A5%E8%AF%86%E7%82%B9&amp;url=/archives/35fe440d-c389-4c3a-8cf8-7e28ba9b3541" width="1" height="1" alt="" style="opacity:0;">
<p>上周，社区组织活动就数据库(Oracle)双活方案进行了深入探讨，包括<strong>如何选择双活方案、具体方案的复杂度、技术关键点、具体应用场合等等方面。</strong></p>
<p>现由社区专家<strong>赵海</strong>通过对本次交流讨论进行深入的提炼和总结，形成以下五个方面的知识点成果，能够帮助大家在进行相关工作时更得心应手。</p>
<p>
 <br>
</p>
<blockquote>
 <h2><strong>1.基于Oracle数据库技术的容灾方案都有那些？各有何优缺点？应该如何去选择适合自己的数据库容灾方案？</strong></h2>
 <p>容灾向来是以RPO/RTO来定义其级别，所谓的双活只是业内对某种较高容灾级别的架构的俗称，根据不同的角度对其理解也有所偏差。那么基于此，本人暂且认为只要是两个数据中心同时能提供业务服务的就认为是所谓的双活。在这个前提条件下，从Oracle数据库本身的技术来讲，有这么几种方案。</p>
</blockquote>
<p>■ 基于跨中心实现的远距离RAC架构。</p>
<p>1）基于ASM冗余设计实现。</p>
<p>2）基于存储集群化之后的分布式存储卷实现。</p>
<p>■ 基于Oracle ADG/OGG 实现的主备库架构。</p>
<p>我们先从大的方案比较：</p>
<p>1)从RPO角度来看，RAC方案可以做到理论上的绝对同步。ADG可以做到近似同步，但是一般用在异步场合。</p>
<p>2)从RTO角度来看，RAC方案可以做到理论上的秒级自动故障转移。ADG一般需要人工去实现备库切换，而且需要应用改变连接IP地址，重新启动。</p>
<p>3)从风险角度来看，RAC方案一旦实现距离拉伸，最大的风险在于远距离光纤条件下的节点之间的数据交互。而ADG方案就没有该风险存在。</p>
<p>4)从方案的复杂度来看，RAC方案理论上需要第三点的仲裁，需要双中心二层打通等复杂环境条件。而ADG和OGG方案只需要网络三层可达即可。</p>
<p>5)从投资成本来看，RAC方案实现距离的拉伸之后，需要的环境成本（网络条件、仲裁条件）等都需要较高的成本。ADG和OGG方案没有这些成本。</p>
<p>由此可以看出，实际上从容灾角度考虑（RTO/RPO）,那么RAC方案一定是比ADG方案能实现RTO和RPO的更高目标，但是从成本和风险角度考虑，ADG又是最佳的选择。</p>
<p>那么撇开成本和风险，只考虑容灾目标的话，我们再来比较一下对于RAC方案的两种不同存储架构的差异：</p>
<p>1)首先是实现难度及投资比较,ASM冗余设计架构的复杂度在于 ASM 层的设计。Oracle RAC 实例节点看到的共享盘是基于双中心存储实现的镜像策略，所有IO的读写分发是由ASM本身的冗余算法规则来决定的，DBA不仅仅要根据磁盘情况来设计合理的 Failure Group，而且需要结合第三方站点的网络存储卷来合理设计仲裁磁盘组的分配。更重要的是需要结合实际的网络环境指标（延时、稳定性等）进行复杂的性能、稳定性、灾难测试等来调整ASM的一些IO参数。基于分布式存储卷设计架构的复杂度在于整体架构的复杂度。</p>
<p>例如仲裁一致性问题，是指双中心之间的存储集群和数据库RAC集群的仲裁结果是否能保证一致性。存储集群是靠仲裁站点分别于两个站点之间的网络连通性来判定站点故障。而数据库集群是通过以太网心跳和OCR仲裁盘来做数据库仲裁。而数据库的OCR仲裁盘是存储集群提供的分布式共享卷。二者仲裁时的一致性如何保障是非常重要的一个问题。假设在发生站点级别故障时，数据库集群首先根据网络故障触发仲裁，判定站点A的节点存活。而存储随后再发生存储集群的仲裁，这个时候如果根据仲裁站点判定的结果恰恰仲裁委站点B的节点存活。那么数据库集群整体就会宕掉，这对于业务来讲就是一个灾难。</p>
<p>2)从实现的基本条件来看，两种架构的实现都会依赖双中心的二层打通。双中心的波分设备、以太转换设备、光纤链路租用就是必不可少的条件了。包括其购置成本和日后的运维成本等。这是非常可观的一项成本预算。从存储层的架构组成来看，ASM冗余设计架构不需要存储层增加任何其他设备成本及运维成本。但是分布式存储卷架构需要依赖存储层的虚拟化网关产品来实现存储虚拟化集群，无疑这需要增加相应的购置成本和相应的运维成本。尤其注意存储集群产品是否有容量许可成本问题。从第三点的仲裁站点成本来看，两种方案都需要第三点的仲裁，区别在于ASM冗余设计架构需要的是NAS存储，而分布式存储卷架构需要的基于以太网的计算资源来配置仲裁虚拟机。投入成本没有什么差异。</p>
<p>3)从Oracle运维成本来看，ASM冗余设计架构对DBA的要求非常苛刻，需要DBA不仅仅能够深知其中的原理，而且需要对性能的分析有较深的造诣，从而保障在复杂的双中心联动环境下各种复杂情况下的性能及稳定性变动有快速和准确的判断和处理能力。分布式存储卷架构对DBA的要求没有特殊的苛刻要求但是需要增加对存储集群的专业维护成本。</p>
<p>从银行业的角度来看，行业看中的更多的是RPO和RTO以及风险度本身这几个点。</p>
<p>从RPO和RTO角度来选的话，那么方案一定是RAC的方案。但是从风险角度来考虑的话，那么ADG会是比较好的选择。OGG主要用于异构平台之间的数据传输。但是这几中方案并不是只能选择其中一种而舍弃其他的方案。方案各有优劣，为了尽可能达到整体最优，可以考虑方案的组合。</p>
<p>比如说同城实现RAC、异地实现ADG。或者在RAC基础之上再增加一个ADG。成本上没有太大的改变，但是保险系数上却增加了很多。</p>
<p>OGG可以考虑到特殊业务场合，比如说为了搭建数据平台实现不同数据库数据的整合等。</p>
<p>
 <br>
</p>
<blockquote>
 <h2><strong>2.基于ORACLE RAC/ADG 双活方案实施，难点和关键点是什么？如何解决？</strong></h2>
 <p>1）针对分布式存储卷架构的仲裁一致性问题</p>
</blockquote>
<p>在这个问题上，风险发生的引发点有两个：数据库和集群的仲裁触发以及仲裁过程的时间顺序发生紊乱；资源被1:1割裂之后的默认仲裁策略不一致。也就是说，只要控制这两个引发点，那么这个问题从理论上也就避免了。对于第一个引发点来讲，实际上存储集群的默认仲裁触发时间会是15秒左右，而数据库仲裁触发的控制参数由misscount这个参数来决定，所以只要我们将misscount这个参数调整到45秒之后，也就是说理论上绝对保障存储集群仲裁在前，而数据库仲裁在后，那么第一个引发点就没有了。对于第二个引发点来讲，假设两站点节点资源对等，仲裁选票同样对等的情况下，存储集群会有一个默认的Winner策略，同样在这种情况下数据库集群也有一个默认仲裁策略：选择实例号小的集群存活。只要我们保证这两个策略结果的一致性，那么第二个引发点也就不存在了。</p>
<p>2）链路稳定状况不可控</p>
<p>这个问题是两种架构都面临的问题。主要表现为两个方面：链路稳定状况不可控；延时指标不可控。因为双中心之间的链路是通过租用运营商的裸光纤链路实现的，那么这其中会经历很多的中继设备及节点。无论从管理上还是从技术把控上都是金融企业自身不可控制的因素。假设双中心间链路延时指标不稳定，也就是说数据库节点之间私网传输的延时会经常出现长延时情况，这势必导致这种延时会加倍放大到数据库节点之间的读写热点竞争上。由于数据库集群之间的数据传输量非常大（缓存、锁、心跳等），在读写热点相对突出的业务上，轻则导致数据库读写性能灾难，重则导致数据库节点直接处于僵死状态。另外，链路的不稳定会导致存储链路频繁切换，甚至会导致集群仲裁频繁发生，这对于业务连续性更是一个灾难。</p>
<p>对于这个问题来讲，就目前金融行业的传统数据架构来讲，并没有一个十足的解决方案。我们只能通过以下措施来减少这种问题带给我们的风险。</p>
<p>i、业务层面需要进行拆分重组：按照IO特点进行合理拆分，将读写业务尽量分布于不同节点上，减少节点间的锁竞争。按照业务将数据库表进行分区，避免在数据库写上的数据热点块儿。例如，对于银行核心系统来讲，尤其是要将批量业务和联机业务区分对待，批量业务的热点以及数据量非常之巨大，所以一定要将批量业务的数据库读写放在单边实现。对于联机业务来讲可以根据热点状况以及链路质量评测结果可以尝试实现双中心同时读写，但是本文建议对于这种重量级的业务还是要从业务层尽量实现应用上的读写分离，或者在应用层双中心部署而在数据库层将数据引到单边来做。</p>
<p>ii、双中心间通讯的整体控制，具体包括对通讯带宽的优先级管理、对通讯的实时监控和控制、对跨中心数据传输的严格策略把控。例如：优先保障存储和数据库通讯的优先级和带宽，严格的规则算法和优先级限定VMOTION、DRS等行为的跨中心随意性，从LTM负载分发上尽可能保障正常情况下纵向IO的单中心效率策略，故障情况下保障跨中心访问的科学性。DWDM上设置双中心间通讯带宽的逻辑隔离以及实时可控。</p>
<p>3）存储网络故障泛滥</p>
<p>这是两种架构都会面临的问题，只是ASM冗余设计架构可能性相对高一些。</p>
<p>如果我们把两个中心的SAN环境整合为一张大网，物理上没有任何隔离的大网，那么可能会因为局部的存储网络故障而波及到整个存储网络。尽管我们通过SAN交换机上的逻辑隔离能够解决大部分的安全问题，但是这样的风险毕竟还是存在的。</p>
<p>所以我们可以通过对数据中心内部SAN环境前后物理隔离，双中心之间靠专一SAN交换机实现存储后端网络的联通来解决该问题。这样的话，单中心内前段SAN环境故障不会波及存储后端，更不会波及整个基础架构的存储网络。</p>
<p>4）串联深度带来的性能问题</p>
<p>这个问题是针对分布式存储卷架构的问题。</p>
<p>架构深度越深，那么IO的性能就会越差，因为IO每经过一层设备就会有一定的延时消耗，纵向深度越深经历的设备越多，那么IO的延时也就越高。如果我们的架构在纵向上越复杂，那么这个问题应该说从本质上是无法消除的，只能通过一定的方法来减少和优化。</p>
<p>从存储层来看，一般存储侧在对物理卷进行虚拟化的时候都会有几种策略。为了增加管理的灵活性及扩展性，虚拟化的时候可能会经过多层映射。另外一种策略是为了提高性能，在虚拟化的时候尽量较少映射。我们在规划存储卷的时候，尽量采用后一种策略。例如VPLEX就会有（1:1map、Raid等策略），我们可以选择1:1map这种策略，仅仅利用它的镜像聚合，而舍弃它的灵活伸缩特性。</p>
<p>
 <br>
</p>
<blockquote>
 <h2><strong>3. 基于ASM冗余设计架构实现的数据库双活方案，如何规划ASM？</strong></h2>
 <p>ASM使用独特的镜像算法：不镜像磁盘，而是镜像盘区。作为结果，为了在产生故障时提供连续的保护，只需要磁盘组中的空间容量，而不需要预备一个热备(hot spare)磁盘。不建议用户创建不同尺寸的故障组，因为这将会导致在分配辅助盘区时产生问题。ASM将文件的主盘区分配给磁盘组中的一个磁盘时，它会将该盘区的镜像副本分配给磁盘组中的另一个磁盘。给定磁盘上的主盘区将在磁盘组中的某个伙伴磁盘上具有各自的镜像盘区。ASM确保主盘区和其镜像副本不会驻留在相同的故障组中。</p>
</blockquote>
<p>磁盘组的冗余可以有如下的形式：双向镜像文件(至少需要两个故障组)的普通冗余(默认冗余)和使用三向镜像(至少需要3个故障组)提供较高保护程度的高冗余。 一旦创建磁盘组，就不可以改变它的冗余级别。为了改变磁盘组的冗余，必须创建具有适当冗余的另一个磁盘组，然后必须使用RMAN还原或DBMS_FILE_TRANSFER将数据文件移动到这个新创建的磁盘组。三种不同的冗余方式如下：</p>
<p>1、 外部冗余（external redundancy）：表示Oracle不帮你管理镜像，功能由外部存储系统实现，比如通过RAID技术；有效磁盘空间是所有磁盘设备空间的大小之和。</p>
<p>2、 默认冗余（normal redundancy）：表示Oracle提供2份镜像来保护数据，有效磁盘空间是所有磁盘设备大小之和的1/2 （使用最多）</p>
<p>3、 高度冗余（high redundancy）：表示Oracle提供3份镜像来保护数据，以提高性能和数据的安全，最少需要三块磁盘（三个failure group）；有效磁盘空间是所有磁盘设备大小之和的1/3，虽然冗余级别高了，但是硬件的代价也最高。</p>
<p>至于如何选择，不言而喻。</p>
<p>一个简单的案例：</p>
<p>A中心存储的LUN在系统上看到的设备文件名叫： hdisk1,hdisk2,100G hdisk3,hdisk4,20G, hdisk5,1G</p>
<p>B中心存储的LUN在系统上看到的设备文件名叫： hdisk6,hdisk7,100G hdisk8,hdisk9,20G, hdisk10,1G</p>
<p>仲裁中心的LUN在系统上看到的文件名叫：hdisk11,1G</p>
<p>DATA磁盘组： 规划两个failuregroup：
 <br>
 failuregroup1=(hdisk1,hdisk2) failuregroup2=(hdisk6,hdisk7)
 <br>
 策略=normal</p>
<p>FRA磁盘组：规划两个failuregroup：
 <br>
 failuregroup1=(hdisk3,hdisk4) failuregroup2=(hdisk8,hdisk9)
 <br>
 策略=normal</p>
<p>OCR磁盘组：
 <br>
 hdisk5,hdisk10,hdisk11</p>
<p>
 <br>
</p>
<blockquote>
 <h2><strong>4. ADG\&amp;OGG的优缺点是什么？各自应用的场合是什么？DG实施的注意点有哪些？</strong></h2>
 <p>ADG 同构平台数据同步，OGG可以异构平台数据同步。</p>
</blockquote>
<p>ADG 用在容灾场合，可以同城、可以异地。OGG一般用在异构平台迁移场合。</p>
<p>ADG 相对比较稳定，OGG 相比而言问题多一些。</p>
<p>ADG 架构可以灵活组合，OGG 架构相对会单一。</p>
<p>ADG 可以通过快照方式保留当前时刻点数据，OGG不能做到。</p>
<p>ADG：五级容灾目标场合下的数据库主备切换架构。</p>
<p>OGG：数据平台的建设或者是异构数据库之间的集中整合或者是同步。</p>
<p>在金融行业，常见的容灾架构为ADG，尤其是异地灾备。也有部分较高要求的采用 RAC + ADG，这里的RAC有的是基于存储集群虚拟出来的分布式卷之上做的RAC，有的是通过ASM冗余设计本身实现的。OGG在重大变更需要异构数据库同步数据的场合下或者是金融行业的数据集中平台上采用。</p>
<p>ADG，最常用的同城，异地灾备解决方案，物理级备份，备机不可写，传输数据为所有redo日志的更改，数据量稍大，不过从以往的使用经验来看，也不太会影响网络，除非应用对网络有很苛刻的要求，即使有，也可以通过vlan或者路由或者多网卡的方法特别建立网络通道，主备库完全一致，缺点是必须全库备份。OGG，DSG这两个是一个类型的，逻辑备份，主要采用特有的技术从联机日志中抽取更改项应用到备库，主备库为两个库，可以全库同步也可以同步单张表或数张表，同步速度较快，传输数据量很少，DML操作和DDL操作均支持。</p>
<p>
 <br>
</p>
<blockquote>
 <h2><strong>5. 在 Oracle RAC 双活方案落地之前，我们应该做那些测试？应该模拟那些灾难故障？</strong></h2>
 <p>观点一：</p>
</blockquote>
<p>最重要的是压力测试，尤其要测试两个节点之间的热点可以承受到什么样的水平。然后就是故障测试，模拟单中心故障、链路故障、以太设备故障、节点故障、网卡故障、仲裁故障等各种条件下是否能如预期实现。</p>
<p>观点二：</p>
<p>测试无非就是设备的测试和应用的测试，这块都差不多，尽可能的压到最大的阀值。</p>
<p>至于灾难故障的话，双活的最大故障就是一个中心完全不可用，模拟一个数据中心全部宕机，当然还有一些其他的单设备或者单应用的测试，虽然操作起来有一定的风险，不过还是值得的。</p>
<hr>
<p><strong>以下专家参与分享：</strong></p>
<p><strong>赵海 大连农商行 架构师</strong></p>
<p>先后就职于IBM、HP、城市商业银行。一直从事系统、存储、数据库等问题诊断、优化、解决方案等规划实施工作。现就职于农商银行信息科技部。主要负责数据中心基础架构规划设计及建设工作，尤其是数据库及存储的高可用及容灾架构规划及建设工作。目前在社区关注排行榜中排名第三位。</p>
<p><strong>冯帅 点融网 高级DBA</strong></p>
<p>精通Oracle、 MySQL。 擅长异构数据库数据同步及迁移、数据库的设计和调优。参与过Oracle数据库的双活项目，有丰富的经验。</p>
<p><strong>韩成亮 某金融单位 数据库架构师</strong></p>
<p>擅长数据库的设计、性能诊断、SQL调优，熟悉异构数据库之间的数据同步和迁移，目前从事MySQL相关的运维和架构工作。曾经设计过Oracle数据库的双活项目方案，有丰富实践经验。</p>
<p>
 <br>
</p>
<blockquote>
 <p>更多相关主题文章请点击阅读原文</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528924939.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630827&amp;idx=1&amp;sn=0684f877a3434b14b22f0e79a6ba5515&amp;chksm=bef9142d898e9d3ba0b8e11c983ad2244ff5a22c777f1469e1b6962f4b126503778a0a9abba6&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/35fe440d-c389-4c3a-8cf8-7e28ba9b3541</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 00:47:11 GMT</pubDate></item><item><title><![CDATA[基于 Oracle RAC 实现双活方案的架构分析和实现难度分析]]></title><link>https://wiki.dgfygk.com/archives/bd16008f-d410-4909-a4f4-8f1e817f99a7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8E%20Oracle%20RAC%20%E5%AE%9E%E7%8E%B0%E5%8F%8C%E6%B4%BB%E6%96%B9%E6%A1%88%E7%9A%84%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90%E5%92%8C%E5%AE%9E%E7%8E%B0%E9%9A%BE%E5%BA%A6%E5%88%86%E6%9E%90&amp;url=/archives/bd16008f-d410-4909-a4f4-8f1e817f99a7" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <h2>原题：基于 Oracle RAC 实现双活方案的分析</h2>
 <h2>作者：赵海 架构师，先后就职于IBM、HP、城市商业银行。一直从事系统、存储、数据库等问题诊断、优化、解决方案等规划实施工作。现就职于农商银行信息科技部。主要负责数据中心基础架构规划设计及建设工作，尤其是数据库及存储的高可用及容灾架构规划及建设工作。目前在社区关注排行榜中排名第三位。</h2>
 <p>
  <br>
 </p>
</blockquote>
<h2><strong>1.架构分析</strong></h2>
<h3><strong>1.1基于ASM冗余设计架构</strong></h3>
<p>图1.1基于ASM冗余设计实现的Oracle RAC
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528925804.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p>存储层实现SAN网络跨数据中心级联，使双数据中心能够实现整体SAN网络。</p>
 </li>
 <li>
  <p>网络层实现二层打通，SCAN IP可以跨数据中心浮动。</p>
 </li>
 <li>
  <p>应用层实现跨数据中心RAC，每一个数据中心分别有一个实例节点。</p>
 </li>
 <li>
  <p>Oracle ASM存储层，数据磁盘组需要实现基于双数据中心存储卷的双镜像冗余策略，OCR仲裁磁盘组需要实现基于双数据中心存储卷以及第三方站点网络存储卷的三块儿磁盘高可用策略。</p>
 </li>
</ul>
<h3><strong>1.2基于存储集群实现的架构</strong></h3>
<p>图1.2基于存储集群实现的架构
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528925967.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p>存储层借助存储虚拟化产品实现双数据中心以及第三方仲裁站点组成的存储集群，使得存储可以提供给应用层分布式虚拟磁盘，最终让应用对存储层的逻辑映射没有任何感知。</p>
 </li>
 <li>
  <p>网络层实现二层打通，SCAN IP可以跨数据中心浮动。</p>
 </li>
 <li>
  <p>应用层实现跨数据中心RAC，每一个数据中心分别有一个实例节点。</p>
 </li>
 <li>
  <p>Oracle ASM存储层，磁盘组不需要做任何特殊冗余配置，只需要将存储层提供的分布式虚拟磁盘看做是本地共享磁盘进行安装配置即可。</p>
 </li>
</ul>
<h2><strong>2.实现难度分析</strong></h2>
<h3><strong>2.1 架构复杂度</strong></h3>
<ul>
 <li>
  <p>架构一的复杂度在于ASM层的设计。ORACLE RAC实例节点看到的共享盘是基于双中心存储实现的镜像策略，所有IO的读写分发是由ASM本身的冗余算法规则来决定的，DBA不仅仅要根据磁盘情况来设计合理的Failure Group，而且需要结合第三方站点的网络存储卷来合理设计仲裁磁盘组的分配。更重要的是需要结合实际的网络环境指标（延时、稳定性等）进行复杂的性能、稳定性、灾难测试等来调整ASM的一些IO参数。</p>
 </li>
 <li>
  <p>架构一的复杂度在于整体架构的复杂度。例如仲裁一致性问题，是指双中心之间的存储集群和数据库RAC集群的仲裁结果是否能保证一致性。存储集群是靠仲裁站点分别于两个站点之间的网络连通性来判定站点故障。而数据库集群是通过以太网心跳和OCR仲裁盘来做数据库仲裁。而数据库的OCR仲裁盘是存储集群提供的分布式共享卷。二者仲裁时的一致性如何保障是非常重要的一个问题。假设在发生站点级别故障时，数据库集群首先根据网络故障触发仲裁，判定站点A的节点存活。而存储随后再发生存储集群的仲裁，这个时候如果根据仲裁站点判定的结果恰恰仲裁委站点B的节点存活。那么数据库集群整体就会宕掉，这对于业务来讲就是一个灾难。</p>
 </li>
</ul>
<h3><strong>2.2 落地成本</strong></h3>
<ul>
 <li>
  <p>从实现的基本条件来看，两种架构的实现都会依赖双中心的二层打通。双中心的波分设备、以太转换设备、光纤链路租用就是必不可少的条件了。包括其购置成本和日后的运维成本等。这是非常可观的一项成本预算。</p>
 </li>
 <li>
  <p>从存储层的架构组成来看，架构一不需要存储层增加任何其他设备成本及运维成本。但是架构二需要依赖存储层的虚拟化网关产品来实现存储虚拟化集群，无疑这需要增加相应的购置成本和相应的运维成本。尤其注意存储集群产品是否有容量许可成本问题。</p>
 </li>
 <li>
  <p>从第三点的仲裁站点成本来看，两种方案都需要第三点的仲裁，区别在于架构一需要的是NAS存储，而架构二需要的基于以太网的计算资源来配置仲裁虚拟机。投入成本没有什么差异。</p>
 </li>
 <li>
  <p>从Oracle运维成本来看，架构一对DBA的要求非常苛刻，需要DBA不仅仅能够深知其中的原理，而且需要对性能的分析有较深的造诣，从而保障在复杂的双中心联动环境下各种复杂情况下的性能及稳定性变动有快速和准确的判断和处理能力。架构二对DBA的要求没有特殊的苛刻要求但是需要增加对存储集群的专业维护成本。</p>
 </li>
</ul>
<h3><strong>2.3 关键问题及解决方案</strong></h3>
<h4><strong>2.3.1针对架构二的仲裁一致性问题</strong></h4>
<p>在这个问题上，风险发生的引发点有两个：数据库和集群的仲裁触发以及仲裁过程的时间顺序发生紊乱；资源被1:1割裂之后的默认仲裁策略不一致。也就是说，只要控制这两个引发点，那么这个问题从理论上也就避免了。对于第一个引发点来讲，实际上存储集群的默认仲裁触发时间会是15秒左右，而数据库仲裁触发的控制参数由misscount这个参数来决定，所以只要我们将misscount这个参数调整到45秒之后，也就是说理论上绝对保障存储集群仲裁在前，而数据库仲裁在后，那么第一个引发点就没有了。对于第二个引发点来讲，假设两站点节点资源对等，仲裁选票同样对等的情况下，存储集群会有一个默认的Winner策略，同样在这种情况下数据库集群也有一个默认仲裁策略：选择实例号小的集群存活。只要我们保证这两个策略结果的一致性，那么第二个引发点也就不存在了。</p>
<h4><strong>2.3.2链路稳定状况不可控</strong></h4>
<h4>这个问题是两种架构都面临的问题。主要表现为两个方面：链路稳定状况不可控；延时指标不可控。因为双中心之间的链路是通过租用运营商的裸光纤链路实现的，那么这其中会经历很多的中继设备及节点。无论从管理上还是从技术把控上都是金融企业自身不可控制的因素。假设双中心间链路延时指标不稳定，也就是说数据库节点之间私网传输的延时会经常出现长延时情况，这势必导致这种延时会加倍放大到数据库节点之间的读写热点竞争上。由于数据库集群之间的数据传输量非常大（缓存、锁、心跳等），在读写热点相对突出的业务上，轻则导致数据库读写性能灾难，重则导致数据库节点直接处于僵死状态。另外，链路的不稳定会导致存储链路频繁切换，甚至会导致集群仲裁频繁发生，这对于业务连续性更是一个灾难。</h4>
<h4>对于这个问题来讲，就目前金融行业的传统数据架构来讲，并没有一个十足的解决方案。我们只能通过以下措施来减少这种问题带给我们的风险。</h4>
<h4>一、业务层面需要进行拆分重组：按照IO特点进行合理拆分，将读写业务尽量分布于不同节点上，减少节点间的锁竞争。按照业务将数据库表进行分区，避免在数据库写上的数据热点块儿。例如，对于银行核心系统来讲，尤其是要将批量业务和联机业务区分对待，批量业务的热点以及数据量非常之巨大，所以一定要将批量业务的数据库读写放在单边实现。对于联机业务来讲可以根据热点状况以及链路质量评测结果可以尝试实现双中心同时读写，但是本文建议对于这种重量级的业务还是要从业务层尽量实现应用上的读写分离，或者在应用层双中心部署而在数据库层将数据引到单边来做。</h4>
<p>二、双中心间通讯的整体控制，具体包括对通讯带宽的优先级管理、对通讯的实时监控和控制、对跨中心数据传输的严格策略把控。例如：优先保障存储和数据库通讯的优先级和带宽，严格的规则算法和优先级限定VMOTION、DRS等行为的跨中心随意性，从LTM负载分发上尽可能保障正常情况下纵向IO的单中心效率策略，故障情况下保障跨中心访问的科学性。DWDM上设置双中心间通讯带宽的逻辑隔离以及实时可控。</p>
<h4><strong>2.3.3存储网络故障泛滥</strong></h4>
<h4>这是两种架构都会面临的问题，只是架构一可能性相对高一些。</h4>
<h4>如果我们把两个中心的SAN环境整合为一张大网，物理上没有任何隔离的大网，那么可能会因为局部的存储网络故障而波及到整个存储网络。尽管我们通过SAN交换机上的逻辑隔离能够解决大部分的安全问题，但是这样的风险毕竟还是存在的。</h4>
<p>所以我们可以通过对数据中心内部SAN环境前后物理隔离，双中心之间靠专一SAN交换机实现存储后端网络的联通来解决该问题。这样的话，单中心内前段SAN环境故障不会波及存储后端，更不会波及整个基础架构的存储网络。</p>
<h4><strong>2.3.4串联深度带来的性能问题</strong></h4>
<h4>这个问题是针对架构二的问题。</h4>
<p>架构深度越深，那么IO的性能就会越差，因为IO每经过一层设备就会有一定的延时消耗，纵向深度越深经历的设备越多，那么IO的延时也就越高。如果我们的架构在纵向上越复杂，那么这个问题应该说从本质上是无法消除的，只能通过一定的方法来减少和优化。</p>
<p>从存储层来看，一般存储侧在对物理卷进行虚拟化的时候都会有几种策略。为了增加管理的灵活性及扩展性，虚拟化的时候可能会经过多层映射。另外一种策略是为了提高性能，在虚拟化的时候尽量较少映射。我们在规划存储卷的时候，尽量采用后一种策略。例如VPLEX就会有（1:1map、Raid等策略），我们可以选择1:1map这种策略，仅仅利用它的镜像聚合，而舍弃它的灵活伸缩特性。</p>
<p>
 <br>
</p>
<p><strong>技术交流活动</strong> <strong>预告</strong> <strong>基于Oracle RAC/ADG/OGG等实现的数据库双活方案设计交流</strong></p>
<p>对于 Oracle 数据库技术，相信大家都不陌生。</p>
<p>为了保障数据库的高可用性，Oracle RAC 技术已经在各行各业进行了广泛的应用，Oracle ADG/OGG 技术在异地容灾方面也有很成熟的应用，相关的知识经验以及分享也比较丰富。</p>
<p>近些年来，随着各行各业对IT建设的容灾标准的高标准要求，越来越多的行业在探讨将 Oracle RAC 技术应用到近距离的同城容灾场合当中，也有部分行业进行了大胆的尝试和落地。但是由于工程的规模比较大，实施复杂度比较高，大部分的企业还缺乏相关的建设经验和知识积累。在这类项目规划设计过程中，我们应该如何选择我们的架构组成？如何进行相关设备的选型？如何将技术的细节落实到架构规划设计过程当中等等问题都是企业IT架构规划设计者非常关注的问题。</p>
<p>基于ORACLE RAC技术实现的双活架构，根据存储设计的不同方式大体上会有两种基本架构，一种是基于ASM实现存储层的跨中心冗余设计，另外一种是借助成熟的存储虚拟化产品实现存储层自身的跨中心冗余设计。但是在每一种架构框架之上，其中有很多细节问题值得我们去探讨和分享。</p>
<p>本次活动以此为技术背景，从以下几个方面展开讨论。希望大家能够慷慨解囊、分享经验，我们一起来成长。</p>
<ol>
 <li>
  <p>基于Oracle RAC 的双活方案，如何选择节点组成模式？（1+1,2+1……）</p>
 </li>
 <li>
  <p>基于ASM冗余设计和存储层集群的两种数据库双活架构，各有什么样的优缺点？我们应该如何根据我们的自身情况去选择？</p>
 </li>
 <li>
  <p>基于ASM冗余设计的架构，仲裁磁盘组应该如何去规划？</p>
 </li>
 <li>
  <p>基于ASM冗余设计的架构，数据及快速恢复磁盘组应该如何去规划？</p>
 </li>
 <li>
  <p>基于存储集群架构设计的数据库双活架构，我们应该如何去选择存储层的架构和产品？</p>
 </li>
 <li>
  <p>Oracle ADG以及OGG技术在数据库双活方案基础之上能起到什么样的辅助作用？应该作为什么样的角色应用到什么样的场合？</p>
 </li>
 <li>
  <p>在 Oracle RAC 双活方案落地之前，我们应该做那些测试？应该模拟那些灾难故障？</p>
 </li>
</ol>
<p><strong>点击阅读原文</strong> ，现在即可参加，本文作者将与你共同交流</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528926053.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630718&amp;idx=1&amp;sn=06eead6c3f885fd9dfa7a8b662512607&amp;chksm=bef914b8898e9dae954c33cc13537964cc099b900ed0b88b2abd5f73f16939a37088ba0fa23d&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/bd16008f-d410-4909-a4f4-8f1e817f99a7</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 00:41:08 GMT</pubDate></item><item><title><![CDATA[Oracle RAC 建设过程中各个层面关键点和优化项总结]]></title><link>https://wiki.dgfygk.com/archives/61aa0e6b-83e4-4afd-8956-d31889131734</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Oracle%20RAC%20%E5%BB%BA%E8%AE%BE%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%90%84%E4%B8%AA%E5%B1%82%E9%9D%A2%E5%85%B3%E9%94%AE%E7%82%B9%E5%92%8C%E4%BC%98%E5%8C%96%E9%A1%B9%E6%80%BB%E7%BB%93&amp;url=/archives/61aa0e6b-83e4-4afd-8956-d31889131734" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <h1><strong>原题：Oracle RAC建设过程中必须要知和要做的事情</strong></h1>
 <h1>作者：赵海，某城商行系统架构师，专注并擅长银行数据中心解决方案规划及设计。</h1>
 <p>
  <br>
 </p>
</blockquote>
<p>数据库建设过程中缺少可以遵循的实践标准，知识点和经验点散落四处难以快速形成逻辑性强的参照标准，面对项目令人无从下手。本文是基于数据建设的规划、实施以及配置优化等阶段对大量文献的总结提炼，以及从项目的实践出发，对数据库建设过程中各个层面应该注意的事项进行的总结。优质长文，建议先收藏后看。</p>
<p>
 <br>
</p>
<h1><strong>1.背景描述</strong></h1>
<p>数据库建设是每一个企业数据中心建设过程中非常重要的一个环节，直接关系到业务连续性和稳定性。但是我们在数据库建设过程当中却很少有可以遵循的实践标准。当我们面对整个建设项目的规划设计和配置优化的时候，又觉得无从下手。散落在官方网站上的一些知识点和经验点无法让我们快速形成一个具有很强逻辑性的参照标准。本文希望通过以下篇幅的总结和分析，从各个层面给予实践标准，为日后从事数据库建设的项目提供一个参考思路。</p>
<p>
 <br>
</p>
<h1><strong>2.存储规划设计的关键点</strong></h1>
<h2><strong>2.1 OCR/VOTE磁盘的合理规划事项</strong></h2>
<p>什么是OCR/VOTE磁盘，它在集群中是什么样的角色呢？</p>
<p>ORACLE RAC ASM管理模式下，磁盘组通常有三个（+DATA，+FRA，+OCR），在OCR磁盘组当中所有的磁盘中存储的数据包括两部分，一部分是Vote File，另外一部分就是OCR（Oracle Cluster Registry）。Vote File是用来记录集群节点的磁盘心跳信息，而OCR是保存集群配置信息的数据。Vote File，以整个文件的方式存储在OCR磁盘上，不做任何条带。下图是其信息记录的一个说明：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528920543.png&amp;size=m" alt=""></p>
<p>以上是一个三节点的ORACLE RAC集群的Vote FIle的一个示意矩阵，每一行是一个节点的写入的信息，例如第一行，Instance1分别把其对集群中的三个成员（1、2、3）进行私网检测的结果写入到仲裁文件当中，Instance2、Instance3同样把其检测结果写入仲裁文件，最终组成了三个节点的仲裁矩阵。当私网发生故障而从网络上导致集群分割为几个孤岛子集的时候，集群是通过这个文件的信息来判断最后存活的节点。具体算法有两个非常重要的规则：</p>
<ol>
 <li>
  <p>保障隔离后的集群子集中节点数目最多的子集存活。</p>
 </li>
 <li>
  <p>当隔离后的集群子集获得的仲裁票数相等时，保障实例号小者存活。</p>
 </li>
</ol>
<p>对于Vote File本身的数目来讲，Oracle又有一个非常重要的规则：集群节点获得的Vote File数目小于N/2+1时，节点就会被集群驱逐出集群，N是Vote File的总数目。也就是说集群中所有节点获得的Vote File数目必须要大于等于N/2+1。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528920685.png&amp;size=m" alt=""></p>
<p>根据以上结果来看，对于VOTE磁盘组的规划，首先偶数个仲裁磁盘必然会造成一个浪费。那么我们就没有必要选择偶数仲裁磁盘。然后我们再考虑磁盘组磁盘的容错能力，为了保障我们至少有1份磁盘容错能力，我们的仲裁磁盘至少是3块儿。也就是说对于OCR磁盘组的规划来讲，至少保障其内有三个容错组，每一个容错组里面一块儿仲裁磁盘。</p>
<p>对于OCR来讲，它属于集群的资源注册信息，是集群运行的前提条件。所以一定要保障它的高可用性。由于它属于配置数据，那么一定会遵循ORACLE ASM的磁盘冗余策略（External、Normal、High）。也就是说OCR在OCR磁盘组里面可以拥有1份、2份、3份镜像。每份镜像的数据条带会落在一个独立的容错组里。</p>
<p>综上所述，对于OCR磁盘组的规划，为了保障仲裁盘的至少一份的容错能力以及OCR数据的高冗余策略，我们应该至少将磁盘组内规划为3个磁盘，每一个磁盘落在一个独立的容错组。磁盘大小建议为1GB以上（虽然OCR Device = 300M左右）。</p>
<h2><strong>2.2 存储外部冗余架构设计</strong></h2>
<p>首先一定要用多路径软件对Lun进行路径管理，并且保障链路切换策略为负载均衡模式。对于链路的数目来讲最佳为8条链路。而且需要保证这8条链路在光纤口、光纤卡、接入交换机、核心交换机、存储控制器5个层面上的冗余。例如2张双口光纤卡，每个光纤卡通过各自接入交换机连接到不同的两个核心交换机上，核心交换机又分别与两个存储控制器的前端口相连接。在光纤交换机的zone配置里，每一个主机光纤口wwn和存储的一个前端口wwn配置在一个zone里面，端口比例为1:2，总共有8个zone。用示意图的方式表示如下：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528920855.png&amp;size=m" alt=""></p>
<h2><strong>2.3 NFS架构的存储配置参数</strong></h2>
<p>Orace RAC的ASM磁盘可以是网络存储架构实现的Lun，当然我们也可以利用文件系统或者裸盘作为数据库的存储资源。但是在挂载NFS卷的时候，有若干参数是值得我们注意的。</p>
<p>1）Hard/soft：当应用进程发送一个请求，Hard情况下，客户端遇到错误不会立即通知应用，而是在后台进行重试直到正常，这会导致应用进程的阻塞；Soft情况下，客户端会立刻通知应用导致应用挂起。从这个意义上来讲，Soft对应用的响应速度会比Hard好，但是如果网络不稳，那么Soft有可能导致应用数据被损坏。这也是Oracle建议将这个参数设置为Hard的理由。</p>
<p>2）Rsize/Wsize:客户端从服务器端读写文件的最大数目（byte）/每次请求。如果该参数不做设置的话，那么它是通过客户端和服务器端的协商完成的。一般建议设置为固定的32768。</p>
<p>3）Timeo: 建议为600（60秒）。</p>
<p>4）Intr/nointr: 是否允许接受文件操作的中断信号，一般而言设置为nointr。</p>
<p>5）Noac/ac: ac情况下，客户端会缓存文件属性信息，从而提高客户端的读性能。Noac情况下，客户端不会缓存文件属性信息，任何情况下的读都是NFS文件系统上文件的实时版本信息。Ac情况下，客户端会定期扫描Server端的文件实时信息，其他时候都是读取自己缓存的信息。NFS卷作为数据库的存储磁盘，只需要实时反映文件的真实版本信息即可，不需要客户端再去做缓存，数据库有自己的缓存机制。因此一般情况下Oracle建议将这个参数设置为Noac。</p>
<p>当然这些参数，根据不同的操作系统特点是会有一些差异。表2.3是摘自Oracle官方发布的NFS存储最佳实践参数表当中的一部分，可以提供通用参考。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528920954.png&amp;size=m" alt=""></p>
<p><strong>2.4 ASM磁盘组规划</strong></p>
<p>（1）磁盘组相关</p>
<p>除了OCR磁盘组之外，一般建议建立磁盘组不超过2个，一个是存放数据的数据磁盘组（+DATA），另外一个是存放日志的闪回区磁盘组(+FRA)。假设我们选择磁盘组的冗余策略为Normal，那么建议磁盘数目为偶数个并且至少为4个相同大小相同性能配置，一方面考虑到冗余为2份，另外一方面保障Failure Group里面数目的条带化分布，可以保障磁盘组的读写性能。如果是其他冗余策略，那么按照同样的思路去选择磁盘组的数目。另外Lun的大小不能超过2T（容易引起ORA-15196、ORA-15099问题）。</p>
<p>（2）磁盘分配单元及文件条带</p>
<p>AU是ASM Disk Group磁盘空间分配单元。Strip实际上是文件层面的条带，准确说法应该是文件的扩展块儿。对于文件的扩展块儿来讲就是文件切割的单元。它有两种模式（coarse \&amp; fine）。对于coarse模式来讲，扩展块儿大小等于AU大小，对应的参数固定不变（<em>asm_stripesize=AU，</em>asm_stripewidth=1）。对于fine模式来讲，扩展块儿大小是可以进行调整，根据我们的业务需求进行适当调整。例如设置为256K，那么原来1M的文件写在一个磁盘中的AU中，那么现在可以并行写入到赐个磁盘的4个AU当中。充分发挥了小IO的并行读写性能。但是对于某些大IO的数据库业务，那么AU可以适当调整到4M，同时启用操作系统的大页读写参数。文件扩展块儿可以保持corse模式。对于一般的OLTP业务来讲，数据文件、归档文件一般设置为corse；而redo日志、控制文件、flashback日志设置为fine。对于11g之后的oracle，这些参数基本不需要我们去主动调整，除非确实有性能问题与之相关。</p>
<p><strong>2.5 ASM内存管理参数</strong></p>
<p>（1）内存参数相关</p>
<p> db_cache_size: 缓冲区，存放metadata块儿的buffer cache，建议值为64M。</p>
<p> shared_pool: 管理ASM实例所需要的内存池，建议值为128M。</p>
<p> Large_pool: 用来存储 extent maps，建议值为64M。</p>
<p>（2）其他参数相关</p>
<p>在11g当中，如果多个数据库共享ASM实例的话，那么建议按照以下规则计算process的数目设置。</p>
<p>ASM processes = 25 + （10 + max（可能的并发数据文件变化））* 数据库的数目。当然这个数目需要一个经验的评估，需要根据集群环境数据库的情况以及业务IO的判断来估算。</p>
<h2><strong>2.6 异步IO配置</strong></h2>
<p>一般来讲数据库应用都是要启用异步IO来提高数据库的IO性能。同时需要打开操作系统的异步IO参数和数据库的异步IO参数。以Linux为例，在操作系统层面需要设置参数 aio-max-nr=1048576（11g 中设置为 4194304），表示同时可以拥有的异步IO请求数目。然后在Oracle数据库层面设置以下两个参数：filesystemio_option=setall；disk_asynch_io=true。对于AIX来说，需要设置以下三个参数（aix_maxservers, aix_minservers,aio_maxreqs）对于OLTP业务来讲，IBM官方的建议值为（800,200，16384）。</p>
<p>以上的参数值只是一个通用的参考，但是以上所述的参数具体配置的值还是需要根据自己环境的数据来评估。比如我们需要关注iostat中的io等待情况和aio的一系列指标来判断设置值的科学与否。</p>
<h2><strong>2.7 ASMLib \&amp; Udev</strong></h2>
<p>对于Linux平台而言，Oracle RAC的ASM磁盘管理有三种方式（ASMlib、DM、udev），我们首选的方式是ASMlib，对于 RHEL6（从6.4开始），内核驱动软件包'kmod-oracleasm'已经在 Redhat 平台上启动，并且可以通过RedHat Network (RHN)上的"RHEL Server Supplementary (v. 6 64-bit x86_64)” 渠道进行安装。这个模块的更新将会由 RedHat 提供。</p>
<p>对于ASMlib的方式，它是通过以下命令方式创建ASM磁盘：</p>
<h1>/usr/sbin/oracleasm createdisk disk_name device_partition_name</h1>
<p>通过这种方式创建的ASM磁盘组名称（disk_name）,唯一绑定的是后面的device_partition_name，因此我们必须保障操作系统在日后的Lun变更过程中，这个命名是不能够变更的。假设我们用的是第三方多路径软件管理方式实现，那么需要通过多路径管理软件的方式来讲磁盘的device_partition_name和磁盘的唯一ID关联。例如emcpowerpath可以用emcadm export/import方式来保障Rac节点上的Lun名称一致。</p>
<p>对于udev的方式，同样道理我们需要将磁盘的scsi-id和最终形成的asm磁盘名称进行关联，而不是用磁盘在操作系统显示的设备名来关联。例如：</p>
<p>KERNEL==“sd*“, BUS==“scsi”, PROGRAM==“/sbin/scsi_id -g -u -s %p”, RESULT==“36000c29a972d7d5fe0bf683b21046b34”, NAME=“asmgrid_disk1”, OWNER=“grid”, GROUP=“asmadmin”, MODE=“0660”</p>
<p>其中的PROGRAM字段非常重要，它表示我们是用什么方式来关联磁盘在操作系统和ASM之间的对应关系。如果在日后的运维过程中，随着磁盘的增减变化或者服务器的重启变更等导致了磁盘设备名发生变化，那么就会导致ASM磁盘符号紊乱，最终数据库集群无法启动。当然这个问题在11g之后就不存在了，因为11g之后ASM自动会去根据磁盘的唯一ID匹配ASM识别的磁盘ID，节点的读写是根据磁盘的ASM标示来执行的。但是从管理科学角度还是应该按照最佳实践来做从而保障没有任何风险。</p>
<h2><strong>2.8 AIX平台关注的存储参数</strong></h2>
<p>对于AIX平台而言，存储卷的系统参数必须遵循以下规则。</p>
<p>(1) reserve_lock、reserve_policy</p>
<p>该两个参数其实都是表示操作系统是否持有存储卷的共享锁方式。存储阵列类型为SSA, FAStT, 或者其他 non-MPIO-capable disks，参数设置参照A。存储阵列类型为SS, EMC, HDS, CLARiiON, 或者其他 MPIO-capable disks，参数设置方式参照B。</p>
<p>A. # chdev -l hdiskn -a reserve_lock=no</p>
<p>B. # chdev -l hdiskn -a reserve_policy=no_reserve</p>
<p>(2) 磁盘在加入ASM磁盘组之前，必须清除其盘头PVID信息。否则就会导致ORA-15063、ORA-15040、ORA-15042等磁盘错误。</p>
<p>(3) fc_err_recov。</p>
<p>该参数表示因为AIX平台下光纤断掉场合下，读写错误切换的时间。正常情况下，这个切换会导致数据库IO挂起10分钟。如果是Vote disk，就会导致集群重启。为了避免此类情况发生需要把该参数的值设置为fast_fail，实现快速切换。</p>
<p>(4) max_transfer。</p>
<p>该参数建议设置最少为Oracle最大请求的IO大小，一般超过1M。</p>
<p>(5) queue_depth。</p>
<p>该参数表示Lun的最大IO队列深度，这个参数的设置必须足以支撑数据库并发读写的负载。</p>
<p>(6) max_xfer_size。</p>
<p>该参数表示光纤卡的最大传输大小，这个参数的设置必须与磁盘的吞吐参数保持倍数关系，并且必须大于磁盘设置的参数。</p>
<p>(7) num_cmd_elems。</p>
<p>该参数表示光纤卡接受的最大IO请求数目，这个参数同样与磁盘的queue_depth有着倍数关系，具体值的设定需要看环境当中光纤卡和其所容纳Lun的数目。</p>
<p>
 <br>
</p>
<h1><strong>3.网络规划设计的关键点</strong></h1>
<h2><strong>3.1 硬件及参数</strong></h2>
<p>从Oracle官方的推荐来看，他们首先推荐使用万兆以太网，至少使用千兆以太网，负载如果很高那么私网可以采用infiniband。当然这个完全取决于客户生产环境的具体业务量及负载情况。这个仅仅是个参考，有条件的情况下可以按照推荐进行配置。私网的连接需要使用交换机，Oracle集群安装并不支持私网的直连架构。网卡及交换机的双攻击速率参数保持正确一致。</p>
<h2><strong>3.2 网卡绑定</strong></h2>
<p>各种平台都有自己的网卡绑定工具，而且提供负载均衡和主备模式的绑定。首先为了提高公网和私网的网络高可用，网卡需要绑定。对于Linux平台我们需要在配置文件 “/etc/modprobe.d/dist.conf” 中将数mode来控制网卡绑定的具体策略：</p>
<p> mod=0，即：(balance-rr)Round-robin policy（平衡抡循环策略）。</p>
<p> mod=1，即： (active-backup)Active-backup policy（主-备份策略）。</p>
<p> mod=2，即：(balance-xor)XOR policy（平衡策略）。</p>
<p> mod=3，即：broadcast（广播策略）。</p>
<p> mod=4，即：IEEE 802.3ad Dynamic link aggregation（IEEE802.3ad 动态链接聚合）。</p>
<p> mod=5，即：(balance-tlb)Adaptive transmit load balancing（适配器传输负载均衡）。</p>
<p> mod=6，即：(balance-alb)Adaptive load balancing（适配器适应性负载均衡）。</p>
<p>对于私网网卡绑定方式mode=3\&amp;6会导致ORA-600，公网网卡绑定方式mode=6会导致BUG9081436。对于具体的绑定模式，对于平台版本低而且网络架构非常复杂的场合，还是建议主备模式，因为主备模式更稳定，不容易产生数据包路径不一致的问题。如果是负载均衡模式的场合，如果网络参数设置不是很科学的情况下，很容易出现从一个物理网卡发送报文，但是回报文却回到另外一个物理网卡上，网络链路再加入防火墙的规则之后，非常容易导致丢包问题发生。</p>
<p>而对于AIX平台来讲，将参数mode修改为NIB或者Standard值。Standard是根据目标IP地址来决定用哪个物理网卡来发送报文，是基于IP地址的负载均衡，也不易产生上述的丢包问题。</p>
<h2><strong>3.3 SCAN</strong></h2>
<p>Oracle RAC，从11gr2之后增加了SCAN（Single ClientAccess Name）的特性。</p>
<p>SCAN是一个域名，可以解析至少1个IP，最多解析3个SCAN IP，客户端可以通过这个SCAN 名字来访问数据库，另外SCAN ip必须与public ip和VIP在一个子网。启用SCAN 之后，会在数据库与客户端之间，添加了一层虚拟的服务层，就是SCAN IP和SCAN IP Listener，在客户端仅需要配置SCAN IP的tns信息，通过SCANIP Listener，连接后台集群数据库。这样，不论集群数据库是否有添加或者删除节点的操作，均不会对客户端产生影响，也就不需要修改配置。对于SCAN相关的配置，有以下一些配置注意事项：</p>
<p>（1）主机的默认网关必须与SCAN以及VIP在同一个子网上。</p>
<p>（2）建议通过 DNS，按round-robin方式将 SCAN 名称（11gR2 和更高版本）至少解析为 3 个 IP 地址，无论集群大小如何。</p>
<p>（3）为避免名称解析出现问题，假设我们设置了三个SCAN地址，那么HOSTs文件当中不能出现SAN的记录，因为HOSTs文件当中的记录是静态解析，与DNS动态解析相悖。</p>
<h2><strong>3.4 网络参数</strong></h2>
<p>操作系统平台上关于网络的内核参数非常重要，直接决定私网公网数据传输的稳定性和性能。不过针对不同的操作系统，相关的参数设置也各有差异。</p>
<p>1．Linux</p>
<p>对于Linux平台的内核参数，有两个非常重要（net.core.rmem_default、net.core.rmem_max）。具体功能解释如下：</p>
<p> net.ipv4.conf.eth#.rp_filter：数据包反向过滤技术。</p>
<p> net.ipv4.ip_local_port_range：表示应用程序可使用的IPv4端口范围。</p>
<p> net.core.rmem_default：表示套接字接收缓冲区大小的缺省值。</p>
<p> net.core.rmem_max：表示套接字接收缓冲区大小的最大值。</p>
<p> net.core.wmem_default：表示套接字发送缓冲区大小的缺省值。</p>
<p> net.core.wmem_max：表示套接字发送缓冲区大小的最大值。</p>
<p>为了获得更好的网络性能，我们需要根据具体情况把以上两个参数从其默认值适当调整为原来的2-3倍甚至更高，关闭或者设置反向过滤功能为禁用0或者宽松模式2。</p>
<p>2.AIX</p>
<p>对于AIX平台的内核参数，以下设置是从Oracle官方文档摘出的最佳配置：</p>
<p>tcp_recvspace = 65536；tcp_sendspace = 65536；</p>
<p>udp_sendspace = ((db_block_size *db_multiblock_read_count) + 4096) ；</p>
<p>udp_recvspace = 655360；</p>
<p>rfc1323 = 1；</p>
<p>sb_max = 4194304；</p>
<p>ipqmaxlen = 512；</p>
<p>第1、2个参数表示TCP窗口大小，第3、4个参数表示UDP窗口大小。rfc1323启用由 RFC 1323（TCP 扩展以得到高性能）指定的窗口定标和时间图标。窗口定标允许 TCP 窗口大小（tcp_recvspace 和 tcp_sendspace）大于 64KB（65536）并且通常用于大的 MTU 网络。默认为0(关)，如果试图将 tcp_sendspace 和 tcp_recvspace 设为大于 64 KB则需要先修改此值为1。ipqmaxlen 表示指定接收包的数目，这些包可以列在 IP 协议输入队列中。sb_max指定一个 TCP 和 UDP 套接字允许的最大缓冲区大小。</p>
<h2><strong>3.5 安全配置事项</strong></h2>
<p>1．Linux平台下的防火墙需要关闭，否则会引起公网或者私网的通讯问题。</p>
<h1>chkconfig iptables stop</h1>
<p>2．Linux平台下的selinux安全配置项需要关闭，配置文件为/etc/security/config。</p>
<p>SELINUX=disabled</p>
<p>3．如果是Power System主机的PowerVM虚拟化架构下的AIX平台，如果发现Oracle RAC的两个节点之间有大量丢包现象或者是以下几种事件：</p>
<p> Cache Fusion “block lost”</p>
<p> IPC Send timeout</p>
<p> Instance Eviction</p>
<p> SKGXPSEGRCV: MESSAGE TRUNCATED user data nnnn bytes payload nnnn bytes</p>
<p>那么我们需要检查VIOS分区操作系统的补丁信息，如果没有APAR IZ97457，那么我们需要将这个补丁打上，详细需到IBM官网找到相应的补丁及其详细解释。</p>
<h2><strong>3.6 通用注意事项</strong></h2>
<p>1.系统主机名、域名等配置不允许有下划线。</p>
<p>2.网卡名称在两个节点上保持一致（例：public-&gt;eth1ð1，private-&gt;eth2ð2）。</p>
<p>3.网卡设备名称当中不能包含”.“等特殊字符。</p>
<p>4.私网地址需遵守RFC1918标准，采用其所规定的ABC三类企业内部私网地址。否则会引起BUG4437727发生。A类：10.0.0.0 -10.255.255.255 (10/8比特前缀)； B类：172.16.0.0 -172.31.255.255 (172.16/12比特前缀)； C类：192.168.0.0 -192.168.255.255 (192.168/16比特前缀)。而且私网VLAN需要与上述no-routeable子网之间需要是1:1的映射关系，以免引起BUG9761210。</p>
<p>5.从11gr2起，私网网段配置需要支持组播功能，因为私网需要通过组播模式实现通讯。</p>
<h2><strong>3.7 send (tx) / receive (rx)</strong></h2>
<p>UDP包传输的过程中，接受进程会读取数据包头的校验值。任何校验值损坏都会使这个包被丢弃，并导致重发，这会增加CPU的使用率并且延缓数据包处理。</p>
<p>由于网卡上开启了Checksum offloading 导致了checksum 错误，如果出现这样的问题请检查checksum offloading的功能是否被禁用，测试后考虑关闭网卡上的该项功能。在Linux系统上执行ethtool -K <if> rx off tx off可以关闭该功能。</if></p>
<h2><strong>3.8 MTU</strong></h2>
<p>不匹配的MTU大小设置会导致传输过程中出现 “packet too big” 错误并丢失数据包，导致global cache block丢失和大量的重传（retransmission）申请。而且私网中不一致的MTU值会导致节点无法加入集群的问题。</p>
<p>对于以太网（Ethernet），大多数UNIX平台的默认值是1500字节。私网链路中所有设备都应该定义相同的MTU。请确认并监控私网链路中的所有的设备。为ping ,tracepath,traceroute命令指定大的，非默认尺寸，ICMP probe 包来检查MTU设置是否存在不一致。使用ifconfig或者厂商推荐的工具为服务器网卡（NIC）的MTU设置合适的值。</p>
<p>Jumbo Frames 并不是IEEE 标准配置。单个Jumb Frame的大小是9000 bytes左右。Frame 的大小取决于网络设备供应商，在不同的通信设备上的大小可能是不一致的。如果默认的MTU 尺寸不是9000bytes，请保证通信路径中的所有设备（例如：交换机/网络设备/网卡）都能够支持一个统一的MTU值，在操作的过程中必须把Frame Size（MTU Size）配置成这个值。不合适的MTU设置，例如：交换机上配置MTU=1500,但是服务器上的私网网卡配置成MTU=9000，这样会造成丢包，包的碎片和重组的错误，这些都会导致严重的性能问题和节点异常宕机。大部分的平台上我们都可以通过netstat –s命令的'IP stats'输出发现包的碎片和重组的错误。大部分的平台上我们可以通过ifconfig –a命令找到frame size的设置。关于交换机上的配置查询，需要查看交换机提供商的文档来确定。</p>
<p>
 <br>
</p>
<p><strong>4.操作系统层的关键优化项</strong></p>
<p><strong>4.1 兼容性检查</strong></p>
<p>在Oracle建设实施之前，根据操作系统平台对即将采用的相关数据库技术进行兼容性检查。下面的link是官方的Matrix，分别针对Linux平台和Unix平台：</p>
<p>http://www.oracle.com/technetwork/database/clustering/tech-generic-linux-new-086754.html</p>
<p>http://www.oracle.com/technetwork/database/clustering/tech-generic-unix-new-166583.html</p>
<p><strong>4.2 平台版本及补丁</strong></p>
<p>当我们选择了具体的操作系统平台以及具体的数据库版本之后，接下需要做的事情就是要根据官方提供的文档来检查我们的系统补丁以及相关软件包是否齐全准确：</p>
<p>https://support.oracle.com/epmos/faces/DocumentDisplay?<em>afrLoop=184026698780346\&amp;parent=DOCUMENT\&amp;sourceId=1526555.1\&amp;id=169706.1\&amp;</em>afrWindowMode=0\&amp;_adf.ctrl-state=bjsizj5t_240</p>
<p>https://support.oracle.com/epmos/faces/DocumentDisplay?<em>afrLoop=184190072886338\&amp;parent=DOCUMENT\&amp;sourceId=1526555.1\&amp;id=1393041.1\&amp;</em>afrWindowMode=0\&amp;_adf.ctrl-state=bjsizj5t_338</p>
<p>https://support.oracle.com/epmos/faces/DocumentDisplay?<em>afrLoop=184287678146090\&amp;parent=DOCUMENT\&amp;sourceId=1526555.1\&amp;id=282036.1\&amp;</em>afrWindowMode=0\&amp;_adf.ctrl-state=bjsizj5t_436</p>
<p>Oracle官方对所有数据库集群及RDBMS等建议的最新补丁列表：</p>
<p>https://support.oracle.com/epmos/faces/DocumentDisplay?<em>afrLoop=202212137729856\&amp;parent=DOCUMENT\&amp;sourceId=1526083.1\&amp;id=756671.1\&amp;</em>afrWindowMode=0\&amp;_adf.ctrl-state=yi6z8ecqc_839#r11204</p>
<p>另外在AIX平台上有一个不容忽视的地方，那就是必须保障Hacmp没有安装或者没有任何残留痕迹。</p>
<p><strong>4.3 时间同步设置项</strong></p>
<p>NTP是11gr2之前必须的选项，用来同步节点之间的时间。而到了11gr2之后，不仅仅可以采用NTP也可以采用CTSSD（Cluster Time Synchronization Daemon）代替NTP。如果NTP启用的话，Oracle会采用NTP，CTSSD自动处于观察模式。但是在NTP的启用模式上，我们需要采用渐进式模式（需要利用启动参数-x）。可以参照下面的配置：</p>
<ol>
 <li>/etc/sysconfig/ntpd</li>
</ol>
<h1>Drop root to id 'ntp:ntp' by default.</h1>
<p>OPTIONS=“-x -u ntp:ntp -p /var/run/ntpd.pid”</p>
<h1>Set to 'yes' to sync hw clock after successful ntpdate</h1>
<p>SYNC_HWCLOCK=no</p>
<h1>Additional options for ntpdate</h1>
<p>NTPDATE_OPTIONS=“”</p>
<p><strong>4.4 ASLR (Address Space Layout Randomization)</strong></p>
<p>ASLR是REL5 Linux以上版本中默认开启的一个特性，是参与保护缓冲区溢出问题的一个计算机安全技术。是为了防止攻击者在内存中能够可靠地对跳转到特定利用函数。ASLR包括随机排列程序的关键数据区域的位置，包括可执行的部分、堆、栈及共享库的位置。ASLR通过制造更多让攻击者预测目标地址的困难以阻碍一些类型的安装攻击。而在ORACLE的进程管理当中，多个进程共享相同地址的共享内存，开启ASLR特性之后，Oracle就无法保障共享内存可用。从而导致ORA-00445错误发生。要在Linux上关闭这个特性需要添加或修改以下两个参数到/etc/sysctl.conf文件：</p>
<p>kernel.randomize_va_space=0</p>
<p>kernel.exec-shield=0</p>
<p><strong>4.5 HugePage</strong></p>
<p>大多数操作系统采用了分段或分页的方式进行管理。分段是粗粒度的管理方式，而分页则是细粒度管理方式，分页方式可以避免内存空间的浪费。相应地，也就存在内存的物理地址与虚拟地址的概念。通过前面这两种方式，CPU必须把虚拟地址转换程物理内存地址才能真正访问内存。为了提高这个转换效率，CPU会缓存最近的虚拟内存地址和物理内存地址的映射关系，并保存在一个由CPU维护的映射表中。为了尽量提高内存的访问速度，需要在映射表中保存尽量多的映射关系。</p>
<p>Linux的内存管理采取的是分页存取机制，为了保证物理内存能得到充分的利用，内核会按照LRU算法在适当的时候将物理内存中不经常使用的内存页自动交换到虚拟内存中，而将经常使用的信息保留到物理内存。</p>
<p>通常情况下，Linux默认情况下每页是4K，这就意味着如果物理内存很大，则映射表的条目将会非常多，会影响CPU的检索效率。因为内存大小是固定的，为了减少映射表的条目，可采取的办法只有增加页的尺寸。因此Hugepage便因此而来。也就是打破传统的小页面的内存管理方式，使用大页面2m,4m等。如此一来映射条目则明显减少。如果系统有大量的物理内存（大于8G），则物理32位的操作系统还是64位的，都应该使用Hugepage。</p>
<p>那么如何控制Oracle数据库对HugePage的利用呢？主要分为以下几个步骤：</p>
<p>1．需要在/etc/security/limits.conf 中设置memlock值(单位KB)，该值小于内存大小。</p>
<p>2． 如果你使用11G及以后的版本，AMM已经默认开启，但是AMM与Hugepages是不兼容的，必须先关闭AMM。</p>
<p>3．编辑/etc/sysctl.conf 设置 vm.nr_hugepages参数的具体值。</p>
<p>4．停止实例并重启OS系统，并通过以下命令检查设置是否生效：</p>
<h1>grep HugePages /proc/meminfo</h1>
<p>HugePages_Total: 1496</p>
<p>HugePages_Free: 485</p>
<p>HugePages_Rsvd: 446</p>
<p>HugePages_Surp: 0</p>
<p>5．通过以下参数控制数据库对HugePage的使用方式(11gr2之后)：</p>
<p>use_large_pages = {true/only/false/auto}</p>
<p>默认值是true，如果系统设置Hugepages的话，SGA会优先使用hugepages，有多少用多少。如果设置为false， SGA就不会使用hugepages。如果设置为only 如果hugepages大小不够的话，数据库实例是无法启动的。设置为auto，这个选项会触发oradism进程重新配置linux内核，以增加hugepages的数量。一般设置为true。</p>
<p>SQL&gt; alter system set use_large_pages=true scope=spfile sid='*';</p>
<p><strong>4.6 Transparent HugePage</strong></p>
<p>透明大页管理和前面所述的标准大页管理都是操作系统为了减少页表转换消耗的资源而发布的新特性，虽然Oracle建议利用大页机制来提高数据库的性能，但是Oracle却同时建议关闭透明大页管理。这二者的区别在于大页的分配机制，标准大页管理是预分配的方式，而透明大页管理则是动态分配的方式。</p>
<p>对于数据库来讲这种动态的分配方式在系统负载很高的情况下非常有可能导致数据库出现严重的性能问题。这在Oracle官方文档1557478.1当中有详细的记载。</p>
<p>那么如何来关闭系统的透明大页管理呢？只要修改如下参数即可：</p>
<h1>echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled</h1>
<p><strong>4.7 vm.min_free_kbytes</strong></p>
<p>该参数是Linux内核当中用来控制保留最小空闲内存的数量，Oracle建议调大该值为512M。这样的设置有利于相对加快内存的回收速度，从而降低内存吃紧的压力。</p>
<p><strong>4.8 AIX虚拟内存参数</strong></p>
<p>这一项主要是针对AIX的内存管理。AIX内存管理和Linux的内存管理机制不一样，它采用计算内存和非计算内存方式来管理内存。IBM对Oracle的建议值为以下方案：</p>
<p>minperm%=3</p>
<p>maxperm%=90</p>
<p>maxclient%=90</p>
<p>lru_file_repage=0</p>
<p>lru_poll_interval=10</p>
<p>strict_maxperm=0</p>
<p>strict_maxclient=1</p>
<p>page_steal_method=1</p>
<p>minperm和maxperm控制非计算内存中的文件页的下限和上限；maxclient控制非计算内存中的客户页面；lru_file_repage表示分页替换守护进程将根据其内部重新分页表来确定选择何种类型的分页进行操作。strict_maxperm\&amp;strict_maxclient表示无论是否有空闲内存，都会严格限制文件页以及客户页的最大占有比率不得超越限制。page_steal_method表示换页时的策略，0为全部页面，1为非计算持久页面。以上参数放方案表示费计算持久页面的上限为90%，下限为3%；客户机页面上限为90%；采用非严格持久页面上限控制策略；严格客户机页面上限控制策略。lru_file_repage \&amp; page_steal_method配合使用表示LRUD在寻找空闲页时，只寻找费计算内存当中的持久内存页面。</p>
<p>vmm_klock_mode=2</p>
<p>这个参数是是否对内核页进行加锁的控制。0则表示不进行加锁，那么内核页有可能被错误换出从而导致Page Fault发生；1则表示部分内核页面加锁；2则表示对所有内核页面进行加锁。在Oracle RAC环境下或者EMC的存储作为系统的Swap Device的时候，IBM强烈建议将该参数设置为2。</p>
<p><strong>4.9 PowerVM环境下的参数调整</strong></p>
<p>PowerVM环境下，由于其利用Hypervisor实现了很多虚拟化的功能，这些功能大多从灵活性及扩展型来考虑，但是如果我们运行的是Oracle Rac的话，那么还是有很多关键点需要注意。</p>
<ol>
 <li>cpu folding</li>
</ol>
<p>虚拟处理器折叠功能，当系统负载比较低的时候，AIX系统自动休眠一些虚拟处理器，以减少Hypervisor的开销，提升PowerVM平台整体性能。但是在某些情况下，当数据库的负载变化非常快的时候，CPU折叠或者打开的速度反而会影响数据库甚至系统的性能，严重导致系统挂起。下面是IBM针对该BUG的一个补丁，主要针对AIX5.3 \&amp; 6.1。</p>
<p>https://www-01.ibm.com/support/docview.wss?uid=isg1fixinfo105201</p>
<p><strong>4.10 Linux内核参数</strong></p>
<p>以下是Oracle官方针对Linux平台内核参数设置的一个通用方案：</p>
<p>fs.aio-max-nr = 1048576</p>
<p>fs.file-max = 6815744</p>
<p>kernel.shmall = 2097152</p>
<p>kernel.shmmax = 4294967295</p>
<p>kernel.shmmni = 4096</p>
<p>kernel.sem = 250 32000 100 128</p>
<p>net.ipv4.ip_local_port_range = 9000 65500</p>
<p>net.core.rmem_default = 4194304</p>
<p>net.core.rmem_max = 4194304</p>
<p>net.core.wmem_default = 4194304</p>
<p>net.core.wmem_max = 4194304</p>
<p><strong>kernel.shmmax：</strong>是核心参数中最重要的参数之一，用于定义单个共享内存段的最大值。设置应该足够大，能在一个共享内存段下容纳下整个的SGA ,设置的过低可能会导致需要创建多个共享内存段，这样可能导致系统性能的下降。</p>
<p>至于导致系统下降的主要原因为在实例启动以及ServerProcess创建的时候，多个小的共享内存段可能会导致当时轻微的系统性能的降低(在启动的时候需要去创建多个虚拟地址段，在进程创建的时候要让进程对多个段进行"识别”，会有一些影响)，但是其他时候都不会有影响。</p>
<p>64位linux系统：可取的最大值为物理内存值-1byte，建议值为多于物理内存的一半，一般取值大于SGA_MAX_SIZE即可，可以取物理内存-1byte。例如，如果为12G物理内存，可取210241024*1024-1=12884901887，SGA肯定会包含在单个共享内存段中。</p>
<p><strong>kernel.shmall：</strong>该参数控制可以使用的共享内存的总页数。Linux共享内存页大小为4KB,共享内存段的大小都是共享内存页大小的整数倍。一个共享内存段的最大大小是16G，那么需要共享内存页数是16GB/4KB=16777216KB /4KB=4194304（页），也就是64Bit系统下16GB物理内存，设置kernel.shmall = 4194304才符合要求(几乎是原来设置2097152的两倍)。这时可以将shmmax参数调整到16G了，同时可以修改SGA_MAX_SIZE和SGA_TARGET为12G（您想设置的SGA最大大小，当然也可以是2G\~14G等，还要协调PGA参数及OS等其他内存使用，不能设置太满，比如16G）。</p>
<p><strong>kernel.shmmni：</strong>该参数是共享内存段的最大数量。shmmni缺省值4096，一般肯定是够用了。</p>
<p><strong>fs.file-max：</strong>该参数决定了系统中所允许的文件句柄最大数目，文件句柄设置代表linux系统中可以打开的文件的数量。</p>
<p><strong>fs.aio-max-nr：</strong>此参数限制并发未完成的请求，应该设置避免I/O子系统故障。</p>
<p><strong>kernel.sem：</strong>以kernel.sem = 250 32000 100 128为例：250是参数semmsl的值，表示一个信号量集合中能够包含的信号量最大数目。32000是参数semmns的值，表示系统内可允许的信号量最大数目。100是参数semopm的值，表示单个semopm()调用在一个信号量集合上可以执行的操作数量。128是参数semmni的值，表示系统信号量集合总数。</p>
<p><strong>net.ipv4.ip_local_port_range：</strong>表示应用程序可使用的IPv4端口范围。</p>
<p><strong>net.core.rmem_default：</strong>表示套接字接收缓冲区大小的缺省值。</p>
<p><strong>net.core.rmem_max：</strong>表示套接字接收缓冲区大小的最大值。</p>
<p><strong>net.core.wmem_default：</strong>表示套接字发送缓冲区大小的缺省值。</p>
<p><strong>net.core.wmem_max：</strong>表示套接字发送缓冲区大小的最大值。</p>
<p>
 <br>
</p>
<p><strong>5.配置集群层的关键点</strong></p>
<p><strong>5.1 diagwait</strong></p>
<p>在集群进行驱逐节点时，节点发生重新启动的场合下，操作系统需要转储CrashDump，这个参数就是集群需要等待操作系统转储的时间参数。对于版本 10gR2 和 11gR1，所有平台上的最佳实践都是将 CSS diagwait 参数设置为小于等于13。对于11gR2已经不要求对该参数进行设置。</p>
<p><strong>5.2 ORA_CRS_HOME</strong></p>
<p>对于这个变量，Oracle官方给出的建议是：不要设置 ORA_CRS_HOME 环境变量（在所有平台上）。设置此变量将使各个 Oracle 组件出现问题，而且 CRS 程序完全不需要此变量，因为它们都有包装脚本。</p>
<p><strong>5.3 关于组播和多播</strong></p>
<p>Oracle11gR2私网采用的是组播方式通讯，组播地址段为230.0.1.0 或 224.0.0.251网段，为了保障集群私网通讯的正常及稳定，一方面这个网段的网络配置需要支持组播，另外一方面不要让主机的任何其他网络配置和这两个网段冲突。曾经遇到过浪潮的X86服务器的带外管理的某个地址也正好利用了这个网段，结果导致了Oracle Rac节点的异常重启。这种问题一般会显示为私网通讯报错，但是正常状况下只要带外管理收集日志工具不启动的话，那么是不会发现这个问题的，属于偶发性问题。所以我们需要特别小心此类问题，不要等到发生时候再去花费大量实践调查。</p>
<p>
 <br>
</p>
<p><strong>6.安装集群时的关键点</strong></p>
<p><strong>6.1 cluvfy</strong></p>
<p>如果安装过10g以后的RAC环境，应该对这个工具并不陌生。在安装Cluster和Database之前通常会执行runcluvfy.sh脚本来检查当前系统是否满足安装条件。runcluvfy.sh将cluvfy工具的功能在shell中实现，使得用户在数据库和CLUSTER安装之前就可以利用这个工具的功能。这个工具的主要作用就是验证系统是否满足安装的条件，尤其是检查网络和域名解析，如果网络和域名解析在安装之前没有正常的配置，通常直接导致安装的失败。主要用法如下所列两个功能：</p>
<h1>runcluvfy.sh stage -list</h1>
<h1>runcluvfy.sh comp -list</h1>
<p><strong>6.2 升级顺序问题</strong></p>
<p>安装完集群软件也好，RDBMS也好，打补丁是必然要做的事情。但是究竟是等安装完所有的组件之后再统一打补丁呢还是说将每一个组件都按照安装\&amp;补丁升级的顺序依次做完呢？Oracle给的建议是：在执行 11gR2 之前的安装时，建议在执行任何 RDBMS 或 ASM 主目录安装前应用补丁程序将 Clusterware 主目录升级到所需的级别。</p>
<p><strong>6.3 root.sh \&amp; rootupgrade.sh</strong></p>
<p>安装或者升级期间，最后的步骤就是要执行以上的脚本。当然按照安装文档的要求必须是用超级用户root来执行。但是可能有些人认为只要利用root的权限去执行就可以了，很想当然的用了su root -c或者是sudo等去执行，结果导致执行失败，日志报错crsd.bin crashes。因为脚本的执行不仅仅是要用超级用户root的权限，还要利用root用户的环境配置等。</p>
<p><strong>6.4 /etc/init.cssd</strong></p>
<p>11.2 之前再AIX系统上，OPROCD 默认不在 AIX 全局运行队列运行 ，这可能会导致 OPROCD 错误地重启节点。(Bug 13623902)。此问题的更正操作是修改 /etc/init.cssd 文件，加入如下参数：</p>
<p>RT_GRQ=ON</p>
<p>export RT_GRQ</p>
<p>但是AIX6.1 TL4以上的版本就自带了对该问题的修正程序。所以当我们采用低版本的AIX操作系统时，需要特别注意这个配置文件。</p>
<p>
 <br>
</p>
<p><strong>7.数据库层的关键优化项</strong></p>
<p><strong>7.1 pre_page_sga \&amp; lock_sga</strong></p>
<p>这两个参数都是对数据库SGA的保护参数。lock_sga是控制SGA不被换出到交换空间上，会保障数据库内存页一致留在物理内存上，从而提高性能。而pre_page_sga是保障数据库实例启动时就把所有SGA读到物理内存上，虽然启动会慢，但是后续性能会好。</p>
<p>但是pre_page_sga同时还有一个作用。pre_page_sga 为true时, 每个进程创建的时候都会去touch一遍sga里的page, 当sga越大的时候,这个touch所消耗的时间就越长，特别是在断开式连接,短连接的Application上, 将会消耗很多资源。当客户端连接感觉到慢的时候，这个参数就一定要设置成false了。Oracle的建议也是false。</p>
<p>SQL&gt; alter system set pre_page_sga=false scope=spfile sid='';</p>
<p><em>SQL&gt; alter system set lock_sag=true scope=spfile sid='';</em></p>
<p><strong>7.2 关于重做日志</strong></p>
<p>首先、接触过数据库的人相信对这个概念都不陌生。数据库在做SQL更新的时候，首先要将事务执行过程记入重做日志当中，然后才会把日志刷入磁盘，将数据更新持久化。一条数据提交之后成功的标准时日志落到磁盘，而不是真正的数据落盘。因此日志的配置（大小、数量）直接决定着数据库读写的性能，如果日志大小非常大，那么会造成归档切换时间非常长，一旦这时候发生了不可恢复的DB灾难，那么通过备份恢复的数据流失量或者说RPO就会较大。日志大小非常小的话，势必会造成日志频繁切换，AWR里面有大量的日志切换事件，这样对数据库的性能会有较大影响。因此根据性能测试的AWR报告中日志切换的等待事件、和切换频度来决定其数据量和大小是否需要调整。一般的OLTP建议（10组、500M）。</p>
<p>接着、我们还需要考虑与其相关的参数设置。</p>
<ol>
 <li>_use_adaptive_log_file_sync</li>
</ol>
<p>它直接决定了日志落盘的方式，对于日志缓冲区的数据落盘的方式，11g增加一种新的方式就是polling的方式，传统方式是post/wait方式。oracle底层自动判断何时用何种方法来完成lgwr进程的写任务。对于post/wait方式来讲，客户端做了commit之后，需要等待事件完成。oracle一旦完成会通知用户进程，用户进程立刻感知。但是这一通知post，会耗费大量CPU资源。polling是oracle前台进程启动检查任务，自动检查后台lgwr写入情况，耗费CPU资源比较少，但是用户进程并不一定能立刻感知。</p>
<p>所以两种方法各有千秋。但是关键是后台实现两种方法切换的时候要耗费系统性能，尤其在繁忙的时候频繁切换的话反而会导致数据库性能下降。awr出现大量log file sync，Bug 13707904。</p>
<p>SQL&gt; alter system set “_use_adaptive_log_file_sync”=false scope=spfile sid='*';</p>
<ol>
 <li>archive_lag_target 它决定了我们是否开启日志强制切换功能，为了减少故障时数据损失，可以设置archive_lag_target参数，强制进行日志切换。这个参数的缺省值是0，即为不启用该参数。建议设置值为1800。 SQL&gt; alter system set archive_lag_target=1800 scope=spfile sid='*';</li>
</ol>
<p><strong>7.3 AMM</strong></p>
<p>首先、ORACLE通用的两种内存管理方式AMM\&amp;ASMM，从Oracle 11g开始，ORACLE默认使用AMM(自动内存管理)，即让数据库完全管理SGA、PGA的大小，而对于管理员只需要设置一个总的大小（memory_target），数据库会动态的调整SGA、PGA的大小以及其中包含的各个组件大小，如Database buffer cache、Shared pool等。这个特性设计的初衷是好的，它希望避免不正确的SGA和PGA设置导致的内存使用不平衡的性能问题。</p>
<p>但是在实际应用过程中，这个特性是不是一定非常出色呢？AMM中在数据库启动是会有一个固定比例来分配SGA/PGA 大小：</p>
<p>sga_target =memory_target 60%</p>
<p><em>pga_aggregate_target=memory_target *40%。</em></p>
<p>但是在并发较高，数据库非常繁忙的场合下，自动内存调整的速度很可能赶不上大量会话对内存的请求的速度。另外当PGA随着会话不断增加而需求量猛增的情况下，它会首先抢占SGA，导致数据库性能故障。在高并发的数据库场景中并不建议使用AMM。采用10g更为成熟的自动共享内存管理(ASMM)和自动PGA管理。手动调整内存参数，具体可以参照以下：</p>
<ol>
 <li>关闭内存自动管理</li>
</ol>
<p>SQL&gt; alter system set memory_target=0 scope=spfile sid='';</p>
<p><em>SQL&gt; alter system set memory_max_target=0 scope=spfile sid='*';</em></p>
<ol>
 <li>设置SGA为固定值，可以根据性能测试中的AWR报告中的建议</li>
</ol>
<p>SQL&gt; alter system set sga_max_size=XG scope=spfile sid='';</p>
<p><em>SQL&gt; alter system set sga_target=XG scope=spfile sid='';</em></p>
<ol>
 <li>设置PGA等参数</li>
</ol>
<p>SQL&gt; alter system set pga_aggregate_target=XG scope=spfile sid='';</p>
<p><em>SQL&gt; alter system set large_pool_size=256M scope=spfile sid='';</em></p>
<p>pga_aggregate_target=XG</p>
<p>large_pool_size=256M</p>
<p>另外很重要的一个参数，“_shared_pool_reserved_pct”，如果这个参数设置小了，很可能导致ORA04031，所以需要一个合理的设置。</p>
<p>SQL&gt; alter system set”_shared_pool_reserved_pct”=10 scope=spfile sid='*';</p>
<p><strong>7.4 SQL解析</strong></p>
<ol>
 <li>绑定变量窥测</li>
</ol>
<p>在Oracle中每条SQL语在执行之前都需要经过解析，这里面又分为软解析和硬解析。在Oracle中存在两种类型的SQL语句，一类为 DDL语句(数据定义语言)，他们是从来不会共享使用的，也就是每次执行都需要进行硬解析。还有一类就是DML语句(数据操纵语言)，他们会根据情况选择要么进行硬解析，要么进行软解析。一般我们希望我们的AWR报告中硬解析偏少，而软解析偏多。因为硬解析的代价会非常高。为了减少带绑定变量的sql的解析时间，oracle 9i引入的绑定变量窥测的功能。也就是在同一个SQL的变量被赋于不同值时采用同一个游标，这样虽然节省了sql的解析时间。大家有没有通过功能的打开或者关闭实际观察过AWR中的软硬解析数目的实际状况呢？其实对于绑定变量窥测这个特性以及后来的自适应游标等特性，都是oracle为了找到最优执行计划而启用的一些新特性，但是在实际应用过程中，对于不同量级不同特性的业务场景也曾经因此出现了很多bug( Bug 20370037，Bug 13456573，Bug 20082921）等。</p>
<p>根据自己的业务系统特点，做大量的性能测试和业务测试，根据参数的关闭打开对比awr报告当中显示出的软硬解析比率以及执行计划数据决定是否打开或者关系相应功能特性。如下参数：</p>
<p>“_optim_peek_user_binds”</p>
<p>“_optimizer_adaptive_cursor_sharing”</p>
<p>“_optimizer_extended_cursor_sharing”</p>
<p>“_optimizer_extended_cursor_sharing_rel”</p>
<p>“_optimizer_use_feedback”</p>
<ol>
 <li>open_cursors \&amp; session_cached_cursors</li>
</ol>
<p>与之相关的几个参数：open_cursors、session_cached_cursors 这两个参数决定着应用会话可以控制打开以及缓存的游标数量，如果数量不足，就会引起SQL解析的性能问题。这两个参数要根据v$resource_limit视图中的值的情况进行调整，避免资源设置不合理导致的性能问题。</p>
<p>SQL&gt; alter system set open_cusors=N scope=spfile sid='';</p>
<p><em>SQL&gt; alter system set session_cached_cursors=M scope=spfile sid='';</em></p>
<ol>
 <li>_b_tree_bitmap_plans</li>
</ol>
<p>与执行解析执行计划相关的几个参数，_b_tree_bitmap_plans、有时将B-Tree索引进行BITMAP转换来进行SQL执行，往往会生成极其恶劣的执行计划，导致CPU100%。Select Fails With ORA-600 [20022] (文档 ID 1202646.1) 建议可以关掉。</p>
<p>SQL&gt; alter system set “_b_tree_bitmap_plans”=false scope=spfile sid='*';</p>
<p><strong>7.5 process \&amp; sessions</strong></p>
<p>process 限制了能够连接到SGA的操作系统进程数，这个总数必须足够大，从而能够适用于后台进程与所有的专用服务器进程，此外共享服务器进程与调度进程的数目也被计算在内。session 是通信双方从开始通信到通信结束期间的一个上下文(context)。这个上下文是一段位于服务器端的内存：记录了本次连接的客户端机器、通过哪个应用程序、哪个用户在登录等信息。</p>
<p>Oracle的连接数sessions与其参数文件中的进程数process相关，它们的关系如下： sessions=(1.1process+5)</p>
<p>这两个参数的设置是需要根据应用的具体并发需求来决定具体的设置方案。</p>
<p>SQL&gt; alter system set process=N scope=spfile sid='';</p>
<p><em>SQL&gt; alter system set sessions=1.1N+5 scope=spfile sid='';</em></p>
<p><strong>7.6 DRM</strong></p>
<p>数据库节点之间的竞争有很多，包括锁（各种粒度锁）的竞争以及数据的传输等。完全避免竞争那就失去了RAC的意义了，RAC本身就是希望能在两个节点并行执行任务。如果特别极致的并行一定引起严重的性能问题，如果完全禁止，既无法做到又失去了集群本来的意义。所以我们只能在一定程度上去平衡：</p>
<p>首先、关于DRM，oracle的DRM特性从理论上来看，它是为了避免节点间的数据量传输，避免节点间的锁等待事件频繁发生。DRM的极致是做到请求节点和Master节点统一化。但是实践中，这个特性引起了很多的BUG、反而导致了节点间的竞争出现了性能故障。Bug 6018125 - Instance crash during dynamic remastering or instance reconfiguration (Doc ID 6018125.8)。所以建议关闭。</p>
<p>SQL&gt; alter system set “_gc_policy_time”=0 scope=spfile sid='*';</p>
<p>SQL&gt; alter system set “_gc_undo_affinity”=false scope=spfile sid='*';</p>
<p><strong>7.7 parallel_force_local</strong></p>
<p>关于参数"parallel_force_local”，ORACLE RAC为了实现多节点并行处理是花费了很大代价的，假设一个集群当中有三个节点，对于某一个数据块儿读写，有一个Master、有一个请求者、有一个拥有者，请求者向Master请求数据块儿的最新版本，Master把请求转发给拥有者，拥有者按照请求信息把数据块儿传送给申请者，然后加锁进行读写。这一过程是需要有大量的数据传输和竞争存在的，一旦这个事情成为多数，那么势必造成节点间的通讯负载过大，造成大量的锁等待时间，严重影响数据库整体性能。尤其是在做跨数据中心高可用的场合下。因此我们只要做到业务级别的并发处理，而不要追求一个SQL级别的绝对并发。物极必反的道理就在于此。因此把参数打开，使得进程级别并发实现本地化处理，不要跨节点处理。在官方文档 ID 1536272.1当中，必须优化的参数就包括这个。</p>
<p>SQL&gt; alter system set parallel_force_local=true scope=spfile sid='*';</p>
<p><strong>7.8 关于自动任务</strong></p>
<p>Oracle 11g 数据库有三个预定义自动维护任务：</p>
<ol>
 <li>
  <p>Automatic Optimizer Statistics Collection（自动优化器统计信息收集）： 收集数据库中所有无统计信息或仅有过时统计信息的 Schema 对象的 Optimizer（优化器）统计信息。QL query optimizer（SQL 查询优化器）使用此任务收集的统计信息提高 SQL 执行的性能。</p>
 </li>
 <li>
  <p>Automatic Segment Advisor（自动段指导）： 识别有可用回收空间的段，并提出如何消除这些段中的碎片的建议。也可以手动运行 Segment Advisor 获取更多最新建议。</p>
 </li>
 <li>
  <p>Automatic SQL Tuning Advisor（自动 SQL 优化指导）：检查高负载 SQL 语句的性能，并提出如何优化这些语句的建议。您可以配置此指导，自动应用建议的SQL profile。</p>
 </li>
</ol>
<p>关于统计信息收集，数据库是有其自己的默认启动时间，11g是在22:00-2:00之间，假设这个时间跟我们的跑批时间有冲突的话，我们可以修改器具体执行时间。但是这个任务必须保留。关于其他的两个优化指导，其实要看我们实际工作中用到的几率是否很高，是否有价值留着给我们提供一些优化的理论指导。如果感觉意义不大，可以不用。</p>
<p><strong>7.9 安全方面的配置优化</strong></p>
<p>首先、是数据库要不要保留审计？如何保留？</p>
<p>假设不打开审计，那么将来出来安全问题，我们无法寻找线索；假设打开，那么很可能因为使得审计日志占用大量的存储空间，甚至影响数据库IO性能。一般情况下还是需要对一些基本登录行为的审计，但是我们可以把日志位置修改制定到操作系统层面减少数据库层因此的性能压力，而且应该定期转储，减少碎文件太多而把文件系统i节点用光的极端情况。可以通过对参数"audit_trail"以及adump参数的调整来实现此项优化。</p>
<p>接着、alert日志和trace文件的控制参数。</p>
<p>max_dump_file_size，它决定了这些文件的大小限制，默认情况下是unlimited，如果生成了很大的文件，就会达到OS对文件上限的要求，导致写入失败。</p>
<p>SQL&gt; alter system set max_dump_file_size='100m' scope=spfile sid='*';</p>
<p><strong>7.10 parallel_min_servers</strong></p>
<p>这个参数决定了实例可以并行执行的进程的最大数目。设置太小查询进程没有并行执行能力。如果设置太大，那么可能会导致内存资源紧张，从而影响系统整体性能。确保监控活动并行服务器进程的数量并计算要应用于 parallel_min_servers 的平均值。可通过以下操作完成：</p>
<p>SQL&gt; select * from v$pq_syssstat;</p>
<p>查看列值 “Servers Highwater”；</p>
<p>根据硬件情况优化 parallel_max_servers的值。最开始可以使用<em>(2 * ( 2 个线程 ) (CPU_COUNT)) = 4 x CPU</em>计算，然后使用测试数据对更高的值重复测试。一般OLTP系统需限制其不超过128（ORACLE的默认算法有BUG，在cpu核数超过128，默认并行参数设置过高时，容易被触发，会导数oracle无法启动。另外，如果这个参数太高，并行的进程开的太大了，会导数数据库无法承受并发压力）。</p>
<p><em>SQL&gt; alter system set parallel_max_servers=128 scope=spfile sid='';</em></p>
<p><strong>7.11 fast_start_mttr_target \&amp; fast_start_parallel_rollback</strong></p>
<p>fast_start_mttr_target={0-3600} 一旦设置具体值，那么崩溃恢复将在此要求的时间范围内完成。fast_start_parallel_rollback={high/low/false}，high启动4倍于CPU数目的并行恢复进程，low启动2倍于CPU数目的并行恢复进程，false关闭并行恢复进程。这两个参数都是用来加速在故障场合下的奔溃恢复，其根本的机制就是要通过主动触发checkpoint来缩短最近依次checkpoint和联机重做日志之间的距离。但是这个无疑会带来一定的性能风险。所以这两个值的设置需要根据具体业务情况来设置，同时要进行压力测试，不要因为激进的策略带来性能问题。</p>
<p>与此相关还有一个参数log_checkpoints_to_alert，默认是关闭状态。打开这个参数会在trace文件当中记录详细的检查点发生信息，对于数据库诊断来讲是一个必不可少的功能。因此建议打开。</p>
<p>SQL&gt; alter system set fast_start_mttr_target=120 scope=spfile sid='';</p>
<p><em>SQL&gt; alter system set fast_start_parallel_rollback=low scope=spfile sid='';</em></p>
<p>SQL&gt; alter system set log_checkpoints_to_alert=true scope=spfile sid='*';</p>
<p><strong>7.12 listener.ora</strong></p>
<p>对于11.2之前的 listener， 首先得保证IPC项存在，且此项列在所有RAC listener的地址列表的第一个。否则，可能会对 VIP 在公网接口出现故障时进行故障转移所用的时长产生不利影响。</p>
<p>LISTENER_n1 =</p>
<p>(DESCRIPTION_LIST =</p>
<p>(DESCRIPTION =</p>
<p>(ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC)))</p>
<p>(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = n1vip)(PORT = 1521)(IP = FIRST)))</p>
<p>(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 10.8.21.121)(PORT = 1521)(IP = FIRST)))</p>
<p>)</p>
<p>)</p>
<p>假设把TCP列到第一项，那么监听需要等待TCP timeout，而TCP timeout是受操作系统相关参数控制，并且timeout时间较长，那么就有可能引起故障转移并不能非常及时的问题。</p>
<p><strong>7.13 sqlnet.ora</strong></p>
<p>数据库连接的客户端异常断开后，其占有的相应并没有被释放，如从v$session视图中依旧可以看到对应的session处于inactive，且对应的服务器进程也没有释放，导致资源长时间地被占用，对于这种情形开该如何处理呢？sqlnet.expire_time对于这个问题我们提供了解决方案，专门用于清理那些异常断开的情形，如网络异常中断，客户端异常掉电，异常重启等。可以在sqlnet.ora文件当中设置参数sqlnet.expire_time=5。</p>
<p><strong>7.14 回收站</strong></p>
<p>从ORACLE 10g开始，引入了一个叫回收站(Recycle Bin)的概念。它的全称叫Tablespace Recycle Bin。回收站实际是一个逻辑容器。它以表空间中现有已经分配的空间为基础，而不是从表空间上物理划出一个固定区域用作回收站。这意味着回收站和表空间中的对象共用存储区域、系统没有给回收站预留空间。因此，当表被DROP后，如果可用空间充足，并且没有对回收站进行清理，那么被DROP掉的对象会一直存在回收站中，但是如果可用空间紧张的情况下，数据库会根据先进先出的顺序覆盖Recycle Bin中的对象。回收站这个特性主要的好处就是在误删除一个表时有一个恢复机制，不必通过数据库还原来实现，避免大量的人工误操作。</p>
<p>但是对于业务场景存在大量的drop对象但是没有purge操作的场合下，那么回收站的开启势必会带来数据库系统表空间大量占用以及数据库性能降低的风险，尤其是在金融业务批量的业务环境。所以就实际的生产环境而言，这个功能的用途并非像理论上想象的那么有意义。基于风险的考虑，还是建议把该功能关闭掉。</p>
<p>SQL&gt; show parameter recyclebin</p>
<p>SQL&gt; salter system set recyclebin=off cope=spfile sid='*';</p>
<p>SQL&gt; spurge recyclebin</p>
<p>SQL&gt; spurge dba_recyclebin</p>
<p><strong>7.15 结果缓存</strong></p>
<p>结果缓存是11g之后增加的特性。分为server和client两种类型，server是指在SGA的Share Pool当中保留一块儿内存空间用来存储SQL或者PLSQL的查询结果，供其他进程来共享使用。Client是指在OCI连接进程当中的内存当中保存SQL查询结果，供进程内所有session来使用。本意都是要提高查询效率。但是在实际使用的过程当中，它曾导致了很多问题，例如RC Latch、reliable message等资源等待问题（ID 1951729.1 - Bug 19557279，12c已经修复了这个问题）。</p>
<p>因此，为了避免系统遇到以上情况下发生更严重的性能问题，建议将结果缓存关闭掉。</p>
<p>SQL&gt; alter system set “_optimizer_ads_use_result_cache”=false scope=spfile sid='*';</p>
<p>SQL&gt; alter system set “_result_cache_max_size”=900 scope=spfile sid='*';</p>
<p><strong>7.16 undo</strong></p>
<p>在事务提交或回滚之后，因为flashback或一致读的需求，还需要将对应的undo数据保存在undo表空间中一段时间，这个时间就是由undo_retention来设置的。根据undo_retention可以继续将undo的inactive状态划分为EXPIRED，UNEXPIRED两类，undo中超过undo_retention时间之外的inactive undo回滚区称为expired, 还处于unod_retention时间之内的inactive undo回滚区称为unexpired。undo_retention不是说必须达到这个时间后才能被覆盖，而只是一个期望值，比如undo表空间文件设置为非自动扩展，当一个大事务需要将undo中未使用的区域及过期的undo区域都使用完了，而undo空间不能自动扩展，这时保证事务顺利运行优先级比较高，undo中没有过期的回滚区也会被覆盖使用(从其中使用时间越早的开始)，也就是说retention设置的时间段内的undo非过期数据是没有保证的。</p>
<p>10g之后引入一个新的参数”_undo_autotune”，这个参数表示Oracle对undo自动优化，就是在undo表空间非自动增长的情况下，Oracle会根据undo表空间的大小来调整undo RETENTION的大小，自动调整retention就是最大限度的利用当前undo表空间的可用空间，尽可能的保留最多的undo数据，以最大化的减少类似ORA-01555 等错误发生。</p>
<p>_undo_autotune此参数默认开启，有利于时间长的查询，但是对于典型的OLTP系统来说不太适用。尤其在系统繁忙的时候，经常会出现undo不够用的情况。回收undo，简单resize会报错。因此建议关闭次参数并设置合理的retetion时间。</p>
<p>SQL&gt; alter system set “_undo_autotune”=false scope=spfile sid='*';</p>
<p>SQL&gt; alter system set undo_retention=900 scope=spfile sid='*';</p>
<p>（1.OLTP系统:15分钟 / 2.混合:1小时 / 3.DSS系统:3小时）</p>
<p><strong>7.17 执行计划中的排序</strong></p>
<p>正常情况下，我们认为SQL查询计划如果利用index排序索引的话，从性能上看会是比较优的选择。但是往往在实际生产过程中，很多中情况如果走排序的话不一定会获得最优的查询计划，有的时候反而会更糟糕。因此Oracle用一个参数”_sort_elimination_cost_ratio"来控制是否一定走排序寻找最优执行计划。</p>
<p>当"不排序的成本/排序的成本"这个比值小于”<em>sort_elimination_cost_ratio"参数值时，Oracle会选择不走排序去获取执行计划。“</em>sort_elimination_cost_ratio"的默认值为0，也就是说Oracle默认任何情况下都选择排序获取执行计划，即使排序的成本是无穷大。</p>
<p>实践证明默认的选择往往是不对的，会将查询引入到错误的执行计划。因此建议将参数值设置为5。 SQL&gt; alter system set “_sort_elimination_cost_ratio”=5 scope=spfile sid='*';</p>
<p><strong>7.18 控制文件</strong></p>
<p>关于控制文件的记录保存时间控制参数control_file_record_keep_time，默认值为7，也就是说控制文件只保留7天记录，超过7天的备份记录会被认为是无效的。这个值的设置需要跟两个地方关联起来：</p>
<ol>
 <li>
  <p>集中备份软件设置的备份策略中关于过期时间的定义。</p>
 </li>
 <li>
  <p>Rman的参数retention policy。</p>
 </li>
</ol>
<p>例如我们的备份策略对某系统备份集过期的时间定义为30天，那么我们就分别对控制文件和RMAN两个地方的参数做如下设置（RMAN保留时间设置太长会导致list backup命令执行多几分钟时间）：</p>
<p>SQL&gt; alter system set control_file_record_keep_time=30 scope=spfile sid='*';</p>
<p>RMAN&gt; configure retention policy to recovery window of 30 days;</p>
<p><strong>7.19 job_queue_processes</strong></p>
<p>如果同一时间内运行的Job数很多，过小的参数值导致job不得不进行等待。而过大的参数值则消耗更多的系统资源。应该设置一个比较合理的数值，以避免此类事情发生。</p>
<p>Oracle默认值为1000，这个值对于一般的OLTP系统来讲比较大，所以需要进行合理的修改。</p>
<p><strong>7.20 rman</strong></p>
<ol>
 <li>
  <p>为了提高rman备份的效率，假设磁带备份设备支持异步I/O，建议将这个参数设置为true，以启动该设置。创建一个large pool，使得RMAN中获得良好的性能。 SQL&gt; alter system set backup_tape_io_slaves=true scope=spfile sid='*';</p>
 </li>
 <li>
  <p>关于ADG备库的RMAN参数设置， RMAN&gt; configure archivelog deletion policy to applied on standby; 这个参数设置是保护没有被应用的日志不被删除，在11g的高版本实际上已经不需要再设置了，但是低版本的就需要注意了。</p>
 </li>
</ol>
<p><strong>7.21 其他</strong></p>
<p>1．表空间的数据文件是否采用了自动扩展的方式？</p>
<p>2．表空间的数据文件是否都用了ASM的方式？</p>
<p>3．ASM的冗余方式是否一致？</p>
<p>4．应用用户的默认密码策略是不是已经取消了180天的限制等等。</p>
<p>5．数据库的监控指标是否覆盖了（集群、服务、监听、ASM、表空间、性能等所有应该涵盖的方面）？</p>
<p>6．OS层面的监控是否已经启用？尤其是私网之间的通讯、CPU、内存的监控等？是Nmon还是osw，他们的日志是定期循环还是持续不断增长等等？</p>
<p>7．数据库巡检的体系是否完善？日巡检月度巡检的内容是否经过精心设计？是否已经实现了自动化等等？强烈建议日巡检工作实现脚本自动化，任务定时执行，日志统一整合到共享文件系统上，有条件的可以进行整合入库，按照自己的巡检机制和体系实现按需调入调出。</p>
<p>
 <br>
</p>
<p><strong>8.总结及展望</strong></p>
<p>本文是基于数据建设的规划、实施以及配置优化等阶段对大量文献的总结提炼以及项目的实践出发，对数据库建设过程中各个层面应该注意的事项进行了一个总结。希望对正在从事此项工作的同业以及将来要从事这类项目的同业给予参考，也希望更多人在此基础之上能够将其完善和优化分享给大家。</p>
<p>
 <br>
</p>
<p>【主要参考文献】</p>
<p>[1]. RAC 和 Oracle Clusterware 最佳实践和初学者指南（平台无关部分） (文档 ID 1526083.1)</p>
<p>[2]. RAC and Oracle Clusterware Best Practices and Starter Kit (Linux) (文档 ID 811306.1)</p>
<p>[3]. RAC and Oracle Clusterware Best Practices and Starter Kit (AIX) (文档 ID 811293.1)</p>
<p>[4]. Top 11 Things to do NOW to Stabilize your RAC Cluster Environment (文档 ID 1344678.1)</p>
<p>[5]. IBM System p Advanced POWER Virtualization Best Practices</p>
<p>[6]. 最佳实践：主动避免数据库和查询相关的性能问题 (文档 ID 1549184.1)</p>
<p>[7]. 11gR2 Clusterware and Grid Home - What You Need to Know (文档 ID 1053147.1)</p>
<p>[8]. Oracle Databases on VMware Best Practices Guide</p>
<p>[9]. Deploying Oracle Database 11g R2 on Red Hat Enterprise Linux 6 Best Practices</p>
<p>[10].10g 和 11gR1 ASM 技术最佳实践 (文档 ID 1602417.1)</p>
<p>[11]. Oracle® Database High Availability Best Practices 11g Release 1 (11.1)</p>
<p>[12]. Oracle® Database High Availability Best Practices 11g Release 2 (11.2)</p>
<p>[13]. Oracle on AIX – Configuration \&amp; Tuning. R Ballough (ppt)</p>
<p>[14]. Oracle Database 11g R2 Oracle Database 11g R2 RAC on IBM AIX Tips and Considerations</p>
<p>[15]. Grid Infrastructure Redundant Interconnect and ora.cluster_interconnect.haip</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<blockquote>
 <p>更多相关内容，请点击阅读原文</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528921093.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633378&amp;idx=1&amp;sn=792851266d3aff69c0fe56100c35b441&amp;chksm=bef90a24898e8332b02b34d1c3e2627525584df6d8f8446e0bb2aadec4c00b93d5f717442ff8&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/61aa0e6b-83e4-4afd-8956-d31889131734</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 00:40:41 GMT</pubDate></item><item><title><![CDATA[风险提示：这么用 x86，小心 Oracle+RAC 中招]]></title><link>https://wiki.dgfygk.com/archives/60bca240-e7a0-4f01-9791-93546a8252fd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%A3%8E%E9%99%A9%E6%8F%90%E7%A4%BA%EF%BC%9A%E8%BF%99%E4%B9%88%E7%94%A8%20x86%EF%BC%8C%E5%B0%8F%E5%BF%83%20Oracle%2BRAC%20%E4%B8%AD%E6%8B%9B&amp;url=/archives/60bca240-e7a0-4f01-9791-93546a8252fd" width="1" height="1" alt="" style="opacity:0;">
<p>最近有朋友和小y反馈：</p>
<p>他们的一台IBM的X86服务器（现在属于联想）出现硬件损坏，维护人员通过管理口收集诊断日志给厂商时，服务器上运行的好好的一套ORACLE 11.2的RAC数据库，
 <br>
 突然有一个节点重启了..这是是什么情况?</p>
<p>听到这里，小y基本上猜到了原因，类似的问题，以前分析和处理过几次，分析过程也不复杂，但是没想到，类似的故障现在居然还在发生.</p>
<p>因此有必要把这个风险提示出来！</p>
<p>这里，小y为大家分享一个过去处理的案例,文章最后会给出X86服务器与RAC结合的具体的风险提示，希望大家早了解，早预防，避免踩坑，伤人伤己。
 <br>
 <strong>问题来了！！</strong></p>
<p>周五，晚上十一点，电话响了，是一位服务商的电话，看来出大事了，一下来了精神。</p>
<p>“小y，一套linux上的11.2.0.3 2节点的RAC，</p>
<p>节点2数据库今天下午自己重启了一次，后来自己起来了。</p>
<p>几个小时前，又挂了，到现在还没起来！</p>
<p>两个节点私网IP互相ping了一下,可以ping通！</p>
<p>你赶紧远程连上来处理下吧”</p>
<p>（BTW：是的，大家没看错，是服务商而不是最终客户的电话。小y所在的公司不仅直接面向客户提供数据库专家服务，也为其他服务商、软件开发商提供三线支持，不过小y最近业绩压力大的晚上睡不着觉，还请各位兄弟多多帮忙推荐和介绍啊。）
 <br>
 <strong>分析与恢复故障</strong></p>
<p><strong>验证节点2无法启动</strong></p>
<p>
 <br>
</p>
<p>时间紧急，远程连入后，发现节点2确实挂掉了，那就直接startup启动数据库看看</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528926777.png&amp;size=m" alt=""></p>
<p>照理来说，startup命令下去后，这里很快就可以看到SGA分配并启动到mount的信息，但命令下去已经一分钟了，还没任何输出，确实不妙。</p>
<p>最终，startup命令在敲入后长时间依然无响应，大概10分钟后后报ORA-03113错误后退出。</p>
<p>如下图所示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528926908.png&amp;size=m" alt=""></p>
<p>看来，数据库启动过程中遇到了异常，需要继续分析alert日志。</p>
<h4><strong>检查节点2数据库的alert日志</strong></h4>
<p>截取altert日志关键信息，如下图所示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528926977.png&amp;size=m" alt=""></p>
<p>可以看到：</p>
<p>由于节点2的Lmon进程通过ipc与节点1的LMON进程通讯超时，简单来说，两个节点的RAC无法通讯，因此无法加入集群。因此需要进一步检查两个节点的私网通讯是否正常。</p>
<p><strong>获取两个节点私网通讯的IP地址</strong></p>
<p>之前他们提到两个节点的私网IP是可以ping通的，我估计他们是ping错IP了。</p>
<p>因为，从11.2.0.2（含）开始，ORACLE私网通讯不再直接采用"我们在私网网卡上所配置的地址（例如192.168这样的地址）“，而是采用GRID启动后，ORACLE在私网网卡上绑定的169.254这个网段的地址。确认了一下，他们果然ping的是192.168这个IP，这个IP能PING通是不够的…</p>
<p>发出下列命令获得，两个节点私网通讯采用的IP地址如下所示:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927048.png&amp;size=m" alt=""></p>
<p>也就是说，RAC两个节点用于私网通讯的真实地址是：</p>
<p>节点1采用的私网通讯地址是169.254.220.75，而不是192.168.x.x</p>
<p>节点2采用的私网通讯地址是169.254.106.133，而不是192.168.x.x</p>
<p>也就是说，GRID启动前后的IP，如下所示：</p>
<h4><strong>检查两个节点私网通讯是否正常</strong></h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927187.png&amp;size=m" alt=""></p>
<p>从上图可以看到：</p>
<p>两个节点之间互相ping不通169.254这个实际的私网通讯地址！这就是为什么节点2的数据库实例加不到集群的原因！</p>
<h4><strong>思考时间</strong></h4>
<p>到这里，我们不妨用一张表表格小结一下：</p>
<p>其中bond0是私网网卡，192.168是手工配置的，169.254这个IP是GRID起来后绑上去的:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927291.png&amp;size=m" alt=""></p>
<p>这是什么情况呢？</p>
<p>其实很简单，别着急，问题原因就在后面，什么时候往下翻，由你决定…</p>
<p>那是什么原因导致两个地址不通呢？</p>
<p>我们进一步检查两个节点的路由情况，发现了异常。如下所示</p>
<p>检查，发现节点1上的私网路由丢失，导致两个节点之间的私网无法正常通讯，继而无法正常加入集群。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927377.png&amp;size=m" alt=""></p>
<p>而节点2上是存在169.254这个路由的！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927515.png&amp;size=m" alt=""></p>
<p><strong>暂时解决问题</strong></p>
<p>在节点1 即node1上使用root用户执行下列命令，补充丢失的路由即可</p>
<h1>route add -net 169.254.0.0 netmask 255.255.0.0 dev bond1</h1>
<p>在节点1上实施该解决方案后，节点2数据库实例启动正常，问题得到解决。</p>
<p>到这里，有同学说，可以不可以把两个节点的GRID全部停掉，全部重启来恢复呢?</p>
<p>答案是yes !</p>
<p>因为重启GRID，会重新在bond0绑169.254这个IP，同时添加169.254.0.0这个路由。</p>
<p>但是缺点也是显而易见的，两个节点停掉，业务不就停了么？</p>
<p>另外，万一节点1停了也起不来了呢？客户显然无法接受。</p>
<p>我们手工加路由其实和GRID加路由是一样的命令，理解本质了，变化会更多一些。</p>
<h4>节点一路由丢失原因进一步核查</h4>
<p>节点1到169.254这个网段的路由丢失，导致两个节点私网无法通讯。
 <br>
 这个路由和169.254的IP都是grid在启动的时候进行绑定IP和添加路由的。</p>
<p>首先，我们通过检查历史命令，排除了人为删除路由的可能。</p>
<p>继续检查节点1这个节点GRID的alert日志,可以看到:</p>
<p>在6月3日19点36分，出现了oraRootAgent将路由删除的日志，而当时这个设备是usb0!即IMM服务器管理口！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927605.png&amp;size=m" alt=""></p>
<p>其次，操作系统/var/log/messages显示：</p>
<p>在路由被删除前，系统正在为usb0网卡通过DHCP动态申请IP，并最终在问题出现前的20秒左右，在usb0上分配了169.254.95.120这个IP:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927736.png&amp;size=m" alt=""></p>
<p>最终，节点2数据库数据库实例由于私网不通，IPC通讯超时，被驱逐出集群,日志如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927859.png&amp;size=m" alt=""></p>
<h4><strong>问题串联总结</strong></h4>
<p>从上述分析不难发现，整个事情发生的顺序如下：</p>
<p>1) 19:36:25, 节点1 USB0网卡被分配169.254.95.120这个IP</p>
<p>2) 19:36:48, 节点1 orarootagent进程发现USB0上分配的169.254 IP与RAC集群通讯的IP冲突后删除169.254的路由 ，导致两个节点RAC无法通讯</p>
<p>3) 19:41:24, 节点2 报IPC 通讯超时，被驱逐出集群</p>
<p>4) 由于节点1的169.254的路由丢失，导致节点2 无法与节点1通讯，一直无法加入集群</p>
<p>5) 手工对节点1增加169.254的路由后，问题解决</p>
<p>不难看出来，这个行为是正常的行为！</p>
<p>我们以"Removed unused HAIProute: 169.254.95.0"作为搜索关键字，在METALINK上查找，MOS上的下面文章也介绍了这个行为，推测得到验证。</p>
<p>Oracle RAC H/A Failure Causes Oracle Linux Node Eviction On Server With IBM Integrated Management Module (IMM) (文档 ID 1629814.1)
 <br>
 <strong>风险提示</strong> <strong>风险提示</strong></p>
<p>在部署了ORACLE11.2.0.2或以上的版本中</p>
<p>由于集群的ASM和DB使用169.254.x.x作为集群私网通讯的IP</p>
<p>【GRID启动后在私网网卡绑定169.254.x.x这个IP并添加169.254.0.0的路由】</p>
<p>目前已知的情况中，IBM X86服务器装完操作系统后，存在USB0这样一块网卡，这个网卡是用来和IMM通讯的，IMM是服务器的管理口，通过USB网络的LAN进行硬件管理。</p>
<p>当USB0网卡被激活时，将分配169.265.95.120（118）这个IP，将导致RAC集群路由被破坏，继而导致DB/ASM无法通讯而重启/节点驱逐的故障, 并且由于路由丢失，导致RAC节点被驱逐出集群后，无法加入集群，需要人工介入处理。</p>
<p>不管是IBM/联想的X86服务器的IMM，还是其他品牌的服务器（例如DELL的iDRAC，或者是SUN的T系列），集群中的任何节点的任何网络设备都不应该分配169.254.x.x这个网段的IP【link local ip】,否则将导致RAC集群路由被破坏，导致节点驱逐的故障。</p>
<h4><strong>如何检查自己的系统是否存在类似问题</strong></h4>
<p>1）发出下列命令，检查系统是否曾经存在某个网卡动态分配过169.254这个网段的IIP</p>
<h1>cat /var/log/messages*\|grep dhclient \|grep “bound to 169.254”</h1>
<p>如有，则进入预防环节</p>
<p>2）发出下列命令，检查系统是否当前存在非RAC私有网卡被分配169.254这个网段的IP</p>
<h1>ifconfig -a</h1>
<p>..</p>
<p>usb0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX</p>
<p>inet addr:169.254.xx.xx Bcast:169.254.95.255 Mask:255.255.255.0</p>
<p>..</p>
<p>如有，则进入预防环节</p>
<h4><strong>发现匹配，如何预防</strong></h4>
<p>修改这些网络设备如USB0口不采用DHCP，而是直接采用静态IP</p>
<p>修改dhcp为static，并分配IP</p>
<h1>vi /etc/sysconfig/network-scripts/ifcfg-usb0</h1>
<h1>BOOTPROTO=dhcp</h1>
<p>BOOTPROTO=static</p>
<p>IPADDR=xxx.xxx.xxx.xxx</p>
<p>重启网卡，使改变生效</p>
<h1>/sbin/ifdown usb0</h1>
<h1>/sbin/ifup usb0</h1>
<h1>/sbin/ifconfig usb0</h1>
<p>
 <br>
</p>
<blockquote>
 <p>本文由作<strong>DBA小y</strong> 者授权，更多DBA小y文章：</p>
 <p>http://www.talkwithtrend.com/home/space.php?p=blog\&amp;uid=994339</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528927947.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630827&amp;idx=3&amp;sn=ec862f8c3c20549114c84b5bdad3ede1&amp;chksm=bef9142d898e9d3b27a1d8c016b9f4f42619af8e694f7ccd2030cce4355fd0f49a884ef31782&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/60bca240-e7a0-4f01-9791-93546a8252fd</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 6 Nov 2024 00:39:53 GMT</pubDate></item><item><title><![CDATA[分布式存储上是否支持部署Oracle RAC？]]></title><link>https://wiki.dgfygk.com/archives/fccceba7-ecc6-4051-8148-de25cb8a6b7b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%86%E5%B8%83%E5%BC%8F%E5%AD%98%E5%82%A8%E4%B8%8A%E6%98%AF%E5%90%A6%E6%94%AF%E6%8C%81%E9%83%A8%E7%BD%B2Oracle%20RAC%EF%BC%9F&amp;url=/archives/fccceba7-ecc6-4051-8148-de25cb8a6b7b" width="1" height="1" alt="" style="opacity:0;">
<p><em>来自社区交流，观点供大家参考</em></p>
<blockquote>
 <h1><strong>在分布式存储上是否支持部署OracleRAC，具体的部署流程是怎样的？</strong></h1>
 <p>
  <br>
 </p>
</blockquote>
<p>@yaoyaozdl 汽车行业</p>
<p>自己DIY还是不建议用分布式存储部署oracle rac，测试环境到没啥，生产环境还是多等等吧。</p>
<p>
 <br>
</p>
<p>@ZhuJun2014 IBM</p>
<p>分布式块存储通过iscsi可以支持Orace RAC，部署流程跟传统存储没有具体分别。最为关键的是，Oracle提供对分布式存储的支持吗？如果没有，通常是不建议部署在生产环境的。</p>
<p>
 <br>
</p>
<p>@岳彩波 复华集团</p>
<p>oracle还是比较传统的，特别是集群，存储方面一定要稳定，不管如何规划，还是要把数据安全和稳定放在第一位。</p>
<p>
 <br>
</p>
<blockquote>
 <p>更多相关内容，请点击阅读原文</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528928684.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632714&amp;idx=3&amp;sn=c3b22b3c0ea47385457f42339e14ff76&amp;chksm=bef90c8c898e859acd6184951273613022d952a8d02e4e775ae112ba6087ed7c2f15a9210753&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/fccceba7-ecc6-4051-8148-de25cb8a6b7b</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 4 Nov 2024 01:46:53 GMT</pubDate></item><item><title><![CDATA[分析 mcelog 引发 x86 RAC 失效原因]]></title><link>https://wiki.dgfygk.com/archives/0f542dc6-f529-4648-ac39-ec299eb7bcc4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%86%E6%9E%90%20mcelog%20%E5%BC%95%E5%8F%91%20x86%20RAC%20%E5%A4%B1%E6%95%88%E5%8E%9F%E5%9B%A0&amp;url=/archives/0f542dc6-f529-4648-ac39-ec299eb7bcc4" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>来自社区专栏"平台人生”</p>
 <p>http://www.talkwithtrend.com/Column/detail/id/11</p>
</blockquote>
<p>
 <br>
</p>
<p>某年某月某天早晨，运维工程师小A刚做完值班交接工作，忽然收到数据库RAC心跳超时的告警，紧接着又收到了一条节点二重启的告警，接着可怕的事情发生了，节点一也重启了！</p>
<p>从现场来看，节点一主机Oracle进程状态异常，此时数据库网络心跳超时，但磁盘心跳正常，根据数据库机制，节点二被踢出RAC集群。随后，节点一主机系统panic，引发重启。此时整个数据库服务不可用，引起系统中断。</p>
<h3>那么这个可怕的原因到底是什么呢？</h3>
<p>且听我慢慢给您分析。</p>
<p>大多数运维工程师都知道内存故障的频率不如硬盘故障的频率高，但是内存发生错误却是很常见的，其中的奥秘就在于ECC内存。</p>
<p>ECC内存指的是带有ECC功能的内存，即Error Checking and Correcting，它实际上是一种错误检查和纠正的技术，它能够容许错误，并可以将错误更正，使系统得以持续地正常工作，不致因错误而中断。ECC内存正是带有这种技术的内存。那么是不是只要使用了带有ECC功能的内存就可以高枕无忧了呢？ECC技术实际上解决的是软错误，例如电子干扰造成的传输错误，但是对于硬件错误来说，是无法被修正的。并且ECC技术也不是万能的，它只能纠正1个比特错误和检测2个比特错误，对1比特以上的错误无法纠正，对2比特以上的错误不保证能检测。</p>
<p>对于内存错误，首先我们需要能够确定是哪些组件导致的问题。是DIMM？是内存控制器？还是内存页？这就轮到本期主角上场了！它就是mcelog。在Linux操作系统中，我们可以使用mcelog服务来跟踪内存错误。正如上面所说，常见内存错误通常是软错误，如DIMM中一个"卡住的"bit，这种错误是可以被ecc技术纠正的，mcelog会跟踪记录这一情况。但是当这个bit附近的一个bit再次发生损坏时，就可能发展成一个不能被修正的内存错误，此时，内核默认策略就是停止使用这个位。也就是说，此时内核将内存页复制到其他地方，并且从内存页管理"清单"中删掉该页，也叫做offline。那么怎么判断该不该offline一个内存页呢？mcelog采取了如下方式：当修复内存错误的次数超过一定阈值（阈值缺省设置为一块内存页中24小时内重复出现10次），该内存页就会被offline。本案例中，用于数据库的db服务器会开启"大页"功能，一般来说，Linux内存页大小为4K，“大页"为2M，在查看日志的时候，我们发现系统panic发生在mcelog服务offline一个大页的时候。日志如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528929606.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528929768.png&amp;size=m" alt=""></p>
<p>查看日志，我们发现系统从13日04:06开始，记录有硬件（内存）错误。并且到8:25分时，系统连续报错达到10次。因此，mcelog将错误的内存 offlining。记录如下：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528929894.png&amp;size=m" alt=""></p>
<p>操作系统在8:25:41报错，kernel: BUG: unable to handle kernel NULL pointer dereference at (null)，并且RIP是page_check_address。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528929995.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528930257.png&amp;size=m" alt=""></p>
<p>过日志我们发现，本问题的关键就在于page_check_address ()这个内核函数。我们先剧透一下发生panic的真实原因，后续再进行详细解释：负责offline的控制模块调起page_check_address()函数，但是由于huge_pte_offset()函数返回了一个NULL pointer，传给了page_check_address()函数，而page_check_address()函数又没有执行空指针判断，因此出现了系统panic。</p>
<p>你肯定会问page_check_address()函数和huge_pte_offset()函数是干嘛的？</p>
<h3>首先我们要知道内存是如何寻址的</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528930399.png&amp;size=m" alt=""></p>
<p>内存的寻址实际上是将虚拟地址转化为物理地址的过程，如上图所示。x86架构中，虚拟地址（又叫linear address）实际上是由内存中各表（pgd表、pud表、pmd表，pte表）的索引组成的，怎么找到一个内存真实的物理地址呢？首先，CR3是内存页目录（pgd表）的基址，通过线性地址中的pgd索引，我们找到了对应pud表的基址，然后通过pud索引，找到了pmd表，又通过pmd表索引，找到了pte表，通过pte索引找到了真实物理地址的起始位置，加上linear address中的offset，就是我们需要的真实物理地址了。</p>
<p>在这个过程中，huge_pte_offset()函数是用来获得大页的物理内存基址的。实际上，大页的寻址中，是没有pte表的，大页的linear address也没有其对应索引，因此这个函数的返回值，实际上是pmd表的索引。</p>
<p>为什么说本案例中huge_pte_offset()函数没有获取到pmd呢？上面的日志文件中，有一行：</p>
<p>Sep 13 08:25:41 prod-mac kernel: PGD 106601a067 PUD 1066085067 PMD 0</p>
<p>也就是说，发生系统panic的时候，PMD为空。</p>
<p>那么page_check_address()函数呢？我们可以从它的返回值窥见：返回值为可用的pte指针。也就是说，该函数是用来获取未被lock的pte指针的。本案例正是因为在寻址过程中，出现了空的pmd表索引，而page_check_address()未对其进行判断，得到了错误的pte，进而导致了panic。这是一个内核bug啊！！还好，这个bug已经有了对应的修复：</p>
<p>mm/hugetlb: check for pte NULL pointer in __page_check_address()</p>
<p>我们来看看这个已经被修正过的函数：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528930531.png&amp;size=m" alt=""></p>
<p>修复过的page_check_address()函数对pte进行了空指针筛查。</p>
<p>针对这一内核bug，临时的解决方案可以通过重启mcelog服务并重置记录内存错误的计数器来暂时避免下线动作的发生，但是这一方案并未从根本上解决问题，显而易见，这种问题的终极解决方案就是升级内核啦\~</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528930668.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<blockquote>
 <p>更多相关文章请点击阅读原文</p>
</blockquote>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528930753.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650631963&amp;idx=3&amp;sn=c64c3cd15ef40f4e5afe85efe6d3dab5&amp;chksm=bef9119d898e988b655e55bc4b0f67b26c05c1d66b42303b18906725dee487dfb8a86a3d675a&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/0f542dc6-f529-4648-ac39-ec299eb7bcc4</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 4 Nov 2024 01:46:30 GMT</pubDate></item><item><title><![CDATA[用TSM的代理节点（proxy node)备份oracle RAC数据库]]></title><link>https://wiki.dgfygk.com/archives/dcd10e6a-508e-41ff-982a-57ff5c0da6ec</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%94%A8TSM%E7%9A%84%E4%BB%A3%E7%90%86%E8%8A%82%E7%82%B9%EF%BC%88proxy%20node%29%E5%A4%87%E4%BB%BDoracle%20RAC%E6%95%B0%E6%8D%AE%E5%BA%93&amp;url=/archives/dcd10e6a-508e-41ff-982a-57ff5c0da6ec" width="1" height="1" alt="" style="opacity:0;">
<p><strong>一、多节都备份的概念</strong></p>
<p>TSM的节点node和namespace是一一对应的，在分布式环境下，多台参与计算的主机如果用传统的node备份方式，将会出现每个节点一个namespace的现象，备份、管理起来比较麻烦。从TSM5.4.1开始，在共享存储的分布式环境下，多个主机可以以一个"整合主机"的方式进行备份，在TSMserver端共用一个namespace。这样，备份的目录和文件可以很方便地restore在任何一台主机上，proxy node设置可以让多个node整合像一个node，它包含2个节点类型，一个是target node，一个是agent node，target node拥有数据namespace，agent node代理target node参与备份数据。</p>
<p>
 <br>
</p>
<p><strong>二、proxy node的设置步骤</strong></p>
<p>1、在相关节点安装B/A客户端软件；</p>
<p>2、在TSM server端注册节点 target node，agent nodes；</p>
<p>3、给注册的agent nodes节点赋予proxy authority 权限，数据将由target node name 存放，原来的agent node将只是target node的代理；</p>
<p><strong>建议:</strong></p>
<ul>
 <li>
  <p>共享环境的所有的节点必须是同一OS平台；</p>
 </li>
 <li>
  <p>不要把target node当做一般的node，target node是以asnodename的形式出现，它将替代传统的node来代表TDPO的备份数据；</p>
 </li>
 <li>
  <p>不能备份/恢复NAS数据。</p>
 </li>
</ul>
<p>
 <br>
</p>
<p><strong>三、proxy node备份oracle RAC数据库的例子（AIX，RAC环境）</strong></p>
<p>节点:</p>
<p>DPOracle_HOSTA,</p>
<p>DPOracle_HOSTB</p>
<p>是Oracle RAC的2个节点，这里设置为agent node,</p>
<p>DPOracle_ASNODE是TSM的象征节点，这里设置为target node。</p>
<p><strong>TSMServer端配置:</strong></p>
<p>* 注册节点</p>
<p>registerDPOracle_ASNODEasnodePswd ##target node
 <br>
 register DPOracle_HOSTAhostaPswd ##agent node
 <br>
 register DPOracle_HOSTBhostbPswd ##agent node</p>
<p>
 <br>
</p>
<p>* 给节点赋予proxy agent authority</p>
<p>grantproxynode target=DPOracle_ASNODE agent=DPOracle_HOSTA
 <br>
 grant proxynode target=DPOracle_ASNODE agent=DPOracle_HOSTB</p>
<p>主机端配置：</p>
<p>HOSTA configuration</p>
<p>dsm.sys
 <br>
 *********************************************************************
 <br>
 * IBM Tivoli Storage Manager dsm.sys
 <br>
 *********************************************************************
 <br>
 ServernameMyFavoriteTSMServer
 <br>
 COMMMethodTCPip
 <br>
 TCPPort 1500
 <br>
 TCPServeraddress my.favorite.tsm.server.com
 <br>
 passwordaccess generate
 <br>
 <strong>nodenameDPOracle_HOSTA</strong>
 <br>
 passworddir /home/oracle10g
 <br>
 asnodenameDPOracle_ASNODE</p>
<p>dsm.opt
 <br>
 *********************************************************************
 <br>
 * IBM Tivoli Storage Manager
 <br>
 * Application Program Interface dsm.opt
 <br>
 *********************************************************************
 <br>
 ServernameMyFavoriteTSMServer</p>
<p>tdpo.opt
 <br>
 *********************************************************************
 <br>
 * IBM Tivoli Storage Manager for Databases
 <br>
 * Data Protection for Oracle
 <br>
 *********************************************************************
 <br>
 DSMI_ORC_CONFIG /usr/tivoli/tsm/client/oracle/bin64/dsm.opt
 <br>
 DSMI_LOG /home/oracle10g
 <br>
 TDPO_FS orc10_db</p>
<p>
 <br>
</p>
<p><strong>HOST B configuration</strong>
 <br>
 dsm.sys
 <br>
 *********************************************************************
 <br>
 * IBM Tivoli Storage Manager dsm.sys
 <br>
 *********************************************************************
 <br>
 ServernameMyFavoriteTSMServer
 <br>
 COMMMethodTCPip
 <br>
 TCPPort 1500
 <br>
 TCPServeraddress my.favorite.tsm.server.com
 <br>
 passwordaccess generate
 <br>
 nodenameDPOracle_HOSTB
 <br>
 passworddir /home/oracle10g
 <br>
 asnodenameDPOracle_ASNODE</p>
<p>
 <br>
</p>
<p>dsm.opt
 <br>
 *********************************************************************
 <br>
 * IBM Tivoli Storage Manager
 <br>
 * Application Program Interface dsm.opt
 <br>
 *********************************************************************
 <br>
 ServernameMyFavoriteTSMServer</p>
<p>tdpo.opt
 <br>
 *********************************************************************
 <br>
 * IBM Tivoli Storage Manager for Databases
 <br>
 * Data Protection for Oracle
 <br>
 *********************************************************************
 <br>
 DSMI_ORC_CONFIG /usr/tivoli/tsm/client/oracle/bin64/dsm.opt
 <br>
 DSMI_LOG /home/oracle10g
 <br>
 TDPO_FS orc10_db</p>
<p>这样，RAC上的2个节点就配置好了，可以从任何一个节点发起备份和恢复，在tsm server端的filespace都是orc10_db。</p>
<p>
 <br>
</p>
<p><strong>四、proxy node 调度（schedule）的设置</strong></p>
<p>由于dsm.sys里面有ASNODENAME选项，ASNODENAME将完全替代nodename，因此配置schedule时不能将schedule与nodename进行associated，必须在dsm.sys添加一个没有ASNAME的新的Standza:</p>
<p>ServernameMyFavoriteTSMServer_sche
 <br>
 COMMMethodTCPip
 <br>
 TCPPort 1500
 <br>
 TCPServeraddress my.favorite.tsm.server.com
 <br>
 passwordaccess generate
 <br>
 nodenameDPOracle_HOSTB
 <br>
 passworddir /home/oracle10g</p>
<p>然后在命令行中启动schedule服务：</p>
<p>dsmcsche-se=MyFavoriteTSMServer_sche</p>
<p>
 <br>
</p>
<p><strong>五、proxy node恢复oracle RAC数据库的步骤</strong></p>
<p>由于采用了proxy node设置，恢复时客户端不用更改nodename，只需增加asnodename。TSM erver端只需grant proxy node。</p>
<p>1)、检查TDPO环境</p>
<p>tdpoconfshowenv-tdpo_optfile=/usr/tivoli/tsm/client/oracle/bin64/tdpo_invdb.opt</p>
<p>2）授权给客户端访问目标端备份数据权限（这里RAC系统为目标端、恢复系统为客户端），并修改TSM client配置文件，使客户端可访问目标端备份数据。</p>
<p>TSM server端授权</p>
<p>tsm: TSMSRV&gt;grant proxynodetarget=DPOracle_ASNODE agent=newnode</p>
<p>ANR0140I GRANT PROXYNODE: success. Node newnode is granted proxy authority to node</p>
<p>DPOracle_ASNOD .</p>
<p>客户端修改：</p>
<p>修改参数文件（dsm.sys）,添加以下内容</p>
<p>Asnnodename DPOracle_ASNODE</p>
<p>RMAN&gt; run {</p>
<p>2&gt; allocate channel ch00 type'sbt_tape' parms'ENV=(TDPO_OPTFILE=/opt/tivoli/tsm/client/oracle/bin64/tdpo.opt)';</p>
<p>3&gt; restore spfile from'pubdb3_spfile_305_1_839800666';</p>
<p>4&gt; release channel ch00;</p>
<p>5&gt; }</p>
<p><strong>六、小结</strong></p>
<p>使用TSM的proxy node，可以在多节点、共享存储环境下进行集成的备份/恢复，方便管理和配置，除了Oracle RAC外，还可用于GPFS、HACMP环境下的备份。</p>
<p>
 <br>
</p>
<blockquote>
 <p>社区会员water1997b分享</p>
</blockquote>
<p>文章地址：http://www.aixchina.net/Document/detail/tid/229475</p>
<p>长按下图二维码关注"AIX专家俱乐部"公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528931422.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627287&amp;idx=3&amp;sn=b3249335a55c4d8b517dda9d9c73c733&amp;chksm=bef92251898eab4766da405162677362c8c9f0d15d1044036d1204a30dff0df605b47031fb6d&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/dcd10e6a-508e-41ff-982a-57ff5c0da6ec</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 4 Nov 2024 01:44:34 GMT</pubDate></item><item><title><![CDATA[AIX集群系统下的 ORACLE RAC 故障处理方法]]></title><link>https://wiki.dgfygk.com/archives/377d720c-de39-45aa-8bea-a06ce5580aa4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AIX%E9%9B%86%E7%BE%A4%E7%B3%BB%E7%BB%9F%E4%B8%8B%E7%9A%84%20ORACLE%20RAC%20%E6%95%85%E9%9A%9C%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95&amp;url=/archives/377d720c-de39-45aa-8bea-a06ce5580aa4" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>社区会员 wangweiww 分享</p>
</blockquote>
<p>
 <br>
</p>
<h3><strong>1、引言</strong></h3>
<p>AIX集群系统下的ORACLE RAC（Oracle Real Application Cluster）双机热备份有效地保障了企业重要的核心数据。但如果服务器在出现误操作意外断电时，会导致双机无法正常工作，这也将严重影响到企业核心数据的存储。</p>
<p><strong>2、故障分析</strong></p>
<p>本示例中出现的故障是由于数据库主服务器1号机出现硬件故障，由2号机接管一切服务的情况下，出现误操作使备机意外断电，致使两台数据库服务器全部瘫痪。</p>
<p>经过缜密的检查，发现2号主机OCR文件严重损坏。OCR文件记录节点成员的配置信息，如database、ASM、instance、listener、VIP等CRS资源的配置信息，可存储于裸设备或者群集文件系统。OCR文件的损坏直接导致ORACLE CRS的启动。CRS一般用来搭建Oracle的并行数据库，即RAC，但除了与RAC的接口之外，CRS还提供了一组高可用性的应用程序接口(API)，用来搭建一般应用程序的高可用集群，即一般我们常说的双机热备，比如使用CRS实现Oracle的双机热备。</p>
<p><strong>3、处理过程</strong></p>
<p>针对以上的故障分析，我们立即采取如下对策：</p>
<p>备份数据库</p>
<p>排除主机硬件故障</p>
<p>恢复OCR文件</p>
<p><strong>1号主机：</strong></p>
<p>该机硬件连续几天出现错误，并无规律宕机，IBM硬件工程师多次在现场进行数据搜集以及问题定位，最终确定为连接外接扩展单元的RIO卡存在可能的接触性问题。</p>
<p>之后在对该主机的RIO板卡，以及相关连线进行重新插拔连接后，慢启动模式硬件全面检查通过。</p>
<p>由于考虑到1号机作为HACMP的一个节点，可能对2号机现有的业务的影响，在修复当天关掉了该主机。</p>
<p>同时，IBM技术专家对该主机的问题进行全面会诊，并更换了RIO板卡，反复多次进行慢启动硬件全面检查，均无新的硬件报错，10日观察一天后，恢复了该主机上的业务。</p>
<p><strong>2号主机：</strong></p>
<p>该主机由于异常断电，可能造成了oracle RAC环境中数据镜像的ocr（oracle cluster registory）文件损坏，crs（cluster ready service）的12个服务全部不能启动。为了存储数据的安全性，首先对数据库进行全面备份。因为两套业务系统需要临时恢复，将2号机数据库服务恢复单机模式运行，但是运行于p561上的infamatic无法在单机模式下连接数据库。</p>
<p>cp cd/oracle/pro*/10*/crs*/cdata/adbcrs.bak拷贝相关文件</p>
<p>cp day.ocrday.ocr.bak</p>
<p>cp week.ocrweek.ocr.bak</p>
<p>cpweek<em>.ocr week</em>.ocr.bak</p>
<p>./ocrdump ORADB2_OCRDUMP 查看OCR文件</p>
<p>ls*.dump 查看相关dump日志</p>
<p>cat*DUMP\|grep WMST\|wc –l执行dump中wc文件</p>
<p>./ocrcheck 执行ocr检查</p>
<p>/etc/init.crsstart 启动crs服务</p>
<p>./crs_stat 查看crs状态</p>
<p>oradb1:oracle:/home/oracle$]crs_stat-t</p>
<p>CRS-0184:Cannot communicate with the CRS daemon.</p>
<p>cd$ORACLE_HOME 进入ORACLE_HOME路径下</p>
<p>cd/oracle/network/admin/tnsnames.ora 查看tnsnames</p>
<p>connsys/oracle as sysdba</p>
<p>sqlplus sys/as sysdba</p>
<p>sqlplus 进入数据库并将相应RAC模式更改为单机模式</p>
<p>ps –ef\|grepjava 查看java环境是否在启动状态</p>
<p>ps –ef\|grepinfa 查看infamatic相关服务是否启动</p>
<p><strong>在修复了1号机后，从2号机同步HACMP到1号机。然后进行数据库RAC环境的修复。</strong></p>
<p>smittyhacmp，从2号机同步HACMP到1号机</p>
<p>ExtendedConfiguration</p>
<p>Synchronization</p>
<p>第二项改为yes→回车。</p>
<p><strong>制定两套方案</strong></p>
<p>方案1：参考国外相似案例，可清空oracle启动后在/tmp下生成的.oracle目录，重新启动RAC；</p>
<p>方案2：由于很可能在异常断电时造成oracle OCR文件的损坏，可以尝试修复OCR文件。</p>
<p><strong>实施方案1</strong></p>
<p>由于方案1风险小，实施快速，所以首先选择实施方案1。</p>
<p>实施后，发现属于1号主机的CRS服务无法online，手工启动也以失败告终。我们对照两台主机的/etc/inittab后发现，2号机是先起HACMP，然后才启动数据库相关的3个进程，而1号机刚好颠倒了顺序，遂立即修改1号机的inittab文件，保持与2号机启动的顺序一致，重启主机后，所有的CRS服务均可online。</p>
<p>$ORACLE_HOME/dbs/init<db_name>.ora ORACLE数据库改为RAC模式</db_name></p>
<p>*.cluster_database= TRUE</p>
<p>*.cluster_database_instances= 2</p>
<p>*.undo_management=AUTO</p>
<p>启动CRS</p>
<p>1) 启动CRS</p>
<p>$ORA_CRS_HOME/bin/crs_start–all （oracle用户操作）</p>
<p>2) 两分钟后</p>
<p>启动命令执行完,等待两分钟后做如下检查</p>
<p>A. 确认CRS服务状态</p>
<p>$ORA_CRS_HOME/bin/crsctlcheck crs</p>
<p>B.手工确认如下4个进程是否启动</p>
<p>crsd</p>
<p>ocssd</p>
<p>oprocd</p>
<p>evmd</p>
<p>C.查看CRS资源状态</p>
<p>$ORA_CRS_HOME/bin/crs_stat-t</p>
<p>用$ORACLE_CRS这个环境变量</p>
<p>cd$ORA_CRS_HOME</p>
<p>tar cf/tmp/crs.tar crs/init crs/log css/init css/log evm/init evm/log srvm/log</p>
<p>1号机操作同上</p>
<p>在之后的测试中发现其中有3个CRS服务（adb.cs，db1.srv，db2.srv）不能自动online，target设置为offline，但每次都可以手工启动至正常状态。</p>
<p>[oradb1:oracle:/home/oracle$]crs_stat-t</p>
<p>ora….b1.instapplication ONLINE ONLINE oradb1</p>
<p>ora….b2.instapplication ONLINE ONLINE oradb2</p>
<p>ora.bietl.db application ONLINE ONLINE oradb2</p>
<p>ora.oradb.db application ONLINE ONLINE oradb2</p>
<p>ora….b1.instapplication ONLINE ONLINE oradb1</p>
<p>ora….b2.instapplication ONLINE ONLINE oradb2</p>
<p>ora…..adb.csapplication OFFLINE OFFLINE oradb2</p>
<p>ora….db1.srvapplication OFFLINE OFFLINE oradb1</p>
<p>ora….db2.srvapplication OFFLINE OFFLINE oradb2</p>
<p>ora….S1.lsnrapplication ONLINE ONLINE oradb1</p>
<p>ora.oradb1.gsdapplication ONLINE ONLINE oradb1</p>
<p>ora.oradb1.onsapplication ONLINE ONLINE oradb1</p>
<p>ora.oradb1.vipapplication ONLINE ONLINE oradb1</p>
<p>ora….S2.lsnrapplication ONLINE ONLINE oradb2</p>
<p>ora.oradb2.gsdapplication ONLINE ONLINE oradb2</p>
<p>ora.oradb2.onsapplication ONLINE ONLINE oradb2</p>
<p>ora.oradb2.vipapplication ONLINE ONLINE oradb2</p>
<p>在次日中午业务比较少的时段，尝试修复上述3个服务的启动状态。</p>
<p>导出上述3个服务的profile，修改其属性</p>
<p>ora.oradb.adb.cs&gt; $CRS_</p>
<p>ora.oradb.adb.oradb1.srv</p>
<p>ora.oradb.adb.oradb2.srv</p>
<p>TARGET=ONLINE</p>
<p>FAILURE_THRESHOLD=10</p>
<p>AUTO_START=always（之前为restore）</p>
<p>RESTART_ATTEMPTS=3（之前为0）</p>
<p>导入修改后的profile，3个服务均可以在主机启动后自动启动到online。</p>
<p>ora….b1.instapplication ONLINE ONLINE oradb1</p>
<p>ora….b2.instapplication ONLINE ONLINE oradb2</p>
<p>ora.bietl.db application ONLINE ONLINE oradb2</p>
<p>ora.oradb.db application ONLINE ONLINE oradb2</p>
<p>ora….b1.instapplication ONLINE ONLINE oradb1</p>
<p>ora….b2.instapplication ONLINE ONLINE oradb2</p>
<p>ora…..adb.csapplication ONLINE ONLINE oradb2</p>
<p>ora….db1.srvapplication ONLINE ONLINE oradb1</p>
<p>ora….db2.srvapplication ONLINE ONLINE oradb2</p>
<p>ora….S1.lsnrapplication ONLINE ONLINE oradb1</p>
<p>ora.oradb1.gsdapplication ONLINE ONLINE oradb1</p>
<p>ora.oradb1.onsapplication ONLINE ONLINE oradb1</p>
<p>ora.oradb1.vipapplication ONLINE ONLINE oradb1</p>
<p>ora….S2.lsnrapplication ONLINE ONLINE oradb2</p>
<p>ora.oradb2.gsdapplication ONLINE ONLINE oradb2</p>
<p>ora.oradb2.onsapplication ONLINE ONLINE oradb2</p>
<p>ora.oradb2.vipapplication ONLINE ONLINE oradb2</p>
<p>此时2号主机修复完成。</p>
<p>1，2号主机全部恢复到故障前的状态。</p>
<p><strong>4、经验总结</strong></p>
<p>采用清空oracle启动后在/tmp下生成的.oracle目录，重新启动RAC来恢复OCR文件，可以避免高风险造成的巨大损失。通过这次故障处理，我们可以对于以后的突发故障可以果断做出相应措施，也积累了很多宝贵经验。</p>
<p>
 <br>
</p>
<p>长按下图二维码关注"AIX专家俱乐部"公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528932190.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627144&amp;idx=2&amp;sn=b7053dc0d9e02c978c28472f5c5707b6&amp;chksm=bef922ce898eabd8a69a823022923b351839d8e524929eadd790e09a2bcb3b250797e2543a83&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/377d720c-de39-45aa-8bea-a06ce5580aa4</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 08:02:02 GMT</pubDate></item><item><title><![CDATA[你的Oracle RAC是真集群还是伪集群？高可用失效风险提醒]]></title><link>https://wiki.dgfygk.com/archives/136266dd-c2e1-47f6-ae8e-3c4045b9efa4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%A0%E7%9A%84Oracle%20RAC%E6%98%AF%E7%9C%9F%E9%9B%86%E7%BE%A4%E8%BF%98%E6%98%AF%E4%BC%AA%E9%9B%86%E7%BE%A4%EF%BC%9F%E9%AB%98%E5%8F%AF%E7%94%A8%E5%A4%B1%E6%95%88%E9%A3%8E%E9%99%A9%E6%8F%90%E9%86%92&amp;url=/archives/136266dd-c2e1-47f6-ae8e-3c4045b9efa4" width="1" height="1" alt="" style="opacity:0;">
<p>今天要和大家分享的是下面这么一个严肃的话题：
 <br>
 你的Oracle RAC是真的数据库集群么？还是一个伪集群呢？</p>
<p>换句话说:</p>
<p>当Oracle RAC集群中的一个节点所在的分区/服务器宕掉的时候，你是否可以和领导拍着胸脯说，<em>“没事，这是ORACLE RAC，还有一个节点呢！只要该节点可以抗住负载，完全可以正常对外提供服务！“</em></p>
<p>如果你再把上面那段话读一遍，是否有开始犹豫的感觉了呢？</p>
<p>再换个方法来问一下这个话题：</p>
<p>虽然系统上线前做过RAC高可用测试，但是当集群中的一个节点长时间运行，包括CPU、内存、进程数在内的负载在不断变化，并且又经历了一些列变更后，期间又没有再做过高可用测试，这样的情况下，如果RAC一个节点所在的分区/服务器宕掉的时候，你是否依然可以拍着胸脯坚定的说，<em>“我的Oracle RAC其他的节点一定可以正常对外提供服务！“?</em></p>
<p>同样的问题，当多了一些话语的铺垫后，再次听到这个问题的你，答案是否又更加犹豫了呢？</p>
<p><strong>今天就为大家奉献一个"RAC高可用丢失"的真实案例其实完整、真实的分析过</strong> <strong>程。报告中将对Oracle数据库稳定运行的共性风险和隐患作出提醒。</strong></p>
<h2>分享本案例的意义：</h2>
<p>估计不少朋友的系统中依然还存在类似的问题，建议参考本案例进行细致检查,排除隐患。如果有朋友反馈查到类似的隐患，那么就没有浪费笔者的一片苦心了。</p>
<h2>本案例精彩看点：</h2>
<p>这个案例会有不小的难度，客户为了分析该问题发生的根因，投入了大量的人力和时间，一度没有结果。笔者接手该case后，在缺少信息的情况下，问题的分析也一度陷入僵局。不过最终还是通过反复梳理所有线索，从一个和数据库毫不相关的小细节中找到了突破口，成功定位到了问题原因，实属不易。</p>
<h1><strong>1 故障描述</strong></h1>
<p>现象：Oracle RAC高可用丢失。表现如下：</p>
<p>1）下午16点1分左右，XX系统数据库RAC集群节点2所在的P595 发生硬件故障，导致节点2 数据库所在的分区不可用。</p>
<p>2）但从16点1分开始，应用程序无法连接到数据库RAC集群中存活的节点1。</p>
<p>ORACLE数据库RAC集群未能发挥高可用架构的作用！</p>
<p>客户指示，务必找到问题根因，以便对系统高可用架构提出改进意见。</p>
<p>笔者很理解，出了这样的大事，对于一个运行着几百套RAC的数据中心而言，是一个巨大的风险，其他系统是否也还存在这样的问题？什么时候会再发生？如果不找到问题根因，又如何做到由点带面全面全面梳理、检查和预防呢？</p>
<p>所以，接到该case时，还是有很大压力的。在开始分析前，得到了以下信息：</p>
<p>1）故障时运维DBA在RAC存活节点1通过sqlplus “/as sysdba"连接到数据库挂起</p>
<p>2）故障时运维DBA在RAC存活节点1通过sqlplus -prelim “/as sysdba"连接到数据库挂起,加了-prelim参数连接到数据库也hang,这是很罕见的情况</p>
<p>3）在存活的节点1上通过 crsctl stop crs -f停止crs无法停止,命令挂起无法结束</p>
<p>4）在存活的节点1上通过shutdown –Fr重启操作系统，命令挂起无法结束，最终通过hmc重启分区，业务恢复正常</p>
<p>
 <br>
</p>
<h1><strong>2 分析过程</strong></h1>
<h2><strong>2.1 故障分析思路</strong></h2>
<p>集群中一个节点宕掉，其他节点无法对外提供服务。</p>
<p>通常情况下，是因为当中的集群软件没有完成对整个集群状态、数据的重组，
 <br>
 因此数据未达到一致，所以集群其他节点无法对外提供服务。</p>
<p>这个环境中部署在IBM小机上，其中用到的集群软件有：</p>
<p>ORACLE RAC/ORACLE CRS/IBM HACMP</p>
<p>因此，需要分别查看这三个集群是否完成了重组、重新配置</p>
<h2><strong>2.2 确认ORACLE RAC是否完成重组</strong></h2>
<p>查看一节点数据库alert日志,可以看到，RAC集群在16点1分32秒就完成了重组。</p>
<p>可排除该问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528933762.png&amp;size=m" alt=""></p>
<p><strong>2.3 确认ORACLE CRS是否完成重组</strong></p>
<p>可以看到，从16:01开始网络心跳超时，开始对节点2进行剔除的动作，最终在16:01:27节点2离开集群。可排除该问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528933883.png&amp;size=m" alt=""></p>
<h2><strong>2.4 确认IBM hacmp是否完成重组</strong></h2>
<p>经AIX专家分析，未发现HACMP中出现异常</p>
<p>整个检查下来，没有发现异常。</p>
<p>既然节点2数据库所在的Lpar宕了，那么节点2的vip应该会漂移到节点1上，接下来通过netstat –in命令检查，发现节点1上居然没有节点2飘过来的VIP ！</p>
<p>接管VIP的动作最终将由CRSD进程来完成，因此检查CRSD.log,以便查看是否在接管过程中发生了什么异常。</p>
<h2><strong>2.5 检查1节点crs日志确认节点2 vip的接管情况</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528934018.png&amp;size=m" alt=""></p>
<h2><strong>2.6 节点1 crs日志总结</strong></h2>
<h2>可以看到：</h2>
<p>1）节点2当掉后，节点1的CRS要把节点2的vip、db等资源接管过来，在节点1启动，但是调用脚本racgwarp来做check/start/stop时均出现了超时timeout,从而把子进程终止了。</p>
<p>2）并且节点1 本身自己的 vip检测中也出现了超时，如下所示</p>
<p>/oracle/app/oracle/product/10.2.0/crs/bin/racgwrap(check) timed out for ora.node1.vip! (timeout=60)</p>
<p>3） 因此，CRS在资源管理上也出现了一定的异常，主要是调用脚本racgwrap时出现了超时异常。</p>
<p>Racgwrap脚本出现timeout,通常是因为：</p>
<ul>
 <li>
  <p>操作系统性能缓慢，如内存大量换页</p>
 </li>
 <li>
  <p>脚本执行过程出现命令挂起的情况</p>
 </li>
</ul>
<h2><strong>2.7 检查nmon数据以获得操作系统性能</strong></h2>
<p>可以看到,5月20日的nmon居然停止在了故障时间点16点1分，这说明NMON执行到某个命令可能出现了挂起等异常</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528934202.png&amp;size=m" alt=""></p>
<p>另外，从监控软件来看，操作系统未出现内存、CPU的告警。</p>
<h2><strong>2.8 确定分析方向</strong></h2>
<p>那么，接下来，我们的分析重点到底是放在数据库还是操作系统层面呢？</p>
<p>通过上面的线索梳理，我们有理由相信：</p>
<p>操作系统在当时出现了某些异常!因此后续把分析的重点方向放在操作系统层面！</p>
<h2><strong>2.9 汇总并梳理所有线索</strong></h2>
<p>1）存活节点Sqlplus –prelim无法attach到共享内存</p>
<p>2）Kill -9 无法杀掉部分进程</p>
<p>进程处于一个原子级的调用，例如一个IO，必须在两个调用之间才可以接受进程终止的信号，说明某个原子调用无法结束导致无法被kill -9终止</p>
<p>3）CRS通过脚本无法接管故障节点vip,出现超时</p>
<p>4）CRS通过脚本无法检测存活节点本身的vip/监听等资源，均出现超时</p>
<p>5）存活节点Nmon故障点后无输出</p>
<h2><strong>2.10 问题分析一度陷入僵局</strong></h2>
<p>虽然把方向放在操作系统上，但AIX专家未检查到异常。</p>
<p>AIX专家的结论是操作系统当时是没有异常的！</p>
<p>因为他们检查了一些crontab的脚本，是有输出的，说明OS还在工作。</p>
<p>如下图所示</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528934277.png&amp;size=m" alt=""></p>
<p><strong>2.11 如何找到突破口</strong></p>
<p>笔者把方向定到了操作系统，但是操作系统专家检查并否认操作系统有异常。</p>
<p>对于存活节点nmon为什么停止写入的问题，双方持不同意见。</p>
<p>至此，问题分析陷入僵局，该如何继续往下分析呢？怎么能证明操作系统的异常呢？</p>
<p>1)如果没有给到操作系统一个明确的点，那么操作系统是很难查到到底有哪些异常的问题</p>
<p>2)问题分析陷入僵局，该如何找到突破口成为关键</p>
<p>3)坚信操作系统异常这个方向是正确的</p>
<p>回到原点，重新梳理和验证每个线索，会不会漏掉了什么重要线索</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528934412.png&amp;size=m" alt=""></p>
<p>重新检查之前的线索，有重大发现！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528934556.png&amp;size=m" alt=""></p>
<p>可以看到：</p>
<p>从16点02分以及后续的的采样可以看到，到了” The file system result is as follows"的检测就停止了，没有写SYSTEM.SH_RUN_COMPLETE关键字来表示脚本执行完成.这说明操作系统在执行非数据库命令时也遇到了异常！</p>
<h2><strong>2.12 确认shell脚本出现异常时调用的具体命令</strong></h2>
<p>检查shell脚本,发现脚本挂在” The file system result is as follows:“的地方，事实上是调用了df命令来查看文件系统。</p>
<p>那么这个操作在什么情况会出现HANG的情况呢。</p>
<p>答案是使用nfs文件系统的时候。</p>
<p>XX系统数据库集群中使用了NFS文件系统，将节点2的/arch2文件系统通过NFS挂载到了节点1的/arch2文件系统上。当节点2出现硬件故障后，导致节点1无法与节点2的nfs server通讯，继而导致节点1上，df命令查看文件系统时挂起。</p>
<p>由此来看，节点1 nmon数据停止的原因是df命令hang住了</p>
<p>但是这和节点1无法连接数据库又有什么关系呢？</p>
<p>当笔者看到df命令时，泪奔了！</p>
<p>所有现象都可以得到解释了！当所有现象都得到解释的时候，心里就踏实了！这意味着你找到了问题的根因，那么预防措施就万无一失了！</p>
<h2><strong>2.13 Nfs mount点丢失和无法连接数据库的关系</strong></h2>
<p>执行连接数据库操作时，需要获取当前工作目录(pwd)</p>
<p>但是由于AIX某些版本操作系统内部实现pwd过程的缺陷,导致必须递归到根目录/下检查目录或文件的权限、类型.</p>
<p>当检查到nfs时，由于nfs 以hard/background方式挂载，当nfs server不可用时，必然导致检查nfs目录时出现挂起，继而导致了无法获得pwd的输出结果，继而导致无法连接数据库!</p>
<h2><strong>2.14 解开所有谜团</strong></h2>
<p>1） 存活节点Sqlplus –prelim无法attach到共享内存
 <br>
 获取当前工作目录时，由于nfs mount点丢失，get_cwd(pwd)挂起</p>
<p>2） Kill -9 无法杀掉部分进程
 <br>
 进程对nfs mount点进行IO操作，挂起，因此没有机会收到进程终止信号</p>
<p>3） CRS通过脚本无法接管故障节点vip,出现超时
 <br>
 Racgwrap脚本中调用了pwd命令</p>
<p>4） CRS通过脚本无法检测存活节点本身的vip/监听等资源，均出现超时
 <br>
 Racgwrap脚本中调用了pwd命令</p>
<p>5） 存活节点Nmon故障点后无输出
 <br>
 Nfs mount点丢失导致nmon调用df命令时挂起</p>
<h2><strong>2.15 再往前一步的分析</strong></h2>
<p>1.该机制下，如果根目录/小文件或目录很多，则pwd(get_cwd)的性能会很差</p>
<p>2.测试环境重现过程</p>
<p>节点2 mount /testfs到节点1的/testfs,停止节点2 nfs服务，未能重现。原因在于pwd的输出为/oracle, 首字母o比t要小，因此未检测到/testfs就获得pwd结果而退出了</p>
<p>节点2 mount /aa到节点1的/aa,停止节点2 nfs服务，未能重现。原因是通过truss命令对比，发现生产和测试环境检索/根目录下的行为不一致，继而检查OS版本，发现测试环境OS版本较高</p>
<p>3.高版本的OS无法重现，低版本的操作系统可以重现，说明操作系统做了修正和增强，在ibm.com上已nfs hang搜索，可以发现IBM发布了APAR来修复该问题</p>
<p>
 <br>
</p>
<h1><strong>3 原因总结和建议</strong></h1>
<h2><strong>3.1 原因总结</strong></h2>
<p>RAC集群节点2所在的P595 发生硬件故障，导致节点2 LPAR不可用。继而导致Nfs mount点丢失。</p>
<p>登陆数据库时，需要获取当前工作目录(pwd)。</p>
<p>但是由于AIX某些版本操作系统内部实现pwd过程的缺陷,导致必须递归到根目录/下检查目录或文件的权限、类型。</p>
<p>当检查到nfs挂载点目录/arch2时，由于nfs 以hard/background方式挂载，当nfs server不可用时，必然导致检查nfs目录时出现挂起，继而导致了无法获得pwd的输出结果，继而导致无法连接数据库!</p>
<p>以hard/background挂载的NFS server丢失是导致RAC成为伪集群的根本原因！</p>
<p>所有故障现象都可以得到解释，如下：</p>
<p>1）存活节点Sqlplus –prelim无法attach到共享内存</p>
<p>获取当前工作目录时，由于nfs mount点丢失，get_cwd(pwd)挂起</p>
<p>2）Kill -9 无法杀掉部分进程</p>
<p>进程对nfs mount点进行IO操作，挂起，因此没有机会收到进程终止信号</p>
<p>3）CRS通过脚本无法接管故障节点vip,出现超时</p>
<p>Racgwrap脚本中调用了pwd命令</p>
<p>4）CRS通过脚本无法检测存活节点本身的vip/监听等资源，均出现超时</p>
<p>Racgwrap脚本中调用了pwd命令</p>
<p>5）存活节点Nmon故障点后无输出</p>
<p>Nfs mount点丢失导致nmon调用df命令时挂起</p>
<h2><strong>3.2 问题解决方案和建议</strong></h2>
<p>1） 如果需要实用nfs,则mount到二级目录，比如mount到/home /arch2而不是/arch2</p>
<p>2） 使用gpfs代替nfs</p>
<p>3） 提供故障线索时，请勿根据个人经验过滤掉认为不重要的信息</p>
<p>4） 安装AIX APAR，改变操作系统get_cwd(pwd)调用的内部实现方式</p>
<p>5）处理故障时Df命令hang的情况如果一开始就反馈，那么这个case直接就解开了，就不需要查了！</p>
<p>6）只在上线前做高可用测试是不够的，因为上线后系统会经历一系列的变更，可能某些因素会报纸RAC冗余性丢失，建议定期做高可用测试，可以选择在变更窗口选择逐个重启实例、服务器的方式来进行验证。</p>
<p>
 <br>
</p>
<blockquote>
 <p>原题：我和数据中心的故事——Oracle RAC高可用失效风险提醒</p>
 <p>作者：黄远邦（曾用笔名小y）,就职于北京中亦安图科技股份有限公司，任数据库团队技术总监。长期活跃于国内多家银行总行生产数据中心，提供Oracle第三方服务，解决过无数疑难问题，在业内具有极佳的口碑。</p>
 <p>本文地址：http://www.aixchina.net/Article/177843</p>
</blockquote>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>长按下图二维码关注"AIX专家俱乐部"公众号</p>
<p>也可以直接搜索公众号名称"AIX专家俱乐部"或微信号"AIXChina"关注</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528934666.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650626355&amp;idx=1&amp;sn=2d25935ca00140e9305890441768a281&amp;chksm=bef927b5898eaea370dac5f2497855655ede08fa49d1a34a6615cc4329ed0ba28c3338c0e3aa&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/136266dd-c2e1-47f6-ae8e-3c4045b9efa4</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:51:14 GMT</pubDate></item><item><title><![CDATA[OLTP 下的 DB2 PureScale 与 Oracle RAC 对比]]></title><link>https://wiki.dgfygk.com/archives/c8db00fc-130c-4bf8-b195-757ca4fb593f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=OLTP%20%E4%B8%8B%E7%9A%84%20DB2%20PureScale%20%E4%B8%8E%20Oracle%20RAC%20%E5%AF%B9%E6%AF%94&amp;url=/archives/c8db00fc-130c-4bf8-b195-757ca4fb593f" width="1" height="1" alt="" style="opacity:0;">
<p>以下内容来自"双活数据中心建设系列之——基于软件架构的双活数据中心建设方案深入探讨”，仅代表社区会员个人观点，供大家参考之用。</p>
<p>
 <br>
</p>
<p><strong>在OLTP并行DB的解决方案中，DB2 PureScale 和ORACLE RAC是很好的选择，但是该如何选择呢？他们的优势和不足都体现在哪？</strong></p>
<p>
 <br>
</p>
<p>funtest 邮储银行合肥数据中心 数据库管理员</p>
<p>我不懂DB2，只从oracle的角度上来说一下吧。</p>
<p>1、实质上，oracle推出RAC（早期叫ops），强调的是其高可用性，而不是可伸缩性。要充分发挥RAC的性能，应用必须针对RAC的特点进行编程（数据分割及数据路由），否则性能反而下降。远的说，01-02年，那时候江西绿卡系统用的是oracle 7 OPS，在结息的时候都是跑单实例；近期的，象双11，我们的某些系统也是单实例在跑（11.2.0.4 rac+asm）</p>
<p>2、在rac环境下，之前在单实例上不是问题的部分也会引起性能问题，最典型的就是gc类等待事件了，等待从行级锁放大到块级等待，象大集中期间的原型性能压测期间，为了规避一个尾箱表引起的gc等待事件，我们把此表的pctfree放大到99以保证一个数据块上只保留一条记录。</p>
<p>3、rac环境中虽然实例是多个，但存储是单点，如果想增加节点来提高性能的话，只适用于cpu密集型的应用（前提是针对rac的特点编程），如果是io密集型的应用，节点越多估计死得越快。</p>
<p>4、无论是单机还是rac，要特别注意lgwr的性能优化，gc类的等待事件中，log file sync是其中的一步，lgwr对gc的影响是指数级的，我们行的某系统在做灾备切换时，主库与备库基本一致，但备库的性能与主库始终差20%，后面是把备库的logfile member去掉就OK了。</p>
<p>
 <br>
</p>
<p>tianshizuoyi IBM 数据库架构师</p>
<p>我从两个最重要的方面评测：</p>
<ol>
 <li>高可用或者节点故障导致的恢复时间</li>
</ol>
<p>PureScale采用独立的CF节点管理全局的缓存，当节点发生故障时只对需要恢复的也没I/O发生阻塞，而RAC采用分布式的缓存管理，所有的请求会被短暂的冻结，在繁忙的OLTP系统中会造成大量SESSION阻塞。</p>
<ol>
 <li>ScaleOut水平扩展性</li>
</ol>
<p>PureScale支持RDMA快速的访问和更新LOCK,BUFFERPOOL等，100节点性能损耗也就20%左右。而RAC也可以使用IB网，但是不支持RDMA，分布式的缓存/锁，随着节点数的增加，节点间通信的开销将会非常大，这也就是很少看到4节点以上RAC的原因了。</p>
<p>结论：PureScale作为后起之秀，在软件设计架构，各方面性能上都要优于Oracle Rac。</p>
<p>
 <br>
</p>
<p>liulei_oracle lgcns china 数据库管理员</p>
<p>如果db2想赶超oracle rac请去掉许可证的约束，谢谢。</p>
<p>
 <br>
</p>
<p>hackergodness ECT 数据库管理员</p>
<p>从搭建、维护角度来看，purescale要比RAC简单</p>
<p>
 <br>
</p>
<p>孙伟光 中国金融电子化公司 IT顾问</p>
<p>再好的技术没有市场也不行.</p>
<p>
 <br>
</p>
<p>jxufe 新云东方 系统工程师</p>
<p>rac商业上更成功</p>
<p>
 <br>
</p>
<p>libai21 海通证券 软件架构设计师</p>
<p>我认为在选择方案的时候，要根据自己的需要出发，看看自己目前的架构是否有什么不足，有什么无法通过完善现有方案解决的问题，然后考虑新的方案能否解决这个问题。</p>
<p>DB2 PureScale和Oracle Rac解决的最主要的两个问题：一是解决单个物理服务器的可靠性的问题，即如果单机发生物理故障，不影响系统的可用性；二是物理机器的横向扩展问题，即通过增加物理机器的数量来提升系统的处理能力。</p>
<p>这个架构没有解决的问题：存储的单点故障问题。当然这个可以通过方案扩展来解决，比如DB2 GDPC，HADR，GPFS复制等。</p>
<p>这个架构的缺点是什么呢？首先是是架构更加复杂，增加了成本和运维的复杂性；其次损失了部分性能。</p>
<p>我觉得比较这两个方案的优劣意义不大。因为选型上技术因素不大。传统的关系型数据库发展到今天，都是成熟的解决方案。不用太纠结。</p>
<p>
 <br>
</p>
<p>邓毓 江西农村信用社联合社 系统工程师</p>
<p>总结一下：</p>
<p>DB2 PS相比ORACLE RAC主要有三个方面的优势：</p>
<p>1.性能扩展上：与Oracle RAC所不同的是，DB2 Purescale采用了CF来提供集中化的锁管理和全局缓存，而Oracle RAC采用的分布式锁管理和分布式缓存。由于分布式管理，RAC随着集群节点的增多，在极端情况下，多节点间的的通讯协调复杂性将指数型增长，虽然Oracle也可以将InfiniBand应用于Oracle RAC集群的多节点通信，但是 Oracle RAC没有利用 RDMA技术，那么意味着 Oracle RAC节点通信将使用速度较慢的套接字协议，并且需要一些开销较大的 CPU 中断，这样会大大降低通信处理效率。而DB2 PS采用RMDA技术，成员通过INFINIBAND直接访问CF的内存。大大提高通信效率。</p>
<p>2.应用透明性上：为了保持Oracle RAC的性能，通常是通过数据库分区的方式来避免热页面，通过业务分割的方式将不同的应用分布在不同的节点上的方式来减少缓存融合的频度。这样对应用来说，应用跑的Oracle节点固定化了，Oracle节点的伸缩，应用的配置也需要相应变更，所有这些都会造成管理和开发成本增加。而DB2 Purescale不需要通过应用或数据分区来实现可伸缩性，增加或减少成员节点时，不会让应用集群感知到，也无需对应用进行修改，同时这也极大地降低了管理和应用开发成本。</p>
<p>3.持续可用性上：ORACLE在成员故障时候，处理过程复杂，并且所有页面将冻结，而DB2 PureScale数据库成员出现故障时，不需要在集群中进行全局冻结，只有动态数据保持锁定，直到成员恢复完成。集群中的所有其他成员可以继续处理事务和执行I/O操作。只有对需要恢复的页面的访问请求会在故障成员的恢复流程完成之前被阻塞。另外故障恢复时，由于Oracle RAC是日志恢复，根据故障节点在出现故障时正在进行的工作量，整个恢复过程可能会花费几十秒到几分钟不等的时间；而DB2 pureScale是内存恢复，所以DB2 PureScale恢复时间比RAC短很多，动态数据在线恢复的目标时间小于20秒。</p>
<p>ORACLE RAC的优势：</p>
<p>Oracle RAC也有自身强大之处，自从发布之后，Oracle RAC同时解决了客户对于性能扩展和高可靠性的要求，多年来没有匹配的对手，成熟度和稳定性高，功能强大，可运行的环境多样，兼容性强，架构相对简单等；</p>
<p>DB2 PureScale也存在一定的不足和缺陷，如DB2 Purescale需要独立的两个CF节点资源；为了保证足够的性能，需要独立的InfiniBand卡和相应的交换机，并且目前只能通过GPFS才能实现磁盘共享，整体软硬件要求较高；整体架构的复杂度也较高；虽然说现在也可以运行在X86的Linux平台上，但对Linux平台操作系统版本的兼容性不够，更多的是定位于高端数据库，采用UNIX平台居多，而IBM的Power平台是Unix中的第一选择，存在一定的局限性；DB2 PureScale的CF重要性太高，即使DB2成员节点的数量很多，但CF故障一个就会造成一小段时间的中断，故障两个就会造成数据库集群全部中断。</p>
<p>
 <br>
</p>
<blockquote>
 <p>点击"阅读原文”，即可浏览"双活数据中心建设系列之——基于软件架构的双活数据中心建设方案深入探讨"交流更多内容，或加入相关交流</p>
</blockquote>
<p>长按下图二维码关注"AIX专家俱乐部"公众号</p>
<p>也可以直接搜索公众号名称"AIX专家俱乐部"或微信号"AIXChina"关注</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528932933.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650626850&amp;idx=3&amp;sn=72c05ce386141af3cdaf2f8a3a28e451&amp;chksm=bef925a4898eacb29b4b40628d8a7f990158786a75d736af00b7069d255190a08bcf9837ca44&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/c8db00fc-130c-4bf8-b195-757ca4fb593f</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:49:53 GMT</pubDate></item><item><title><![CDATA[由重启引起的Oracle RAC节点宕机分析及追根溯源]]></title><link>https://wiki.dgfygk.com/archives/3f85536e-4a68-4c9b-a50d-dbb11962fdbc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%94%B1%E9%87%8D%E5%90%AF%E5%BC%95%E8%B5%B7%E7%9A%84Oracle%20RAC%E8%8A%82%E7%82%B9%E5%AE%95%E6%9C%BA%E5%88%86%E6%9E%90%E5%8F%8A%E8%BF%BD%E6%A0%B9%E6%BA%AF%E6%BA%90&amp;url=/archives/3f85536e-4a68-4c9b-a50d-dbb11962fdbc" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>由公众号DBAplus社群（ID：dbaplus）授权转载</p>
</blockquote>
<p>
 <br>
</p>
<p><strong>作者介绍</strong></p>
<p>
 <br>
</p>
<p><strong>裴征峰</strong> ，现就职于北京海天起点，二线专家成员，南京办事处负责人，OCP 10g、OCP 11g、OCM11g。超八年Oracle服务经验，擅长数据库故障诊断和性能调优。目前主要从事客户的现场维护、重大问题的解决、数据库性能分析、二线服务质量保证等工作。</p>
<p>
 <br>
</p>
<p>1 <strong>背景说明</strong></p>
<p>
 <br>
</p>
<p>某省份的电信业务系统由于业务量较大，按地市划分部署在4套配置相同的RAC上，相同主机版本，相同的CRS和数据库版本。该系统已正常运行3年多，其间也有重启主机等正常维护操作。从4月24日 开始，这个系统的4套RAC的节点，一个接一个地宕机重启，但每次都不是同一个节点。整理的宕机情况列表如下：</p>
<p>
 <br>
</p>
<p>系统 宕机时间</p>
<hr>
<p>xx1db01 04-24</p>
<p>xx1db02</p>
<p>xx2db01 07-30</p>
<p>xx2db02 07-23</p>
<p>xx3db01 08-19</p>
<p>xx3db02 07-27</p>
<p>xx4db01</p>
<p>xx4db02</p>
<p>
 <br>
</p>
<p>这4套RAC是3年前安装的，平时运行非常稳定，基本没有问题，从4月份开始有节点宕机，并且在7，8月份宕机频率非常高。但有点比较奇怪，每次宕机都是不同的节点，没有相同的节点发生宕机。由于配置了业务隔离，所以RAC宕一个节点，对业务影响不大，但是如此大规模的节点宕机，肯定有一些共性的问题。</p>
<p>
 <br>
</p>
<p>每次宕机基本为ocssd.bin进程出错，直接重启节点。ocssd.bin报错的日志基本为：</p>
<p>
 <br>
</p>
<p>clssscExit: CSSD signal 11 in thread GMClientListener</p>
<p>
 <br>
</p>
<p>这4套RAC的配置如下：</p>
<p>
 <br>
</p>
<ul>
 <li>
  <p>主机版本：HP-UX B.11.31 U ia64</p>
 </li>
 <li>
  <p>数据库CRS版本为：10.2.0.5.0（未打PSU）</p>
 </li>
 <li>
  <p>数据库版本为：10.2.0.5.8（PSU号：13923855）</p>
 </li>
 <li>
  <p>安装了Veritas Storage Foundataion，使用VCS集群文件系统。</p>
 </li>
</ul>
<p>
 <br>
</p>
<p>2 <strong>4月24日宕机分析过程</strong></p>
<p>
 <br>
</p>
<p>4月24日，xx1db01节点宕机，这是这套业务系统的第一次节点宕机。由于没有安装OSWatch工具，无法得知宕机时操作系统资源情况。</p>
<p>
 <br>
</p>
<p>检查操作系统日志，没有发现报错信息。这可能是系统直接重启时，还没有来得及把内存中的信息刷到磁盘。检查数据库的alert日志，宕机重启时数据库实例没有异常信息。</p>
<p>
 <br>
</p>
<p>检查ocssd.log，发现如下报错：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528935442.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>clssscExit：</strong>CSSD signal 11 in thread这个报错以前没有碰见过。</p>
<p>
 <br>
</p>
<p><strong>对这个报错的相关解释：</strong>当RAC GM client监听线程在处理"clsc_disc_orphans"时，CSSD.LOG中会出现"clsc_disc_orphans"的信息，该函数在处理clsc_disc尝试断开连接时，负责获得和持有线程信息。存在BUG(Bug 9132429: LNX64-10205-CRS:NODE CRASH AFTER 5 MINUTES OF HANG/RESUME OCSSD.BIN。)可能导致多个session形成死锁，最终导致节点HANG住或被驱逐宕机。</p>
<p>
 <br>
</p>
<p>MOS上的这个Bug 9132429是在Linux平台上，当前主机是HP-UX。请网络组检查心跳交换机，没有发现闪断或者其它异常。由于没有检查到有用的信息，认为这可能是ocssd.bin进程的偶然报错现象，暂时没法解释这个问题。</p>
<p>
 <br>
</p>
<p>3 <strong>7月23日宕机分析过程</strong></p>
<p>
 <br>
</p>
<p>07月23日，xx2db02发生宕机，检查结果和4月23日一样，其它全部没有信息，只有ocssd.log日志信息如下：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528935531.png&amp;size=m" alt=""></p>
<p>经过确认，主机重启是由于ocssd的GMClientListener问题导致。由于未在主机上安装系统资源监控工具，没有有效的主机资源使用情况。在MOS上了开SR，SR回复可能是主机资源使用存在问题，但没有OSW的信息，他们给不出解释。</p>
<p>
 <br>
</p>
<p>4 <strong>7月27日宕机分析过程</strong></p>
<p>
 <br>
</p>
<p>7月27日，xx3db02宕机，Oracle Support认为在ocssd.log中存在Authentication OSD error信息，可能是认证失败导致GMClientListener发生问题，进而cssd.bin进程宕掉。相关日志信息如下：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528935614.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>认证信息失败的原因，可能有以下几点：</p>
<p>
 <br>
</p>
<p>1、节点间有防火墙 【没有配置】</p>
<p>2、节点间有authentication tools 【未配置】</p>
<p>3、$ORA_CRS_HOME/crs/css目录权限发生改变 【未发生】</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528935696.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>4、/oracle文件系统满 【未满】</p>
<p>5、/tmp目录下的.oracle目录被删除 【未删除】</p>
<p>6、节点间认证信息网络包发生问题 【无证据】</p>
<p>
 <br>
</p>
<p>而前两次宕机，没有Authentication OSD error的信息，所以没有直接证据表明Authentication OSD error造成了CSSD signal 11 in thread GMClientListener，Authentication OSD error错误信息也可能是结果，不是根本原因。</p>
<p>
 <br>
</p>
<p>监控系统上主机资源使用情况如下，CPU使用率：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528935747.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>IO使用率：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528935825.png&amp;size=m" alt=""></p>
<p>SR认为监控粒度太粗。给这4套RAC、8个节点全部安装了OSWatch，下次如果有其它节点宕机就有详细的操作系统资源使用信息。</p>
<p>
 <br>
</p>
<p>5 <strong>7月30日宕机分析过程</strong></p>
<p>
 <br>
</p>
<p>7月30日，xx2db01宕机，日志信息如下：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936007.png&amp;size=m" alt=""></p>
<p>发生"clsc_event_hndlr: (6000000000ae51b0) answer error, rc 15”，Oracle认为是操作系统发生了问题。</p>
<p>
 <br>
</p>
<p><strong>检查ulimit信息：</strong></p>
<p>
 <br>
</p>
<p>root@xx2db01:/#ulimit -a</p>
<p>time(seconds) unlimited</p>
<p>file(blocks) unlimited</p>
<p>data(kbytes) 4194300</p>
<p>stack(kbytes) 392192</p>
<p>memory(kbytes) unlimited</p>
<p>coredump(blocks) 4194303</p>
<p>nofiles(descriptors) 20480</p>
<p>root@xx2db01:/#su - oracle</p>
<p>oracle@xx2db01:/oracle#ulimit -a</p>
<p>time(seconds) unlimited</p>
<p>file(blocks) unlimited</p>
<p>data(kbytes) 4194300</p>
<p>stack(kbytes) 392192</p>
<p>memory(kbytes) unlimited</p>
<p>coredump(blocks) 4194303</p>
<p>nofiles(descriptors) 20480</p>
<p>
 <br>
</p>
<p><strong>检查file相关的内核参数：</strong></p>
<p>
 <br>
</p>
<p>oracle@xx2db01:/oracle#/usr/sbin/kctune \|grep file</p>
<p>fcache_seqlimit_file 100 Default Immed</p>
<p>filecache_max 130470211584 Default Auto</p>
<p>filecache_min 13047017472 Default Auto</p>
<p>max_acct_file_size 2560000 Default Immed</p>
<p>maxfiles 20480 20480</p>
<p>maxfiles_lim 20480 20480 Immed</p>
<p>
 <br>
</p>
<p><strong>检查nproc内核参数：</strong></p>
<p>
 <br>
</p>
<p>oracle@xx2db01:/oracle#/usr/sbin/kctune \|grep nproc</p>
<p>nproc 30000 30000 Immed</p>
<p>oracle@xx2db01:/oracle#/usr/sbin/kctune nfile</p>
<p>Tunable Value Expression Changes</p>
<p>nfile 0 Default Immed</p>
<p>
 <br>
</p>
<p>SR认为这些参数都正常。上次故障时，在所有节点上都安装了OSW工具，这次有了相关主机资源信息。经过检查，vmstat中显示：从7月30号的08:42到宕机时的09:34，一直存在pi的情况，而空闲内存为11g左右。xx2db01主机是256G内存，sga_max_size=100g，pga_aggregate_target=40g。</p>
<p>
 <br>
</p>
<p>宕机时vmstat信息如下，空闲内存为11g，有一些内存页交换。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936094.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>而正常情况下xx2db01主机的空闲内存是100g，pi基本为0。</p>
<p>
 <br>
</p>
<p>经过检查，内核参数filecache_max 130470211584 Default Auto这个参数表明主机允许文件缓存最大为130g左右，该参数设置较高，有可能占用大部分内存，但是OSWatch没有监控这个参数，所以宕机时无文件系统缓存的相关信息。当前使用了Veritas ODM，基本用不到操作系统缓存，建议把该参数调整到10g。</p>
<p>
 <br>
</p>
<p>在宕机时的ps输出文件中存在大量的sendmail和rmail进程，但具体不清楚是什么作用。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936185.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936274.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>这次故障的原因，猜测是主机swap造成，但从vmstat中监控到主机空闲内存还有11g左右（主机内存是256g），不怎么确认一定是主机内存耗尽导致ocssd.bin出现故障。</p>
<p>
 <br>
</p>
<p><strong>暂时建议：</strong></p>
<p>
 <br>
</p>
<p>filecache_max调整到10g左右，减小文件系统占用内存导致宕机的概率。</p>
<p>调整CSSD的日志级别为4，让cssd出故障时，输出更多的日志信息。</p>
<p>crsctl debug log css CSSD:4</p>
<p>
 <br>
</p>
<p>6 <strong>8月19日宕机分析过程</strong></p>
<p>
 <br>
</p>
<p>8月19日，xx3db01节点也是因为"CSSD signal 11 in thread GMClientListener"发生宕机重启。vmstat查看宕机时内存使用情况，发现宕机时空闲内存有将近80g，这又排除了内存紧张，主机swap导致的问题。</p>
<p>通过详细检查OSWatch日志，发现了宕机时ocssd.bin内存占用达到8g。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936433.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>跟SR交流，SR建议以下ulimit设置为unlimited，如下：</p>
<p>
 <br>
</p>
<p>data(kbytes) unlimited</p>
<p>stack(kbytes) unlimited</p>
<p>
 <br>
</p>
<p>我检查了7月30日宕机时，ocssd.bin的大小，发现也是8g左右。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936484.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>怀疑是不是进程所使用的内存达到限制导致，这时侯我咨询了主机工程师，是否有内核参数，限制一个进程能够使用的内存大小，查到如下参数：</p>
<p>
 <br>
</p>
<p>root@xx3db01:/ # kctune \| grep maxdsiz</p>
<p>maxdsiz 4294963200 4294963200 Immed</p>
<p>maxdsiz_64bit 8589934592 8589934592 Immed</p>
<p>
 <br>
</p>
<p><strong>这两个参数解释如下：</strong></p>
<p>maxdsiz、maxdsiz_64bit，是指任何用户进程的数据段的最大大小（以字节为单位）。HP-UX 系统上的用户程序由五个不连续的虚拟内存段组成：文本（或代码）、数据、堆栈、共享的和 I/O。每个段都占用一段已设定大小上限的结构化定义的虚拟地址空间范围，但由于 maxtsiz、maxdsiz 和 maxssiz 可调参数的强制约束，文本、数据段和堆栈段的最大值可能会略小。此可调参数定义了 32 位和 64 位进程的静态数据存储段的最大大小。数据存储段包含了固定的数据存储，例如使用 sbrk() 和 malloc() 在main()、字符串和空间中分配的全局变量、数组变量、静态变量和局部变量。</p>
<p>
 <br>
</p>
<p>检查其它HP-UX上的10g库，发现10.2.0.4的RAC，ocssd.bin内存占用正常，而所有10.2.0.5版本的ocssd.bin内存占用在缓慢增加。</p>
<p>
 <br>
</p>
<p>检查未发生过宕机的xx4db02节点上ocssd.bin进程一段时间的内存使用情况，确认存在内存泄露问题。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936623.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>猜想可能是内存泄露导致了ocssd.bin内存占用过高，达到操作系统限制，造成ocssd.bin出现问题，进而宕机重启。经过到MOS上检查，发现如下Bug：</p>
<p>
 <br>
</p>
<p>Memory usage of crsd.bin and ocssd.bin keeps growing after upgrade to 10.2.0.5 (文档 ID 1633236.1)</p>
<p>Bug 11704113 - OVER TIME THE MEMORY USAGE OF THE CRSD.BIN PROCESS GROWS TO VERY LARGE VALUES</p>
<p>
 <br>
</p>
<p>SR也确认是由于crsd.bin的nsdo()函数发生内存泄露，导致ocssd.bin受影响。当前情况与MOS文档上描述一致。</p>
<p>
 <br>
</p>
<p>7 <strong>总结</strong></p>
<p>
 <br>
</p>
<p>问题的根本原因是，由于存在如下Bug：</p>
<p>
 <br>
</p>
<p>Memory usage of crsd.bin and ocssd.bin keeps growing after upgrade to 10.2.0.5 (文档 ID 1633236.1)</p>
<p>Bug 11704113 - OVER TIME THE MEMORY USAGE OF THE CRSD.BIN PROCESS GROWS TO VERY LARGE VALUES</p>
<p>
 <br>
</p>
<p>导致ocssd.bin进程的内存使用率持续上升，达到了maxdsiz_64bit内核参数的限制，进而造成ocssd.bin进程故障，ocssd.log日志中的"clssscExit: CSSD signal 11 in thread GMClientListener"信息，signal 11代表内存申请失败。</p>
<p>
 <br>
</p>
<p><strong>解决办法有两个：</strong></p>
<p>
 <br>
</p>
<ol>
 <li>
  <p>打CRS补丁11704113</p>
 </li>
 <li>
  <p>由于内存泄露缓慢，可能要半年到一年左右，ocssd.bin的内存使用才能达到限制，并且只有在特定的版本和平台上存在这个问题，可以监控一下ocssd.bin进程的内存使用率，在快达到限制前，手工重启下CRS。</p>
 </li>
</ol>
<p>
 <br>
</p>
<p>这个问题非常之隐蔽，会被很多因素遮盖掉，因为在1年时间之内，主机很可能因为维护、硬件故障等造成了主机重启，而重启主机后CRS的ocssd.bin进程的内存就释放了，又要经过长时间的内存泄露才会触发。如果没有多套RAC的长时间稳定运行，宕机后操作系统资源使用数据的相互交叉比对，很难诊断出根本原因。</p>
<p>
 <br>
</p>
<p>长按下图二维码关注"AIX专家俱乐部"公众号</p>
<p>也可以直接搜索公众号名称"AIX专家俱乐部"或微信号"AIXChina"关注</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528936708.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650625095&amp;idx=1&amp;sn=470e7261441d240289cce770ae97c6c6&amp;chksm=bef92ac1898ea3d7ae5ec67fd149aba3be9cfd43ae5ccc7a483aadd50b35b4e2a8d3865f92ad&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/3f85536e-4a68-4c9b-a50d-dbb11962fdbc</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:49:07 GMT</pubDate></item><item><title><![CDATA[RAC集群搭建ASM和私网网卡问题分析 _ 过程悲催幸好结局圆满]]></title><link>https://wiki.dgfygk.com/archives/53e78e25-a1cb-46a3-b96a-03cb8683c027</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=RAC%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BAASM%E5%92%8C%E7%A7%81%E7%BD%91%E7%BD%91%E5%8D%A1%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90%20_%20%E8%BF%87%E7%A8%8B%E6%82%B2%E5%82%AC%E5%B9%B8%E5%A5%BD%E7%BB%93%E5%B1%80%E5%9C%86%E6%BB%A1&amp;url=/archives/53e78e25-a1cb-46a3-b96a-03cb8683c027" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>作者：包雷，数据库主管，擅长oracle mysql组建搭建、故障处理、sql优化，专精高可用方案。</p>
</blockquote>
<p>
 <br>
</p>
<p><strong>一、项目背景</strong></p>
<p>
 <br>
</p>
<p>此项目所有主机资源、网络环境、存储资源都是由XX方提供，都是在云上资源的划分，本文分析XX方一系列问题导致搭建集群失败，后续经过调整，集群成功搭建的过程。</p>
<p>在搭建前的准备和搭建中一共遇到了两个很重要的问题，在此分享给大家，以后如果有需要搭建集群环境的项目应该注意。</p>
<ul>
 <li>
  <p><strong>单块ASM磁盘不能超过2T大小，即裸LUN的分配要注意不超过2T</strong></p>
 </li>
 <li>
  <p><strong>私网网卡在虚机上配置不要加地址绑定限制和HAIP的相关问题</strong></p>
 </li>
</ul>
<p>
 <br>
</p>
<p><strong>二、ASM磁盘的BUG</strong></p>
<p>
 <br>
</p>
<p>刚到项目，进行环境检查时发现两个节点挂载的是一块15T的裸LUN，就想到了去年搭建某市教育云时遇到的ASM磁盘组的BUG，这些细节地方确实前期很容易被忽视。</p>
<p>早期的集群是通过各存储厂家的存储技术实现的，直到10G之后，oracle公司研究出ASM磁盘的技术，将集群技术进行统一。</p>
<p>ASM是一个卷管理器，Oracle数据库文件的一个文件系统，ASM支持单实例和集群配置，它是oracle推荐的存储方案，为传统卷管理器，文件系统，裸设备提供一个选择。</p>
<p><strong>2.1 每块ASM磁盘不大于2T</strong></p>
<p>ASM磁盘有一定限制：单块ASM的磁盘不能大于2T，不然创建磁盘组就会报错ORA-15196 WITH ASM DISKS LARGER THAN 2TB。</p>
<p>联系XX云平台的相关人员进行处理，其反馈了两个问题：</p>
<ul>
 <li>
  <p>15T裸LUN得重新划分需要先将其格式化，15T大小的磁盘格式化时间大约8个小时；</p>
 </li>
 <li>
  <p>15T的裸LUN要划分成每块盘不大于2T，至少需要7个LUN来进行划分，但是目前的云平台是不支持划分这么多数量的，无法保证云平台的稳定。</p>
 </li>
</ul>
<p>由于ASM磁盘的限制问题，和XX方表示，LUN的重新划分必须要进行，后进行沟通，按照我们提出的需求XX方进行处理，但是由于前期对资源没沟通到位，确实对项目的推进效率造成了一定的影响。</p>
<p><strong>2.2 ASM - Scalability and Limits</strong></p>
<p><em>参考MOS文档：文档 ID 370921.1</em></p>
<p>ASM磁盘共有如下一些限制：</p>
<p>1、 63 disk groups in a storage system</p>
<p>代表一个存储系统最多只能有63各磁盘组，但是一般我们只需要3到四个磁盘组即可：CRS、ARCH、DATA。</p>
<p>2、 10,000 ASM disks in a storage system</p>
<p>代表一个存储系统最多1万个ASM盘文件，假设我们单块磁盘2T，那我们有2*10000=2万T的空间，这已经很大了，绰绰有余。</p>
<p>3、 2 terabyte maximum storage for each ASM disk</p>
<p>即上文提到的每个ASM磁盘不超过2T，代表我们在挂裸LUN的时候，单个lUN要注意大小。</p>
<p>4、 1 million files for each disk group</p>
<p>代表每个磁盘组最多100万个文件</p>
<p>5、 2.4 terabyte maximum storage for each file</p>
<p>每个文件的最大存储容量2.4T,参考一下即可，我们单块磁盘都不超过2T，没意义的限制。</p>
<p>当然，以上限制到12.1版本之后有所更改，比如只能有63个磁盘组增加到511个，2T磁盘限制也有所修改，具体可以参考<em>文档 ID 370921.1</em>。</p>
<p>
 <br>
</p>
<p><strong>三、私网网卡相关问题</strong></p>
<p>
 <br>
</p>
<p>在安装Grid集群时候，有一步要在两个节点分别执行root.sh，类似的操作如果有安装过数据库的小伙伴也应该有经历过。那在Grid安装的这一步会出现各种莫名奇妙的问题，这个时候就需要有查看日志进行解决的能力了。、</p>
<p><strong>3.1 私网网卡上绑定的HAIP无法通信</strong></p>
<p>在一节点上执行root.sh成功后，在二节点执行root.sh时报错，截取了oraagent_grid.log一段最初的报错信息，除此之后没有其他的报错信息：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528937398.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528937526.png&amp;size=m" alt=""></p>
<p>截取的这一段日志比较重要，可以看到第一行开始InstAgent，然后进行clsdmc_respget检查（对此我的理解是两个节点上私网网卡绑定的HAIP进行通信检查），可以看出多次通信未成功，然后报错ORA-03113: end-of-file on communication channel，直接把和ASM信息的数据库断开，然后开始清除InstAgent的信息，至此节点2执行root.sh失败，意味着集群搭建失败。</p>
<p>对于这个错误我的初步判断是XX分配的私网网卡有问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528937693.png&amp;size=m" alt=""></p>
<p>和XX方负责人沟通后，并未得到想要的答案，其表示正常的私网网卡，使用没有问题。</p>
<p>没办法，只能去找确凿的证据，来告诉XX方其私网网卡确实有问题。</p>
<p>经过一天的排查下来，最终在<em>MOS文档上 Doc ID 1383737.1</em>找到证据。</p>
<h4><strong>3.1.1 Symptoms</strong></h4>
<p>如果这个问题出现在安装Grid Infrastructure执行root.sh脚本时，表现出以下症状：</p>
<ul>
 <li>root script screen output</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528937825.png&amp;size=m" alt=""></p>
<ul>
 <li>$GRID_HOME/cfgtoollogs/crsconfig/rootcrs_<nodename>.log</nodename></li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528937902.png&amp;size=m" alt=""></p>
<ul>
 <li>For 12.1.0.2, the root.sh on the 2nd node could report:</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938060.png&amp;size=m" alt=""></p>
<p>可以看出我们正好符合症状1，执行root.sh脚本出现ORA-03113: end-of-file on communication channel错误。</p>
<h4><strong>3.1.2 Details</strong></h4>
<ul>
 <li>
  <p>**case 1:link local IP (169.254.x.x) is being used by other adapter/network **
   <br>
   169.254.X.X这个IP时执行root.sh脚本时候，会自动绑定一个HAIP的信息到私网网卡上，如果这个IP正好被其他设备占用，那当然会绑定失败，最简单的检查办法是通过ifconfig -a进行检查是否存在169.254.X.X相关信息。</p>
 </li>
 <li>
  <p>Case2: firewall exists between nodes on private network (iptables etc)
   <br>
   两节点之间的private network之间存在防火墙等问题，比如iptables,ipmon等等。</p>
 </li>
 <li>
  <p>HAIP is up on some nodes but not on all
   <br>
   HAIP只在一个节点上绑定了，但是并不是所有节点都绑定了HAIP，在两个节点都进行ifconfig检查，就会发现eth1网卡（私有网卡）下面多了一个169.254.X.X格式的HAIP
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938111.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>Case4: HAIP is up on all nodes but some do not have route info
   <br>
   虽然所有节点上都有HAIP的信息，但是路由表上没有相关信息。
   <br>
   检查路由表，发现两个节点都是有对应的路由信息，应该也不是这个问题</p>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938160.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>Case5. HAIP is up on all nodes and route info is presented but HAIP is not pingable 虽然HAIP在每个节点上都有绑定，而且路由表上也有对应IP信息，但是无法ping通。</p>
 </li>
</ul>
<p>在项目现场，用两个16.254.X.X的IP进行互相ping，发现两个IP无法ping通，于是将此问题和XX方沟通，告知其肯定是私网的问题，希望其进行排查，最终XX方给了回复，确实是私网出现了问题。</p>
<p>由于拿出了十足的证据告诉XX方确实是他们方面出现了问题，所以后面问题的解决也比较快。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938245.png&amp;size=m" alt=""></p>
<p>这儿告诉XX方问题确实出现了eth1这个网卡上。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938359.png&amp;size=m" alt=""></p>
<p>原来XX方面做了对应网卡的地址绑定，每块网卡写死IP就只有对应IP能通信，坑…</p>
<h3><strong>3.2 root.sh Fails to Start HAIP as Default Gateway is Configured for Private Network VLAN</strong></h3>
<h3><em>参考文档 ID 1366211.1</em></h3>
<p>在XX方将对应地址绑定配置修改掉后，重新搭建集群，每次重新搭建集群就得将所有文件删掉，将存储dd清空，非常麻烦。</p>
<p>本以为这次应该可以大功告成了，没想到跑一节点的root.sh就直接报错，真是尴尬和烦！</p>
<p>报错信息：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938439.png&amp;size=m" alt=""></p>
<p>这次有了私网的经验，直接检查HAIP，发现根本没有绑定HAIP，于是直接将情况和XX方进行沟通，XX方应该也意识到时自己的问题，这次对方配合很迅速。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938608.png&amp;size=m" alt=""></p>
<p>告知XX方，eth1网卡无法绑定IP信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938727.png&amp;size=m" alt=""></p>
<p>原来问题是由于他们CAS配置只改了一半，没改完全，导致无法绑定IP信息，真是坑…</p>
<p>
 <br>
</p>
<p><strong>四、总结</strong></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>还好，最后将集群搭建的事宜圆满完成了。</p>
<p><strong>简单总结下吧：</strong></p>
<p>1.ASM磁盘不能大于2T在项目上以后很常见吧，现在存储空间都很大，动辄就是申请15T的资源，那么前期沟通，应该就要知晓裸LUN不能大于2T，免得15T又只划分一个LUN，重新格式化需要很长时间；</p>
<p>2.项目上有其他厂家进行配合时候，有事情一定要给他拿出证据，告诉他哪里哪里有问题，厂家很多都是提交工单，然后进行处理模式，这种固化的模式导致你告诉他可能哪里哪里有问题，他们根本不会分析的，你必须，必须告诉他你们这个地方确实有问题，他们才会进行处理；</p>
<p>3.心态的问题,考虑到后期还要和XX方一直合作，克制，克制，克制……</p>
<p>
 <br>
</p>
<p><em>原文地址：http://www.aixchina.net/Article/178531</em></p>
<p>长按二维码关注"AIX专家俱乐部"公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528938907.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627960&amp;idx=1&amp;sn=914078a2384e3e1645a637febe92831c&amp;chksm=bef921fe898ea8e8f3d704472f60619ba668c78b29331097c240670e41892cbfe35415dd961c&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/53e78e25-a1cb-46a3-b96a-03cb8683c027</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:48:50 GMT</pubDate></item><item><title><![CDATA[ORACLE RAC 修改实例名（实操记录）]]></title><link>https://wiki.dgfygk.com/archives/6d585ae2-02fc-4927-85b5-82c5ecae5bab</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ORACLE%20RAC%20%E4%BF%AE%E6%94%B9%E5%AE%9E%E4%BE%8B%E5%90%8D%EF%BC%88%E5%AE%9E%E6%93%8D%E8%AE%B0%E5%BD%95%EF%BC%89&amp;url=/archives/6d585ae2-02fc-4927-85b5-82c5ecae5bab" width="1" height="1" alt="" style="opacity:0;">
<p>1、备份全库和spfile backup as compressed backupset database plus archivelog; expdp dbank/dbank directory=dmp_dir dumpfile=dbank_2015623.dmp logfile=dbank.log parallel=5 SCHEMAS=DBANK Create pfile from spfile;</p>
<p>2、收集数据库信息
 <br>
 $DB_HOME/bin/srvctl config database -d
 <db_unique_name>
  -a
  <br>
  $DB_HOME/bin/srvctl config service -d <db_unique_name></db_unique_name>
 </db_unique_name></p>
<p>3、创建一个新的pfile
 <br>
 Create pfile from spfile;</p>
<p>4、关闭所有节点 crsctl stop cluster -all</p>
<p>5、重命名或编辑实例名称和其他参数使用实例名称。</p>
<p>For example: instance_name, service_names</p>
<p>6、创建新的实例之一Sid名字节点新密码文件。复制密码文件到其他节点和重命名它与相应的新的SID的名字。
 <br>
 If Data guard is in use, copy the password file from the primary $ORACLE_HOMEs to the standby server and rename it with the corresponding standby SID name.</p>
<p>7、Startup the database with new pfile file and create a spfile using new pfile.</p>
<p>8、Remove the old instance name and add a new instance name using srvctl command. ( Follow below for all instances whose names are changed )</p>
<p>$ srvctl remove instance -d
 <db_unique_name>
  -i
  <instance_name>
   <br>
   $ srvctl add instance -d
   <db_unique_name>
    -i
    <instance_name>
     -n <node> 
      <br>Configure the services back (if necessary)
     </node>
    </instance_name>
   </db_unique_name>
  </instance_name>
 </db_unique_name></p>
<p>$ srvctl modify service -d
 <db_unique_name>
  -s <service_name> 
   <br>Start the database
  </service_name>
 </db_unique_name></p>
<p>$ srvctl start database -d <db_unique_name></db_unique_name></p>
<p>SQL&gt; select instance_name,status from gv$instance</p>
<p>9、Check tnsnames.ora, if INSTANCE_NAME is used, rename it accordingly.</p>
<p>For example:</p>
<p>net_service_name=
 <br>
 (DESCRIPTION=
 <br>
 (ADDRESS=…)
 <br>
 (ADDRESS=…)
 <br>
 (CONNECT_DATA=
 <br>
 (SERVICE_NAME=sales.us.example.com)
 <br>
 (INSTANCE_NAME=sales1)))</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528939631.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630280&amp;idx=3&amp;sn=9b7c6e18a05148fcb2ce371bc2026aab&amp;chksm=bef9160e898e9f18c4b30a504e929afb55d7950260919e8de93128455e40960a17fefa961f8a&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/6d585ae2-02fc-4927-85b5-82c5ecae5bab</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:48:29 GMT</pubDate></item><item><title><![CDATA[不可不看的 Oracle RAC 日常基本维护命令]]></title><link>https://wiki.dgfygk.com/archives/9b8d9058-6ea8-4cf4-beb9-9452df381b7a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%8D%E5%8F%AF%E4%B8%8D%E7%9C%8B%E7%9A%84%20Oracle%20RAC%20%E6%97%A5%E5%B8%B8%E5%9F%BA%E6%9C%AC%E7%BB%B4%E6%8A%A4%E5%91%BD%E4%BB%A4&amp;url=/archives/9b8d9058-6ea8-4cf4-beb9-9452df381b7a" width="1" height="1" alt="" style="opacity:0;">
<p><strong>所有实例和服务的状态</strong></p>
<p>$ srvctl status database -d orcl</p>
<p>Instance orcl1 is running on node linux1</p>
<p>Instance orcl2 is running on node linux2</p>
<p><strong>单个实例的状态</strong></p>
<p>
 <br>
</p>
<p>$ srvctl status instance -d orcl -i orcl2</p>
<p>Instance orcl2 is running on node linux2</p>
<p><strong>在数据库全局命名服务的状态</strong></p>
<p>$ srvctl status service -d orcl -s orcltest</p>
<p>Service orcltest is running on instance(s) orcl2, orcl1</p>
<p><strong>特定节点上节点应用程序的状态</strong></p>
<p>$ srvctl status nodeapps -n linux1</p>
<p>VIP is running on node: linux1</p>
<p>GSD is running on node: linux1</p>
<p>Listener is running on node: linux1</p>
<p>ONS daemon is running on node: linux1</p>
<p><strong>ASM 实例的状态</strong></p>
<p>
 <br>
</p>
<p>$ srvctl status asm -n linux1</p>
<p>ASM instance +ASM1 is running on node linux1.</p>
<p>
 <br>
</p>
<p><strong>列出配置的所有数据库</strong></p>
<p>
 <br>
</p>
<p>$ srvctl config database</p>
<p>orcl</p>
<p>
 <br>
</p>
<p><strong>显示 RAC 数据库的配置</strong></p>
<p>
 <br>
</p>
<p>$ srvctl config database -d orcl</p>
<p>linux1 orcl1 /u01/app/oracle/product/10.2.0/db_1</p>
<p>linux2 orcl2 /u01/app/oracle/product/10.2.0/db_1</p>
<p>
 <br>
</p>
<p><strong>显示指定集群数据库的所有服务</strong></p>
<p>
 <br>
</p>
<p>$ srvctl config service -d orcl</p>
<p>orcltest PREF: orcl2 orcl1 AVAIL:</p>
<p>
 <br>
</p>
<p><strong>显示节点应用程序的配置 —（VIP、GSD、ONS、监听器）</strong></p>
<p>
 <br>
</p>
<p>$ srvctl config nodeapps -n linux1 -a -g -s -l</p>
<p>VIP exists.: /linux1-vip/192.168.1.200/255.255.255.0/eth0:eth1</p>
<p>GSD exists.</p>
<p>ONS daemon exists.</p>
<p>Listener exists.</p>
<p>
 <br>
</p>
<p><strong>显示 ASM 实例的配置</strong></p>
<p>
 <br>
</p>
<p>$ srvctl config asm -n linux1</p>
<p>+ASM1 /u01/app/oracle/product/10.2.0/db_1</p>
<p>
 <br>
</p>
<p><strong>集群中所有正在运行的实例</strong></p>
<p>
 <br>
</p>
<pre><code>* inst_id




* , instance_number inst_no




* , instance_name inst_name




* , parallel




* , status




* , database_status db_status




* , active_state state




* , host_name host




* FROM gv$instance




* ORDER BY inst_id;




* 




* INST_ID INST_NO INST_NAME PAR STATUS DB_STATUS STATE HOST




* -------- -------- ---------- --- ------- ------------ --------- -------




* 1 1 orcl1 YES OPEN ACTIVE NORMAL rac1




* 2 2 orcl2 YES OPEN ACTIVE NORMAL rac2
</code></pre>
<p>
 <br>
</p>
<p><strong>位于磁盘组中的所有数据文件</strong></p>
<p>
 <br>
</p>
<pre><code>* union




* select member from v$logfile




* union




* select name from v$controlfile




* union




* select name from v$tempfile;




* 




* NAME




* -------------------------------------------




* +FLASH_RECOVERY_AREA/orcl/controlfile/current.258.570913191




* +FLASH_RECOVERY_AREA/orcl/onlinelog/group_1.257.570913201




* +FLASH_RECOVERY_AREA/orcl/onlinelog/group_2.256.570913211




* +FLASH_RECOVERY_AREA/orcl/onlinelog/group_3.259.570918285




* +FLASH_RECOVERY_AREA/orcl/onlinelog/group_4.260.570918295




* +ORCL_DATA1/orcl/controlfile/current.259.570913189




* +ORCL_DATA1/orcl/datafile/example.257.570913311




* +ORCL_DATA1/orcl/datafile/indx.270.570920045




* +ORCL_DATA1/orcl/datafile/sysaux.260.570913287




* +ORCL_DATA1/orcl/datafile/system.262.570913215




* +ORCL_DATA1/orcl/datafile/undotbs1.261.570913263




* +ORCL_DATA1/orcl/datafile/undotbs1.271.570920865




* +ORCL_DATA1/orcl/datafile/undotbs2.265.570913331




* +ORCL_DATA1/orcl/datafile/undotbs2.272.570921065




* +ORCL_DATA1/orcl/datafile/users.264.570913355




* +ORCL_DATA1/orcl/datafile/users.269.570919829




* +ORCL_DATA1/orcl/onlinelog/group_1.256.570913195




* +ORCL_DATA1/orcl/onlinelog/group_2.263.570913205




* +ORCL_DATA1/orcl/onlinelog/group_3.266.570918279




* +ORCL_DATA1/orcl/onlinelog/group_4.267.570918289




* +ORCL_DATA1/orcl/tempfile/temp.258.570913303




* 




* 21 rows selected.




* 
</code></pre>
<p>
 <br>
</p>
<p><strong>属于 “ORCL_DATA1” 磁盘组的所有 ASM 磁盘</strong></p>
<p>
 <br>
</p>
<pre><code>* FROM v$asm_disk




* WHERE group_number IN (select group_number




* from v$asm_diskgroup




* where name = 'ORCL_DATA1');




* 




* PATH




* ----------------------------------




* ORCL:VOL1




* ORCL:VOL2
</code></pre>
<p>
 <br>
</p>
<p>启动 / 停止 RAC 集群</p>
<p>确保是以 oracle UNIX 用户登录的。我们将从 rac1 节点运行所有命令：</p>
<h1>su – oracle</h1>
<p>$ hostname</p>
<p>Rac1</p>
<p>
 <br>
</p>
<p><strong>停止 Oracle RAC 10g 环境</strong></p>
<p>
 <br>
</p>
<p>第一步是停止 Oracle 实例。当此实例（和相关服务）关闭后，关闭 ASM 实例。最后，关闭节点应用程序（虚拟 IP、GSD、TNS 监听器和 ONS）。</p>
<p>
 <br>
</p>
<p>$ export ORACLE_SID=orcl1</p>
<p>$ emctl stop dbconsole</p>
<p>$ srvctl stop instance -d orcl -i orcl1</p>
<p>$ srvctl stop asm -n rac1</p>
<p>$ srvctl stop nodeapps –n rac1</p>
<p>
 <br>
</p>
<p><strong>启动 Oracle RAC 10g 环境</strong></p>
<p>
 <br>
</p>
<p>第一步是启动节点应用程序（虚拟 IP、GSD、TNS 监听器和 ONS）。当成功启动节点应用程序后，启动 ASM 实例。最后，启动 Oracle 实例（和相关服务）以及企业管理器数据库控制台。</p>
<p>
 <br>
</p>
<p>$ export ORACLE_SID=orcl1</p>
<p>$ srvctl start nodeapps -n rac1</p>
<p>$ srvctl start asm -n rac1</p>
<p>$ srvctl start instance -d orcl -i orcl1</p>
<p>$ emctl start dbconsole</p>
<p><strong>使用 SRVCTL 启动 / 停止所有实例</strong></p>
<p>启动 / 停止所有实例及其启用的服务。我只是觉得有意思就把此步骤作为关闭所有实例的一种方法加进来了！</p>
<p>
 <br>
</p>
<p>$ srvctl start database -d orcl</p>
<p>$ srvctl stop database -d orcl</p>
<p>
 <br>
</p>
<p><strong>启停止监听</strong></p>
<p>
 <br>
</p>
<pre><code>* hostb$lsnrctl start listener_hostb




* hostb$lsnrctl stop listener_hostb 备份 votning diskdd if=voting_disk_name f=backup_file_name




* dd if=/dev/rdsk/c4t600C0FF000000000098ADE240330A000d0s4 f=votingdisk.bak# dd if=/dev/zero f=/dev/rdsk/c4t600C0FF000000000098ADE240330A000d0s4 bs=512 count=261120




* 测试




* # dd if=/dev/rdsk/c4t600C0FF000000000098ADE240330A000d0s4 f=/data/backup/rac/vd_backup0420.bak




* 261120+0 记录进入




* 261120+0 记录调出




* # cd /data/backup/rac   




* # ls




* ocr0420.bak         ocrdisk             vd_backup0420.bak   votingdisk.bak      votingdisk0420.bak




* # dd if=/data/backup/rac/vd_backup0420.bak f=/dev/rdsk/c4t600C0FF000000000098ADE240330A000d0s4




* 261120+0 记录进入




* 261120+0 记录调出备份 OCR 磁盘




* 查看备份




* $ocrconfig -showbackup




* 备份




* /data/oracle/crs/bin/ocrconfig -export /data/backup/rac/ocrdisk.bak




* 还原 需要停掉所有的节点，Stop the Oracle Clusterware software on all of the nodes




* /data/oracle/crs/bin/ocrconfig -import file_name




* 自动备份的还原# /data/oracle/crs/bin/ocrconfig -showbackup




* # /data/oracle/crs/bin/ocrconfig -restore /data/oracle/crs/cdata/db168crs/backup00.ocrhosta$cluvfy comp ocr -n all // 检验




* ocr 检查




* #ocrcheck 配置路径在




* /var/opt/oracle/ocrconfig_loc 文件里面 如果需要更改 OCR 磁盘的路径配置。




* OCR 磁盘空间检查




* # /data/oracle/crs/bin/ocrcheck




* Status of Oracle Cluster Registry is as follows :




*          Version                  :          2




*          Total space (kbytes)     :     399752




*          Used space (kbytes)      :       3784




*          Available space (kbytes) :     395968




*          ID                       : 148562961




*          Device/File Name         : /dev/rdsk/c4t600C0FF000000000098ADE240330A000d0s5




*                                     Device/File integrity check succeeded                                    Device/File not configured         Cluster registry integrity check succeeded#
</code></pre>
<p>
 <br>
</p>
<p><strong>ASM 实例的状态</strong></p>
<pre><code>* ASM 实例 +ASM1 正在节点 hosta 上运行。




* hosta$ 特定节点上节点应用程序的状态




* hosta$srvctl status nodeapps -n hosta




* VIP 正在运行的节点: hosta




* GSD 正在运行的节点: hosta




* 监听程序未在节点上运行: hosta




* ONS 守护程序正在节点上运行: hosta




* hosta$ 所有实例和服务的状态




* hosta$srvctl status database -d cole




* 实例 cole1 正在节点 hosta 上运行




* 实例 cole2 正在节点 hostb 上运行




* hosta$ 列出配置的所有数据库




* hosta$srvctl config database




* cole




* hosta$




* 显示 RAC 数据库的配置 hosta$srvctl config database -d cole




* hosta cole1 /data/oracle/OracleHome




* hostb cole2 /data/oracle/OracleHome




* hosta$ 显示 ASM 实例的配置




* hosta$srvctl config asm -n hosta




* +ASM1 /data/oracle/OracleHome 显示节点应用程序的配置 -（VIP、GSD、ONS、监听器）




* hosta$srvctl config nodeapps -n hosta -a -g -s -l




* VIP 已存在。: /a1-vip/10.1.1.9/255.255.0.0/ce0




* GSD 已存在。




* ONS 守护程序已存在。




* 监听程序已存在。




* hosta$




* 集群中所有正在运行的实例




* SELECT




* inst_id




* , instance_number inst_no




* , instance_name inst_name




* , parallel




* , status




* , database_status db_status




* , active_state state




* , host_name host




* FROM gv$instance




* ORDER BY inst_id;   INST_ID    INST_NO INST_NAME        PAR STATUS       DB_STATUS STATE     HOST




* ---------- ---------- ---------------- --- ------------ -------------------------- -




*          1          1 cole1            YES OPEN         ACTIVE      NORMAL    hosta         2          2 cole2            YES OPEN         ACTIVE      NORMAL    hostb
</code></pre>
<p>
 <br>
</p>
<p><strong>位于磁盘组中的所有数据文件</strong></p>
<pre><code>* SQL&gt; select name from v$datafile




* union




* select member from v$logfile




* union




* select name from v$controlfile




* union




* select name from v$tempfile;
</code></pre>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528940366.png&amp;size=m" alt=""></p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629834&amp;idx=4&amp;sn=347a0bfd707783a03cabff767765ffa4&amp;chksm=bef9184c898e915a6e21f3908c55ef441ab5f57b361abbad9879cd5facd6365cf26706bd36a6&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/9b8d9058-6ea8-4cf4-beb9-9452df381b7a</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:46:33 GMT</pubDate></item><item><title><![CDATA[150 篇数据库架构和运维知识，含Oracle、MySQL、Redis、PostgreSQL、Db2]]></title><link>https://wiki.dgfygk.com/archives/eaf52b1d-1dea-4aaa-99ac-ff53b7683fea</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=150%20%E7%AF%87%E6%95%B0%E6%8D%AE%E5%BA%93%E6%9E%B6%E6%9E%84%E5%92%8C%E8%BF%90%E7%BB%B4%E7%9F%A5%E8%AF%86%EF%BC%8C%E5%90%ABOracle%E3%80%81MySQL%E3%80%81Redis%E3%80%81PostgreSQL%E3%80%81Db2&amp;url=/archives/eaf52b1d-1dea-4aaa-99ac-ff53b7683fea" width="1" height="1" alt="" style="opacity:0;">
<p>以下文章点击标题即可阅读，欢迎收藏转发</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642722&amp;idx=1&amp;sn=fd7087a94b89c4d04c9356daa2cd2f5b&amp;chksm=bef9e7a4898e6eb2b560422dcca69d9c3ec87173996a31e4add65353070e1ca0eaf39132d721&amp;scene=21#wechat_redirect">☛ 分布式架构下，传统数据库运维究竟要面对哪些变化？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650644740&amp;idx=2&amp;sn=6a86e8477247bda419c8e1139556e4fc&amp;chksm=bef9df82898e5694adef20272fc400adfe1aba7285a8131e79a27815f13278aae5dbe4a8f36b&amp;scene=21#wechat_redirect">☛ 干货：分布式关系型数据库选型原则和 POC 测试方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650643469&amp;idx=2&amp;sn=5fdef923b5fde5477109d932ccd6d0b0&amp;chksm=bef9e28b898e6b9dc08928a8fa4dd53a45572a7a53ddf3b5b099f4fadadf18a09e1ab1429780&amp;scene=21#wechat_redirect">☛ NoSQL 数据库和传统关系型数据库到底有什么区别？</a></p>
<p>☛ 几种常用（闭源、开源）关系型数据库的架构和实现原理解读</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650645180&amp;idx=3&amp;sn=24d1995e9b3120bc9801b999d591977b&amp;chksm=bef9dc3a898e552ca4c5529062b122064739ca876b82726beeeed4c370353c24c3c1c4553812&amp;scene=21#wechat_redirect">☛ 应用分布式数据库常见的 5 个顾虑和难点</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650643865&amp;idx=1&amp;sn=7a322d28e66d0825b90d0115adf925b0&amp;chksm=bef9e31f898e6a09707fb99f836af1a4b17476b61dead39d1e72a29a93bcb4c26489d7cff95b&amp;scene=21#wechat_redirect">☛ 银行核心系统采用分布式数据库的探索</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650643378&amp;idx=2&amp;sn=e26818d3fa62cd98fd8310b7eebe12e5&amp;chksm=bef9e534898e6c22007d80f3c0fe2fcc5577a47f5fc43c25e52c141fc1ffaffd67514ae27523&amp;scene=21#wechat_redirect">☛ 核心系统主机环境如何向分布式数据库转型？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650643267&amp;idx=1&amp;sn=e6a23770e207b15f8ac6175225307cda&amp;chksm=bef9e5c5898e6cd3e984a0fb01207801d18591ec35a00f0c14bcb38726d4686132f4b28137d2&amp;scene=21#wechat_redirect">☛ 银行分布式数据库设计与运维中的典型难点</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642896&amp;idx=2&amp;sn=0e0c17e946f50831722ab576015473cc&amp;chksm=bef9e756898e6e4045f0c26de408701e1e742e68160d55bb8a84dc679e9ecfb3d3cdc86cdd4d&amp;scene=21#wechat_redirect">☛ 三个避不开的疑问：从传统关系型数据库转向分布式数据库的思考</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642299&amp;idx=1&amp;sn=95e744ef3ca83f8aad36e873a0cbde55&amp;chksm=bef9e9fd898e60eb47c8af1fbd3aa71e635fcb87d3cbf461c2b2edc85b0c09985b0b06663f8a&amp;scene=21#wechat_redirect">☛ 亲身感悟：业务系统数据库向开源和分布式过渡，运维人员要Get哪些技能？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642092&amp;idx=1&amp;sn=dbd644d0d07fea5905fb3dceb9739944&amp;chksm=bef9e82a898e613c80949f8915c39ab348b8e78f1b876f8da92c1b4b40247879feb0a242911b&amp;scene=21#wechat_redirect">☛ 四种分布式数据库的场景选型和优缺点分析</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650641722&amp;idx=2&amp;sn=77d28c67cb4f677ac3b6e80f3226cef8&amp;chksm=bef9ebbc898e62aa9af41b7b4fc021f81928cc1494a2e94631ef57c5e9f062756d5bd199f1ce&amp;scene=21#wechat_redirect">☛ 分布式数据库（Elasticsearch、Redis、MySQL分布式集群等）场景选型和应用难点</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642307&amp;idx=1&amp;sn=31bb459c6c1d1f128fc75dc722438945&amp;chksm=bef9e905898e60134e989a2a7a65bc759aeb21b25457ff87230a8cdf9bf1893d914d4fd2ce50&amp;scene=21#wechat_redirect">☛ 中小金融企业如何做好数据库资源整合？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650641919&amp;idx=1&amp;sn=94b872d51fa0df85a2d8da59258e71e8&amp;chksm=bef9eb79898e626f477e89745de0569db39319cc306acc6b65be69d5a748173e0463b2033f3b&amp;scene=21#wechat_redirect">☛ 民生银行数据库智能运维的探索与实践——DBPaaS数据库统一管理平台</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650641678&amp;idx=1&amp;sn=4ab8993b82a390f8757fad23390a943b&amp;chksm=bef9eb88898e629e37ff081647eb64ef53a852d7f93dc7fb8dca476fc59e02462379712e9bc9&amp;scene=21#wechat_redirect">☛ 一次核心系统的告警分析过程——数据库问题智能分析实践</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650641530&amp;idx=1&amp;sn=860353dc49402e4391865d8553693f7c&amp;chksm=bef9eafc898e63ea71d50b5767d9ecd3f7bd44db97318aa68580f6b8ba1881a89f0a9c7e25d7&amp;scene=21#wechat_redirect">☛ 定位分析从几小时变成一分钟–银行数据库智能运维平台建设经验分享</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650640406&amp;idx=1&amp;sn=a86008e7dca1c51d5f68ca7d8b07f1b9&amp;chksm=bef9ee90898e67863bab027a75263d560264d32e964896179f5e2b3cd593d7c6cce8740a71c9&amp;scene=21#wechat_redirect">☛ 医疗行业核心系统数据库和基础架构解决方案</a></p>
<p>
 <br>
</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650647847&amp;idx=2&amp;sn=0df5697c87d2585edf63af2afcf2f3a4&amp;chksm=bef9d3a1898e5ab7a58729d6da2b74aa1fa7ed0381a03cef6d503c0f76694d7b0175abc5a6d0&amp;scene=21#wechat_redirect">☛ 50 个 Redis 必备知识：基础知识，架构、调优和监控知识及难点解决</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646024&amp;idx=2&amp;sn=561acccb30b148cb428d097b4d8113eb&amp;chksm=bef9d88e898e5198d6334315c9ddec2822eaad83e1bdf37f4a14033e04c2a56bcd0a89bd8ef1&amp;scene=21#wechat_redirect">☛ Redis 运维必备技能：内存使用和管理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642144&amp;idx=3&amp;sn=a5b5c540b3ced331cf1a31659411dad8&amp;chksm=bef9e866898e61709f149dbecbb30f7dbb3239fac411c91bc6a4a06f54e97da2af375240fce0&amp;scene=21#wechat_redirect">☛ Redis 主从复制及其原理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650641753&amp;idx=3&amp;sn=57ccebd7584b4b7d3f9789ab4103a671&amp;chksm=bef9ebdf898e62c9eda75346ffa625367e81e2d261e28dcd59f00adf0f6e1f6352803ea6b507&amp;scene=21#wechat_redirect">☛ Redis 性能测试与监控</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650639358&amp;idx=3&amp;sn=d61db88ab1e892f88d1b0bec76ef7418&amp;chksm=bef9f578898e7c6e2f21ae60a69d2ab8fa4f4481af34dce6029cf404521ed6f09fc5da6d3acd&amp;scene=21#wechat_redirect">☛ 总结几个在 Redis 上踩过的坑</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629753&amp;idx=3&amp;sn=4aa23023b21d974d40b5113afd006076&amp;chksm=bef918ff898e91e9ca33cf4cd34ac13b6e858b7750a40fe37abd77a4c2c23cc8050821e12b99&amp;scene=21#wechat_redirect">☛ Redis 漏洞及安全加固方案</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630750&amp;idx=5&amp;sn=2323de3c2075f1d7ac17f4ea59f99cd6&amp;chksm=bef914d8898e9dcef607cccfafa1d255acf340293088acb060808539c73670ce95997d3da5c1&amp;scene=21#wechat_redirect">☛ Redis 数据结构和主要命令</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650634318&amp;idx=2&amp;sn=c4bace205945ff785b9b10c2a0c4625f&amp;chksm=bef906c8898e8fdea5cb4ac6c017885b337c7e954ffdbfd384be1ea3693ed123262bb91be7ca&amp;scene=21#wechat_redirect">☛ Redis 构建缓存数据库 6 个难点问题</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650644685&amp;idx=3&amp;sn=ffc3570bf81dcaddc51dd2d9e52e84dd&amp;chksm=bef9de4b898e575d6ad8a01c4def07c2d07fedc85bef9864fd4c68cea23c285035db5b17264b&amp;scene=21#wechat_redirect">☛ Redis 在互联网金融账务核心系统中的应用实践</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650637349&amp;idx=1&amp;sn=ef10ee67c4b8c92bd50963be1dbadbce&amp;chksm=bef9faa3898e73b55b2c1674a0c25fb8bc094f5672830fecb8324b98cc96f8f331f045d83ee0&amp;scene=21#wechat_redirect">☛ Redis Cluster 服务平台化之路</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650634258&amp;idx=1&amp;sn=80e766d7920b1d1810f96c51b9336ab0&amp;chksm=bef90694898e8f82735cc7aae10f319dfef06920385e716e9defb1489290416ea032711975c5&amp;scene=21#wechat_redirect">☛ Redis 在支付标记化系统中的应用</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633234&amp;idx=2&amp;sn=e9cd43a66c1d29435c99c8dc795e1abe&amp;chksm=bef90a94898e8382f3c8b7c940bb944ce54a748e19c20a8a6120ff4f48a49e4a37f0045b1696&amp;scene=21#wechat_redirect">☛ 一个具有代表性的 Redis 横向扩展案例</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650644059&amp;idx=4&amp;sn=cff0e7265329dfa2ff1e43e6c5eabb4a&amp;chksm=bef9e0dd898e69cbab761cd33e85f0b7f4159f44e64d26b58e95a8e0348370f98eed2247c8a5&amp;scene=21#wechat_redirect">☛ 只是作为缓存数据库的 Redis 发生故障，会影响业务的正常访问吗？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642278&amp;idx=2&amp;sn=0e5b0df3a7e615af52c5816cd8428895&amp;chksm=bef9e9e0898e60f63e7b77f7e19fa80bdd75047f2ba958ec2b0f633ec262c1e9c4ed8c534d8e&amp;scene=21#wechat_redirect">☛ 如何用慢查询找到 Redis 的性能瓶颈？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638403&amp;idx=3&amp;sn=49f99c72bbc9d2ca27b7cadb6ee4bf16&amp;chksm=bef9f6c5898e7fd3cd6bd90036a2a38dff4fe0ff882455e347f99cc503d77316fe37d0c140b4&amp;scene=21#wechat_redirect">☛ Redis 是否有成熟的水平扩展机制？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638133&amp;idx=3&amp;sn=3189bd42c67b0bf1f5825049f9cdcae4&amp;chksm=bef9f9b3898e70a57304be5529923c89e9c7dc20a50d339ed5f2c4f411bd17b16bcdd13346cb&amp;scene=21#wechat_redirect">☛ 大量视频录像、语音和图片的场景下，选择 Redis 还是 MongoDB？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630623&amp;idx=4&amp;sn=07f059e11614d58f4882c2148bb5962a&amp;chksm=bef91759898e9e4ff9f96031f95b7d0bdcafd828dbf42378f19f3a51126fc957a0e157e9bafb&amp;scene=21#wechat_redirect">☛ Redis 和 Memcached 的区别在哪里？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650634759&amp;idx=2&amp;sn=61a4e2887afc929eac440f2300e01289&amp;chksm=bef90481898e8d97bc4f98d35e328b4db0f24d1dd65f63128881710e20bdb7d0f807e0338ee9&amp;scene=21#wechat_redirect">☛ 银行如何使用 Redis 构建缓存数据库？</a></p>
<p>
 <br>
</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646840&amp;idx=1&amp;sn=d94d4573014d4af1721fdf9b7eb8768e&amp;chksm=bef9d7be898e5ea8330b2882175bba768dfb437dd02790fec4f2d682664ae5dfd7e7957fe813&amp;scene=21#wechat_redirect">☛ 分布式数据库原理和 PostgreSQL 分布式架构，看这篇就清楚了→</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650644141&amp;idx=3&amp;sn=c63dc669841f2ca363b30175fe7dbf2f&amp;chksm=bef9e02b898e693d9bf60fa9707e8db548c73e8660123315d724d640f85692ca54749f6a861e&amp;scene=21#wechat_redirect">☛ PostgreSQL 的备份、恢复、容灾</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650643971&amp;idx=2&amp;sn=7d3deac1b01b0bf47e1b8457ef34f011&amp;chksm=bef9e085898e6993f23e8d894bf76c5d89ed27bc01488b87bdb25b8aa6099893f89defa95692&amp;scene=21#wechat_redirect">☛ PostgreSQL 数据库在分布式场景下的架构</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642200&amp;idx=3&amp;sn=1a85ee28498dda69e48ed076a10d4175&amp;chksm=bef9e99e898e6088f85fa9275f5c130862f8e0edfac014243fb289f3bb4dca8eea8a4a988d9c&amp;scene=21#wechat_redirect">☛ PostgreSQL 数据库检测到 ShareLock 死锁的处理方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642129&amp;idx=3&amp;sn=a29f5922691ac7a9a7861c7a42240499&amp;chksm=bef9e857898e61411570cf8294df623d28dbc260f718979757faffcff7c988b695625ae30c17&amp;scene=21#wechat_redirect">☛ 两图看懂 PostgreSQL 流复制逻辑</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642061&amp;idx=2&amp;sn=da503d95508e85b8b29ac92b9a11f201&amp;chksm=bef9e80b898e611d22a338c4330965cb14a114ee6c789b65c57f5571cb6e44e5d04e0b1d815d&amp;scene=21#wechat_redirect">☛ Postgres 数据库大批量单表导入数据引发性能故障的处理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642025&amp;idx=2&amp;sn=ac22ee91d94f08c430d68b3e22a3db2c&amp;chksm=bef9e8ef898e61f9501d625189d05051813725a1020425a164e21fe4ed8012fb0e8d119509ae&amp;scene=21#wechat_redirect">☛ Postgres 异常断电导致启动失败的解决方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650641791&amp;idx=3&amp;sn=9602357a02b12905b0f87ab737c76a2d&amp;chksm=bef9ebf9898e62ef42887a67b51977ce8d830d178730a80a9743dbefd134f84b6e8066414dda&amp;scene=21#wechat_redirect">☛ Postgres 启动失败问题的分析与处理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638707&amp;idx=1&amp;sn=f38e30450cc419aa366b0d592fe1aa5f&amp;chksm=bef9f7f5898e7ee3ddc7a7d53dc3b5fcfd8c663bfce06b193c6c2fdc266a155f93a64b640456&amp;scene=21#wechat_redirect">☛ 五个 PostgreSQL 典型故障案例及处理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650643175&amp;idx=1&amp;sn=a943742e63bc1f4796a9623a37012fdf&amp;chksm=bef9e461898e6d779696f7466b56446f2320ac848c9bd10464e4c7e925c19f64d0d8148590a3&amp;scene=21#wechat_redirect">☛ 争议 \| PostgreSQL vs 主流开源如MySQL及主流闭源如Oracle，是否有优势？</a></p>
<p>
 <br>
</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646775&amp;idx=1&amp;sn=2d4b5c80a4966c84cdfee05766eb2054&amp;chksm=bef9d671898e5f676bbee456feaf46163762efbfaa9e95026228958b9ad6015d5357d3e05408&amp;scene=21#wechat_redirect">☛ Oracle 迁移至 MySQL、PG等分布式数据库，可能遇到的12个典型问题</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650647693&amp;idx=2&amp;sn=0f5654822da40f780ea88882fb7d7b54&amp;chksm=bef9d20b898e5b1d3d95729b6bc8fc29ee94872de699b6808030c43bf4899f8bf44ac32e370d&amp;scene=21#wechat_redirect">☛ Oracle19c RAC for Centos7.6 详细安装文档</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646841&amp;idx=4&amp;sn=a5964a5b657325759ec2c042a2024576&amp;chksm=bef9d7bf898e5ea9edf8e936660de375d6173c945038ddfbe06c1372ec2dfd910194fbd60a72&amp;scene=21#wechat_redirect">☛ Oracle RAC/ADG/OGG 双活方案比较</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646655&amp;idx=2&amp;sn=1189e6e14da9a661a15597134c7155a2&amp;chksm=bef9d6f9898e5fef5ec77c9866944d8c8cd1536a75724570dade53ee86e714e6e7789b2e1f53&amp;scene=21#wechat_redirect">☛ 经典实用：Oracle RAC 建设过程中各个层面的关键点和优化项总结</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650645201&amp;idx=3&amp;sn=e5b296dac923c62fc9179703579c79f9&amp;chksm=bef9dc57898e55414075e602e26c97d1c50e54ed6cc2edd0581285f4cfe11a279a94f0468168&amp;scene=21#wechat_redirect">☛ 中小金融企业基于 Oracle 数据库进行整合的规划方案</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650644059&amp;idx=3&amp;sn=b412cdfb55b47550a77db30d51799311&amp;chksm=bef9e0dd898e69cbc088020bd0c0da34184dc68253d976f170c7be78144f6af7ec6995b59c5a&amp;scene=21#wechat_redirect">☛ 基于 Oracle RAC 实现双活的关键问题和解决方案</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629925&amp;idx=2&amp;sn=6a85bfd25f3dd3cb3f7187d3a14b98ef&amp;chksm=bef919a3898e90b54ba9157182888170dae93a48cc98fecd50776c01e5edc3ab2ecb4c545528&amp;scene=21#wechat_redirect">☛ Oracle 高可用性（RAC）技术解决方案及实现过程</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629168&amp;idx=1&amp;sn=c124c4f09d003cf9a8557590e9d48efb&amp;chksm=bef91ab6898e93a0e073f9c4c5cc6f6fa017826eed69047fea99c786b3d05b9fb4a8b3729bc5&amp;scene=21#wechat_redirect">☛ RAC 群集配置及恢复手册</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630827&amp;idx=1&amp;sn=0684f877a3434b14b22f0e79a6ba5515&amp;chksm=bef9142d898e9d3ba0b8e11c983ad2244ff5a22c777f1469e1b6962f4b126503778a0a9abba6&amp;scene=21#wechat_redirect">☛ 基于 Oracle RAC/ADG/OGG 等设计数据库双活方案，必须掌握五方面知识点</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630718&amp;idx=1&amp;sn=06eead6c3f885fd9dfa7a8b662512607&amp;chksm=bef914b8898e9dae954c33cc13537964cc099b900ed0b88b2abd5f73f16939a37088ba0fa23d&amp;scene=21#wechat_redirect">☛ 基于 Oracle RAC 实现双活方案的架构分析和实现难度分析</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628951&amp;idx=1&amp;sn=f7fc8f5bb70fbbebd8b36130db984c0c&amp;chksm=bef91dd1898e94c714ba36de267e3996ba59e2df9366f928344ded259d103cc84f3fdd1f769c&amp;scene=21#wechat_redirect">☛ Oracle / MySQL数据库高可用解决方案：数据一致性问题等探讨</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630827&amp;idx=3&amp;sn=ec862f8c3c20549114c84b5bdad3ede1&amp;chksm=bef9142d898e9d3b27a1d8c016b9f4f42619af8e694f7ccd2030cce4355fd0f49a884ef31782&amp;scene=21#wechat_redirect">☛ 风险提示：这么用 x86，小心 Oracle+RAC 中招</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650631963&amp;idx=3&amp;sn=c64c3cd15ef40f4e5afe85efe6d3dab5&amp;chksm=bef9119d898e988b655e55bc4b0f67b26c05c1d66b42303b18906725dee487dfb8a86a3d675a&amp;scene=21#wechat_redirect">☛ 分析 mcelog 引发 x86 RAC 失效原因</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627287&amp;idx=3&amp;sn=b3249335a55c4d8b517dda9d9c73c733&amp;chksm=bef92251898eab4766da405162677362c8c9f0d15d1044036d1204a30dff0df605b47031fb6d&amp;scene=21#wechat_redirect">☛ 用TSM的代理节点（proxy node)备份oracle RAC数据库</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627144&amp;idx=2&amp;sn=b7053dc0d9e02c978c28472f5c5707b6&amp;chksm=bef922ce898eabd8a69a823022923b351839d8e524929eadd790e09a2bcb3b250797e2543a83&amp;scene=21#wechat_redirect">☛ AIX集群系统下的 ORACLE RAC 故障处理方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650626850&amp;idx=3&amp;sn=72c05ce386141af3cdaf2f8a3a28e451&amp;chksm=bef925a4898eacb29b4b40628d8a7f990158786a75d736af00b7069d255190a08bcf9837ca44&amp;scene=21#wechat_redirect">☛ OLTP 下的 DB2 PureScale 与 Oracle RAC 对比</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650626355&amp;idx=1&amp;sn=2d25935ca00140e9305890441768a281&amp;chksm=bef927b5898eaea370dac5f2497855655ede08fa49d1a34a6615cc4329ed0ba28c3338c0e3aa&amp;scene=21#wechat_redirect">☛ 你的Oracle RAC是真集群还是伪集群？高可用失效风险提醒</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650625095&amp;idx=1&amp;sn=470e7261441d240289cce770ae97c6c6&amp;chksm=bef92ac1898ea3d7ae5ec67fd149aba3be9cfd43ae5ccc7a483aadd50b35b4e2a8d3865f92ad&amp;scene=21#wechat_redirect">☛ 由重启引起的Oracle RAC节点宕机分析及追根溯源</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627960&amp;idx=1&amp;sn=914078a2384e3e1645a637febe92831c&amp;chksm=bef921fe898ea8e8f3d704472f60619ba668c78b29331097c240670e41892cbfe35415dd961c&amp;scene=21#wechat_redirect">☛ RAC集群搭建ASM和私网网卡问题分析</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630280&amp;idx=3&amp;sn=9b7c6e18a05148fcb2ce371bc2026aab&amp;chksm=bef9160e898e9f18c4b30a504e929afb55d7950260919e8de93128455e40960a17fefa961f8a&amp;scene=21#wechat_redirect">☛ ORACLE RAC 修改实例名（实操记录）</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629834&amp;idx=4&amp;sn=347a0bfd707783a03cabff767765ffa4&amp;chksm=bef9184c898e915a6e21f3908c55ef441ab5f57b361abbad9879cd5facd6365cf26706bd36a6&amp;scene=21#wechat_redirect">☛ 不可不看的 Oracle RAC 日常基本维护命令</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629405&amp;idx=3&amp;sn=4341e8c57c72415677759a77c1344300&amp;chksm=bef91b9b898e928da6175ba8f0784db812f1a9104840d70a6b3b4111cda6b6c0c706d91b5efb&amp;scene=21#wechat_redirect">☛ RAC 节点重做记录</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628691&amp;idx=2&amp;sn=25c89165a208a8560955f1b7dcd1c8ab&amp;chksm=bef91cd5898e95c3c13efcdba6438108d230c3b1b345250421dffaaaf2f6acc46ab3a8f83181&amp;scene=21#wechat_redirect">☛ Oracle RAC 管理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632775&amp;idx=1&amp;sn=c8651248d8eace0f682a7e570980cc45&amp;chksm=bef90cc1898e85d7ae0c6d4d1980a37a3311e6d4b74548761a0a360b3ce1f50c4371f05a4d5e&amp;scene=21#wechat_redirect">☛ 系统投产前，Oracle数据库优化思路和9个典型问题</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650631865&amp;idx=2&amp;sn=c4f1f4d6f32ff7982d3392201aac3fb5&amp;chksm=bef9103f898e992974a8b1118ce16b538f37753837f77912828e3c6fea58e52b5eccb02e5bdd&amp;scene=21#wechat_redirect">☛ 此类情况下解决Oracle数据库重启的四种方案</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650631579&amp;idx=2&amp;sn=4c6ac1ec3184450ee23324137261a23f&amp;chksm=bef9131d898e9a0be067f7c58976abcaa01ab76c104cbd298e78e760382e13ec7824dc965104&amp;scene=21#wechat_redirect">☛ Oracle ADG 切换场景及具体切换步骤</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630364&amp;idx=3&amp;sn=b29595bd9cca5579826a6df52d1a3401&amp;chksm=bef9165a898e9f4c310ebba957cb9b58f41c0f860e5b112781fc468e852ff9bfe5ae985d09a2&amp;scene=21#wechat_redirect">☛ Oracle数据库安全管理的5个方面</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632699&amp;idx=1&amp;sn=c633fd05446fdb7ae00eac30426a53b8&amp;chksm=bef90f7d898e866bc2909941ec98107b901a363f7c996b37ff02921d6d503922cb6d6b9468ea&amp;scene=21#wechat_redirect">☛ Oracle 备份与恢复 15 个典型问题</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632688&amp;idx=2&amp;sn=70317cb890b40696bc7385bf3b97109f&amp;chksm=bef90f76898e8660725427a4563b22f853df6dbedc315ff8aca25fc1f4b17a58a6e1061e1874&amp;scene=21#wechat_redirect">☛ Oracle 数据库序列相关等待事件解析</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632678&amp;idx=1&amp;sn=c56f886d2a2918cb1dfd808f9f80afd7&amp;chksm=bef90f60898e86768a7bb387dd12bbf407df3e8e3170ad211cce1ee620bc22ce7008a6b248ed&amp;scene=21#wechat_redirect">☛ Oracle 备份恢复：错误类型及实例恢复方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632670&amp;idx=1&amp;sn=463163dbf3baa5803e64b29c19e23f77&amp;chksm=bef90f58898e864e47669cfa4dca787b02afd85e8abdedb2a97f4d0aa7449967c9e637bc4ced&amp;scene=21#wechat_redirect">☛ Oracle 数据库服务器 IO 高的分析方案和案例</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632643&amp;idx=3&amp;sn=e08831fae71d1db73ef9450fb450bfe8&amp;chksm=bef90f45898e86533b2294e3ffaf65491fca968792afb620f72c658db458960960d4fe4bc0fa&amp;scene=21#wechat_redirect">☛ 误删除 Oracle 数据库数据的恢复方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630336&amp;idx=1&amp;sn=bc8c299f55c57b97cc3e766b50a96732&amp;chksm=bef91646898e9f50ec2f4f06f793ba292def35c4cbcb4fac20c703b7569f8fd3831a756ab850&amp;scene=21#wechat_redirect">☛ 性能测试关注的 Oracle 指标</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628682&amp;idx=1&amp;sn=4a841f8a168d0090ef1dac54497521b0&amp;chksm=bef91ccc898e95da7ae3ce64a142b338cef6e4454cbd0dc0345b3bd911be8b649cfb60046025&amp;scene=21#wechat_redirect">☛ Oracle 三种高可用方案原理介绍</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632915&amp;idx=3&amp;sn=3c1ad63791159c09e18afba5e3c10a0e&amp;chksm=bef90c55898e854363074c4c8b6979143feeaa9a04098560b70e7cb40c4a051b8801014c1ce8&amp;scene=21#wechat_redirect">☛ Oracle 双机热备份和容灾备份有什么区别？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633731&amp;idx=3&amp;sn=1a218221cf807cf1f76aa553b474f512&amp;chksm=bef90885898e81935671236779cc1c41bc946f72211218e4c8245da32986d6917e0eb2ba6c7e&amp;scene=21#wechat_redirect">☛ 导致 Oracle 性能抖动的参数提醒</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633719&amp;idx=2&amp;sn=c5ac41513aa4ea264d721544934fee9e&amp;chksm=bef90b71898e82673bb3dc8c0419086575a6f7187700ce70e2aab663df0c4531f52a3d5839dd&amp;scene=21#wechat_redirect">☛ 深入学习Oracle分区表及分区索引</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632732&amp;idx=2&amp;sn=73d0c67fed6376543bd25fae6b6d36a0&amp;chksm=bef90c9a898e858cd8850a4cdc81e0a7558ca062320e8d0ff79ebad90258943fcef9245d5b34&amp;scene=21#wechat_redirect">☛ 小机上运行 Oracle 需要注意的进程调度 BUG</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650631716&amp;idx=2&amp;sn=7a2368a84f8b89e78f647644e2b23878&amp;chksm=bef910a2898e99b4fe5859cea11dfea9923a8d8b8ede2fd96a52b6a0d7ae6fe96dae48cacca4&amp;scene=21#wechat_redirect">☛ Oracle安全防范经验：使用应用上下文提升性能及安全</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628632&amp;idx=4&amp;sn=d4ab2f8bcab6a5ea32c4380dae1eae78&amp;chksm=bef91c9e898e95885d6cf2309e8ecb027e977756d68533e330fe8556fd04799aeefd3d843f17&amp;scene=21#wechat_redirect">☛ Oracle Dataguard 管理命令</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650647989&amp;idx=4&amp;sn=67d3af044cdabc0be001ef5b653758a2&amp;chksm=bef9d333898e5a25393dc114775a7da9ae37004c89ed5e2244669f1441629fd067cecb8a8f56&amp;scene=21#wechat_redirect">☛ Oracle 常用的十一大操作指令</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628579&amp;idx=2&amp;sn=f540100499026d25f73642fdf8674bfa&amp;chksm=bef91f65898e9673df132de6b5fcdf4e401ce76f891fedd9a50d3de2d969e8a646d7c9bab6f3&amp;scene=21#wechat_redirect">☛ Oracle数据库知识点：truncate和delete区别</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628848&amp;idx=2&amp;sn=97a7026d50fca057217443c7ebde0b6e&amp;chksm=bef91c76898e9560c16176506dcd538d1ab3274a69728c5849fdc3716853ab35ad0002ac09ec&amp;scene=21#wechat_redirect">☛ Oracle 数据库知识点：逻辑备份</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630022&amp;idx=3&amp;sn=831bd27056a473dfaa2681d99cc31f6b&amp;chksm=bef91900898e901655d1a5d7be0a2c28db28a712abfb0942a7b8d878165163c68d17fd1d8a72&amp;scene=21#wechat_redirect">☛ Oracle 数据库知识点之ROWNUM</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650635654&amp;idx=3&amp;sn=1b59e25049c1386e93cae0de18ab6bc0&amp;chksm=bef90300898e8a16f38c2339a073c4bac5a2c4bd23bf047275626c459d08d2811b60a08fe699&amp;scene=21#wechat_redirect">☛ Oracle 数据库隐含参数使用的原则和方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650635938&amp;idx=2&amp;sn=f86fada4d26745fa9fc483d993ed3682&amp;chksm=bef90024898e893218a4381e97dab55ed848e47cda43b717fba72ba10929ba59509b4e2fd0ad&amp;scene=21#wechat_redirect">☛ 怎样删除 TSM 中 Oracle 数据库的备份文件？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628008&amp;idx=3&amp;sn=645ddfd7e35ffc11f0e231e9b326a8fa&amp;chksm=bef9212e898ea8385cea95722d3d8788d3ce2e2889ce3c54547f120030dc41839950c0703c44&amp;scene=21#wechat_redirect">☛ 使用TSM备份Oracle，怎么设置通道更好？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627988&amp;idx=2&amp;sn=bf7f8a75208e4878bda2ca00b986c6ae&amp;chksm=bef92112898ea8046cbfd5f49a1c033f0fa653eec06c2cc8a89e50b59fb72f0e68f05fb060a5&amp;scene=21#wechat_redirect">☛ oracle常用命令，适合新手</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629131&amp;idx=5&amp;sn=fab72d2dc053ba2581301ce8142657c2&amp;chksm=bef91a8d898e939b16313a89d1791ea542c4dae75099aeaa7ba80fd2302927b8463ccc3ab0e7&amp;scene=21#wechat_redirect">☛ Oracle初学者必知的100个问题</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650619237&amp;idx=3&amp;sn=90f48b6e83f78b31a5782bb6776f0706&amp;scene=21#wechat_redirect">☛ Linux 下安装Oracle</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=401927427&amp;idx=2&amp;sn=ae95bf689c2bba7cd99e9e5a7fc1036c&amp;scene=21#wechat_redirect">☛ PowerLinux 服务器上安装 Oracle</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=401916175&amp;idx=2&amp;sn=220361dd965f043d296e554593fff398&amp;scene=21#wechat_redirect">☛ TSM for oracle 实施手册</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632254&amp;idx=1&amp;sn=6c302e73435daf19a27163d0ebed3f62&amp;chksm=bef90eb8898e87ae09c8bb08ae3584c2f44c6e91d2dc8d819b3e8c6b18f1d9f185a2dd31fb5c&amp;scene=21#wechat_redirect">☛ AIX HACMP Oracle 双机集群实施步骤</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632734&amp;idx=1&amp;sn=b4452c05a8029d5b09322c4853fb20b2&amp;chksm=bef90c98898e858ebcecadbb93bae36505804e1d3981ce2b6cbabf6bc4b8999b4cb377cee8cf&amp;scene=21#wechat_redirect">☛ Oracle RMAN (基于catalog) 配置与使用步骤</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632862&amp;idx=1&amp;sn=98d66433ecceaab4ba917d13e1fc4d32&amp;chksm=bef90c18898e850ec98f6a226f09126af47eb43ec2fb837d77bbf76a6c83fd6cead5fcdad1d7&amp;scene=21#wechat_redirect">☛ Oracle数据库备份还原标准化实施部署指南</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632499&amp;idx=1&amp;sn=13e7512e42a0361a0489c5c17aceab0a&amp;chksm=bef90fb5898e86a3e376c7d3b14ef3bb32c40e55ffbca0891e829d610a3c809ce4878b647d00&amp;scene=21#wechat_redirect">☛ Oracle数据库巡检模板</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650636429&amp;idx=2&amp;sn=7b5c9ff322106ea03ba417279bf9ff42&amp;chksm=bef9fe0b898e771d6a990e8f543394defe16e749a4bfb51bd1e293ce23e13dff2cd56d6ce936&amp;scene=21#wechat_redirect">☛ Oracle 数据库维护</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650631397&amp;idx=1&amp;sn=37f5d0a09166c4c9a3deb8684fa69612&amp;chksm=bef91263898e9b75e16306b8e943290f852dc3e1f501035b15fc95e3167b74b202c49d1b5210&amp;scene=21#wechat_redirect">☛ 基于Oracle数据库的数据实时查询系统基础架构部署</a></p>
<p>
 <br>
</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650644906&amp;idx=2&amp;sn=5d8686f10d115774ab5cd1d3bd5e3fa2&amp;chksm=bef9df2c898e563ae1c6fd75e56c583862736790988b765bc34d46f3490b7f3386d86c846148&amp;scene=21#wechat_redirect">☛ MySQL 运维中的疑难问题解读</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650647474&amp;idx=4&amp;sn=57615dae984554845a651ccd2625ed87&amp;chksm=bef9d534898e5c229aec89d8b4d1f228f4a9dc660fa956fd0ba59a65f4c74ce26f45500aeb25&amp;scene=21#wechat_redirect">☛ 用这些简单手段就可以分析 MySQL 瓶颈</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642229&amp;idx=1&amp;sn=67baa0bde5991a76380707c89438c010&amp;chksm=bef9e9b3898e60a5688486bbdb5ab0fc3f424069f6a70d027339408b8fc42be239573977f064&amp;scene=21#wechat_redirect">☛ MySQL 入门知识</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650637489&amp;idx=3&amp;sn=0b2faa8cbc055776f56ae886838d0617&amp;chksm=bef9fa37898e7321ce124b654939a5cce350a1f2daf22975822f256db244bb06b04d62879054&amp;scene=21#wechat_redirect">☛ MySQL 安全规范</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638994&amp;idx=3&amp;sn=8212549c2f89b0d0f603735c38ef5fc1&amp;chksm=bef9f414898e7d02cb6a36f99539f4d1f7d105ad1e721e051fb8b6c098596478d5466a199c21&amp;scene=21#wechat_redirect">☛ MySQL 数据交互原理与性能问题分析</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642025&amp;idx=1&amp;sn=920b2821c8305a33009001f3d23efdaf&amp;chksm=bef9e8ef898e61f97f161f20b481791b4fdf78d1011afb39f51464209f718e5801545f9b4e64&amp;scene=21#wechat_redirect">☛ MySQL 18个难点解读：涉及性能问题排查、优化及高可用、安全、迁移等</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650646775&amp;idx=1&amp;sn=2d4b5c80a4966c84cdfee05766eb2054&amp;chksm=bef9d671898e5f676bbee456feaf46163762efbfaa9e95026228958b9ad6015d5357d3e05408&amp;scene=21#wechat_redirect">☛ Oracle 迁移至 MySQL、PG等分布式数据库，可能遇到的12个典型问题</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650643610&amp;idx=1&amp;sn=6747bf6ffe710cf64936a36a38cc5a49&amp;chksm=bef9e21c898e6b0a56eeed2024e615ee1848ebd12b2d2bf5789c7a00590784a08c8bff1a6b50&amp;scene=21#wechat_redirect">☛ 基于 Prometheus 的云上 MySQL 监控实践</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650624749&amp;idx=3&amp;sn=302d3c302357cf7aa82e2d148a9a362c&amp;chksm=bef92c6b898ea57de131ec4083672ce3f25afdacf8a72857720b5a781ed6bf0ec961a725f187&amp;scene=21#wechat_redirect">☛ MySQL字符集介绍及乱码解决方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650632841&amp;idx=2&amp;sn=22fc33a7a90f12f586f8c7e2e37d67d5&amp;chksm=bef90c0f898e851990a7434fed265b61c255c227aedbd9714d99920b5b4a3de76e68574e8a8b&amp;scene=21#wechat_redirect">☛ 不停机不影响业务变更表结构 \| MySQL 实用技巧 7 则</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650635947&amp;idx=3&amp;sn=e170830fbd244915f24dab6aad4e28ed&amp;chksm=bef9002d898e893bfa11372e47e57d80c9944e503e535839154bc910899fba5fd224d4ead945&amp;scene=21#wechat_redirect">☛ mySQL 中分组函数的写法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627293&amp;idx=2&amp;sn=c4f8c9ca259d6a821a37577962d5c1e2&amp;chksm=bef9225b898eab4dd4dc89ed51bd0a1d6b0f9c2469f0f924429ab37dd9e8f69019ad2b9120c0&amp;scene=21#wechat_redirect">☛ MySQL 数据库基本命令汇总整理，需要的赶紧学起来</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628422&amp;idx=2&amp;sn=f671b704fd5775e110a26379e33d6ad9&amp;chksm=bef91fc0898e96d6e8f3234d7c0aaac13798ec290f3a9d06a3ede3fcff302f73765d88aaaf68&amp;scene=21#wechat_redirect">☛ MySQL分区实战以及分区的管理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630619&amp;idx=5&amp;sn=cf35371e224e46eeec8a9b834ca933b6&amp;chksm=bef9175d898e9e4bb1433192a337ba70e241498b89fc601aa3c42d7bc1ce36dd313992b0239b&amp;scene=21#wechat_redirect">☛ 详解 MySQL 基准测试和 sysbench 工具</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650634037&amp;idx=3&amp;sn=d1ad11e6a6d39b67a87f1a35a3eb4749&amp;chksm=bef909b3898e80a5e09b2573e55d1acb69d76d11670a081006a61bcff36c4aa538bb81b2d5f8&amp;scene=21#wechat_redirect">☛ MySQL \| unrecognized service 错误解决办法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650627360&amp;idx=1&amp;sn=49864134efd1f676612c622535da37d0&amp;chksm=bef923a6898eaab0ca5b6fed3f626169d176f61cdb0f377c56114b5ce0d44fea68f67f9d53e5&amp;scene=21#wechat_redirect">☛ 不停机不影响业务，DBA必掌握：MySQL 可以这样优雅地变更表结构</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650637298&amp;idx=2&amp;sn=29f6de222aaefd48558d3177fbd0e20c&amp;chksm=bef9fd74898e74625ca4780d26649a4cdbf00458b04e875695040e9f60ca3e0f8b3e487b7322&amp;scene=21#wechat_redirect">☛ MySQL 分区和分表总结</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650628632&amp;idx=5&amp;sn=5db3460d1300e7791e8eabfd9087f842&amp;chksm=bef91c9e898e9588492709bc3844081404263a86abd3f587c12c93c8b5d27fd004709c00d743&amp;scene=21#wechat_redirect">☛ MySQL分区中可能遇到哪些坑点和问题？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650636845&amp;idx=2&amp;sn=58b5361ff1f6f8929d3c78fc63813eb7&amp;chksm=bef9fcab898e75bd5ad507726b9b88d00e5ccc1d745e15199a3c1a66a76adb27b4b447f00be0&amp;scene=21#wechat_redirect">☛ MySQL 诊断分析低效 SQL 的方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650630289&amp;idx=4&amp;sn=a586b5105252f6472f1b10c0586e69de&amp;chksm=bef91617898e9f014d34f003e4f31d22b996edfb26c24b71b1f8d6e5e21879e329c18f3562f8&amp;scene=21#wechat_redirect">☛ 使用 Percona 的 Zabbix 插件监控 MySQL</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650633098&amp;idx=1&amp;sn=3c4bf83d477c12068f7a1e633861e954&amp;chksm=bef90d0c898e841aa2286e42fb6faa93d2b0fb9d53721e9d02d3edd633ddff92991aab91b63d&amp;scene=21#wechat_redirect">☛ MySQL 数据库服务 CPU 高？案例说明如何分析与优化</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638915&amp;idx=1&amp;sn=0e7398739cf0d259d096d4e2cb39f3cb&amp;chksm=bef9f4c5898e7dd394c69225c18087e85209e9429ebf8cf1264ccaeb06b9fda8a441d5b18898&amp;scene=21#wechat_redirect">☛ MySQL 集群架构、性能问题排查和优化方法</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650626856&amp;idx=2&amp;sn=ee7bf96c1a3885815e3b31e77db220e5&amp;chksm=bef925ae898eacb832723b4efbfc66044c1737237e98f5386cf3fb432740965e21372a692a8b&amp;scene=21#wechat_redirect">☛ MySQL备份的几种常用方法与恢复步骤</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638277&amp;idx=2&amp;sn=c4818791334ed4440875674242777c5d&amp;chksm=bef9f943898e705583b5b5e826aff9702f36eef813c7c8492e960cc69297bde0226cde9a4272&amp;scene=21#wechat_redirect">☛ MySQL 双活同步复制的四种方案</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650639133&amp;idx=3&amp;sn=3bb9d72ab72f42ee89aecda130819a67&amp;chksm=bef9f59b898e7c8d52e4ce185fe39b9aa75e04bb3c2d44a719a2f5b30907286e50dc574700dd&amp;scene=21#wechat_redirect">☛ MySQL 8.0 异步复制的三种方式</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629405&amp;idx=2&amp;sn=666eac9efb01619303056d0e0fd2d324&amp;chksm=bef91b9b898e928ddf89bf732ca4899b0f7d9c65cc7b03c57ea439fc5ee8f5e6a9b9881c9d18&amp;scene=21#wechat_redirect">☛ 金融级MySQL高可用方案选型</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650642760&amp;idx=2&amp;sn=eb333253bb2bad25325097d8b383118a&amp;chksm=bef9e7ce898e6ed85008883895688d69e3af94c5a89b4a308798b6ee90d8eb20133493ec98db&amp;scene=21#wechat_redirect">☛ 银行应用 MySQL 需解决哪三个问题？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650629969&amp;idx=1&amp;sn=23d17536b2b2fff5c8aef637de32cf2b&amp;chksm=bef919d7898e90c1dafcc56b31b4eba8aea8ad453ab8af4d2489dbfceb6f7a15cf60c8f38bb7&amp;scene=21#wechat_redirect">☛ 中小金融机构 MySQL 数据库安全设计及防范，八个"如何"该怎么做？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650636086&amp;idx=3&amp;sn=d4bdc06b51e204ea85315d0c2a8d8f80&amp;chksm=bef901b0898e88a6a8443723515e518aa0b9491588ec4db3bba086bb03fdc6a4218d2ea874b6&amp;scene=21#wechat_redirect">☛ 一个高并发、大数据量的业务环境中，你会选Oracle还是mySQL？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650637620&amp;idx=2&amp;sn=dea7bb7b1a58a5e81989355694a012cd&amp;chksm=bef9fbb2898e72a4fc48ae9fcbb82fb7d56863fc97d6efef27875ba270c3f9bedfbf049d6b32&amp;scene=21#wechat_redirect">☛ Oracle / MySQL 数据库高可用方案三大难点问题</a></p>
<p>
 <br>
</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650640634&amp;idx=1&amp;sn=d5c7885df77afb50a483ea2e3062bc78&amp;chksm=bef9ee7c898e676a611b2c49ac122af9bbe8d828070c9a3d7f38543a97b4fb020f4fb228ea19&amp;scene=21#wechat_redirect">☛ Db2 数据库 SQL 调优技巧总结</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650639560&amp;idx=1&amp;sn=63d15df3dd7e70306e5e17e122746ace&amp;chksm=bef9f24e898e7b58f596c089638735dab6b4c76b78f6e53f6895f5d47cc6dc6bbd0a9e6fec31&amp;scene=21#wechat_redirect">☛ 在单个 SUSE Linux 虚拟机中安装 Db2 pureScale</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650639031&amp;idx=1&amp;sn=1a85dc4d2ba35507d6a7bda0b9ff0126&amp;chksm=bef9f431898e7d272b57e5cd62978fdc3818909d50b190cb7799bc335f4a8f82b27082da04da&amp;scene=21#wechat_redirect">☛ 在单个 RedHat 虚拟机中安装 Db2 开发版中的 pureScale 特性</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638808&amp;idx=1&amp;sn=db7762c7420e2d861803632d613af590&amp;chksm=bef9f75e898e7e488622a8fd2754372d75263ca11130ca2a7c1126cbf38538cf32eb7fe9f423&amp;scene=21#wechat_redirect">☛ 在单个 RedHat 虚机中安装 Db2 开发版中的 HADR 功能</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638597&amp;idx=3&amp;sn=76105dad093cbf1c0e8fc4d52306b58e&amp;chksm=bef9f783898e7e95445e46269469295772589ebf32733eb9d836fca02c1bc69390ea6b41e211&amp;scene=21#wechat_redirect">☛ Db2 BLU 仅仅是 “列表” 那么简单吗？</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638503&amp;idx=3&amp;sn=1eaa8ff9606c8973129ef8b4c40f1106&amp;chksm=bef9f621898e7f3756bf63809ee215d826af5ff2c0022d0b4667eb78260519b4cfd6d5533215&amp;scene=21#wechat_redirect">☛ 一个与DB2 “内部时钟” 相关的有趣问题</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638403&amp;idx=1&amp;sn=61e13b4b39dd16a7c8ac3ebc4504fd32&amp;chksm=bef9f6c5898e7fd3ca884b9a96a0d92a142cdc10762ad904b3df14d67a2f3d6b7be999581498&amp;scene=21#wechat_redirect">☛ Db2 数据库 HADR 部署实施 17 个疑难问题解答</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638385&amp;idx=4&amp;sn=9c4d6d6cf90cf68f0f16e3240a7b4955&amp;chksm=bef9f6b7898e7fa1611871214fca93f59385f80e262cb9fd26361054bc7b45465a9dbc99880e&amp;scene=21#wechat_redirect">☛ DB2 pureScale 架构知识梳理</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638320&amp;idx=2&amp;sn=41f1473a9cc4e5409552c19dede9a9e0&amp;chksm=bef9f976898e7060f6225843b303d4568b1930cd34bd16191f7807eb1b67cac15c5aacde280d&amp;scene=21#wechat_redirect">☛ Db2 数据库 HADR 部署三个难点</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638007&amp;idx=1&amp;sn=ce71b621a9085e066ae7cfe9543871ed&amp;chksm=bef9f831898e7127fe73dc9a776a1bf4f2ebaba461a3790fced417c57cc3352934163d7c687b&amp;scene=21#wechat_redirect">☛ Db2 高可用及优化 7 个难题解析</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650639857&amp;idx=1&amp;sn=36737798c72fb06311487ea171de435b&amp;chksm=bef9f377898e7a616fb52bb590c9cdb827bbec0f4070e1d96459c0eed42fa4bd14ae5d0f8557&amp;scene=21#wechat_redirect">☛ 与数据坏页的战争——一次Db2数据库损坏的修复过程</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650638217&amp;idx=1&amp;sn=438b14ab2c5fe32d2fa49d460e6d7152&amp;chksm=bef9f90f898e701935c5cdac0d7f136a6c5ccfc59ff8b81eea0c684cd5375da0cbed155f82ce&amp;scene=21#wechat_redirect">☛ 某省级机构 Db2 数据库 HADR 部署实施方案</a></p>
<p>
 <br>
</p>
<blockquote>
 <p><strong>觉得本文有用，请</strong> <strong>转发、点赞</strong> <strong>或点击</strong> <strong>“在看”</strong> <strong>，让更多同行看到</strong></p>
</blockquote>
<p>
 <br>
</p>
<p>资料/文章推荐：</p>
<ul>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650644887&amp;idx=1&amp;sn=d4449487b6e0354e876c4807002c01ec&amp;chksm=bef9df11898e56078defe4156d89a6822635d39c2ea22df401369f91da94f5c42de20c3fbd08&amp;scene=21#wechat_redirect">100 篇银行 IT 建设实用参考！请收好，一定有你需要的→</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650645294&amp;idx=1&amp;sn=09a094bebad33563fe48ee9a0a8af453&amp;chksm=bef9dda8898e54bed262bab7f944ee531cd7844e2506b215480e5cdb76d5e34eb9b869c2d2e3&amp;scene=21#wechat_redirect">75 篇容灾\&amp;备份实用参考！请收好</a></p>
 </li>
</ul>
<p>
 <br>
</p>
<p>点击阅读原文关注社区<strong>“数据库”</strong>技术主题<strong></strong> ，将会不断更新优质资料、文章，您也可以前往提出疑难问题，与同行切磋交流。地址：https://www.talkwithtrend.com/Channel/597</p>
<p>
 <br>
</p>
<p><strong>下载 twt 社区客户端 APP</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528943803.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528943825.png&amp;size=m" alt=""></p>
<p>长按识别二维码即可下载</p>
<p>或到应用商店搜索"twt”</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730528943847.png&amp;size=m" alt=""></p>
<p>*本公众号所发布内容仅代表作者观点，不代表社区立场</p>
<p>原文链接: http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650648050&amp;idx=1&amp;sn=17088cad3c796acf1ab9e4a6f6df6c19&amp;chksm=bef9d374898e5a622c40e7fa94058c68ad745fb7ff3cb2785b81a4217fdc4350a1b32803da85&amp;scene=21#wechat_redirect</p>]]></description><guid isPermaLink="false">/archives/eaf52b1d-1dea-4aaa-99ac-ff53b7683fea</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:43:55 GMT</pubDate></item><item><title><![CDATA[不懂组网方案、网络平面、网络架构？看这一篇就够了]]></title><link>https://wiki.dgfygk.com/archives/bf7b6ee8-e544-4be4-b731-4697ef376cdb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%8D%E6%87%82%E7%BB%84%E7%BD%91%E6%96%B9%E6%A1%88%E3%80%81%E7%BD%91%E7%BB%9C%E5%B9%B3%E9%9D%A2%E3%80%81%E7%BD%91%E7%BB%9C%E6%9E%B6%E6%9E%84%EF%BC%9F%E7%9C%8B%E8%BF%99%E4%B8%80%E7%AF%87%E5%B0%B1%E5%A4%9F%E4%BA%86&amp;url=/archives/bf7b6ee8-e544-4be4-b731-4697ef376cdb" width="1" height="1" alt="" style="opacity:0;">
<p><strong>点击上方蓝色字体关注，知识分享</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532642608.png&amp;size=m" alt=""> <strong>0</strong> <strong>1</strong></p>
<p><strong>前言</strong></p>
<p><strong>大家好，云智能知识分享，</strong><strong>不懂组网方案、网络平面、网络架构？看这一篇就够了，下面我们一起来看下有哪些知识点！</strong>
 <br>
 <strong>0</strong> <strong>2</strong></p>
<p><strong>正文</strong> <strong>组网方案</strong> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532642752.png&amp;size=m" alt=""></p>
<p>为了提升网络的安全性，使业务接口的流量负载均衡，NCE把网络划分为多个相互隔离的网络平面，分别为硬件管理网络、客户端/北向网络、南向网络和异地容灾网络。NCE组网方案图如下。</p>
<p><strong>图1 组网方案</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532642808.png&amp;size=m" alt=""></p>
<p><strong>表1 NCE组网方案解读</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532642870.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><strong>网络平面划分</strong> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532642904.png&amp;size=m" alt=""></p>
<p>首先我们了解一下什么是bond。</p>
<p>bond是一种虚拟网卡技术。通过将服务器的两个物理网口配置为一个bond，这两个网口不仅被绑定为一个IP地址，MAC地址也被自动设定为同一个，进而构成一个虚拟的网口。</p>
<p>bond绑定的网口支持主备模式（mode1）和负载均衡模式（mode0）。</p>
<p>主备模式：当其中一个网口出现故障时，会自动切换到另外一个正常的网口进行数据处理，从而实现网口之间的保护。NCE系统默认采用主备方式绑定网口。</p>
<p>
 <br>
</p>
<p>负载均衡模式：远端机器向服务器请求数据，服务器上的网口接到请求后，网口根据某种算法智能决定由谁来处理数据的传输，从而提高主机的网络吞吐量和可用性。</p>
<p>NCE物理机安装场景下，服务器有8个物理网口，最多支持配置4个bond。下图以2288X V5服务器（4*2 GE电口）为例说明bond的组成，操作系统安装完成后服务器的网口会按照下图所示的规格命名，后续会按照ethx的方式来标识服务器上的网口。</p>
<p><strong>图2 bond成员规格</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532642933.png&amp;size=m" alt=""></p>
<p>接下来我们了解一下NCE网络平面和bond之间的对应关系：</p>
<p>NCE系统最多支持4类网络平面，每一类网络平面的作用如下表，每一类网络平面使用一个bond配置IP地址。</p>
<p><strong>表2 网络平面说明</strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643069.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643159.png&amp;size=m" alt=""></p>
<p>下面以2288X V5服务器（4*2 GE电口），搭配S5335交换机为例说明。网络架构图以分平面出线方式呈现。</p>
<p><strong>图3 网络架构图</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643258.png&amp;size=m" alt=""></p>
<p><strong>表3 网络架构图说明</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643346.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><strong>硬件连线介绍</strong> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643412.png&amp;size=m" alt=""></p>
<p>参考下图使用标准网线连接PC、服务器和交换机。根据分平面出线或汇聚出线方式，提前布线，预留对接客户网络的连线。下图以2288X V5服务器（4*2 GE电口）和S5335交换机为例介绍硬件连线。</p>
<p>采用分平面出线方式，具体的线缆连接如下。</p>
<p><strong>图4 硬件连线（分平面）</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643441.png&amp;size=m" alt=""></p>
<p>采用汇聚出线方式，具体的线缆连接如下。</p>
<p><strong>图5 硬件连线（汇聚）</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643537.png&amp;size=m" alt=""></p>
<p><strong>表4 服务器物理网口与交换机网口对应关系</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643636.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643671.png&amp;size=m" alt=""></p>
<p>来源：网络</p>
<p>如有侵权，请联系删除
 <br>
 <strong>最新精品资料介绍</strong>
 <br>
 <strong>近期发布了</strong><strong>2023-2024</strong><strong>年的公众号文章。里面有方案，有图片，基本涵盖了智能化弱电行业的各个系统的文章，精品资料，每一篇都带着项目名称，方便大家查找相关内容。此资料非常适合新人学习，尤其没有时间、又不知道怎么搜索资料的，建议弄一套，各种类型方案，绝对有帮助！</strong></p>
<p><strong>文章中的</strong><strong>PPT</strong><strong>方案，</strong><strong></strong>共计<strong></strong><strong></strong>170<strong></strong><strong></strong>个PPT，<strong></strong> <strong></strong>最全完整<strong></strong><strong></strong>系统图集<strong></strong><strong></strong>，行业标准<strong></strong><strong></strong>规范<strong></strong><strong></strong>及<strong></strong><strong></strong>施工图集<strong></strong><strong></strong>，<strong></strong><strong></strong>word文档<strong></strong><strong></strong>，<strong></strong><strong></strong>施工组织设计<strong></strong><strong></strong>，<strong></strong><strong></strong>工程量清单<strong></strong><strong></strong>，<strong></strong><strong></strong>图纸<strong></strong><strong></strong>以及<strong></strong><strong></strong>素材<strong></strong><strong></strong>，<strong></strong><strong></strong>VISIO图块<strong></strong><strong></strong>以及<strong></strong><strong></strong>拓扑图<strong></strong><strong></strong>，<strong></strong><strong></strong>项目管理<strong></strong><strong></strong>及<strong></strong><strong></strong>施工方案<strong></strong><strong></strong>，带<strong></strong><strong></strong>报价的工程量清单<strong></strong><strong></strong>，<strong></strong><strong></strong>投标方案<strong></strong><strong></strong>及<strong></strong><strong></strong>施工组织设计<strong></strong><strong>等等。</strong>
 <br>
 <strong>点击下方查看： 
  <br>
  <a href="http://mp.weixin.qq.com/s?__biz=Mzg2MTU3MDkzNQ==&amp;mid=2247515023&amp;idx=1&amp;sn=5f65f2a85ef976fa620dacfe0516b297&amp;chksm=ce17e997f9606081bfbd00f41c3d3ee6d049564d1d13d47920552fcfc83a8d4f62ed3ffe9447&amp;scene=21#wechat_redirect">2021年-2022年资料汇总，升级最新资料方案，赠送多个最新弱电资料</a></strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643735.png&amp;size=m" alt=""> END <strong>关于作者，关于项目合作，关于交流群</strong>
 <br>
 <strong>我是作者云智能，一位弱电经营者。现主要培训，智能化行业发展趋势，业余时间运营这个公众号。</strong>
 <br>
 <strong>了解更多弱电技术，弱电方案，回复公众平台咨询！</strong> <strong>感谢朋友们的光临，我们一直在努力！</strong> <strong>感谢这一刻的陪伴！</strong> <strong>您的无私分享是我们传递价值的动力！</strong></p>
<p><strong>如要加入弱电行业微信交流群，请加弱电微信好友</strong><strong>L1609910173</strong><strong>，好友申请注明"弱电智能化”，并附"公司+姓名</strong>
 <br>
 扫码关注我们
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532643766.png&amp;size=m" alt=""> 期待你的
 <br>
 分享
 <br>
 点赞
 <br>
 在看</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/onZqY0cbPoruCiPHvygvJg</p>]]></description><guid isPermaLink="false">/archives/bf7b6ee8-e544-4be4-b731-4697ef376cdb</guid><dc:creator>黄健</dc:creator><category>IT运维</category><category>网络与通信</category><pubDate>Sat, 2 Nov 2024 07:43:30 GMT</pubDate></item><item><title><![CDATA[Linux 运维七大实用技能]]></title><link>https://wiki.dgfygk.com/archives/b4c1f3fa-2e65-48b2-8c66-1bb105194f57</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%20%E8%BF%90%E7%BB%B4%E4%B8%83%E5%A4%A7%E5%AE%9E%E7%94%A8%E6%8A%80%E8%83%BD&amp;url=/archives/b4c1f3fa-2e65-48b2-8c66-1bb105194f57" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532663429.png&amp;size=m" alt=""> Linux运维工作中常用的几大技能，<strong>董志卫</strong>整理</p>
<p><strong>一、初始化配置</strong></p>
<p><strong>禁用服务</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532663579.png&amp;size=m" alt=""></p>
<h2><strong>禁用SeLinux</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532663661.png&amp;size=m" alt=""></p>
<h2><strong>配置YUM源配置</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532663725.png&amp;size=m" alt=""></p>
<p>可以配置为光盘，内部YUM源或EPEL等</p>
<h2><strong>常用软件安装</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532663875.png&amp;size=m" alt=""></p>
<h2><strong>安装xwindows</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664015.png&amp;size=m" alt=""></h2>
<h2><strong>配置ntp</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664141.png&amp;size=m" alt=""></h2>
<p>Crontab 添加如上记录，指定内部ntp服务器</p>
<h2><strong>SSH 登录设置</strong></h2>
<p>修改ssh 禁用DNS 选项:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664200.png&amp;size=m" alt=""></p>
<p>添加允许指定用户登录：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664263.png&amp;size=m" alt=""></p>
<h2><strong>上传扫描工具</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664327.png&amp;size=m" alt=""></h2>
<p>网络上有该脚本，下载自行使用</p>
<h2><strong>修改历史记录格式</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664417.png&amp;size=m" alt=""></h2>
<p><strong>二、安全加固</strong></p>
<h1>本次安全加固内容主要参考的是Redhat和Centos系列版本系统：</h1>
<p>参考链接
 <br>
 http://www.centoscn.com/CentosSecurity/CentosSafe/2015/0315/4881.html</p>
<h2><strong>注释掉系统不需要的用户和用户组</strong></h2>
<p>注意：不建议直接删除，当你需要某个用户时，自己重新添加会很麻烦。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664510.png&amp;size=m" alt=""></p>
<h2><strong>关闭系统不需要的服务</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664647.png&amp;size=m" alt=""></p>
<h2><strong>给下面的文件加上不可更改属性，从而防止非授权用户获得权限</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664723.png&amp;size=m" alt=""></h2>
<h2><strong>限制不同文件的权限</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664848.png&amp;size=m" alt=""></h2>
<h2><strong>禁止使用Ctrl+Alt+Del快捷键重启服务器</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532664980.png&amp;size=m" alt=""></p>
<h2><strong>使用yum update更新系统时不升级内核，只更新软件包</strong></h2>
<p>注意：由于系统与硬件的兼容性问题，有可能升级内核后导致服务器不能正常启动，这是非常可怕的，没有特别的需要，建议不要随意升级内核。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665062.png&amp;size=m" alt=""></p>
<h2><strong>关闭Centos自动更新</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665135.png&amp;size=m" alt=""></h2>
<h2><strong>关闭多余的虚拟控制台</strong></h2>
<h2>我们知道从控制台切换到 X 窗口，一般采用 Alt-F7 ，为什么呢？因为系统默认定义了 6 个虚拟控制台，</h2>
<p>所以 X 就成了第7个。实际上，很多人一般不会需要这么多虚拟控制台的，修改/etc/inittab ，注释掉那些你不需要的。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665273.png&amp;size=m" alt=""></p>
<h2><strong>修改history命令记录</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665375.png&amp;size=m" alt=""></h2>
<h2><strong>隐藏服务器系统信息</strong></h2>
<p>在缺省情况下，当你登陆到linux系统，它会告诉你该linux发行版的名称、版本、内核版本、服务器的名称。</p>
<p>为了不让这些默认的信息泄露出来，我们要进行下面的操作，让它只显示一个"login:“提示符。</p>
<p>删除/etc/issue和/etc/issue.net这两个文件，或者把这2个文件改名，效果是一样的。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665501.png&amp;size=m" alt=""></p>
<h2><strong>优化Linux内核参数</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665585.png&amp;size=m" alt=""></h2>
<h2><strong>系统优化</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665665.png&amp;size=m" alt=""></h2>
<h2><strong>服务器禁止ping</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665812.png&amp;size=m" alt=""></h2>
<h2><strong>检查口令策略设置是否符合复杂度要求</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532665939.png&amp;size=m" alt=""></h2>
<h2><strong>检查登录提示-是否设置登录成功后警告Banner</strong></h2>
<p>修改文件/etc/motd的内容，如没有该文件，则创建它。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666020.png&amp;size=m" alt=""></p>
<h2><strong>检查是否设置登录超时</strong></h2>
<p>执行备份：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666077.png&amp;size=m" alt=""></p>
<p>修改/etc/csh.cshrc文件，添加如下行：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666141.png&amp;size=m" alt=""></p>
<p>改变这项设置后，重新登录才能有效
 <br>
 <strong>三、多路径设置</strong></p>
<p>随着X86 环境的普及化，Linux 的市场占有率也越来越高，为了方便后续的设备管理我们和Linux 自带多路径软件的成熟化，我们在日常的设备多路径软件选择方面经常会首先考虑使用DM 软件，本次多路径设置主要是结合REDHAT和CENTOS自带软件DM— Multipath</p>
<p>参考：
 <br>
 <a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650626579&amp;idx=2&amp;sn=5ebc8b566abd20d9a20e84ada309e183&amp;chksm=bef92495898ead8341ed5507f72acd1c37b5303d6a87331431588cfa547668c37ed55751a63e&amp;scene=21#wechat_redirect">Linux 操作系统层面Multipath 配置使用技巧及注意事项</a>（点击标题可读）
 <br>
 <strong>四、系统异常性能指标获取</strong></p>
<h2><strong>CPU占用最高的10个进程</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666202.png&amp;size=m" alt=""></p>
<h2><strong>内存占用最高的10个进程</strong></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666301.png&amp;size=m" alt=""></h2>
<h2><strong>虚拟内存使用最多的前10个进程</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666442.png&amp;size=m" alt=""></p>
<h2><strong>查看系统负载</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666502.png&amp;size=m" alt=""></p>
<h2><strong>统计当前连接数</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666562.png&amp;size=m" alt=""></p>
<h2><strong>当前连接数最多的10个进程</strong></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666633.png&amp;size=m" alt="">
 <br>
 <strong>五、LVM日常使用</strong></p>
<p>LVM在日常运维工作当中占据着很大的比重，在此我列举LVM常见的日常操作及步骤。</p>
<ol>
 <li>添加一个磁盘到OS，格式化文件系统使用，参考如下：</li>
</ol>
<p>扫描磁盘</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666781.png&amp;size=m" alt=""></p>
<p>这个其中每个步骤就不在做详细解释，有兴趣的可以独自搜索</p>
<p>2.文件系统的扩容和缩小（根文件系统缩小要相当的慎重）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666851.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532666937.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667085.png&amp;size=m" alt=""></p>
<p>在线扩容文件系统：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667235.png&amp;size=m" alt=""></p>
<p>缩小文件系统：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667381.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667479.png&amp;size=m" alt=""></p>
<ol>
 <li>在线删除一个共享磁盘LUN</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667550.png&amp;size=m" alt=""></p>
<p>例如：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667647.png&amp;size=m" alt=""></p>
<p>删除一个不用的lun</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667745.png&amp;size=m" alt=""></p>
<p>删除环节：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667848.png&amp;size=m" alt="">
 <br>
 <strong>六、Linux 运维命令掌握</strong></p>
<p>Linux 下面日常运维使用的命令有太多了，可以根据个人的情况进行适当的记忆。</p>
<p>系统负载：top ，nmon，dstat 等</p>
<p>网络：ss，netstat，route，diag，ping，ip，lsof 等</p>
<p>io：dd，iostat，fio，nmon，dstat，pvs，lvs，vgs 等</p>
<p>内存：free，dstat等</p>
<p>进程：ps，lsof 等</p>
<p>配置：lscpu，lspci，dmidecode,lsscsi，udev 等</p>
<p>设备识别：echo '—' ,rescan-scsi-bus.sh 等</p>
<p>诊断：strace,ltrace等</p>
<p>比如还有find 如何结合xargs ，tree的使用，lsblk 等等，还有很多很多，需要长期的积累，当然主要使用还是配置查看，LVM设置，网络</p>
<p>还有很多成熟的开源和商业产品进行管理，在此不一一列举，感觉可自行百度和google。</p>
<p>目前主流的python，ruby这些语言工具可以根据自身情况选择一个掌握。
 <br>
 <strong>七、诊断工具</strong></p>
<p>在日常的运维过程当中，不免要进行所谓的性能或者故障方面问题的诊断，工具和手段包罗万象，在此列举一些日常使用的工具用于参考。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532667996.png&amp;size=m" alt=""></p>
<p>这里附上一幅Linux 开机流程图，这样很多人就可以更加清楚的了解了Linux在启动的整个流程，便于此类问题的解决。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532668102.png&amp;size=m" alt="">
 <br>
 <strong>八、网络必杀技</strong></p>
<p>Netcat，SSH 几种隧道转发模式 ，lsof，dstat ，ethtool，iptraf，iperf，diag，route 和多个网卡路由及双网卡绑定技术值得了解，这些都是os层面运维网络方面经常会使用到的几个方面</p>
<p>在日常较为常用的操作就是进行双网卡的绑定，下面分享一个绑定的具体流程：</p>
<p>1、cp /etc/sysconfig/network/ifcfg-eth0 /etc/sysconfig/network/ifcfg-eth0.bak</p>
<p>cp /etc/sysconfig/network/ifcfg-eth0 /etc/sysconfig/network/ifcfg-bond0</p>
<p>2、vi /etc/sysconfig/network/ifcfg-eth0，注释所有（除以下两行内容），并将值修改如下：</p>
<p>BOOTPROTO='none'</p>
<p>STARTMODE='off'</p>
<p>3、cp /etc/sysconfig/network/ifcfg-eth0 /etc/sysconfig/network/ifcfg-eth1</p>
<p>4、vi /etc/sysconfig/network/ifcfg-bond0，增加或更新如下内容，其他内容可注释：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532668236.png&amp;size=m" alt=""></p>
<p>说明：以上配置mode=0为负载均衡模式，如果需要配置成主备模式，BONDING_MODULE_OPTS配置如下：</p>
<p>BONDING_MODULE_OPTS='mode=1 miimon=100 use_carrier=0 primary=eth0'</p>
<p>5、rcnetwork restart，重启网络服务生效，并进行测试。</p>
<p>6、cat /proc/net/bonding/bond0 可以查看bonding的状态。</p>
<blockquote>
 <p><strong>觉得本文有用，请</strong> <strong>转发、点赞</strong> <strong>或点击</strong> <strong>“在看”</strong> <strong>，让更多同行看到</strong></p>
</blockquote>
<p>
 <br>
</p>
<p><strong>推荐阅读：</strong></p>
<ul>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650696789&amp;idx=2&amp;sn=50ae552a435fc8af19cbaf316ffdbe25&amp;chksm=befa12d3898d9bc52aed505b665fc06b43a9fa79872b47cc32ecba6866ede8bb9aec0799136a&amp;scene=21#wechat_redirect">Linux系统性能优化：七个实战经验</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650678421&amp;idx=3&amp;sn=b08ea6146f604a450feaeb7bb50c3c88&amp;chksm=befa5a13898dd30517a97283e810c634123851dd677cfde234699be1a471b2c14b0c85cb2e44&amp;scene=21#wechat_redirect">Linux 服务器性能参数指标怎么看？</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650680086&amp;idx=3&amp;sn=f1b750e96225425dced7308ea98444f6&amp;chksm=befa5590898ddc86514d3fa97f54727a60a32c93629c764854aefd7ff0e3892aadc35604e3de&amp;scene=21#wechat_redirect">Linux 系统性能优化 \| 资料</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650677707&amp;idx=2&amp;sn=4e3e2c04e703c450b08f9ad7abcf69a4&amp;chksm=befa5f4d898dd65b0db9adf6ab917a1884b45c254d14d48ded5694514970940249f0aea6a7fe&amp;scene=21#wechat_redirect">运维必会的 30 个 Linux 压缩管理、进程管理、网络管理命令 \| 周末送资料</a></p>
 </li>
 <li>
  <p><a href="http://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&amp;mid=2650677677&amp;idx=2&amp;sn=24002e27d1fb12a0b6dd8bc22dbff613&amp;chksm=befa5f2b898dd63d0f80f001a267826c93feff39aeda5227855b86c70acd2fa38ed62727ae2f&amp;scene=21#wechat_redirect">运维工程师必会的 48 个 Linux 文件管理、磁盘管理命令 \| 周末送资料</a></p>
 </li>
</ul>
<p>
 <br>
</p>
<p><strong>下载 twt 社区客户端 APP</strong></p>
<p>与更多同行在一起</p>
<p>高手随时解答你的疑难问题</p>
<p>轻松订阅各领域技术主题</p>
<p>浏览下载最新文章资料</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532668271.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532668305.png&amp;size=m" alt=""></p>
<p>长按识别二维码即可下载</p>
<p>或到应用商店搜索"twt”</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>长按二维码关注公众号 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532668338.png&amp;size=m" alt=""></p>
<p>*本公众号所发布内容仅代表作者观点，不代表社区立场</p>
<p>原文链接: https://mp.weixin.qq.com/s/_2P_ZWaZ2SgPT_cShb6y7g</p>]]></description><guid isPermaLink="false">/archives/b4c1f3fa-2e65-48b2-8c66-1bb105194f57</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 2 Nov 2024 07:42:52 GMT</pubDate></item><item><title><![CDATA[万字：支付“核心系统”详解]]></title><link>https://wiki.dgfygk.com/archives/d34fc329-f40b-4176-bd59-93ad344e2b4c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%87%E5%AD%97%EF%BC%9A%E6%94%AF%E4%BB%98%E2%80%9C%E6%A0%B8%E5%BF%83%E7%B3%BB%E7%BB%9F%E2%80%9D%E8%AF%A6%E8%A7%A3&amp;url=/archives/d34fc329-f40b-4176-bd59-93ad344e2b4c" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719059.png&amp;size=m" alt="">
 <br>
 专栏作者：隐墨星辰 \| 主编：陈天宇宙 <em>这篇文章也尝试化繁为简，探寻支付系统的本质，讲清楚在线支付系统最核心的一些概念和设计理念。</em></p>
<p><em>虽然支付行业已经过了风头最劲的时光，但跨境支付仍然在蓬勃发展，每年依然有很多新人进入这个行业，这篇文章尝试为这些刚入行的新人提供一点帮助。</em></p>
<p><em>文章只介绍一些支付行业十几年来都没有太大变化的通用知识点。当然不可避免会夹杂一些个人不成熟的见解，请各位以"取其精华，去其糟粕"的精神辩证地看待此文内容。</em></p>
<hr>
<ol>
 <li>
  <h1>十年前各大公司支付系统架构图</h1>
 </li>
</ol>
<p>看一下十年前各大互联网公司支付系统架构图，就会发现最基础的偏底层的那部分到现在也很有参考价值，但是性能、可用性、基础架构等已经发生了天翻地覆的变化。比如根据公开资料，2019 年双11 支付宝的支付峰值为54.4 万笔/秒，真实承载能力应该远高于此。创新的业务也发生了极大的变化，比如京东白条的横空出世，在海外旅游可以直接用支付宝或微信扫码付款等。</p>
<p>晒几个网上公开的图（来源在图片右下角的水印上有），图虽然有点老，但基础的产品功能到现在也适用。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719203.png&amp;size=m" alt=""></p>
<p>某团的：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719373.png&amp;size=m" alt=""></p>
<p>某Q旅游的：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719424.png&amp;size=m" alt=""></p>
<p>某东金融的：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719468.png&amp;size=m" alt=""></p>
<p>某蚁金服的：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719513.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <h1>基本概念</h1>
 </li>
</ol>
<p>下面描述的概念大部分做了极致简化，只是用于入门，对于理解概念应该是够用的。真实的实现会复杂非常多。</p>
<p>这些概念如同支付核心系统拼图的一些小碎片，串起这些小碎片，就是一个完整的支付系统大图。</p>
<p><em>另：后面的描述中，经常混着用"支付系统”、“支付平台”，“支付机构”，“收单机构”，本质是一个东西。在内部来说，就是一个支付系统，但从和外部机构交互来说，就是一个支付平台。对用户来说是支付，对商户来说就是帮商户收单。</em></p>
<h2>2.1. 最简支付流程</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719584.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>这是一个最简化的支付流程。真实的交互比这个复杂得多，单收银台渲染就可以写一整篇文章。但对于讲清楚支付系统的作用，已经足够。</p>
 </li>
 <li>
  <p>从图中可以引申出<strong>支付系统最核心的作用：帮商户收钱</strong> 。所以有牌照的也称"收单机构”。如果没有资质，只是做信息转发，也被称为"收单转接”。</p>
 </li>
 <li>
  <p>有支付当然就有退款、撤销等逆向操作，复杂的跨境支付还会有外汇交易，跨境结算等业务。</p>
 </li>
</ol>
<h2>2.2. 最简清结算流程</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719703.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>这里画的是信息流。</p>
 </li>
 <li>
  <p>银行和支付平台之间是机构对机构的关系，通常使用清算概念，因为金融机构之间大部分情况下会由独立的清算机构完成清算服务。</p>
 </li>
 <li>
  <p>支付平台和商户之间，通常使用结算概念，由支付平台直接打款给商户。（清算与结算区别是中文环境才会有，本质是一个东西）</p>
 </li>
 <li>
  <p>上面画的是结算到商户开在支付平台的内部账户余额，所以需要商户手动提现，支付平台通常也支持直接结算到卡，这样就不需要商户手动提现。</p>
 </li>
 <li>
  <p>清结算三个字还有另外一层含义：清分 + 结算。前者是把钱算清楚，后者是真实打款。也有些公司叫清分清偿，前者算好钱怎么分，后面完成债权任务关系的完结。本质也是一个东西。</p>
 </li>
</ol>
<h2>2.3. 最简本对本收单流程</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719805.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>所谓本对本收单，就是指商户的商品标价币种、向支付系统的下单币种、用户支付币种、商户结算币种都是同一个币种。不涉及到外汇交易。</p>
 </li>
 <li>
  <p>一个中国人拿着中国招商银行信用卡在中国境内通过多多买了两斤山东大樱桃，就是标准的本对本收单。</p>
 </li>
</ol>
<h2>2.4. 最简跨境收单流程</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532719959.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>所谓跨境收单，就是结算给商户的币种和用户支付的币种不一样，需要经过外汇机构换汇。</p>
 </li>
 <li>
  <p>在扣款EUR成功后，支付平台会调用外部的外汇机构进行锁汇（HA）。</p>
 </li>
 <li>
  <p>在银行清算后，支付平台再调用外部的外汇机构进行真正的换汇（TA）。</p>
 </li>
 <li>
  <p>最后支付平台结算给商户USD。</p>
 </li>
 <li>
  <p>上面也是只是跨境的一个小场景，真实的跨境场景极为丰富和复杂。不信你问问这段时间做俄罗斯生意收卢布的朋友们。</p>
 </li>
</ol>
<p>如果换成时序图，如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532720097.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>上面之所以有锁汇，是因为外汇时刻在变化，支付平台不想承担汇损风险，直接在支付款里加点手续费。能力强的支付机构也不需要锁汇，更高风险，但可能有更多收益。</p>
 </li>
 <li>
  <p>还有些渠道直接提供空中换汇的能力。比如土耳其用户使用TRY进行支付，在支付成功后，由渠道侧直接换汇成USD，然后由渠道直接结算USD给支付平台。</p>
 </li>
 <li>
  <p>一般来说，很多国家的货币是受管制的，无法自由出境，如果使用空中换汇直接拿到的就是USD，就比较容易出境。</p>
 </li>
 <li>
  <p>涉及跨境场景下，往往需要设计各种各样的资金流，最主要是考虑合规诉求，某次是收益。如果能力强，利用流动性管理，资金量大，收益还是非常可观的，毕竟外面不像某国要求备付金100%缴存央行，还不给利息。</p>
 </li>
</ol>
<h2>2.5. 稍复杂的跨境支付示例</h2>
<p>我们以最典型的电商购物举个例子（只是举例）：小明使用PayPal在拼多多电商（海外）通过多多钱包（海外）支付了50美金。</p>
<p>经过简化后的交互图如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532720223.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>持牌的第三方支付机构和电商是独立的法律主体，所以多多钱包和多多电商是互相独立的，需要走独立的结算。</p>
 </li>
 <li>
  <p>为突出重点，中间省略了很多中间机构，比如花旗通过清算网络才能转账到汇丰，清算网络先略过。</p>
 </li>
 <li>
  <p>为简化描述，还有几个假设：</p>
 </li>
</ol>
<!-- -->
<ol>
 <li>
  <p>假设拼多多电商选择结算到银行卡。还有一个场景是电商选择结算到余额，然后自己手动提现。</p>
 </li>
 <li>
  <p>假设单币种场景，跨币种场景还涉及到外汇兑换。</p>
 </li>
</ol>
<h2>2.6. 最简信息流与资金流</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532720359.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>用户在支付平台充值10元，支付平台向银行发起扣款请求，这些指令操作归属于信息交互，属于信息流。</p>
 </li>
 <li>
  <p>真实资金流：银行账户余额的变动。比如：银行在内部把用户的余额减10元，给支付平台备付金账户加10元。</p>
 </li>
 <li>
  <p>虚拟资金流：支付平台内部账户余额的变动。比如：支付平台内部把银行应收账户加10元，给用户余额账户加10元。</p>
 </li>
 <li>
  <p>为什么会有真实资金流和虚拟资金流之分？因为我们真正能拿到钱的地方是银行，在支付系统内看到的只是一个数字，如果想变成真实世界的钱，还得发给银行提现。</p>
 </li>
</ol>
<h2>2.7. 加入结算的极简信息流与资金流</h2>
<p>在支付流程中，就是商户委托收单机构（支付平台）把用户的钱收回来，然后再把钱结算给商家。</p>
<p>下面以典型通过外部渠道的卡支付为例说明。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532720581.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>用户的钱最终会走到商户的收款银行账户。真实情况下用户的支付的钱会分成多份，包括通道收的费用，支付平台收的手续费，税费，营销分润，商户结算款等。通道费用还可以继续细分为发卡行手续费，收单行手续费，清算机构手续费等。</p>
 </li>
 <li>
  <p>跨行一般都需要通过清算机构，这里为简化也没有画出来。</p>
 </li>
 <li>
  <p>支付平台内部的资金流在详细版中给出。</p>
 </li>
</ol>
<h2>2.8. 极简跨境收单的协议关系</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532720760.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>这只是跨境收单的一种协议关系，真实场景存在多种形态。</p>
 </li>
 <li>
  <p>上述的收单机构是持牌的，但是没有跨境结算的能力，所以需要委托有跨境结算牌照的金融机构代为处理跨境结算业务。</p>
 </li>
 <li>
  <p>跨境电商平台只是一个商户平台，没有收单资质，所以需要委托收单机构给它下面的供应商结算打款。</p>
 </li>
 <li>
  <p>剩下的协议关系都是一目了然的，只是我们日常没有注意。比如用户和电商平台之间在注册时就会有会员协议要签署。</p>
 </li>
 <li>
  <p>特殊的情况下，一些实力雄厚的机构，比如蚂蚁、财付通、连连支付、空中云汇等，下面会成立多个实体，然后用不同的实体去申请不同的牌照（收单、银行、外汇、跨境代发等），这样表面上全部是一家公司搞定，但是实际的协议关系仍然是上面这样的，在各实体之间仍然需要签署各种协议。</p>
 </li>
 <li>
  <p>如果是本对本收单场景就简单很多，没有外汇和跨境结算这一层关系，如果跨境电商的货品全部是电商实体自营的，那就更简单，没有供应商委托结算的协议。</p>
 </li>
 <li>
  <p>一般电商平台在没有牌照情况下是不能开设余额账户的，如果电商想开通余额，可以委托第三方有牌照的公司托管（通常也是收单机构，收单机构一般会同时申请PA、PG牌照），这种情况下，电商平台和收单机构还会签署账户委托协议。</p>
 </li>
</ol>
<h2>2.9. 极简跨境资金方案</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532720927.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>这是一个典型的跨境资金流案例。用户支付USD，收单机构收到的是USD，但是需要结算CNY给中国境内的商户。</p>
 </li>
 <li>
  <p>收单机构（也就是支付平台）需要先将USD兑换成CNH（离岸人民币），再由入境代发机构把CNY结算给中国境内商户。这是所谓的"结汇入境”。</p>
 </li>
 <li>
  <p>如果采用"入境结汇"的方式，则收单机构直接结算USD给商户在境外的银行账户中，由商户以USD汇入境内，再兑换成CNY。或者收单机构先把USD汇入境内备付金账户，再兑换成CNY，然后再结算CNY给中国境内商户。</p>
 </li>
 <li>
  <p>以上这些不同的资金处理方案，统称为资金方案。</p>
 </li>
 <li>
  <p>不同的资金方案，一方面要考虑合规的诉求，另一方面就是考虑收益最大化，以及资金周转的时效性。</p>
 </li>
</ol>
<h2>2.10. 渠道路由</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721026.png&amp;size=m" alt=""></p>
<p>渠道路由核心作用是当有多个渠道同时满足业务诉求时，综合<strong>支付成功率、支付成本、用户体验、渠道状态</strong> 等多种因素<strong>挑选出最优的一条渠道</strong> 。具体如下：</p>
<ol>
 <li>
  <p><strong>提高支付成功率</strong> ：通过选择最合适的渠道，可以提高支付的成功率，减少支付失败带来的用户流失。原因在于不同的渠道在其内部的风险偏好是不一样的，同一个请求在A渠道会失败，但在B渠道会成功。</p>
 </li>
 <li>
  <p><strong>优化成本</strong> ：不同渠道的费用可能不同，通过合理的路由，可以降低支付成本。一些渠道还有阶梯收费，需要通过分流不同的渠道，保持整体成本最优。</p>
 </li>
 <li>
  <p><strong>提升用户体验</strong> ：快速、稳定的支付体验能增强用户的满意度和忠诚度。用户如果经常在A渠道支付，新的请求过来后，仍然发给A渠道支付的成功率往往会更高。</p>
 </li>
 <li>
  <p><strong>负载均衡</strong> ：将支付请求合理分配到不同的渠道，避免某个渠道过载，提升整体系统的稳定性。</p>
 </li>
</ol>
<h2>2.11. 简明复式记账</h2>
<p>金融机构的记账一定是基于复式记账法。下面以用户通过支付平台使用银行支付500块为例做个简要说明。</p>
<p>假设：支付平台使用CMB做为收单行，在CMB开设有备付金账户。</p>
<p>涉及的支付平台内部账户：</p>
<p>|———-|—————————————————-|——————————-|
 <br>
 | <strong>账户类型</strong> | <strong>账户</strong> | <strong>备注</strong> |
 <br>
 | 借记账户 | 应收-渠道-CMB | 应收归属借记账户 |
 <br>
 | 贷记账户 | 应付-过渡-网关过渡户 应付-平台托管-商户待结算 应付-平台托管-商户余额 手续费收入-商户-消费 | 应付归属贷记账户 手续费意味着所有者权益增加，归属贷记账户 |</p>
<p>记账步骤：</p>
<p>|——————-|——————————–|——–|
 <br>
 | <strong>阶段</strong> | <strong>操作账户</strong> | <strong>金额</strong> |
 <br>
 | 第一步 资金从渠道到网关过渡户 | 借：应收-渠道-CMB 贷：应付-过渡-网关过渡户 | 500 |
 <br>
 | 第二步 扣除手续费 | 借：应付-过渡-网关过渡户 贷：手续费收入-商户-消费 | 10 |
 <br>
 | 第三步 网关过渡户到商户待结算账户 | 借：应付-过渡-网关过渡户 贷：应付-平台托管-商户待结算 | 490 |
 <br>
 | 第四步 结算给商户 | 借：应付-平台托管-商户待结算 贷：应付-平台托管-商户余额 | 490 |</p>
<p>说明：</p>
<ol>
 <li>持牌支付机构的记账一定是复式记账法。内部开设了很多账户和科目。</li>
</ol>
<p><em>【借记类】账户：资产，应收款等；</em></p>
<p><em>【贷记类】账户：负债，所有者权益，应付款等；</em></p>
<ol>
 <li>借贷简要公式（不太严谨，但是够用）：</li>
</ol>
<p><em>【借记类】账户（如资产，应收款），【增加】为【借】，【减少】为【贷】；</em></p>
<p><em>【贷记类】账户（如负债和所有者权益，应付款），【增加】为【贷】，【减少】为【借】；</em></p>
<ol>
 <li>复式记账的专业书籍很多，这里只摘录几个重要的说明：</li>
</ol>
<p><em>复式记账法定义：对每项经济业务按相等的金额在两个或两个以上有关账户中同时进行登记的方法。</em></p>
<p><em>记账原则：有借必有贷，借贷必相等。</em></p>
<p><em>记账依据：会计恒等式：1. 资产 = 负债 + 所有者权益；2. 利润 = 收入 - 费用。</em></p>
<p><em>账户：具有一定格式和结构，能够用来连续、系统、全面的记录反映某种经济业务的增减变化及其结果。</em></p>
<p><em>科目：同类财务交易的分类，比如资产、负债、所有者权限、收入或费用等都属于科目。一般科目会分为多级。</em></p>
<p><em>账户和科目的区别：科目只有名字，账户包括结构和格式，每个账户对应一个特定的科目。</em></p>
<h2>2.12. 账户分类</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721139.png&amp;size=m" alt=""></p>
<p>在账务系统中，通常包含以下几种账户类型：</p>
<ol>
 <li>
  <p>客户账户：对客可见。包括：对私的个人客户账户，对公的商户账户。</p>
 </li>
 <li>
  <p>内部账户：对客不可见。包括：头寸、手续费收入、过渡户（也称中间户）等。</p>
 </li>
</ol>
<h2>2.13. 记账方向</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721284.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>账户类型与借贷方向，相同为加，相异为减，也就是所谓的：DD+，DC-，CC+，CD-。</p>
 </li>
 <li>
  <p>示例：用户提现100元，记账如下：</p>
 </li>
</ol>
<p>DR：用户余额（负债类账户）100</p>
<p>CR：提现过渡户（负债类账户）100</p>
<h2>2.14. 实时记账与缓冲记账</h2>
<p>一般来说，客户账户的记账需要是实时的，比如用户充值、提现，商家提现，用户退款等。</p>
<p>这些账户如果不做实时记账，一来有损用户体验，二来有资损风险。比如用户充值100块，如果延时不到账，用户可能会投诉。如果提现不实时记账，用户有可能重复提现成功。如果退款不实时记账，有可能在退款场景下被透支。</p>
<p>假设记账需要几十毫秒（数据库性能决定的），一个账户最高也就只支持几十个TPS的记账请求，对于一些高并发的账户（也称为热点账户）一定是性能不足的。这个时候一般使用缓冲记账，以提高性能。开通缓冲记账的，通常是内部账户或允许商户透支的流出场景。</p>
<p>缓冲记账通常就是先记录流水，然后起定时任务去捞取流水，汇总后进行记账。前提是一定要做好资损防控。</p>
<p>除了缓冲记账外，还有拆分账户的方式来解决热点账户问题。</p>
<h2>2.15. 会计科目与会计分录</h2>
<p>会计科目就是把会计要素进行分类，比如资产、负债等。通常都会有多级分类。</p>
<p>会计科目示例：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721438.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>一般支付系统使用三级科目就已经足够。部分特别复杂的系统，可能会用到五级科目。</p>
 </li>
 <li>
  <p>为便于理解，上面的示例做了很大的精简，各公司内部对科目的编制差异可能会比较大。</p>
 </li>
</ol>
<h2>2.16. 记账方案</h2>
<p>有了账户和会计科目，发生一笔交易时，如何让系统自动去记账？这个是记账方案做的事。其中一个解决方案就是给不同的交易场景制定不同的交易码，通过交易码来驱动记账。</p>
<p>下面是一个典型的支付系统的记账方案示例。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721580.png&amp;size=m" alt=""></p>
<h2>2.17. 会计日与日切</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721749.png&amp;size=m" alt=""></p>
<p>会计日，也称为会计结算日或账务结算日，是支付平台在会计周期中进行账务处理和结算的特定日期。比如在分布式环境下，各机器可能存在时间差，一笔交易在零点时有可能跨天处理，如何判断一笔交易归属于哪天，就依据会计日来计算。</p>
<p>所谓日切，简单理解就是切换到下一个会计日。主要做的工作：</p>
<ol>
 <li>
  <p>借贷试算平衡：也就是所谓"有借必有贷，借贷必相等"这条会计恒等式的落地。</p>
 </li>
 <li>
  <p>父子科目试算平衡。</p>
 </li>
 <li>
  <p>总账试算平衡。</p>
 </li>
 <li>
  <p>日、月、季度、年汇总。</p>
 </li>
 <li>
  <p>会计日变更。</p>
 </li>
</ol>
<p>日切试算平衡核心逻辑：</p>
<ol>
 <li>
  <p>借方发生额 = 贷方发生额</p>
 </li>
 <li>
  <p>借方余额 = 贷方余额</p>
 </li>
 <li>
  <p>期末余额 = 期初发生额 + 发生额</p>
 </li>
 <li>
  <p>父科目累积额 = 子科目累积额</p>
 </li>
</ol>
<h2>2.18. 对账差异处理</h2>
<p>对账一般有几种结果：</p>
<ol>
 <li>
  <p>对平：双方交易类型、单号、状态、币种、金额都是一致的。</p>
 </li>
 <li>
  <p>长款：我方多钱。支付长款：支付90块，渠道清算100块，或我方失败，渠道成功。退款长款：退款100块，渠道清算90块。充值长款、提现长款类比。</p>
 </li>
 <li>
  <p>短款：我方少钱。支付短款：支付00块，渠道清算90块。退款长款：退款90块，渠道清算100块。充值短款、提现短款类比。</p>
 </li>
</ol>
<p>因为我方和渠道之间有一定的时间差，所以长短款在T+1对账对不上时，往往先进入存疑清单里面，第T+2对账还是对不上，才会进入差异处理。</p>
<h2>2.19. 银行通道三层对账体系</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721841.png&amp;size=m" alt=""></p>
<p>第一层是信息流明细对账。我方流水和银行清算文件的流水逐一核对。可能会存在长短款情况。</p>
<p>第二层是账单对账。就是把我方流水汇总生成我方账单，然后把银行流水汇总生成银行账单，进行对账。可能会存在银行账单和我方账单不一致的情况，比如共支付100万，渠道分2次打款，一笔98万，一笔2万。</p>
<p>第三层是账实对账。就是我方内部记录的银行头寸和银行真实的余额是否一致。可能存在我方记录的头寸是220万，但是银行实际余额只有200万的情况。</p>
<h2>2.20. 支付记账</h2>
<p>我们通常说的记账，哪怕是一笔简单的支付，也会有多次记账。具体在什么节点记什么账，一般由财务人员决定。</p>
<p>下面是一个典型的使用银行通道进行支付的记账，会涉及网关过渡户，渠道待清算，商户待结算，手续费，银行头寸等多个内部户。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532721984.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>图中只画了正常场景，像明细对账出现差异（长短款）、账单对不平（渠道少打款或多打款）等场景没有画出来。</p>
 </li>
 <li>
  <p>上面只是一个典型的记账方案，真实的场景有些更简单，有些更复杂。</p>
 </li>
 <li>
  <p>开多个中间账户，什么场景下记到哪个账户，一般都是由财务团队决定。</p>
 </li>
</ol>
<h2>2.21. 商户结算记账</h2>
<p>商户结算和用户支付是两个独立流程。</p>
<p>以典型的商户结算到卡记账为例，通常涉及商户待结算户，网关过渡户，渠道应清算，渠道已清算，银行头寸等内部户。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532722201.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>上述是商户结算到卡场景。</p>
 </li>
 <li>
  <p>各公司的内部户编制可能有所不同。</p>
 </li>
 <li>
  <h1>简明产品架构图</h1>
 </li>
</ol>
<p>所谓产品架构图，简单的理解，就是站在产品角度，提供什么样的服务能力。下面是一个典型的支付系统的产品架构图。实际实现时差异会很大，尤其是上面的产品或应用层，有很多机构为特殊的行业提供一些特殊的能力，比如携程的支付就会有航空方面的B2B业务。但基础的能力基本也就这些。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532722321.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>这个图画得比较简单，但是已经涵养一个支付系统最核心的产品能力。</p>
 </li>
 <li>
  <p>上面部分是会员或商户感知的产品能力，包括门户、收银台，收单产品，资金产品等。下面部分是支付系统最核心的服务，用于支撑对外的产品能力。</p>
 </li>
 <li>
  <p>如果多跳几家公司，就会发现基础的部分大家都差不太多。</p>
 </li>
 <li>
  <h1>极简支付系统架构图</h1>
 </li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532722479.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>这个图很精简，但是基本已经够用，应付本对本交易这种简单的业务是完全没有问题的。</p>
 </li>
 <li>
  <p>一些复杂的支付系统可能还有外汇、额度中心、产品中心、卡中心等，甚至一个子系统可能会拆分为多个应用独立部署，比如收单结算就可以拆成收单和结算两个独立的应用。</p>
 </li>
 <li>
  <h1>完整支付系统架构图及各子系统简介</h1>
 </li>
</ol>
<p>跳过几个支付公司，这些基础的概念在几家公司都差不太多，区别是底层技术实现。比如RPC框架，数据库，业务流程，部署架构等。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532722619.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>这是一比较完整的系统架构图，属于逻辑划分。在单体应用中，就是一些模块，在分布式应用中，就是一些子域、子应用或子系统。</p>
 </li>
 <li>
  <p>下面针对各子系统/子模板做些简单介绍。</p>
 </li>
</ol>
<h2>5.1. 核心系统依赖图</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532722736.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>简化起见，只画了部分系统，比如额度中心、计收费等就没有画出来。</p>
 </li>
 <li>
  <p>同样简化起见，部分调用关系没有画出来，否则交叉起来太乱，比如收银核心也会调用卡中心查询用户的绑卡信息，且收银核心也会调用渠道网关查询可用渠道信息等。</p>
 </li>
</ol>
<h2>5.2. 开放网关</h2>
<p>主要对接商户，比如下单、支付等接口入口。通常要求有比较高的安全性。部分公司可能会把移动端网关、PC门户网关、商户通知等能力集成在开放网关，也可能会单独拆出部署。</p>
<h2>5.3. 收单结算</h2>
<p>负责把商户的单收下来，并给商户发起结算。承担的收单产品包括有：线上收单，线下收单，担保交易、即时到账等，每个公司的商业策略不同，开出的收单产品会有差异。</p>
<p>有些公司把结算划到出款中心，对接银企直连的渠道。</p>
<h2>5.4. 资金产品</h2>
<p>承担无买卖标的的纯资金转移能力。典型的有：充值、转账、提现、代发。和支付的区分在于支付是有买卖标的，而资金产品没有。也就是在系统中没有买卖记录发生，但在线下可能有。</p>
<p>资金产品一般需要独立的牌照。</p>
<h2>5.5. 收银核心</h2>
<p>渲染可用支付方式。包括查询账户是否有余额，查询营销是否有营销券，查询渠道网关是否有可用的外部渠道，最后组合成可用支付方式，供前端渲染。</p>
<p>收银核心就像一个大内总管，收到请求后，找商户平台核实身份，找合约平台核实权限，找会员平台核实用户身份，找收单看一下这笔单是否可以继续支付，找账务中心获取余额信息，营销看看有没有可用的券，找渠道网关看看没有可用的渠道，找额度中心看看是否超限额了，找风控问一下当前支付是否安全，找会员平台校验支付密码 … …</p>
<h2>5.6. 支付引擎</h2>
<p>负责真正的扣款或转账。有些公司叫支付核心。</p>
<p>如果是余额就调账务扣减余额，如果是红包就调营销做核销，如果是外部银行通道就调渠道网关。</p>
<h2>5.7. 渠道网关</h2>
<p>负责去外部渠道扣款。通常还会提供渠道路由、渠道咨询等能力，做得细的公司可能会把渠道核心和报文/文件网关单独拆出来。其中渠道核心就提供渠道路由、渠道咨询、渠道开关等服务，报文/文件网关负责报文转换、签名验签等。</p>
<h2>5.8. 会员平台</h2>
<p>管理会员的生命周期，比如注册、注销、登录等。同时还提供核身服务（比如登录密码，支付密码，短信验证码等）、实名认证服务等。</p>
<h2>5.9. 商户平台</h2>
<p>管理商户的入驻签约、KYB、交易管理等。</p>
<h2>5.10. 产品中心</h2>
<p>管理对外提供的产品能力，比如快捷支付，代扣等。一般大的支付系统才会独立成一个子系统。</p>
<h2>5.11. 账务中心</h2>
<p>负责账户开立，记账等。</p>
<h2>5.12. 会计中心</h2>
<p>会计科目管理、分录管理、日切管理等。</p>
<p>监管报表有时候也放在这里，有些公司也会独立出去。</p>
<p>很多集团公司往往有一套独立的专业财务系统，这个时候往往需要会计中心做完日切后，要把记账信息合并到集团公司的财务系统中去，简称并账。</p>
<h2>5.13. 对账中心</h2>
<p>负责明细对账和资金对账。</p>
<h2>5.14. 营销平台</h2>
<p>提供满减、红包等营销工具。</p>
<h2>5.15. 风控平台</h2>
<p>针对账户和交易，提供实时、离线风控，控制交易的风险。反洗钱、反欺诈是基本要求。</p>
<p>通常各公司对风控规则看成是机密，研发也可能看不到运营配置的规则。经常看到有网友问：“有xx公司的人在吗？我有xxx场景下的支付总是提示风控不过，是否知道是什么原因，怎么才能通过？“，完全是浪费口舌，谁会对外公布自己的风控规则，让人去钻空子呢？</p>
<h2>5.16. 运营平台</h2>
<p>订单管理、渠道管理、产品管理等综合运营工具。</p>
<h2>5.17. 数据平台</h2>
<p>主要用于数据汇总和分析。当前各支付公司基本都是分布式部署N多个应用，数据都在散落在各子系统中，需要汇总到数据平台用于经营分析。</p>
<h2>5.18. 卡中心</h2>
<p>负责管理用户的绑卡信息。需要经过PCI认证。</p>
<h2>5.19. 额度中心</h2>
<p>累计用户、商户的额度，通常有日、月、年，单卡等各种分类。</p>
<h2>5.20. 外汇平台</h2>
<p>负责外汇报价和兑换。</p>
<h2>5.21. 流动性与调拨中心</h2>
<p>一些跨境支付公司，在多个国家多个银行有头寸，各头寸之间经常需要做流动性管理，提高资金利用率。</p>
<p>毕竟在国外不需要把备付金强制存到央行还不给利息。当资金量大的时候，这笔收益可不少。</p>
<h2>5.22. 差错中心</h2>
<p>负责差错处理。比如渠道退款失败（银行账号销户，过了银行的退款有效期等），需要通过其它的方式退给用户。</p>
<h2>5.23. 拒付中心</h2>
<p>处理用户的拒付和举证。在跨境支付场景下，信用卡用户联系发卡行说卡被盗刷或商品没有收到，或商品有问题等，拒绝支付给商户。</p>
<p>国内基本没有看到拒付场景。</p>
<ol>
 <li>
  <h1>一些拓展知识</h1>
 </li>
</ol>
<h2>6.1. 技术风险与资损防控</h2>
<p>一般来说，技术风险主要包含稳定性和资损两个方面。其中稳定性风险就是大家经常说的几个9，比如99.999%可用，就是5个9。资损风险就是平台或用户的资金损失。</p>
<p>虽然资损也是技术风险的一种，但是因为对于专业的持牌支付公司来，资损是一种非常严重的事故，容易引发客诉、网络事件、甚至监管介入，所以又较一般的风险更为严重，常常把资损防控单独拿出来说。</p>
<p>技术风险体系过于庞大，这里只谈几点通用知识。</p>
<h3>识别风险来自哪里</h3>
<p>我们通常先需要知道风险来自哪里，才知道如何去防控。而风险往往来自变化。举几个例子，抛砖引玉：</p>
<p>流量变化：大促场景下，流量会暴增。</p>
<p>代码变化：引入了新的代码。</p>
<p>业务变化：修改了业务流程，或引入了新的业务。</p>
<p>外部变化：外部新的攻击手段。</p>
<h3>应对风险</h3>
<p>根据变化去应对风险。比如大促引入了流量变化，那就做压测、扩容、限流、降级非核心业务等应对。比如原来只有支付，这些有了用户提现，针对用户提现，内部多个子域可能状态/金额不一致，和银行渠道的状态/金额也可能不一致，那就加入各种对账手段，以及对应的应急预案。</p>
<h3>内部系统实时与离线对账</h3>
<p>对账是资损防控中最效的手段之一。</p>
<p>前面讲过的三层对账主要是和银行渠道对账，除了这个之外，一般的支付平台还会有内部系统之间的两两核对，这种核对主要是信息流层面的核对，主要核对状态、金额的一致性。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532722790.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>可以拆成离线核对和实时核对。</p>
 </li>
 <li>
  <p>离线核对一般就是把生产数据库的数据定时清洗到离线库（一般还可以分为天表和小时表）。</p>
 </li>
 <li>
  <p>实时核对一般就是监听数据库的binlog，当数据有变动时，延时几秒后请求双方系统的查询接口，查到数据后进行核对。</p>
 </li>
</ol>
<h2>6.2. 幂等</h2>
<p>幂等是针对重复请求的，支付系统一般会面临以下几个重复请求的场景：</p>
<ol>
 <li>
  <p><strong>用户多次点击支付按钮</strong> ：在网络较差或系统过载情况下，用户由于不确定交易是否完成而重复点击。</p>
 </li>
 <li>
  <p><strong>自动重试机制</strong> ：系统在超时或失败时重试请求，可能导致同一支付多次尝试。</p>
 </li>
 <li>
  <p><strong>网络数据包重复</strong> ：数据包在网络传输过程中，复制出了多份，导致支付平台收到多次一模一样的请求。</p>
 </li>
 <li>
  <p><strong>异常恢复</strong> ：在系统升级或崩溃后，未决事务需要根据已有记录恢复和完成。内部系统重发操作。</p>
 </li>
</ol>
<p><strong>幂等解决方案</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532722936.png&amp;size=m" alt=""></p>
<p>所谓业务幂等，就是由各域自己把<strong>唯一性的交易ID</strong> 作为数据库唯一索引，这样可以保证不会重复处理。</p>
<p>在数据库前面可以加一层缓存来提高性能，但是缓存只用于查询，查到数据认为就返回幂等成功，但是但不到，需要尝试插入数据库，插入成功后再刷新数据到缓存。</p>
<p>为什么要使用数据库的唯一索引做为兜底，是因为缓存是可能失效的。</p>
<p>在面临时经常有同学只回答到"使用redis分布式锁来实现幂等”，这是不对的。因为<strong>缓存有可能失效</strong> ，<strong>分布式锁只是用于防并发操作</strong> 的一种手段，无法根本性解决幂等问题，幂等一定是依赖数据库的唯一索引解决。</p>
<p>大部分简单的支付系统只要有业务幂等基本也够用了。</p>
<h2>6.3. 分库分表</h2>
<p>当数据量大的时间，分库分表是再所难免的。一个经典的面试题是：如果分了100张表，按商户来分表，还是按商户订单号来分表？如果按商户分表怎么解决各表流水数据量平衡问题？如果是按商户订单号来分表，商户想按时间段查询怎么办？</p>
<p>解法有很多种。一种典型的解法，就是线上数据库按商户订单号分表，同时有一个离线库冗余一份按商户号分表的数据，甚至直接使用离线数据平台的能力，把商户的按时间段查询需求从在线库剥离出来。</p>
<h2>6.4. 分布式事务</h2>
<p>分布式事务是个好东西，但是复杂度也高，还经常出现所谓的事务悬挂问题，且虽然各家都号称简单易用，对业务代码侵入少，但事实并非如此。</p>
<p>所以我个人更倾向于避免使用分布式事务解决方案，而是采用最终一致性来解决。对大部分中小公司来说，最终一致性已经够用。</p>
<h2>6.5. 金额处理规范</h2>
<p>对于研发经验不足的团队而言，经常会犯以下几种错误：</p>
<ol>
 <li>
  <p><strong>没有定义统一的Money类</strong> ，各系统间使用BigDecimal、double、long等数据类型进行金额处理及存储。</p>
 </li>
 <li>
  <p>定义了统一的Money类，<strong>但是写代码时不严格遵守</strong> ，仍然有些代码使用BigDecimal、double、long等数据类型进行金额处理。</p>
 </li>
 <li>
  <p><strong>手动对金额进行加、减、乘、除运算，单位（元与分）换算</strong> 。</p>
 </li>
</ol>
<p>带来的后果，通常就是资金损失，再细化一下，最常见的情况有下面3种：</p>
<ol>
 <li><strong>手动做单位换算导致金额被放大或缩小100倍</strong> 。</li>
</ol>
<!-- -->
<ol>
 <li>
  <p>比如大家规定传的是元，但是其中有位同学忘记了，以为传的是分，外部渠道要求传元，就手动乘以100。或者反过来。</p>
 </li>
 <li>
  <p>还有一种情况，部分币种比如日元最小单元就是元，假如系统约定传的是分，外部渠道要求传元，就可能在网关处理时手动乘以100。</p>
 </li>
</ol>
<!-- -->
<ol>
 <li>
  <p><strong>1分钱归属问题</strong> 。比如结算给商家，或计算手续费时，碰到除不尽时，使用四舍五入，还是向零舍入，还是银行家舍入？这取决于财务策略。</p>
 </li>
 <li>
  <p><strong>精度丢失</strong> 。在大金额时，double有可能会有精度丢失问题。</p>
 </li>
</ol>
<p><strong>最佳实践：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532723038.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <p><strong>制定适用于公司业务的Money类</strong> 来统一处理金额。</p>
 </li>
 <li>
  <p>在<strong>入口</strong> 网关接收到请求后，就<strong>转换为Money类</strong> 。</p>
 </li>
 <li>
  <p>所有内部应用的金额处理，<strong>强制全部使用Money类运算、传输</strong> ，禁止自己手动加减乘除、单位换算（比如元到分）。</p>
 </li>
 <li>
  <p>数据库使用DECIMAL类型保存，保存单位为元。</p>
 </li>
 <li>
  <p>在出口网关外发时，再根据外部接口文档要求，转换成使用指定的单位。有些是元，有些是分（最小货币单位）</p>
 </li>
</ol>
<h2>6.6. 业务ID生成规则</h2>
<p>数据库一般都会设计一个自增ID作为主键，同时还会设计一个能唯一标识一笔业务的ID，这就是所谓的业务ID（也称业务键）。比如收单域的收单单号。</p>
<p>也有人采用所谓雪花算法，但其实不适用于支付场景。</p>
<p>下面以32位的支付系统业务ID生成为例说明。实际应用时可灵活调整。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532723183.png&amp;size=m" alt=""></p>
<p>第1-8位：日期。通过单号一眼能看出是哪天的交易。</p>
<p>第9位：数据版本。用于单据号的升级。</p>
<p>第10位：系统版本。用于内部系统版本升级，尤其是不兼容升级的时候，老业务使用老的系统处理，新业务使用新系统处理。</p>
<p>第11-13位：系统标识码。支付系统内部每个域分配一段，由各域自行再分配给内部系统。比如010是收单核心，012是结算核心。</p>
<p>第14-15位：业务标识位。由各域内部定，比如00-15代表支付类业务，01支付，02预授权，03请款等。</p>
<p>第16-17位：机房位。用于全球化部署。</p>
<p>第18-19位：用户分库位。支持百库。</p>
<p>第20-21位：用户分表位。支持百表。</p>
<p>第22位：预发生产标识位。比如0代表预发环境，1代表生产环境。</p>
<p>第23-24位：预留。各域根据实际情况扩展使用。</p>
<p>第24-32位：序列号空间。一亿规模，循环使用。一个机房一天一亿笔是很大的规模了。如果不够用，可以扩展到第24位，到十亿规模。</p>
<h2>6.7. 状态机设计</h2>
<p>状态机，也称为有限状态机（FSM, Finite State Machine），是一种行为模型，由一组定义良好的状态、状态之间的转换规则和一个初始状态组成。它根据当前的状态和输入的事件，从一个状态转移到另一个状态。</p>
<p>下图就是收单子域设计中交易单的状态机设计。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532723311.png&amp;size=m" alt=""></p>
<p>从图中可以看到，一共4个状态，每个状态之间的转换由指定的事件触发。</p>
<p><strong>常见代码实现误区</strong></p>
<p>经常看到工作几年的同事实现状态机时，仍然使用if else或switch case来写。这是不对的，会让实现变得复杂，且容易出现问题。</p>
<p>甚至直接在订单的领域模型里面使用String来定义，而不是把状态模式封装单独的类。</p>
<p>还有就是直接调用领域模型更新状态，而不是通过事件来驱动。</p>
<p>限于篇幅，这里就不给正确的示例了。有兴趣的可以去网上看看，良好的示例有很多。</p>
<h2>6.8. 日志规范</h2>
<p>只要在公司写过代码，就一定打印过日志，但经常发现一些工作多年的工程师打印的日志也是乱七八糟的。我曾经在一家头部互联网公司接手过一个上线一年多的业务，相关日志一开始就没有设计好，导致很多监控无法实现，出了线上问题也不知道，最后只能安排工程师返工改造相关的日志。</p>
<p>我们要明白<strong>日志是用来做什么的</strong> 。只是<strong>先弄明白做事的目的，我们才能更好把事情做对</strong> 。在我看来，日志有两个核心的作用：1）<strong>监控</strong> ，诊断系统或业务是否存在问题；2）<strong>排查问题</strong> 。</p>
<p>对于监控而言，我们需要知道几个核心的数据：业务/接口的请求量、成功量、成功率、耗时，系统返回码、业务返回码，异常信息等。对于排查问题而言，我们需要有出入参、中间处理数据的上下文、报错的上下文等。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532723451.png&amp;size=m" alt=""></p>
<p>接下来，基于上面的分析，我们就清楚我们应该有几种日志：</p>
<ol>
 <li>
  <p><strong>接口摘要日志</strong> 。监控接口的请求量、成功量、耗时、返回码等。使用固定格式，需要打印：时间、接口名称、结果（成功/失败)、返回码、耗时等基本信息就足够。</p>
 </li>
 <li>
  <p><strong>业务摘要日志</strong> 。监控业务的请求量、成功量、<strong>核心业务信息</strong> 、返回码等。使用固定格式，需要打印：时间、业务类型、上一步状态、当前状态、返回码、核心业务信息（<strong>不同业务有不同的核心业务信</strong> 息，比如流入，就有支付金额/退款金额，卡品牌，卡BIN等)。</p>
 </li>
 <li>
  <p><strong>详细日志</strong> 。用于排查问题，不用于监控。<strong>格式不固定</strong> 。主要包括时间、接口、入参、出参、中间处理数据输入、异常的堆栈信息等。</p>
 </li>
 <li>
  <p><strong>系统异常日志</strong> 。同时用于监控。格式固定。需要打印：时间、错误码、错误信息、堆栈信息等。</p>
 </li>
</ol>
<h2>6.9. 支付安全</h2>
<h3>支付安全核心关注点</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532723570.png&amp;size=m" alt=""></p>
<p>支付安全是一个很大的范畴，但我们一般只需要重点关注以下几个核心点就够：</p>
<ol>
 <li><strong>敏感信息安全存储。</strong></li>
</ol>
<p>对个人和商户/渠道的敏感信息进行安全存储。</p>
<p>个人敏感信息包括身份证信息、支付卡明文数据和密码等，而商户/渠道的敏感信息则涉及商户登录/操作密码、渠道证书密钥等。</p>
<ol>
 <li><strong>交易信息安全传输。</strong></li>
</ol>
<p>确保客户端与支付系统服务器之间、商户系统与支付系统之间、支付系统内部服务器与服务器之间、支付系统与银行之间的数据传输安全。这包括采用加密技术等措施来保障数据传输过程中的安全性。</p>
<ol>
 <li><strong>交易信息的防篡改与防抵赖。</strong></li>
</ol>
<p>确保交易信息的完整性和真实性，防止交易信息被篡改或者被抵赖。一笔典型的交易，通常涉及到用户、商户、支付机构、银行四方，确保各方发出的信息没有被篡改也无法被抵赖。</p>
<ol>
 <li><strong>欺诈交易防范。</strong></li>
</ol>
<p>识别并防止欺诈交易，包括套现、洗钱等违规操作，以及通过识别用户信息泄露和可疑交易来保护用户资产的安全。这一方面通常由支付风控系统负责。</p>
<ol>
 <li><strong>服务可用性。</strong></li>
</ol>
<p>防范DDoS攻击，确保支付系统的稳定运行和服务可用性。通过部署防火墙、入侵检测系统等技术手段，及时发现并应对可能的DDoS攻击，保障支付服务的正常进行。</p>
<h3>极简支付安全大图</h3>
<p>支付安全是一个综合性的系统工程，除了技术手段外，还需要建立健全的安全制度和合规制度，而后两者通常被大部分人所忽略。</p>
<p>下图是一个极简版的支付安全大图，包含了支付安全需要考虑的核心要点。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532723749.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li><strong>制度是基础</strong> 。</li>
</ol>
<p>哪种场景下需要加密存储，加密需要使用什么算法，密钥长度最少需要多少位，哪些场景下需要做签名验签，这些都是制度就明确了的。制度通常分为行业制度和内部安全制度。行业制度通常是国家层面制定的法律法规，比如《网络安全法》、《支付业务管理办法》等。内部安全制度通常是公司根据自身的业务和能力建立的制度，小公司可能就没有。</p>
<ol>
 <li><strong>技术手段主要围绕四个目标：</strong></li>
</ol>
<p>1）敏感数据安全存储。</p>
<p>2）交易安全传输。</p>
<p>3）交易的完整性和真实性。</p>
<p>4）交易的合法性（无欺诈）。</p>
<p>对应的技术手段有：</p>
<ul>
 <li>
  <p><strong>敏感信息安全存储</strong> ：采用<strong>加密技术</strong> 对个人和商户/渠道的敏感信息进行加密存储，限制敏感信息的访问权限，防止未授权的访问和泄露。</p>
 </li>
 <li>
  <p><strong>交易信息安全传输</strong> ：使用安全套接字层（SSL）或传输层安全性协议（TLS）等<strong>加密技术</strong> ，确保数据在传输过程中的机密性和完整性。</p>
 </li>
 <li>
  <p><strong>交易的完整性和真实性</strong> ：采用<strong>数字签名技术和身份认证技术</strong> 确保交易信息的完整性和真实性，对交易信息进行记录和审计，建立可追溯的交易日志，以应对可能出现的交易篡改或抵赖情况。</p>
 </li>
 <li>
  <p><strong>防范欺诈交易</strong> ：通过<strong>支付风控系统</strong> ，及时识别和阻止可疑交易行为。</p>
 </li>
 <li>
  <p><strong>服务可用性</strong> ：部署<strong>流量清洗设备和入侵检测系统</strong> ，及时发现并阻止恶意流量，确保支付系统的稳定运行和服务可用性，抵御DDoS攻击。</p>
 </li>
 <li>
  <h1>结束语</h1>
 </li>
</ul>
<p>所谓提纲挈领，就是先掌握核心主干，有了这个前提，再去深入了解细节，才不至于"乱花渐欲迷人眼”，解决问题时才能如庖丁解牛，行云流水。伟人邓公提倡的"抓住主要矛盾”，也是这个道理。</p>
<p>本文主要讲了一些支付核心系统相关的基本概念，希望能为大家在学习在线支付系统相关知识时能提供一些有益的参考。</p>
<p>犹记得N年前那天早上，我穿上最帅的衬衣、笔挺的西装裤、贼亮的皮鞋，推开房门，清风徐来，朝阳灿烂，一如我的心情，意气风发。那是我进入正值蓬勃发展的第三方支付行业的第一天。</p>
<p>入职当天老板扔了很多文档给我，看了一周，没看懂。想起老祖宗说的"读书百遍，其义自见”，继续苦读一周，仍然是雾里看花。不幸中的万幸，是挺过了试用期。直到多年后的一天，整理老旧硬盘的资料，才发现一方面是自己愚钝，另一方面也是那些资料写得过于晦涩难懂。于是萌发一个念头：要不我自己也总结总结？这是其中的一篇。</p>
<p>斗转星移，外面的阳光依然灿烂，衬衣、西装裤、皮鞋却已不知何处去了。</p>
<p>推荐阅读</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247499745&amp;idx=1&amp;sn=1f4a6ab231b04a29ba3399beb36e6551&amp;chksm=ce127416f965fd0076c253e2330a182b737aab43a62fd839a3816e3e4883d49e4244da127c17&amp;scene=21#wechat_redirect">“跨境支付"课终于来了，25个子专栏，11套原型</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247499196&amp;idx=1&amp;sn=5d7a1fc62e590c2567236734a4d73533&amp;chksm=ce12764bf965ff5de50eb7b49f0a4b9c070ce1f9879e5fa242de664a3a54f05e880798167fdc&amp;scene=21#wechat_redirect">万字：清结算体系，全局方案深度解析</a>
 <br>
 <a href="http://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247496752&amp;idx=1&amp;sn=873a2c35041818c331ba1062cc3c99e2&amp;chksm=ce127fc7f965f6d1eb1cee0568de0d294702303297039e212f3fc71053a3c4fe1b05b14a7b48&amp;scene=21#wechat_redirect">3.5万字：一文搞懂"支付系统”</a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247498666&amp;idx=1&amp;sn=dc43ff849d95bef07407501662262f25&amp;chksm=ce12705df965f94b72f4d6156ff1d56bcbf1a131aae956d60ce9b3f33ce06e3455d1cb907290&amp;scene=21#wechat_redirect">支付全集-珍藏版V8.0</a></p>
<p>原文链接: https://mp.weixin.qq.com/s/-UOHkMDN9ex0kbywwwHTHg</p>]]></description><guid isPermaLink="false">/archives/d34fc329-f40b-4176-bd59-93ad344e2b4c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 2 Nov 2024 07:37:29 GMT</pubDate></item><item><title><![CDATA[资深支付架构师视角：实战从问题定义到代码落地的完整套路]]></title><link>https://wiki.dgfygk.com/archives/089414c0-8af2-4cef-b205-796ef6584956</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%B5%84%E6%B7%B1%E6%94%AF%E4%BB%98%E6%9E%B6%E6%9E%84%E5%B8%88%E8%A7%86%E8%A7%92%EF%BC%9A%E5%AE%9E%E6%88%98%E4%BB%8E%E9%97%AE%E9%A2%98%E5%AE%9A%E4%B9%89%E5%88%B0%E4%BB%A3%E7%A0%81%E8%90%BD%E5%9C%B0%E7%9A%84%E5%AE%8C%E6%95%B4%E5%A5%97%E8%B7%AF&amp;url=/archives/089414c0-8af2-4cef-b205-796ef6584956" width="1" height="1" alt="" style="opacity:0;">
<ol>
 <li>前言</li>
</ol>
<p>今天从一个实际案例入手，介绍站在架构师的角度，如何识别并定义问题，提炼需求，技术方案选型，再到详细设计，最后利用AI的能力协助写出核心的代码，验证与调优。</p>
<p>解决问题存在一定的模式，也可以称之为框架，总结出自己的思考和解题框架，以后再碰到同类型的问题就可以如庖丁解牛一样容易。</p>
<p>很多年前，我写代码仍然是if else，最多加个for，也就是所谓的面条式编程。</p>
<p>这三板斧应付简单的业务也没有太大问题，大不了50行代码能搞定的事，我就写个500行。但是一旦需要写一些稍微偏底层的框架，就无从下手。</p>
<p>不过现在的研发工程师比我们以前幸运很多，借助于AI辅助编码，只要把设计写出来，由AI协助实现一个稍微复杂一点的框架也不是什么大问题。</p>
<ol>
 <li>
  <h1>识别问题</h1>
 </li>
</ol>
<p>在支付系统中，有一个基本的需求就是流程编排，对应的模块就是流程引擎。</p>
<p>比如收银支付需要把整个支付流程串起来，包括用户校验，商户校验，合约校验，风控校验，余额校验等；商户入驻需要针对不同类型的商户串不同的流程；外发渠道请求支付时需要针对不同的渠道交互编排不同的流程。</p>
<p>以渠道流程编排为例，我刚入行第三方支付时，还没有断直连，快捷支付对接了中工农建交招等各种银行，当然还有银联。不同的渠道交互模式是不一样的，有些是先提供独立的签约接口，有些是签约+支付合并在一个接口，有些需要由渠道发送并验证短信验证码，有些需要由我方发送并验证短信验证码，有些需要先刷新token后，再去做支付。</p>
<p>下面是一个典型的渠道交互。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532737843.png&amp;size=m" alt=""></p>
<p>当时没有经验，于是每个渠道写一套独立的接入代码，各种逻辑都能通过if else编排出来。</p>
<p>但实际上，这些交互都可以分解成一个个组件操作，然后通过流程引擎做一个编排，就可以减少很多重复的代码，也能提高系统的健壮性，还能减少经验不足的工程师出现问题的可能性。</p>
<p>小结一下，识别问题：<strong>为每个渠道写独立的代码，冗余代码过多，可维护性差，研发效率低，需要有一个流程编排的能力</strong> <strong>。</strong></p>
<ol>
 <li>
  <h1>明确需求</h1>
 </li>
</ol>
<p>针对不同的渠道接入模式，配置出一些通用的配置，然后通过<strong>流程引擎</strong> 驱动<strong>流程配置</strong> ，完成和渠道的交互，减少冗余代码，提高可维护性。</p>
<ol>
 <li>
  <h1>方案选型</h1>
 </li>
</ol>
<p>有个经典的"奥卡姆剃刀原理”，核心思想是：“如无必要，勿增实体”。所以我们先看看市场上有哪些解决方案。</p>
<p>流程引擎在企业软件中无处不在，尤其是内部审批业务里面。市场上的确有很多现成的流程引擎，老牌的比如jBPM，Activiti等都很知名，中国后起之秀且开源的项目liteflow也是其中佼佼者。</p>
<p>其中jBPM，Activiti的配置过于繁琐和复杂，一个简单的支付流程，可能有上百行的配置，放弃。</p>
<p>liteflow的配置非常简单，可以做为备选方案。liteflow的核心思想：所有的操作都是组件，把组件串起来，完成业务能力。</p>
<p>而liteflow的问题之一也是因为其配置过于简单，把业务推进以及判断逻辑全部放在代码中去，导致在配置时无法看到推进的条件。比如下面是一个支付流程：</p>
<ul>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li>
  <p>
   <flow>
    <chain name="commonPayChain"> THEN(transitionToProcess, requestPay, SWITH(subOrderStateSwitch).to(transitionToSuccess, transitionToFail)) </chain>
   </flow>
  </p>
 </li>
</ul>
<p>我们只知道先推进到支付中，再发起请求，然后判断子订单状态，最后推进到成功或失败。</p>
<p><strong>如果我们一定要选择业界现有的技术解决方案，个人推荐使用liteflow。</strong></p>
<p><strong>还有一个方案，就是自己实现一个订制化的流程引擎</strong> 。制定流程引擎有几个诉求：</p>
<ol>
 <li>
  <p>配置要简单清晰。</p>
 </li>
 <li>
  <p>各种触发条件、推进条件、操作等需要在配置文件中体现。</p>
 </li>
</ol>
<p>配置文件也有多种格式，比如XML，YAML，JSON等。除了这几种，还有另外一种选择：<strong>自定义一种语法，使用链式处理。</strong></p>
<p>市面上也很多这样使用的。</p>
<p>在测试Mock框架Mockito中，我们经常这样使用：</p>
<ul>
 <li>
  <p>when(mockedList.get(0)).thenReturn(“first element”);</p>
 </li>
</ul>
<p>在java的流式代码中，我们经常这样使用：</p>
<ul>
 <li></li>
 <li></li>
 <li>
  <p>names.stream() .filter(name -&gt; name.startsWith(“A”)) .collect(Collectors.toList());</p>
 </li>
</ul>
<p>在jQuery中，我们经常这样使用（很多年前我也写JQuery）：</p>
<ul>
 <li></li>
 <li>
  <p>$('#element').css('color', 'red').show() .click(function() { alert('Clicked!'); });</p>
 </li>
</ul>
<p>所有以上这些都有一个共同的特点：<strong>链式调用</strong> 。</p>
<p><strong>链式调用</strong> 有个好处：非常接近自然语言，容易理解。最终选择此方案。</p>
<ol>
 <li>
  <h1>详细设计</h1>
 </li>
</ol>
<p>使用<strong>链式调用</strong> 的形式编写配置文件，易于理解和配置。关键字和示例在后面有。</p>
<h2>5.1. 整体架构图</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532737966.png&amp;size=m" alt=""></p>
<p>说明：</p>
<ol>
 <li>
  <p>流程引擎在启动时加载所有配置，生成不同的处理链。</p>
 </li>
 <li>
  <p>根据请求的参数，找到对应的链。</p>
 </li>
 <li>
  <p>根据处理链的配置，从第一个节点开始，驱动调用不同的组件，直接全部节点运行完毕。</p>
 </li>
</ol>
<h2>5.2. 状态、事件、操作</h2>
<p>分解支付业务的处理到最小粒度，只有三个：订单状态，当前的事件，触发的操作。</p>
<p>也就是：在指定主订单状态的情况下，当前过来了什么事件，进而触发什么样的操作。</p>
<ul>
 <li>
  <p><strong>状态（State</strong> ）：表示流程中的不同阶段，例如：INIT、PROCESS、SUCCESS、FAIL。</p>
 </li>
 <li>
  <p><strong>事件（Event）</strong> ：触发状态转移的事件，例如：CREATE、QUERY、CALLBACK。</p>
 </li>
 <li>
  <p><strong>操作（Operation）</strong> ：需要执行的操作，例如：PAY、PAY_QUERY、REFUND。</p>
 </li>
</ul>
<h2>5.3. 定义关键字</h2>
<p>和前面Mockitor或JAVA流式处理一样，<strong>链式调用</strong> 需要有一些专用语法。对于支付流程来说，有以下几个关键字：</p>
<ol>
 <li>
  <p><strong>whenOrderState</strong>：初始条件，主订单状态要满足给定条件。比如：whenOrderState(CommonOrderState.INIT)。</p>
 </li>
 <li>
  <p><strong>onEvent</strong>：触发事件。比如：onEvent(CommonEvent.CREATE)。</p>
 </li>
 <li>
  <p><strong>transitionOrderStateTo</strong>：推进主订单状态。比如：transitionOrderStateTo(CommonOrderState.PROCESS)。</p>
 </li>
 <li>
  <p><strong>request</strong>：请求操作。比如：request(CommonOperation.PAY)。</p>
 </li>
 <li>
  <p><strong>when</strong>：判断request返回的数据，比如：when(“subOrder.currentState == SubOrderState.S”)。</p>
 </li>
 <li>
  <p><strong>notify</strong>：发出订单消息给其它域。比如：notify()。</p>
 </li>
 <li>
  <p><strong>then</strong>：进入子流程。比如：when(“subOrder.currentState == SubOrderState.S”)
   <br>
   .then(xxx)。</p>
 </li>
 <li>
  <p><strong>subFlow</strong>：子流程标识。比如subFlow().request(xxx)。</p>
 </li>
</ol>
<p>下面是一个通用支付配置的示例：</p>
<ul>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li>
  <p>whenOrderState(CommonOrderState.INIT).onEvent(CommonEvent.CREATE).transitionOrderStateTo(CommonOrderState.PROCESS).request(CommonOperation.PAY) .when(“subOrder.currentState == SubOrderState.S”) .transitionOrderStateTo(CommonOrderState.SUCCESS) .when(“subOrder.currentState == SubOrderState.F”) .transitionOrderStateTo(CommonOrderState.FAIL) .when(“subOrder.currentState == SubOrderState.U &amp;&amp; subOrder.webForm != null”) .notifyNode();</p>
 </li>
</ul>
<p>简单说明一下：</p>
<ol>
 <li>
  <p>当初始状态为INIT（初始），触发了CREATE（创建），主订单状态流转到PROCESS（处理中），并发起请求PAY（支付）。</p>
 </li>
 <li>
  <p>支付结果回来后，当子订单结果为S（成功）就推进主订单成功，当子订单结果为F(失败）就推进主订单失败，当子订单结果为U（未知）且webForm（表单）不为空就发出消息通知其它域。</p>
 </li>
</ol>
<h2>5.4. 支持Groovy脚本</h2>
<p>需要支持配置中的groovy脚本。比如.when(“subOrder.currentState == SubOrderState.F”)中"subOrder.currentState == SubOrderState.F"就是groovy脚本，当脚本结果为真，就执行后面的操作。</p>
<p>注意在groovy脚本引擎上下文中导入必要的类，包括但不限于 SubOrderState等类，否则脚本运行会报错。</p>
<h2>5.5. 核心组件</h2>
<p>主要有以下几大类组件：</p>
<ol>
 <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>
</ol>
<p>
 <br>
</p>
<p>详细如下：</p>
<p>com.yinmo.flowengine</p>
<p>\|-enums : 枚举，包括状态，事件等</p>
<p>\| \|-FlowOperation : 操作枚举基类</p>
<p>\| \|-CommonOperation : 通用操作类型：支付，支付查询，支付回调等</p>
<p>\| \|-FlowState : 状态基类</p>
<p>\| \|-CommonOrderState : 通用订单状态</p>
<p>\| \|-SubOrderState : 子订单状态</p>
<p>\| \|-FlowEvent : 事件基类</p>
<p>\| \|-CommonEvent : 通用事件类型</p>
<p>\|-handler : 处理器</p>
<p>\| \|-HandlerNodeType : 节点处理器类型</p>
<p>\| \|-HandlerNode : 节点处理器</p>
<p>\| \|-SubOrderHandler : 子订单处理器</p>
<p>\| \|-SubOrderSendHandler : 子订单发送处理器</p>
<p>\| \|-SubOrderCallbackHandler : 子订单回调处理器</p>
<p>\|-config：配置</p>
<p>\| \|-FlowConfig : 流程配置接口</p>
<p>\| \|-AbstractConfig : 抽象流程配置</p>
<p>\| \|-CommonPayConfig : 通用支付配置类</p>
<p>\| \|-RefreshTokenPayConfig : 刷新TOKEN支付配置类</p>
<p>\| \|-CommonPayQueryConfig : 通用支付查询配置类</p>
<p>\| \|-CommonPayCallbackConfig : 通用支付回调配置类</p>
<p>\|-context：上下文</p>
<p>\| \|-FlowContext : 流程上下文</p>
<p>\| \|-GatewayCallbackContext : 网关回调上下文</p>
<p>\|-com.yinmo.flowengine.model：模型</p>
<p>\| \|-Order : 主订单</p>
<p>\| \|-SubOrder : 子订单</p>
<p>\|-com.yinmo.flowengine.service：服务</p>
<p>\| \|-FlowEngineService : 流程引擎服务接口</p>
<p>\| \|-impl.FlowEngineServiceImpl : 流程引擎服务实现</p>
<p>\|-com.yinmo.flowengine.test：测试类</p>
<p>\|-CommonPayTest : 通用支付测试</p>
<p>\|-RefreshTokenPayTest : 刷新TOKEN支付测试</p>
<p>\|-CommonPayQueryTest : 通用支付查询测试</p>
<p>\|-CommonPayCallbackTest : 通用支付回调测试</p>
<p>需要特别说明的几点：</p>
<ol>
 <li>
  <p>状态、事件等枚举都先定义接口做为基类，主要是考虑扩展性。</p>
 </li>
 <li>
  <p>配置根据渠道模式来配置，比如有50条渠道，抽象出10个模式，只需要配置10个配置文件就行。</p>
 </li>
</ol>
<h2>5.6. 部分核心类定义说明</h2>
<p>这些描述主要用于生成ChatGPT的提示词用的，ChatGPT能理解并可以在生成代码时生成对应的注释。</p>
<ol>
 <li>
  <p><strong>模型：</strong></p>
 </li>
 <li>
  <p>Order 模型：</p>
 </li>
</ol>
<!-- -->
<ul>
 <li>
  <p>属性包括：String orderId；FlowState previousState；FlowState currentState；Money transactionAmount；List subOrders；String webForm等。</p>
 </li>
 <li>
  <p>增加方法：transitionToState(FlowState)。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>SubOrder 模型：</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>属性包含：String subOrderId, String parentOrderId，SubOrderState previousState, SubOrderState currentState, Money transactionAmount, String channelResponseCode, String channelResponseMessage, String standardResponseCode, String standardResponseMessage, String webForm, String sendToChannelContext, String receiveFromChannelContext等。</p>
 </li>
 <li>
  <p>增加方法：transitionToState(SubOrderState)。</p>
 </li>
 <li>
  <p><strong>枚举：</strong></p>
 </li>
 <li>
  <p>定义interface：FlowState，FlowEvent，FlowOperation。所有枚举全部继承于interface，方便扩展。</p>
 </li>
 <li>
  <p>interface FlowOperation增加方法：String name()、String getMethod() 和 boolean isCallback()。isCallback()用于表示是否是一个外部渠道回调操作，回调是直接解析，不需要发给渠道。getMethod()获取方法名，会发给网关，网关根据这个参数组装发给外部渠道的参数。</p>
 </li>
 <li>
  <p>通用主订单状态：enum CommonOrderState implements FlowState。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>有：INIT, PROCESS, SUCCESS, FAIL。</li>
</ul>
<!-- -->
<ul>
 <li>通用子订单状态：enum SubOrderState implements FlowState。</li>
</ul>
<!-- -->
<ul>
 <li>有：I(Init), U(Unkown), S(Success), F(Fail)。</li>
</ul>
<!-- -->
<ul>
 <li>通用操作类型：enum CommonOperation implements FlowOperation。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>有：PAY(“pay”, false), PAY_QUERY(“payQuery”, false), REFUND(“refund”, false), REFRESH_TOKEN(“refreshToken”, false), PARSE(“parse”, true)等。</p>
 </li>
 <li>
  <p>示例：PAY(“pay”, false)为例：说明是一个支付操作，“pay"会发给网关，网关根据这个参数组装发给外部渠道的参数，“false"表示是否是一个回调。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>通用事件：enum CommonEvent implements FlowEvent。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>有：CREATE(订单创建事件，驱动系统向渠道发起支付退款等操作）, QUERY（订单查询事件，驱动系统向渠道发起查询操作）, CALLBACK（外部渠道主动回调通知事件，驱动系统直接解析网关接收到的报文）。</p>
 </li>
 <li>
  <p><strong>上下文：</strong></p>
 </li>
 <li>
  <p><strong>FlowContext</strong> 。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>属性：channelName、 order、event、configName、gatewayCallbackContext 和 subOrder 等。</li>
</ul>
<!-- -->
<ul>
 <li><strong>GatewayCallbackContext。</strong></li>
</ul>
<!-- -->
<ul>
 <li>属性：subOrderId、state、callbackMessage、channelResponseCode 、 channelResponseMessage 、standardResponseCode 、 standardResponseMessage 等。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>上下文用于在处理器和服务之间传递需要处理的数据。</p>
 </li>
 <li>
  <p><strong>配置相关：</strong></p>
 </li>
 <li>
  <p>interface FlowConfig。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>方法：String name()，HandlerNode getHandlerNode(FlowState orderState, FlowEvent event)。</li>
</ul>
<!-- -->
<ul>
 <li>abstract class <code>AbstractConfig</code> implements FlowConfig。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>核心能力：使用构建器模式来定义流程。</p>
 </li>
 <li>
  <p>属性：Map<flowstate, map<flowevent, handlernode>&gt; 存储配置。</flowstate,></p>
 </li>
 <li>
  <p>方法：void init()；abstract initConfig()让子类实现。在@PostConstruct init()调用initConfig进行初始化，子类在initConfig()配置脚本。</p>
 </li>
 <li>
  <p>提供方法定义状态、事件、转换、请求和通知。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>
  <p>所有具体的流程配置类继续自AbstractConfig。所有流程配置类提供String name()方法，返回当前的类简要名称用于标识流程名称。示例：getClass().getSimpleName()。</p>
 </li>
 <li>
  <p>定义子类：CommonPayConfig ，RefreshTokenPayConfig ，CommonPayQueryConfig，CommonPayCallbackConfig ，全部继承自 AbstractConfig。</p>
 </li>
 <li>
  <p><strong>处理节点：</strong></p>
 </li>
 <li>
  <p>节点处理器类型：enum HandlerNodeType。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>有：TRANSITION（推进主订单状态变更），REQUEST（向外部渠道发送请求，或解析外部渠道回调报文），NOTIFY（发送消息通知其他域）</li>
</ul>
<!-- -->
<ul>
 <li>处理器节点：classHandlerNode ，表示流程中的一个节点。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>属性：节点类型、下一个状态、操作、回调处理器和子节点列表等字段。</p>
 </li>
 <li>
  <p>确保 HandlerNode 能通过其子节点来完成链式操作。</p>
 </li>
 <li>
  <p><strong>处理器和服务：</strong></p>
 </li>
 <li>
  <p>class SubOrderHandler 。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>
  <p>方法：void handle(FlowContext, CommonOperation)。</p>
 </li>
 <li>
  <p>根据操作委托给 SubOrderSendHandler 或 SubOrderCallbackHandler。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>class SubOrderSendHandler 。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>方法：void handle(FlowContext, CommonOperation)。</p>
 </li>
 <li>
  <p>创建子订单，保存到数据库，组装网关报文，发送给网关，由网关发送给外部渠道，解析渠道返回的报文，更新子订单，保存子订单最新数据到数据库。把子订单放到FlowContext中。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>class SubOrderCallbackHandler。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>方法：void handle(FlowContext, CommonOperation)。</p>
 </li>
 <li>
  <p>创建子订单，解析渠道主动回调通知返回的报文，更新子订单，保存子订单最新数据到数据库。把子订单放到FlowContext中。</p>
 </li>
</ul>
<!-- -->
<ul>
 <li>interface FlowEngineService 。</li>
</ul>
<!-- -->
<ul>
 <li>void execute(FlowContext);</li>
</ul>
<!-- -->
<ul>
 <li>class FlowEngineServiceImpl implements FlowEngineServiceImpl。</li>
</ul>
<!-- -->
<ul>
 <li>
  <p>功能：执行从配置生成的处理节点列表来处理流程。迭代处理节点，并根据需要处理链式操作，通过递归处理子节点来实现。</p>
 </li>
 <li>
  <p>属性：@Autowired List configs， Map<string, config> configMap;</string,></p>
 </li>
 <li>
  <p>方法：@PostConstruct init()方法用于初始化。在init()方法中，把自动装配的List configs转存到configMap，在execute方法中直接使用configMap.get(context.getConfigName())获取配置。</p>
 </li>
 <li>
  <p>其它：初始化完成后，使用fastjson格式化打印configMap的内容。</p>
 </li>
 <li>
  <p><strong>日志：</strong></p>
 </li>
 <li>
  <p>在需要日志的类中使用 @Slf4j。</p>
 </li>
 <li>
  <p>以下情况需要打印日志：关键处理，状态变更，只写了空方法还没有具体实现的方法，异常。</p>
 </li>
 <li>
  <p><strong>测试：</strong></p>
 </li>
 <li>
  <p>编写测试类：CommonPayTest， CommonPayQueyTest，CommonPayCallbackConfig ，RefreshTokenPayTest。</p>
 </li>
 <li>
  <p>所有测试类使用Mockitor进行mock子订单的返回值。</p>
 </li>
 <li>
  <p>数据校验使用断言。</p>
 </li>
 <li>
  <p>所有测试类需要有三个方法：testSubOrderSuccess(), testSubOrderSuccessFail()，testSubOrderUnkown()。分别是子订单返回成功，返回失败，返回未知。</p>
 </li>
 <li>
  <h1>代码实现</h1>
 </li>
</ul>
<p><strong>代码全部由ChatGPT生成</strong> 。为减少篇幅，这里只展示了核心的两个类。</p>
<p>也可以根据提示词自己生成，生成代码的完整提示词，在前面的文章有详细介绍：<a href="http://mp.weixin.qq.com/s?__biz=MzkwOTYyODA4Nw==&amp;mid=2247485757&amp;idx=1&amp;sn=0602bb7bf4cf004e997205cb8a9ba6e9&amp;chksm=c1368ee5f64107f3abc7d476c794235fb3979563b9b0914c4bbbf5e2b31ad9de4cd5d2912032&amp;scene=21#wechat_redirect">‍https://mp.weixin.qq.c‍om/s/XtMUIHBuJs7trnsbdgHjMg‍</a>。</p>
<p>抽象配置类AbstractConfig，<strong>提供whenOrderState, onEvent, when，then，subFlow，request, transitionOrderStateTo等自定义关键字的实现逻辑</strong> 。</p>
<ul>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li>
  <p>abstract class AbstractConfig implements FlowConfig {</p>
  <pre><code>protected Map&lt;FlowState, Map&lt;FlowEvent, HandlerNode&gt;&gt; configMap = new HashMap&lt;&gt;();
@PostConstruct    public void init() {        initConfig();    }
/**     * 子类实现该方法来初始化配置     */    protected abstract void initConfig();
@Override    public HandlerNode getHandlerNode(FlowState orderState, FlowEvent event) {        Map&lt;FlowEvent, HandlerNode&gt; eventMap = configMap.get(orderState);        if (eventMap != null) {            return eventMap.get(event);        }        return null;    }
protected NodeBuilder whenOrderState(FlowState orderState) {        Map&lt;FlowEvent, HandlerNode&gt; eventMap = configMap.computeIfAbsent(orderState, k -&gt; new HashMap&lt;&gt;());        return new NodeBuilder(eventMap, null);    }
protected NodeBuilder subFlow() {        HandlerNode subFlowNode = new HandlerNode(null);        subFlowNode.setSubFlow(true); // 标记为子流程节点        return new NodeBuilder(null, subFlowNode);    }
protected class NodeBuilder {        private Map&lt;FlowEvent, HandlerNode&gt; eventMap;        private HandlerNode rootNode;        private HandlerNode currentNode;
    public NodeBuilder(Map&lt;FlowEvent, HandlerNode&gt; eventMap, HandlerNode rootNode) {            this.eventMap = eventMap;            this.rootNode = rootNode;            this.currentNode = rootNode;        }
    public NodeBuilder onEvent(FlowEvent event) {            if (eventMap != null) {                HandlerNode eventNode = new HandlerNode(null);                eventMap.put(event, eventNode);                this.rootNode = eventNode;                this.currentNode = eventNode;            } else {                throw new IllegalStateException("子流程中不能调用 onEvent()");            }            return this;        }
    public NodeBuilder transitionOrderStateTo(FlowState nextState) {            currentNode.setNodeType(HandlerNodeType.TRANSITION);            currentNode.setNextState(nextState);            return this;        }
    public NodeBuilder request(FlowOperation operation) {            HandlerNode requestNode = new HandlerNode(HandlerNodeType.REQUEST);            requestNode.setOperation(operation);            currentNode.addChild(requestNode);            this.rootNode = requestNode;            this.currentNode = requestNode;            return this;        }
    public NodeBuilder when(String condition) {            HandlerNode whenNode = new HandlerNode(null);            whenNode.setConditionScript(condition);            rootNode.addChild(whenNode);            this.currentNode = whenNode;            return this;        }
    public NodeBuilder then(NodeBuilder nodeBuilder) {            currentNode.addChild(nodeBuilder.rootNode);            return this;        }
    public NodeBuilder notifyNode() {            HandlerNode notifyNode = new HandlerNode(HandlerNodeType.NOTIFY);            currentNode.addChild(notifyNode);            return this;        }
    public HandlerNode getCurrentNode() {            return currentNode;        }    }}
</code></pre>
 </li>
</ul>
<p>流程引擎入口实现类：FlowEngineServiceImpl，提供链式处理，并调用各处理器完成业务处理。</p>
<ul>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li>
  <p>public class FlowEngineServiceImpl implements FlowEngineService {</p>
  <pre><code>private final Map&lt;String, FlowConfig&gt; configMap = new HashMap&lt;&gt;();    @Autowired    @Setter    private List&lt;FlowConfig&gt; configs;    @Autowired    @Setter    private SubOrderHandler subOrderHandler;
@PostConstruct    public void init() {        for (FlowConfig config : configs) {            configMap.put(config.name(), config);        }        log.info("ConfigMap initialized with configs: {}", JSON.toJSONString(configMap));    }
@Override    public void execute(FlowContext context) {        FlowConfig config = configMap.get(context.getConfigName());        if (config == null) {            log.error("No configuration found for name: {}", context.getConfigName());            return;        }
    FlowState currentState = context.getOrder().getCurrentState();        FlowEvent event = context.getEvent();        log.info("当前主订单状态：{}， 触发事件：{}", currentState.name(), event.name());
    HandlerNode handlerNode = config.getHandlerNode(currentState, event);        if (handlerNode == null) {            log.error("No handler node found for state: {} and event: {}", currentState, event);            return;        }
    executeNode(context, handlerNode);    }
private void executeNode(FlowContext context, HandlerNode node) {        // 处理节点，根据节点类型执行相应的操作        if (node.getNodeType() != null) {            switch (node.getNodeType()) {                case TRANSITION:                    // 推进主订单状态变更                    context.getOrder().transitionToState(node.getNextState());                    log.info("主订单状态变更为：{} -&gt; {}", context.getOrder().getPreviousState(), context.getOrder().getCurrentState());                    break;                case REQUEST:                    // 向外部渠道发送请求，或解析外部渠道回调报文                    subOrderHandler.handle(context, (CommonOperation) node.getOperation());                    log.info("执行操作：{}", node.getOperation());                    break;                case NOTIFY:                    // 发送消息通知其他域                    notifyOtherDomains(context);                    log.info("通知其它域：{}", context.getOrder());                    break;                default:                    log.warn("未知的节点类型: {}", node.getNodeType());            }        }
    // 处理子节点        if (node.getChildren() != null) {            for (HandlerNode child : node.getChildren()) {                // 如果有条件，执行Groovy脚本判断                boolean conditionMet = true;                if (child.getConditionScript() != null) {                    conditionMet = evaluateCondition(context, child.getConditionScript());                }
            if (conditionMet) {                    executeNode(context, child);                    // 如果条件满足，执行后不再检查其他兄弟节点                    break;                }            }        }    }

private boolean evaluateCondition(FlowContext context, String conditionScript) {
    // 使用Groovy脚本引擎执行条件        try {            GroovyScriptEngineImpl scriptEngine = new GroovyScriptEngineImpl();
        // 设置脚本上下文            scriptEngine.put("subOrder", context.getSubOrder());            scriptEngine.put("order", context.getOrder());
        // 将必要的导入语句和条件脚本拼接在一起            String fullScript = "import com.yinmo.flowengine.enums.*;\n" + conditionScript;
        Object result = scriptEngine.eval(fullScript);            log.info("执行判断：{}, 结果：{}", conditionScript, result);            if (result instanceof Boolean) {                return (Boolean) result;            } else {                log.warn("条件脚本未返回布尔值: {}", conditionScript);                return false;            }        } catch (Exception e) {            log.error("执行条件脚本时出错: {}", conditionScript, e);            return false;        }    }

private void notifyOtherDomains(FlowContext context) {        // TODO: 实现通知其他域的逻辑        log.info("Notify other domains with context: {}", context);    }}
</code></pre>
 </li>
</ul>
<ol>
 <li>
  <h1>验证与调优</h1>
 </li>
</ol>
<p>多写几份配置文件，比如支付，退款，支付查询，刷新后支付，回调通知等，多跑几个测试用例，正常，异常等，测试类要有基本的断言。如果有报错或不符合预期，就扔给ChatGPT去修复。</p>
<p>支付成功测试：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532738056.png&amp;size=m" alt=""></p>
<p>支付失败测试：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532738281.png&amp;size=m" alt=""></p>
<p>其中的一小段测试代码：</p>
<ul>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 <li>
  <p>@Test public void testSubOrderSuccess() { // 设置Mock行为 SubOrder subOrder = new SubOrder(); subOrder.setCurrentState(SubOrderState.S);</p>
  <pre><code>    Mockito.doAnswer(invocation -&gt; {            FlowContext context = invocation.getArgument(0);            context.setSubOrder(subOrder);            return null;        }).when(subOrderSendHandler).handle(Mockito.any(), Mockito.any());
    // 构建上下文        Order order = new Order();        order.setOrderId("ORDER123");        order.setCurrentState(CommonOrderState.INIT);
    FlowContext context = new FlowContext();        context.setOrder(order);        context.setEvent(CommonEvent.CREATE);        context.setConfigName("CommonPayConfig");
    // 执行流程        flowEngineService.execute(context);
    // 验证结果        Assert.assertEquals(CommonOrderState.SUCCESS, order.getCurrentState());    }
</code></pre>
 </li>
 <li>
  <h1>结束语</h1>
 </li>
</ul>
<p>解决问题是我们研发工程师们的核心竞争力。</p>
<p>本文以"为支付流程这个细分领域构建一套专用的极轻量级流程引擎"为例子，介绍了在工作中如何做问题抽象，参考行业经验，设计解决方案，并利用AI辅助实现几乎所有的代码。希望能为大家在提高解决工作问题的能力方面提供一些有益的参考。</p>
<p>这里面有一个重要前提是做好知识储备，否则仍然无法顺利解决碰到的工作问题。拿这个案例来说，我们需要了解流程编排的特性，知道流程编排适用于哪些场景，行业有哪些流程引擎，各自的特点是什么，了解链式处理的特点，也需要了解如何使用AI辅助写复杂逻辑代码等。</p>
<p>具体使用ChatGPT生成全套流程引擎的代码，请参考上一篇文章：<a href="https://mp.weixin.qq.com/s?__biz=MzkwOTYyODA4Nw==&amp;mid=2247485757&amp;idx=1&amp;sn=0602bb7bf4cf004e997205cb8a9ba6e9&amp;scene=21#wechat_redirect">https://mp.weixin.qq.com/s/XtMUIHBuJs7trnsbdgHjMg</a>。如果需要GPT生成的完整代码，请私信。</p>
<p><em>这是《图解支付系统设计与实现》专栏系列文章中的番外第（3）篇。</em> <em>欢迎关注公众号”</em> <strong><em>隐墨星辰</em></strong> <em>“，和我一起深入解码支付系统设计与实现的方方面面。</em></p>
<p>原文链接: https://mp.weixin.qq.com/s/E0nEavIXYIRHJEwfn1zsRA</p>]]></description><guid isPermaLink="false">/archives/089414c0-8af2-4cef-b205-796ef6584956</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 2 Nov 2024 07:37:06 GMT</pubDate></item><item><title><![CDATA[光纤的跳线、尾纤区别在哪？]]></title><link>https://wiki.dgfygk.com/archives/56f73cd3-5325-4de0-9276-f1afc293fa31</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%89%E7%BA%A4%E7%9A%84%E8%B7%B3%E7%BA%BF%E3%80%81%E5%B0%BE%E7%BA%A4%E5%8C%BA%E5%88%AB%E5%9C%A8%E5%93%AA%EF%BC%9F&amp;url=/archives/56f73cd3-5325-4de0-9276-f1afc293fa31" width="1" height="1" alt="" style="opacity:0;">
<p>跳线和尾纤是光纤通信系统中常见的两种连接组件，它们在结构、用途和应用场景上有所不同。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730532779564.png&amp;size=m" alt=""></p>
<h2>主要区别</h2>
<p>| 特性 | 跳线 | 尾纤 |
 <br>
 |———–|————————–|—————————|
 <br>
 | <strong>定义</strong> | 一段带有两端连接器的光纤线缆，用于设备间连接 | 一段只有一端带有连接器的光纤线缆，另一端裸露光纤 |
 <br>
 | <strong>结构</strong> | 两端都有预装的光纤连接器 | 只有一端有预装的光纤连接器，另一端是裸露的光纤 |
 <br>
 | <strong>连接器数量</strong> | 两端各有一个连接器 | 只有一端有连接器 |
 <br>
 | <strong>用途</strong> | 用于设备之间光纤接口的直接连接，如交换机到路由器 | 用于熔接光纤电缆和光纤设备，如光纤配线架到光纤电缆 |
 <br>
 | <strong>应用场景</strong> | 数据中心、通信机房、网络设备间的快速连接 | 光纤配线架、终端盒和光纤接入网中的光纤电缆连接 |
 <br>
 | <strong>长度</strong> | 通常较短，适合设备间的直接连接 | 长度较短，用于设备和光纤电缆之间的连接 |
 <br>
 | <strong>灵活性</strong> | 可重复插拔，灵活性高 | 一般用于固定连接，需要熔接或机械接续 |
 <br>
 | <strong>安装复杂度</strong> | 简单，即插即用 | 较复杂，需要熔接工具和专业技术 |</p>
<h2>跳线</h2>
<ul>
 <li><strong>定义</strong>：跳线（Patch Cord）是两端都有光纤连接器的光纤线缆，用于连接不同的光纤通信设备。</li>
 <li><strong>用途</strong>：常用于交换机、路由器、光纤配线架（ODF）之间的连接。它们提供一种方便、快速、可靠的连接方式。</li>
 <li><strong>灵活性</strong>：由于其两端都有连接器，可以多次插拔，灵活性高，非常适合频繁的连接和断开操作。</li>
 <li><strong>安装</strong>：即插即用，不需要额外的熔接或专业技能，适合快速部署和更换。</li>
</ul>
<h2>尾纤</h2>
<ul>
 <li><strong>定义</strong>：尾纤（Pigtail）是一端有光纤连接器，另一端是裸露光纤的光纤线缆。</li>
 <li><strong>用途</strong>：常用于光纤配线架、终端盒等设备中，连接裸纤光缆和设备的光纤接口。尾纤需要与光纤电缆进行熔接或机械接续。</li>
 <li><strong>安装</strong>：安装相对复杂，需要使用熔接机或机械接续工具将尾纤的裸露端与光纤电缆熔接在一起，需要专业技能和设备。</li>
</ul>
<h2>总结</h2>
<ul>
 <li><strong>跳线</strong>：适合设备间的快速连接和频繁插拔，安装简单，即插即用。</li>
 <li><strong>尾纤</strong>：适合固定连接和光纤电缆的终端处理，安装较复杂，需要熔接。</li>
</ul>
<p>根据不同的应用需求和安装环境，选择合适的光纤连接组件可以提高网络的可靠性和性能。</p>
<p>
 <br>
</p>
<p>往期推荐
 <br>
 <a href="http://mp.weixin.qq.com/s?__biz=MzI4MTM2NTQ5OA==&amp;mid=2247496933&amp;idx=1&amp;sn=7545cba3a4f3dca06bfecfadeaadd8f2&amp;chksm=eba8ea70dcdf63669618513214da735133425d434650d751e158c48ce74417c4e316057e55a9&amp;scene=21#wechat_redirect">三层交换机有路由功能，为什么还需要路由器？</a> <a href="http://mp.weixin.qq.com/s?__biz=MzI4MTM2NTQ5OA==&amp;mid=2247496926&amp;idx=1&amp;sn=826351d5ca347744b15d34a1b52736f6&amp;chksm=eba8ea4bdcdf635d698cd14a188540bb1e7f0ef351ddcf8f5c2c4059aab8101eb645dfa6b028&amp;scene=21#wechat_redirect">你觉得DNS和IP地址有关系吗？</a> <a href="http://mp.weixin.qq.com/s?__biz=MzI4MTM2NTQ5OA==&amp;mid=2247496920&amp;idx=1&amp;sn=2a5512ec2e19f2bd442ab4b290bdad43&amp;chksm=eba8ea4ddcdf635b320311e941d273cf2d12978a5d779be0f4c1f5c64ae57ac8a3d31f53c05f&amp;scene=21#wechat_redirect">家用路由器和无线AP有什么区别？你不会不知道吧</a> <a href="http://mp.weixin.qq.com/s?__biz=MzI4MTM2NTQ5OA==&amp;mid=2247496919&amp;idx=1&amp;sn=d9cede7c782ab1d620ca1654dfb876be&amp;chksm=eba8ea42dcdf6354e268b86742cf7515cb79ef001fa3b9876f94e432290be42ac27a6a8dd99f&amp;scene=21#wechat_redirect">10个IPV6常识</a> <a href="http://mp.weixin.qq.com/s?__biz=MzI4MTM2NTQ5OA==&amp;mid=2247496913&amp;idx=1&amp;sn=73ad4171c6e23c1ea65769dcc34a3373&amp;chksm=eba8ea44dcdf63529f09b03d2d2fdb4b7a5641644f6e6f823873f28720451cf1df3e38534826&amp;scene=21#wechat_redirect"></a> <a href="http://mp.weixin.qq.com/s?__biz=MzI4MTM2NTQ5OA==&amp;mid=2247496913&amp;idx=1&amp;sn=73ad4171c6e23c1ea65769dcc34a3373&amp;chksm=eba8ea44dcdf63529f09b03d2d2fdb4b7a5641644f6e6f823873f28720451cf1df3e38534826&amp;scene=21#wechat_redirect"></a></p>
<p><a href="http://mp.weixin.qq.com/s?__biz=MzI4MTM2NTQ5OA==&amp;mid=2247496913&amp;idx=1&amp;sn=73ad4171c6e23c1ea65769dcc34a3373&amp;chksm=eba8ea44dcdf63529f09b03d2d2fdb4b7a5641644f6e6f823873f28720451cf1df3e38534826&amp;scene=21#wechat_redirect">网络\&amp;运维工程师的1024</a>
 <br>
 <strong>喜欢就</strong> <strong>分享</strong>
 <br>
 <strong>认同就</strong> <strong>点赞</strong> <strong>支持就</strong> <strong>在看</strong> <strong>一键四连，你的技术也四连</strong></p>
<p>原文链接: https://mp.weixin.qq.com/s/lV2WFBnznHYIjeTV6Mcexw</p>]]></description><guid isPermaLink="false">/archives/56f73cd3-5325-4de0-9276-f1afc293fa31</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 2 Nov 2024 07:36:22 GMT</pubDate></item><item><title><![CDATA[螺丝基础知识]]></title><link>https://wiki.dgfygk.com/archives/ef882b66-43b3-429a-9fbc-d36b0be31e2c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%9E%BA%E4%B8%9D%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86&amp;url=/archives/ef882b66-43b3-429a-9fbc-d36b0be31e2c" width="1" height="1" alt="" style="opacity:0;">
<p><strong>不想错过更多精彩内容，记得把本公众号关注加星号哦！</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268075997.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268076120.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268076158.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268076196.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268076236.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268076276.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268076316.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730268076355.png&amp;size=m" alt=""></p>
<p>原文链接: https://mp.weixin.qq.com/s/J9bjt3wlhz3m85o9-p_c-g</p>]]></description><guid isPermaLink="false">/archives/ef882b66-43b3-429a-9fbc-d36b0be31e2c</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Wed, 30 Oct 2024 06:02:57 GMT</pubDate></item><item><title><![CDATA[全国最全DNS地址大全（含电信、移动、联通）填好DNS地址，提升网速并降低网络延时]]></title><link>https://wiki.dgfygk.com/archives/69871c3d-d16f-4832-a12c-c515675a791b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%A8%E5%9B%BD%E6%9C%80%E5%85%A8DNS%E5%9C%B0%E5%9D%80%E5%A4%A7%E5%85%A8%EF%BC%88%E5%90%AB%E7%94%B5%E4%BF%A1%E3%80%81%E7%A7%BB%E5%8A%A8%E3%80%81%E8%81%94%E9%80%9A%EF%BC%89%E5%A1%AB%E5%A5%BDDNS%E5%9C%B0%E5%9D%80%EF%BC%8C%E6%8F%90%E5%8D%87%E7%BD%91%E9%80%9F%E5%B9%B6%E9%99%8D%E4%BD%8E%E7%BD%91%E7%BB%9C%E5%BB%B6%E6%97%B6&amp;url=/archives/69871c3d-d16f-4832-a12c-c515675a791b" width="1" height="1" alt="" style="opacity:0;">
<p>DNS是互联网中负责将域名解析为具体ip地址的系统，充当了互联网的"电话簿"，提供了将易于记忆的域名映射到计算机可理解的ip地址的功能。电脑填上最近的DNS可以更快的解析网络服务， 提供网速速度，并降低网络延时。以下是全网最全的DNS服务器地址，可以参照自己填写。</p>
<table>
 <thead>
  <tr>
   <th>服务商</th>
   <th>云公共DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>114DNS</td>
   <td>114.114.114.114</td>
   <td>114.114.115.115</td>
  </tr>
  <tr>
   <td>DNSPod DNS+</td>
   <td>119.29.29.29</td>
   <td>182.254.116.116</td>
  </tr>
  <tr>
   <td>DNSPod DNS+</td>
   <td>2402:4e00::</td>
   <td></td>
  </tr>
  <tr>
   <td>DNS 派 电信/移动/铁通</td>
   <td>101.226.4.6</td>
   <td>218.30.118.6</td>
  </tr>
  <tr>
   <td>DNS 派 联通</td>
   <td>123.125.81.6</td>
   <td>140.207.198.6</td>
  </tr>
  <tr>
   <td>cnnicDNS</td>
   <td>1.2.4.8</td>
   <td>210.2.4.8</td>
  </tr>
  <tr>
   <td>cnnicDNS</td>
   <td>2001:dc7:1000::1</td>
   <td></td>
  </tr>
  <tr>
   <td>GoogleDNS</td>
   <td>8.8.8.8</td>
   <td>8.8.4.4</td>
  </tr>
  <tr>
   <td>GoogleDNS</td>
   <td>2001:4860:4860::8888</td>
   <td>2001:4860:4860::8844</td>
  </tr>
  <tr>
   <td>CloudflareDNS</td>
   <td>1.1.1.1</td>
   <td>1.0.0.1</td>
  </tr>
  <tr>
   <td>CloudflareDNS</td>
   <td>2606:4700:4700::1111</td>
   <td>2606:4700:4700::1001</td>
  </tr>
  <tr>
   <td>IBM Quad9DNS</td>
   <td>9.9.9.9</td>
   <td></td>
  </tr>
  <tr>
   <td>IBM Quad9DNS</td>
   <td>2620:fe::fe</td>
   <td>2620:fe::9</td>
  </tr>
  <tr>
   <td>DNS.SB</td>
   <td>185.222.222.222</td>
   <td>185.184.222.222</td>
  </tr>
  <tr>
   <td>DNS.SB</td>
   <td>2a09::</td>
   <td>2a11::</td>
  </tr>
  <tr>
   <td>OpenDNS</td>
   <td>208.67.222.222</td>
   <td>208.67.220.220</td>
  </tr>
  <tr>
   <td>OpenDNS</td>
   <td>2620:0:ccc::2</td>
   <td>2620:0:ccd::2</td>
  </tr>
  <tr>
   <td>V2EXDNS</td>
   <td>199.91.73.222</td>
   <td>178.79.131.110</td>
  </tr>
  <tr>
   <td>阿里云DNS</td>
   <td>223.5.5.5</td>
   <td>223.6.6.6</td>
  </tr>
  <tr>
   <td>阿里云DNS</td>
   <td>2400:3200::1</td>
   <td>2400:3200:baba::1</td>
  </tr>
  <tr>
   <td>腾讯云DNS</td>
   <td>183.60.83.19</td>
   <td>183.60.82.98</td>
  </tr>
  <tr>
   <td>百度云DNS</td>
   <td>180.76.76.76</td>
   <td>2400:da00::6666</td>
  </tr>
  <tr>
   <td>微软云DNS</td>
   <td>4.2.2.1</td>
   <td>4.2.2.2</td>
  </tr>
  <tr>
   <td>华为云DNS</td>
   <td>122.112.208.1</td>
   <td>139.9.23.90</td>
  </tr>
  <tr>
   <td>华为云DNS</td>
   <td>114.115.192.11</td>
   <td>116.205.5.1</td>
  </tr>
  <tr>
   <td>华为云DNS</td>
   <td>116.205.5.30</td>
   <td>122.112.208.175</td>
  </tr>
  <tr>
   <td>华为云DNS</td>
   <td>139.159.208.206</td>
   <td></td>
  </tr>
  <tr>
   <td>中華電信DNS</td>
   <td>168.95.192.1</td>
   <td>168.95.1.1</td>
  </tr>
  <tr>
   <td>香港宽频DNS</td>
   <td>203.80.96.10</td>
   <td>203.80.96.9</td>
  </tr>
  <tr>
   <td>赛门铁克诺顿DNS</td>
   <td>199.85.126.10</td>
   <td>199.85.127.10</td>
  </tr>
  <tr>
   <td>oracle+dynDNS</td>
   <td>216.146.35.35</td>
   <td>216.146.36.36</td>
  </tr>
  <tr>
   <td>瑞士瑞信银行DNS</td>
   <td>64.6.64.6</td>
   <td>64.6.65.6</td>
  </tr>
  <tr>
   <td>[公共DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<h2 id="国内dns地址大全">国内dns地址大全</h2>
<table>
 <thead>
  <tr>
   <th>省市</th>
   <th>中国电信DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>中国电信IPv6DNS</td>
   <td>240e:4c:4008::1</td>
   <td>240e:4c:4808::1</td>
  </tr>
  <tr>
   <td>安徽电信DNS</td>
   <td>61.132.163.68</td>
   <td>202.102.213.68</td>
  </tr>
  <tr>
   <td>北京电信DNS</td>
   <td>219.141.136.10</td>
   <td>219.141.140.10</td>
  </tr>
  <tr>
   <td>重庆电信DNS</td>
   <td>61.128.192.68</td>
   <td>61.128.128.68</td>
  </tr>
  <tr>
   <td>福建电信DNS</td>
   <td>218.85.152.99</td>
   <td>218.85.157.99</td>
  </tr>
  <tr>
   <td>甘肃电信DNS</td>
   <td>202.100.64.68</td>
   <td>61.178.0.93</td>
  </tr>
  <tr>
   <td>广东电信DNS</td>
   <td>202.96.128.86</td>
   <td>202.96.128.166</td>
  </tr>
  <tr>
   <td>广东电信DNS</td>
   <td>202.96.134.133</td>
   <td>202.96.128.68</td>
  </tr>
  <tr>
   <td>广东电信DNS</td>
   <td>202.96.154.8</td>
   <td>202.96.154.15</td>
  </tr>
  <tr>
   <td>广西电信DNS</td>
   <td>202.103.225.68</td>
   <td>202.103.224.68</td>
  </tr>
  <tr>
   <td>贵州电信DNS</td>
   <td>202.98.192.67</td>
   <td>202.98.198.167</td>
  </tr>
  <tr>
   <td>河南电信DNS</td>
   <td>222.88.88.88</td>
   <td>222.85.85.85</td>
  </tr>
  <tr>
   <td>黑龙江电信DNS</td>
   <td>219.147.198.230</td>
   <td>219.147.198.242</td>
  </tr>
  <tr>
   <td>湖北电信DNS</td>
   <td>202.103.24.68</td>
   <td>202.103.0.68</td>
  </tr>
  <tr>
   <td>湖南电信DNS</td>
   <td>59.51.78.211</td>
   <td>59.51.78.210</td>
  </tr>
  <tr>
   <td>湖南电信DNS</td>
   <td>222.246.129.80</td>
   <td></td>
  </tr>
  <tr>
   <td>江苏电信DNS</td>
   <td>218.2.2.2</td>
   <td>218.4.4.4</td>
  </tr>
  <tr>
   <td>江苏电信DNS</td>
   <td>61.147.37.1</td>
   <td>218.2.135.1</td>
  </tr>
  <tr>
   <td>江西电信DNS</td>
   <td>202.101.224.69</td>
   <td>202.101.226.68</td>
  </tr>
  <tr>
   <td>内蒙古电信DNS</td>
   <td>219.148.162.31</td>
   <td>222.74.39.50</td>
  </tr>
  <tr>
   <td>山东电信DNS</td>
   <td>219.146.1.66</td>
   <td>219.147.1.66</td>
  </tr>
  <tr>
   <td>陕西电信DNS</td>
   <td>218.30.19.40</td>
   <td>61.134.1.4</td>
  </tr>
  <tr>
   <td>上海电信DNS</td>
   <td>202.96.209.133</td>
   <td>116.228.111.118</td>
  </tr>
  <tr>
   <td>上海电信DNS</td>
   <td>202.96.209.5</td>
   <td>180.168.255.118</td>
  </tr>
  <tr>
   <td>四川电信DNS</td>
   <td>61.139.2.69</td>
   <td>218.6.200.139</td>
  </tr>
  <tr>
   <td>天津电信DNS</td>
   <td>219.150.32.132</td>
   <td>219.146.0.132</td>
  </tr>
  <tr>
   <td>云南电信DNS</td>
   <td>222.172.200.68</td>
   <td>61.166.150.123</td>
  </tr>
  <tr>
   <td>浙江电信DNS</td>
   <td>202.101.172.35</td>
   <td>61.153.177.196</td>
  </tr>
  <tr>
   <td>浙江电信DNS</td>
   <td>61.153.81.75</td>
   <td>60.191.244.5</td>
  </tr>
  <tr>
   <td>[<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730180036666.png&amp;size=m" alt=""> 中国电信DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<table>
 <thead>
  <tr>
   <th>省市</th>
   <th>中国联通DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>中国联通IPv6DNS</td>
   <td>2408:8899::8</td>
   <td>2408:8888::8</td>
  </tr>
  <tr>
   <td>北京联通DNS</td>
   <td>123.123.123.123</td>
   <td>123.123.123.124</td>
  </tr>
  <tr>
   <td>北京联通DNS</td>
   <td>202.106.0.20</td>
   <td>202.106.195.68</td>
  </tr>
  <tr>
   <td>重庆联通DNS</td>
   <td>221.5.203.98</td>
   <td>221.7.92.98</td>
  </tr>
  <tr>
   <td>广东联通DNS</td>
   <td>210.21.196.6</td>
   <td>221.5.88.88</td>
  </tr>
  <tr>
   <td>河北联通DNS</td>
   <td>202.99.160.68</td>
   <td>202.99.166.4</td>
  </tr>
  <tr>
   <td>河南联通DNS</td>
   <td>202.102.224.68</td>
   <td>202.102.227.68</td>
  </tr>
  <tr>
   <td>黑龙江联通DNS</td>
   <td>202.97.224.69</td>
   <td>202.97.224.68</td>
  </tr>
  <tr>
   <td>吉林联通DNS</td>
   <td>202.98.0.68</td>
   <td>202.98.5.68</td>
  </tr>
  <tr>
   <td>江苏联通DNS</td>
   <td>221.6.4.66</td>
   <td>221.6.4.67</td>
  </tr>
  <tr>
   <td>内蒙古联通DNS</td>
   <td>202.99.224.68</td>
   <td>202.99.224.8</td>
  </tr>
  <tr>
   <td>山东联通DNS</td>
   <td>202.102.128.68</td>
   <td>202.102.152.3</td>
  </tr>
  <tr>
   <td>山东联通DNS</td>
   <td>202.102.134.68</td>
   <td>202.102.154.3</td>
  </tr>
  <tr>
   <td>山西联通DNS</td>
   <td>202.99.192.66</td>
   <td>202.99.192.68</td>
  </tr>
  <tr>
   <td>陕西联通DNS</td>
   <td>221.11.1.67</td>
   <td>221.11.1.68</td>
  </tr>
  <tr>
   <td>上海联通DNS</td>
   <td>210.22.70.3</td>
   <td>210.22.84.3</td>
  </tr>
  <tr>
   <td>四川联通DNS</td>
   <td>119.6.6.6</td>
   <td>124.161.87.155</td>
  </tr>
  <tr>
   <td>天津联通DNS</td>
   <td>202.99.104.68</td>
   <td>202.99.96.68</td>
  </tr>
  <tr>
   <td>浙江联通DNS</td>
   <td>221.12.1.227</td>
   <td>221.12.33.227</td>
  </tr>
  <tr>
   <td>辽宁联通DNS</td>
   <td>202.96.69.38</td>
   <td>202.96.64.68</td>
  </tr>
  <tr>
   <td><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730180036729.png&amp;size=m" alt=""> 中国联通DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<table>
 <thead>
  <tr>
   <th>省市</th>
   <th>中国移动DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>中国移动IPv6DNS</td>
   <td>2409:8088::a</td>
   <td>2409:8088::b</td>
  </tr>
  <tr>
   <td>安徽移动DNS</td>
   <td>211.138.180.2</td>
   <td>211.138.180.3</td>
  </tr>
  <tr>
   <td>山东移动DNS</td>
   <td>218.201.96.130</td>
   <td>211.137.191.26</td>
  </tr>
  <tr>
   <td>山东移动DNS</td>
   <td>218.201.124.18</td>
   <td>218.201.124.19</td>
  </tr>
  <tr>
   <td>山西移动DNS</td>
   <td>211.138.106.2</td>
   <td>211.138.106.3</td>
  </tr>
  <tr>
   <td>山西移动DNS</td>
   <td>211.138.106.18</td>
   <td>211.138.106.19</td>
  </tr>
  <tr>
   <td>山西移动DNS</td>
   <td>211.138.106.7</td>
   <td></td>
  </tr>
  <tr>
   <td>江苏移动DNS</td>
   <td>221.131.143.69</td>
   <td>112.4.0.55</td>
  </tr>
  <tr>
   <td>江苏移动DNS</td>
   <td>221.130.13.133</td>
   <td>211.103.55.50</td>
  </tr>
  <tr>
   <td>江苏移动DNS</td>
   <td>221.130.56.241</td>
   <td>211.103.13.101</td>
  </tr>
  <tr>
   <td>江苏移动DNS</td>
   <td>211.138.200.69</td>
   <td></td>
  </tr>
  <tr>
   <td>浙江移动DNS</td>
   <td>211.140.13.188</td>
   <td>211.140.188.188</td>
  </tr>
  <tr>
   <td>浙江移动DNS</td>
   <td>211.140.10.2</td>
   <td></td>
  </tr>
  <tr>
   <td>湖南移动DNS</td>
   <td>211.142.210.98</td>
   <td>211.142.210.99</td>
  </tr>
  <tr>
   <td>湖南移动DNS</td>
   <td>211.142.210.100</td>
   <td>211.142.210.101</td>
  </tr>
  <tr>
   <td>湖南移动DNS</td>
   <td>211.142.236.87</td>
   <td></td>
  </tr>
  <tr>
   <td>湖北移动DNS</td>
   <td>211.137.58.20</td>
   <td>211.137.64.163</td>
  </tr>
  <tr>
   <td>湖北移动DNS</td>
   <td>211.136.17.107</td>
   <td>211.136.20.203</td>
  </tr>
  <tr>
   <td>江西移动DNS</td>
   <td>211.141.90.68</td>
   <td>211.141.90.69</td>
  </tr>
  <tr>
   <td>江西移动DNS</td>
   <td>211.141.85.68</td>
   <td></td>
  </tr>
  <tr>
   <td>陕西移动DNS</td>
   <td>211.137.130.3</td>
   <td>211.137.130.19</td>
  </tr>
  <tr>
   <td>陕西移动DNS</td>
   <td>218.200.6.139</td>
   <td></td>
  </tr>
  <tr>
   <td>四川移动DNS</td>
   <td>211.137.82.4</td>
   <td></td>
  </tr>
  <tr>
   <td>重庆移动DNS</td>
   <td>218.201.4.3</td>
   <td>218.201.21.132</td>
  </tr>
  <tr>
   <td>重庆移动DNS</td>
   <td>218.201.17.2</td>
   <td></td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.130.33.52</td>
   <td>221.130.33.60</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>211.136.28.231</td>
   <td>211.136.28.234</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>211.136.28.237</td>
   <td>211.136.28.228</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.130.32.103</td>
   <td>221.130.32.100</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.130.32.106</td>
   <td>221.130.32.109</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.176.3.70</td>
   <td>221.176.3.73</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.176.3.76</td>
   <td>221.176.3.79</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.176.3.83</td>
   <td>221.176.3.85</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.176.4.6</td>
   <td>221.176.4.9</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.176.4.12</td>
   <td>221.176.4.15</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>221.176.4.18</td>
   <td>221.176.4.21</td>
  </tr>
  <tr>
   <td>北京移动DNS</td>
   <td>211.138.30.66</td>
   <td></td>
  </tr>
  <tr>
   <td>天津移动DNS</td>
   <td>211.137.160.50</td>
   <td>211.137.160.185</td>
  </tr>
  <tr>
   <td>上海移动DNS</td>
   <td>211.136.112.50</td>
   <td>211.136.150.66</td>
  </tr>
  <tr>
   <td>广东移动DNS</td>
   <td>211.136.192.6</td>
   <td>211.136.20.204</td>
  </tr>
  <tr>
   <td>广东移动DNS</td>
   <td>211.139.163.6</td>
   <td>211.139.136.68</td>
  </tr>
  <tr>
   <td>广东移动DNS</td>
   <td>211.136.18.171</td>
   <td></td>
  </tr>
  <tr>
   <td>广西移动DNS</td>
   <td>211.138.245.180</td>
   <td>211.136.17.108</td>
  </tr>
  <tr>
   <td>广西移动DNS</td>
   <td>211.139.5.30</td>
   <td></td>
  </tr>
  <tr>
   <td>贵州移动DNS</td>
   <td>211.139.5.29</td>
   <td>211.139.5.30</td>
  </tr>
  <tr>
   <td>福建移动DNS</td>
   <td>211.138.151.161</td>
   <td>211.138.156.66</td>
  </tr>
  <tr>
   <td>福建移动DNS</td>
   <td>218.207.217.241</td>
   <td>218.207.217.242</td>
  </tr>
  <tr>
   <td>福建移动DNS</td>
   <td>211.143.181.178</td>
   <td>211.143.181.179</td>
  </tr>
  <tr>
   <td>福建移动DNS</td>
   <td>218.207.128.4</td>
   <td>218.207.130.118</td>
  </tr>
  <tr>
   <td>福建移动DNS</td>
   <td>211.138.145.194</td>
   <td></td>
  </tr>
  <tr>
   <td>河北移动DNS</td>
   <td>211.143.60.56</td>
   <td>211.138.13.66</td>
  </tr>
  <tr>
   <td>甘肃移动DNS</td>
   <td>218.203.160.194</td>
   <td>218.203.160.195</td>
  </tr>
  <tr>
   <td>黑龙江移动DNS</td>
   <td>211.137.241.34</td>
   <td>211.137.241.35</td>
  </tr>
  <tr>
   <td>吉林移动DNS</td>
   <td>211.141.16.99</td>
   <td>211.141.0.99</td>
  </tr>
  <tr>
   <td>辽宁移动DNS</td>
   <td>211.137.32.178</td>
   <td>211.140.197.58</td>
  </tr>
  <tr>
   <td>云南移动DNS</td>
   <td>211.139.29.68</td>
   <td>211.139.29.69</td>
  </tr>
  <tr>
   <td>云南移动DNS</td>
   <td>211.139.29.150</td>
   <td>211.139.29.170</td>
  </tr>
  <tr>
   <td>云南移动DNS</td>
   <td>218.202.1.166</td>
   <td></td>
  </tr>
  <tr>
   <td>海南移动DNS</td>
   <td>221.176.88.95</td>
   <td>211.138.164.6</td>
  </tr>
  <tr>
   <td>内蒙古移动DNS</td>
   <td>211.138.91.1</td>
   <td>211.138.91.2</td>
  </tr>
  <tr>
   <td>新疆移动DNS</td>
   <td>218.202.152.130</td>
   <td>218.202.152.131</td>
  </tr>
  <tr>
   <td>西藏移动DNS</td>
   <td>211.139.73.34</td>
   <td>211.139.73.35</td>
  </tr>
  <tr>
   <td>青海移动DNS</td>
   <td>211.138.75.123</td>
   <td></td>
  </tr>
  <tr>
   <td>香港移动DNS</td>
   <td>203.142.100.18</td>
   <td>203.142.100.21</td>
  </tr>
  <tr>
   <td><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730180036747.png&amp;size=m" alt=""> 中国移动DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<table>
 <thead>
  <tr>
   <th>省市</th>
   <th>中国铁通DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>广东铁通DNS</td>
   <td>61.235.70.252</td>
   <td>211.98.4.1</td>
  </tr>
  <tr>
   <td><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730180036769.png&amp;size=m" alt=""> 中国铁通DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<table>
 <thead>
  <tr>
   <th>省市</th>
   <th>中国广电DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>广东广电DNS 珠江宽频DNS</td>
   <td>116.199.0.200</td>
   <td>116.116.116.116</td>
  </tr>
  <tr>
   <td><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730180036786.png&amp;size=m" alt=""> 中国广电DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<table>
 <thead>
  <tr>
   <th>省市</th>
   <th>中国长城宽带DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>广东长城宽带DNS</td>
   <td>211.162.78.1</td>
   <td>211.162.78.2</td>
  </tr>
  <tr>
   <td><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730180036805.png&amp;size=m" alt=""> 中国长城宽带DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<br>
<table>
 <thead>
  <tr>
   <th>省市</th>
   <th>中国天威视讯DNS服务器IP大全</th>
   <th></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>广东天威视讯DNS</td>
   <td>211.148.192.141</td>
   <td></td>
  </tr>
  <tr>
   <td><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730180036824.png&amp;size=m" alt=""> 中国天威视讯DNS服务器地址大全]</td>
   <td></td>
   <td></td>
  </tr>
 </tbody>
</table>
<br>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/n46u89pWMhedYrjn3RHU1w">https://mp.weixin.qq.com/s/n46u89pWMhedYrjn3RHU1w</a></p>]]></description><guid isPermaLink="false">/archives/69871c3d-d16f-4832-a12c-c515675a791b</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Tue, 29 Oct 2024 05:35:00 GMT</pubDate></item><item><title><![CDATA[如何确定光纤用几芯？用光纤与网线区别在哪里？]]></title><link>https://wiki.dgfygk.com/archives/5db43044-2536-4d17-be02-4445039f50bd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E7%A1%AE%E5%AE%9A%E5%85%89%E7%BA%A4%E7%94%A8%E5%87%A0%E8%8A%AF%EF%BC%9F%E7%94%A8%E5%85%89%E7%BA%A4%E4%B8%8E%E7%BD%91%E7%BA%BF%E5%8C%BA%E5%88%AB%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F&amp;url=/archives/5db43044-2536-4d17-be02-4445039f50bd" width="1" height="1" alt="" style="opacity:0;">
<p>不少朋友在做光纤项目时，都有一个疑问，<strong>光纤需要用几芯的？</strong> 本期我们一起来总结下。
 <br>
 01
 <br>
 光纤用几芯？</p>
<p>光纤芯数，主要和光纤连接的<strong>设备接口和设备</strong>的通信方式有关。一般来说，光纤中光芯的数量，为设备接口总数乘以2后，再加上10%～20％的备用数量，而如果设备的通信方式有设备多路复用，就能减少芯数。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849197951.png&amp;size=m" alt=""></p>
<p>按照IBDN标准，一般推荐每个<strong>建筑物内通讯间为12芯</strong> ，<strong>建筑间用24芯或48芯</strong>。当然这是一般情况，具体的话可以这样考虑：</p>
<p><strong>一、先清楚了解布线的数量：</strong></p>
<p>算出交换的台数，交换机之间的连接是堆叠还是不堆叠。</p>
<p><strong>堆叠：</strong> 核心交换机为双机热备冗余的话，6芯就可以（2台核心各用2芯，2芯冗余）。</p>
<p><strong>不堆叠：</strong> 一台交换机要4芯，多少台交换机乘以4加上4芯的冗余。</p>
<p><strong>二、一般情况下有几个终端就需要几芯：</strong></p>
<p>实际方案和施工时会考虑冗余，所以多会每个终端用两芯，要考虑成本也可以采用整个线路冗1-2芯，比如说你有三个光纤接入交换机，就需要有三芯（实际要用四芯的光缆）。</p>
<p>当然也不是绝对一光芯只能接一个终端设备，也可以在一根光芯上串联多台终端，<strong>但这需要多次熔接</strong>，光衰较大，不能实现远距离传输。</p>
<p><strong>三、根据终端使用光缆：</strong></p>
<p>例如某个光节点，应用系统有网络、监控。其中，网络只需要一条路由，占用2芯光纤；监控有4路，占用1芯光纤。则从机房至该光节点则共需要3芯光纤。</p>
<p>光缆设计从机房至该光节点为6芯光缆，其中3芯为冗余。实际就是拉一条6芯单模光缆至该光节点。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849198054.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>02
 <br>
 用光缆与网线的区别？</p>
<pre><code>那么使用光缆与网线有何区别呢？我们通过一个示例来了解：






      ![](/upload/1729849198168.png)
</code></pre>
<p>比如说某建筑有16层，每个楼层有200个信息点，若是全部用铜缆布的话，需要200根双绞线电缆到楼层配线间。 而最下面的中心机房也会有大量的双绞线接入，这样算下来的话，单单要容纳下这些双绞线的的坚井的容量就得非常的大。</p>
<pre><code>```
</code></pre>
<p>而换成光纤的话，每个楼层只要一根12芯的光纤就可以胜任。那么楼下中心机房最多就需要16根光纤就够了。</p>
<pre><code>      ![](/upload/1729849198183.png)
</code></pre>
<p>
 <br>
</p>
<p>弱电 \| 通信 \| 标准 \| 最新
 <br>
 公众号：弱电课堂 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849198198.png&amp;size=m" alt=""></p>
<pre><code>      学习推荐：


VIP技术交流群：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247590782&amp;idx=1&amp;sn=8dae01671b4f144f4402a0745222b97c&amp;chksm=ce4d3733f93abe25fe822b6d021a28be86e80b1c69069565e3e005e399ecf16b7661c29dc43d&amp;scene=21#wechat_redirect


弱电方案及报价：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247585221&amp;idx=1&amp;sn=e679fcec1b61b7ea758642e83fdc047b&amp;chksm=ce4dd988f93a509ecd8710c39c32ff0b216a1a119e5ba86831bb3c43f96e93ebc630d3459930&amp;scene=21#wechat_redirect


弱电图纸大全：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247591326&amp;idx=1&amp;sn=8cfffbd2ebcf811f49a6a7a398f9ad80&amp;chksm=ce4d31d3f93ab8c50064f000c68bd8a55912a10fb1efe057598ca5e6593443f71cea647d1c13&amp;scene=21#wechat_redirect
</code></pre>
<p>原文链接: https://mp.weixin.qq.com/s/5OkXqudx8_Ufy7Sacm_t3A</p>]]></description><guid isPermaLink="false">/archives/5db43044-2536-4d17-be02-4445039f50bd</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849197951.png&amp;size=m" type="image/jpeg" length="37562"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 01:03:51 GMT</pubDate></item><item><title><![CDATA[监控项目不会处理这三大故障，很难成为老司机]]></title><link>https://wiki.dgfygk.com/archives/61ff7856-137f-429c-94f7-a6c2b8d20ed6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%9B%91%E6%8E%A7%E9%A1%B9%E7%9B%AE%E4%B8%8D%E4%BC%9A%E5%A4%84%E7%90%86%E8%BF%99%E4%B8%89%E5%A4%A7%E6%95%85%E9%9A%9C%EF%BC%8C%E5%BE%88%E9%9A%BE%E6%88%90%E4%B8%BA%E8%80%81%E5%8F%B8%E6%9C%BA&amp;url=/archives/61ff7856-137f-429c-94f7-a6c2b8d20ed6" width="1" height="1" alt="" style="opacity:0;">
<p>在做监控项目中，最常易遇到故障就是<strong>画面卡顿、不显示、时有时无</strong> ，这三个故障基本上<strong>每个做监控项目的弱电人都会遇到</strong>，在我们弱电vip群中也经常讨论到，
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185045.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>本期我们一起来看下网络摄像机<strong>画面卡顿、不显示、时有时无</strong>的主要原因是什么。</p>
<p>
 <br>
</p>
<p><strong>一、监控画面卡顿</strong></p>
<p>
 <br>
</p>
<p><strong>原因一：</strong> <strong>摄像机自身原因</strong> 网络摄像机生产<strong>厂家本身技术存在缺陷</strong> ，生产出来的的网络摄像机长时间运行后会出现延时比较大的现象，大大超过了国标要求的延时时间，给人感觉画面卡顿，只有给摄像机断电后才能恢复正常，对于这种情况建议大家选择大品牌的设备，而且售后有保障的监控产品品牌。</p>
<p>
 <br>
</p>
<p><strong>原因二：</strong> <strong>解码器的解码能力不足</strong> 对于使用网络高清硬盘录像机显示存储的客户来说，选择的网络高清硬盘录像机的解码能力也会影响到画面的流畅性，如果连接画面超过解码器的<strong>最大解码能力</strong>，或者硬盘录像机选择的核心芯片<strong>本身处理能力不强</strong>，都会导致画面卡顿的现象。</p>
<p>
 <br>
</p>
<p><strong>原因三、NVR资源不足</strong></p>
<p>很多监控项目，大家喜欢将<strong>NVR满载</strong> ，或者接入摄像机的<strong>像素过高</strong> ，超出了NVR的能力，就会出现NVR资源不足，可能会引起画面卡顿或不显示，可以稍微<strong>降低码流或减少接入量来解决。</strong></p>
<p><strong>原因四：</strong> <strong>网线质量太差或距离太远</strong> 使用的网线质量太差，不是采用无氧铜质材料。一般正规国标网线最远传输距离建议不要超过100米，而劣质网线的传输性能更会大打折扣，如果采用了<strong>劣质网线</strong>，前期画面可能一切正常，后期随着线路的氧化衰减，容易导致信号传输丢包，时断时连，画面卡顿等现象。这种情况建议在施工中选择质量好的国标网线，可以通过<strong>测试百米电阻</strong>来判断网线好坏。</p>
<p>
 <br>
</p>
<p><strong>原因五：</strong> <strong>交换机的选择以及网络结构不合理</strong> 如果选择的交换机为非线速交换机，就会导致画面卡顿。选择交换机时不能单单只看<strong>包转发率、与背板带宽</strong> ，尤其是核心交换机的选择，目前国内的很多厂家对于包转发率与背板带宽这<strong>两个数据标的很虚</strong>，不能够反映出交换机的真实性能，所以在选择核心交换机时务<strong>必选择大品牌</strong>，详细了解交换机的用途与参数。</p>
<p>
 <br>
</p>
<p>建议8个点以下可使用百兆交换机，8个点以上需使用带千兆上传口的交换机。超过50个点位就需要三层汇聚网络结构，<strong>核心交换机需为大牌子</strong>，这个很多朋友为了省钱选择了一些小牌子，在后期出现故障都找不到原因。</p>
<p>对于画面卡顿的现象，通常是可以<strong>降低码流或帧率的方法解决</strong>，但是这样做虽然提高了画面的流畅性，却是以牺牲图像的<strong>清晰度</strong> 为代价，如果用户对画面清晰度要求较高，不建议使用，需要找到根本的原因问题，从而解决。</p>
<p>
 <br>
</p>
<p><strong>关于卡顿，弱电行业网补充个问题，也是比较常见的。</strong></p>
<p><strong>监控预览本身很流畅，没有卡顿现象，但回放时卡？</strong></p>
<p>这种情况一般是因为使用的<strong>硬盘读取速度过低</strong> 或硬盘录像机本身其他关系到<strong>读写性能的硬件配置过低造成的</strong>。可以将需要查看的监控通过备份的方式将监控录像备份到U盘，然后到普通电脑上进行查看，另外建议使用监控专用硬盘，购买硬盘时注意查看参数。</p>
<p>
 <br>
</p>
<p><strong>二、监控画面不显示</strong>
 <br>
 在做监控项目时，很多时候经常会遇到一个常见的问题，就是总有<strong>几路图像不显示</strong>，相信很多朋友都会遇到过，那到底是什么原因造成的呢？如何解决呢？我们一起来总结下。
 <br>
 1、线缆的问题</p>
<p>监控安装好后，插电调试，结果有几路摄像头的图像不显示。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185234.png&amp;size=m" alt=""></p>
<p>一般出现这样的情况，很多朋友首先会把问题定位在码率上，<strong>降低码率</strong>来解决问题，但是降低了码率问题<strong>还是存在</strong>。</p>
<p>
 <br>
</p>
<p>可能是网线出了问题？一看网线都在100米内，使用是超5类线，用<strong>测线仪测量</strong> ，线都是通的，<strong>都没有什么问题，电源问题？</strong>在电源上儿狠狠的花了些时间检测，还是没有发现问题的所在。
 <br>
 最后发现这几路不显示的图像，监控都是离主机80-90米左右的距离，按理说是没有什么问题，但问题如此集中在80-90米的监控上面，就是个问题，于是就把网线拆下来，测电阻，结果发现<strong>电阻在30多欧上</strong>。
 <br>
 是网线出了问题，网线应该是铜包铝网线，并不是无氧铜的，没有在正规渠道购买，正规<strong>无氧铜网线</strong> 的<strong>电阻100米不超过10欧</strong>，全部换国标线问题解决了。</p>
<p>2、供电问题</p>
<p>接手了一个维保的项目，突然有十几路的图像不显示，这种<strong>大面积的图像不显示</strong>，我们一般首先想到的是供电问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185444.png&amp;size=m" alt="">
 <br>
 于是检查电源，发现这十几路监控都是一路供电的，所以应该是供电出了问题，于是从弱电箱到电源一路检测，最终发现问到出现<strong>漏电断路器</strong>上。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185623.png&amp;size=m" alt=""></p>
<p>按漏电断路器不跳闸，经过万用表测试，确实是<strong>漏电断路器</strong>坏了，有问题，于是换个新的，那十几路不显示的监控图像都显示了。</p>
<p>所以在监控维护的时候，漏电断路器需要定期查看。</p>
<p>
 <br>
</p>
<p>3、水晶头没有做好</p>
<p>监控项目做完后，发现有<strong>一路摄像头没有图像</strong>，其它的都是完好，怎么回事？</p>
<p>
 <br>
</p>
<p>这是个非常容易忽略的细节问题，通过我们会把可能发生故障大方向的问题，都会一 一检测，检查电源供电、<strong>换摄像头</strong>，测网线问题都没有解决。</p>
<p>
 <br>
</p>
<p>问题就发生在细节上，很多朋友在做水晶头施工时，网线钳给双绞线去皮时，<strong>用力太大</strong>，造成<strong>一根网线几近断开</strong> ，或者水晶头<strong>没有按标准顺序接线</strong>，测试能通，但在千兆网络中就会丢包严重等。重新按标准方式制作水晶头，问题可以解决。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185817.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>4、核心交换机使用不当</p>
<p>有个朋友问到，200路新装的监控，项目做好后，经常有<strong>几十路监控不显示</strong>，时好时坏，什么原因？</p>
<p>
 <br>
</p>
<p>这类问题刚开始都会想到带宽或ip地址设置不当，当调低了码率和检查了ip后，发现问题依然存在。</p>
<p>
 <br>
</p>
<p><strong>那怎么办呢？</strong></p>
<p>于是就对监控的ip地址进行批量ping下。</p>
<p>使用以下批量ping命令：</p>
<p><strong>for /L %D in (1,1,255) do ping 10.168.1.%D</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849186004.png&amp;size=m" alt="">
 <br>
 当输入批量命令后，那么它就自动把网段内所有的ip地址都ping完为止。
 <br>
 代码中的这个<strong>(1,1,255)</strong> 就是网段起与始，就是检测网段192.168.1.1到192.168.1.255之间的所有的ip地址，每次逐增1，直接到1到255这255个ip检测完为止。 发现有一些<strong>ip地址ping不通</strong> ，所以就是检查交换机。
 <br>
 首先问了下对方交换机的牌子，使用是平时家用比较多的交换机牌子，很明显，对于这类的<strong>200路监控的项目</strong>，某些<strong>家用</strong>比较多的牌子还是不适合，还是需要使用项目用的比较多牌子的交换机（华为、h3c、锐捷等）。</p>
<p>除了背板带宽与包转发率，核心交换机还需要看可扩展性、虚拟化、的链路聚合、冗余、堆叠等参数，这些都是影响网络系统的稳定的参数。</p>
<p>
 <br>
</p>
<p><strong>三、图像时有时无</strong></p>
<p>
 <br>
</p>
<h1><strong>1、图像时有时无</strong></h1>
<p>1、电源电压供应是否稳定</p>
<p>2、网线水晶头是否松动未牢固</p>
<p>3、电源线是否有断线、接触不良</p>
<p>4、网络带宽达到上限，导致有时摄像机无图像传输。</p>
<p>
 <br>
</p>
<h1><strong>2、红外摄像机在白天工作正常晚上无图像</strong></h1>
<p>是电源或PoE交换机的<strong>功率达不到</strong>要求所致，因为红外一体机在白天工作时与普通摄像机无异，但到晚上时红外灯处于工作状态，工作<strong>电流突然变大导致超过负载</strong>。</p>
<p>
 <br>
</p>
<p>举个例子，摄像头是18W，共有7个这样的摄像头，如果你用的一个8 PoE供电口的交换机，电源功率是150W，除去损耗PoE总功率大概在140W左右，是对的白天没问题，但是到了晚上<strong>开启红外后</strong>，<strong>功率就会大些</strong>，所以还是这台交换机，肯定带不动了，那么就得更换一个功率更高的PoE交换机，比如换成200W以上的。</p>
<p>
 <br>
</p>
<p>弱电 \| 通信 \| 标准 \| 最新
 <br>
 公众号：弱电课堂 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849186123.png&amp;size=m" alt=""></p>
<pre><code>      学习推荐：


VIP技术交流群：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247590782&amp;idx=1&amp;sn=8dae01671b4f144f4402a0745222b97c&amp;chksm=ce4d3733f93abe25fe822b6d021a28be86e80b1c69069565e3e005e399ecf16b7661c29dc43d&amp;scene=21#wechat_redirect


弱电方案及报价：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247585221&amp;idx=1&amp;sn=e679fcec1b61b7ea758642e83fdc047b&amp;chksm=ce4dd988f93a509ecd8710c39c32ff0b216a1a119e5ba86831bb3c43f96e93ebc630d3459930&amp;scene=21#wechat_redirect


弱电图纸大全：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247591326&amp;idx=1&amp;sn=8cfffbd2ebcf811f49a6a7a398f9ad80&amp;chksm=ce4d31d3f93ab8c50064f000c68bd8a55912a10fb1efe057598ca5e6593443f71cea647d1c13&amp;scene=21#wechat_redirect
</code></pre>
<p>原文链接: https://mp.weixin.qq.com/s/7dDAdJ7MjHL6WdtyyzsUDA</p>]]></description><guid isPermaLink="false">/archives/61ff7856-137f-429c-94f7-a6c2b8d20ed6</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185234.png&amp;size=m" type="image/jpeg" length="212487"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 01:03:21 GMT</pubDate></item><item><title><![CDATA[利用打印机迁移功能实现三个步骤快速批量安装所有网络打印机]]></title><link>https://wiki.dgfygk.com/archives/a8eaf9d8-0fbc-4ab3-aa40-7baff6ee4718</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%A9%E7%94%A8%E6%89%93%E5%8D%B0%E6%9C%BA%E8%BF%81%E7%A7%BB%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0%E4%B8%89%E4%B8%AA%E6%AD%A5%E9%AA%A4%E5%BF%AB%E9%80%9F%E6%89%B9%E9%87%8F%E5%AE%89%E8%A3%85%E6%89%80%E6%9C%89%E7%BD%91%E7%BB%9C%E6%89%93%E5%8D%B0%E6%9C%BA&amp;url=/archives/a8eaf9d8-0fbc-4ab3-aa40-7baff6ee4718" width="1" height="1" alt="" style="opacity:0;">
<p>朋友们，当你面对一台电脑需要安装很多台不同网段网络打印机的时候你会怎么操作呢？是每台电脑一台一台地去安装还是有很便捷的方法呢？下边通过三个步骤给大家分享一个快速便捷安装多打印机的方法。</p>
<p>使用场景：企业中未规划统一分发打印功能，而员工能够在不同楼层都能使用该楼层的网络打印机进行资料打印。</p>
<p>安装步骤：</p>
<p>1、先利用局域网内一台电脑安装好所有需要的网络打印机的驱动程序。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730162986867.png&amp;size=m" alt=""></p>
<p>2、通过键盘视窗+R 打开 运行窗口 输入 printBrmUi.exe，呼出打印机迁移对话框，根据默认选择导出打印队列及打印驱动程序等打印机配置信息，并将该文件选择保存到自己指定的位置（导出的配置文件扩展名为.printerExport）。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730162986926.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730162986945.png&amp;size=m" alt=""></p>
<p>3、将导出的打印机配置文件拷贝到U盘或者网络共享位置，然后拷贝到需要安装网络打印机的电脑，双击该文件弹出打印机迁移对话框，再点下一步，直到打印机队列和打印机驱动程序导入完成。那么这个时候这台电脑中所有需要安装的网络打印机就安装好啦。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730162986961.png&amp;size=m" alt=""></p>
<p>朋友们，打印机迁移安装技术很便捷，有个建议给到大家：所有需要通过该方法导入网络打印机的电脑，其操作系统版本尽量跟导出的电脑操作系统版本保持一致。</p>
<p>原文链接: https://mp.weixin.qq.com/s/1E3blMbaOHCbLFJgqUjAxA</p>]]></description><guid isPermaLink="false">/archives/a8eaf9d8-0fbc-4ab3-aa40-7baff6ee4718</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730162986867.png&amp;size=m" type="image/jpeg" length="32982"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 01:02:34 GMT</pubDate></item><item><title><![CDATA[学会自定义打印纸去应付不同类型的文档打印]]></title><link>https://wiki.dgfygk.com/archives/ec4aa17d-5fed-4418-951d-46e936994a03</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AD%A6%E4%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89%E6%89%93%E5%8D%B0%E7%BA%B8%E5%8E%BB%E5%BA%94%E4%BB%98%E4%B8%8D%E5%90%8C%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%96%87%E6%A1%A3%E6%89%93%E5%8D%B0&amp;url=/archives/ec4aa17d-5fed-4418-951d-46e936994a03" width="1" height="1" alt="" style="opacity:0;">
<p>朋友们，对于一些财务部门工作的朋友来讲，凭证打印或发票打印面对的纸张大小是不一样的，那么我们如何按照特定的打印纸大小来打印相对应大小纸张的发票或者凭证呢，今天就来说说纸张大小的设定方法。</p>
<p>1、打开运行窗口，输入control printers调出设备和打印机设定窗口</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163015853.png&amp;size=m" alt=""></p>
<p>2、选择对应发票或凭证打印的打印机，然后右击该打印机选属性，再点 首选项呼出打印机首选项设定窗口。</p>
<p>3、在打印机首选项窗口中选中"用户自定义打印纸”，新建名称输入定义的打印纸名称，然后在打印纸尺寸按照打印纸的实际尺寸进行填写，边距根据自己实际的边距进行填写，填写好之后点确定完成自定义纸张的设定。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163015920.png&amp;size=m" alt=""></p>
<p>4、当需要打印发票或者财务凭证时，选定自己自定义的纸张进行打印就可以了。</p>
<p>朋友们，来打个总结：打印机首选项是可以自定义纸张大小的，当你面对打印不同类型的文档时会面对的不同纸张，如果你面对很多种类型的文档的话，那么你也可以新建出很多种不同尺寸的打印纸名称，当需要打印对应文档选择对应尺寸纸张就可以了。这新建纸张的方式对打印文档来说算不算未雨绸缪呢，欢迎朋友们留言。</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/O6PevToBg_TZlnxuCthrsA</p>]]></description><guid isPermaLink="false">/archives/ec4aa17d-5fed-4418-951d-46e936994a03</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163015853.png&amp;size=m" type="image/jpeg" length="46384"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 01:01:40 GMT</pubDate></item><item><title><![CDATA[两个步骤搞定打印机默认纸张大小规格的调整]]></title><link>https://wiki.dgfygk.com/archives/e709fa6e-d10d-4a12-a100-0560d68dc48c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%A4%E4%B8%AA%E6%AD%A5%E9%AA%A4%E6%90%9E%E5%AE%9A%E6%89%93%E5%8D%B0%E6%9C%BA%E9%BB%98%E8%AE%A4%E7%BA%B8%E5%BC%A0%E5%A4%A7%E5%B0%8F%E8%A7%84%E6%A0%BC%E7%9A%84%E8%B0%83%E6%95%B4&amp;url=/archives/e709fa6e-d10d-4a12-a100-0560d68dc48c" width="1" height="1" alt="" style="opacity:0;">
<p>朋友们，对于有些特定的岗位面对特定的纸张打印文档和资料，比如打印图纸的朋友输出A3大小较多，打印发票的朋友输入A5的大小比较多，那么这个时候按照自己的需求设定默认纸张进行输出将省去每次打印时纸张选择的步骤，为提高工作效率又加了一块砖。接下来我们来说说如何调整打印机的默认纸张规格的方法。</p>
<p>1、打开控制面板找到"设备和打印机”，右击需要设定的打印机，选打印机首选项</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163027648.png&amp;size=m" alt=""></p>
<p>2、进入"打印首选项"对话框，找到"文档尺寸"设定项，下拉，根据自己的需求选定需要的纸张尺寸，选定之后点应用，然后再按确定纸张的设置就完成啦。当打开文档打印时，不对文档纸张进行操作，默认打印大小就会是你设定的纸张规格进行打印了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163027697.png&amp;size=m" alt=""></p>
<p>两个步骤就搞定纸张规格的设定，是不是很简单呢，供朋友们参考。</p>
<p>原文链接: https://mp.weixin.qq.com/s/P5tG9u6oKixx-rXU_PfMkQ</p>]]></description><guid isPermaLink="false">/archives/e709fa6e-d10d-4a12-a100-0560d68dc48c</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163027648.png&amp;size=m" type="image/jpeg" length="44680"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 01:00:43 GMT</pubDate></item><item><title><![CDATA[一招搞定WIN10 共享打印机无法连接的问题]]></title><link>https://wiki.dgfygk.com/archives/32e0c908-e58e-4427-85b7-6ed5585ebde3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%80%E6%8B%9B%E6%90%9E%E5%AE%9AWIN10%20%E5%85%B1%E4%BA%AB%E6%89%93%E5%8D%B0%E6%9C%BA%E6%97%A0%E6%B3%95%E8%BF%9E%E6%8E%A5%E7%9A%84%E9%97%AE%E9%A2%98&amp;url=/archives/32e0c908-e58e-4427-85b7-6ed5585ebde3" width="1" height="1" alt="" style="opacity:0;">
<p>局域网内设置打印机共享是很普遍的事情，那么，当连接到Windows 10 配置的共享的打印机时，有些电脑就会出现Windows无法连接到打印机或者出现操作无法完成（错误0x00000709）。这种情况我们该如何处理呢？接下来我们用一招就能够搞定它。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163036778.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163036861.png&amp;size=m" alt=""></p>
<p>解决方法：通过新增注册表项将这个问题完美解决。</p>
<p>步骤：</p>
<p>1、打开运行输入regedit按回车</p>
<p>2、在注册表编辑器中找到如下路径：
 <br>
 计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print</p>
<p>3、在 Print 路径下新增DWORD（32位）值，将其命名为 RpcAuthnLevelPrivacyEnabled 。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163036911.png&amp;size=m" alt=""></p>
<p>4、新增DWORD值完成之后，需要重新启动电脑让新建注册表项生效。</p>
<p>此时，再去双击连接或打开这台电脑的共享打印机就可以顺利连接并能正常进行打印了。</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/3S04lUxunbSy42xH3Gz_iA</p>]]></description><guid isPermaLink="false">/archives/32e0c908-e58e-4427-85b7-6ed5585ebde3</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163036778.png&amp;size=m" type="image/jpeg" length="217365"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 01:00:02 GMT</pubDate></item><item><title><![CDATA[无网络接口打印机进行网络共享打印的配置步骤来了]]></title><link>https://wiki.dgfygk.com/archives/2052578a-feb1-48bc-a86e-313fd47e9aae</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%97%A0%E7%BD%91%E7%BB%9C%E6%8E%A5%E5%8F%A3%E6%89%93%E5%8D%B0%E6%9C%BA%E8%BF%9B%E8%A1%8C%E7%BD%91%E7%BB%9C%E5%85%B1%E4%BA%AB%E6%89%93%E5%8D%B0%E7%9A%84%E9%85%8D%E7%BD%AE%E6%AD%A5%E9%AA%A4%E6%9D%A5%E4%BA%86&amp;url=/archives/2052578a-feb1-48bc-a86e-313fd47e9aae" width="1" height="1" alt="" style="opacity:0;">
<p>朋友们，办公室有这么一个场景：有一台USB口的打印机连接到电脑主机，通过电脑主机进行打印工作。那有没有办法实现在办公室中所有电脑都可以用这台打印机进行文档打印呢？办法肯定是有的，那就是网络共享打印，实现方法也超级简单。下边跟着我一起来实现办公室所有电脑都能用这台打印机吧。</p>
<p>一、准备工作</p>
<p>确认设备：打印机已经通过USB线等方式连接到一台主机上，并且该电脑主机已经连接到内网中，和其他办公室内的电脑都在一个网络之中。
 <br>
 安装驱动程序：在主机上安装该打印机的驱动程序，确保打印机能够正常工作。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163045895.png&amp;size=m" alt=""></p>
<p>二、设置打印机共享</p>
<p>打开"设备和打印机”：在主机上，打开"控制面板”，选择"设备和打印机”。找到打印机：在"设备和打印机"窗口中，找到已经连接安装好的打印机图标。设置共享：右键点击打印机图标，选择"打印机属性”。在弹出的窗口中，选择"共享"选项卡。勾选"共享这台打印机"选项，并设置一个易于识别的共享名称。点击"应用"和"确定"按钮保存设置。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163045965.png&amp;size=m" alt=""></p>
<p>三、确认共享打印机承载的主机网络共享已开启</p>
<p>打开网络和共享中心：在主机上，打开"控制面板”，选择"网络和共享中心”。
 <br>
 更改高级共享设置：在"网络和共享中心"中，点击"更改高级共享设置”。
 <br>
 启用网络发现和文件共享：
 <br>
 在"专用"或"家庭"网络设置下，确保"网络发现"和"文件和打印机共享"选项已被启用。如果需要，还可以设置密码保护共享等安全选项。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163045999.png&amp;size=m" alt=""></p>
<p>四、在其他电脑上添加该共享打印机</p>
<p>1、在其他电脑上使用快捷键 Windows+R呼出运行框，输入安装打印机的那台电脑主机的网络IP地址（注意前边加双斜杠）回车，然后在网络对话框中出现已经被共享的打印机了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163046146.png&amp;size=m" alt=""></p>
<p>2、双击该打印机或者右击选择打开或者点击连接开始安装打印机驱动程序，直到安装打印机的对话框消失。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163046271.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163046334.png&amp;size=m" alt=""></p>
<p>3、打开"设备和打印机”：在主机上，打开"控制面板”，选择"设备和打印机”，是不是你双击安装的共享打印机已经出现在这里了呢。当你需要打印文档时，选择它进行打印就可以了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163046400.png&amp;size=m" alt=""></p>
<p>朋友们，通过以上步骤，您可以将无网络接口的普通打印机设置为网络共享打印机，方便局域网内的多台电脑共同使用打印机资源，是不是很方便呢。</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/Bo6rUnscvTWj0rZ5E3yq6g</p>]]></description><guid isPermaLink="false">/archives/2052578a-feb1-48bc-a86e-313fd47e9aae</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163046400.png&amp;size=m" type="image/jpeg" length="30202"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 00:59:11 GMT</pubDate></item><item><title><![CDATA[网络打印机安装明细步骤来了]]></title><link>https://wiki.dgfygk.com/archives/6d7923d8-9d2a-4deb-a927-2a5d90a120e3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%BB%9C%E6%89%93%E5%8D%B0%E6%9C%BA%E5%AE%89%E8%A3%85%E6%98%8E%E7%BB%86%E6%AD%A5%E9%AA%A4%E6%9D%A5%E4%BA%86&amp;url=/archives/6d7923d8-9d2a-4deb-a927-2a5d90a120e3" width="1" height="1" alt="" style="opacity:0;">
<p>朋友们，现在很多企业都通过网络的形式来连接打印机进行日常打印工作，下边我们就通过简便的步骤来安装我们的网络打印机，这样我们可以不仅可以避开打印机官方臃肿的安装文件，还可以节省过长的安装时间。</p>
<p>首先我们在安装之前先匹配我们的打印机型号在官方驱动文件中抽取适配我们打印机的驱动程序文件出来。一般这个驱动程序在官方驱动程序文件包中比较容易识别，如果看到带打印机型号的文件夹或者叫Driver的文件夹，你就可以将它拎出来了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163052789.png&amp;size=m" alt=""></p>
<p>打印机驱动程序主文件拎出来之后将它拷贝到需要安装的电脑上，以备安装之用。接下来，我们开始上真正的表演技术啦：</p>
<p>1、打开控制面板找到"设备和打印机"选项，点添加打印机，选择通过手动设置添加本地打印机或网络打印机</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163052838.png&amp;size=m" alt=""></p>
<p>2、选中创建新端口，选择Standard TCP/IP Port 项</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163052925.png&amp;size=m" alt=""></p>
<p>3、根据网络打印机的设置输入对应设置的打印机网络地址</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163053085.png&amp;size=m" alt=""></p>
<p>4、选择从磁盘安装，找到抽取出来的打印机驱动程序</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163053236.png&amp;size=m" alt=""></p>
<p>5、不通过该台计算机进行打印机共享的话就选择不共享这台打印机</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163053336.png&amp;size=m" alt=""></p>
<p>6、到这个页面网络打印机驱动程序就安装完成啦！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163053424.png&amp;size=m" alt=""></p>
<p>现在有些品牌网络打印机安装打印机弄得有点繁杂了，甚至有些还严重嵌入一些商业服务广告，在进行打印机安装向导的过程中会被一步步带偏，影响打印机安装速度。所以为提高工作效率，选择合适的安装方法我觉得是很有必要的，所以写一个直通目的地的安装方法出来，少点弯路，为朋友们服务，希望能帮到大家！</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/wPu–kcBJWGVo3KfsGTqmg</p>]]></description><guid isPermaLink="false">/archives/6d7923d8-9d2a-4deb-a927-2a5d90a120e3</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163052789.png&amp;size=m" type="image/jpeg" length="24152"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 00:58:48 GMT</pubDate></item><item><title><![CDATA[Windows自带的远程桌面功能比任何三方软件连接都便捷稳定]]></title><link>https://wiki.dgfygk.com/archives/985ff0ab-edb1-4180-93ba-06c46bd8a23d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Windows%E8%87%AA%E5%B8%A6%E7%9A%84%E8%BF%9C%E7%A8%8B%E6%A1%8C%E9%9D%A2%E5%8A%9F%E8%83%BD%E6%AF%94%E4%BB%BB%E4%BD%95%E4%B8%89%E6%96%B9%E8%BD%AF%E4%BB%B6%E8%BF%9E%E6%8E%A5%E9%83%BD%E4%BE%BF%E6%8D%B7%E7%A8%B3%E5%AE%9A&amp;url=/archives/985ff0ab-edb1-4180-93ba-06c46bd8a23d" width="1" height="1" alt="" style="opacity:0;">
<p>朋友们，作为运维的同学是否经常远程桌面电脑进行一些软件安装或者其他问题的处理，今天讲讲系统自带的远程桌面功能。</p>
<p>1、被控电脑远程桌面功能开启,方法如下：</p>
<p>1）windows 10系统开启方法：通过系统属性：按"Win + R"键，输入"sysdm.cpl"并按回车，打开系统属性。单击"远程"选项卡，勾选"允许远程连接到此计算机”。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163194820.png&amp;size=m" alt=""></p>
<p>2）windows 11系统开启方法：按"Windows + R"键打开运行对话框，执行control打开控制面板，选择"系统和安全”&gt;“允许远程访问”。在"远程桌面"区域中，选择"允许远程连接到此计算机”，并勾选"仅允许运行使用网络级别身份验证的远程桌面的计算机连接（建议）“。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163194915.png&amp;size=m" alt=""></p>
<p>2、系统防火墙开放远程端口的准入，方法如下：呼出运行栏输入wf.msc回车调出高级防火墙设置，确认远程桌面-用户模式启用，或者新建入站规则，TCP端口3389允许入站。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163195012.png&amp;size=m" alt=""></p>
<p>3、访问连接：通过上述两步骤设置，在同一个局域网内就可以通过内网IP地址对设置好的计算机进行远程访问控制了。方法：呼出运行栏输入mstsc回车，在对话框输入需要远控的计算机IP地址然后输入用户名密码就可以连接进行远程控制了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163195103.png&amp;size=m" alt=""></p>
<p>4、外网连接：如果企业在运营商处申请的有固定IP地址，那么可以将需要远程访问的地址端口映射到外网地址，然后通过固定的外网IP地址和端口进行远程访问此计算机。</p>
<p>朋友们，自带远程功能是不是比其他第三方稳定快捷呢？欢迎留言交流。</p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/I7-ptw2Ij7j7v6XXuMXuNg</p>]]></description><guid isPermaLink="false">/archives/985ff0ab-edb1-4180-93ba-06c46bd8a23d</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163194820.png&amp;size=m" type="image/jpeg" length="32848"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 00:56:11 GMT</pubDate></item><item><title><![CDATA[三个步骤解决台式电脑无网络的临时应急方法]]></title><link>https://wiki.dgfygk.com/archives/953b911b-a80e-4612-896a-4b77ddcaf790</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%89%E4%B8%AA%E6%AD%A5%E9%AA%A4%E8%A7%A3%E5%86%B3%E5%8F%B0%E5%BC%8F%E7%94%B5%E8%84%91%E6%97%A0%E7%BD%91%E7%BB%9C%E7%9A%84%E4%B8%B4%E6%97%B6%E5%BA%94%E6%80%A5%E6%96%B9%E6%B3%95&amp;url=/archives/953b911b-a80e-4612-896a-4b77ddcaf790" width="1" height="1" alt="" style="opacity:0;">
<p>朋友们，有时候因宽带故障、路由器等设备故障或其它原因，导致没有网络可用，但是电脑又急需要网络来处理一些应急工作或要紧的事情。如果使用的是笔记本电脑倒是可以利用手机的无线热点共享的方法来解决上网问题，那么咱们有线连接的台式电脑该怎么利用手机网络进行临时应急呢？下边通过三个步骤说一下通过USB数据线连接手机网络共享的方法供朋友们参考。</p>
<p>1、朋友们准备好手机和手机USB数据线（建议原装手机数据线，有些充电线仅有充电功能而无数据传输功能）。</p>
<p>2、用USB数据线将手机和电脑USB口连接。</p>
<p>3、在手机上进入 设置 &gt; 移动网络 &gt; 个人热点 &gt; 更多共享设置 ，开启 USB 共享网络开关，共享移动网络或已连接的 WLAN 网络给电脑。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163214268.png&amp;size=m" alt=""></p>
<p>此时查看电脑网络连接，就会看到多出来一个以太网2了，如果还不能上网就把原有的以太网通过鼠标右键禁用即可。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163214325.png&amp;size=m" alt=""></p>
<p>朋友们，设置完成之后，台式机电脑就可以通过USB连接手机共享网络连接互联网了，是不是很简单？还有哪些连接方式欢迎朋友们留言交流。</p>
<p>原文链接: https://mp.weixin.qq.com/s/7TCa0cN9A_rp7wjt-yH7_g</p>]]></description><guid isPermaLink="false">/archives/953b911b-a80e-4612-896a-4b77ddcaf790</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1730163214325.png&amp;size=m" type="image/jpeg" length="22140"/><category>IT运维</category><pubDate>Tue, 29 Oct 2024 00:55:57 GMT</pubDate></item><item><title><![CDATA[如何确定光纤用几芯？用光纤与网线区别在哪里？]]></title><link>https://wiki.dgfygk.com/archives/2edeb809-fab8-48c3-93d1-593039ce25e4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E7%A1%AE%E5%AE%9A%E5%85%89%E7%BA%A4%E7%94%A8%E5%87%A0%E8%8A%AF%EF%BC%9F%E7%94%A8%E5%85%89%E7%BA%A4%E4%B8%8E%E7%BD%91%E7%BA%BF%E5%8C%BA%E5%88%AB%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F&amp;url=/archives/2edeb809-fab8-48c3-93d1-593039ce25e4" width="1" height="1" alt="" style="opacity:0;">
<p>不少朋友在做光纤项目时，都有一个疑问，<strong>光纤需要用几芯的？</strong> 本期我们一起来总结下。
 <br>
 01
 <br>
 光纤用几芯？</p>
<p>光纤芯数，主要和光纤连接的<strong>设备接口和设备</strong>的通信方式有关。一般来说，光纤中光芯的数量，为设备接口总数乘以2后，再加上10%～20％的备用数量，而如果设备的通信方式有设备多路复用，就能减少芯数。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849197951.png&amp;size=m" alt=""></p>
<p>按照IBDN标准，一般推荐每个<strong>建筑物内通讯间为12芯</strong> ，<strong>建筑间用24芯或48芯</strong>。当然这是一般情况，具体的话可以这样考虑：</p>
<p><strong>一、先清楚了解布线的数量：</strong></p>
<p>算出交换的台数，交换机之间的连接是堆叠还是不堆叠。</p>
<p><strong>堆叠：</strong> 核心交换机为双机热备冗余的话，6芯就可以（2台核心各用2芯，2芯冗余）。</p>
<p><strong>不堆叠：</strong> 一台交换机要4芯，多少台交换机乘以4加上4芯的冗余。</p>
<p><strong>二、一般情况下有几个终端就需要几芯：</strong></p>
<p>实际方案和施工时会考虑冗余，所以多会每个终端用两芯，要考虑成本也可以采用整个线路冗1-2芯，比如说你有三个光纤接入交换机，就需要有三芯（实际要用四芯的光缆）。</p>
<p>当然也不是绝对一光芯只能接一个终端设备，也可以在一根光芯上串联多台终端，<strong>但这需要多次熔接</strong>，光衰较大，不能实现远距离传输。</p>
<p><strong>三、根据终端使用光缆：</strong></p>
<p>例如某个光节点，应用系统有网络、监控。其中，网络只需要一条路由，占用2芯光纤；监控有4路，占用1芯光纤。则从机房至该光节点则共需要3芯光纤。</p>
<p>光缆设计从机房至该光节点为6芯光缆，其中3芯为冗余。实际就是拉一条6芯单模光缆至该光节点。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849198054.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>02
 <br>
 用光缆与网线的区别？</p>
<pre><code>那么使用光缆与网线有何区别呢？我们通过一个示例来了解：






      ![](/upload/1729849198168.png)
</code></pre>
<p>比如说某建筑有16层，每个楼层有200个信息点，若是全部用铜缆布的话，需要200根双绞线电缆到楼层配线间。 而最下面的中心机房也会有大量的双绞线接入，这样算下来的话，单单要容纳下这些双绞线的的坚井的容量就得非常的大。</p>
<pre><code>```
</code></pre>
<p>而换成光纤的话，每个楼层只要一根12芯的光纤就可以胜任。那么楼下中心机房最多就需要16根光纤就够了。</p>
<pre><code>      ![](/upload/1729849198183.png)
</code></pre>
<p>
 <br>
</p>
<p>弱电 \| 通信 \| 标准 \| 最新
 <br>
 公众号：弱电课堂 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849198198.png&amp;size=m" alt=""></p>
<pre><code>      学习推荐：


VIP技术交流群：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247590782&amp;idx=1&amp;sn=8dae01671b4f144f4402a0745222b97c&amp;chksm=ce4d3733f93abe25fe822b6d021a28be86e80b1c69069565e3e005e399ecf16b7661c29dc43d&amp;scene=21#wechat_redirect


弱电方案及报价：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247585221&amp;idx=1&amp;sn=e679fcec1b61b7ea758642e83fdc047b&amp;chksm=ce4dd988f93a509ecd8710c39c32ff0b216a1a119e5ba86831bb3c43f96e93ebc630d3459930&amp;scene=21#wechat_redirect


弱电图纸大全：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247591326&amp;idx=1&amp;sn=8cfffbd2ebcf811f49a6a7a398f9ad80&amp;chksm=ce4d31d3f93ab8c50064f000c68bd8a55912a10fb1efe057598ca5e6593443f71cea647d1c13&amp;scene=21#wechat_redirect
</code></pre>
<p>原文链接: https://mp.weixin.qq.com/s/5OkXqudx8_Ufy7Sacm_t3A</p>]]></description><guid isPermaLink="false">/archives/2edeb809-fab8-48c3-93d1-593039ce25e4</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849197951.png&amp;size=m" type="image/jpeg" length="37562"/><category>网络与通信</category><pubDate>Sat, 26 Oct 2024 00:44:25 GMT</pubDate></item><item><title><![CDATA[监控项目不会处理这三大故障，很难成为老司机]]></title><link>https://wiki.dgfygk.com/archives/24954ca1-3535-49c6-9e50-b5dd069b57b4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%9B%91%E6%8E%A7%E9%A1%B9%E7%9B%AE%E4%B8%8D%E4%BC%9A%E5%A4%84%E7%90%86%E8%BF%99%E4%B8%89%E5%A4%A7%E6%95%85%E9%9A%9C%EF%BC%8C%E5%BE%88%E9%9A%BE%E6%88%90%E4%B8%BA%E8%80%81%E5%8F%B8%E6%9C%BA&amp;url=/archives/24954ca1-3535-49c6-9e50-b5dd069b57b4" width="1" height="1" alt="" style="opacity:0;">
<p>在做监控项目中，最常易遇到故障就是<strong>画面卡顿、不显示、时有时无</strong> ，这三个故障基本上<strong>每个做监控项目的弱电人都会遇到</strong>，在我们弱电vip群中也经常讨论到，
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185045.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>本期我们一起来看下网络摄像机<strong>画面卡顿、不显示、时有时无</strong>的主要原因是什么。</p>
<p>
 <br>
</p>
<p><strong>一、监控画面卡顿</strong></p>
<p>
 <br>
</p>
<p><strong>原因一：</strong> <strong>摄像机自身原因</strong> 网络摄像机生产<strong>厂家本身技术存在缺陷</strong> ，生产出来的的网络摄像机长时间运行后会出现延时比较大的现象，大大超过了国标要求的延时时间，给人感觉画面卡顿，只有给摄像机断电后才能恢复正常，对于这种情况建议大家选择大品牌的设备，而且售后有保障的监控产品品牌。</p>
<p>
 <br>
</p>
<p><strong>原因二：</strong> <strong>解码器的解码能力不足</strong> 对于使用网络高清硬盘录像机显示存储的客户来说，选择的网络高清硬盘录像机的解码能力也会影响到画面的流畅性，如果连接画面超过解码器的<strong>最大解码能力</strong>，或者硬盘录像机选择的核心芯片<strong>本身处理能力不强</strong>，都会导致画面卡顿的现象。</p>
<p>
 <br>
</p>
<p><strong>原因三、NVR资源不足</strong></p>
<p>很多监控项目，大家喜欢将<strong>NVR满载</strong> ，或者接入摄像机的<strong>像素过高</strong> ，超出了NVR的能力，就会出现NVR资源不足，可能会引起画面卡顿或不显示，可以稍微<strong>降低码流或减少接入量来解决。</strong></p>
<p><strong>原因四：</strong> <strong>网线质量太差或距离太远</strong> 使用的网线质量太差，不是采用无氧铜质材料。一般正规国标网线最远传输距离建议不要超过100米，而劣质网线的传输性能更会大打折扣，如果采用了<strong>劣质网线</strong>，前期画面可能一切正常，后期随着线路的氧化衰减，容易导致信号传输丢包，时断时连，画面卡顿等现象。这种情况建议在施工中选择质量好的国标网线，可以通过<strong>测试百米电阻</strong>来判断网线好坏。</p>
<p>
 <br>
</p>
<p><strong>原因五：</strong> <strong>交换机的选择以及网络结构不合理</strong> 如果选择的交换机为非线速交换机，就会导致画面卡顿。选择交换机时不能单单只看<strong>包转发率、与背板带宽</strong> ，尤其是核心交换机的选择，目前国内的很多厂家对于包转发率与背板带宽这<strong>两个数据标的很虚</strong>，不能够反映出交换机的真实性能，所以在选择核心交换机时务<strong>必选择大品牌</strong>，详细了解交换机的用途与参数。</p>
<p>
 <br>
</p>
<p>建议8个点以下可使用百兆交换机，8个点以上需使用带千兆上传口的交换机。超过50个点位就需要三层汇聚网络结构，<strong>核心交换机需为大牌子</strong>，这个很多朋友为了省钱选择了一些小牌子，在后期出现故障都找不到原因。</p>
<p>对于画面卡顿的现象，通常是可以<strong>降低码流或帧率的方法解决</strong>，但是这样做虽然提高了画面的流畅性，却是以牺牲图像的<strong>清晰度</strong> 为代价，如果用户对画面清晰度要求较高，不建议使用，需要找到根本的原因问题，从而解决。</p>
<p>
 <br>
</p>
<p><strong>关于卡顿，弱电行业网补充个问题，也是比较常见的。</strong></p>
<p><strong>监控预览本身很流畅，没有卡顿现象，但回放时卡？</strong></p>
<p>这种情况一般是因为使用的<strong>硬盘读取速度过低</strong> 或硬盘录像机本身其他关系到<strong>读写性能的硬件配置过低造成的</strong>。可以将需要查看的监控通过备份的方式将监控录像备份到U盘，然后到普通电脑上进行查看，另外建议使用监控专用硬盘，购买硬盘时注意查看参数。</p>
<p>
 <br>
</p>
<p><strong>二、监控画面不显示</strong>
 <br>
 在做监控项目时，很多时候经常会遇到一个常见的问题，就是总有<strong>几路图像不显示</strong>，相信很多朋友都会遇到过，那到底是什么原因造成的呢？如何解决呢？我们一起来总结下。
 <br>
 1、线缆的问题</p>
<p>监控安装好后，插电调试，结果有几路摄像头的图像不显示。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185234.png&amp;size=m" alt=""></p>
<p>一般出现这样的情况，很多朋友首先会把问题定位在码率上，<strong>降低码率</strong>来解决问题，但是降低了码率问题<strong>还是存在</strong>。</p>
<p>
 <br>
</p>
<p>可能是网线出了问题？一看网线都在100米内，使用是超5类线，用<strong>测线仪测量</strong> ，线都是通的，<strong>都没有什么问题，电源问题？</strong>在电源上儿狠狠的花了些时间检测，还是没有发现问题的所在。
 <br>
 最后发现这几路不显示的图像，监控都是离主机80-90米左右的距离，按理说是没有什么问题，但问题如此集中在80-90米的监控上面，就是个问题，于是就把网线拆下来，测电阻，结果发现<strong>电阻在30多欧上</strong>。
 <br>
 是网线出了问题，网线应该是铜包铝网线，并不是无氧铜的，没有在正规渠道购买，正规<strong>无氧铜网线</strong> 的<strong>电阻100米不超过10欧</strong>，全部换国标线问题解决了。</p>
<p>2、供电问题</p>
<p>接手了一个维保的项目，突然有十几路的图像不显示，这种<strong>大面积的图像不显示</strong>，我们一般首先想到的是供电问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185444.png&amp;size=m" alt="">
 <br>
 于是检查电源，发现这十几路监控都是一路供电的，所以应该是供电出了问题，于是从弱电箱到电源一路检测，最终发现问到出现<strong>漏电断路器</strong>上。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185623.png&amp;size=m" alt=""></p>
<p>按漏电断路器不跳闸，经过万用表测试，确实是<strong>漏电断路器</strong>坏了，有问题，于是换个新的，那十几路不显示的监控图像都显示了。</p>
<p>所以在监控维护的时候，漏电断路器需要定期查看。</p>
<p>
 <br>
</p>
<p>3、水晶头没有做好</p>
<p>监控项目做完后，发现有<strong>一路摄像头没有图像</strong>，其它的都是完好，怎么回事？</p>
<p>
 <br>
</p>
<p>这是个非常容易忽略的细节问题，通过我们会把可能发生故障大方向的问题，都会一 一检测，检查电源供电、<strong>换摄像头</strong>，测网线问题都没有解决。</p>
<p>
 <br>
</p>
<p>问题就发生在细节上，很多朋友在做水晶头施工时，网线钳给双绞线去皮时，<strong>用力太大</strong>，造成<strong>一根网线几近断开</strong> ，或者水晶头<strong>没有按标准顺序接线</strong>，测试能通，但在千兆网络中就会丢包严重等。重新按标准方式制作水晶头，问题可以解决。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185817.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>4、核心交换机使用不当</p>
<p>有个朋友问到，200路新装的监控，项目做好后，经常有<strong>几十路监控不显示</strong>，时好时坏，什么原因？</p>
<p>
 <br>
</p>
<p>这类问题刚开始都会想到带宽或ip地址设置不当，当调低了码率和检查了ip后，发现问题依然存在。</p>
<p>
 <br>
</p>
<p><strong>那怎么办呢？</strong></p>
<p>于是就对监控的ip地址进行批量ping下。</p>
<p>使用以下批量ping命令：</p>
<p><strong>for /L %D in (1,1,255) do ping 10.168.1.%D</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849186004.png&amp;size=m" alt="">
 <br>
 当输入批量命令后，那么它就自动把网段内所有的ip地址都ping完为止。
 <br>
 代码中的这个<strong>(1,1,255)</strong> 就是网段起与始，就是检测网段192.168.1.1到192.168.1.255之间的所有的ip地址，每次逐增1，直接到1到255这255个ip检测完为止。 发现有一些<strong>ip地址ping不通</strong> ，所以就是检查交换机。
 <br>
 首先问了下对方交换机的牌子，使用是平时家用比较多的交换机牌子，很明显，对于这类的<strong>200路监控的项目</strong>，某些<strong>家用</strong>比较多的牌子还是不适合，还是需要使用项目用的比较多牌子的交换机（华为、h3c、锐捷等）。</p>
<p>除了背板带宽与包转发率，核心交换机还需要看可扩展性、虚拟化、的链路聚合、冗余、堆叠等参数，这些都是影响网络系统的稳定的参数。</p>
<p>
 <br>
</p>
<p><strong>三、图像时有时无</strong></p>
<p>
 <br>
</p>
<h1><strong>1、图像时有时无</strong></h1>
<p>1、电源电压供应是否稳定</p>
<p>2、网线水晶头是否松动未牢固</p>
<p>3、电源线是否有断线、接触不良</p>
<p>4、网络带宽达到上限，导致有时摄像机无图像传输。</p>
<p>
 <br>
</p>
<h1><strong>2、红外摄像机在白天工作正常晚上无图像</strong></h1>
<p>是电源或PoE交换机的<strong>功率达不到</strong>要求所致，因为红外一体机在白天工作时与普通摄像机无异，但到晚上时红外灯处于工作状态，工作<strong>电流突然变大导致超过负载</strong>。</p>
<p>
 <br>
</p>
<p>举个例子，摄像头是18W，共有7个这样的摄像头，如果你用的一个8 PoE供电口的交换机，电源功率是150W，除去损耗PoE总功率大概在140W左右，是对的白天没问题，但是到了晚上<strong>开启红外后</strong>，<strong>功率就会大些</strong>，所以还是这台交换机，肯定带不动了，那么就得更换一个功率更高的PoE交换机，比如换成200W以上的。</p>
<p>
 <br>
</p>
<p>弱电 \| 通信 \| 标准 \| 最新
 <br>
 公众号：弱电课堂 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849186123.png&amp;size=m" alt=""></p>
<pre><code>      学习推荐：


VIP技术交流群：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247590782&amp;idx=1&amp;sn=8dae01671b4f144f4402a0745222b97c&amp;chksm=ce4d3733f93abe25fe822b6d021a28be86e80b1c69069565e3e005e399ecf16b7661c29dc43d&amp;scene=21#wechat_redirect


弱电方案及报价：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247585221&amp;idx=1&amp;sn=e679fcec1b61b7ea758642e83fdc047b&amp;chksm=ce4dd988f93a509ecd8710c39c32ff0b216a1a119e5ba86831bb3c43f96e93ebc630d3459930&amp;scene=21#wechat_redirect


弱电图纸大全：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247591326&amp;idx=1&amp;sn=8cfffbd2ebcf811f49a6a7a398f9ad80&amp;chksm=ce4d31d3f93ab8c50064f000c68bd8a55912a10fb1efe057598ca5e6593443f71cea647d1c13&amp;scene=21#wechat_redirect
</code></pre>
<p>原文链接: https://mp.weixin.qq.com/s/7dDAdJ7MjHL6WdtyyzsUDA</p>]]></description><guid isPermaLink="false">/archives/24954ca1-3535-49c6-9e50-b5dd069b57b4</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185444.png&amp;size=m" type="image/jpeg" length="178713"/><category>IT运维</category><pubDate>Sat, 26 Oct 2024 00:44:08 GMT</pubDate></item><item><title><![CDATA[机房建设标准化施工规范]]></title><link>https://wiki.dgfygk.com/archives/83aee8b2-6633-4a65-a93b-af3f702f3e40</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%BA%E6%88%BF%E5%BB%BA%E8%AE%BE%E6%A0%87%E5%87%86%E5%8C%96%E6%96%BD%E5%B7%A5%E8%A7%84%E8%8C%83&amp;url=/archives/83aee8b2-6633-4a65-a93b-af3f702f3e40" width="1" height="1" alt="" style="opacity:0;">
<h4><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472816616.png&amp;size=m" alt=""></strong></h4>
<p><strong></strong>不少朋友问到这个施工规范。<strong></strong></p>
<p>主要关于<strong>机房环境、用电、安全、布置、设备安装</strong>这些等等问题，在我们弱电VIP技术群中也不断有朋友讨论到。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472816639.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>本期我们来通过这个来了解下。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472816722.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472816777.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472816821.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472816904.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817012.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817105.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817214.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817279.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817342.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817389.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817459.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817504.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817569.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817636.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817685.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817761.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817859.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472817935.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818008.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818070.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818147.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818205.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818357.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818418.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818488.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818635.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818699.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818766.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818874.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472818948.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819018.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819071.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819122.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819184.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819239.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819298.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819376.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819455.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819514.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819568.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819663.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>弱电机房建设标准化规范</strong> ：</p>
<p>可以在弱电行业网后台回复”<strong>机房施工</strong>“即可获取。</p>
<p><a href="http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247592737&amp;idx=2&amp;sn=8a4ece91304a2b6ac924a946e6a29386&amp;chksm=ce4d3f6cf93ab67a30e0f7a98d1df4dc37e704ccda9dc11c0f6a631681b4f808c5c16f7a26d7&amp;scene=21#wechat_redirect"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472819712.png&amp;size=m" alt=""></a></p>
<p>
 <br>
</p>
<p><strong>学习推荐：</strong></p>
<p>VIP技术交流群：<a href="http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247590782&amp;idx=1&amp;sn=8dae01671b4f144f4402a0745222b97c&amp;chksm=ce4d3733f93abe25fe822b6d021a28be86e80b1c69069565e3e005e399ecf16b7661c29dc43d&amp;scene=21#wechat_redirect">点我</a></p>
<p>弱电方案及报价：<a href="http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247603624&amp;idx=1&amp;sn=ead5079147266011ef77534a7fa5888b&amp;chksm=ce4d01e5f93a88f30f80dd86b8b4c2fcc33d4fac46881df51f77c5be18fe2fda1bf10452d4f3&amp;scene=21#wechat_redirect">点我</a></p>
<p>弱电图纸大全：<a href="http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247601025&amp;idx=1&amp;sn=e266190e06523f705088c69fc6f4cda4&amp;chksm=ce4d1fccf93a96daf18583f554111b312fbe37eae9d1801a4c928599dcf3c1a620f389a46ab9&amp;scene=21#wechat_redirect">点我</a></p>
<p>原文链接: https://mp.weixin.qq.com/s/znFHBORVKAli9p_HKYpacw</p>]]></description><guid isPermaLink="false">/archives/83aee8b2-6633-4a65-a93b-af3f702f3e40</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729849185045.png&amp;size=m" type="image/jpeg" length="198313"/><category>网络运维</category><pubDate>Mon, 21 Oct 2024 01:09:50 GMT</pubDate></item><item><title><![CDATA[智能化弱电工程界面划分表，17个系统，很详细]]></title><link>https://wiki.dgfygk.com/archives/5e16c73f-d810-4ecb-8e02-73d17757331c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%99%BA%E8%83%BD%E5%8C%96%E5%BC%B1%E7%94%B5%E5%B7%A5%E7%A8%8B%E7%95%8C%E9%9D%A2%E5%88%92%E5%88%86%E8%A1%A8%EF%BC%8C17%E4%B8%AA%E7%B3%BB%E7%BB%9F%EF%BC%8C%E5%BE%88%E8%AF%A6%E7%BB%86&amp;url=/archives/5e16c73f-d810-4ecb-8e02-73d17757331c" width="1" height="1" alt="" style="opacity:0;">
<h4 id=""><strong><strong><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472697682.png&amp;size=m" alt=""></strong></strong></strong></h4>
<p><strong>弱电系统有哪些</strong> ，如何进行划分界面呢？这个经常有项目经理不是很清楚，常见的弱电系统有17个，我们本期一起来看下，如何对弱电系统划分施工。</p>
<p><strong>弱电智能化工程界面划分表</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472697898.png&amp;size=m" alt=""></p>
<p>比较详细，包括弱电大部分的系统，适用于弱电大部分项目。</p>
<p><strong>此图下载版本已上传知识星球，星球资料不断更新，若要下载相关弱电精品资料的，可以扫码加入，持续更新。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472697995.png&amp;size=m" alt="">
 <br>
 弱电 | 通信 | 标准 | 最新
 <br>
 公众号：弱电课堂 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472698099.png&amp;size=m" alt=""></p>
<pre><code>  
   
    
     


     
学习推荐：

     
VIP技术交流群：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247590782&amp;idx=1&amp;sn=8dae01671b4f144f4402a0745222b97c&amp;chksm=ce4d3733f93abe25fe822b6d021a28be86e80b1c69069565e3e005e399ecf16b7661c29dc43d&amp;scene=21#wechat_redirect

     
弱电方案及报价：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247603624&amp;idx=1&amp;sn=ead5079147266011ef77534a7fa5888b&amp;chksm=ce4d01e5f93a88f30f80dd86b8b4c2fcc33d4fac46881df51f77c5be18fe2fda1bf10452d4f3&amp;scene=21#wechat_redirect

     
弱电图纸大全：http://mp.weixin.qq.com/s?__biz=Mzg2NjIyMzM5NA==&amp;mid=2247601025&amp;idx=1&amp;sn=e266190e06523f705088c69fc6f4cda4&amp;chksm=ce4d1fccf93a96daf18583f554111b312fbe37eae9d1801a4c928599dcf3c1a620f389a46ab9&amp;scene=21#wechat_redirect

    
   
  
</code></pre>
<p>原文链接: <a href="https://mp.weixin.qq.com/s/3nrAfuwQBK8gL8LNt3qnlQ">https://mp.weixin.qq.com/s/3nrAfuwQBK8gL8LNt3qnlQ</a></p>]]></description><guid isPermaLink="false">/archives/5e16c73f-d810-4ecb-8e02-73d17757331c</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1729472697682.png&amp;size=m" type="image/jpeg" length="186799"/><category>网络与通信</category><pubDate>Mon, 21 Oct 2024 01:09:35 GMT</pubDate></item><item><title><![CDATA[Spring 实现 3 种异步接口]]></title><link>https://wiki.dgfygk.com/archives/fd8aa66a-8308-430d-b384-602f7490c8d2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20%E5%AE%9E%E7%8E%B0%203%20%E7%A7%8D%E5%BC%82%E6%AD%A5%E6%8E%A5%E5%8F%A3&amp;url=/archives/fd8aa66a-8308-430d-b384-602f7490c8d2" width="1" height="1" alt="" style="opacity:0;">
<p><strong>大家好，我是苏三～</strong></p>
<p>如何处理比较耗时的接口？</p>
<p>这题我熟，直接上异步接口，使用 <code>Callable</code>、<code>WebAsyncTask</code> 和 <code>DeferredResult</code>、<code>CompletableFuture</code>等均可实现。</p>
<p>但这些方法有局限性，处理结果仅返回单个值。在某些场景下，如果需要接口异步处理的同时，还持续不断地向客户端响应处理结果，这些方法就不够看了。</p>
<p>Spring 框架提供了多种工具支持异步流式接口，如 <code>ResponseBodyEmitter</code>、<code>SseEmitter</code> 和 <code>StreamingResponseBody</code>。这些工具的用法简单，接口中直接返回相应的对象或泛型响应实体 <code>ResponseEntity&lt;xxxx&gt;</code>，如此这些接口就是异步的，且执行耗时操作亦不会阻塞 <code>Servlet</code> 的请求线程，不影响系统的响应能力。</p>
<p>下面将逐一介绍每个工具的使用及其应用场景。</p>
<h2>ResponseBodyEmitter</h2>
<p><code>ResponseBodyEmitter</code>适用于要动态生成内容并逐步发送给客户端的场景，例如：文件上传进度、实时日志等，可以在任务执行过程中逐步向客户端发送更新。</p>
<p>举个例子，经常用GPT你会发现当你提问后，得到的答案并不是一次性响应呈现的，而是逐步动态显示。这样做的好处是，让你感觉它在认真思考，交互体验比直接返回完整答案更为生动和自然。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_gif%2F0OzaL5uW2aPBc1E1o03KEbA2DXxEVodxMfrNnCURSNc9WH3mNIPHGspTZDX15ytmU3NvXUktfXibhlT3w1Xo9qw%2F640%3Fwx_fmt%3Dgif&amp;size=m" alt=""></p>
<p>使用<code>ResponseBodyEmitter</code>来实现下这个效果，创建 ResponseBodyEmitter 发送器对象，模拟耗时操作逐步调用 send 方法发送消息。</p>
<blockquote>
 <p>注意：ResponseBodyEmitter 的超时时间，如果设置为 <code>0</code> 或 <code>-1</code>，则表示连接不会超时；如果不设置，到达默认的超时时间后连接会自动断开。<strong>其他两种工具也是同样的用法，后边不在赘述了</strong></p>
 <pre><code>@GetMapping("/bodyEmitter")
public ResponseBodyEmitter handle() {
    // 创建一个ResponseBodyEmitter，-1代表不超时
    ResponseBodyEmitter emitter = new ResponseBodyEmitter(-1L);
    // 异步执行耗时操作
    CompletableFuture.runAsync(() -&gt; {
        try {
            // 模拟耗时操作
            for (int i = 0; i &lt; 10000; i++) {
                System.out.println("bodyEmitter " + i);
                // 发送数据
                emitter.send("bodyEmitter " + i + " @ " + new Date() + "\n");
                Thread.sleep(2000);
            }
            // 完成
            emitter.complete();
        } catch (Exception e) {
            // 发生异常时结束接口
            emitter.completeWithError(e);
        }
    });
    return emitter;
}
</code></pre>
</blockquote>
<p>实现代码非常简单。通过模拟每2秒响应一次结果，请求接口时可以看到页面数据在动态生成。效果与 GPT 回答基本一致。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_gif%2F0OzaL5uW2aPBc1E1o03KEbA2DXxEVodxaOEwibCRwicCiaId25d2oe2xldHs4D8KcUicGzlvNgzRSAL57j2IU80AMw%2F640%3Fwx_fmt%3Dgif&amp;size=m" alt=""></p>
<h2>SseEmitter</h2>
<p><code>SseEmitter</code> 是 <code>ResponseBodyEmitter</code> 的一个子类，它同样能够实现动态内容生成，不过主要将它用在<strong>服务器向客户端</strong> 推送实时数据，如实时消息推送、状态更新等场景。在我之前的一篇文章 <a href="https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg==&amp;mid=2247500543&amp;idx=1&amp;sn=2a75dbd8846b1c0ea6ce6c59dfb98cef&amp;chksm=9b865202acf1db14009f2d5bedc463396ed0ac213de1dcc2eaf3daf378996c6f4a0353306f4b&amp;token=1013468972&amp;lang=zh_CN&amp;scene=21#wechat_redirect">我有 7种 实现web实时消息推送的方案</a> 中详细介绍了 <code>Server-Sent Events (SSE)</code> 技术，感兴趣的可以回顾下。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F0OzaL5uW2aPBc1E1o03KEbA2DXxEVodx7JsVM2UIstVjRAEoVgDaYT6TMicHpEMamZS3InfCx47mE9WXXo8JfGg%2F640%3Fwx_fmt%3Dpng&amp;size=m" alt=""></p>
<p>SSE在服务器和客户端之间打开一个单向通道，服务端响应的不再是一次性的数据包而是<code>text/event-stream</code>类型的数据流信息，在有数据变更时从服务器流式传输到客户端。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F0OzaL5uW2aPBc1E1o03KEbA2DXxEVodx7dib5hzTuU7PwCFdk1ZyAicqq26qqYBWS2euETAknmDBB5D3fh81mX9w%2F640%3Fwx_fmt%3Dpng&amp;size=m" alt=""></p>
<p>整体的实现思路有点类似于在线视频播放，视频流会连续不断的推送到浏览器，你也可以理解成，客户端在完成一次用时很长（网络不畅）的下载。</p>
<p>客户端JS实现，通过一次 HTTP 请求建立连接后，等待接收消息。此时，服务端为每个连接创建一个 <code>SseEmitter</code> 对象，通过这个通道向客户端发送消息。</p>
<pre><code>&lt;body&gt;
&lt;div id="content" style="text-align: center;"&gt;
    &lt;h1&gt;SSE 接收服务端事件消息数据&lt;/h1&gt;
    &lt;div id="message"&gt;等待连接...&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
    let source = null;
    let userId = 7777

    function setMessageInnerHTML(message) {
        const messageDiv = document.getElementById("message");
        const newParagraph = document.createElement("p");
        newParagraph.textContent = message;
        messageDiv.appendChild(newParagraph);
    }

    if (window.EventSource) {
        // 建立连接
        source = new EventSource('http://127.0.0.1:9033/subSseEmitter/'+userId);
        setMessageInnerHTML("连接用户=" + userId);
        /**
         * 连接一旦建立，就会触发open事件
         * 另一种写法：source.onopen = function (event) {}
         */
        source.addEventListener('open', function (e) {
            setMessageInnerHTML("建立连接。。。");
        }, false);
        /**
         * 客户端收到服务器发来的数据
         * 另一种写法：source.onmessage = function (event) {}
         */
        source.addEventListener('message', function (e) {
            setMessageInnerHTML(e.data);
        });
    } else {
        setMessageInnerHTML("你的浏览器不支持SSE");
    }
&lt;/script&gt;
&lt;/body&gt;
</code></pre>
<p>在服务端，我们将 <code>SseEmitter</code> 发送器对象进行持久化，以便在消息产生时直接取出对应的 SseEmitter 发送器，并调用 <code>send</code> 方法进行推送。</p>
<pre><code>private static final Map&lt;String, SseEmitter&gt; EMITTER_MAP = new ConcurrentHashMap&lt;&gt;();

@GetMapping("/subSseEmitter/{userId}")
public SseEmitter sseEmitter(@PathVariable String userId) {
    log.info("sseEmitter: {}", userId);
    SseEmitter emitterTmp = new SseEmitter(-1L);
    EMITTER_MAP.put(userId, emitterTmp);
    CompletableFuture.runAsync(() -&gt; {
        try {
            SseEmitter.SseEventBuilder event = SseEmitter.event()
                    .data("sseEmitter" + userId + " @ " + LocalTime.now())
                    .id(String.valueOf(userId))
                    .name("sseEmitter");
            emitterTmp.send(event);
        } catch (Exception ex) {
            emitterTmp.completeWithError(ex);
        }
    });
    return emitterTmp;
}

@GetMapping("/sendSseMsg/{userId}")
public void sseEmitter(@PathVariable String userId, String msg) throws IOException {
    SseEmitter sseEmitter = EMITTER_MAP.get(userId);
    if (sseEmitter == null) {
        return;
    }
    sseEmitter.send(msg);
}
</code></pre>
<p>接下来向 <code>userId=7777</code> 的用户发送消息，127.0.0.1:9033/sendSseMsg/7777?msg=欢迎关注–&gt;程序员小富，该消息可以在页面上实时展示。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_gif%2F0OzaL5uW2aPBc1E1o03KEbA2DXxEVodxTOnWickFY4qYfDnibbAMmHMMm8pL7wf5fbtS0iav4kbqiaicrPDSbibaNYhQ%2F640%3Fwx_fmt%3Dgif&amp;size=m" alt=""></p>
<p>而且SSE有一点比较好，客户端与服务端一旦建立连接，即便服务端发生重启，也可以做到<strong>自动重连</strong>。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F0OzaL5uW2aPBc1E1o03KEbA2DXxEVodxkmb1r45o1icsD8r4Sy8yOxGUSEHRfOLbkmvRFba3BGticd6siamibdHC8Q%2F640%3Fwx_fmt%3Dpng&amp;size=m" alt=""></p>
<h2>StreamingResponseBody</h2>
<p><code>StreamingResponseBody</code> 与其他响应处理方式略有不同，主要用于处理大数据量或持续数据流的传输，支持将数据直接写入<code>OutputStream</code>。</p>
<p>例如，当我们需要下载一个超大文件时，使用 StreamingResponseBody 可以避免将文件数据一次性加载到内存中，而是持续不断的把文件流发送给客户端，从而解决下载大文件时常见的内存溢出问题。</p>
<p>接口实现直接返回 StreamingResponseBody 对象，将数据写入输出流并刷新，调用一次<code>flush</code>就会向客户端写入一次数据。</p>
<pre><code>@GetMapping("/streamingResponse")
public ResponseEntity&lt;StreamingResponseBody&gt; handleRbe() {

    StreamingResponseBody stream = out -&gt; {
        String message = "streamingResponse";
        for (int i = 0; i &lt; 1000; i++) {
            try {
                out.write(((message + i) + "\r\n").getBytes());
                out.write("\r\n".getBytes());
                //调用一次flush就会像前端写入一次数据
                out.flush();
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(stream);
}
</code></pre>
<p>demo这里输出的是简单的文本流，如果是下载文件那么转换成文件流效果是一样的。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_gif%2F0OzaL5uW2aPBc1E1o03KEbA2DXxEVodx8SjicuX7DDyKe5esicqUCycyJ2Vliap3Ygk1Yv6qMGLm73ae6BONB7ZVQ%2F640%3Fwx_fmt%3Dgif&amp;size=m" alt=""></p>
<h2>总结</h2>
<p>这篇介绍三种实现异步流式接口的工具，算是 Spring 知识点的扫盲。使用起来比较简单，没有什么难点，但它们在实际业务中的应用场景还是很多的，通过这些工具，可以有效提高系统的性能和响应能力。</p>
<p>最后欢迎<a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247519298&amp;idx=1&amp;sn=8e052ae328c37d2e0aa7093e5864cdd6&amp;chksm=c0e854aaf79fddbc9b819f9e099b911615aad6e13c30f2fc59e6b28a07d266d057ee6c927a66&amp;token=249273388&amp;lang=zh_CN&amp;scene=21#wechat_redirect">加入苏三的星球</a>，你将获得：商城系统实战、秒杀系统实战、代码生成工具、系统设计、性能优化、技术选型、高频面试题、底层原理、Spring源码解读、工作经验分享、痛点问题等多个优质专栏。</p>
<ul>
 <li><a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247519298&amp;idx=1&amp;sn=8e052ae328c37d2e0aa7093e5864cdd6&amp;chksm=c0e854aaf79fddbc9b819f9e099b911615aad6e13c30f2fc59e6b28a07d266d057ee6c927a66&amp;token=249273388&amp;lang=zh_CN&amp;scene=21#wechat_redirect">我的技术成长之路</a></li>
 <li><a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247518752&amp;idx=1&amp;sn=51b60dc6ee21e5042192eed74682f9ef&amp;chksm=c0e852c8f79fdbdea7bd1a0ac93d986c32ef5ab5542e24d672dae60f786424ed50055a96d941&amp;token=249273388&amp;lang=zh_CN&amp;scene=21#wechat_redirect">我的三个项目</a></li>
 <li><a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247518298&amp;idx=1&amp;sn=8bb5d8eef5d818bf3b1df2d31f6fa7f7&amp;chksm=c0e850b2f79fd9a4aab89b9968b369819aba7680b99f44a12bc92bb36dbe6d4090b3cdb7ed8d&amp;token=249273388&amp;lang=zh_CN&amp;scene=21#wechat_redirect">被官方推荐了</a></li>
</ul>
<p>还有1V1答疑、修改简历、职业规划、送书活动、技术交流。</p>
<p>目前星球已经更新了<a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247519298&amp;idx=1&amp;sn=8e052ae328c37d2e0aa7093e5864cdd6&amp;chksm=c0e854aaf79fddbc9b819f9e099b911615aad6e13c30f2fc59e6b28a07d266d057ee6c927a66&amp;token=249273388&amp;lang=zh_CN&amp;scene=21#wechat_redirect">4400+</a>篇优质内容，还在持续爆肝中..星球已经被官方推荐了3次，收到了小伙伴们的一致好评。<a href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&amp;mid=2247519298&amp;idx=1&amp;sn=8e052ae328c37d2e0aa7093e5864cdd6&amp;chksm=c0e854aaf79fddbc9b819f9e099b911615aad6e13c30f2fc59e6b28a07d266d057ee6c927a66&amp;token=249273388&amp;lang=zh_CN&amp;scene=21#wechat_redirect">戳我加入学习，已有1400+小伙伴加入学习</a>。</p>
<p>我的技术专栏《程序员最常见的100个问题》，目前已经更新了80篇干货文章，里面收录了很多踩坑经历，对你的职业生涯或许有些帮助，最近收到的好评挺多的。</p>
<p>这个专栏总结了我10年工作中，遇到过的100个非常有代表性的技术问题，非常有参考和学习价值。</p>
<p>Java、Spring、分布式、高并发、数据库、海量数据、线上问题什么都有。</p>
<p>每篇文章从发现问题、分析问题、解决问题和问题总结等多个维度，深入浅出，分享了很多技术细节，定位和排查问题思路，解决问题技巧，以及实际工作经验。</p>
<p>你能从中学到很多有用知识，帮你少走很多弯路。</p>
<p>扫描下方二维码即可订阅：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2FibJZVicC7nz5j2gbKD6hFIC6G1dfexoaBVczEy895RvFGzsUUvacv8VW3o7NOcgzI7xKtKqlDI0O6Kcyf9yCiaxIA%2F640%3Fwx_fmt%3Dother%5C%26from%3Dappmsg%5C%26wxfrom%3D5%5C%26wx_lazy%3D1%5C%26wx_co%3D1%5C%26tp%3Dwebp&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原价199，现价只需23，即将涨价。</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FuL371281oDEU1Oia130N2IFx8rmOZ0srhOFG6TXVUUaIJSqbJWCFibwMAh0u5cZp3EBYVzJIy27G2YoficryvVTQA%2F640%3Fwx_fmt%3Dother%5C%26wxfrom%3D5%5C%26wx_lazy%3D1%5C%26wx_co%3D1%5C%26tp%3Dwebp&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: https://mp.weixin.qq.com/s/T7WT7bQNoMrDd2WU8WrROg</p>]]></description><guid isPermaLink="false">/archives/fd8aa66a-8308-430d-b384-602f7490c8d2</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 18 Oct 2024 01:07:29 GMT</pubDate></item><item><title><![CDATA[光纤跳线fc-sc ]]></title><link>https://wiki.dgfygk.com/archives/guang-xian-tiao-xian-fc-sc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%89%E7%BA%A4%E8%B7%B3%E7%BA%BFfc-sc%20&amp;url=/archives/guang-xian-tiao-xian-fc-sc" width="1" height="1" alt="" style="opacity:0;">
<p style="">光纤跳线FC-SC是一种常见的光纤连接器跳线，它用于连接不同类型的光纤接口。这种跳线的一端装有FC连接器，另一端装有SC连接器。</p>
<p style="">FC连接器（Ferrule Connector）是一种早期的光纤连接器类型，它采用金属套管和螺纹锁紧机制，具有较好的机械性能和稳定性。FC连接器通常用于单模光纤的连接，其端面经过抛光处理，以减少插入损耗。</p>
<p style="">SC连接器（Subscriber Connector）是一种更现代的光纤连接器类型，它采用直插式设计和卡扣式锁定机制，安装简便且插拔次数多。SC连接器同样适用于单模光纤的连接，其端面也经过抛光处理。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-tzci.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F9da061257b6e4f5189ed3d693c017de6.webp&amp;size=m" width="991px" height="874px" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%253A%252F%252Fcbu01.alicdn.com%252Fimg%252Fibank%252F2018%252F098%252F352%252F8857253890_1103706557.jpg%26refer%3Dhttp%253A%252F%252Fcbu01.alicdn.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1731679103%26t%3D770acc495d35330f4f7a8f4e28f324a0&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">FC-SC光纤跳线在数据中心、企业网络、电信运营商以及测试实验室等场合中广泛应用。它提供了一种灵活的连接解决方案，允许从FC接口的设备连接到SC接口的设备，或者反之。这种跳线有助于实现不同光纤设备之间的无缝连接，同时确保了光信号的高效传输。</p>
<p style="">在选择FC-SC光纤跳线时，应确保跳线的光纤类型（如单模或多模）、光纤芯径以及连接器的质量符合应用需求。此外，应确保跳线的长度适中，既能满足设备之间的距离要求，又能保持信号的质量。x</p>]]></description><guid isPermaLink="false">/archives/guang-xian-tiao-xian-fc-sc</guid><dc:creator>Administrator</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F9da061257b6e4f5189ed3d693c017de6.webp&amp;size=m" type="image/jpeg" length="24714"/><category>网络运维</category><category>计算机基础</category><pubDate>Wed, 16 Oct 2024 14:00:43 GMT</pubDate></item><item><title><![CDATA[Windows原版镜像]]></title><link>https://wiki.dgfygk.com/archives/windowsyuan-ban-jing-xiang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Windows%E5%8E%9F%E7%89%88%E9%95%9C%E5%83%8F&amp;url=/archives/windowsyuan-ban-jing-xiang" width="1" height="1" alt="" style="opacity:0;">
<p style="">本文搜集了微软官方的原版Windows镜像，无任何第三方插件，无任何定制内容。</p>
<p style="text-align: start; ">使用U盘安装时需要使用U盘写入工具。</p>
<h2 style="text-align: start; " id="1%E3%80%81windows-11"><strong>1、Windows 11</strong></h2>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpic4.zhimg.com%2Fv2-cb341b7fd598893cb9ab7598b8c0ba15_b.jpg&amp;size=m" width="279" height="auto" style="display: inline-block"></p>
<p style="text-align: start; ">首先确认电脑是否支持Windows11，不能支持的话请下载使用Windows10系统。</p>
<p style=""><a href="https://link.zhihu.com/?target=https%3A//learn.microsoft.com/zh-cn/windows-hardware/design/minimum/supported/windows-11-22h2-supported-amd-processors" target="_blank" class="LinkCard new css-1vqsdx1"><strong><span style="font-size: 15px; color: rgb(25, 27, 31)">Windows 处理器要求 - Windows 11 版本 22H2 支持的 AMD 处理器</span></strong><span fontsize="" color="">​</span>learn.microsoft.com/zh-cn/windows-hardware/design/minimum/supported/windows-11-22h2-supported-amd-processors<span fontsize="" color=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpicx.zhimg.com%2Fv2-3e54a8bea93a92dc204e84a1faba72e5_180x120.jpg&amp;size=m" alt="" width="60px" height="60px" style="display: inline-block"></span></a></p>
<p style=""><a href="https://link.zhihu.com/?target=https%3A//learn.microsoft.com/zh-cn/windows-hardware/design/minimum/supported/windows-11-22h2-supported-intel-processors" target="_blank" class="LinkCard new css-1vqsdx1"><strong><span style="font-size: 15px; color: rgb(25, 27, 31)">Windows 处理器要求 - Windows 11 版本 22H2 支持的 Intel 处理器</span></strong><span fontsize="" color="">​</span>learn.microsoft.com/zh-cn/windows-hardware/design/minimum/supported/windows-11-22h2-supported-intel-processors<span fontsize="" color=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpicx.zhimg.com%2Fv2-3e54a8bea93a92dc204e84a1faba72e5_180x120.jpg&amp;size=m" alt="" width="60px" height="60px" style="display: inline-block"></span></a></p>
<h3 style="text-align: start; " id="windows-11-(business-editions)%2C-version-23h2-(x64)---dvd-(chinese-simplified)"><strong>Windows 11 (business editions), version 23H2 (x64) - DVD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">含专业版+企业版+教育版+专业教育版+专业工作站版</p>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|zh-cn_windows_11_business_editions_version_23h2_x64_dvd_2a79e0f1.iso|6613571584|1AFADEBC5966E9E689F242549FFB5562|/</code></pre>
<p style="text-align: start; ">BT链接地址</p>
<pre><code class="language-text">magnet:?xt=urn:btih:b5fed4ee16b1c9b886f44dd30cd70fdf746f58f5&amp;dn=zh-cn_windows_11_business_editions_version_23h2_x64_dvd_2a79e0f1.iso&amp;xl=6613571584</code></pre>
<h3 style="text-align: start; " id="windows-11-(consumer-editions)%2C-version-23h2-(x64)---dvd-(chinese-simplified)"><strong>Windows 11 (consumer editions), version 23H2 (x64) - DVD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">含家庭版+专业版+教育版+专业教育版+专业工作站版</p>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|zh-cn_windows_11_consumer_editions_version_23h2_x64_dvd_91207780.iso|6714650624|5E927EF9FA65D78D1898213281CB3757|/</code></pre>
<p style="text-align: start; ">BT链接地址</p>
<pre><code class="language-text">magnet:?xt=urn:btih:19ffe4debe0e0466dcf7a93dc910756e23d77356&amp;dn=zh-cn_windows_11_consumer_editions_version_23h2_x64_dvd_91207780.iso&amp;xl=6714650624</code></pre>
<h2 style="text-align: start; " id="2%E3%80%81windows-10"><strong>2、Windows 10</strong></h2>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpicx.zhimg.com%2Fv2-c2130cbb8c0d370762ec414f270bc309_b.jpg&amp;size=m" width="260" height="auto" style="display: inline-block"></p>
<h3 style="text-align: start; " id="windows-10-(business-editions)%2C-version-22h2-(updated-oct-2023)-(x64)---dvd-(chinese-simplified)"><strong>Windows 10 (business editions), version 22H2 (updated Oct 2023) (x64) - DVD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">含专业版+企业版+教育版+专业工作站版+专业教育版</p>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|zh-cn_windows_10_business_editions_version_22h2_updated_oct_2023_x64_dvd_eb713365.iso|6467465216|319250DB5C7A85D0F567E468FE1BD1D5|/</code></pre>
<p style="text-align: start; ">BT链接地址</p>
<pre><code class="language-text">magnet:?xt=urn:btih:fc0a90cb47251361d696fd2057e82d3f3ee2b108&amp;dn=zh-cn_windows_10_business_editions_version_22h2_updated_oct_2023_x64_dvd_eb713365.iso&amp;xl=6467465216</code></pre>
<h3 style="text-align: start; " id="windows-10-(consumer-editions)%2C-version-22h2-(updated-oct-2023)-(x64)---dvd-(chinese-simplified)"><strong>Windows 10 (consumer editions), version 22H2 (updated Oct 2023) (x64) - DVD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">含家庭版+专业版+家庭单语言+教育版+专业工作站版+专业教育版</p>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|zh-cn_windows_10_consumer_editions_version_22h2_updated_oct_2023_x64_dvd_eb811ccc.iso|6598432768|45CC11AB8B6C51ED9B03598B83E79025|/</code></pre>
<p style="text-align: start; ">BT链接地址</p>
<pre><code class="language-text">magnet:?xt=urn:btih:4077052e62c2edf64dfba46092af1df7c5137627&amp;dn=zh-cn_windows_10_consumer_editions_version_22h2_updated_oct_2023_x64_dvd_eb811ccc.iso&amp;xl=6598432768</code></pre>
<h3 style="text-align: start; " id="windows-10-enterprise-ltsc-2021-(x64)---dvd-(chinese-simplified)"><strong>Windows 10 Enterprise LTSC 2021 (x64) - DVD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|SW_DVD9_WIN_ENT_LTSC_2021_64BIT_ChnSimp_MLF_X22-84402.ISO|5044211712|1555B7DCA052B5958EE68DB58A42408D|/</code></pre>
<p style="text-align: start; ">BT链接地址</p>
<pre><code class="language-text">magnet:?xt=urn:btih:366ADAA52FB3639B17D73718DD5F9E3EE9477B40&amp;dn=SW_DVD9_WIN_ENT_LTSC_2021_64BIT_ChnSimp_MLF_X22-84402.ISO&amp;xl=5044211712</code></pre>
<h2 style="text-align: start; " id="3%E3%80%81windows-7"><strong>3、Windows 7</strong></h2>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpic1.zhimg.com%2Fv2-1f3d98a58700144c94c8dbcac4b4f42e_b.jpg&amp;size=m" width="268" height="auto" style="display: inline-block"></p>
<h3 style="text-align: start; " id="windows-7-ultimate-with-service-pack-1-(x64)---dvd-(chinese-simplified)"><strong>Windows 7 Ultimate with Service Pack 1 (x64) - DVD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso|3420557312|B58548681854236C7939003B583A8078|/</code></pre>
<p style="text-align: start; ">BT链接地址</p>
<pre><code class="language-text">magnet:?xt=urn:btih:E86414F638E11104248108B155BE9408A8362509&amp;dn=cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso&amp;xl=3420557312</code></pre>
<h3 style="text-align: start; " id="windows-7-ultimate-with-service-pack-1-(x86)---dvd-(chinese-simplified)"><strong>Windows 7 Ultimate with Service Pack 1 (x86) - DVD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|cn_windows_7_ultimate_with_sp1_x86_dvd_u_677486.iso|2653276160|7503E4B9B8738DFCB95872445C72AEFB|/</code></pre>
<p style="text-align: start; ">BT链接地址</p>
<pre><code class="language-text">magnet:?xt=urn:btih:585DF592DE43A067C75CFE5A639B41FC3F24DA6F&amp;dn=cn_windows_7_ultimate_with_sp1_x86_dvd_u_677486.iso&amp;xl=2653276160</code></pre>
<h2 style="text-align: start; " id="4%E3%80%81windows-xp"><strong>4、Windows XP</strong></h2>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpic3.zhimg.com%2Fv2-d321d94eca3c150782006661c8c62cd2_b.jpg&amp;size=m" width="200" height="auto" style="display: inline-block"></p>
<h3 style="text-align: start; " id="windows-xp-professional-with-service-pack-3-(x86)---cd-vl-(chinese-simplified)"><strong>Windows XP Professional with Service Pack 3 (x86) - CD VL (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|zh-hans_windows_xp_professional_with_service_pack_3_x86_cd_vl_x14-74070.iso|630237184|EC51916C9D9B8B931195EE0D6EE9B40E|/</code></pre>
<h3 style="text-align: start; " id="windows-xp-professional-with-service-pack-3-(x86)---cd-(chinese-simplified)"><strong>Windows XP Professional with Service Pack 3 (x86) - CD (Chinese-Simplified)</strong></h3>
<p style="text-align: start; ">ED2K链接地址</p>
<pre><code class="language-text">ed2k://|file|zh-hans_windows_xp_professional_with_service_pack_3_x86_cd_x14-80404.iso|630239232|CD0900AFA058ACB6345761969CBCBFF4|/</code></pre>
<hr>
<p style="text-align: start; ">收录的镜像资源，来自微软官方镜像，未做任何更改。如有侵权私信联系删除。</p>
<p style="text-align: start; ">原文链接：https://zhuanlan.zhihu.com/p/665128603</p>]]></description><guid isPermaLink="false">/archives/windowsyuan-ban-jing-xiang</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Wed, 16 Oct 2024 00:49:13 GMT</pubDate></item><item><title><![CDATA[最全的办公楼智能化解决方案]]></title><link>https://wiki.dgfygk.com/archives/zui-quan-de-ban-gong-lou-zhi-neng-hua-jie-jue-fang-an</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%80%E5%85%A8%E7%9A%84%E5%8A%9E%E5%85%AC%E6%A5%BC%E6%99%BA%E8%83%BD%E5%8C%96%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&amp;url=/archives/zui-quan-de-ban-gong-lou-zhi-neng-hua-jie-jue-fang-an" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 16px; color: rgb(0, 0, 0)">办公楼综合体智能化如何建设？</span></strong><span style="font-size: 16px; color: rgb(0, 0, 0)">有哪些系统？近几年，办公楼智能化的项目越来越多，不少项目经理都参与其它，同事办公楼综合体也是弱电系统涉及的最多的项目之一，</span><strong><span style="font-size: 16px; color: rgb(0, 0, 0)">本期我们一起来看下，最全的办公楼项目智能化设计方案。</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pryi.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vlsn.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dytl.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hpnj.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ihfx.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-neso.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nbrq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zzab.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-weeu.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ytnl.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-stuj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cgwu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bryr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mjmr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ldgx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-riht.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zkqm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lesa.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qpfv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pcun.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rmdm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tngh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-htmy.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aifd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bpid.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ymwv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mtbm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lahm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qfbm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mfny.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bnbw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yqvn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xhrg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wcpf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-apmv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-neal.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rikn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vpcz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ocod.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qefj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kfkx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-elxm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ipwy.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zude.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jjvl.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zhbw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lssn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-igmh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zkao.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-scye.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-poox.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-obwr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-avmn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aszj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fmxh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aaxg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gmzj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yavf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zfql.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iqhu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ktov.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ufkv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hblc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wljk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zsbx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jtzq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mxsp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fuyv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">原文链接：https://mp.weixin.qq.com/s/HKR4fMiags0LXYlE4J7WWQ</p>]]></description><guid isPermaLink="false">/archives/zui-quan-de-ban-gong-lou-zhi-neng-hua-jie-jue-fang-an</guid><dc:creator>黄健</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pryi.webp&amp;size=m" type="image/jpeg" length="67538"/><category>默认分类</category><pubDate>Wed, 16 Oct 2024 00:45:30 GMT</pubDate></item><item><title><![CDATA[如何将NAS上的共享文件夹设置成桌面快速方式]]></title><link>https://wiki.dgfygk.com/archives/ru-he-jiang-nasshang-de-gong-xiang-wen-jian-jia-she-zhi-cheng-zhuo-mian-kuai-su-fang-shi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%B0%86NAS%E4%B8%8A%E7%9A%84%E5%85%B1%E4%BA%AB%E6%96%87%E4%BB%B6%E5%A4%B9%E8%AE%BE%E7%BD%AE%E6%88%90%E6%A1%8C%E9%9D%A2%E5%BF%AB%E9%80%9F%E6%96%B9%E5%BC%8F&amp;url=/archives/ru-he-jiang-nasshang-de-gong-xiang-wen-jian-jia-she-zhi-cheng-zhuo-mian-kuai-su-fang-shi" width="1" height="1" alt="" style="opacity:0;">
<p style="">使用场景：已将A电脑的文件设置分享，打算在B电脑中找到A电脑的共享文件</p>
<p style="text-align: start; ">1、在桌面空白区域点击右键，点击菜单【新建】-【快捷方式】；</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpicx.zhimg.com%2F80%2Fv2-45ff58b866e7d4ca5574bb014c34e167_720w.webp&amp;size=m" width="500" height="167" style="display: inline-block"></p>
<p style="text-align: start; ">
 <br>
 2、在【创建快捷方式】页面，输入网络存储中的共享文件夹UNC路径（\\ip\共享文件夹名）后，点击【下一步】；
</p>
<p style="text-align: start; ">PS：进行本步骤的提前，已将A电脑的文件设置分享。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpic3.zhimg.com%2F80%2Fv2-dbf1d509e8190df5eb8e0418aa7a8740_720w.webp&amp;size=m" width="500" height="367" style="display: inline-block"></p>
<p style="text-align: start; ">3、输入连接网络存储的账号及密码，并勾选【记住我的凭证】，点击【确定】；
 <br>
 PS：输入连接网络存储的账号及密码是A电脑的。以下卡片链接是教如何查询网络账号及密码【网络凭据的用户名和密码】。</p>
<p style=""><a href="https://zhuanlan.zhihu.com/p/634588148" target="_blank" class="LinkCard new css-1vqsdx1"><strong><span style="font-size: 15px; color: rgb(25, 27, 31)">KLAUS：分享文件的电脑，怎么查输入网络凭据的用户名和密码是什么</span></strong><span style="font-size: 13px; color: rgb(145, 150, 161)">31 赞同 · 0 评论</span><span style="font-size: 11px">文章</span></a></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpicx.zhimg.com%2F80%2Fv2-cf46f1d8f42f82154c20c3c3fc1dd56d_720w.webp&amp;size=m" width="500" height="310" style="display: inline-block"></p>
<p style="text-align: start; ">4、给快速方式输入一个自定义名称，点击【完成】；</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpic1.zhimg.com%2F80%2Fv2-88c458e98c6723e0e6a88274007a048e_720w.webp&amp;size=m" width="500" height="367" style="display: inline-block"></p>
<p style="text-align: start; ">5、桌面就生成了访问网络存储的快捷方式，双击快捷方式就能直接打开共享文件夹了，是不是很方便呢！</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fpic4.zhimg.com%2F80%2Fv2-4c56bfcc12b8551c0074ecba04ed6bab_720w.webp&amp;size=m" width="500" height="304" style="display: inline-block"></p>]]></description><guid isPermaLink="false">/archives/ru-he-jiang-nasshang-de-gong-xiang-wen-jian-jia-she-zhi-cheng-zhuo-mian-kuai-su-fang-shi</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Mon, 14 Oct 2024 05:53:31 GMT</pubDate></item><item><title><![CDATA[常见管道管件名称【中英文】对照表]]></title><link>https://wiki.dgfygk.com/archives/chang-jian-guan-dao-guan-jian-ming-cheng-zhong-ying-wen-dui-zhao-biao</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B8%B8%E8%A7%81%E7%AE%A1%E9%81%93%E7%AE%A1%E4%BB%B6%E5%90%8D%E7%A7%B0%E3%80%90%E4%B8%AD%E8%8B%B1%E6%96%87%E3%80%91%E5%AF%B9%E7%85%A7%E8%A1%A8&amp;url=/archives/chang-jian-guan-dao-guan-jian-ming-cheng-zhong-ying-wen-dui-zhao-biao" width="1" height="1" alt="" style="opacity:0;">
<p style=""><span style="font-size: 14px">之前多次有粉丝让整理一份管道管件名称的中英文对照表，于是利用国庆假期整理了一下，这里发出来供各位参考，不准确的欢迎留言指正。</span></p>
<p style=""><span style="font-size: 14px">
  <br>
 </span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fujc.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dltu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mwcu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pmlb.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/chang-jian-guan-dao-guan-jian-ming-cheng-zhong-ying-wen-dui-zhao-biao</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 14 Oct 2024 00:55:04 GMT</pubDate></item><item><title><![CDATA[什么是KVM分布式系统？由哪些设备构成？]]></title><link>https://wiki.dgfygk.com/archives/shi-me-shi-kvmfen-bu-shi-xi-tong-you-na-xie-she-bei-gou-cheng</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AFKVM%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%EF%BC%9F%E7%94%B1%E5%93%AA%E4%BA%9B%E8%AE%BE%E5%A4%87%E6%9E%84%E6%88%90%EF%BC%9F&amp;url=/archives/shi-me-shi-kvmfen-bu-shi-xi-tong-you-na-xie-she-bei-gou-cheng" width="1" height="1" alt="" style="opacity:0;">
<p style="">做过机房项目的都知道KVM系统，KVM是Keyboard Video Mouse的缩写。一般应用在电脑机房内，由一个KVM系统可以控制整个机柜的服务器，共享一个显示器，一套键盘和屏幕。</p>
<p style=""></p>
<p style=""><strong>编解码器</strong></p>
<p style="">KVM分布式系统是将这种KVM技术和视频编解码技术进行整合，利用IP网络传输视频数据和控制数据。一套最简单的KVM分布式系统就是有编解码构成，部署在一个IP网络内。</p>
<p style=""></p>
<p style="">在视频源端，通过编码器对视频进行编码，比如我们的电脑输出的画面，可以通过HDMI显示器接口接入编码器，以网络视频流的方式传输。这样就可以将本机的视频画面显示到其它显示设备上。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xrxr.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span style="font-size: 14px">分布式组网基础架构</span></p>
<p style="">同样，在分布式网络内，所有的显示设备都可以搭配解码器，将网络视频解码到显示设备上，这些显示设备可以是大屏幕，电脑显示器等设备。</p>
<p style=""></p>
<p style="">所以分布式系统的核心，其实就是一些编码器，解码器构成。这些编解码通过网传输，达到灵活调看的目前。KVM分布式系统一般应用在指挥中心，视频会议室等环境，利用本地网络传输等优势，实现更好的显示效果。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wodc.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span style="font-size: 14px">带有光纤接口的分布式编解码器</span></p>
<p style="">目前KVM分布式系统为了更好的显示效果和更低的传输延时。除了我们常见的IP分布式系统还有光纤分布式系统。在指挥大厅的各种编解码器都通过光纤传输，达到更快的传输，更大带宽的视频传输。但是光纤分布式需要专门的布线，成本也会有所上升。</p>
<p style=""></p>
<p style="">当然，视频的编码器，解码器只是KVM分布式系统的基础。一套KVM分布式系统部署下来还会涉及很多设备。还会涉及到视频矩阵，视频拼接，视频控制和视频融合等多个部分。</p>
<p style=""></p>
<p style=""><strong>视频矩阵</strong></p>
<p style="">视频矩阵很好理解，我们在部署一套分布式系统时，一定是由很多的视频输入，输出设备构成，那么这些视频必须进行统一的接入管理后才能灵活的对其控制分配，通过视频矩阵将所有涉及的视频以输入，输出的方式接入视频矩阵。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ujvd.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span style="font-size: 14px">分布式视频矩阵</span></p>
<p style="">通过对视频矩阵的控制，可以将任意视频在分布式系统内的任何屏幕进行显示，复制等操控，视频源的切换都可以灵活的进行管理分配。</p>
<p style=""></p>
<p style=""><strong>拼接设备</strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wtse.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span style="font-size: 14px">视频拼接设备</span></p>
<p style="">KVM分布式系统一般都是应用在指挥中心，控制中心等环境，都会配置大屏进行多种视频源的呈现。在很多时候需要对不同的视频进行拼接后上屏。或者在显示画面上进行开窗，漫游，轮询等。都需要通过视频拼接设备完成，特别是分布式系统，大屏都是尺寸较大，需要实现更丰富的功能，需要能力更强的视频拼接设备。</p>
<p style=""></p>
<p style=""><strong>控制设备</strong></p>
<p style="">控制设备一般分两部分，一部分是由传统的中控系统发展而来，结合分布式的控制特点，通过串口，IP，485等方式，控制音视频设备以及灯光，空调等设备。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aimh.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span style="font-size: 14px">分布式控制界面</span></p>
<p style="">另外分布式系统等控制就是对于KVM坐席等应用，提供软件等控制能力支撑，比如视频等操控，显示，复制，音视频等对讲通话等能力，这些都是软件能力的支撑才可以实现。一般需要专门的软件服务器进行支撑。</p>
<p style=""></p>
<p style=""></p>
<p style=""><strong>融合设备</strong></p>
<p style="">KVM分布式一般在指挥中心部署，但是指挥中心除了音视频的本地呈现外，还会涉及到与第三方平台的整合。比如我们需要将视频监控接入到分布式系统，将视频会议，无人机等系统接入到分布式系统进行控制。</p>
<p style=""></p>
<p style="">这种一般通过外部的网关设备或者系统实现，比如要对接视频监控，无人机等视频，可以通过思载科技视频接入网关，提供多种能力接入，并输出RTSP，RTP等低延迟流媒体格式，方便分布式系统编解码。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hmxx.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span style="font-size: 14px">视频转码服务器的分布式应用</span></p>
<p style="">在与融合通信系统和调度指挥系统整合中，可以配置视频转码服务器，解决其它系统视频码流较小，分布式系统下码流较大的问题。并且提供SIP协议和RTSP协议的互转，让分布式系统和基于SIP的融合通信系统可以无缝融合。</p>
<p style=""></p>
<p style="">KVM分布式系统利用音视频编解码的特性，通过网络实现了更灵活的控制和显示能力，丰富了指挥中心等应用场景等应用方式。一套KVM分布式系统是一个庞大的系统，除了上述等设备系统外，还会涉及音响系统，无纸化系统，视频会议系统等系统等部署。需要对相关系统设备有较好的理解才可以设计出更完善的解决方案，作为用户，对KVM分布式系统有所了解，也可以更准确的了解自身需求，选择适合自己的解决方案。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/CW24zM-Efdnb8s1Dqtt6hg：</p>]]></description><guid isPermaLink="false">/archives/shi-me-shi-kvmfen-bu-shi-xi-tong-you-na-xie-she-bei-gou-cheng</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Mon, 14 Oct 2024 00:41:22 GMT</pubDate></item><item><title><![CDATA[PVC管道的Dn是内径还是外径？De/D/Φ分别指什么？]]></title><link>https://wiki.dgfygk.com/archives/pvcguan-dao-de-dnshi-nei-jing-huan-shi-wai-jing-de-d-ffen-bie-zhi-shi-me</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=PVC%E7%AE%A1%E9%81%93%E7%9A%84Dn%E6%98%AF%E5%86%85%E5%BE%84%E8%BF%98%E6%98%AF%E5%A4%96%E5%BE%84%EF%BC%9FDe%2FD%2F%CE%A6%E5%88%86%E5%88%AB%E6%8C%87%E4%BB%80%E4%B9%88%EF%BC%9F&amp;url=/archives/pvcguan-dao-de-dnshi-nei-jing-huan-shi-wai-jing-de-d-ffen-bie-zhi-shi-me" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">一、国家标准是如何规定的？</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-girx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">根据《建筑给水排水制图标准》GB/T 50106-2010规定，管径的表达方法应符合下列规定：</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">1，水煤气输送钢管（镀锌或非镀锌）、铸铁管等管材，管径宜以</span><strong><span style="font-size: 14px; color: rgb(99, 99, 99)">公称直径DN</span></strong><span style="font-size: 14px; color: rgb(99, 99, 99)">表示；</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">2，无缝钢管、焊接钢管（直缝或螺旋缝）等管材，管径宜以</span><strong><span style="font-size: 14px; color: rgb(99, 99, 99)">外径D X壁厚</span></strong><span style="font-size: 14px; color: rgb(99, 99, 99)">表示；</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">3，铜管、薄壁不锈钢管等管材，管径宜以</span><strong><span style="font-size: 14px; color: rgb(99, 99, 99)">公称外径Dw</span></strong><span style="font-size: 14px; color: rgb(99, 99, 99)">表示；</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">4，建筑给水排水塑料管材，管径宜以</span><strong><span style="font-size: 14px; color: rgb(99, 99, 99)">公称外径dn</span></strong><span style="font-size: 14px; color: rgb(99, 99, 99)">表示；</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">5，钢筋混凝土（或混凝土）管，管径宜以</span><strong><span style="font-size: 14px; color: rgb(99, 99, 99)">内径d</span></strong><span style="font-size: 14px; color: rgb(99, 99, 99)">表示；</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">6，复合管、结构壁塑料管等管材，管径应按产品标准的方法表示；</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">7，</span><span style="font-size: 14px">当设计中均采用公称直径DN表示管径时，应有公称直径DN与相应产品规格对照表。</span></p>
<p style="text-align: right; "><span style="font-size: 14px; color: rgb(99, 99, 99)">——摘自《建筑给水排水制图标准》GB/T 50106-2010</span></p>
<p style="text-align: right; "><span style="font-size: 14px; color: rgb(99, 99, 99)">
  <br>
 </span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-dfms.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">二、DN究竟指什么？</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">DN（Nominal&nbsp;Diameter）</span></strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">是指</span><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">公称直径</span></strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">，又称</span><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">平均外径</span></strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">(Mean Outside Diameter)，是指</span><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">容器、管道及其附件的</span><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">标准化直径系列</span><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">。</span></strong></p>
<p style=""><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">要明确一点，</span><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">DN既不是内径，不是外径，也不是内径和外径的平均值</span></strong><span fontsize="" color="">（很多人误传说</span>DN是内径外径平均值，其实并不科学），它是为了设计制造和维修方便人为地规定的一种<span style="font-size: 14px; color: rgb(99, 99, 99)">标准</span><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">，</span><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">根据公称直径可以确定管子、管件、阀门、法兰、垫片等结构尺寸与连接尺寸。</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-dfyn.png&amp;size=m" width="100%" height="100%" style="display: inline-block">
   <br></span></strong></p>
<p style="text-align: center; "></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">每一公称直径，对应一个外径，其内径数值随厚度不同而不同。</span></strong><span fontsize="" color="">公称直径可用公制</span>mm表示，也可用英制in表示。</p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 10.5ptpx; color: rgb(255, 0, 0)">注意区分：大写DN是指公称直径，小写dn是指公称外径，应用范围也不相同。</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 14px">三、De/D/d/Φ分别指什么？</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">●&nbsp;</span><span style="font-size: 14px; color: rgb(99, 99, 99)">De（External Diameter）：是指管道外径 ,一般用于描述</span><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">需要标注厚度的管材</span></strong><span style="font-size: 14px; color: rgb(99, 99, 99)">，需标注成</span><strong><span fontsize="" color="rgb(99, 99, 99)" style="color: rgb(99, 99, 99)">“外径*壁厚”</span></strong><span style="font-size: 14px; color: rgb(99, 99, 99)">的形式，如De160*6.2mm。</span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">
  <br>
 </span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-znoc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<p style="text-align: center; "></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">拿PE给水管为例，用DN、De两种标注方法如下：</span></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">DN20 &nbsp;De25×2.3mm</span></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">DN25 &nbsp;De32×3.0mm &nbsp;&nbsp;</span></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">DN32 &nbsp;De40×3.7mm</span></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">DN40 &nbsp;De50×4.6mm&nbsp;</span></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">……
  <br></span></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-acnc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span fontsize="" color="">备注：</span><span style="font-size: 10.5ptpx">在不涉及到壁厚的情况下很少使用De来标注管道。</span></strong></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">
  <br>
 </span></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">●&nbsp;</span>D：一般是指管道内径。</p>
<p style=""></p>
<p style=""><span style="font-size: 14px; color: rgb(99, 99, 99)">●&nbsp;</span>d：指的是<strong>混凝土管内径 </strong>，钢筋混凝土（或混凝土）管、陶土管、耐酸陶瓷管、缸瓦管等管材，管径宜以内径d表示（如d230、d380等）。</p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">
  <br>
 </span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-lsdh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">●&nbsp;Φ：是指普通圆的直径，这里用来表示</span><strong><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">管材的</span>外径</strong><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">，即</span><strong>包括管道壁厚在内的管子的外缘直径</strong><span style="font-size: 10.5ptpx; color: rgb(99, 99, 99)">。一般无缝钢管、螺旋钢管是用Φ来标识规格尺寸的，标注成外径X壁厚的形式。如：Φ108×4，表示外径为108mm，壁厚为4mm的管材。</span></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-ahjk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span style="font-size: 14px">虽然说起来这都是很基础的管道基础常识，但实际上对于很多从业人员都很难讲清楚这些区别，这里再次罗列出来，希望对各位有所帮助。</span></p>]]></description><guid isPermaLink="false">/archives/pvcguan-dao-de-dnshi-nei-jing-huan-shi-wai-jing-de-d-ffen-bie-zhi-shi-me</guid><dc:creator>Administrator</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-acnc.png&amp;size=m" type="image/jpeg" length="38619"/><category>计算机基础</category><pubDate>Sun, 13 Oct 2024 15:17:08 GMT</pubDate></item><item><title><![CDATA[PVC线管a管b管C管区别]]></title><link>https://wiki.dgfygk.com/archives/pvcxian-guan-aguan-bguan-cguan-qu-bie</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=PVC%E7%BA%BF%E7%AE%A1a%E7%AE%A1b%E7%AE%A1C%E7%AE%A1%E5%8C%BA%E5%88%AB&amp;url=/archives/pvcxian-guan-aguan-bguan-cguan-qu-bie" width="1" height="1" alt="" style="opacity:0;">
<p style="">PVC线管分为A管、B管、C管，它们在标准、壁厚、价格和用途方面存在显著区别。</p>
<p style=""></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 980px">
  <colgroup>
   <col style="width: 97px">
   <col style="width: 280px">
   <col style="width: 283px">
   <col style="width: 320px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="97">
     <p style=""></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="280">
     <p style="">A<span style="font-size: 16px; color: rgb(51, 51, 51)">型</span>管</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="283">
     <p style="">B<span style="font-size: 16px; color: rgb(51, 51, 51)">型</span>管</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="320">
     <p style=""><span style="font-size: 16px; color: rgb(51, 51, 51)">C型管</span></p>
    </th>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="97">
     <p style="">标准不同</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="280">
     <p style="">A管遵循GB/T 5836.1-2018国家标准，是国标管；</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="283">
     <p style="">B管则采用企业标准，是企标管。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="320">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="97">
     <p style="">壁厚不同</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="280">
     <p style="">A管的管壁较厚，例如50管壁厚2.0mm，75管壁厚2.3mm，110管壁厚3.2mm，160管壁厚4.0mm，200管壁厚4.9mm等；</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="283">
     <p style="">B管的管壁较薄，例如50管壁厚1.8mm，75管壁厚2.0mm，110管壁厚2.8mm，160管壁厚3.0mm等。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="320">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="97">
     <p style="">价格不同</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="280">
     <p style="">A管用料好，管壁厚，强度高，因此价格更高；</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="283">
     <p style="">B管钙粉含量高，管壁略薄，强度略差，价格更便宜。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="320">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="97">
     <p style="">用途不同：</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="280">
     <p style="text-align: start; ; line-height: 32px">A管通常用于要求较高的工程排水、排气，较少家庭使用；</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="283">
     <p style="">B管多为要求相对较低的工程排水、排气以及家庭使用。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="320">
     <p style=""><span style="font-size: 16px; color: rgb(51, 51, 51)">C型管则质量一般，不建议使用，主要适用于做一些吊顶里的明装套管或者墙上的套管。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="97">
     <p style="">安装使用场景</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="280">
     <p style="">预埋</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="283">
     <p style="">预埋</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="320">
     <p style="">明装</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="text-align: start; ; line-height: 32px"></p>
<p style="text-align: start; ; line-height: 32px">标准不同：</p>
<p style="text-align: start; ; line-height: 32px">A管遵循GB/T 5836.1-2018国家标准，是国标管；</p>
<p style="text-align: start; ; line-height: 32px">B管则采用企业标准，是企标管。</p>
<p style="text-align: start; ; line-height: 32px">壁厚不同：</p>
<p style="text-align: start; ; line-height: 32px">A管的管壁较厚，例如50管壁厚2.0mm，75管壁厚2.3mm，110管壁厚3.2mm，160管壁厚4.0mm，200管壁厚4.9mm等；</p>
<p style="text-align: start; ; line-height: 32px">B管的管壁较薄，例如50管壁厚1.8mm，75管壁厚2.0mm，110管壁厚2.8mm，160管壁厚3.0mm等。</p>
<p style="text-align: start; ; line-height: 32px">价格不同：</p>
<p style="text-align: start; ; line-height: 32px">A管用料好，管壁厚，强度高，因此价格更高；</p>
<p style="text-align: start; ; line-height: 32px">B管钙粉含量高，管壁略薄，强度略差，价格更便宜。</p>
<p style="text-align: start; ; line-height: 32px">用途不同：</p>
<p style="text-align: start; ; line-height: 32px">A管通常用于要求较高的工程排水、排气，较少家庭使用；</p>
<p style="text-align: start; ; line-height: 32px">B管多为要求相对较低的工程排水、排气以及家庭使用。</p>
<p style="text-align: start; ; line-height: 32px"><span style="font-size: 16px; color: rgb(51, 51, 51)">此外，PVC线管的分类还包括A型管和B型管，其中A型管和A型加厚管一般使用于高层住宅和写字楼的主体预埋线管，而B型管和B型加厚管一般使用于小型工程，质量属于中等。C型管则质量一般，不建议使用，主要适用于做一些吊顶里的明装套管或者墙上的套管。</span></p>]]></description><guid isPermaLink="false">/archives/pvcxian-guan-aguan-bguan-cguan-qu-bie</guid><dc:creator>Administrator</dc:creator><enclosure url="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-acnc.png&amp;size=m" type="image/jpeg" length="38619"/><category>计算机基础</category><pubDate>Sun, 13 Oct 2024 15:10:34 GMT</pubDate></item><item><title><![CDATA[规范标准查询、下载网站]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%A7%84%E8%8C%83%E6%A0%87%E5%87%86%E6%9F%A5%E8%AF%A2%E3%80%81%E4%B8%8B%E8%BD%BD%E7%BD%91%E7%AB%99&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style="">&nbsp; 我们在工作中经常需要用到各种各样的规范标准，这里给大家介绍一些免费查询和下载规范的网站，个人亲测可用。</p>
<p style=""><strong><span style="font-size: 16ptpx">标准查找查新网站</span></strong></p>
<p style=""><span fontsize="" color="">工标网：</span> <u><span fontsize="" color="">http://www.csres.com/</span></u></p>
<p style=""><span style="font-size: 14ptpx">中国国家标准化管理委员会：</span><u><span fontsize="" color="">http://openstd.samr.gov.cn/bzgk/gb/index</span></u></p>
<p style=""><span style="font-size: 14ptpx">全国标准信息公共服务平台：</span><u><span fontsize="" color="">http://std.samr.gov.cn/</span></u></p>
<p style=""><span style="font-size: 14ptpx">中国标准服务网：</span><u><span fontsize="" color="">www.cssn.net.cn</span></u></p>
<p style=""><strong><span style="font-size: 16ptpx">标准下载网站</span></strong></p>
<p style=""><span fontsize="" color="">标准下载库：</span> <u><span fontsize="" color="">https://www.bzxzku.com/</span></u></p>
<p style=""><span fontsize="" color="">学兔兔：</span> <u><span fontsize="" color="">http://www.bzfxw.com/</span></u></p>
<p style=""><span fontsize="" color="">库七七：</span> <u><span fontsize="" color="">https://www.kqqw.com/</span></u></p>
<p style=""><span fontsize="" color="">UPBZ标准下载网：</span><u><span style="font-size: 14ptpx; color: rgb(0, 0, 255)">https://www.upbz.net/</span></u></p>
<p style=""><span fontsize="" color="">免费标准网：</span> <u><span style="font-size: 14ptpx; color: rgb(128, 0, 128)">https://www.freebz.net/</span></u></p>
<p style=""><span fontsize="" color="">标准下载网：</span> <u><span style="font-size: 14ptpx; color: rgb(128, 0, 128)">https://www.biao-zhun.cn/</span></u></p>
<p style=""><span fontsize="" color="">国家标准全文公开系统：</span><u><span style="font-size: 14ptpx; color: rgb(128, 0, 128)">https://openstd.samr.gov.cn/bzgk/gb/inde</span></u></p>
<p style=""><span fontsize="" color="">行业标准信息服务平台地址：</span><u><span style="font-size: 14ptpx; color: rgb(128, 0, 128)">https://hbba.sacinfo.org.cn/</span></u></p>
<p style=""><span fontsize="" color="">工程资料范本网地址：</span><u><span style="font-size: 14ptpx; color: rgb(128, 0, 128)">https://www.bzmfxz.com/</span></u></p>
<p style=""><span style="font-size: var(--articleFontsize)px">&nbsp; &nbsp; &nbsp; &nbsp; 查询是免费的，下载的话很多网站是免费的。有些网站需要注册，注册非常简单有QQ或者微信注册一下就好了。有些网站中一部分规范下载需要金币，金币要充值。有些部门官网也有新颁布的规范电子版，例如交通部官网提供最新的集料、岩石、无机结合料PDF版本下载。&nbsp;</span></p>
<p style=""><span style="font-size: var(--articleFontsize)px">&nbsp; &nbsp;规范&nbsp; 下载还可以在QQ群里搜索群文件，有很多</span><span fontsize="" color="">QQ</span>群有群友分享的规范。<span style="font-size: var(--articleFontsize)px">还可以在微信里面搜索，有些公众号有网盘分享的规范。</span></p>
<p style="text-align: center; "><u><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-seno.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></u></p>
<p style="">&nbsp; 这个是工标网的网页首页，个人觉得查规范的话这个网站很好用。</p>
<p style=""><span style="font-size: var(--articleFontsize)px">&nbsp; &nbsp; &nbsp; &nbsp;查询是免费的，下载的话很多网站是免费的。有些网站需要注册，注册非常简单有QQ或者微信注册一下就好了。有些网站中一部分规范下载需要金币，金币要充值。有些部门官网也有新颁布的规范电子版，例如交通部官网提供最新的集料、岩石、无机结合料PDF版本下载。&nbsp;</span></p>
<p style=""><span style="font-size: var(--articleFontsize)px">&nbsp; &nbsp; 规范下载还可以在QQ群里搜索群文件，有很多群有群友分享的规范。还可以在微信里面搜索，有些公众号有网盘分享的规范。</span></p>
<p style=""></p>
<p style=""></p>
<h1 style="" id="%E5%8E%9F%E6%96%87%E9%93%BE%E6%8E%A5%EF%BC%9Ahttps%3A%2F%2Fmp.weixin.qq.com%2Fs%2Fhp9orgbtlyo82hzv7dy3cg">原文链接：https://mp.weixin.qq.com/s/hp9oRGbtLYo82hZV7Dy3Cg</h1>
<p style=""></p>
<p style="">
 <br>
</p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 12 Oct 2024 08:42:15 GMT</pubDate></item><item><title><![CDATA[关于IT运维项目需方验收的几点建议]]></title><link>https://wiki.dgfygk.com/archives/guan-yu-ityun-wei-xiang-mu-xu-fang-yan-shou-de-ji-dian-jian-yi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%B3%E4%BA%8EIT%E8%BF%90%E7%BB%B4%E9%A1%B9%E7%9B%AE%E9%9C%80%E6%96%B9%E9%AA%8C%E6%94%B6%E7%9A%84%E5%87%A0%E7%82%B9%E5%BB%BA%E8%AE%AE&amp;url=/archives/guan-yu-ityun-wei-xiang-mu-xu-fang-yan-shou-de-ji-dian-jian-yi" width="1" height="1" alt="" style="opacity:0;">
<p style="">本文对IT运维项目验收这件事从需方角度提出了几点建议，关于不同阶段的验收内容、验收标准、验收方式等不进行详细阐述，至于下面的内容究竟适用于哪个阶段以及是否适用，请自行斟酌，同时也欢迎不同的见解，共同讨论、共同进步。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px; color: rgb(0, 0, 0)">对什么进行验收？验收什么？</span></em></strong></p>
<ul>
 <li>
  <p style="">对运维人员进行验收？</p>
  <ul>
   <li>
    <p style="">精明不、勤快不、听话不、技术水平够不......</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">对备品备件进行验收？</p>
  <ul>
   <li>
    <p style="">到位没、到位时限是多久、品牌型号对不对......</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">对工作内容进行验收？</p>
  <ul>
   <li>
    <p style="">巡检没、保养没、保障到位没、隐患发现没......</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">对工作标准进行验收？</p>
  <ul>
   <li>
    <p style="">工作内容完整不、响应及时不、操作规范不......</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">对交付成果进行验收？</p>
  <ul>
   <li>
    <p style="">记录有没有、报告有没有、分析有没有、建议有没有......</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">对.................进行验收？</p>
  <p style="line-height: 1.6em"></p>
 </li>
</ul>
<p style="line-height: 1.6em">&nbsp;&nbsp;&nbsp;&nbsp;可以验收的内容不少，但是大部分都是供方的内部验收内容或者考核内容，从需方角度来看，对运维服务的验收内容应该满足需方采购运维服务的最终目标，从保证目标实现的角度出发去寻找关键点，这些关键点就是我们要验收的点。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px; color: rgb(0, 0, 0)">最终目标是什么？</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;今天是聊验收，但是又不能不扯目标，没有目标就没有必要验收。。。
 <br></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;需方采购IT运维服务，无非是需要系统的工作<strong><span style="font-size: 20px">状态</span></strong>正常一些、设备的使用<strong><span style="font-size: 20px">寿命</span></strong>增加一些，数据<strong><span style="font-size: 20px">分析</span></strong>能够为后续的增、删、减、补提供合理的依据。</p>
<p style=""><strong><u><span fontsize="" color="">关键点一：</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 保障系统的工作状态及使用寿命，定期的巡检、保养工作是必不可少的。定期的巡检、保养工作有利于及时发现问题、及时处理问题，避免产生较大的影响；有利于保持设备在良好的环境下工作，从而提高使用寿命；有利于提出准确、经济的优化方案，提高系统效率。</p>
<p style="">&nbsp;&nbsp;&nbsp; 通过对供方提供的巡检工作、保养工作以及日常例行工作的记录进行统计分析，可以实现对重点部位、高频障碍、备品材料以及人员配置等进行统筹管理，也可以为信息系统建设提供参考依据。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;工作做了，记录有了，每隔一段时间汇总、分析一次，并形成阶段性运维服务报告，供需方验收。
 <br></p>
<p style=""><strong>关键点二：</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;无论是供方还是需方，都需要了解维护对象，有多少设备、有什么品牌、什么时候建设的、在系统中什么作用、出问题了去哪修、有没有替代品、替代品供货周期多久、需不需要备品备件、需要多少备品备件等一系列问题。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;建立并管理IT资产台账以及设备档案是IT运维服务的重要环节之一，可以作为验收的关键点之一。</p>
<p style=""><strong>关键点三：</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;备品备件的设立是为了系统在出现问题时，能够在规定时限内快速替换，以确保系统工作的连续性，特别是业务窗口、关键路径等影响较大的部位。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;所以把备品备件的管理作为关键点之一。
 <br></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px; color: rgb(0, 0, 0)">验收一：阶段性运维服务报告</span></em></strong></p>
<p style="line-height: 1.6em">&nbsp;&nbsp;&nbsp;&nbsp;阶段性运维报告是需方对某一阶段内实施的IT运维服务工作的总结性报告，体现了需方本阶段的工作完成情况。所以将阶段性运维报告作为验收内容之一。</p>
<p style="line-height: 1.6em">&nbsp;&nbsp;&nbsp;&nbsp;阶段性运维服务报告至少应该包括巡检、保养、优化、分析等工作的完成情况和未完成情况、存在的隐患及处理情况、系统及设备的变更和发布情况、数据分析及建议情况、下阶段工作计划情况、附件记录等。</p>
<p style="line-height: 1.6em">&nbsp;&nbsp;&nbsp;&nbsp;阶段性验收的频率可以依据付款条件进行，也可以根据自身工作需要按月、季、半年、年进行，建议每季度进行一次，有人可能觉得按月、季太频繁了，哪有时间干这个，但是年终验收结果不合格怎么办？真能黑下脸来不签字、不盖章吗。<em>（走形式的什么频率都行）</em></p>
<p style="line-height: 1.6em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;阶段性运维服务报告验收的内容包括：</p>
<ol>
 <li>
  <p style="">检查报告内容的<strong>完整性</strong>，应包括工作的完成情况和未完成情况、存在的隐患及处理情况、系统及设备的变更和发布情况、数据分析及建议情况、下阶段工作计划情况、附件记录等。</p>
 </li>
 <li>
  <p style="">核对本阶段的例行操作（巡查、保养、保障）频次、范围及完成情况是否<strong>符合</strong>技术协议约定。<em>（有人说了，这怎么核对，得核对到什么时候去，有一种东西叫做“服务目录”，统计一下即可）</em></p>
 </li>
 <li>
  <p style="">抽查本阶段例行操作<strong>记录</strong>（巡查、保养、保障）的内容及其合格标准是否有效，首次合作建议抽查30%以上，逐渐减少抽查比例，建议不低于10%。</p>
 </li>
</ol>
<p style="text-align: center; "><strong><em><span style="font-size: 20px; color: rgb(0, 0, 0)">验收二：IT资产台账及设备档案</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;如果技术协议约定了建立IT资产台账及设备档案，建议每季度验收一次，如果没有约定，那赶紧约定一下吧。<em>（就是不需要除外）</em></p>
<ul>
 <li>
  <p style="">设备不见了，“让谁谁谁拆走了”、“送修了”、“装别地方了”，谁让拆的不知道，谁拆的不知道，也有可能根本就没发现不见了。</p>
 </li>
 <li>
  <p style="">设备被更换了，谁换的，什么时候换的，为什么换的，换下来的去哪了，通通不知道。&nbsp;&nbsp;&nbsp;</p>
 </li>
 <li>
  <p style="">设备突然罢工了，“不知道密码呀”，“没操作过”，“什么配置”，需要找到早已离开的人才能解决。</p>
 </li>
</ul>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;此时肯定有人头顶飘过一群~~n~~m~~</p>
<p style="">&nbsp;&nbsp;&nbsp; IT资产台账及设备档案验收的内容包括：</p>
<ol>
 <li>
  <p style="">供方提供的台账、图纸、档案资料是否完整、准确，建议随机抽查几处。</p>
 </li>
 <li>
  <p style="">抽查IT资产的变更记录和发布记录，抽查数量看心情~~。变更指设备的部分或整体产生变化；发布指变更后状态的更新及发布。</p>
 </li>
</ol>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;给大家分享一个设备建档的要求：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F8sD1EBOCmDOaBnYO3O2QWA4GWqCKhXgDTQQfuqg9yMjt0gMicGmvDXp5SoibNuZ72yjBL3oGTrofibzdJhPlMwKMQ%2F640%3Fwx_fmt%3Dpng%26tp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="512px" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px; color: rgb(0, 0, 0)">验收三：备品备件的管理</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;关于备品备件的内容和作用，就不过多介绍了，直接上验收内容：</p>
<ol>
 <li>
  <p style="">检查备品备件台账的范围是否符合技术协议要求，这个可以直接比对供方提供的维护工作计划内容。</p>
 </li>
 <li>
  <p style="">按照台账抽查现场备品备件。</p>
 </li>
 <li>
  <p style="line-height: 2em">未到达现场的备品备件，按照响应时限抽检。&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;</p>
 </li>
</ol>
<p style="text-align: center; "><strong><em><span style="font-size: 20px; color: rgb(0, 0, 0)">验收补充：人员情况</span></em></strong></p>
<p style="line-height: 1.6em">&nbsp;&nbsp;&nbsp;&nbsp;考虑再三，觉得<strong><span style="font-size: 20px">驻场</span></strong>人员也应该作为验收对象，因为一般情况下驻场服务人员的费用占合同额比例很高，所以人员的资质是否符合技术协议要求，以及人员的出勤和缺岗情况就有可能影响合同的真实履行情况。对人员情况的验收分为进场时验收和服务期满后验收。</p>
<p style="">人员首次进场时需要确保进场人员的资格要求得到满足，验收的内容包括：</p>
<ol>
 <li>
  <p style="">供方应提供人员的身份信息、学历信息、技能等级信息等一切本岗位要求的相关信息。</p>
 </li>
 <li>
  <p style="">供方应提供企业与职工的保密协议。</p>
 </li>
 <li>
  <p style="line-height: 1.6em"></p>
 </li>
</ol>
<p style="">服务期（或结算期）满后需要确保人员的出勤情况，验收的内容包括：</p>
<ol>
 <li>
  <p style="">检查供方提供的人员出勤记录、请假记录；</p>
 </li>
 <li>
  <p style="">核对供方提供的人员出勤、缺岗统计。</p>
 </li>
</ol>
<p style="text-align: center; "><strong><em><span style="font-size: 20px; color: rgb(0, 0, 0)">结语</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; IT运维项目的验收工作是对合同履行情况验证的一种表现，也是服务期满后费用审计工作的依据。不仅需方需要验收，供方更应该组织内验，以确保需方得到标准化的运维服务，并且促进供方在合同执行中不断改进，提高自身的专业能力。所以，验收工作是“你好、我好、大家好”的事，多做无害。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/3mEm1Dl1lDDr-nOA6yQ-oQ</p>]]></description><guid isPermaLink="false">/archives/guan-yu-ityun-wei-xiang-mu-xu-fang-yan-shou-de-ji-dian-jian-yi</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:48:33 GMT</pubDate></item><item><title><![CDATA[选择IT运维工具，拒绝裸奔！]]></title><link>https://wiki.dgfygk.com/archives/xuan-ze-ityun-wei-gong-ju-ju-jue-luo-ben</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%80%89%E6%8B%A9IT%E8%BF%90%E7%BB%B4%E5%B7%A5%E5%85%B7%EF%BC%8C%E6%8B%92%E7%BB%9D%E8%A3%B8%E5%A5%94%EF%BC%81&amp;url=/archives/xuan-ze-ityun-wei-gong-ju-ju-jue-luo-ben" width="1" height="1" alt="" style="opacity:0;">
<p style="">关于IT运维工具，国标28827.1在其“资源”部分对运行维护工具提出了要求和关键指标，2012版标准规定如下图所示：<strong><span style="font-size: 14ptpx; color: rgb(217, 33, 66)">↓↓</span></strong>（2022版标准将在2023年实施）</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nuom.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;提到标准这个词，听到较多的是“没用”、“很难落地”、“实际情况不允许”这些将标准形容成负担的内容，即便有些人，大谈特谈标准如何如何的好，但实际做的时候早已回归到了逐利优先的本质，也不难理解，毕竟“活着”才能向往未来。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;国标这把尺子实际上是卡在了最低的“刻度线”上，相当于一个行业的基础准则，基础准则能有多么苛刻？我们看到的和看不到的诸多困难，终究可以归咎到一个字“钱”。</p>
<p style="">&nbsp;&nbsp;&nbsp; 标准中对运维工具提出了要求，说明运维工具在IT运维服务工作中的重要性。我们来看看为什么用、用什么以及如何管理运维工具。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">使用运维工具可能解决哪些问题？</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">问题一：由被动式运维转为主动式运维</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;传统运维的关注点在于快速响应及排除故障，由于故障预警机制不完善，往往是故障发生后或报警后才会进行处理，就好比救火队员几分钟能够响应，几分钟能够灭火一样，而比灭火更重要的应该是预防火灾。从运维角度来看，以预防为主的主动式运维是否应该成为运维服务工作的首要目标呢。</p>
<p style="">&nbsp; &nbsp; 运维工具可以将日常工作中大部分的重复性和周期性工作自动化，是实现预防为主的方法之一。<span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;运维工具有利于保障质量可控和风险可控，可以实现提前预警和主动防御，通过追踪故障原因，降低频发故障对系统的影响。</span></p>
<p style=""><strong><u><span fontsize="" color="">问题二：人员低效</span></u></strong>
 <br></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;由于IT运维人员日常工作中大部分都是处理一些简单重复的问题，随着业务体量和运维的横向跨度不断增加，大量的简单重复性问题对人员数量的需求不断增加，导致大量使用基础型技术人员，即缺乏高端技术人才，又不能覆盖全面，遇到关键问题时则无法快速定位并解决。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;运维工具可以实现人员可管理、工作可计划、绩效可测量。它可以准确记录运维人员的工作量和工作计划完成情况，通过数据分析及需方评价实现对运维人员的绩效管理，从而优化人员配置，提高人员工作效率。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;高效带来的是人员数量的减少、综合能力的提高以及运维项目造价或成本的降低。
 <br></p>
<p style=""><strong><u><span fontsize="" color="">问题三：数据及分析</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;运维服务工作需要记录很多数据，例如记录、变更、反馈、报告及关键指标等。运维工具通过收集、存储、展示各类数据，实现运维服务可视化，做到运维工作内容可追溯、人员绩效指标可量化、系统运行状态一目了然等。</p>
<p style="">&nbsp;&nbsp;&nbsp; <strong><u><span fontsize="" color="">假定</span></u></strong>：通过数据分析按照障碍发生的频率由高到低排列，<span fontsize="" color="">用20%的人员解决80%高频并且简单的障碍，用20%的人员解决剩余20%可能复杂的障碍，用60%的人员实施预防性维护工作，降低障碍的基数，这有没有可能是一个成立的假设，如果成立，很可能会进一步提升运维工作质量，并降低“紧急救火”的频次。</span><strong><span style="font-size: 14ptpx; color: rgb(217, 33, 66)">↓↓</span></strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 500px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="mso-height-source:userset;height:31.5pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="">
     <p style="">障碍名称</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-left: medium none;">
     <p style="">频率</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-left: medium none;">
     <p style="">累计频率</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-left: medium none;">
     <p style="">障碍处理人员比例</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-left: medium none;">
     <p style="">预防性维护人员比例</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍一</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">35%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">35%</p>
    </td>
    <td colspan="1" rowspan="4" colwidth="100" style="border-top:none;">
     <p style="">20%</p>
    </td>
    <td colspan="1" rowspan="8" colwidth="100" style="border-top:none;">
     <p style="">60%</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍二</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">20%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">55%</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍三</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">15%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">70%</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍四</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">10%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">80%</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍五</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">8%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">88%</p>
    </td>
    <td colspan="1" rowspan="4" colwidth="100" style="border-top:none;">
     <p style="">20%</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍六</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">6%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">94%</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍七</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">5%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">99%</p>
    </td>
   </tr>
   <tr style="mso-height-source:userset;height:16.0pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-top: medium none;">
     <p style="">障碍八</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">1%</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-top:none;border-left:none;">
     <p style="">100%</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style=""><strong><u><span fontsize="" color="">问题四：需方的问题</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 1）IT运维项目造价是怎么组成的？定价依据是什么？创造了多少价值？运维工具就是运维项目造价的一部分，创造的价值就是输出工作成果，展示运维工作的完成情况，为管理者的决策提供数据支撑。</p>
<p style="">&nbsp;&nbsp;&nbsp; 2）运维人员的管理问题是一个比较突出的问题（有人就有江湖），如果供方与需方都不重视，处理不好就会形成两不管的局面，人员处于“散养”状态。运维工具可以在某种程度上解决一部分管理问题，至少在需方看来，运维人员是按照规范、流程进行操作的。至于供方，远程管理本身就存在难度，再没有一套行之有效的管理办法，那就只能靠“磨嘴皮子打嘴仗”了。</p>
<p style=""><strong><u><span fontsize="" color="">问题五：供方的问题</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 1）运维工具的重要作用就是帮助组织实现“理论落地”，将运维服务标准化、规范化的理论落到实施过程中。降低由于人的状态和意识形态的不同带来的不同结果。</p>
<p style="">&nbsp;&nbsp;&nbsp; 2）技术能力是企业的核心竞争力之一，将技术能力在企业内部沉淀下来并传承下去，才能促进企业的可持续发展。通过运维工具的数据积累及其对知识的管理，将知识库与服务台对接，提高运维人员发现问题和解决问题的能力。知识可以从运维实施过程中获得，也可以为运维实施过程提供支持，以促进组织技术能力的发展，实现组织能力的传承。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">运维工具是什么？
    <br></span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;跟着国标的思路走，运维工具分为监控工具、过程管理工具和专用工具。&nbsp;</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;监控工具从名字上就能看出来是用来监控的，没错，就是用来代替人监控运维对象的，说一个我们熟悉的监控工具，机房动环监控系统。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;过程管理工具是对运维服务过程进行管理的工具，包括对服务级别、服务报告、事件、问题、配置、变更、发布及信息安全的管理。我觉得运维工具可以实现全面管理，包括对人员、资源和技术的管理，即便是平台不能够或不便于整合，数据也可以实现共享。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;有很多运维工具是开源免费的，也有需要付费的工具，开源的需要部署和操作人员具备相关的技术能力，并且没有后续的技术支持，付费的对操作人员技术能力要求不高，也有技术支持，自己看着选吧，适合就好。</p>
<p style=""><strong><u><span fontsize="" color="">过程管理工具：</span></u></strong>&nbsp;&nbsp;&nbsp;</p>
<p style="line-height: 1.6em">&nbsp;&nbsp;&nbsp;&nbsp;去网上搜索“ITSM”，搜出来一堆的收费系统，收费的运维服务管理系统可以实现国标的要求，但是模式已经固化，估计提出的修改意见大概率会收费，<strong><span style="font-size: 20px">可能</span></strong>是个无底洞，不一定适合你的组织。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;还有一种是开源的，例如iTop，有能力的组织可以进行二次开发，优化系统的功能使其更符合组织管理的需求。iTop对设备的管理直接管到端口，能够自动建立系统拓扑及模型，简而言之，它带来的好处就是任何操作都是联动的<em>（这个我喜欢）</em>。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 再就是感觉钉钉还不错，正在研究作为过程管理工具的可行性，如果真的可行，能够节省一笔<strong><span fontsize="" color="rgb(255, 169, 0)" style="color: rgb(255, 169, 0)">¥</span></strong>。先说说我觉得可行的几个理由：</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;①可以建立数据表单和流程表单</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;②可以关联表单</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;③可以自定义报表</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;④可以实现对接云端数据库</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;⑤可以实现跨组织审批</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;有了这些功能，就可以打造一个简版运维服务管理系统，满足小型组织的运维服务管理需求。<em>（有点期待了）</em></p>
<p style="line-height: 1.6em"><strong><u><span fontsize="" color="">监控工具：</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 监控工具能监控哪些对象？网络、服务器、交换机、视频。。。。。。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;能实现哪些功能？监控设备状态、性能、故障，监控网络链路，监控日志异常、进行统计分析。。。。。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;看下图，不靠工具靠人工如何监控<strong><span style="font-size: 14ptpx; color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jmtu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp; &nbsp; Zabbix就是一个能够监视各种网络参数的开源工具，配合它使用的是用于可视化展示和数据分析的工具Grafana，这两个工具一个监视，一个展示，至于工具如何使用，可以到网上搜索教程。</p>
<p style="line-height: 1.6em"><strong><u><span fontsize="" color="">专用工具：</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;关于专用工具，我的理解就是以上两种工具以外的所有工具，例如操作设备时使用的防静电手环，专用的测量和检测工具，数据展示和分析工具，其它如shell、ftp、EditPlus、Navicat、WinPcap等。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">运维工具如何管理？</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">建立制度：</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;建立运维工具管理相关制度，明确工具的管理职责、操作权限、日常维护、数据管理及使用情况评估等。将运维工具的管理和操作分开，让专业的人员管理工具，来保障操作人员日常使用。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;运维工具的管理人员应负责工具的日常维护、运维管理、数据管理及组织培训，组织进行运维工具评估，研究落实改进措施。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;运维人员负责日常操作和使用工具，配合管理人员进行日常维护和数据管理，根据实际情况对运维工具进行评估，并提出改进建议。
 <br></p>
<p style=""><strong><u><span fontsize="" color="">操作手册：</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;运维工具的操作和使用应明确其操作步骤和方法，编制操作手册，确保能够指导运维人员日常操作和使用。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;运维人员对运维工具的掌握程度可以作为工作能力评估的指标之一，也可以做为绩效评估的指标。</p>
<p style=""><strong><u><span fontsize="" color="">自评与改进：</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;是不是一定要自评？是，一定要自评。自评能够促进系统不断优化，提升组织效率。通俗的讲，自评就是相关人员一起发现问题、评估问题和解决问题的过程。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">结语</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 运维工具的配备和使用还是需要根据运维对象、自身条件和项目需求而定，而过程管理工具的应用或者应用程度还是要看决策者对运维服务标准化建设的决心，以及组织的综合管理水平。无论选择什么，适合自己的就是好的。</p>
<p style=""></p>
<p style="">参考资料：</p>
<p style="">GB/T 28827.1-2012 &nbsp;信息技术服务&nbsp; 运行维护&nbsp; 第1部分：通用要求</p>
<p style="">中国IT运维能力建设指南（28827.1标准的解读与应用）</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/og0iUDLfWO21ed6LfFImgg</p>]]></description><guid isPermaLink="false">/archives/xuan-ze-ityun-wei-gong-ju-ju-jue-luo-ben</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:46:59 GMT</pubDate></item><item><title><![CDATA[使用钉钉实现运维服务管理]]></title><link>https://wiki.dgfygk.com/archives/shi-yong-ding-ding-shi-xian-yun-wei-fu-wu-guan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8%E9%92%89%E9%92%89%E5%AE%9E%E7%8E%B0%E8%BF%90%E7%BB%B4%E6%9C%8D%E5%8A%A1%E7%AE%A1%E7%90%86&amp;url=/archives/shi-yong-ding-ding-shi-xian-yun-wei-fu-wu-guan-li" width="1" height="1" alt="" style="opacity:0;">
<p style="">我原来想的运维服务综合管理平台是这样的<strong><span style="font-size: 14ptpx">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wbth.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">设计实现这些功能<strong><span style="font-size: 14ptpx">↓↓</span></strong></p>
<p style=""><strong><span style="font-size: 14ptpx"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xclo.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block">
   <br></span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-olbe.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-usby.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kgmq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">钉钉工作台是这样的<strong><span style="font-size: 14ptpx">↓↓
   <br></span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mmbc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span style="font-size: 14ptpx">钉钉后台是这样的</span><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kjpv.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（一）设计思路</span></em></strong></p>
<p style="">整体思路是尽量使用钉钉<strong><span style="font-size: 20px">免费</span></strong><span style="font-size: 17px">的</span>功能实现我们想要的，例如：</p>
<p style="">①通过角色管理实现权限分配</p>
<p style="">&nbsp;<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mkee.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">②通过跨组织审批打造服务台<strong><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓</span></strong></p>
<p style="">&nbsp;<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uvbq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">③通过数据表单建立数据集<strong><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓</span></strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gqon.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block">
 <br>
 ④通过流程表单建立审批流程集<strong><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vtbb.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></strong>
 <br>
 ⑤通过关联表单的方式将数据表单关联到流程表单中，实现数据格式统一、内容完整、多方共享<strong><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓
   <br></span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-okoa.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">⑥使用智能报表分析、展示数据<strong><em><span fontsize="" color="">（收费）</span></em><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rxjb.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;如果能自建一个报表应用也不错<strong><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓</span></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gtdp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">⑦使用钉钉文档→知识库<strong><span fontsize="" color="">↓</span><span style="font-size: 14ptpx">↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nulr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（二）设计内容
    <br></span></em></strong></p>
<p style=""><strong><u><span style="font-size: 17px">设置角色</span></u></strong></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp; ①数据管理员，用于管理全部数据和流程。</span></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;②运维管理员，用于部分数据和流程的维护。
  <br></span></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;③客户服务台，用于接收客户服务请求（跨组织审批）。
  <br></span></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;④主管，用于接收内部服务请求，相当于内部服务台。</span></p>
<p style=""><strong><u><span style="font-size: 17px">建立客户服务台</span></u></strong></p>
<ol>
 <li>
  <p style=""><span style="font-size: 17px">邀请客户</span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">OA审批→跨组织审批→客户档案→邀请组织外人员→客户扫码填写即可。</span></p>
 </li>
 <li>
  <p style="">生成客户工单二维码</p>
  <p style=""><span style="font-size: 17px">OA审批→跨组织审批→客户</span>工单→分享（生成二维码）。</p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">客户下单</span></p>
  <p style=""><span style="font-size: 17px">使用钉钉扫一扫客户工单二维码即可下单。</span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">服务台接单后</span></p>
  <ol>
   <li>
    <p style=""><span style="font-size: 17px">服务台自行处理→客户评价→结单。</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">服务台转交处理→客户评价→结单。</span></p>
   </li>
  </ol>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">工单跟踪</span></p>
  <ol>
   <li>
    <p style=""><span style="font-size: 17px">关联组织内工作流程，便于客户服务台跟踪处理结果。</span></p>
   </li>
  </ol>
 </li>
</ol>
<p style=""></p>
<p style=""><strong><u><span style="font-size: 17px">新建分组</span></u></strong></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp; ①数据表单，用于分组数据表单。
  <br></span></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;②运维服务管理，用于分组流程表单。</span></p>
<p style=""><strong><u><span style="font-size: 17px">数据表单</span></u></strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 600px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="text-align: center;margin-bottom: 0.0001pt;border-color: rgb(238, 238, 238);border-style: solid;border-width: 1pt;padding: 0.75pt;height: 13.95pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="text-align: center;margin-bottom: 0.0001pt;border-color: rgb(238, 238, 238);border-style: solid;border-width: 1pt;padding: 0.75pt;height: 13.95pt;">
     <p style=""><span style="font-size: 15px; color: rgb(0, 0, 0)">表单名称</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">表单类型</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">所在分组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">发起人/</span><span fontsize="" color="">角色</span>/<span fontsize="" color="">部门</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">管理角色</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">用途</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:1;height:21.05pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">单位信息数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义所有项目干系单位。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:2;height:47.25pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">项目类型数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义项目类型，例如服务、销售、工程等。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:3;height:47.25pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">系统类型数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">按照服务目录内容定义项目中包含的服务对象类型。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:4;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">品牌数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义供应商品牌及售后信息。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:5;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">设备类型数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义设备类型。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:6;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">配件耗材数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义日常使用的配件、耗材类型。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:7;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">项目数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义项目基本信息。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:8;mso-yfti-lastrow:yes;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">点位数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义维护对象的基本信息。</span></p>
    </td>
   </tr>
   <tr style="text-align: center;margin-bottom: 0.0001pt;border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;height: 24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="text-align: center;margin-bottom: 0.0001pt;border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;height: 24.75pt;">
     <p style=""><span style="font-size: 15px; color: rgb(0, 0, 0)">资产数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="text-align: center;margin-bottom: 0.0001pt;border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;height: 24.75pt;">
     <p style=""><span style="font-size: 15px; color: rgb(0, 0, 0)">定义IT资产的基本信息</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style=""><strong><u><span style="font-size: 17px">流程表单</span></u></strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 600px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="mso-yfti-irow:0;mso-yfti-firstrow:yes;height:13.95pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-style: solid;border-width: 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">表单名</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">类型</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">所在分组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">发起人/</span><span fontsize="" color="">角色</span>/<span fontsize="" color="">部门</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">管理角色</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238) rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: solid solid solid none;border-width: 1pt 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">用途</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:1;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建设备档案</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维、技术、交付</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">收集设备档案数据的审核流程。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:2;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建派工单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维、技术、交付</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义日常维护工作流程。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:3;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建事件</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维、技术、交付</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义事件处理流程，审核事件处理情况。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:4;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建问题</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维、技术、交付</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义问题处理流程，审核问题解决方案。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:5;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建变更测试发布</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维、技术、交付</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义变更、测试、发布环节的操作流程。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:6;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建配置更新</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维、技术、交付</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义配置更新流程。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:7;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建巡检记录</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维、技术、交付</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义巡检记录模板，审核巡检记录。</span></p>
    </td>
   </tr>
   <tr style="mso-yfti-irow:8;mso-yfti-lastrow:yes;height:24.75pt;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238);border-style: none solid solid;border-width: medium 1pt 1pt;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">新建巡检报告</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">主管、运维管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: currentcolor rgb(238, 238, 238) rgb(238, 238, 238) currentcolor;border-style: none solid solid none;border-width: medium 1pt 1pt medium;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义巡检报告模板，审核巡检报告。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-left-color: rgb(238, 238, 238);border-left-width: 1pt;border-top-color: rgb(238, 238, 238);border-top-width: 1pt;word-break: break-all;">
     <p style=""><span style="font-size: 15px; color: rgb(0, 0, 0)">客户工单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">客户服务</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">客户、服务台</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">数据管理员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(238, 238, 238);border-right-style: solid;border-bottom-style: solid;border-width: 1pt;padding: 0.75pt;word-break: break-all;">
     <p style="text-align: center; "><span style="font-size: 15px; color: rgb(0, 0, 0)">定义客户工单的服务流程。</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp; 由于巡检工作是周期性的，且在一个服务期内基本不会改变，可以通过设置流程提醒的方式，提醒运维人员执行巡检工作计划。</span></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aylx.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span style="font-size: 17px">数据表单和流程表单的关联</span></u></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sjnz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;实在是太乱了<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fres.wx.qq.com%2Ft%2Fwx_fed%2Fwe-emoji%2Fres%2Fv1.3.10%2Fassets%2FExpression%2FExpression_21%402x.png%3Ftp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block">，就是这个意思。
  <br></span></p>
<p style=""><strong><u><span style="font-size: 17px">知识库功能应用</span></u></strong></p>
<ol>
 <li>
  <p style=""><span style="font-size: 17px">团队协同编辑
    <br></span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">知识沉淀
    <br></span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">任务管理
    <br></span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">权限管控
    <br></span></p>
 </li>
</ol>
<p style=""><strong><u><span style="font-size: 17px">数据报表</span></u></strong></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp; 数据报表功能的试用期已经过了，无法说明，赶明儿付费了再来介绍，特别是对服务级别协议（SLA）的监控和报告。
  <br></span></p>
<p style=""><strong><u><span style="font-size: 17px">定时数据备份</span></u></strong></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp; 只能人工备份了，计划每季度备份一次。等时机成熟了，将云服务器数据库与钉钉数据打通再实现自动备份。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kaaw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">结语</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;以上所有表单都是在钉钉→OA审批中建立的，也测试过其它应用中建立表单，经过试验后，发现数据导出和删除的操作不如在OA审批中便捷，就放弃了。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/TaKr35ZdQ63_tJt17nYisg</p>]]></description><guid isPermaLink="false">/archives/shi-yong-ding-ding-shi-xian-yun-wei-fu-wu-guan-li</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:39:00 GMT</pubDate></item><item><title><![CDATA[使用钉钉实现运维服务管理→事件管理]]></title><link>https://wiki.dgfygk.com/archives/shi-yong-ding-ding-shi-xian-yun-wei-fu-wu-guan-li-shi-jian-guan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8%E9%92%89%E9%92%89%E5%AE%9E%E7%8E%B0%E8%BF%90%E7%BB%B4%E6%9C%8D%E5%8A%A1%E7%AE%A1%E7%90%86%E2%86%92%E4%BA%8B%E4%BB%B6%E7%AE%A1%E7%90%86&amp;url=/archives/shi-yong-ding-ding-shi-xian-yun-wei-fu-wu-guan-li-shi-jian-guan-li" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><u><span style="font-size: 17px; color: rgb(255, 0, 0)"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fres.wx.qq.com%2Ft%2Fwx_fed%2Fwe-emoji%2Fres%2Fv1.3.10%2Fassets%2Fnewemoji%2F2_05.png%3Fwxfrom%3D13&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block"></span></u><span style="font-size: 17px; color: rgb(255, 0, 0)"> 本 内 容 仅 适 合 钉 钉 </span><span style="font-size: 12px; color: rgb(255, 0, 0)">¥</span><span style="font-size: 14px; color: rgb(255, 0, 0)"> ¥ </span><span style="font-size: 15px; color: rgb(255, 0, 0)">¥ </span><span style="font-size: 16px; color: rgb(255, 0, 0)">¥ </span><span style="font-size: 17px; color: rgb(255, 0, 0)">¥</span><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)"> 流 程 增 强 </span><span style="font-size: 17px; color: rgb(255, 0, 0)">¥ </span><span style="font-size: 16px; color: rgb(255, 0, 0)">¥ </span><span style="font-size: 15px; color: rgb(255, 0, 0)">¥</span><span style="font-size: 14px; color: rgb(255, 0, 0)"> ¥ </span><span style="font-size: 12px; color: rgb(255, 0, 0)">¥</span></strong></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（一）设计流程图</span></em></strong></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;在新建流程之前，我们需要设计制作描述工作过程的流程图，上图：
  <br></span></p>
<p style=""><strong><u><span fontsize="" color="">一般事件处理流程图</span></u><span fontsize="" color="">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-swjn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">重大事件处理流程图</span></u><span fontsize="" color="">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xtyg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp; 事件管理流程的复杂程度是根据事件的难度、级别由简入繁，在实际情况下，影响大、难度高的事件经过的环节大概率多于简单的事件，另外，事件本身及其解决过程可以作为同类事件处置的参考案例，所以，在设计流程时，不应将简化流程作为重点，更应该着重考虑的是过程、监控、质量、报告、评估等。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;相比事件处理流程的应用频率，派工任务流程更应该着重考虑流程简化。</p>
<p style=""><strong><u><span fontsize="" color="">未解决的问题</span></u></strong></p>
<ol>
 <li>
  <p style="">流程不包括事件升级的处理流程，还没想好如何在钉钉流程中实现。</p>
 </li>
 <li>
  <p style="">服务台的全局匹配解决方案可以归属到主管或者事件处理人员进行匹配，但是由于权限管理的问题，暂时没有优化。</p>
 </li>
</ol>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（二）设置角色</span></em></strong></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;流程图设计完成后，整理流程中涉及的角色及其职责，并在钉钉角色管理中设置。
  <br>
  <br>&nbsp;&nbsp;&nbsp;&nbsp;角色管理建议全局统筹，结合企业实际情况和其它工作流程统一规划，统一设置。</span></p>
<p style=""><strong><u><span style="font-size: 17px">设置角色</span></u></strong></p>
<ol>
 <li>
  <p style=""><span style="font-size: 17px">数据管理员：可以查看、导出、删除全部事件流程记录。</span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">运维管理员：可以查看和导出全部事件流程记录。
    <br></span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">客户服务台：接收客户服务请求（跨组织审批），全局匹配解决方案，发起并跟踪内部解决流程直至结束，反馈事件处理结果。
    <br></span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">主管：接收服务请求，审核事件要素准确性（对最终准确性负责），指派驻场人员处理事件，现场处理事件。
    <br></span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">经理：在部门范围内指派技术人员支持。
    <br></span></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">重大事件经理：在公司范围内组建并领导事件处置小组。
    <br></span></p>
 </li>
</ol>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（三）表单设计</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">表单设计</span></u>
  <br></strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 700px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><strong><span style="font-size: 12px">表单字段</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><strong><span style="font-size: 12px">控件类型</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><strong><span style="font-size: 12px">字段设置</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><strong><span style="font-size: 12px">是否必填</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><strong><span style="font-size: 12px">操作方式</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><strong><span style="font-size: 12px">可编辑角色</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><strong><span style="font-size: 12px">用途</span></strong></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">项目数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联模式：引用模式。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联字段：无。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">数据过滤：无。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">展示数据条目：单条。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择项目编号、名称。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">一级事件说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">说明文字</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">提示</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">二级事件说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">说明文字</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">提示</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">三级事件说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">说明文字</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">提示</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">事件级别</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">单选框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：一级、二级、三级。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项关联：一级→一级事件说明；二级→二级事件说明；三级→三级事件说明。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">主管</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">根据事件级别定义事件上报层级。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">事件发生时间</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">日期</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">日期类型：年-月-日 时：分。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">事件描述及处置要求</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">多行输入框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">默认。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">联系人姓名</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">单行输入框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">默认。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">否</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">联系人电话</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">数字输入框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">默认。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">否</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">计划/已经开始处理时间</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">计划处理结束时间</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">日期区间</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">日期类型：年-月-日。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">自动计算时长：是。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">否</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">最早开始处理的日期</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">最迟处理完成的日期</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">指派驻场人员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">联系人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：可同时选择多人。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">主管</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">非驻场人员发起流程时，驻场主管指派人员处理事件。</span></p>
    </td>
   </tr>
   <tr style="height: 104px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">历史处置情况</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">单选框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：是、否。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项关联：是→历史处置说明、历史处置过程；否→空。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项平铺：是。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">主管</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">明确是否已经指派现场人员处置过，并生成了派工记录。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">历史处置说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">说明文字</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">说明文字：历史处置说明：该事件存在历史处理过程的，请选择该派工单。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">提示</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">无</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">历史处置过程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">明细/表格</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写方式：表格。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">打印格式：横向。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联表单：派工单。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联模式：引用模式。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联字段：工作内容。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">数据过滤：无。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">展示数据条目：多条。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">主管</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">全局匹配解决方案</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">明细/表格</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写方式：表格。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">打印格式：横向。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联表单：派工单。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联模式：引用模式。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联字段：工作内容。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">数据过滤：无。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">展示数据条目：多条。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">否</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">服务台</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">明确是否已经指派现场人员处置过，并生成了派工记录。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">指派技术人员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">联系人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：可同时选择多人。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">经理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;word-break: break-all;">
     <p style="line-height: 1"><span style="font-size: 12px">在部门内指派技术支持人员进行支持。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">组建处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">联系人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：可同时选择多人。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;word-break: break-all;">
     <p style="line-height: 1"><span style="font-size: 12px">重大事件经理经理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">在公司内指派技术支持人员组建处置小组。</span></p>
    </td>
   </tr>
   <tr style="height: 119px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">解决情况</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">单选框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：已解决、待解决。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项关联：已解决→解决方式、处理过程简述、变更情况描述；待解决→未完成情况说明。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项平铺：是。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">驻场人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">技术人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">当前办理人确定事件是否解决。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">解决方式</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">单选框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：现场、远程。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项关联：无。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项平铺：是。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">驻场人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">技术人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">当前办理人填写事件解决方式。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">处理过程简述</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">多行输入框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">默认。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">驻场人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">技术人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">当前办理人填写处理过程。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">变更情况描述</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">多行输入框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">默认。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">驻场人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">技术人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">当前办理人填写变更内容等。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">未完成情况说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">多行输入框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">默认。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">驻场人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">技术人员</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">当前办理人填写未完成情况说明。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联问题处理流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联模式：引用模式。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联字段：。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">数据过滤：无。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">展示数据条目：单条。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">否</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">当前办理人关联问题处理流程。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">完成情况确认</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">单选框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选项：是、否。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项关联：是→空；否→确认情况描述。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">选项平铺：是。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">上传</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人确认事件已经解决。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">未确认情况描述</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">多行输入框</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">默认。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">是</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人填写未确认情况描述。</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联变更、测试、发布流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联表单</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">关联模式：引用模式。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">关联字段：。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">数据过滤：无。</span></p>
     <p style="line-height: 1"><span style="font-size: 12px">展示数据条目：单条。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">否</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">选择</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="line-height: 1"><span style="font-size: 12px">发起人关联变更、测试、发布流程。</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style=""><strong><em><span style="font-size: 20px">（四）流程设计
    <br></span></em></strong></p>
<p style=""><strong><u><span style="font-size: 17px">钉钉流程设计图</span></u><span style="font-size: 17px">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vexm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qvsw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zqft.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-schs.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fouj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span style="font-size: 17px">操作权限设计</span></u></strong></p>
<ol>
 <li>
  <p style="">发起人、驻场主管有事件级别编辑权限。</p>
 </li>
 <li>
  <p style="">重大事件经理有组建处置小组的权限。</p>
 </li>
 <li>
  <p style="">经理有指派技术人员的权限。</p>
 </li>
 <li>
  <p style="">发起人、驻场主管、驻场人员、技术人员、处置小组均具有事件解决相关控件的编辑权限。</p>
 </li>
 <li>
  <p style="">最后由发起人确认事件完成情况。</p>
 </li>
</ol>
<p style=""><strong><u><span style="font-size: 17px">表单操作权限表格</span></u></strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 1100px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="height: 35px;">
    <td colspan="1" rowspan="2" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">表单字段</span></p>
    </td>
    <td colspan="10" rowspan="1" colwidth="100,100,100,100,100,100,100,100,100,100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">表单操作权限</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">发起人</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">驻场主管审批</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">驻场主管办理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">驻场人员办理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">服务台办理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">部门经理审批</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">技术人员办理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">重大事件经理审批</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">处置小组办理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">发起人办理</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">项目数据</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">一级事件说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">二级事件说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">三级事件说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">事件级别</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">事件发生时间</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">事件描述及处置要求</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">联系人姓名</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">联系人电话</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">计划/已经开始处理时间</span></p>
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">计划处理结束时间</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">指派驻场人员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">历史处置情况</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">历史处置说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">历史处置过程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">全局匹配解决方案</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">指派技术人员</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">组建处置小组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">解决情况</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">解决方式</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">处理过程简述</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">变更情况描述</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">未完成情况说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">关联问题处理流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">完成情况确认</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">未确认情况描述</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">关联变更、测试、发布流程</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">只读</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">可编辑</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（五）表单设置</span></em></strong></p>
<p style=""><strong>基础设置</strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 500px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">表单名称</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">所在分组</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">表单说明</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">谁可以发起</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">模板管理员</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">新建事件 Build 22xxxx</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">运维服务管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">用于一般事件、重大事件的处置管理。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">XX部、XX部、XX部</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style="text-align: center; ; line-height: 1"><span style="font-size: 12px">流程管理员</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style=""><strong>高级设置</strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 975px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 875px">
  </colgroup>
  <tbody>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">设置项</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="875" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">设置内容</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">流程高级设置</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="875" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">自动去重：关</span></p>
     <p style=""><span style="font-size: 12px">允许加签：开</span></p>
     <p style=""><span style="font-size: 12px">撤销/修改审批单：</span></p>
     <ul>
      <li>
       <p style=""><span style="font-size: 12px">允许提交人撤销审批中的审批单（最多90天）</span></p>
      </li>
     </ul>
     <p style=""><span style="font-size: 12px">限时审批：关（收费的）</span></p>
     <p style=""><span style="font-size: 12px">手写签名：关</span></p>
     <p style=""><span style="font-size: 12px">审批意见：默认</span></p>
     <p style=""><span style="font-size: 12px">允许代他人提交：关</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">数据展示设置</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="875" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">默认</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">提醒填写</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="875" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">无</span></p>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">权限管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="875" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">默认权限组：</span></p>
     <ul>
      <li>
       <p style=""><span style="font-size: 12px">权限成员→数据管理员</span></p>
      </li>
      <li>
       <p style=""><span style="font-size: 12px">管理范围→全公司</span></p>
      </li>
      <li>
       <p style=""><span style="font-size: 12px">操作权限→全部</span></p>
      </li>
     </ul>
     <p style=""><span style="font-size: 12px">权限组1：</span></p>
     <ul>
      <li>
       <p style=""><span style="font-size: 12px">权限成员→运维数据管理员</span></p>
      </li>
      <li>
       <p style=""><span style="font-size: 12px">管理范围→全公司</span></p>
      </li>
      <li>
       <p style=""><span style="font-size: 12px">操作权限→查看、导出</span></p>
      </li>
     </ul>
     <p style=""><span style="font-size: 12px">权限组2：</span></p>
     <ul>
      <li>
       <p style=""><span style="font-size: 12px">权限成员→运维部、xx部、xx部</span></p>
      </li>
      <li>
       <p style=""><span style="font-size: 12px">管理范围→自己负责的</span></p>
      </li>
      <li>
       <p style=""><span style="font-size: 12px">操作权限→查看</span></p>
      </li>
     </ul>
     <p style=""><span style="font-size: 12px">权限组外人员查看权限：</span></p>
     <ul>
      <li>
       <p style=""><span style="font-size: 12px">仅权限组内人员以及表单流程内相关人员可查看审批单详情</span></p>
      </li>
     </ul>
    </td>
   </tr>
   <tr style="height: 33px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">打印模板</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="875" style="border-color: rgb(217, 217, 217);border-style: solid;border-width: 1px;">
     <p style=""><span style="font-size: 12px">默认</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（六）注意事项</span></em></strong></p>
<ol>
 <li>
  <p style="">在钉钉流程设计之前，一定要设计流程图，否则一个小小的改动都有可能导致流程重建。<strong><span fontsize="" color="">↓↓</span></strong></p>
  <p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-exrv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
 </li>
 <li>
  <p style=""><strong><u><span fontsize="" color="">免费的不行收费的行：</span></u></strong>表单字段一旦被设置为条件分支，默认只有且必须发起人可编辑，其它环节均不可设置可编辑。正确操作：先设置需要表单操作权限的环节，再设置条件，但是发起人也需要可编辑。<strong><span fontsize="" color="">↓↓</span></strong></p>
  <p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-utuk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
  <p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tlkr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
 </li>
 <li>
  <p style=""><strong><u><span fontsize="" color="">免费的不行收费的行：</span></u></strong>流程过程中修改作为条件的选项，不会改变发起人选择的选项确定的流程路径。</p>
 </li>
 <li>
  <p style="">控件设置“必填”开启才可以在流程设计中作为条件。<strong><span fontsize="" color="">↓↓</span></strong></p>
  <p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qlth.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">关联表单的关联模式如下：
    <br></span></p>
  <ol>
   <li>
    <p style="">拷贝模式：不可以查看关联表单的详细内容，可以直接作为过滤条件。</p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">引用模式：可以查看关联表单的详细内容，不可以作为过滤条件。</span></p>
    <p style=""><strong><em><span style="font-size: 20px">结语</span></em></strong></p>
   </li>
  </ol>
 </li>
</ol>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp; 看下图</span><strong><span fontsize="" color="">↓↓</span></strong>，解决事件是能力，检测事件也是能力，<span style="font-size: 17px">在解决事件之前，先要弄清楚什么是事件，分为哪些类别，定义了哪些级别，这样才能够确保判断准确，避免资源浪费。</span></p>
<p style="text-align: center; "><strong><span fontsize="" color=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xcdj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></strong><span style="font-size: 17px">
  <br>
 </span></p>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp; 利用钉钉建立的事件处理流程，即明确了工作流程和责任分工，又监控了事件处理的过程，但是不包括事件的定义和评估等，所以不要以为一个流程就解决了所有的事，剩下的事情即复杂又庞大，“先浅后深”也许就不会显得那么庞杂了。</span></p>
<p style=""><span style="font-size: 17px">原文链接：https://mp.weixin.qq.com/s/KlEyrytbJTN0-LkfGLoT6g</span></p>]]></description><guid isPermaLink="false">/archives/shi-yong-ding-ding-shi-xian-yun-wei-fu-wu-guan-li-shi-jian-guan-li</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:35:26 GMT</pubDate></item><item><title><![CDATA[IT运维01：服务目录]]></title><link>https://wiki.dgfygk.com/archives/ityun-wei-01-fu-wu-mu-lu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B401%EF%BC%9A%E6%9C%8D%E5%8A%A1%E7%9B%AE%E5%BD%95&amp;url=/archives/ityun-wei-01-fu-wu-mu-lu" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><em><u><span style="font-size: 20px">服务目录？</span></u></em></strong>
 <br></p>
<p style="">服务目录 = 服务 + 目录。</p>
<p style="">服务是产品，服务目录等于产品目录。</p>
<p style="">服务目录一方面描述了交付内容和交付标准，另一方面描述了交付内容的定价。</p>
<p style="">对于需方，服务目录让你知道自己<strong>购买了哪些服务</strong>。</p>
<p style="">对于供方，服务目录让你知道自己<strong><span fontsize="" color="">能够提供哪些服务</span></strong>。</p>
<p style="">这就是服务目录列表<strong><span fontsize="" color="">↓↓
   <br></span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vyea.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><u><span style="font-size: 20px">便于需方</span></u></em></strong></p>
<p style="">便于需方掌握服务交付的内容。</p>
<p style="">便于需方了解服务标准。</p>
<p style="">便于需方进行考核验收，提供关键指标。</p>
<p style="">便于需方选择差异化服务。</p>
<p style="">便于需方优化预算金额。</p>
<p style="">便于需方进行费用审计。</p>
<p style="text-align: center; "><strong><em><u><span style="font-size: 20px">有利于供方</span></u></em></strong></p>
<p style="">有利于供方服务人员识别服务范围。</p>
<p style="">有利于供方服务人员掌握服务标准。</p>
<p style="">有利于供方确保运维服务项目的利润率。
 <br></p>
<p style="">有利于供方识别服务目录内容和实际提供服务内容的一致性。</p>
<p style="">有利于供方为过程管理提供接口。</p>
<p style="">有利于供方持续发展IT运维业务。</p>
<p style="text-align: center; "><strong><em><u><span style="font-size: 20px">建立服务目录</span></u></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">定义服务目录
    <br></span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;建立服务目录应该考虑组织的业务能力、客户的需求以及内外部环境来定义<strong>服务种类</strong>和<strong>服务目标</strong>。需要与组织的发展目标、业务规划、组织架构和服务保障体系相结合。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;从这些角度来看，服务目录并不是由普通的服务目录管理人员定义的，而是由管理层参与的组织定义的。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;无论是最初的建立服务目录，还是服务目录维护过程中的变更，都需要组织进行谨慎的评定，以避免出现错误信息、依赖关系紊乱、错误的绩效评价等问题。
 <br></p>
<p style=""><strong><u><span fontsize="" color="">分类列表</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;服务目录的分类可以参考国家标准GB/T 29264《信息技术服务&nbsp; 分类与代码》，按照国标做不会出错。</p>
<p style="">&nbsp;&nbsp;&nbsp; 如下图所示<strong><span fontsize="" color="">↓↓</span></strong>，每一个服务名称好比将一系列服务过程打包出售，暂且称之为服务包，我们可以基于组织的定价原则对
 <br>
 服务包的颗粒度进行把握，不宜过细，也不宜过粗，只要拿出来是“一道菜”或者“一席菜”即可。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dlyl.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">服务详述</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 服务目录好比“菜牌”，服务详述好比“菜谱”，餐馆的顾客们可能不需要知道每道菜是如何制作的，而IT运维服务项目的客户们需要知道我们这道“菜”有哪些内容，实现哪些目标，交付哪些成果。
 <br>
 <br>
 &nbsp;&nbsp;&nbsp;&nbsp;服务详述是对服务列表中每项服务内容的详细表述，我们需要用客户能够看得懂的语言介绍服务的目标、内容、交付成果等。<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-eevk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><u><span style="font-size: 20px">管理服务目录</span></u></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;服务目录的管理应当建立相应的管理制度，明确各部门的管理角色及其职责，并且对服务目录的建立、发布、使用、修订、评审以及输出文件进行约束。&nbsp;&nbsp;&nbsp;</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;定义角色应该考虑决策过程、维护过程、内审过程所涉及到人员和组织，以服务目录管理员为例，其职责至少应该包括：</p>
<ol>
 <li>
  <p style="">维护服务目录，包括编制、更新、发布服务目录。</p>
 </li>
 <li>
  <p style="">发现和收集服务目录信息不准确项。</p>
 </li>
 <li>
  <p style="">确保服务目录在过程管理中的接口和依赖关系适当、准确。</p>
 </li>
 <li>
  <p style="">组织服务目录的修订评审。</p>
 </li>
</ol>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp; 在IT运维服务管理过程中，落实制度可以借助运维过程管理工具来实现，至少在“僵化”这一过程中可以起到很好的辅助作用。<em>（有兴趣可以了解一下华为管理进步三部曲：僵化→优化→固化）</em></p>
<p style="text-align: center; "><strong><em><u><span style="font-size: 20px">结语</span></u></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; &nbsp; 关于服务目录，建议组织人员投入时间好好的研究和讨论一番，我们不仅需要“菜牌”和“菜谱”，还需要成本和利润，更重要的是，它是展示服务标准化的外在表现之一。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;服务目录建立的过程是审视自我的过程，能够让管理者清晰的看到组织的IT运维服务能力，能够帮助组织制定符合实际的运维服务管理目标，从而采取切实有效的管理手段。</p>
<p style="">https://mp.weixin.qq.com/s/5zyEqMKgsjK4hNhfZRpRTg</p>]]></description><guid isPermaLink="false">/archives/ityun-wei-01-fu-wu-mu-lu</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:28:59 GMT</pubDate></item><item><title><![CDATA[IT运维02：准备工作]]></title><link>https://wiki.dgfygk.com/archives/ityun-wei-02-zhun-bei-gong-zuo</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B402%EF%BC%9A%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C&amp;url=/archives/ityun-wei-02-zhun-bei-gong-zuo" width="1" height="1" alt="" style="opacity:0;">
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hjxa.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 24px">（一）运维团队</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;运维团队的建立应该以合同约定为基础，按照约定的服务方式、服务内容、服务标准以及当前的组织状态来建立运维团队。</p>
<p style="">&nbsp;&nbsp;&nbsp; 运维项目成本中，人员费用是主要的组成之一，所以人员的数量和质量直接影响项目成本，有些供方通过减少人员数量、增加人员复用、降低人员质量来降低项目成本。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;减少人员数量会导致服务团队承担更多的工作，对团队成员的质量要求更高，否则工作很难达到合同约定的服务标准，如果合同明确约定人员数量的，“减少人员”对于供、需双方而言，从需要承担的法律风险及其后果来看，性价比并不高。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;增加人员复用和降低人员质量可以在特定条件下实施，建议实施之前获得需方的同意。</p>
<p style=""><strong><u><span fontsize="" color="">建立运维团队应该考虑的内容
    <br></span></u></strong></p>
<p style="">1）服务方式：远程支持、现场服务、驻场服务</p>
<p style="">&nbsp;&nbsp;&nbsp; 在远程支持和临时性现场服务中，服务人员的管理是供方管理，通过合理的组织分工，可以实现人员复用最大化。
 <br>
 &nbsp;&nbsp;&nbsp;&nbsp;在驻场服务中，服务人员的管理是供方+需方管理，合同中对人员的数量和资质有明确要求的，很难实现人员复用。</p>
<p style="">2）服务内容及标准：服务对象所涉及的专业领域、服务类别、交付方式及交付成果、服务级别、服务时间及频度</p>
<p style="">&nbsp;&nbsp;&nbsp; 关于服务内容和服务标准，参考组织级的运维服务目录（<a href="https://mp.weixin.qq.com/s?__biz=Mzg3Nzg3NTc2Ng==&amp;mid=2247483804&amp;idx=1&amp;sn=bdfa46f5c49793306eb01e9658b74542&amp;chksm=cf1d06c5f86a8fd38581382014f78fea5b90fcca51bab3e5721b21cd37b0034c43bd34bba125&amp;token=2058625526&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank">IT运维01：服务目录</a>）进行归类，原运维服务目录中存在未涉及或服务标准不一致的，应按照组织的服务目录管理制度进行添加和修改，直至达到与项目技术要求一致，并编制项目级的服务目录，作为运维项目实施和编制操作规范的依据之一。</p>
<p style="">3）服务人员：人员数量、学历及其专业、技能证书</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;人员的数量和资质应该按照合同约定执行，建立运维团队人员名册及档案，人员档案建议包括身份证复印件、学历证书复印件、技能证书复印件、个人简历、无犯罪证明和健康证<em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（有些行业需要）</span></em>、与组织签订的保密协议及各种承诺书。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;建立人员档案的目的是便于供方管理和需方审查。</p>
<p style=""><strong><u><span fontsize="" color="">运维团队的角色</span></u></strong></p>
<p style="">1）管理人员</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;基层管理人员实际上就是组织的眼睛，一只眼睛盯着员工，并带领员工完成工作任务，另一只眼睛发现问题，提出改进需求，所以对基层管理人员的选用，往往会有更多的考量，引用一下南州翁遗训，掌握了大原则就不会太跑偏。</p>
<blockquote>
 <p style=""><em>立庙堂为大政，乃行天道，不可些许挟私。秉公平，踏正道，广选贤人，举能者执政柄，即天意也。是故，确乎贤能者，即让己职。于国有勋然不堪任者而赏其官职，乃不善之最也。适者授官，功者赏禄，方惜才也。</em></p>
</blockquote>
<p style="">&nbsp;&nbsp;&nbsp; 以上这段话基本都是字面意思，很容易理解，选拔和任命管理人员可以作为参考，一个人只有在合适的位置上才能发挥最大的作用。</p>
<p style="">&nbsp;&nbsp;&nbsp; 但是，选用的管理人员必须与组织保持同步，即目标一致，否则组织建设就成了“笑话”。如果把组织比喻为党，团队比喻为军队，那么党能否指挥枪就决定了组织能走多远，推荐一本超级“励志”的书→《中华苏维埃共和国史》，哪一位高管的发家史能比得上这个。</p>
<p style="">从运维管理角度来讲，管理人员至少应该包括以下职责：</p>
<ol>
 <li>
  <p style="">按照企业的要求管理运维团队，履行合同约定的服务标准；</p>
  <p style=""></p>
 </li>
 <li>
  <p style="">根据成本目标控制项目成本，</p>
  <p style=""></p>
 </li>
 <li>
  <p style="">与组织保持沟通，开发需方的真正需求，提升项目附加价值；</p>
  <p style=""></p>
 </li>
 <li>
  <p style="">协助运维人员建立能力提升规划，提升运维团队整体能力。</p>
  <p style=""></p>
 </li>
</ol>
<p style="">2）团队骨干</p>
<p style="">&nbsp;&nbsp;&nbsp; 任何团队都需要骨干，士官能做的事情军官不一定能做，骨干能做的事情管理者也未必能做，举个例子一看就懂，没有走过万里长征的红军，哪儿来的百万解放大军，“红军”就是你要选择的骨干，即忠诚，又会打。</p>
<p style="">3）操作人员</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;操作人员通常从事日常重复性工作，这类工作需要编制规范性文件用于指导、规范日常工作，例如操作规范、过程文档的模板等，结合常态化的培训工作，不断提升操作人员的技术水平，在满足岗位需求的前提下，进行能力提升规划，从而提高基层员工与组织的粘合度。</p>
<p style=""><strong><u><span fontsize="" color="">运维团队能力评估</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 对运维人员进行能力评估，评估的目的是确保运维人员的能力满足服务需求，通过评估不仅能够获取团队成员当前的能力状态，还能确定人员的岗位职责分工，辅导制定未来的个人能力提升计划，以及为组织整体能力的提升提供参考数据。</p>
<p style="">1）评估步骤</p>
<ol>
 <li>
  <p style="">建立评估小组，评估小组由组织的骨干人员组成，一般为临时性组织。</p>
  <p style=""></p>
 </li>
 <li>
  <p style="">确定评估办法，根据组织的运维服务能力管理计划确定人员能力评估办法，对人员的服务能力划分等级，确定评估标准。</p>
  <p style=""></p>
 </li>
 <li>
  <p style="">人员能力调查，根据评估标准编制人员能力调查表。</p>
  <p style=""></p>
 </li>
 <li>
  <p style="">能力评估报告，根据调查结果输出能力评估报告。&nbsp;&nbsp;&nbsp;</p>
 </li>
</ol>
<p style="">人员能力调查表可以是这样<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vdrv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">人员能力调查表也可以是这样<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ozte.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">运维团队的主要任务
    <br></span></u></strong></p>
<p style=""><span fontsize="" color="">关于运维团队的主要任务，我觉得以下两个方面比较重要：</span></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一方面是运维服务合同的履行，通过履行过程和交付结果展现了组织服务的标准化和专业化，打造组织的服务品牌。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;另一方面是组织与需方信息传递的纽带，通过挖掘需方的真实需求，提升运维项目的附加价值。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 24px">（二）运维工具</span></em></strong></p>
<p style="">&nbsp;&nbsp; 运维工具是什么？为什么使用运维工具？可以参考<a href="http://mp.weixin.qq.com/s?__biz=Mzg3Nzg3NTc2Ng==&amp;mid=2247483727&amp;idx=1&amp;sn=c1b2f5aaa44039427b5587d64990c0e2&amp;chksm=cf1d0616f86a8f00ba10fad1fc20546d1ca87f76c2a567b8f3a9a9fc871e58ea3c62ffbdf621&amp;scene=21#wechat_redirect" target="_blank">选择IT运维工具，拒绝裸奔！</a>，至于运维工具怎么用？还是参考度娘吧。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;在运维服务实施前，关于运维工具我们需要做的一些事情：</p>
<ol>
 <li>
  <p style="">内部沟通协调部署所需硬件的配置和采购、部署条件及所需的资源等。</p>
 </li>
 <li>
  <p style="">根据采购计划编制部署实施计划，明确人员职责分工。</p>
 </li>
 <li>
  <p style="">与需方沟通运维工具的部署环境、功能实现、对系统的影响以及部署实施计划等是否符合需方的要求。</p>
 </li>
 <li>
  <p style="">利用已有环境或参考操作手册组织针对运维工具的培训，确保操作人员已经了解运维工具的部署情况、需方的功能需求与功能实现的差异等基本情况。</p>
 </li>
</ol>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;带上“两册”进场→操作手册和用户手册，运维工具维护人员可以通过操作手册进行日常管理和维护的操作，运维服务人员或者客户可以根据用户手册操作系统的功能。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;部署完成之后记得进行系统备份。。。。。。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 24px">（三）备品备件</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一般情况下，需方会明确备品备件的响应和管理要求，此时，供方按照要求执行即可，如果备品备件的管理要求不够明确时，我们应该考虑以下问题：</p>
<ol>
 <li>
  <p style="">备品备件的现场管理和非现场管理。</p>
 </li>
 <li>
  <p style="">备品备件的供应商及响应时长。</p>
 </li>
 <li>
  <p style="">备品备件的操作使用。</p>
 </li>
 <li>
  <p style="">备品备件的管理（领用、使用、维修、异常损耗）。</p>
 </li>
 <li>
  <p style="">备品备件的配置计划。</p>
 </li>
</ol>
<p style=""><span style="font-size: 17px">&nbsp;&nbsp;&nbsp;&nbsp;备品备件的管理直接影响了响应速度，当日响应、下一个工作日响应、下一自然日响应，还是N日响应。</span><strong><em><span style="font-size: 24px">
    <br>
   </span></em></strong></p>
<p style="text-align: center; "><strong><em><span style="font-size: 24px">（四）运维资料</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">应急预案
    <br></span></u></strong></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;应急预案的作用是提高团队预警和应对突发事件的处理能力，新项目、新系统、新设备、新环境都有可能出现应急事件，作为一个专业的运维服务团队，应急事件发生时，管理人员是否知道何时启动应急预案，应该提供哪些支持，运维人员是否熟悉先期处置的内容以及备品备件的使用规则，相比“无头苍蝇式”的逐个打电话和“一问三不知”，规范、有序、高效的处理突发事件会不会为我们的运维服务工作提高一个层次。
  <br></span></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;制定应急预案之前，应该进行运维服务风险分析，针对不同的风险提出相应的风险对策，并根据风险危害的性质和影响范围，对潜在风险进行分级管控，分级管控可以根据不同的风险提供更多的资源支持，同时也减少没必要的资源浪费。
  <br></span></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;方案编制完成后，应该组织团队成员进行方案演练，并形成演练记录，编制方案是为了指导运维人员的运维工作，组织演练是学习和熟练的过程，总不能等到事情发生了，让客户等等，我们先查查文件。
  <br></span></p>
<p style=""><strong><u><span fontsize="" color="">操作规程</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 操作规程主要是对服务内容标准动作的固化，让运维人员了解什么工作该怎么做、做成什么样，操作规程的内容应该覆盖所有维护对象，并对维护工作要点、流程、周期以及输出成果进行详细说明，作为操作人员的工作指导手册。
 <br>
 <br>
 我的操作规程有以下内容：<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qqgd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xnjg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cqxz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">文档范本</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 服务过程记录和服务报告的范本文件为运维工作提供统一的交付成果，确保基础数据的完整和真实有效，没有模板的结果有两个：一个是交上来的文件“千奇百怪”，另一个是压根就不做了，统一模板的好处就多了，标准化管理、监督工作完整性、为数据分析提供基础数据等。</p>
<p style="">分享个IT运维服务报告目录<strong><span fontsize="" color="">↓↓
   <br></span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gchp.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">再分享个巡检记录范本<strong><span fontsize="" color="">↓↓
   <br></span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hwzi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 24px">（五）结语</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 团队管理主要是对人的管理，人们常说：有人的地方就有江湖，好在运维的“江湖”基本都是技术，少了许多<em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（或者说简单了许多）</span></em>八面玲珑和尔虞我诈，但是，却多了许多一丝不苟，复杂的问题简单不了，简单的问题也简单不了，这就是搞技术的特点。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/M-peHdZaIz4jihN0kidClw</p>]]></description><guid isPermaLink="false">/archives/ityun-wei-02-zhun-bei-gong-zuo</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:27:51 GMT</pubDate></item><item><title><![CDATA[Linux运维：安全措施（修改SSH端口和密码错误锁定用户）]]></title><link>https://wiki.dgfygk.com/archives/linuxyun-wei-an-quan-cuo-shi-xiu-gai-sshduan-kou-he-mi-ma-cuo-wu-suo-ding-yong-hu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E8%BF%90%E7%BB%B4%EF%BC%9A%E5%AE%89%E5%85%A8%E6%8E%AA%E6%96%BD%EF%BC%88%E4%BF%AE%E6%94%B9SSH%E7%AB%AF%E5%8F%A3%E5%92%8C%E5%AF%86%E7%A0%81%E9%94%99%E8%AF%AF%E9%94%81%E5%AE%9A%E7%94%A8%E6%88%B7%EF%BC%89&amp;url=/archives/linuxyun-wei-an-quan-cuo-shi-xiu-gai-sshduan-kou-he-mi-ma-cuo-wu-suo-ding-yong-hu" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 10.5ptpx">操作系统：CentOS 7</span><span style="font-size: 18px">
   <br>
  </span></strong></p>
<p style=""><strong><em><span style="font-size: 17px">（一）修改SSH远程登录端口</span></em></strong></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;SSH默认端口22/TCP，可以通过SSH密钥扫描或者root账户登录尝试进行暴利破解，如下图即为境外IP尝试登录失败的记录信息：</span></p>
<pre><code>lastb</code></pre>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jnnp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;通过统计登录失败次数，发现来自新加坡的159.89.199.153这个货在短时间内尝试登录了1635次，如下：</span></p>
<pre><code>lastb | awk '{ print "    IP地址：" $3}' | sort | uniq -c | sort -n</code></pre>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wcip.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;所以，我们先修改默认端口，这样就减少了普遍性暴利攻击的概率。
  <br></span></p>
<p style=""><strong><u><span style="font-size: 10.5ptpx">设置步骤：</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">1）编辑配置文件</span></p>
<pre><code>vim&nbsp;/etc/ssh/sshd_config</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;取消Port 22前的注释符号“#”，新增Port 5000作为SSH远程登录的端口，两条记录并存是为了防止修改操作失误导致无法远程登录，修改成功后删除Port 22即可。</span></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vyep.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;重启SSH服务</span></p>
<pre><code>systemctl restart sshd</code></pre>
<p style=""><span style="font-size: 10.5ptpx">2）开放防火墙端口
  <br></span></p>
<pre><code>firewall-cmd --zone=public --add-port=5000/tcp –permanent</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;重启防火墙服务使修改生效。</span></p>
<pre><code>systemctl restart firewalld</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;查看防火墙已开放的端口，输出结果应该包括5000。</span></p>
<pre><code>firewall-cmd&nbsp;--list-ports</code></pre>
<p style=""><span style="font-size: 10.5ptpx">3）由于我用的是云服务器，所以需要在安全组设置规则，在入方向增加5000/5000的端口范围。
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mpnv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">4）使用5000端口远程连接成功后，可删除Port 22。
  <br>5）提示：如果开启了Selinux，需要进行相应的设置。
  <br>&nbsp;&nbsp;&nbsp;&nbsp;检查Selinux是否开启。</span></p>
<pre><code>sestatus</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp; disabled表示关闭状态，enabled表示开启状态。
  <br></span></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gybq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;如果开启状态执行下面命令：</span></p>
<pre><code>semanage&nbsp;port&nbsp;-a&nbsp;-t&nbsp;ssh_port_t&nbsp;-p&nbsp;tcp&nbsp;5000</code></pre>
<p style="text-align: center; "><strong><em><span style="font-size: 17px">（二）设置登录密码错误锁定用户</span></em></strong></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;根据前文提到的来自新加坡的货不断尝试登录，我们可以通过设置限制密码错误次数锁定用户，禁止他继续尝试登录，如果用户解锁后输入的密码仍然错误，则再次重新锁定。</span></p>
<p style=""><strong><u><span style="font-size: 10.5ptpx">设置步骤：</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">1）备份sshd和login文件，备份的目的是怕搞砸了，有备无患。</span></p>
<pre><code>cp /etc/pam.d/sshd /etc/pam.d/sshd.bakcp&nbsp;/etc/pam.d/login&nbsp;/etc/pam.d/login.bak</code></pre>
<p style=""><span style="font-size: 10.5ptpx">检查是否备份成功。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bqdk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">2）检查pam的pam_tally2.so模块，其用途就是实现锁定和解锁的功能。</span></p>
<pre><code>find / -name "pam_tally2.so"</code></pre>
<p style=""><span style="font-size: 10.5ptpx">如下图正常。
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bbru.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">3）修改文件，该文件用于密码设置及登陆控制。</span></p>
<pre><code>vim&nbsp;/etc/pam.d/system-auth</code></pre>
<p style=""><span style="font-size: 10.5ptpx">在第一行添加如下：</span></p>
<pre><code>auth&nbsp;required&nbsp;pam_tally2.so&nbsp;onerr=fail&nbsp;deny=3&nbsp;unlock_time=40&nbsp;even_deny_root&nbsp;root_unlock_time=30</code></pre>
<p style=""><span style="font-size: 10.5ptpx">onerr=fail：表示连续失败。</span></p>
<p style=""><span style="font-size: 10.5ptpx">deny=3：表示用户（普通和root用户）登录密码连续错误3次后锁定用户。
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">unlock_time=40：表示普通用户解锁时间，单位是秒。
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">root_unlock_time=30：表示root用户解锁时间，单位是秒</span></p>
<p style=""><span style="font-size: 10.5ptpx">4）修改配置文件/etc/pam.d/sshd，限制ssh远程登录尝试。</span></p>
<pre><code>vim /etc/pam.d/sshd</code></pre>
<p style=""><span style="font-size: 10.5ptpx">在第一行添加如下：</span></p>
<pre><code>auth required pam_tally2.so deny=3 unlock_time=20 even_deny_root root_unlock_time=30</code></pre>
<p style=""><span style="font-size: 10.5ptpx">保存退出后，重启SSH服务。</span></p>
<pre><code>systemctl restart sshd</code></pre>
<p style=""><span style="font-size: 10.5ptpx">5）修改登录配置文件/etc/pam.d/login，限制tty方式登录尝试。</span></p>
<pre><code>vim /etc/pam.d/login</code></pre>
<p style=""><span style="font-size: 10.5ptpx">在第一行添加如下：</span></p>
<pre><code>auth required pam_tally2.so deny=3 unlock_time=20 even_deny_root root_unlock_time=30</code></pre>
<p style=""><strong><u><span style="font-size: 10.5ptpx">解除用户锁定：</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">查看用户的错误登录次数</span></p>
<pre><code>pam_tally2 –-user</code></pre>
<p style=""><span style="font-size: 10.5ptpx">清空root</span><code>用户的错误登录次数</code></p>
<pre><code>pam_tally2 -u root -r</code></pre>
<p style=""><code>清空指定用户的错误登录次数</code></p>
<pre><code>pam_tally2 --user 用户名 --reset</code></pre>
<p style="">原文链接：https://mp.weixin.qq.com/s/Uv2b6qyXwaYxu0jn5fUSMQ</p>]]></description><guid isPermaLink="false">/archives/linuxyun-wei-an-quan-cuo-shi-xiu-gai-sshduan-kou-he-mi-ma-cuo-wu-suo-ding-yong-hu</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:03:38 GMT</pubDate></item><item><title><![CDATA[Linux运维：安全措施（用户无操作登出/远程KEY登录/禁止使用密码、root用户远程登录）]]></title><link>https://wiki.dgfygk.com/archives/linuxyun-wei-an</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E8%BF%90%E7%BB%B4%EF%BC%9A%E5%AE%89%E5%85%A8%E6%8E%AA%E6%96%BD%EF%BC%88%E7%94%A8%E6%88%B7%E6%97%A0%E6%93%8D%E4%BD%9C%E7%99%BB%E5%87%BA%2F%E8%BF%9C%E7%A8%8BKEY%E7%99%BB%E5%BD%95%2F%E7%A6%81%E6%AD%A2%E4%BD%BF%E7%94%A8%E5%AF%86%E7%A0%81%E3%80%81root%E7%94%A8%E6%88%B7%E8%BF%9C%E7%A8%8B%E7%99%BB%E5%BD%95%EF%BC%89&amp;url=/archives/linuxyun-wei-an" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 17px">温馨提示：
   <br></span></strong></p>
<p style="text-align: center; "><strong><u><span style="font-size: 17px; color: rgb(255, 0, 0)">任何修改前做好文件的备份，必要时进行系统备份，此事与技术水平无关。</span></u></strong></p>
<p style=""><strong><span style="font-size: 10.5ptpx">操作系统：CentOS 7</span></strong></p>
<p style="text-align: center; "><strong><em><span style="font-size: 17px">（一）设置用户无操作N秒后自动退出</span></em></strong></p>
<p style=""><strong><u><span style="font-size: 10.5ptpx">针对设置用户生效</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">1）保持用户登录后的默认目录，编辑.bashrc，该文件用于储存用户的个性化设置，既然是用户个性化设置，也就是说以下设置对其它用户不管用。</span></p>
<pre><code>vim&nbsp;.bashrc</code></pre>
<p style=""><span style="font-size: 10.5ptpx">编辑模式下在最后一行插入如下内容，保存退出。</span></p>
<pre><code>TMOUT=180</code></pre>
<p style=""><span style="font-size: 10.5ptpx">表示180秒内无任何操作就会自动退出登录，如果设置为0表示永不自动退出。
  <br>2）保存后执行source命令，使修改立刻生效</span></p>
<pre><code>source .bashrc</code></pre>
<p style=""><strong><u><span fontsize="" color="">对非root一般用户生效</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">1）常规操作，备份profile文件</span></p>
<pre><code>cp&nbsp;/etc/profile&nbsp;/etc/profilebak</code></pre>
<p style=""><span style="font-size: 10.5ptpx">2）编辑文件</span></p>
<pre><code>vim /etc/profile</code></pre>
<p style=""><span style="font-size: 10.5ptpx">编辑模式下在插入如下内容，保存退出。</span></p>
<pre><code>export&nbsp;TMOUT=300</code></pre>
<p style=""><span style="font-size: 10.5ptpx">表示300秒内无任何操作就会自动退出登录。
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">3）注销用户重新登录后生效。</span></p>
<p style="text-align: center; "><strong><em><span style="font-size: 17px">（二）远程KEY登录和禁止使用密码远程登录</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">Xshell7创建用户密钥</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">1）工具→新建密钥生成向导
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">密钥类型：RSA
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">密钥长度：2048
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zjpr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cuci.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">密钥名称：默认即可
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">密钥加密的密码：</span><span fontsize="" color="">此处创建密码目的是防止私钥被盗用，如果设置，选择key登录也需要输入密码。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jxlp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">公钥格式：选择SSH2 - OpenSSH
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">点击“保存为文件”按钮，记住存储位置，点击完成即可。
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-errx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span style="font-size: 10.5ptpx">远程服务器配置</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">1）上传文件
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;上传生成的公钥文件id_rsa_2048.pub至用户家目录下，我使用的是Xftp完成的上传。
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">2）移动、修改文件
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;登录用户，移动文件至.ssh目录（如果没有这个目录就创建一个），同时修改文件名称：</span></p>
<pre><code>mv&nbsp;id_rsa_2048.pub&nbsp;.ssh/authorized_keys</code></pre>
<p style=""><span style="font-size: 10.5ptpx">3）修改目录和文件权限
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;修改.ssh目录权限：</span></p>
<pre><code>chmod 700 .ssh</code></pre>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-maeb.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;修改</span><code>authorized_keys</code>文件权限：</p>
<pre><code>chmod 600 authorized_keys</code></pre>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-syrc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">4）编辑配置文件
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">常规操作备份文件
  <br></span></p>
<pre><code>cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak</code></pre>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qhgb.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 10.5ptpx">编辑文件</span></p>
<pre><code>vim /etc/ssh/sshd_config</code></pre>
<p style=""><span style="font-size: 10.5ptpx">修改以下内容，去掉注释#，没有的就手动敲上：
  <br></span></p>
<pre><code>Protocol&nbsp;2&nbsp;ServerKeyBits 2048RSAAuthentication yes PubkeyAuthentication&nbsp;yes&nbsp;AuthorizedKeysFile&nbsp;&nbsp;&nbsp;&nbsp;.ssh/authorized_keys&nbsp;</code></pre>
<p style=""><span style="font-size: 10.5ptpx">退出保存，重启ssh服务
  <br></span></p>
<pre><code>systemctl&nbsp;restart sshd</code></pre>
<p style=""><span style="font-size: 10.5ptpx">5）使用Xshell
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;方法选择Public Key，如果生成了多个私钥就在“设置”里面选择。
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bucu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span style="font-size: 10.5ptpx">密码设置</span></u></strong></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;KEY登录验证成功后，可以设置</span>禁止使用密码远程登录<strong><span style="font-size: 10.5ptpx; color: rgb(255, 0, 0)">#慎重修改此项，验证KEY登录成功后再修改#
   <br></span></strong></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;编辑配置文件</span><code>sshd_config修改以下内容，设置禁止使用密码远程登录。</code></p>
<pre><code>PasswordAuthentication&nbsp;no</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;设置不允许空密码登录。
  <br></span></p>
<pre><code>PermitEmptyPasswords no</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp; 退出保存，重启ssh服务。
  <br></span></p>
<p style=""></p>
<pre><code>systemctl&nbsp;restart sshd</code></pre>
<p style=""><strong><em><span style="font-size: 17px">（三）禁止root远程登录</span></em></strong></p>
<p style=""><strong><u><span style="font-size: 10.5ptpx">禁止root用户远程登录</span></u></strong></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;在禁止root用户远程登录前，</span><strong><span style="font-size: 10.5ptpx; color: rgb(255, 0, 0)">应该创建好用于远程管理的用户</span></strong><span style="font-size: 10.5ptpx">，并设置好权限，试验成功后再禁止。
  <br></span></p>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;由于root用户拥有最高权限，建议</span><span fontsize="" color="">远程登录尽量不使用root用户，使用普通用户登录远程连接，然后再切换到root用户。</span></p>
<p style=""><span style="font-size: 10.5ptpx">1）常规操作，备份sshd_config文件（参考上文）</span></p>
<p style=""><span style="font-size: 10.5ptpx">2）编辑配置文件</span></p>
<pre><code>vim /etc/ssh/sshd_config</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;拒绝root用户远程登录设置为no。</span></p>
<pre><code>PermitRootLogin no</code></pre>
<p style=""><span style="font-size: 10.5ptpx">&nbsp;&nbsp;&nbsp;&nbsp;重启服务，设置生效。</span></p>
<pre><code>service sshd restart</code></pre>
<p style="text-align: center; "></p>
<p style="text-align: center; ">不蒙圈秘诀：修改前做备份，修改后做记录。</p>
<p style="text-align: center; ">原文链接：https://mp.weixin.qq.com/s/VLdyO8m3TuSVqIO8eHK7Vg</p>]]></description><guid isPermaLink="false">/archives/linuxyun-wei-an</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 02:00:48 GMT</pubDate></item><item><title><![CDATA[IT运维03：绩效指标]]></title><link>https://wiki.dgfygk.com/archives/ityun-wei-03-ji-xiao-zhi-biao</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B403%EF%BC%9A%E7%BB%A9%E6%95%88%E6%8C%87%E6%A0%87&amp;url=/archives/ityun-wei-03-ji-xiao-zhi-biao" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><em><span style="font-size: 20px">（一）开发绩效指标需要注意</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">关键工作</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 绩效评价指标的选择应该从关键工作及其主要影响因素入手，确定绩效评价指标的重点就在于“关键”两个字。</p>
<p style="">1、从需方角度考虑，运维的两个关键工作：</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一个是常规巡检，常规巡检工作完成的全面与否、是否符合质量要求、是否能够发现问题隐患、是否能够及时处理等等，是保障系统运行正常的手段之一，可以将巡检工作的交付成果作为绩效评价指标，常规巡检工作解决的是客户“安心”的问题。
 <br>
 &nbsp;&nbsp;&nbsp;&nbsp;另一个是响应，对客户需求的响应，多久能够开始，多久能够解决，不同等级的问题能否在SLA规定的时限内解决，响应工作的及时情况、完成质量以及过程记录都可以作为绩效评价指标，及时响应解决的是客户“舒心”的问题。</p>
<p style="">2、从供方角度看，包含需方考虑的内容之外，至少还需要包括：</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;组织的管理目标，即标准化管理，我们在实现标准化管理的过程中，哪些问题阻碍了、延缓了标准化的进程，这些问题就是我们需要设置的绩效评价指标，正如“杜拉拉升职记”书中所述：</p>
<blockquote>
 <p style="">在DB,你是先迈左脚,还是先迈右脚,都能在SOP(标准作业程序)里面找到答案。</p>
</blockquote>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;我们大多数不考虑左脚或者右脚的问题，有些组织也不具备这个条件，但是，他给我们提供了思路，我们可以根据组织的特点进行细化，细化到适合为止，至于是否适合，可以从先迈哪只脚对结果的影响进行分析，如果没影响，或者影响不大，那就没有理由进行评价，反之，就有必要作为绩效评价指标。</p>
<p style=""><strong><u><span fontsize="" color="">交付成果</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;所有流程的建设应该以交付成果为基础，我们可以通过月评、季评等方式对绩效评价指标进行分析，及时放弃那些失效的绩效评价指标，不能让失效的绩效评价指标成为组织的负担。</p>
<p style="">&nbsp;&nbsp;&nbsp; 开发关键绩效指标的专家→戴维.帕门特主张：</p>
<blockquote>
 <p style="">放弃年度规划流程，企业需要季度滚动计划，而不是年度规划。&nbsp;</p>
 <p style="">放弃每年一次或者每年两次的绩效评价，管理层应该至少每月一次给员工提供反馈意见。</p>
 <p style="">《关键绩效指标》</p>
</blockquote>
<p style=""><strong><u><span fontsize="" color="">客户需求</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 建立绩效评价指标要以客户为中心，以标准为基础，以目标为导向，最终实现企业的管理目标。
 <br>
 &nbsp;&nbsp;&nbsp; 准备好我们建立的绩效评价指标库，通过与客户进行深度沟通，寻找客户的关注点，确保绩效评价指标不脱离客户的实际需求。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（二）开发运维服务绩效评价指标</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 在开发运维服务绩效评价指标的过程中，离不开运维能力模型，如下图：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tuzi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">人员方面的绩效评价指标</span></u></strong></p>
<p style="">1、客户满意率</p>
<p style="">通过调查需方对服务人员的服务态度、技术水平、作业标准化的评价，了解客户的满意度，这个指标的影响范围和好处就不用多说了。</p>
<p style="">2、培训计划完成率</p>
<p style="">指标用于评价运维管理实施培训工作的完成情况，评价运维服务人员参训人次是否符合计划要求。培训工作可以间接影响到组织运维服务能力建设的进度，有利于提升运维人员的综合能力。</p>
<p style="">3、运维人员流失率</p>
<p style="">检验供方为保证服务协议得到连续实施而保持服务团队的稳定性。该指标会直接影响服务质量，降低客户满意度，管理者从成本角度出发，往往会延长从人员流失至招聘到岗的时间，实际上是在透支客户的信任，降低组织的服务标准，为了避免这种情况的发生，还有一个人员储备完成率的指标。</p>
<p style="">4、运维人员储备完成率</p>
<p style="">指标用于评价关键技术人员或骨干人员储备目标达成情况，人员储备不一定是超编配置，可以是平行储备，也可以是上下级储备，储备方案的可行性和及时更新就可以作为绩效评价指标。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dllj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">资源方面的绩效评价指标</span></u></strong></p>
<p style="">1、备品备件出库信息的有效率</p>
<p style="">通过评价备品备件的数据记录信息的有效性，可以确保得到真实、准确的备品备件使用记录信息。</p>
<p style="">2、备品备件可用率</p>
<p style="">评价备品备件的完好情况和可使用情况，确保在需要备品备件时，可以在规定的时间内提供完好的、可用的备件。</p>
<p style="">3、虚拟备件库响应及时率</p>
<p style="">评价第三方备件提供商响应的及时情况，一方面可以得到备件供应商的真实响应数据，作为选择供应商的依据之一，另一方面可以为组织做出响应承诺提供参考。</p>
<p style="">4、知识有效率</p>
<p style="">​<span fontsize="" color=""> </span>评价知识库的知识是否被运维组织有效利用，知识存在的目的是为了提升运维人员技术能力，降低解决问题的时长，促进高频问题的快速处理，所以，知识的有效性决定了它的价值。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lzjg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">过程方面的绩效评价指标</span></u></strong></p>
<p style="">1、实际交付率</p>
<p style="">通过服务过程记录评价需要交付工作是否完整，可以确保组织完全履行合同约定。</p>
<p style="">2、服务报告提交率</p>
<p style="">评价服务报告按照服务协议要求提交的比例，运维服务报告是运维服务过程的交付成果之一，直接体现了组织的运维能力。</p>
<p style="">3、过程记录完成率</p>
<p style="">评价过程记录按照合同要求完成的比例，可以确保运维服务过程规范化，为运维服务报告提供准确、可靠的基础数据。</p>
<p style="">4、信息安全事件数量</p>
<p style="">评价运维人员信息安全意识及遵章操作的情况，可以提高运维人员的信息安全事件的辨别能力，加深运维人员的信息安全意识。</p>
<p style="">5、SLA响应率</p>
<p style="">对现场服务请求的响应速度，该指标直接体现了服务要求的达成情况。</p>
<p style="">6、服务请求解决率</p>
<p style="">评价运维服务请求的解决比例，促进组织发现问题、解决问题。</p>
<p style="">7、服务请求解决及时率</p>
<p style="">评价运维服务请求解决的速度，促进组织发现影响及时性的因素，并寻求解决方案，提高及时率。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lgsz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">技术方面的绩效评价指标</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;涉猎不精。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">结语</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 关于运维绩效评价指标开发的理论知识，就不在这里进行总结了，推荐一本书《关键绩效指标》戴维.帕门特。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;上文关于绩效评价指标的内容，并未完全涵盖标准的要求，一些组织级的指标，不在运维掌控范围之内，就放弃了。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;欢迎讨论指正。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/5rsZyw7l_GvZb1uiVME-5g</p>]]></description><guid isPermaLink="false">/archives/ityun-wei-03-ji-xiao-zhi-biao</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:56:49 GMT</pubDate></item><item><title><![CDATA[IT运维04：服务级别]]></title><link>https://wiki.dgfygk.com/archives/ityun-wei-04-fu-wu-ji-bie</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B404%EF%BC%9A%E6%9C%8D%E5%8A%A1%E7%BA%A7%E5%88%AB&amp;url=/archives/ityun-wei-04-fu-wu-ji-bie" width="1" height="1" alt="" style="opacity:0;">
<p style="">服务级别管理是对IT运维服务的级别进行定义、记录和管理。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;定义服务级别可以确定关键服务目标及其度量值，可以用于需方周期性考核的关键指标，也可以用于供方的评估机制，从而及时发现并解决组织存在的问题，是标准化建设的必经之路。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;从运维管理角度出发，提取合同中与服务级别相关的信息如下：<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lowv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（一）识别干系人</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">需方干系人包括：</span></u></strong></p>
<p style="">1）主要管理人员</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;负责与供方对接运维相关事宜，制定运维实施过程中的一系列规则<em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（需方制度）</span></em>，对运维服务提出更加精细的要求，对运维服务提出评价，是供方的主要汇报对象。</p>
<p style="">2）系统管理人员</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;负责供方IT资产的管理，监督运维实施，对运维人员的技能、专业程度进行评价。</p>
<p style="">3）提出需求人员</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;提出用户需求或者事件需求，对需求响应结果进行确认，对运维人员的态度、仪容进行评价。</p>
<p style=""><strong><u><span fontsize="" color="">供方干系人包括：</span></u></strong></p>
<p style="">1）运维服务经理</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;负责与供方对接需求，响应供方提出的改进要求，监督运维团队落实并提升运维服务标准。</p>
<p style="">2）服务台</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;作为供方的对外服务窗口，负责对需方提出的需求进行响应、处理和反馈。</p>
<p style="">3）运维人员</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;负责运维服务实施工作。</p>
<p style=""><strong><u><span fontsize="" color="">第三方供应商：</span></u></strong></p>
<p style="">1）服务经理</p>
<p style="">2）技术人员</p>
<p style=""><strong><em><span style="font-size: 20px">（二）服务与子服务</span></em></strong>
 <br></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;前期通过对服务目录的整理，我们已知了服务内容有哪些，这一环节，我们需要将服务、子服务及服务类型整理出来，用于对应服务目标的度量值，也可以用于区分需方的服务范围，例如<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mzdq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（三）定义服务级别</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 服务级别协议（SLA）可以根据客户的需求来确定，也可以根据供方组织提供的服务来确定。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;我们可以把SLA理解为关键服务目标的集合，通过对事件的影响范围进行分析，确定服务优先级，对不同优先级的服务约定响应时限和解决时限。</p>
<p style=""><strong><u><span fontsize="" color="">以主机设备的故障分级为例：</span></u></strong></p>
<p style=""><span fontsize="" color="">1）重大故障</span></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;<span fontsize="" color="">优先级：非常高。</span></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;级别定义：设备故障导致业务中断8小时以上；核心业务受到入侵，危及重要数据，系统文件及门户网站页面被非法窜改，容易引发入侵扩散。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;响应时限：立即启动应急预案，5分钟内完成通报；30分钟内提出问题分析及解决方案。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;解决时限：2小时之内完成设备的故障处理，恢复设备正常运行状态。</p>
<p style=""><span fontsize="" color="">2）严重故障</span></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;<span fontsize="" color="">优先级：高。</span></p>
<p style="">&nbsp;&nbsp; 级别定义：设备故障导致业务中断1-8小时；核心业务受到入侵，未危及重要数据。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;响应时限：10分钟内完成通报。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;解决时限：4小时之内完成设备的故障处理，恢复设备正常运行状态。</p>
<p style=""><span fontsize="" color="">3）一般故障</span></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;<span fontsize="" color="">优先级：中、低。</span></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;级别定义：设备故障导致业务中断4小时以下；存在高危端口或系统漏洞，但无重大后果。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;响应时限：30分钟内完成通报。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;解决时限：4小时之内完成设备的故障处理，恢复设备正常运行状态。</p>
<p style=""><strong><u><span fontsize="" color="">确定解决时限应该考虑：</span></u></strong></p>
<p style="">1）关键设备的影响范围</p>
<p style="">2）备品备件情况</p>
<p style="">3）供应商情况</p>
<p style="">4）售后维修响应情况</p>
<p style="">5）需配合的单位情况</p>
<p style=""><strong><u><span fontsize="" color="">为服务分配SLA
    <br></span></u></strong></p>
<p style=""><span fontsize="" color="">如下图，主机设备维护服务对应SLA：2440
  <br></span></p>
<p style=""><span fontsize="" color="">SLT2440对应SLT：5分钟响应、10分钟响应、30分钟响应、2小时解决、4小时解决（高）、4小时解决（中、低）。</span><strong><span fontsize="" color="">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-epad.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><em><span style="font-size: 20px">（四）确定交付模式</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;交付模式可以确定运维团队和客户组织的服务关系，通过对客户组织进行识别，判断哪些客户组织会提出需求（创建工单），不同的客户组织是否需要不同的运维团队提供服务，通俗来讲就是客户提出需求，由哪些运维人员负责解决。<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fjpm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sfrc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp; 定义交付模式的同时，要求运维团队定义角色，例如由谁来负责分配工单、谁来负责牵头解决技术问题等。<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bknc.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">交付模式可以这样<strong><span fontsize="" color="">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tsid.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（五）结语</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 服务级别管理的内容不仅限于本文<em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（所以标题没有管理两个字）</span></em>，至少没有包括SLA的监控、对SLA的评估考核以及下一服务周期的优化，监控、考核、优化是SLA存在的真正意义，漂亮的SLA指标和流于形式的工作回顾对组织运维服务标准化没有任何好处，我们应该真实、真实、再真实！</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/jPZn-Ff7tCIuzwdUqInQ8g</p>]]></description><guid isPermaLink="false">/archives/ityun-wei-04-fu-wu-ji-bie</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:55:02 GMT</pubDate></item><item><title><![CDATA[IT运维05：配置管理]]></title><link>https://wiki.dgfygk.com/archives/ityun-wei-05-pei-zhi-guan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B405%EF%BC%9A%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86&amp;url=/archives/ityun-wei-05-pei-zhi-guan-li" width="1" height="1" alt="" style="opacity:0;">
<p style="">终于把IT运维服务中关于配置管理的内容整理了一遍，分享一下整理过程<em><span fontsize="" color="rgb(153, 153, 153)" style="color: rgb(153, 153, 153)">（见大标题）</span></em>，希望能够提供一些思路。主要做了两部分：</p>
<p style="">第一部分，设计配置管理过程控制的内容和方法，输出了《配置管理控制程序》；</p>
<p style="">第二部分，设计指导运维人员如何开展配置管理的工作手册，完成了《IT运维服务工作规程》之配置管理部分。</p>
<hr>
<hr>
<p style=""><strong>配置管理是什么？</strong></p>
<p style="">配置管理是对运维对象信息进行管理的过程，包括运维对象具体的配置项信息和运维对象之间关系的管理。</p>
<p style=""><strong>配置管理有什么用？</strong></p>
<p style="">1）运维对象的各种信息能够被识别和记录，记录是完整的，持续更新的；</p>
<p style="">2）可以为运维或其它管理活动提供准确的信息，提高工作效率，为实现SLA目标提供基础；</p>
<p style="">3）所有运维对象的信息可追溯，部分甚至可回退至某一状态。</p>
<p style=""><strong>配置管理做什么？<span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ltyq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 1.412empx">（一）确定配置管理的范围</span></em></strong></p>
<p style="">配置管理的范围主要是确定配置项有哪些，可以将配置项理解为运维对象中需要记录当前和历史状态的硬件、软件和文档。</p>
<p style=""><strong><u><span fontsize="" color="">确定配置项范围，可以考虑以下几点：</span></u></strong></p>
<ol>
 <li>
  <p style="">需要记录当前和历史状态的；</p>
 </li>
 <li>
  <p style="">需要追溯历史操作记录的；</p>
 </li>
 <li>
  <p style="">需要确定初始状态或回退至某一历史状态的；</p>
 </li>
 <li>
  <p style="">需要经常报告基本信息和状态的；</p>
 </li>
 <li>
  <p style="">需要被审计的；&nbsp;&nbsp;&nbsp;&nbsp;</p>
 </li>
 <li>
  <p style="">服务范围内的。</p>
 </li>
</ol>
<p style=""><strong><u><span fontsize="" color="">分类管理配置项：</span></u></strong></p>
<ol>
 <li>
  <p style="">a. 文档类配置项包括合同、运维服务体系文件、工具文档等，例如：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
 </li>
</ol>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vmoz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<ol start="2">
 <li>
  <p style="">b. 软件类配置项包括应用软件、中间件、运维工具等，例如：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
 </li>
</ol>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-abit.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<ol start="3">
 <li>
  <p style="">c. 硬件类配置项，按照组织的服务目录进行分类，例如：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
 </li>
</ol>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-held.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 1.412empx">（二）设计配置层次结构</span></em></strong></p>
<p style="">通过设计配置层次结构、命名规则，实现区别配置项的层次关系或从属关系等，层次结构如下图：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hxxd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><em><span style="font-size: 1.412empx">（三）确定配置项的关系、状态、命名规则
    <br></span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">配置项之间的关系</span></u></strong></p>
<ol>
 <li>
  <p style="">安装在……上</p>
  <ol>
   <li>
    <p style="">表示软件安装在硬件平台上，例如数据库安装在服务器上。</p>
   </li>
   <li>
    <p style="">表示硬件配置项的子CI关系，例如光模块安装在交换机上。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">依赖关系</p>
  <ol>
   <li>
    <p style="">表示软件之间的依赖关系，例如OA需要数据库才能正常运行。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">连接关系</p>
  <ol>
   <li>
    <p style="">表示硬件之间物理连接，例如服务器连接到交换机上。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">文档关联</p>
  <ol>
   <li>
    <p style="">表示文档类配置项与其它配置项之间的关系，例如与某硬件或软件相关的文档。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">接入</p>
  <ol>
   <li>
    <p style="">表示接入至某管理系统进行统一管理的硬件，例如摄像机接入至视频管理平台。</p>
   </li>
   <li>
    <p style="">表示接入至运维工具软件进行统一监测的硬件，例如服务器接入至运维平台。</p>
   </li>
   <li>
    <p style="">表示硬件接受管理或监测的所有软件。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">配置调试软件</p>
  <ol>
   <li>
    <p style="">表示硬件需要使用的运维工具软件，例如某特种设备需要使用的专用配置调试客户端。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">托管关系</p>
  <ol>
   <li>
    <p style="">表示机柜安装的硬件设备，例如1#机柜安装的交换机、路由器。</p>
   </li>
  </ol>
 </li>
</ol>
<p style=""><strong><u><span fontsize="" color="">配置项的状态</span></u></strong></p>
<ol>
 <li>
  <p style="">文档状态</p>
  <ol>
   <li>
    <p style="">已发布，文档审核通过并进行发布。</p>
   </li>
   <li>
    <p style="">审核中，文档草稿已经提交审核，尚未形成审核结果。</p>
   </li>
   <li>
    <p style="">草稿，文档草稿拟定中。</p>
   </li>
   <li>
    <p style="">废弃，文档已废弃。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">软件状态</p>
  <ol>
   <li>
    <p style="">启用，软件测试通过并发布，投入使用。</p>
   </li>
   <li>
    <p style="">测试，软件测试中，尚未提交测试报告。</p>
   </li>
   <li>
    <p style="">停用，软件停用状态。</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">硬件状态</p>
  <ol>
   <li>
    <p style="">启用，硬件投入使用中，工作状态正常。</p>
   </li>
   <li>
    <p style="">闲置，硬件功能正常，未投入使用。</p>
   </li>
   <li>
    <p style="">故障，硬件处于故障状态，无法正常工作。</p>
   </li>
   <li>
    <p style="">在线，指硬件设备接入网络正常。</p>
   </li>
   <li>
    <p style="">离线，指硬件设备未接入网络，设备工作状态不详。</p>
   </li>
  </ol>
 </li>
</ol>
<p style=""><strong><u><span fontsize="" color="">设计命名规则</span></u></strong></p>
<p style="">按照配置项类型分别定义，每一类配置项又细分为不同类型，目的是通过名称能够快速理解配置项的属性和关系信息。</p>
<ol>
 <li>
  <p style="">文档类包括:</p>
  <ol>
   <li>
    <p style="">合同类文档</p>
   </li>
   <li>
    <p style="">服务体系类文档</p>
   </li>
   <li>
    <p style="">工具类文档</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">软件类包括:</p>
  <ol>
   <li>
    <p style="">通用软件</p>
   </li>
   <li>
    <p style="">运维工具软件</p>
   </li>
  </ol>
 </li>
 <li>
  <p style="">硬件类包括:</p>
  <ol>
   <li>
    <p style="">需方资产或安装在现场的供方资产</p>
   </li>
   <li>
    <p style="">需方资产或安装在现场的供方资产的子CI</p>
   </li>
   <li>
    <p style="">非现场的供方资产</p>
   </li>
   <li>
    <p style="">非现场的供方资产的子CI</p>
   </li>
  </ol>
 </li>
</ol>
<p style="">分享一个例子<em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（硬件类a）</span></em>：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-szve.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">配置项命名注意事项：</p>
<ol>
 <li>
  <p style="">名称不能太长</p>
 </li>
 <li>
  <p style="">需要有具体的含义解释</p>
 </li>
 <li>
  <p style="">能够反映从属关系和关联关系</p>
 </li>
 <li>
  <p style="">应考虑未来的可扩展性</p>
 </li>
</ol>
<p style="text-align: center; "><strong><em><span style="font-size: 1.412empx">（四）确定配置项的属性</span></em></strong></p>
<p style="">接下来需要针对不同的配置项定义其属性及关联信息，可以按照配置项的类型和功能分别进行定义。</p>
<p style="">文档配置项的属性：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ztox.png&amp;size=m" width="100%" height="100%" style="display: inline-block">
</p>
<p style="">软件配置项的属性：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zwfq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">部分硬件配置项的属性：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fwyf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dgun.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-najj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">服务对象种类越多，需要定义的属性信息就越多，好在都是一次性工作。<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xnwh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><em><span style="font-size: 1.412empx">（五）建立配置项</span></em></strong></p>
<p style=""><strong>第一步，编制计划</strong></p>
<p style="">计划内容至少应该包括以下内容：</p>
<ol>
 <li>
  <p style="">合同、服务协议中规定的维护对象清单；<em><span fontsize="" color="rgb(153, 153, 153)" style="color: rgb(153, 153, 153)">（获取途径：复制粘贴）</span></em></p>
 </li>
 <li>
  <p style="">运维对象的定义与分类；<em><span fontsize="" color="rgb(153, 153, 153)" style="color: rgb(153, 153, 153)">（获取途径：复制粘贴）</span></em></p>
 </li>
 <li>
  <p style="">配置数据的规范要求；<em><span fontsize="" color="rgb(153, 153, 153)" style="color: rgb(153, 153, 153)">（获取途径：复制粘贴）</span></em></p>
 </li>
 <li>
  <p style="">人员配置及分工；<em><span fontsize="" color="rgb(153, 153, 153)" style="color: rgb(153, 153, 153)">（自己编）</span></em></p>
 </li>
 <li>
  <p style="">时间计划及交付成果。<em><span fontsize="" color="rgb(153, 153, 153)" style="color: rgb(153, 153, 153)">（自己编）</span></em></p>
 </li>
</ol>
<p style=""><strong>第二步，获取IT资产台账</strong></p>
<p style="">与需方沟通并获取客户IT资产台账，一般的IT资产台账都涉及到保密的问题，但是如果运维人员不了解运维对象，就无法兑现承诺的SLA，况且经过日积月累，运维人员终究会获取到运维对象的信息，所以说不存在对运维人员或组织保密的做法。</p>
<p style="">可靠的做法是需方与供方组织签订保密协议，要求需方组织与员工签署保密协议，并提供切实有效的保密措施，而不是“范本”。</p>
<p style="">将已获取的IT资产台账与合同中规定的维护对象清单对比，确定最终的配置项清单初稿。</p>
<p style=""><strong>第三步，收集配置项信息</strong></p>
<p style="">在收集配置项的属性信息和关联信息之前，事先编制好用于统计配置项信息的台账范本，如下图：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xbcx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ulma.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">台账范本应该根据配置项的属性信息进行编制，以确保导入CMDB的配置项信息的完整性。</p>
<p style="">如果组织有自己的ITSM系统，可以自动生成台账模板，便于后续批量导入到系统的CMDB中。</p>
<p style=""><strong>第四步，给配置项命名</strong></p>
<p style="">配置项命名的工作可以分为两部分：</p>
<p style="">第一部分是前置工作，对不同类型的设备，我们可以在工作规程中对命名信息进行定义；<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mkra.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">第二部分是将编号部分筛选后序列填充即可，尽量减少由于命名带来的工作量。</p>
<p style=""><strong>第五步，配置项审核</strong></p>
<p style="">配置审核工作由配置经理组织、配置管理员实施、配置项负责人配合进行，审核工作应以系统、单位、区域为单位进行抽查<em><span fontsize="" color="rgb(153, 153, 153)" style="color: rgb(153, 153, 153)">（量大抽查、量小全查）</span></em>，漏查的列入下次审核工作中，总之，要对全部新建的配置项进行审核，确保信息准确。
 <br>
 <br>
 审核的内容至少应该包括：</p>
<ol>
 <li>
  <p style="">配置项信息准确、完整并与实际情况一致；</p>
 </li>
 <li>
  <p style="">配置项的状态描述准确；</p>
 </li>
 <li>
  <p style="">配置项之间的关系描述一致。</p>
 </li>
</ol>
<hr>
<p style="text-align: center; "><strong><em><span style="font-size: 1.412empx">结语</span></em></strong></p>
<p style="">通过实际操作发现实施配置管理的最大阻力来自于人，即供方运维人员、需方的主管人员等相关干系人，大家对配置管理的认识比较模糊，不理解其内容和作用，再加上各方的关注点不同，导致这项工作很容易被忽视。</p>
<p style="">所以，我觉得它需要一个适宜的环境，才能发挥它应有的作用。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/8A3FVf0rEk_vopWhcPgH0A</p>]]></description><guid isPermaLink="false">/archives/ityun-wei-05-pei-zhi-guan-li</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:53:27 GMT</pubDate></item><item><title><![CDATA[IT运维文档：IT运维服务工作规程及服务报告]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B4%E6%96%87%E6%A1%A3%EF%BC%9AIT%E8%BF%90%E7%BB%B4%E6%9C%8D%E5%8A%A1%E5%B7%A5%E4%BD%9C%E8%A7%84%E7%A8%8B%E5%8F%8A%E6%9C%8D%E5%8A%A1%E6%8A%A5%E5%91%8A&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style="">有些朋友私信我：需要IT运维相关的文档，这个范围的确是太大了，如果是一整套，我也不全，我是边研究、边编辑、边分享。当然，我有要完成的计划目标，以下目录就是我<strong><u><span fontsize="" color="">将要完成或完善</span></u></strong>的所有文档：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ktly.jpg&amp;size=m" width="991px" height="1752px" style="display: inline-block"></p>
<p style=""></p>
<p style="">在编辑文档的过程中，参考了一些书籍和网络的资料，所以会存在一定的“复制和粘贴”，当然不是无脑粘贴，还是需要结合实际工作情况进行修改和取舍。</p>
<p style="">我的想法是取之于网络、回馈于网络，希望能对大家有一点点帮助，欢迎大家提出修改意见，使其更加完善。</p>
<p style=""><strong><u><span fontsize="" color="">IT运维服务工作规程</span></u></strong></p>
<p style="">偏向于硬件的基础运维，所以文档中不包含软件运维的内容，目录内容如下：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ohgj.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ywfg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yrbs.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">注：其中的巡检记录均在更新过程中，更新完成后上传。</p>
<p style=""><strong><u><span fontsize="" color="">服务报告</span></u></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kwac.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">下载方式：
    <br></span></u></strong></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;发消息“规程”。</span></p>
<p style=""><span fontsize="" color="">原文链接：</span>https://mp.weixin.qq.com/s/WTD8qF9hEB0LZq8rDYeR7w</p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:47:54 GMT</pubDate></item><item><title><![CDATA[Linux运维：shell实现拒绝恶意IP的ssh访问]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E8%BF%90%E7%BB%B4%EF%BC%9Ashell%E5%AE%9E%E7%8E%B0%E6%8B%92%E7%BB%9D%E6%81%B6%E6%84%8FIP%E7%9A%84ssh%E8%AE%BF%E9%97%AE&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style="">&nbsp;&nbsp;&nbsp;编写shell脚本，在secure日志文件中查找失败登录20次以上的记录，将其IP地址添加到hosts.deny文件中，实现拒绝该IP的访问。</p>
<p style=""><code>1、创建shell脚本文件：auto_deny_failedIP.sh</code></p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>[root@SCSir ~]# vi auto_deny_failedIP.sh</code></pre>
<p style=""><code>插入以下内容，wq保存退出。</code></p>
<pre><code>#!/bin/bash#Auto deny ssh failed IP address#By author SCSir 202304
for&nbsp;i&nbsp;in&nbsp;$(grep&nbsp;"Failed&nbsp;password"&nbsp;/var/log/secure|awk&nbsp;'{print&nbsp;$(NF-3)}'|sort|uniq&nbsp;-c|sort&nbsp;-nr|awk&nbsp;'{if($1&gt;=20)&nbsp;print&nbsp;$2}')do#判断hosts.deny是否包含变量$i的值（IP地址），避免重复IP  cat /etc/hosts.deny |grep $i &gt;/dev/null  if    [ $? -ne 0 ]; then      echo "sshd:$i:deny" &gt;&gt; /etc/hosts.deny  fidone</code></pre>
<p style=""><strong>2、添加计划任务</strong></p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>[root@SCSir&nbsp;~]#&nbsp;crontab&nbsp;-e</code></pre>
<p style="">或者</p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>[root@SCSir ~]# vi /var/spool/cron/root</code></pre>
<p style="">插入以下内容：每日23:59执行auto_deny_failedIP.sh，wq保存退出。</p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>59&nbsp;23&nbsp;*&nbsp;*&nbsp;*&nbsp;sh&nbsp;/root/a_shell/auto_deny_failedIP.sh</code></pre>
<p style="">或者每隔31分钟执行一次。</p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>*/31&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;sh&nbsp;/root/a_shell/auto_deny_failedIP.sh</code></pre>
<hr>
<p style="">
 <br>
 注意：不支持TCP Wrappers函数功能的软件程序无法使用hosts.deny的设置值。
</p>
<p style="">检查方法：</p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>[root@SCSir ~]# ldd $(which sshd)</code></pre>
<pre><code>返回内容如果没有libwrap.so函数库文件就不支持，有就支持。</code></pre>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:46:16 GMT</pubDate></item><item><title><![CDATA[IT运维文档：配置管理控制程序及制度]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B4%E6%96%87%E6%A1%A3%EF%BC%9A%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86%E6%8E%A7%E5%88%B6%E7%A8%8B%E5%BA%8F%E5%8F%8A%E5%88%B6%E5%BA%A6&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><u><span fontsize="" color="">客户IT资产运维管理制度</span></u></strong></p>
<ol>
 <li>
  <p style="">总则</p>
 </li>
 <li>
  <p style="">职责和范围</p>
 </li>
 <li>
  <p style="">统计建档</p>
 </li>
 <li>
  <p style="">档案更新</p>
 </li>
 <li>
  <p style="">资料管理</p>
 </li>
 <li>
  <p style="">归档管理</p>
 </li>
 <li>
  <p style="">附则</p>
 </li>
</ol>
<p style=""><strong><u><span fontsize="" color="">配置管理控制程序</span></u></strong></p>
<ul>
 <li>
  <p style=""><span fontsize="" color="">1．概述</span><span fontsize="" color="windowtext" style="color: windowtext">... 5</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">2．术语</span><span fontsize="" color="windowtext" style="color: windowtext">... 5</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">3．角色与职责</span><span fontsize="" color="windowtext" style="color: windowtext">... 5</span></p>
  <ul>
   <li>
    <p style=""><span style="font-size: 17px">3.1</span><span fontsize="" color="">配置经理</span><span fontsize="" color="windowtext" style="color: windowtext">... 5</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">3.2</span><span fontsize="" color="">配置管理员</span><span fontsize="" color="windowtext" style="color: windowtext">... 6</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">3.3</span><span fontsize="" color="">配置项负责人</span><span fontsize="" color="windowtext" style="color: windowtext">... 6</span></p>
   </li>
  </ul>
 </li>
 <li>
  <p style=""><span fontsize="" color="">4．配置管理流程</span><span fontsize="" color="windowtext" style="color: windowtext">... 7</span></p>
  <ul>
   <li>
    <p style=""><span style="font-size: 17px">4.1</span><span fontsize="" color="">流程图</span><span fontsize="" color="windowtext" style="color: windowtext">... 7</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">4.2</span><span fontsize="" color="">配置管理规划</span><span fontsize="" color="windowtext" style="color: windowtext">... 7</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">4.3</span><span fontsize="" color="">识别配置项</span><span fontsize="" color="windowtext" style="color: windowtext">... 7</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">4.4</span><span fontsize="" color="">建立</span>CMDB<span fontsize="" color="windowtext" style="color: windowtext">. 8</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">4.5</span><span fontsize="" color="">配置项控制</span><span fontsize="" color="windowtext" style="color: windowtext">... 8</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">4.6</span><span fontsize="" color="">配置项审核</span><span fontsize="" color="windowtext" style="color: windowtext">... 8</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">4.7</span><span fontsize="" color="">配置状态报告</span><span fontsize="" color="windowtext" style="color: windowtext">... 8</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">4.8</span><span fontsize="" color="">配置管理回顾及改进</span><span fontsize="" color="windowtext" style="color: windowtext">... 8</span></p>
   </li>
  </ul>
 </li>
 <li>
  <p style=""><span fontsize="" color="">5．配置管理范围</span><span fontsize="" color="windowtext" style="color: windowtext">... 9</span></p>
  <ul>
   <li>
    <p style=""><span style="font-size: 17px">5.1</span><span fontsize="" color="">文档</span><span fontsize="" color="windowtext" style="color: windowtext">... 9</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">5.2</span><span fontsize="" color="">软件</span><span fontsize="" color="windowtext" style="color: windowtext">... 9</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">5.3</span><span fontsize="" color="">硬件</span><span fontsize="" color="windowtext" style="color: windowtext">... 10</span></p>
   </li>
  </ul>
 </li>
 <li>
  <p style=""><span fontsize="" color="">6．配置项属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 12</span></p>
  <ul>
   <li>
    <p style=""><span style="font-size: 17px">6.1</span><span fontsize="" color="">文档配置项的属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 12</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">6.2</span><span fontsize="" color="">软件配置项的属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 12</span></p>
    <ul>
     <li>
      <p style=""><span style="font-size: 17px">6.2.1 WEB</span><span fontsize="" color="">应用属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 12</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.2.2 </span><span fontsize="" color="">中间件属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 13</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.2.3 </span><span fontsize="" color="">应用软件、操作系统、运维工具属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 14</span></p>
     </li>
    </ul>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">6.3</span><span fontsize="" color="">硬件配置项的属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 15</span></p>
    <ul>
     <li>
      <p style=""><span style="font-size: 17px">6.3.1 </span><span fontsize="" color="">网络设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 15</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.2 </span><span fontsize="" color="">主机设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 16</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.3 </span><span fontsize="" color="">存储设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 17</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.4 </span><span fontsize="" color="">桌面终端设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 18</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.5 </span><span fontsize="" color="">办公设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 19</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.6 </span><span fontsize="" color="">视频监控设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 20</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.7 </span><span fontsize="" color="">出入口、报警、环控主机设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 21</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.8 </span><span fontsize="" color="">机柜属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 22</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.9 IP</span><span fontsize="" color="">电话设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 23</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.10 </span><span fontsize="" color="">功放、</span>CD<span fontsize="" color="">机属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 23</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.11 </span><span fontsize="" color="">解码器、视频矩阵属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 24</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.12 MCU</span><span fontsize="" color="">、中控主机属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 25</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">6.3.13 </span><span fontsize="" color="">通用设备属性</span><span fontsize="" color="windowtext" style="color: windowtext">... 26</span></p>
     </li>
    </ul>
   </li>
  </ul>
 </li>
 <li>
  <p style=""><span fontsize="" color="">7．配置项管理</span><span fontsize="" color="windowtext" style="color: windowtext">... 27</span></p>
  <ul>
   <li>
    <p style=""><span style="font-size: 17px">7.1</span><span fontsize="" color="">配置层次结构</span><span fontsize="" color="windowtext" style="color: windowtext">... 27</span></p>
    <ul>
     <li>
      <p style=""><span style="font-size: 17px">7.1.1</span><span fontsize="" color="">硬件层次结构</span><span fontsize="" color="windowtext" style="color: windowtext">... 27</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.1.2</span><span fontsize="" color="">软件层次结构</span><span fontsize="" color="windowtext" style="color: windowtext">... 28</span></p>
     </li>
    </ul>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">7.2</span><span fontsize="" color="">配置项控制</span><span fontsize="" color="windowtext" style="color: windowtext">... 30</span></p>
    <ul>
     <li>
      <p style=""><span style="font-size: 17px">7.2.1</span><span fontsize="" color="">新建配置项</span><span fontsize="" color="windowtext" style="color: windowtext">... 30</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.2.2</span><span fontsize="" color="">变更配置项</span><span fontsize="" color="windowtext" style="color: windowtext">... 30</span></p>
     </li>
    </ul>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">7.3</span><span fontsize="" color="">配置基线</span><span fontsize="" color="windowtext" style="color: windowtext">... 30</span></p>
    <ul>
     <li>
      <p style=""><span style="font-size: 17px">7.3.1</span><span fontsize="" color="">配置基线内容</span><span fontsize="" color="windowtext" style="color: windowtext">... 30</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.3.2</span><span fontsize="" color="">配置基线类型</span><span fontsize="" color="windowtext" style="color: windowtext">... 31</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.3.3</span><span fontsize="" color="">配置基线名称</span><span fontsize="" color="windowtext" style="color: windowtext">... 31</span></p>
     </li>
    </ul>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">7.4</span><span fontsize="" color="">配置项状态</span><span fontsize="" color="windowtext" style="color: windowtext">... 31</span></p>
    <ul>
     <li>
      <p style=""><span style="font-size: 17px">7.4.1</span><span fontsize="" color="">文档状态</span><span fontsize="" color="windowtext" style="color: windowtext">... 31</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.4.2</span><span fontsize="" color="">软件状态</span><span fontsize="" color="windowtext" style="color: windowtext">... 31</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.4.3</span><span fontsize="" color="">硬件状态</span><span fontsize="" color="windowtext" style="color: windowtext">... 31</span></p>
     </li>
    </ul>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">7.5</span><span fontsize="" color="">配置组</span><span fontsize="" color="windowtext" style="color: windowtext">... 32</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">7.6</span><span fontsize="" color="">配置项的命名规则</span><span fontsize="" color="windowtext" style="color: windowtext">... 32</span></p>
    <ul>
     <li>
      <p style=""><span style="font-size: 17px">7.6.1</span><span fontsize="" color="">文档类命名结构</span><span fontsize="" color="windowtext" style="color: windowtext">... 32</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.6.2</span><span fontsize="" color="">软件类命名结构</span><span fontsize="" color="windowtext" style="color: windowtext">... 33</span></p>
     </li>
     <li>
      <p style=""><span style="font-size: 17px">7.6.3</span><span fontsize="" color="">硬件类命名结构</span><span fontsize="" color="windowtext" style="color: windowtext">... 33</span></p>
     </li>
    </ul>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">7.7</span><span fontsize="" color="">配置项标识</span><span fontsize="" color="windowtext" style="color: windowtext">... 34</span></p>
   </li>
  </ul>
 </li>
 <li>
  <p style=""><span fontsize="" color="">8．配置状态报告</span><span fontsize="" color="windowtext" style="color: windowtext">... 34</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">9．配置审核</span><span fontsize="" color="windowtext" style="color: windowtext">... 35</span></p>
  <ul>
   <li>
    <p style=""><span style="font-size: 17px">9.1</span><span fontsize="" color="">发起审核</span><span fontsize="" color="windowtext" style="color: windowtext">... 35</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">9.2</span><span fontsize="" color="">配置审核的要点</span><span fontsize="" color="windowtext" style="color: windowtext">... 35</span></p>
   </li>
   <li>
    <p style=""><span style="font-size: 17px">9.3</span><span fontsize="" color="">审计状态说明</span><span fontsize="" color="windowtext" style="color: windowtext">... 36</span></p>
   </li>
  </ul>
 </li>
 <li>
  <p style=""><span style="font-size: 17px">10．配置管理回顾及改进</span><span style="font-size: 17px; color: windowtext">&nbsp;&nbsp;&nbsp; 36</span></p>
 </li>
</ul>
<p style=""><strong><u><span fontsize="" color="">台账范本</span></u></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ztgs.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><u><span fontsize="" color="">下载方式：
    <br></span></u></strong></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;发消息“配置”。</span></p>
<p style=""></p>
<p style="">原文链接：https://mp.weixin.qq.com/s/xkFx1BClExu17FL6sR8ZhQ</p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:44:37 GMT</pubDate></item><item><title><![CDATA[IT运维文档：IT运维服务目录]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B4%E6%96%87%E6%A1%A3%EF%BC%9AIT%E8%BF%90%E7%BB%B4%E6%9C%8D%E5%8A%A1%E7%9B%AE%E5%BD%95&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style="">之前对服务目录相关进行了分享（<em><span style="font-size: 12ptpx; color: rgb(136, 136, 136)">需要多了解一些的朋友可以看看</span></em><span style="font-size: 12ptpx">“</span><a href="http://mp.weixin.qq.com/s?__biz=Mzg3Nzg3NTc2Ng==&amp;mid=2247483804&amp;idx=1&amp;sn=bdfa46f5c49793306eb01e9658b74542&amp;chksm=cf1d06c5f86a8fd38581382014f78fea5b90fcca51bab3e5721b21cd37b0034c43bd34bba125&amp;scene=21#wechat_redirect" target="_blank">IT运维01：服务目录</a><span style="font-size: 12ptpx">”</span>），但是没有提供下载链接，原因是觉得手中的模板需要优化的地方太多，然而，整个运维服务体系需要做的工作更多，所以，还是先给大家分享了，有需要的先用着，接下来我会查找资料进行优化，也希望大家能够提供宝贵的建议，给更多的运维人工作提供便利。</p>
<p style="text-align: center; "><span style="font-size: 16ptpx">目</span><span fontsize="" color="">&nbsp; </span><span style="font-size: 16ptpx">录</span></p>
<p style="">1 服务目录列表... 3</p>
<p style="">1.1 基础环境运维服务... 3</p>
<p style="">1.2 硬件运维服务... 5</p>
<p style="">1.3 软件运维服务... 10</p>
<p style="">2 基础环境运维服务... 11</p>
<p style="">2.1 安防系统... 11</p>
<p style="">2.1.1 视频监控设备... 11</p>
<p style="">2.1.2 出入口控制系统设备... 16</p>
<p style="">2.1.3 报警系统设备... 20</p>
<p style="">2.2 机房设备... 23</p>
<p style="">2.2.1 机房设备... 23</p>
<p style="">2.2.2 不间断供电设备... 26</p>
<p style="">2.2.3 环境及设备监控系统... 28</p>
<p style="">3 硬件运维服务... 29</p>
<p style="">3.1 网络运维服务... 29</p>
<p style="">3.1.1 网络设备... 29</p>
<p style="">3.1.2 传输设备... 34</p>
<p style="">3.2 主机运维服务... 37</p>
<p style="">3.2.1 PC服务器、小型机... 37</p>
<p style="">3.3 存储运维服务... 40</p>
<p style="">3.3.1 存储设备... 40</p>
<p style="">3.4 桌面运维服务... 43</p>
<p style="">3.4.1 桌面终端... 43</p>
<p style="">3.4.2 办公设备... 46</p>
<p style="">3.5 其他硬件运维服务... 49</p>
<p style="">3.5.1 音频设备... 49</p>
<p style="">3.5.2 显示设备... 52</p>
<p style="">3.5.3 审讯录音录像设备... 55</p>
<p style="">3.5.4 会议系统设备... 58</p>
<p style="">4 软件运维服务... 61</p>
<p style="">4.1 应用软件运维服务... 61</p>
<p style="">4.1.1 桌面终端操作系统或相关软件重部署及调试服务... 61</p>
<p style="">4.1.2 PC服务器、小型机系统重部署及调试服务... 61</p>
<p style="">4.1.3 应用软件系统排障服务... 62</p>
<p style="">4.1.4 应用软件系统评估服务<span fontsize="" color="">... </span><span fontsize="" color="windowtext" style="color: windowtext">62</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ihwv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rggc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; ">原文链接：https://mp.weixin.qq.com/s/HCtMm6dD_RuLDB7G2K5LxA</p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:43:28 GMT</pubDate></item><item><title><![CDATA[IT运维06：服务方案]]></title><link>https://wiki.dgfygk.com/archives/ityun-wei-06-fu-wu-fang-an</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IT%E8%BF%90%E7%BB%B406%EF%BC%9A%E6%9C%8D%E5%8A%A1%E6%96%B9%E6%A1%88&amp;url=/archives/ityun-wei-06-fu-wu-fang-an" width="1" height="1" alt="" style="opacity:0;">
<p style="">好的方案！好的方案！</p>
<p style="text-align: center; ">能够解决客户问题的方案是好的方案！</p>
<p style="text-align: center; ">能够解决客户问题且能够落地的方案是好的方案！</p>
<p style="text-align: center; ">能够解决客户问题且能够落地，又能保持合理的经济效益是好的方案！</p>
<p style="text-align: center; ">方案 ＝&amp;≠ 复制+粘贴</p>
<hr>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（一）干系人在想什么？</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">客户在想什么？</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;先说明一下，客户和用户是有区别的：一个是花钱的，一个是使用的，这两个角色由于所处位置的不同，思考的问题也不一样，关注的重点更是不同。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;我们面对不同角色的客户需要考虑他们的关注点，有针对性的介绍我们的服务方案，如果客户想吃土豆丝，偏要上盘小龙虾，结果往往会事与愿违。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;即便是同一角色的客户，他们的想法也有可能大相径庭，这与地域、环境以及个人都有关系，但是，无论在什么情况下，几乎都会有一个共同的关注点：<strong>钱花哪了？</strong><em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（不关注这个可能会被坑）</span></em></p>
<p style="">&nbsp;&nbsp;&nbsp; 想要知道“钱花哪了”，大概率会牵出来一堆的国标、地标、行标<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tusa.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;关于这些标准，本文就不展开了，但是，<strong>如果</strong>我们希望通过方案让客户了解“他的钱到底花在哪了”、“为什么这么花”等一系列问题，就需要我们多少都要了解一些关于成本度量方面的标准，以便于有据可依。</p>
<p style="">&nbsp;&nbsp;&nbsp; 另外，当客户对预算组成有疑问时，我们需要给出合理的回复，同时方案也需要体现出相应的预算组成，让客户能够清楚的了解到，我们所提供的直接的和间接的人力、物力等等这些看得见的、看不见的服务内容，让方案更加饱满、充实，让客户放心。
 <br></p>
<p style=""><strong><u><span fontsize="" color="">用户在想什么？</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp; 用户关心的是<strong>你能做什么？</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;比如，我能做这些：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vwrv.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp; 比如，我是这么做：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sizm.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;比如，计划这么做：<strong><span fontsize="" color="rgb(217, 33, 66)" style="color: rgb(217, 33, 66)">↓↓</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zigs.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;建议将每一章的内容都浓缩成一句话，比如关于“第三章 运维服务内容”总结成一句话：“我们能够保证机房24小时专人值守，响应时间不会超过5分钟，影响业务连续性的问题，解决时间不会超过24小时，每日、每周、每月、每季度都会进行预防性巡检，并提供相应的巡检报告。”</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;以上总结的这句话好不好并不重要，重要的是你总结的需要尽量好，通过我们总结的这句话，可以延伸出更多的问题，例如：“影响业务连续性的问题考虑的是否全面？”、“如何保证24小时之内解决问题？”、“预防性巡检都检查什么？”、“巡检报告会不会流于形式？”等，用户提出疑问的时候就是他阅读方案和深入沟通的时候。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;通过陈述抛出一些列问题，促使用户发现并提出问题，切入用户的关注点，接下来的沟通就会事半功倍。</p>
<p style=""><strong><u><span fontsize="" color="">老板在想什么？</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;老板关心的是<strong>赚多少钱？</strong><em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（其它战略除外）</span></em></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;“赚多少钱？”言外之意就是能不能少上几个人、能不能少提供备件、能不能用省钱的方式解决问题。当然，有远见的老板不会以降低服务标准的方式获取更大的利益。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;能不能少上几个人不一定，但是可能做到少上几个“高人”；能不能少提供备件不好说，但是备件复用是可能的（也有可能不备到现场）；能不能用省钱的方式解决问题，答案是一定能，就看老板你的“饼”是怎么画的了。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一套好的方案可以实现企业利益的最大化，也可以为客户节省预算<em><span fontsize="" color="rgb(136, 136, 136)" style="color: rgb(136, 136, 136)">（此节省非彼节省）</span></em>，方案的价值远非只是复制粘贴的饭囊衣架，只可惜太多的这种价值被领导们无情的忽视了，当然，工作是为自己而做，所以，其它的就不重要了。
 <br></p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（二）几套方案？</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一份几十页甚至几百页的方案给谁看？</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;想必是作者写三天，读者只看3分钟。问题来了，不看还需要认真写吗？当然需要，因为你不确定他看到了什么。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;技术型的管理者可能会通篇浏览，对感兴趣的部分详细看，而管理型的管理者可能连标题都不会看。</p>
<p style="">&nbsp;&nbsp;&nbsp; 前者可能存在较强的求知欲，方案的大小标题尽量使用专业术语，求知欲会诱导他去详细阅读，以便能够了解我们的专业程度。</p>
<p style="">&nbsp;&nbsp;&nbsp; 后者可能更关心价值，如果需要你将方案浓缩成10~20页PPT，给你5~10分钟，你能否把你的方案讲明白？</p>
<p style="">&nbsp;&nbsp;&nbsp; 一本专业的、详细的服务方案，一份内容充实、表达清晰的PPT方案。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（三）做什么？怎么做？</span></em></strong></p>
<p style=""><strong><u><span fontsize="" color="">做什么</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;做什么主要体现的是我们能够提供的服务有哪些，去服务目录里寻找，并策略的描述出来。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一定要弄清楚，我们提供的服务能够解决客户什么问题，契合度越高，越容易被接受。</p>
<p style=""><strong><u><span fontsize="" color="">怎么做</span></u></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;怎么做是你为兑现承诺而做出的一系列的努力，曾经有一个用户这样说：漂亮的文档很多见，关键是看能否实现。我同意他的关注点，运维服务项目的特点注定了不是一个人或组织经过努力就可以实现目标的，供方和需方的目标一致很重要。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;能不能走上标准化这条路，还是要看领导的远见和组织的决心。</p>
<p style="text-align: center; "><strong><em><span style="font-size: 20px">（四）结语</span></em></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp; 方案是不是复制粘贴不重要，重要的是方案解决了多方干系人的问题，突出了组织的特点。好的方案说起来容易做起来难，但是我们仍然需要不断摸索前进，总不能因为梦想遥不可及就轻言放弃，专注十年，也许你就是下一个成功者。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/BXWGIDKsgV2Q8QO1B5oXCg</p>]]></description><guid isPermaLink="false">/archives/ityun-wei-06-fu-wu-fang-an</guid><dc:creator>黄健</dc:creator><category>IT运维</category><pubDate>Sat, 12 Oct 2024 01:39:54 GMT</pubDate></item><item><title><![CDATA[重学SpringBoot3-集成Redis（五）之布隆过滤器]]></title><link>https://wiki.dgfygk.com/archives/bbfc46d9-1d00-44e4-a44b-d465a644b007</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%87%8D%E5%AD%A6SpringBoot3-%E9%9B%86%E6%88%90Redis%EF%BC%88%E4%BA%94%EF%BC%89%E4%B9%8B%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8&amp;url=/archives/bbfc46d9-1d00-44e4-a44b-d465a644b007" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p><strong>更多SpringBoot3内容请关注我的专栏：<a href="https://blog.csdn.net/u014390502/category_12592157.html">《SpringBoot3》</a></strong>
  <br>
  期待您的点赞👍收藏⭐评论✍</p>
</blockquote>
<p>
 <br>
</p>
<h4>重学SpringBoot3-集成Redis（五）之布隆过滤器</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1__9">1. 什么是布隆过滤器？</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_11">基本概念</a></li>
 <li><a href="https://wiki.dgfygk.com#_23">适用场景</a></li>
 <li><a href="https://wiki.dgfygk.com#2__Redis__29">2. 使用 Redis 实现布隆过滤器</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_33">项目依赖</a></li>
 <li><a href="https://wiki.dgfygk.com#Redis__51">Redis 配置</a></li>
 <li><a href="https://wiki.dgfygk.com#3__103">3. 创建布隆过滤器服务</a></li>
 <li><a href="https://wiki.dgfygk.com#4__147">4. 布隆过滤器的初始化</a></li>
 <li><a href="https://wiki.dgfygk.com#5__202">5. 使用布隆过滤器进行缓存穿透防护</a></li>
 <li><a href="https://wiki.dgfygk.com#6__302">6. 测试效果</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#61__344">6.1. 启动项目</a></li>
 <li><a href="https://wiki.dgfygk.com#62__350">6.2. 查询存在的商品</a></li>
 <li><a href="https://wiki.dgfygk.com#63__356">6.3. 查询不存在的商品</a></li>
 <li><a href="https://wiki.dgfygk.com#7__363">7. 总结</a></li>
</ul>
<p>
 <br>
</p>
<p>在高并发场景下，缓存是提升系统性能的重要手段。然而，常规缓存机制中，若遇到大量无效请求访问（请求的 key 不存在于缓存或数据库），就会导致 <strong>缓存穿透</strong> 。为了应对这种问题，<strong>布隆过滤器</strong> 和 <strong>缓存空值</strong> 是应对缓存穿透的两大主流方案，布隆过滤器适用于大规模、复杂场景，缓存空值适用于小规模场景。<strong>布隆过滤器（Bloom Filter）</strong> 能够通过哈希算法判断一个 key 是否可能存在，减少无效请求对数据库的压力。</p>
<p>本篇博客将介绍如何使用 <strong>Spring Boot 3</strong> 和 <strong>Redis</strong> 实现布隆过滤器，并结合示例代码来详细讲解布隆过滤器的原理和在 Redis 中的实现方式。</p>
<ol>
 <li>
  <h2>什么是布隆过滤器？</h2>
 </li>
</ol>
<h3>基本概念</h3>
<p>布隆过滤器是一种空间效率高的 <strong>概率性数据结构</strong>，用于快速判断某个元素是否在集合中。它有以下特点：</p>
<ul>
 <li><strong>内存占用小</strong>：相比传统的集合结构，布隆过滤器的内存使用更少。</li>
 <li><strong>可能存在误判</strong>：布隆过滤器只能确定某个元素"可能存在"或"绝对不存在”。但存在误判的概率可以通过调整参数降低。</li>
 <li><strong>不支持删除</strong>：布隆过滤器不支持删除已添加的元素，删除某个元素会导致误判率增加。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F7d4d1cb05db95392c8844d87bb20f7af.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F1b94b1c830409e4ba1a1590e75d9d11b.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<h3>适用场景</h3>
<p>布隆过滤器在以下场景中非常适用：</p>
<ul>
 <li>
  <p>防止缓存穿透：将不存在的 key 存储在布隆过滤器中，避免大量无效请求直接查询数据库。</p>
 </li>
 <li>
  <p>防止重复数据：在大规模数据处理中，使用布隆过滤器避免重复处理相同的数据。</p>
 </li>
 <li>
  <h2>使用 Redis 实现布隆过滤器</h2>
 </li>
</ul>
<p>Redis 提供了开箱即用的布隆过滤器功能，通过 Redis 的插件 <strong>RedisBloom</strong> ，安装过程参考：<a href="">Redis安装RedisBloom</a>，我们可以非常方便地使用布隆过滤器存储和管理 key。</p>
<h3>项目依赖</h3>
<p>首先，在 Spring Boot 项目中引入相关依赖，可参考之前文章。需要 Redis 的支持，以及 Spring Data Redis 来实现与 Redis 的交互。</p>
<blockquote>
 <p><strong>注意</strong>: Redisson 提供了对布隆过滤器的支持，具体实现会利用它的 API。</p>
</blockquote>
<pre><code class="prism language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.redisson&lt;/groupId&gt;
    &lt;artifactId&gt;redisson-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;3.20.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<h3>Redis 配置</h3>
<p>在 <code>application.yml</code> 文件中配置 Redis 的连接信息，详细请参考上一章<a href="">重学SpringBoot3-集成Redis（四）之Redisson</a>，进行 Redisson 配置。</p>
<pre><code class="prism language-yaml">spring:
  redis:
    redisson:
      config: |
        singleServerConfig:
          address: redis://1.94.26.81:6379   # Redis 连接地址，前缀为 redis://
          password: redis123456              # 如果 Redis 需要密码认证，则填写密码
          timeout: 3000                      # 命令执行超时时间（毫秒）
</code></pre>
<p>配置类中初始化 Redisson 客户端：</p>
<pre><code class="prism language-java">package com.coderjia.boot310redis.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.spring.starter.RedissonProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author CoderJia
 * @create 2024/10/5 下午 04:53
 * @Description
 **/
@Configuration
public class RedissonConfig {


    @Autowired
    private RedissonProperties redissonProperties;

    @Bean
    public RedissonClient redissonClient() throws Exception{

        Config config = Config.fromYAML(redissonProperties.getConfig());
        Redisson.create(config);
        System.out.println("Redisson 已启动");
        return Redisson.create(config);
    }

}
</code></pre>
<ol>
 <li>
  <h2>创建布隆过滤器服务</h2>
 </li>
</ol>
<p>接下来，我们需要定义一个服务来管理布隆过滤器。利用 Redisson 提供的 API，可以轻松实现布隆过滤器。</p>
<pre><code class="prism language-java">import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BloomFilterService {


    private static final String BLOOM_FILTER_NAME = "bloomFilter";

    @Autowired
    private RedissonClient redissonClient;

    // 初始化布隆过滤器
    public void initBloomFilter(long expectedInsertions, double falseProbability) {

        RBloomFilter&lt;String&gt; bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
        bloomFilter.tryInit(expectedInsertions, falseProbability);
    }

    // 添加元素到布隆过滤器中
    public void addToBloomFilter(String key) {

        RBloomFilter&lt;String&gt; bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
        bloomFilter.add(key);
    }

    // 检查元素是否存在于布隆过滤器中
    public boolean mightContain(String key) {

        RBloomFilter&lt;String&gt; bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
        return bloomFilter.contains(key);
    }
}
</code></pre>
<p><strong>代码解释</strong></p>
<ol>
 <li>
  <p><strong>初始化布隆过滤器</strong> ：<code>initBloomFilter</code> 方法可以根据期望插入的数量和误判率初始化布隆过滤器。布隆过滤器的大小和哈希函数数量根据这些参数自动计算。</p>
 </li>
 <li>
  <p><strong>添加元素</strong> ：<code>addToBloomFilter</code> 方法向布隆过滤器中添加新的 key。</p>
 </li>
 <li>
  <p><strong>判断元素是否存在</strong> ：<code>mightContain</code> 方法用来判断某个 key 是否在布隆过滤器中。</p>
 </li>
 <li>
  <h2>布隆过滤器的初始化</h2>
 </li>
</ol>
<p>通常我们会在应用启动时初始化布隆过滤器，并将数据库中的所有 key 预先加入过滤器。</p>
<pre><code class="prism language-java">package com.coderjia.boot310redis.config;

import com.coderjia.boot310redis.bean.Product;
import com.coderjia.boot310redis.dao.ProductMapper;
import com.coderjia.boot310redis.service.BloomFilterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author CoderJia
 * @create 2024/10/6 下午 12:38
 * @Description
 **/
@Slf4j
@Component
public class BloomFilterInitializer implements CommandLineRunner {


    @Autowired
    private BloomFilterService bloomFilterService;

    @Autowired
    private ProductMapper productMapper;

    @Override
    public void run(String... args) throws Exception {


        // 查询所有产品数据
        List&lt;Product&gt; all = productMapper.findAll();

        // 初始化布隆过滤器
        bloomFilterService.initBloomFilter(all.size(), 0.01);

        // 将所有产品的ID加入布隆过滤器
        all.forEach(product -&gt; {

            bloomFilterService.addToBloomFilter(product.getId().toString());
        });

        log.info("初始化布隆过滤器完成，添加产品数：{}", all.size());
    }
}
</code></pre>
<p><strong>代码解释</strong></p>
<ul>
 <li>
  <p>在应用启动时，通过 <code>CommandLineRunner</code> 初始化布隆过滤器，并将数据库中的所有商品 ID 加入过滤器中。</p>
 </li>
 <li>
  <h2>使用布隆过滤器进行缓存穿透防护</h2>
 </li>
</ul>
<p>接下来，我们通过一个简单的示例，结合 Redis 的缓存功能和布隆过滤器，展示如何防止缓存穿透。</p>
<pre><code class="prism language-java">package com.coderjia.boot310redis.service;

import com.coderjia.boot310redis.bean.Product;
import com.coderjia.boot310redis.dao.ProductMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @author CoderJia
 * @create 2024/10/6 下午 12:37
 * @Description
 **/
@Slf4j
@Service
public class ProductService {


    @Autowired
    private BloomFilterService bloomFilterService;

    @Autowired
    private ProductMapper productMapper;

    @Cacheable(value = "product", key = "#p0")
    public Product getProductById(Long id) {

        // 使用布隆过滤器防止缓存穿透
        if (!bloomFilterService.mightContain(id.toString())) {

            throw new IllegalArgumentException("Product not found!");
        }

        log.info("准备查询产品信息，id:{}", id);
        return productMapper.findById(id)
                .orElseThrow(() -&gt; new IllegalArgumentException("Product not found!"));
    }
}
</code></pre>
<p><strong>代码解释</strong></p>
<ol>
 <li>在查询商品前，首先通过布隆过滤器判断 key 是否可能存在。若布隆过滤器判断 key 不存在，则直接抛出异常，避免查询数据库。</li>
 <li>如果布隆过滤器判断 key 可能存在，接着通过缓存获取商品数据。如果缓存未命中，则查询数据库。</li>
</ol>
<p><code>productMapper</code> 参考：</p>
<pre><code class="prism language-java">package com.coderjia.boot310redis.dao;

import com.coderjia.boot310redis.bean.Product;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;
import java.util.Optional;

/**
 * @author CoderJia
 * @create 2024/3/16 下午 05:22
 * @Description
 **/
@Mapper
public interface ProductMapper {


    Optional&lt;Product&gt; findById(Long id);

    List&lt;Product&gt; findAll();
}
</code></pre>
<p><code>Product</code> 也很简单：</p>
<pre><code class="prism language-java">package com.coderjia.boot310redis.bean;

import lombok.Data;

import java.io.Serial;
import java.io.Serializable;

/**
 * @author CoderJia
 * @create 2024/10/6 下午 12:46
 * @Description
 **/
@Data
public class Product implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;
    private Long id;
    private String name;
}
</code></pre>
<ol>
 <li>
  <h2>测试效果</h2>
 </li>
</ol>
<p>在你的业务逻辑中调用上面创建的 <code>getProductById</code> 方法。</p>
<pre><code class="prism language-java">package com.coderjia.boot310redis.demos.web;

import com.coderjia.boot310redis.bean.Product;
import com.coderjia.boot310redis.service.LockService;
import com.coderjia.boot310redis.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author CoderJia
 * @create 2024/10/5 下午 05:14
 * @Description
 **/
@Slf4j
@RestController
public class LockController {


    @Autowired
    private ProductService productService;

    @GetMapping("/get-product")
    public Product getProduct(@RequestParam("id") Long id) {

        log.info("准备产品产品信息，id:{}", id);
        try {

            Product product = productService.getProductById(id);
            return product;
        }catch (Exception e) {

            log.error("获取产品信息异常，id:{}", id, e);
            return null;
        }
    }
}
</code></pre>
<h3>6.1. 启动项目</h3>
<p>可以看到布隆过滤器的初始化过程，查询出所有的产品信息并添加到布隆过滤器中。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F25c4b587bf56185ba423f9d2b2d6e8b0.png&amp;size=m" alt=""></p>
<h3>6.2. 查询存在的商品</h3>
<p>调用 <code>curl "http://localhost:8080/get-product?id=1"</code> 接口:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F94340ee50d137cccb11c7876149a6163.png&amp;size=m" alt=""></p>
<h3>6.3. 查询不存在的商品</h3>
<p>调用 <code>curl "http://localhost:8080/get-product?id=101"</code> 接口，产品不存在布隆过滤器器中，直接报错。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F8a488e18a419ace86a11a0d23f3dfe2d.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <h2>总结</h2>
 </li>
</ol>
<p>通过结合 <strong>Spring Boot 3</strong> 、<strong>Redis</strong> 和 <strong>Redisson</strong>，我们可以非常方便地实现布隆过滤器，来防止缓存穿透问题。在高并发场景下，布隆过滤器是一种有效的工具，可以降低数据库的压力，提升系统性能。布隆过滤器并不是万能的，在某些场景下会有少量误判，但结合 Redis 的强大功能，它依然是防止缓存穿透的最佳选择之一。</p>
<p><strong>关键点总结</strong></p>
<ul>
 <li>布隆过滤器通过空间换时间，能够快速判断元素是否存在，减少无效请求。</li>
 <li>Redisson 提供了开箱即用的布隆过滤器 API，大大简化了开发工作。</li>
 <li>在结合缓存时，布隆过滤器可以显著减少数据库查询，提升系统性能。</li>
</ul>
<p>原文链接: https://blog.csdn.net/u014390502/article/details/142736485</p>]]></description><guid isPermaLink="false">/archives/bbfc46d9-1d00-44e4-a44b-d465a644b007</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:41:56 GMT</pubDate></item><item><title><![CDATA[设计模式第16讲——迭代器模式（Iterator）]]></title><link>https://wiki.dgfygk.com/archives/bf03c814-8427-4e26-a0c7-78d3ec926400</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%AC16%E8%AE%B2%E2%80%94%E2%80%94%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F%EF%BC%88Iterator%EF%BC%89&amp;url=/archives/bf03c814-8427-4e26-a0c7-78d3ec926400" width="1" height="1" alt="" style="opacity:0;">
<h3>一、什么是迭代器模式</h3>
<blockquote>
 <p>迭代器模式是一种行为型设计模式，它提供了一种<strong>统一的方式来访问集合对象中的元素，而不是暴露集合内部的表示方式</strong>。简单地说，就是将遍历集合的责任封装到一个单独的对象中，我们可以按照特定的方式访问集合中的元素。</p>
</blockquote>
<h3>二、角色组成</h3>
<blockquote>
 <ul>
  <li>抽象迭代器（Iterator）：定义了遍历聚合对象所需的方法，包括hashNext()和next()方法等，用于遍历聚合对象中的元素。</li>
  <li>具体迭代器（Concrete Iterator）：它是实现迭代器接口的具体实现类，负责具体的遍历逻辑。它保存了当前遍历的位置信息，并可以根据需要向前或向后遍历集合元素。</li>
  <li>抽象聚合器（Aggregate）： 一般是一个接口，提供一个iterator()方法，例如java中的Collection接口，List接口，Set接口等。</li>
  <li>具体聚合器（ConcreteAggregate）：就是抽象容器的具体实现类，比如List接口的有序列表实现ArrayList，List接口的链表实现LinkList，Set接口的哈希列表的实现HashSet等。</li>
 </ul>
</blockquote>
<h3>三、 优缺点</h3>
<blockquote>
 <p><strong>优点：</strong></p>
 <ul>
  <li>简化了集合类的接口，使用者可以更加简单地遍历集合对象，而不需要了解集合内部结构和实现细节。</li>
  <li>将集合和遍历操作解耦，使得我们可以更灵活地使用不同的迭代器来遍历同一个集合，根据需求选择不同的遍历方式。</li>
  <li>满足开闭原则，如果需要增加新的遍历方式，只需实现一个新的具体迭代器即可，不需要修改原先聚合对象的代码。</li>
 </ul>
 <p><strong>缺点：</strong></p>
 <ul>
  <li>具体迭代器实现的算法对外不可见，因此不利于调试和维护。</li>
  <li>对于某些小型、简单的集合对象来说，使用迭代器模式可能会显得过于复杂，增加了代码的复杂性。</li>
 </ul>
</blockquote>
<h3>四、应用场景</h3>
<h4>4.1 生活场景</h4>
<blockquote>
 <ul>
  <li>遍历班级名单：假设你是一名班主任，你需要遍历班级名单来点名。班级名单可以看作是一个集合，每个学生名字可以看作是集合中的一个元素。使用迭代器模式，你可以通过迭代器对象逐个访问学生的名字，而不需要了解班级名单的具体实现细节。</li>
  <li>遍历音乐播放列表：当我们在手机或电脑上播放音乐时，通常会创建一个播放列表。播放列表可以被视为一个集合，每首歌曲可以被视为集合中的一个元素。使用迭代器模式，我们可以通过迭代器对象逐个访问播放列表中的歌曲，进行播放、暂停或切歌等操作。</li>
 </ul>
</blockquote>
<h4>4.2 java场景</h4>
<blockquote>
 <ul>
  <li>集合框架中的迭代器：在Java中，集合包括List、Set、Map等等，每个集合类中都提供了一个获取迭代器的方法，例如List提供的iterator()方法、Set提供的iterator()方法等等。通过获取对应的迭代器对象，可以对集合中的元素进行遍历和访问。</li>
  <li>JDBC中的ResultSet对象：在Java中，如果需要对数据库中的数据进行遍历和访问，可以使用JDBC操作数据库。JDBC中，查询结果集使用ResultSet对象来表示，通过使用ResultSet的next()方法，就可以像使用迭代器一样遍历和访问查询结果中的数据。</li>
  <li>文件读取：在Java中，我们可以使用BufferedReader类来读取文本文件。BufferedReader类提供了一个方法readLine()来逐行读取文件内容。实际上，BufferedReader在内部使用了迭代器模式来逐行读取文本文件的内容。</li>
 </ul>
</blockquote>
<h3>五、代码实现</h3>
<p>下面以班级名单为例，解释一下迭代器模式。</p>
<blockquote>
 <ul>
  <li>抽象迭代器：StudentIterator</li>
  <li>具体迭代器：StudentListIterator</li>
  <li>抽象聚合器：StudentAggregate</li>
  <li>具体聚合器：ClassList</li>
 </ul>
</blockquote>
<h4>5.0 UML类图</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F24a3774c5725ec555d55b76e56399469.png&amp;size=m" alt=""></p>
<h4>5.1 Student——学生实体类</h4>
<p>首先我们定义一个学生类，用来表示学生信息。</p>
<pre><code class="java">/**
 * @author Created by njy on 2023/6/25
 * 学生实体类
 */
@Data
public class Student {
    private String name;
    private Integer age;
    public Student(String name,Integer age){
        this.age=age;
        this.name=name;
    }
}
</code></pre>
<h4>5.2 StudentIterator——抽象迭代器（Iterator）</h4>
<p>接下来创建一个抽象迭代器（学生迭代器）并继承Iterator接口（java.util包下的Iterator）</p>
<pre><code class="java">import java.util.Iterator;
/**
 * @author Created by njy on 2023/6/25
 * 抽象迭代器（Iterator）：学生迭代器
 * 实现Iterator接口
 * 负责定义访问和遍历元素的接口，例如提供hasNext()和next()方法。
 */
public interface StudentIterator extends Iterator&lt;Student&gt; {
}
</code></pre>
<h4>5.3 StudentListIterator——具体迭代器（Concrete iterator）</h4>
<p>在这个具体迭代器中，实现抽象迭代器，重写hashNext()和next()方法。</p>
<pre><code class="java">/**
 * @author Created by njy on 2023/6/25
 * 具体迭代器（Concrete iterator）：
 * 实现抽象迭代器定义的接口，负责实现对元素的访问和遍历。
 */
public class StudentListIterator implements StudentIterator{
    private List&lt;Student&gt; students;
    private int index;

    public StudentListIterator(List&lt;Student&gt; students) {
        this.students = students;
        this.index = 0;
    }

    //检查是否还有下一个元素
    @Override
    public boolean hasNext() {
        return (index &lt; students.size());
    }

    //返回下一个元素
    @Override
    public Student next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        Student student = students.get(index);
        index++;
        return student;
    }
}
</code></pre>
<h4>5.4 StudentAggregate——抽象聚合器（Aggregate）</h4>
<p>定义一个抽象聚合器，并定义一个iterator()方法，用于创建具体的迭代器对象。</p>
<pre><code class="java">/**
 * @author Created by njy on 2023/6/25
 * 抽象聚合器（Aggregate）：学生聚合器
 * 提供创建迭代器的接口，例如可以定义一个iterator()方法。
 */
public interface StudentAggregate {
    //用于创建具体的迭代器对象
    StudentIterator iterator();
    void add(Student student);
}
</code></pre>
<h4>5.5 ClassList——具体聚合器（Concrete Aggregate）</h4>
<p>实现抽象聚合器定义的接口，负责创建具体的迭代器对象。</p>
<pre><code class="java">/**
 * @author Created by njy on 2023/6/25
 * 具体聚合器（ConcreteAggregate）：班级列表
 * 实现抽象聚合器定义的接口，负责创建具体的迭代器对象，并返回该对象。
 */
public class ClassList implements StudentAggregate{
    private List&lt;Student&gt; students = new ArrayList&lt;&gt;();

    //创建迭代器对象
    @Override
    public StudentIterator iterator() {
        return new StudentListIterator(students);
    }

    //向班级名单中添加学生信息
    @Override
    public void add(Student student) {
        students.add(student);
    }
}
</code></pre>
<h4>5.6 testIterator</h4>
<pre><code class="java">/**
 * @author Created by njy on 2023/6/25
 * 迭代器模式测试类
 */
@SpringBootTest
public class TestIterator {
    @Test
    void testIterator(){
        ClassList classList = new ClassList();
        // 添加学生信息
        classList.add(new Student("张三", 18));
        classList.add(new Student("李四", 19));
        classList.add(new Student("王五", 20));
        // 获取迭代器，遍历学生信息
        StudentIterator iterator = classList.iterator();
        while(iterator.hasNext()) {
            Student student = iterator.next();
            System.out.println("学生姓名：" + student.getName() + "，学生年龄：" + student.getAge());
        }
    }

}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fddb01a9fb49114e8db695a48de546037.png&amp;size=m" alt=""></p>
<h3>六、总结</h3>
<blockquote>
 <ul>
  <li>迭代器模式提供了一种统一的方式来遍历集合对象中的元素。</li>
  <li>它将遍历操作封装到一个独立的迭代器对象中，使得我们可以按照特定的方式访问集合中的元素。</li>
  <li>迭代器模式将集合对象和遍历操作分离开来，提高了代码的灵活性和可维护性。</li>
  <li>使用迭代器模式可以让我们用相同的方式遍历不同类型的集合对象，而不需要了解集合的内部结构。</li>
 </ul>
</blockquote>
<p><strong>END：更多设计模式的介绍，推荐移步至</strong>👉 <a href="https://blog.csdn.net/weixin_45433817/article/details/131037102" title="23种设计模式学习导航（Java完整版）">23种设计模式学习导航（Java完整版）</a>👈</p>
<p>原文链接: https://blog.csdn.net/weixin_45433817/article/details/131382881</p>]]></description><guid isPermaLink="false">/archives/bf03c814-8427-4e26-a0c7-78d3ec926400</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Tue, 8 Oct 2024 03:41:18 GMT</pubDate></item><item><title><![CDATA[vue2路由和vue3路由区别及原理]]></title><link>https://wiki.dgfygk.com/archives/b1551670-d634-432e-8bee-10b5e5dd3c96</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue2%E8%B7%AF%E7%94%B1%E5%92%8Cvue3%E8%B7%AF%E7%94%B1%E5%8C%BA%E5%88%AB%E5%8F%8A%E5%8E%9F%E7%90%86&amp;url=/archives/b1551670-d634-432e-8bee-10b5e5dd3c96" width="1" height="1" alt="" style="opacity:0;">
<h3>一、Vue2 与 Vue3 路由的区别</h3>
<h4>1. <strong>创建路由实例方式的不同</strong></h4>
<ul>
 <li>
  <p><strong>Vue 2</strong> 中，通过 <code>Vue.use()</code> 注册路由插件，并通过 <code>new VueRouter()</code> 来创建路由实例。</p>
  <p>import Vue from 'vue';import VueRouter from 'vue-router';import Home from '@/components/Home.vue';​Vue.use(VueRouter);​const routes = [ { path: '/', component: Home }];​const router = new VueRouter({</p>
  <pre><code>routes});​new Vue({
router,  render: h =&gt; h(App)}).$mount('#app');​
</code></pre>
 </li>
</ul>
<p><strong>Vue 3</strong> 中，通过 <code>createRouter</code> 和 <code>createWebHistory</code> 等 API 创建路由实例，直接使用 <code>app.use()</code> 挂载，不再需要 <code>Vue.use()</code> 来注册插件。</p>
<pre><code>import { createApp } from 'vue';import { createRouter, createWebHistory } from 'vue-router';import Home from '@/components/Home.vue';import App from './App.vue';​const routes = [  { path: '/', component: Home }];​const router = createRouter({
    history: createWebHistory(),  routes});​const app = createApp(App);app.use(router);app.mount('#app');​
</code></pre>
<h4>2. <strong>动态路由的添加方式</strong></h4>
<ul>
 <li>
  <p><strong>Vue 2</strong> 中使用 <code>addRoutes()</code> 来动态添加多个路由，这个方法已经在 Vue 3 中被移除。</p>
  <p>router.addRoutes([ { path: '/about', component: About }]);​</p>
 </li>
</ul>
<p><strong>Vue 3</strong> 中使用 <code>addRoute()</code> 来动态添加单个路由，且可以指定父级路由进行嵌套路由的动态添加。</p>
<pre><code>router.addRoute({ path: '/about', component: About });router.addRoute('parent', { path: 'child', component: Child });​
</code></pre>
<h4>3. <strong>路由守卫的变化</strong></h4>
<p>Vue 3 在组合式 API (<code>setup</code>) 中新增了 <code>onBeforeRouteUpdate</code> 和 <code>onBeforeRouteLeave</code> 这两个钩子，可以直接在 <code>setup</code> 中使用，而不再依赖组件选项（Options API）。</p>
<ul>
 <li>
  <p><strong>Vue 2</strong> 中，路由守卫在组件选项中定义。</p>
  <p>export default {</p>
  <pre><code>beforeRouteEnter(to, from, next) {
  // 在进入组件前调用    next();  },  beforeRouteUpdate(to, from, next) {
  // 路由更新时调用    next();  },  beforeRouteLeave(to, from, next) {
  // 离开组件时调用    next();  }};​
</code></pre>
 </li>
</ul>
<p><strong>Vue 3</strong> 中，可以在 <code>setup</code> 中直接使用 <code>onBeforeRouteUpdate</code> 和 <code>onBeforeRouteLeave</code>。</p>
<pre><code>import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';​export default {
    setup() {
      onBeforeRouteLeave((to, from) =&gt; {
        // 路由离开时触发的守卫    });​    onBeforeRouteUpdate((to, from) =&gt; {
        // 路由更新时触发的守卫    });  }};​
</code></pre>
<h4>4. <strong>组合式 API 的支持</strong></h4>
<p>Vue 3 使用了 <code>useRouter</code> 和 <code>useRoute</code> 这两个 Composition API，能够在 <code>setup</code> 中方便地访问路由实例和路由信息。这在 Vue 2 中是无法做到的，因为 Vue 2 的 <code>vue-router</code> 与 <code>Options API</code> 深度耦合。</p>
<pre><code>import { useRouter, useRoute } from 'vue-router';​export default {
    setup() {
      const router = useRouter();    const route = useRoute();​    const goToHome = () =&gt; {
        router.push({ name: 'Home' });    };​    return { goToHome, route };  }};​
</code></pre>
<h4>5. <strong>路由模式的选择</strong></h4>
<ul>
 <li>
  <p><strong>Vue 2</strong> 中，使用 <code>mode</code> 参数来决定使用 <code>history</code> 还是 <code>hash</code> 模式。</p>
  <p>const router = new VueRouter({</p>
  <pre><code>mode: 'history',  // 或者 'hash'  routes});​
</code></pre>
 </li>
</ul>
<p><strong>Vue 3</strong> 中，使用 <code>createWebHistory()</code> 或 <code>createWebHashHistory()</code> 明确区分路由模式。</p>
<pre><code>const router = createRouter({
    history: createWebHistory(),  // 或者 createWebHashHistory()  routes});​
</code></pre>
<h4>6. <strong>其他差异</strong></h4>
<ul>
 <li>
  <p><strong>更好的 TypeScript 支持</strong> ：Vue 3 和 <code>vue-router@4</code> 对 TypeScript 进行了优化，提供了完整的类型推断和类型检查。</p>
 </li>
 <li>
  <p><strong>过渡效果改进</strong> ：Vue 3 的 <code>&lt;router-view&gt;</code> 支持新的 <code>v-slot</code> 用法来控制过渡效果。</p>
 </li>
</ul>
<h3>二、Vue Router 的原理解析</h3>
<h4>1. <strong>路由的核心原理</strong></h4>
<p>Vue Router 的核心原理就是通过 <strong>Hash 模式</strong> 或 <strong>History 模式</strong> 来监听 URL 的变化，从而实现视图组件的动态切换。</p>
<ol>
 <li>
  <p><strong>Hash 模式</strong> ：通过监听 <code>window.onhashchange</code> 事件来感知 URL 中 <code>#</code> 后面内容的变化。</p>
 </li>
 <li>
  <p><strong>History 模式</strong> ：基于 <code>window.history.pushState</code> 和 <code>window.history.replaceState</code> API 来修改 URL，并通过 <code>popstate</code> 事件监听 URL 的变化。</p>
 </li>
</ol>
<h4>2. <strong>路由匹配机制</strong></h4>
<p>Vue Router 的路由匹配基于路由表 (<code>routes</code>) 进行匹配。它使用了 <code>path-to-regexp</code> 库将 <code>path</code> 转换成正则表达式，并与当前 URL 进行比对，从而找到合适的路由。</p>
<ul>
 <li>
  <p>每个路由规则（<code>RouteRecord</code>）都包含 <code>path</code>、<code>component</code> 等信息。</p>
 </li>
 <li>
  <p>在匹配过程中，会将路由表中的每一个 <code>RouteRecord</code> 进行遍历，并基于路径解析进行比对。</p>
 </li>
 <li>
  <p>一旦找到匹配的路由规则，就会渲染对应的组件。</p>
 </li>
</ul>
<h4>3. <strong>导航守卫执行顺序</strong></h4>
<p>Vue Router 提供了三种导航守卫：全局守卫、路由独享守卫和组件内守卫。它们的执行顺序如下：</p>
<ol>
 <li>
  <p><strong>全局前置守卫 (<code>beforeEach</code>)</strong></p>
 </li>
 <li>
  <p><strong>路由独享守卫 (<code>beforeEnter</code>)</strong></p>
 </li>
 <li>
  <p><strong>组件内守卫 (<code>beforeRouteEnter</code>)</strong></p>
 </li>
 <li>
  <p><strong>全局解析守卫 (<code>beforeResolve</code>)</strong></p>
 </li>
 <li>
  <p><strong>全局后置守卫 (<code>afterEach</code>)</strong></p>
 </li>
</ol>
<h4>4. <strong>路由切换过程</strong></h4>
<ul>
 <li>
  <p><strong>路由解析</strong> ：在 <code>router.push</code> 或者 URL 变化时，路由会首先匹配路由表中符合条件的路由规则。</p>
 </li>
 <li>
  <p><strong>执行导航守卫</strong>：依次执行全局、路由独享和组件内的导航守卫。</p>
 </li>
 <li>
  <p><strong>更新视图</strong> ：在守卫全部通过后，更新组件树，重新渲染 <code>router-view</code> 视图。</p>
 </li>
</ul>
<h3>三、总结</h3>
<p>Vue 3 的 <code>vue-router@4</code> 相较于 Vue 2 更加现代化和灵活，并且对 TypeScript 进行了更好的支持。与此同时，Vue 3 的路由实现基于 Composition API，使其能够更好地与 Vue 3 的设计理念相契合，提升了开发体验和可维护性。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/qq_25416827/article/details/142746748</p>]]></description><guid isPermaLink="false">/archives/b1551670-d634-432e-8bee-10b5e5dd3c96</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Tue, 8 Oct 2024 03:41:07 GMT</pubDate></item><item><title><![CDATA[SpringBoot整合异步任务执行]]></title><link>https://wiki.dgfygk.com/archives/bcbc79b9-8ef2-4c65-8512-6744cf95f8ff</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E6%95%B4%E5%90%88%E5%BC%82%E6%AD%A5%E4%BB%BB%E5%8A%A1%E6%89%A7%E8%A1%8C&amp;url=/archives/bcbc79b9-8ef2-4c65-8512-6744cf95f8ff" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<p>同步任务：</p>
<p>同步任务是在<strong>单线程中按顺序执行，每次只有一个任务在执行，不会引发线程安全和数据一致性等</strong></p>
<p>并发问题</p>
<p><strong>同步任务需要等待任务执行完成后才能执行下一个任务，无法同时处理多个任务，响应慢，影响用</strong></p>
<p><strong>户体验</strong></p>
<p>异步任务：</p>
<p>异步任务是在多线程中同时执行，<strong>多个任务可以并发执行，同时处理多个请求，响应快，资源利用</strong></p>
<p><strong>率高，用户体验好</strong></p>
<p>异步任务需要创建额外的线程来执行任务操作，可以使用线程池技术，灵活配置线程资源，以提高</p>
<p>系统的资源利用率</p>
<p><strong>应该场景:比如用户的操作日志，我操作完之后还需要去执行日志记录，这个时候如果日志记录比较慢，那么用户就需要一直等待，体验感较差，我们应该开启一个新的线程去执行日志记录，返回给用户操作成功。比较经典应用就是下单。</strong></p>
<p><strong>调用python程序，我这边不可能一直等待python程序调用，给调用的代码加一个异步处理，直接返回给前端调用成功，对于python代码的调用结果可以通过websocket推送到前端去。</strong></p>
<h4>springBoot框架使用异步</h4>
<h5>在启动类上加上注解</h5>
<pre><code class="prism language-java">@EnableAsync  // 启用异步任务支持
</code></pre>
<h5>在需要异步的方法上面添加注解</h5>
<pre><code class="prism language-java"> @Async
    public  void  print() throws InterruptedException {

        Thread.sleep(3000);
        System.out.println("这是执行的异步方法"+System.currentTimeMillis());
    }
</code></pre>
<p><strong>调用异步方法</strong></p>
<pre><code class="prism language-java">@Autowired
private testService testService;
@SneakyThrows //这个是抛异常的
@GetMapping("/hello")
public String  asny(){

    System.out.println("hello world");
    testService.print();
    return "hello"+System.currentTimeMillis();
}
</code></pre>
<p><strong>注意异步方法不能和调用的方法同一个类中，否则异步不会生效。</strong></p>
<p><strong>使用了异步之后，方法会直接返回，下面的操作会在后面慢慢执行。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fcecc9a9191b01320a2084aa7e25f2772.png&amp;size=m" alt=""></p>
<ul>
 <li><strong>异步调用必须通过代理</strong>: 确保异步方法是在不同的 Spring 管理的 bean 中调用，而不是在同一个类中直接调用。</li>
 <li><strong>启用异步功能</strong> : 在启动类中添加 <code>@EnableAsync</code> 注解，以启用 Spring 的异步功能。</li>
</ul>
<hr>
<p><strong>配置自定义线程池</strong></p>
<hr>
<p>**默认情况下，Spring Boot 使用一个简单的线程池来执行异步任务。如果你需要更复杂的配置，例如增加线程池大小或设置线程名称前缀，可以自定义 <strong><code>**Executor**</code></strong>。 **</p>
<pre><code class="prism language-java">package com.njitzx.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration //交给spring容器管理
public class AsyncConfig implements AsyncConfigurer {

    @Bean(name = "asyncExecutor")
    public Executor asyncExecutor() {

        // 定义线程池，使用Spring提供的ThreadPoolTaskExecutor
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); // 核心线程数
        executor.setMaxPoolSize(20); // 最大线程数
        executor.setQueueCapacity(50); // 缓冲队列大小
        executor.setKeepAliveSeconds(60); // 线程的空闲时间
        executor.setThreadNamePrefix("AsyncTaskExecutor-"); // 线程前缀
        executor.setRejectedExecutionHandler(new
                                             ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝处理策略，交由调用方线程运行
        executor.initialize(); // 初始化
        return executor;
    }

    @Override
    public Executor getAsyncExecutor() {

        return asyncExecutor();
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/ngczx/article/details/142705970</p>]]></description><guid isPermaLink="false">/archives/bcbc79b9-8ef2-4c65-8512-6744cf95f8ff</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:40:49 GMT</pubDate></item><item><title><![CDATA[springboot kafka多数据源，通过配置动态加载发送者和消费者]]></title><link>https://wiki.dgfygk.com/archives/a663066b-aea6-4c4f-90c6-4f7cda7ef17c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=springboot%20kafka%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90%EF%BC%8C%E9%80%9A%E8%BF%87%E9%85%8D%E7%BD%AE%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E5%8F%91%E9%80%81%E8%80%85%E5%92%8C%E6%B6%88%E8%B4%B9%E8%80%85&amp;url=/archives/a663066b-aea6-4c4f-90c6-4f7cda7ef17c" width="1" height="1" alt="" style="opacity:0;">
<h2>前言</h2>
<p>最近做项目，需要支持kafka多数据源，实际上我们也可以通过代码固定写死多套kafka集群逻辑，但是如果需要不修改代码扩展呢，因为kafka本身不处理额外逻辑，只是起到削峰，和数据的传递，那么就需要对架构做一定的设计了。</p>
<h2>准备test</h2>
<p>kafka本身非常容易上手，如果我们需要单元测试，引入jar依赖，JDK使用1.8，当然也可以使用JDK17</p>
<pre><code class="XML">    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
            &lt;version&gt;2.7.17&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;version&gt;2.7.17&lt;/version&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.kafka&lt;/groupId&gt;
            &lt;artifactId&gt;spring-kafka&lt;/artifactId&gt;
            &lt;version&gt;2.9.13&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.kafka&lt;/groupId&gt;
            &lt;artifactId&gt;spring-kafka-test&lt;/artifactId&gt;
            &lt;version&gt;2.9.13&lt;/version&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;!-- https://mvnrepository.com/artifact/org.testcontainers/kafka --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
            &lt;artifactId&gt;kafka&lt;/artifactId&gt;
            &lt;version&gt;1.20.1&lt;/version&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;

    &lt;/dependencies&gt;
</code></pre>
<p>修改发送者和接收者</p>
<pre><code class="java">@Component
public class KafkaProducer {

    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProducer.class);

    @Autowired
    private KafkaTemplate&lt;String, String&gt; kafkaTemplate;

    public void send(String topic, String payload) {
        LOGGER.info("sending payload='{}' to topic='{}'", payload, topic);
        kafkaTemplate.send(topic, payload);
    }
}

@Component
public class KafkaConsumer {

    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumer.class);

    private String payload;

    @KafkaListener(topics = "${test.topic}")
    public void receive(ConsumerRecord&lt;?, ?&gt; consumerRecord) {
        LOGGER.info("----------------received payload='{}'", consumerRecord.toString());
        payload = consumerRecord.toString();
    }
    public String getPayload() {
        return payload;
    }

    public void setPayload(String payload) {
        this.payload = payload;
    }
}
</code></pre>
<p>然后写main方法，随意写一个即可，配置入戏</p>
<pre><code class="bash">spring:
  kafka:
    consumer:
      auto-offset-reset: earliest
      group-id: mytest
test:
  topic: embedded-test-topic
</code></pre>
<p>写一个单元测试</p>
<pre><code class="java">@SpringBootTest
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
class DemoMainTest {

    @Autowired
    private KafkaConsumer consumer;

    @Autowired
    private KafkaProducer producer;

    @Value("${test.topic}")
    private String topic;

    @Test
    void embedKafka() throws InterruptedException {
        String data = "Sending with our own simple KafkaProducer";

        producer.send(topic, data);
        Thread.sleep(3000);
        assertThat(consumer.getPayload(), containsString(data));
        Thread.sleep(10000);
    }
}
</code></pre>
<p>通过</p>
<pre><code>@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
</code></pre>
<p>直接模拟一个kafka，里面有一些注解参数，可以设置broker的 数量端口，zk的端口，topic和partition数量等</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbf5c9c31d8834ff3900def0d0676b612.png&amp;size=m" alt=""></p>
<p>实际上是通过embed zk和kafka来mock了一个kafka server</p>
<p>单元测试运行成功</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5010993867f145bb9839f6ac9521ce44.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa58d9760fdf3431c9e69058bf9c967f1.png&amp;size=m" alt=""></p>
<h2>思路</h2>
<p>有了kafka单元测试后，根据springboot map可以接收多套配置的方式不就实现了kafka的多数据源的能力，貌似非常简单；但是如果需要不用修改代码，消费端怎么办，发送者可以手动创建，消费端是注解方式，topic等信息在注解参数中，注解参数值却是常量，代码写死的，那么我们就需要：</p>
<ol>
 <li>不让Springboot自动扫描，根据配置手动扫描注册bean</li>
 <li>字节码生成bean，就可以根据参数</li>
</ol>
<p>这里没考虑把消费端和发送者的额外处理逻辑写在这里的做法，统一处理kafka，类似kafka网关，因为kafka一般不会仅一套，且不会仅有一个topic，需要分发处理，比如slb，feign等。</p>
<h2>kafka消费者的原理</h2>
<p>其实kafka发送者和消费者也是类似逻辑，但是spring-kafka通过注解方式实现消费者，如果我们使用原生kafka的kafkaconsumer，那么只需要通过Map接收参数，然后自己实现消费逻辑就行，但是spring-kafka毕竟做了很多公共没必要的逻辑，拉取消费的一系列参数，线程池管理等处理措施。看看Spring-kafka的消费者初始化原理，</p>
<pre><code>BeanPostProcessor的kafka实现
</code></pre>
<p>org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor</p>
<p>看前置处理</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5ba19f1a7f024ca8a4660f5cdeead924.png&amp;size=m" alt=""></p>
<p>什么都没做，所以，所有逻辑都在后置处理</p>
<pre><code class="java">public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
        if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
            Class&lt;?&gt; targetClass = AopUtils.getTargetClass(bean);
            //找到注解，消费注解KafkaListener打在类上，一般不用这种方式
            Collection&lt;KafkaListener&gt; classLevelListeners = findListenerAnnotations(targetClass);
            //类上KafkaListener注解的标志
            final boolean hasClassLevelListeners = classLevelListeners.size() &gt; 0;
            final List&lt;Method&gt; multiMethods = new ArrayList&lt;&gt;();
            //找到消费方法，去每个方法上找KafkaListener注解
            Map&lt;Method, Set&lt;KafkaListener&gt;&gt; annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                    (MethodIntrospector.MetadataLookup&lt;Set&lt;KafkaListener&gt;&gt;) method -&gt; {
                        Set&lt;KafkaListener&gt; listenerMethods = findListenerAnnotations(method);
                        return (!listenerMethods.isEmpty() ? listenerMethods : null);
                    });
            if (hasClassLevelListeners) {
                //类上KafkaListener注解的时候，通过另外的注解KafkaHandler的方式，找到消费方法
                Set&lt;Method&gt; methodsWithHandler = MethodIntrospector.selectMethods(targetClass,
                        (ReflectionUtils.MethodFilter) method -&gt;
                                AnnotationUtils.findAnnotation(method, KafkaHandler.class) != null);
                multiMethods.addAll(methodsWithHandler);
            }
            //实际上大部分类是没有kafka消费注解的，效率并不高，但是因为日志是trace，所以日志一般默认看不见
            //注解KafkaListener打在方法上的时候
            if (annotatedMethods.isEmpty() &amp;&amp; !hasClassLevelListeners) {
                this.nonAnnotatedClasses.add(bean.getClass());
                this.logger.trace(() -&gt; "No @KafkaListener annotations found on bean type: " + bean.getClass());
            }
            else {
                // Non-empty set of methods
                for (Map.Entry&lt;Method, Set&lt;KafkaListener&gt;&gt; entry : annotatedMethods.entrySet()) {
                    Method method = entry.getKey();
                    for (KafkaListener listener : entry.getValue()) {
                        //核心逻辑
                        processKafkaListener(listener, method, bean, beanName);
                    }
                }
                this.logger.debug(() -&gt; annotatedMethods.size() + " @KafkaListener methods processed on bean '"
                            + beanName + "': " + annotatedMethods);
            }
            //注解KafkaListener打在类上，实际上处理逻辑跟KafkaListener打在方法上差不多
            if (hasClassLevelListeners) {
                processMultiMethodListeners(classLevelListeners, multiMethods, bean, beanName);
            }
        }
        return bean;
    }
</code></pre>
<p>如果是注解打在类上，如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F02fb9e315b9548ed8cc3ffdb21585588.png&amp;size=m" alt=""></p>
<p>本文中的示例的@KafkaListener打在方法上，所以分析</p>
<pre><code>processKafkaListener&nbsp;
</code></pre>
<p>其实原理都一样，spring-kafka不会写2份一样逻辑，只是读取处理的参数略有不同</p>
<pre><code class="java">protected synchronized void processKafkaListener(KafkaListener kafkaListener, Method method, Object bean,
            String beanName) {
        //检查代理
        Method methodToUse = checkProxy(method, bean);
        //终端设计思想，Spring很多地方都这样设计，尤其是swagger
        MethodKafkaListenerEndpoint&lt;K, V&gt; endpoint = new MethodKafkaListenerEndpoint&lt;&gt;();
        endpoint.setMethod(methodToUse);
        //bean的名称，这里需要定制全局唯一，否则多个listener会冲突
        String beanRef = kafkaListener.beanRef();
        this.listenerScope.addListener(beanRef, bean);
        String[] topics = resolveTopics(kafkaListener);
        TopicPartitionOffset[] tps = resolveTopicPartitions(kafkaListener);
        if (!processMainAndRetryListeners(kafkaListener, bean, beanName, methodToUse, endpoint, topics, tps)) {
            //核心逻辑
            processListener(endpoint, kafkaListener, bean, beanName, topics, tps);
        }
        this.listenerScope.removeListener(beanRef);
    }
</code></pre>
<p>继续</p>
<pre><code>processListener
</code></pre>
<pre><code>```java
protected void processListener(MethodKafkaListenerEndpoint&lt;?, ?&gt; endpoint, KafkaListener kafkaListener,
                                Object bean, String beanName, String[] topics, TopicPartitionOffset[] tps) {
        //MethodKafkaListenerEndpoint赋值了，这个很关键
        processKafkaListenerAnnotation(endpoint, kafkaListener, bean, topics, tps);
        //容器工厂
        String containerFactory = resolve(kafkaListener.containerFactory());
        KafkaListenerContainerFactory&lt;?&gt; listenerContainerFactory = resolveContainerFactory(kafkaListener,
                containerFactory, beanName);
        //注册终端，最终生效
        this.registrar.registerEndpoint(endpoint, listenerContainerFactory);
    }
</code></pre>
<p>processKafkaListenerAnnotation</p>
<pre><code class="java">private void processKafkaListenerAnnotation(MethodKafkaListenerEndpoint&lt;?, ?&gt; endpoint,
            KafkaListener kafkaListener, Object bean, String[] topics, TopicPartitionOffset[] tps) {

        endpoint.setBean(bean);
        endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
        endpoint.setId(getEndpointId(kafkaListener));
        endpoint.setGroupId(getEndpointGroupId(kafkaListener, endpoint.getId()));
        endpoint.setTopicPartitions(tps);
        endpoint.setTopics(topics);
        endpoint.setTopicPattern(resolvePattern(kafkaListener));
        endpoint.setClientIdPrefix(resolveExpressionAsString(kafkaListener.clientIdPrefix(), "clientIdPrefix"));
        endpoint.setListenerInfo(resolveExpressionAsBytes(kafkaListener.info(), "info"));
        String group = kafkaListener.containerGroup();
        if (StringUtils.hasText(group)) {
            Object resolvedGroup = resolveExpression(group);
            if (resolvedGroup instanceof String) {
                endpoint.setGroup((String) resolvedGroup);
            }
        }
        String concurrency = kafkaListener.concurrency();
        if (StringUtils.hasText(concurrency)) {
            endpoint.setConcurrency(resolveExpressionAsInteger(concurrency, "concurrency"));
        }
        String autoStartup = kafkaListener.autoStartup();
        if (StringUtils.hasText(autoStartup)) {
            endpoint.setAutoStartup(resolveExpressionAsBoolean(autoStartup, "autoStartup"));
        }
        resolveKafkaProperties(endpoint, kafkaListener.properties());
        endpoint.setSplitIterables(kafkaListener.splitIterables());
        if (StringUtils.hasText(kafkaListener.batch())) {
            endpoint.setBatchListener(Boolean.parseBoolean(kafkaListener.batch()));
        }
        endpoint.setBeanFactory(this.beanFactory);
        resolveErrorHandler(endpoint, kafkaListener);
        resolveContentTypeConverter(endpoint, kafkaListener);
        resolveFilter(endpoint, kafkaListener);
    }
</code></pre>
<p>各种参数注册，尤其是其中的ID和handler是必须的，不注册不行；笔者试着自己设置endpoint，发现其中的各种handler注册。</p>
<h2>解决方式</h2>
<p>先写一个工具类，用于创建一些关键类的bean，定义了发送者创建，消费者工厂类，消费者的创建由注解扫描实现，引用工具类的消费者容器工厂bean。</p>
<pre><code class="java">public class KafkaConfigUtil {

    private DefaultKafkaProducerFactory&lt;String, String&gt; initProducerFactory(KafkaProperties kafkaProperties) {
        return new DefaultKafkaProducerFactory&lt;&gt;(kafkaProperties.buildProducerProperties());
    }

    public KafkaTemplate&lt;String, String&gt; initKafkaTemplate(KafkaProperties kafkaProperties) {
        return new KafkaTemplate&lt;&gt;(initProducerFactory(kafkaProperties));
    }

    private ConsumerFactory&lt;? super Integer, ? super String&gt; initConsumerFactory(KafkaProperties kafkaProperties) {
        return new DefaultKafkaConsumerFactory&lt;&gt;(kafkaProperties.buildConsumerProperties());
    }

    public KafkaListenerContainerFactory&lt;ConcurrentMessageListenerContainer&lt;Integer, String&gt;&gt;
    initKafkaListenerContainerFactory(KafkaProperties kafkaProperties) {
        ConcurrentKafkaListenerContainerFactory&lt;Integer, String&gt; factory =
                new ConcurrentKafkaListenerContainerFactory&lt;&gt;();
        factory.setConsumerFactory(initConsumerFactory(kafkaProperties));
        return factory;
    }
}
</code></pre>
<h3>1、通过Map接收多数据源</h3>
<p>定义一个配置接收器，仿造zuul的模式</p>
<pre><code class="java">@ConfigurationProperties(prefix = "spring.kafka")
public class KafkaMultiProperties {

    private Map&lt;String, KafkaProperties&gt; routes;

    public Map&lt;String, KafkaProperties&gt; getRoutes() {
        return routes;
    }

    public void setRoutes(Map&lt;String, KafkaProperties&gt; routes) {
        this.routes = routes;
    }
}
</code></pre>
<p>每一个route其实就说一套kafka，再写一个Configuration，注入配置文件</p>
<pre><code class="java">@Configuration
@EnableConfigurationProperties(KafkaMultiProperties.class)
public class KafkaConfiguration {

}
</code></pre>
<p>这样就可以注入配置了，从此可以根据配置的不同初始化不同的kafka集群逻辑。 这样就可以把自定义的Properties注入Springboot的placeholder中。</p>
<h3>2、通过自定义扫描支持消费者</h3>
<p>如果消费者或者发送者逻辑需要写在当前kafka网关应用，那么只能通过自定义扫描方式支持配置不同，所有配置的生成者和消费者必须代码实现逻辑，通过配置加载方式，自定义扫描注入bean即可。以消费者为例，生产者不涉及注解发送方式相对简单。</p>
<pre><code class="java">public class KafkaConfigInit {

    private KafkaMultiProperties kafkaMultiProperties;

    private ConfigurableApplicationContext applicationContext;

    public KafkaConfigInit(KafkaMultiProperties kafkaMultiProperties,
                           ConfigurableApplicationContext applicationContext) {
        this.kafkaMultiProperties = kafkaMultiProperties;
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void initConfig() {
        if (kafkaMultiProperties == null || kafkaMultiProperties.getRoutes() == null) return;
        kafkaMultiProperties.getRoutes().forEach((k, v) -&gt; {
            //register producer by config
            ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
            beanFactory.registerSingleton(k + "_producer", KafkaConfigUtil.initKafkaTemplate(v));

            //register consumer container factory
            KafkaListenerContainerFactory&lt;ConcurrentMessageListenerContainer&lt;Integer, String&gt;&gt; kafkaListenerContainerFactory = KafkaConfigUtil.initKafkaListenerContainerFactory(v);
            beanFactory.registerSingleton(k + "_consumerFactory", kafkaListenerContainerFactory);
        });
    }
}
</code></pre>
<p>写了一个初始化的bean，用于通过配置加载bean。但是有2个问题：</p>
<ol>
 <li>消费者是注解方式扫描，bean需要根据配置加载，不能写在代码里面</li>
 <li>这里仅仅是注册bean，并不会被beanpostprocessor处理</li>
</ol>
<p>关于第1点</p>
<p>因为需要按照配置加载，不能代码写bean的加载逻辑，只能自己扫描按照配置加载，那么需要自定义扫描注解和扫描包名（减少扫描范围，提高效率）</p>
<p>关于第2点</p>
<p>需要手动执行beanpostprocessor的逻辑即可</p>
<h4>show me the code</h4>
<p>完善刚刚写的部分代码：</p>
<p>写一个注解</p>
<pre><code class="java">@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface KafkaConfigConsumer {
    String beanId() default "";
}
</code></pre>
<p>通过beanId区分，配置文件的key+“_consumer"可以作为唯一标识，定义一种标准</p>
<p>可以使用Spring的</p>
<pre><code>PathMatchingResourcePatternResolver
</code></pre>
<p>自己解析resources信息，来拿到写的自定义注解的类，然后生成对象，注入Spring</p>
<pre><code class="java">public class KafkaConfigInit {

    private KafkaMultiProperties kafkaMultiProperties;

    private ConfigurableApplicationContext applicationContext;

    private KafkaListenerAnnotationBeanPostProcessor&lt;?,?&gt; kafkaListenerAnnotationBeanPostProcessor;

    private static final Map&lt;String, Object&gt; consumerMap = new ConcurrentHashMap&lt;&gt;();

    public KafkaConfigInit(KafkaMultiProperties kafkaMultiProperties, ConfigurableApplicationContext applicationContext, KafkaListenerAnnotationBeanPostProcessor&lt;?, ?&gt; kafkaListenerAnnotationBeanPostProcessor) {
        this.kafkaMultiProperties = kafkaMultiProperties;
        this.applicationContext = applicationContext;
        this.kafkaListenerAnnotationBeanPostProcessor = kafkaListenerAnnotationBeanPostProcessor;
    }

    @PostConstruct
    public void initConfig() throws IOException {
        scanConsumer();
        if (kafkaMultiProperties == null || kafkaMultiProperties.getRoutes() == null) return;
        kafkaMultiProperties.getRoutes().forEach((k, v) -&gt; {
            //register producer by config
            ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
            beanFactory.registerSingleton(k + "_producer", KafkaConfigUtil.initKafkaTemplate(v));

            //register consumer container factory
            KafkaListenerContainerFactory&lt;ConcurrentMessageListenerContainer&lt;Integer, String&gt;&gt; kafkaListenerContainerFactory = KafkaConfigUtil.initKafkaListenerContainerFactory(v);
            beanFactory.registerSingleton(k + "_containerFactory", kafkaListenerContainerFactory);

            beanFactory.registerSingleton(k+"_consumer", consumerMap.get(k+"_consumer"));
            kafkaListenerAnnotationBeanPostProcessor.postProcessAfterInitialization(consumerMap.get(k+"_consumer"), k+"_consumer");
        });
    }

    private void scanConsumer() throws IOException {
        SimpleMetadataReaderFactory register = new SimpleMetadataReaderFactory();
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "com/feng/kafka/demo/init/*");
        Arrays.stream(resources).forEach((resource)-&gt;{
            try {
                MetadataReader metadataReader = register.getMetadataReader(resource);
                if (metadataReader.getAnnotationMetadata().hasAnnotatedMethods("org.springframework.kafka.annotation.KafkaListener")){
                    String className = metadataReader.getClassMetadata().getClassName();
                    Class&lt;?&gt; clazz = Class.forName(className);
                    KafkaConfigConsumer kafkaConfigConsumer = clazz.getDeclaredAnnotation(KafkaConfigConsumer.class);
                    Object obj = clazz.newInstance();
                    consumerMap.put(kafkaConfigConsumer.beanId(), obj);
                }
            } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        });
    }

}
</code></pre>
<p>同时，需要手动执行</p>
<pre><code>kafkaListenerAnnotationBeanPostProcessor
</code></pre>
<p>的逻辑，上面有源码分析，而且因为要支持多数据源，所以需要修改消费者的注解参数</p>
<pre><code class="java">//@KafkaListener(topics = "${test.topic}")
//@Component
@KafkaConfigConsumer(beanId = "xxx_consumer")
public class KafkaConsumer {

    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumer.class);

    private String payload;

//    @KafkaHandler
    @KafkaListener(topics = "${test.topic}", beanRef = "xxx_listener", containerFactory = "xxx_containerFactory")
    public void receive(ConsumerRecord&lt;?, ?&gt; consumerRecord) {
        LOGGER.info("----------------received payload='{}'", consumerRecord.toString());
        payload = consumerRecord.toString();
    }


    // other getters


    public String getPayload() {
        return payload;
    }

    public void setPayload(String payload) {
        this.payload = payload;
    }
}
</code></pre>
<p><strong>增加beanRef属性外加我们自己写的注解</strong>，然后通过@Configuration注入</p>
<pre><code class="java">@Configuration
@EnableConfigurationProperties(KafkaMultiProperties.class)
public class KafkaConfiguration {

    @Bean
    public KafkaConfigInit initKafka(KafkaMultiProperties kafkaMultiProperties,
                                     ConfigurableApplicationContext applicationContext,
                                     KafkaListenerAnnotationBeanPostProcessor&lt;?, ?&gt; kafkaListenerAnnotationBeanPostProcessor){
        return new KafkaConfigInit(kafkaMultiProperties, applicationContext, kafkaListenerAnnotationBeanPostProcessor);
    }
}
</code></pre>
<p>然后修改配置文件和单元测试类</p>
<pre><code class="bash">spring:
  kafka:
    routes:
      xxx:
        producer:
          batchSize: 1
        consumer:
          auto-offset-reset: earliest
          group-id: xxx
</code></pre>
<p>然后修改单元测试代码</p>
<pre><code class="java">@SpringBootTest
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
class DemoMainTest {

    @Lazy
    @Autowired
    private KafkaConsumer consumer;

    @Autowired
    private ApplicationContext applicationContext;

    @Value("${test.topic}")
    private String topic;

    @Test
    void embedKafka() throws InterruptedException {
        String data = "Sending with our own simple KafkaProducer";

        applicationContext.getBean("xxx_producer", KafkaTemplate.class).send(topic, data);
        Thread.sleep(3000);
        assertThat(consumer.getPayload(), containsString(data));
        Thread.sleep(10000);
    }
}
</code></pre>
<p>执行单元测试成功</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3ad02deda2da41aca4b19f12a7b6a9dd.png&amp;size=m" alt=""></p>
<p>数据正确发送消费，断言正常</p>
<h3>3、通过字节码生成支持消费者</h3>
<p>上面的方式觉得还是不方便，一般而言处理消息和消费消息是异步的，即使是同步也不会在消费线程直接处理，一般是发送到其他地方接口处理，所以为啥还要写消费者代码呢，默认一个不就好了，但是注解参数确是常量，那么字节码生成一个唯一的类即可。</p>
<p>如果生成者和消费者处理逻辑不用网关应用处理，那么仅仅是无脑转发，类似zuul，可以通过字节码生成方式实现统一逻辑，主要是消费者，毕竟有注解，生产者不存在注解可以直接new出来注入bean。</p>
<p>以javassist为例，简单些，当然asm也可以</p>
<h4>show me the code</h4>
<p>其实就说把扫描的消费者类，变成固定某个类消费</p>
<pre><code class="java">//@KafkaListener(topics = "${test.topic}")
//@Component
//@KafkaConfigConsumer(beanId = "xxx_consumer")
public class KafkaConsumer {

    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumer.class);

    private String payload;

//    @KafkaHandler
//    @KafkaListener(topics = "${test.topic}", beanRef = "xxx_listener", containerFactory = "xxx_containerFactory")
    public void receive(ConsumerRecord&lt;?, ?&gt; consumerRecord) {
        LOGGER.info("----------------received payload='{}'", consumerRecord.toString());
        payload = consumerRecord.toString();
    }
</code></pre>
<p>去掉注解，因为注解需要我们动态加上去，下一步修改bean创建流程</p>
<pre><code class="java">    @PostConstruct
    public void initConfig() throws IOException {
//        scanConsumer();
        if (kafkaMultiProperties == null || kafkaMultiProperties.getRoutes() == null) return;
        kafkaMultiProperties.getRoutes().forEach((k, v) -&gt; {
            //register producer by config
            ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
            beanFactory.registerSingleton(k + "_producer", KafkaConfigUtil.initKafkaTemplate(v));

            //register consumer container factory
            KafkaListenerContainerFactory&lt;ConcurrentMessageListenerContainer&lt;Integer, String&gt;&gt; kafkaListenerContainerFactory = KafkaConfigUtil.initKafkaListenerContainerFactory(v);
            beanFactory.registerSingleton(k + "_containerFactory", kafkaListenerContainerFactory);

//            beanFactory.registerSingleton(k + "_consumer", consumerMap.get(k + "_consumer"));
            Object obj = initConsumerBean(k);
            beanFactory.registerSingleton(k + "_consumer", obj);
            kafkaListenerAnnotationBeanPostProcessor.postProcessAfterInitialization(obj, k + "_consumer");
        });
    }

    private Object initConsumerBean(String key) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ct = pool.getCtClass("com.feng.kafka.demo.init.KafkaConsumer");
            //修改类名，避免重复
            ct.setName("com.feng.kafka.demo.init.KafkaConsumer"+key);
            //获取类中的方法
            CtMethod ctMethod = ct.getDeclaredMethod("receive");
            MethodInfo methodInfo = ctMethod.getMethodInfo();
            ConstPool cp = methodInfo.getConstPool();
            //获取注解属性
            AnnotationsAttribute attribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
            Annotation annotation = new Annotation("org.springframework.kafka.annotation.KafkaListener", cp);
            ArrayMemberValue arrayMemberValue = new ArrayMemberValue(cp);
            arrayMemberValue.setValue(new MemberValue[]{new StringMemberValue("embedded-test-topic", cp)});
            annotation.addMemberValue("topics", arrayMemberValue);
            annotation.addMemberValue("beanRef", new StringMemberValue(key+"_listener", cp));
            annotation.addMemberValue("containerFactory", new StringMemberValue(key+"_containerFactory", cp));
            attribute.addAnnotation(annotation);
            methodInfo.addAttribute(attribute);
            byte[] bytes = ct.toBytecode();
            Class&lt;?&gt; clazz = ReflectUtils.defineClass("com.feng.kafka.demo.init.KafkaConsumer" + key, bytes, Thread.currentThread().getContextClassLoader());
            return clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
</code></pre>
<p>通过字节码生成和动态加载class方式，生成唯一的对象，实现通过配置方式支持多数据源，不需要写一句消费代码。</p>
<p>单元测试</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0dbf07a691834b01a2d4d9f11acf4719.png&amp;size=m" alt=""></p>
<p>去掉了断言，因为类是动态变化的了。</p>
<h2>总结</h2>
<p>实际上spring-kafka已经非常完善了，spring-kafka插件的支持也很完善，不需要关注kafka的消费过程，只需要配置即可，但是也为灵活性埋下了隐患，当然一般而言我们基本上用不到多kafka的情况，也不会做一个kafka网关应用，不过当业务需要的时候，可以设计一套kafka网关应用，分发kafka的消息，起到一个流量网关的能力，解耦业务的应用，实现架构的松耦合。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/fenglllle/article/details/142731737</p>]]></description><guid isPermaLink="false">/archives/a663066b-aea6-4c4f-90c6-4f7cda7ef17c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:40:36 GMT</pubDate></item><item><title><![CDATA[SpringBoot 集成 Redis]]></title><link>https://wiki.dgfygk.com/archives/b20a3e1a-b811-484c-b085-db5aa4dbb8e0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%20%E9%9B%86%E6%88%90%20Redis&amp;url=/archives/b20a3e1a-b811-484c-b085-db5aa4dbb8e0" width="1" height="1" alt="" style="opacity:0;">
<p>一：SpringBoot 集成 Redis
 <br>
 ①Redis是一个 NoSQL（not only）数据库， 常作用缓存 Cache 使用。</p>
<p>②Redis是一个中间件、是一个独立的服务器；常用的数据类型： string , hash ,set ,zset , list</p>
<p>③通过Redis客户端可以使用多种语言在程序中，访问 Redis 数据；java 语言中使用的客户端库有 Jedis，lettuce, Redisson 等。</p>
<p>④SpringBoot中使用 RedisTemplate（和StringRedisTemplate） 模版类操作 Redis 数据。</p>
<p>⑤Redis只要是运用在Linux服务器上，其中有两个重要的二进制文件</p>
<p>redis-server：服务端：在src目录下，执行./redis-server启动服务器端，不要关闭。</p>
<p>redis-cli：客户端： 在src目录下，执行./redis-cli启动客户端，访问redis中的数据。</p>
<p>
 <br>
</p>
<p><strong>第一步：创建SpingBoot项目，选择Web、Redis模块</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1ddef9ddc8cd44a28875406ef78da5b5.png&amp;size=m" alt=""></p>
<p>pom.xml配置</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1c5deb61dcdd463faef1aa03f10114b9.png&amp;size=m" alt=""></p>
<p>Redis使用是lettuce客户端</p>
<p>在程序中使用RedisTemplate类的方法去操作redis数据， 实际就是调用的lettuce客户端的中的方法！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5dde30c820a449cf900108a3a09de896.png&amp;size=m" alt=""></p>
<p><strong>第二步：配置核心配置文件 application.properties</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fca3ea6d3240f44d3a0caeaf23c0e2768.png&amp;size=m" alt="">打开redis.conf修改配置</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F432f56aef4014fab9dfd2a71ff894fe3.png&amp;size=m" alt=""></p>
<p><strong>编写Controller</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F945848daddbf43e284ffae2daadfb9eb.png&amp;size=m" alt=""></p>
<h3><strong>StringRedisTemplate 和 RedisTemplate</strong></h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7cf395b83cc941d2b7812eccf135f332.png&amp;size=m" alt=""></p>
<p>（1）序列化定义</p>
<p>序列化：把对象转化为可传输的字节序列过程称为序列化。</p>
<p>反序列化：把字节序列还原为对象的过程称为反序列化。</p>
<p>注：默认使用的jdk序列化， 可以修改为其它的序列化！</p>
<p>（2）为什么需要序列化？</p>
<p>序列化最终的目的是为了对象可以跨平台存储，和进行网络传输！而我们进行跨平台存储和网络传输的方式就是IO，而我们的IO支持的数据格式就是字节数组。我们必须在把对象转成字节数组的时候就制定一种规则（序列化），那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来（反序列化）。</p>
<p>（3）序列化的方式</p>
<p>序列化只是一种拆装组装对象的规则，那么这种规则肯定也可能有多种多样，比如现在常见的序列化方式有：JDK（不支持跨语言）、JSON（常用）、XML、Hessian、Kryo（高性能）、Thrift、Protofbuff。</p>
<p>java的序列化：把java对象转为byte[], 二进制数据。</p>
<p>json序列化：json序列化功能将对象转换为 JSON 格式或从 JSON 格式转换对象。例如把一个Student对象转换为JSON字符串{“name”:“李四”, “age”:29} )，反序列化(将JSON字符串 {“name”:“李四”, “age”:29} 转换为Student对象)</p>
<p>（4）设置RedisTemplate或者StringRedisTemplate的序列化方式</p>
<p>注：默认采用的是JDK的序列化机制，引入spring-boot-starter-data-redis依赖后由SpringBoot创建RedisTemplate或者StringRedisTemplate对象！可以设置Key的序列化，可以设置value的序列化，也可以同时设置。</p>
<p>package com.zl.controller;</p>
<p>import org.springframework.data.redis.core.RedisTemplate;
 <br>
 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
 <br>
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 <br>
 import org.springframework.web.bind.annotation.PostMapping;
 <br>
 import org.springframework.web.bind.annotation.RestController;</p>
<p>import javax.annotation.Resource;</p>
<p>@RestController
 <br>
 public class RedisController {</p>
<p>@Resource
 <br>
 private RedisTemplate redisTemplate;</p>
<p>@PostMapping(“/redis/addrstr”)
 <br>
 public String addString(String key,String value){</p>
<p>// 修改序列化方式
 <br>
 // 设置key,value为String的序列化
 <br>
 redisTemplate.setKeySerializer(new StringRedisSerializer());
 <br>
 redisTemplate.setValueSerializer(new StringRedisSerializer());
 <br>
 // 存数据
 <br>
 redisTemplate.opsForValue().set(key,value);
 <br>
 return “定义RedisTemplate对象的key，value的序列化”;
 <br>
 }
 <br>
 }
 <br>
 调用setKeySerializer设置key的序列化，参数是RedisSerializer接口对象</p>
<p>这个接口有很多实现类，其中一个就是默认采用的JDK序列化。</p>
<p>总结：实际上RedisTemplate使用范围比StringRedisTemplate要大。后者采用的就是String的序列化，专门用来处理字符串的。前者比较通用比如当前是一个Java对象，就需要使用RedisTemplate，然后修改序列化的方式即可！</p>
<p>（5） json序列化</p>
<p>第一步：准备一个可序列化的Java类，设置IDEA自动生成</p>
<p>如果我们不想手动输入序列化版本号，想让IDEA自动给我们生成一个怎们办呢？需要进行设置：File—&gt;Settings—&gt;Editor—&gt;Code Style—&gt;Inspections—&gt;JVM languages—&gt;把下面这个打上对勾—&gt;Apply—&gt;OK最终回到我们继承Serializable接口的类名上，alt+Enter即可自动生成序列版本号！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F82f13f0b4f5348dca46c892e10c84af5.png&amp;size=m" alt=""></p>
<p>：把Java对象以json格式存储到redis</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0ca894f5ec6341e38d8002e3db87e961.png&amp;size=m" alt=""></p>
<p>进行反序列化</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc475f2cd34dd4857b074a22432773bc7.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/2301_79438104/article/details/142750471</p>]]></description><guid isPermaLink="false">/archives/b20a3e1a-b811-484c-b085-db5aa4dbb8e0</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:40:13 GMT</pubDate></item><item><title><![CDATA[SpringBoot整合QQ邮箱]]></title><link>https://wiki.dgfygk.com/archives/cefc2c33-6ed9-4be3-a108-e61d2c22630d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E6%95%B4%E5%90%88QQ%E9%82%AE%E7%AE%B1&amp;url=/archives/cefc2c33-6ed9-4be3-a108-e61d2c22630d" width="1" height="1" alt="" style="opacity:0;">
<p>SpringBoot可以通过导入依赖的方式集成多种技术，这当然少不了我们常用的邮箱，现在本章演示SpringBoot整合QQ邮箱发送邮件….</p>
<p>下面按步骤进行：</p>
<h2>1.获取QQ邮箱授权码</h2>
<h3>1.1 登录QQ邮箱</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9e9a7023171c4e3b8fb005b99bd885bf.png&amp;size=m" alt=""></p>
<h3>1.2 开启SMTP服务</h3>
<p>找到下图中的SMTP服务区域，如果当前账号未开启的话自己手动开启。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Feaed8b22744f4e7aa80fb1b92f1339dd.png&amp;size=m" alt=""></p>
<h3>1.3 获取授权码</h3>
<p>进入上图中的【管理服务】后：在【安全设置中生成授权码】,也可以直接点击【继续生成授权码】来生成…</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa4f2b9bc31ff447fbbf31b612ae5e66a.png&amp;size=m" alt=""></p>
<h2>2.导入依赖</h2>
<p>自己新建一个SpringBoot项目，在pom.xml文件中导入依赖：</p>
<pre><code class="java">&lt;!-- 邮件依赖 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-mail&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h2>3.配置文件</h2>
<p>导入依赖后在配置文件中添加以下配置(本文章使用properties文件)：</p>
<pre><code class="java">spring.mail.host=smtp.qq.com
spring.mail.port=587
# 用于发送验证码的qq邮箱
spring.mail.username=（此处换成自己的QQ邮箱地址）
# 邮箱授权码
spring.mail.password=（换成上面邮箱的授权码）
spring.mail.protocol=smtp
spring.mail.properties.mail.smtp.auth=true
# 使用587接口需要启用STARTTLS加密
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
</code></pre>
<h2>4.发送邮件</h2>
<p><strong>此处只演示service层发送邮件代码。</strong></p>
<pre><code class="java">@Service
public class MailServer {

    @Autowired
    private JavaMailSender javaMailSender;

    //发送邮件方法
    public ResponseResult sendAuthCode(String emailAddress) throws MessagingException, UnsupportedEncodingException {

        MimeMessage mimeMailMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage);

        String myEmail = "我的邮箱";
        String displayName = "发送邮箱时显示的名称";  //这个可以不选 
        String emailAddress = "目标邮箱地址";
        String subject = "邮件主题";   
        String text = "邮件内容";    

        mimeMessageHelper.setFrom(myEmail,displayName);
        mimeMessageHelper.setTo(emailAddress);
        mimeMessageHelper.setSubject(subject);
        mimeMessageHelper.setText(text);

        try {
            javaMailSender.send(mimeMailMessage);
            authCodeUtil.setAuthCodeIntoRedis(emailAddress,authCode);
        } catch (MailException e) {
            return "无法发送邮件";
        }
        return "发送成功"；
    }

}
</code></pre>
<h2>可能遇见的问题</h2>
<p>在springboot整合QQ邮箱的过程中，可能回遇到 “连接超时”，这时检查配置文件中的邮箱地址、授权码，如果这两项没错的话可能是网络问题，我们可以在终端ping一下qq邮箱的邮件发送服务器看一下当前网络是否可以连接qq邮箱，如下图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fdb6444f84c574a169de5b0a6df51e068.png&amp;size=m" alt=""></p>
<p>如果结果是"请求超时"的话建议换个网络再尝试。。。。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/2301_81034686/article/details/142698072</p>]]></description><guid isPermaLink="false">/archives/cefc2c33-6ed9-4be3-a108-e61d2c22630d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:40:00 GMT</pubDate></item><item><title><![CDATA[【计算机网络】网络层协议解析]]></title><link>https://wiki.dgfygk.com/archives/e5d6d97a-5103-439a-b76b-214c4af5d688</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E3%80%91%E7%BD%91%E7%BB%9C%E5%B1%82%E5%8D%8F%E8%AE%AE%E8%A7%A3%E6%9E%90&amp;url=/archives/e5d6d97a-5103-439a-b76b-214c4af5d688" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#_9">网络层的两种服务</a></li>
 <li><a href="https://wiki.dgfygk.com#IPv4_11">IPv4</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_13">分类编址</a></li>
 <li><a href="https://wiki.dgfygk.com#_22">划分子网</a></li>
 <li><a href="https://wiki.dgfygk.com#_23">无分类地址</a></li>
 <li><a href="https://wiki.dgfygk.com#IPv4_24">IPv4地址应用</a></li>
 <li><a href="https://wiki.dgfygk.com#IP_34">IP数据报的发送和转发过程</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#IP_35">主机发送IP数据报</a></li>
 <li><a href="https://wiki.dgfygk.com#IP_37">路由器转发IP数据报</a></li>
 <li><a href="https://wiki.dgfygk.com#IPv4_42">IPv4数据报首部格式</a></li>
 <li><a href="https://wiki.dgfygk.com#ICMP_45">ICMP网际控制报文协议</a></li>
 <li><a href="https://wiki.dgfygk.com#VPNNAT_60">虚拟专用网VPN与网络地址转换NAT</a></li>
</ul>
<p>
 <br>
</p>
<p>网络层主要任务是实现网络互连，进而实现数据包在各网络之间的传输。
 <br>
 因特网是目前全世界用户数量最多的互联网，它使用TCP/IP协议栈。
 <br>
 由于TCP/IP协议栈的网络层使用网际协议IP，它是整个协议栈的核心协议，因此在TCP/IP协议栈中网络层通常称为网际层</p>
<h2>网络层的两种服务</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcaea97adf2a74690b5cfebc934a8325f.png&amp;size=m" alt=""></p>
<h2>IPv4</h2>
<p>IPv4地址是因特网上的每一台主机的每一个接口分配在一个全世界范围内的唯一32比特的标识符。</p>
<h3>分类编址</h3>
<p>A类地址：8位网络号（0开头）+24位主机号
 <br>
 B类地址：16位网络号（10开头）+ 16位主机号
 <br>
 C类地址：24位网络号（110）+8位主机号
 <br>
 D类地址：多播地址（1110）
 <br>
 E类地址：保留为今后使用（1111）
 <br>
 只有ABC类地址可分配给网络中的主机或路由器的各接口
 <br>
 主机号为全0的地址是网络地址，不能分配给主机或路由器的各接口
 <br>
 主机号为全1的地址是广播地址，不能分配给主机或路由器的各接口</p>
<h3>划分子网</h3>
<h3>无分类地址</h3>
<h2>IPv4地址应用</h2>
<p>定长的子网掩码FLSM
 <br>
 使用同一个子网掩码来划分子网，子网划分方式不灵活只能划分出2的n次方个子网，每个子网所分配的IP地址数量相同，容易造成IP地址浪费。
 <br>
 变长的子网掩码VLSM
 <br>
 使用不同子网掩码来划分子网，网划分方式灵活，可以按需分配，每个子网所分配的IP地址数量可以不同，不容易造成IP地址浪费。</p>
<p>中继器和集线器工作在物理层，既不隔离冲突域也不隔离广播域 。
 <br>
 网桥和交换机工作在数据链路层，可以隔离冲突域，但是不能隔离广播域
 <br>
 路由器工作在网络层，既隔离冲突域，又隔离广播域。</p>
<h2>IP数据报的发送和转发过程</h2>
<h3>主机发送IP数据报</h3>
<p>判断目的主机是否与自己在同一个网络，如果在同一网络则直接交付，如果不在同一网络则属于间接交付，传输给主机所在网络的默认网关（路由器）由默认路由器帮忙转发</p>
<h3>路由器转发IP数据报</h3>
<p>检查IP数据报首部是否出错，出错则直接丢弃IP数据报并通告源主机，如果没有出错则直接进行转发。
 <br>
 检查IP数据报的目的地址在路由表中查找匹配的条目
 <br>
 找到匹配条目，则转发给条目中指示的下一跳，如果找不到，则丢弃该IP数据报并通告源主机</p>
<h2>IPv4数据报首部格式</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe6124da1dcc14f98ad9f0b6cfe5443f8.png&amp;size=m" alt=""></p>
<h2>ICMP网际控制报文协议</h2>
<p>为了更有效的转发IP数据报和提高交付成功的机会，在网际层使用了网际控制报文协议ICMP。
 <br>
 主机或路由器使用ICMP来发送差错报告报文和查询报文。
 <br>
 ICMP报文被封装在IP数据报中发送。
 <br>
 ICMP差错报告报文有五种：终点不可达（路由器或主机不能交付数据报时）、源点抑制（路由器或主机由于拥塞而丢弃数据报时）、时间超过（目的IP地址不是自己的数据报、TTL值会减1）、参数问题（首部在传输过程中出现误码）、改变路由（重定向）</p>
<p>对于以下情况将不会发送ICMP差错报告报文：
 <br>
 对于ICMP差错报告报文不再发送ICMP差错报告报文
 <br>
 对于第一个分片的数据报的所有后续数据报片都不发送ICMP差错报告报文
 <br>
 对于具有多播地址的数据报都不发送ICMP差错报告报文
 <br>
 对于具有特殊地质的（127.0.0.1、0.0.0.0等）都不发送ICMP差错报告报文</p>
<p>ICMP询问报文：回送请求和回答、时间戳请求和回答
 <br>
 ICMP应用：分组网间探测PING、跟踪路由tracterout</p>
<h2>虚拟专用网VPN与网络地址转换NAT</h2>
<p>利用公用的因特网作为本机构各专用网之间的通信载体，这样的专用网又称为虚拟专用网。
 <br>
 同一机构内不同部门的内部网络所构成的虚拟专用网VPN又称为内联网。
 <br>
 VPN要保证传输数据的安全性，会将原始的内部数据报进行加密，然后再将其封装成在因特网上发送的外部数据报。</p>
<p>由于IP地址的紧缺，一个机构能够申请到的IP地址数量往往远小于本机构所拥有的主机数量。因此，虚拟专用网中各主机分配的地址应该是本机构可自由分配的专用地址，而不是需要申请的、在因特网上使用的公有地址。
 <br>
 NAT使大量的使用内部专用地址的专用网络用户共享少量外部全球地址来访问因特网上的主机和资源。
 <br>
 NAPT技术利用运输层的端口号和IP地址一起进行转换，用一个全球IP地址就可以使用多个拥有本地地址的主机同时和因特网上的主机进行通信。</p>
<p>原文链接: https://blog.csdn.net/2202_75623950/article/details/140909099</p>]]></description><guid isPermaLink="false">/archives/e5d6d97a-5103-439a-b76b-214c4af5d688</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 8 Oct 2024 03:39:29 GMT</pubDate></item><item><title><![CDATA[Mybatis的基本使用]]></title><link>https://wiki.dgfygk.com/archives/06193ef2-755a-4d59-a396-aff5fdb58528</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Mybatis%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8&amp;url=/archives/06193ef2-755a-4d59-a396-aff5fdb58528" width="1" height="1" alt="" style="opacity:0;">
<h2>什么是Mybatis？</h2>
<ol>
 <li>Mybatis是一个简化JDBC的持久层框架，MyBatis是一个半自动化框架，是因为它在SQL执行过程中<strong>只提供了基本的SQL执行功能</strong> ，而没有像Hibernate那样将所有的ORM操作都自动化了。在MyBatis中，需要手动编写SQL语句，但是它提供了很多便捷的操作，例如参数映射、结果集映射、缓存机制等等，使得开发者能够更加灵活地操作SQL。此外，MyBatis还提供了许多插件机制，可以<strong>对SQL执行流程进行拦截和修改</strong>，以满足各种不同的需求。这也是MyBatis相对于其他ORM框架的一个优点。</li>
 <li><strong>持久层</strong> ：负责<strong>将数据保存到数据库</strong>的那一层代码</li>
 <li>JavaEE三层架构：表现层，业务层，持久层</li>
 <li>框架：写好部分代码的半成品软件</li>
</ol>
<h3>JDBC的缺点：</h3>
<ol>
 <li>硬编码：</li>
 <li><strong>注册驱动，获取连接</strong>的那部分代码不灵活，改变的时候会很麻烦</li>
 <li>写死的SQL语句没有那么灵活</li>
 <li>操作繁琐</li>
 <li>需要手动设置参数（prepareStatement）</li>
 <li>手动封装结果集（resultSet）</li>
</ol>
<h3>Mybatis的优点</h3>
<ol>
 <li>硬编码–》利用**配置文件（xml）**可以减少硬编码的编写</li>
 <li>操作繁琐–》底层自动完成参数的设置以及结果集的封装</li>
 <li>Mybatis 免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作</li>
</ol>
<h3><a href="https://editor.csdn.net/md/?articleId=133866799">Mybatis快速入门（mybatis-demo）</a></h3>
<ul>
 <li>非maven项目要使用 MyBatis， 只需将 <a href="https://github.com/mybatis/mybatis-3/releases">mybatis-x.x.x.jar</a>文件置于类路径（classpath）中即可。</li>
 <li>如果使用 Maven 来构建项目，则需将下面的依赖代码置于 pom.xml 文件中</li>
</ul>
<h2>Mapper代理开发：</h2>
<p><strong>使用Mapper代理方式，必须满足以下要求：</strong></p>
<ol>
 <li>定义与SQL映射文件同名的Mapper接口，并且将Mapper接口和SQL映射文件放置在同一目录下（紧对于使用包扫描方式映射xml文件）。如下图：
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2a7e7f0e38139e11ed6d4911662cc11d.png&amp;size=m" alt="">
  <br>
  <strong>注意：这里的UserMapper.xml里面的内容可以到mybatis官网上面粘贴进来的，然后加以修改一下就能成为我们自己的代码了</strong></li>
</ol>
<pre><code class="prism language-xml">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;
&lt;mapper namespace="com.knife.mapper.BrandMapper"&gt;&lt;!--  只有这里namespace的路径是需要修改的  --&gt;
&lt;/mapper&gt;
</code></pre>
<ol>
 <li>
  <p>设置SQL映射文件的namespace属性为<strong>Mapper接口全限定名</strong> （即对应mapper接口的路径）
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9de34b12470a4aba4c8e63d73237be03.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>在 Mapper 接口中定义方法，<strong>方法名就是SQL映射文件中sql语句的id</strong> ，并<strong>保持参数类型和返回值类型一致</strong>
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F847ed372265d5952857fbcc96a858846.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>注意：</p>
 </li>
 <li>
  <p><strong>如果Mapper接口名称和SQL映射文件名称相同，并在同一目录下</strong> ，则<code>可以使用包扫描的方式</code>简化SQL映射文件的加载（即在mybatis-config.xml文件下配置）。也就是将核心配置文件的加载映射配置文件的配置修改为</p>
 </li>
</ol>
<pre><code class="prism language-xml">&lt;mappers&gt;
&lt;!--加载sql映射文件--&gt;
&lt;!-- &lt;mapper resource="com/itheima/mapper/UserMapper.xml"/&gt;--&gt;
&lt;!--Mapper代理方式--&gt;
&lt;package name="com.itheima.mapper"/&gt;
&lt;/mappers&gt;
</code></pre>
<h3>Mybatis的核心配置（mybatis-config.xml）</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F58624a65b1199affc4f45a847739a239.png&amp;size=m" alt=""></p>
<h4>环境配置</h4>
<ol>
 <li>在核心配置文件的 environments 标签中其实是可以配置多个 environment ，使用 id 给每段环境起名，在environments 中使用 default='环境id' 来指定使用哪儿段配置。我们一般就配置一个 environment 即可。</li>
</ol>
<pre><code class="prism language-xml">&lt;environments default="development"&gt;
    &lt;environment id="development"&gt;
        &lt;transactionManager type="JDBC"/&gt;
        &lt;dataSource type="POOLED"&gt;
        &lt;!--数据库连接信息--&gt;
         &lt;property name="driver" value="com.mysql.cj.jdbc.Driver"/&gt;
        &lt;property name="url" value="jdbc:mysql://localhost:3306/db1?useSSL=false&amp;amp;serverTimeZone=UTC"/&gt;
        &lt;property name="username" value="root"/&gt;
        &lt;property name="password" value="1234"/&gt;
        &lt;/dataSource&gt;
    &lt;/environment&gt;

    &lt;environment id="test"&gt;
        &lt;transactionManager type="JDBC"/&gt;
        &lt;dataSource type="POOLED"&gt;
        &lt;!--数据库连接信息--&gt;
         &lt;property name="driver" value="com.mysql.cj.jdbc.Driver"/&gt;
        &lt;property name="url" value="jdbc:mysql://localhost:3306/db1?useSSL=false&amp;amp;serverTimeZone=UTC"/&gt;
        &lt;property name="username" value="root"/&gt;
        &lt;property name="password" value="1234"/&gt;
        &lt;/dataSource&gt;
    &lt;/environment&gt;
&lt;/environments&gt;
</code></pre>
<ol>
 <li>类型别名
  <br>
  在映射配置文件中的 resultType 属性需要配置数据封装的类型（类的全限定名（即类的路径））。而每次这样写是特别麻烦的，Mybatis提供了 类型别名 (typeAliases) 可以简化这部分的书写。
  <br>
  首先需要现在核心配置文件中配置类型别名，也就意味着给pojo包下所有的类起了别名（<strong>默认的别名就是：实体类名</strong> ），不区分大小写。
  <br>
  内容如下：</li>
</ol>
<pre><code class="prism language-xml">&lt;typeAliases&gt;
    &lt;!--name属性的值是实体类所在包--&gt;
    &lt;package name="com.itheima.pojo"/&gt;
&lt;/typeAliases&gt;
</code></pre>
<p>通过上述的配置，我们就可以简化映射配置文件中 resultType 属性值的编写</p>
<pre><code class="prism language-xml">&lt;mapper namespace="com.itheima.mapper.UserMapper"&gt;
    &lt;select id="selectAll" resultType="user"&gt;
    select * from tb_user;
    &lt;/select&gt;
&lt;/mapper&gt;
</code></pre>
<p>配置各个标签需要遵循前后顺序（即上面图片的那个顺序）</p>
<h2>一、查询</h2>
<h3>1、查询所有</h3>
<p><strong>实体类属性名和数据库表的列名不一致，则idea不能自动封装数据</strong></p>
<p>解决方法：</p>
<pre><code class="prism language-xml"> * 方法一: 起别名 :对不一样的字段名起别名,让别名和实体类的属性名一样
           - 缺点:每次查询都要定义一次别名
&lt;select id="selectAll" resultType="brand"&gt;
        select id , brand_name as brandName, company_name as companyName, ordered,description,status  from tb_brand;
&lt;/select&gt;
              * 解决方法:使用sql片段
                &lt;sql id="brand_column"&gt;
                   id , brand_name as brandName, company_name as companyName, ordered,description,status
                &lt;/sql&gt;
                &lt;select id="selectAll" resultType="brand"&gt;
                    select  &lt;include refid="brand_column"&gt;&lt;/include&gt; from tb_brand;
                &lt;/select&gt;
                * 缺点:sql片段不灵活
                   -解决:方法二

        * 方法二:resultMap:
          1. 在&lt;mapper namespace="com.itheima.mapper.BrandMapper"&gt;&lt;/mapper&gt;标签中定义resultMap标签
          2. 在&lt;select&gt;标签中使用resultMap属性替换resultType属性

 &lt;!--
      id: 唯一标识
      type:映射的类型(支持别名)
    --&gt;
    &lt;resultMap id="brandResultMap" type="brand"&gt;
        &lt;!--
          id:完成主键字段的映射
          result:完成一般字段的映射
              column: 该字段在数据库中的列名
              property: 该字段在实体类pojo的属性名
             &lt;id column="brand_name" property="brandName"/&gt;//主键字段的映射
        --&gt;
         &lt;result column="brand_name" property="brandName"/&gt;
        &lt;result column="company_name" property="companyName"/&gt;
    &lt;/resultMap&gt;
</code></pre>
<h3>2、Id查询</h3>
<pre><code class="prism language-xml">&lt;!--
       *参数占位符:
        1. #{};会替换为 ?, 为了防止SQl注入
        2. ${}; 拼sql语句,会存在sql注入问题
        3. 使用时机:
            * 参数传递的时候,使用#{}
            * 数据库表名或者列名不固定的时候,使用${}


         * 参数类型:
             parameterType="int";可以省略
             &lt;select id="selectById" parameterType="int" resultMap="brandResultMap"&gt;
                 select * from tb_brand where id = ${id};
             &lt;/select&gt;
         * 特殊字符处理:
            1. 转移字符:
             &lt;select id="selectById" resultMap="brandResultMap"&gt;
                     select * from tb_brand where id &amp;lt; ${id};
              &lt;/select&gt;
            2. CDATA区://其实就相当于文本框；&lt;![CDATA[ 这里就是填数据的地方 ]]&gt;
             &lt;select id="selectById" resultMap="brandResultMap"&gt;
                 select * from tb_brand where id &lt;![CDATA[ &lt; ]]&gt; ${id};
              &lt;/select&gt;
    --&gt;
</code></pre>
<h3>3、多条件查询</h3>
<pre><code class="prism language-xml">//    多条件查询

    /**
     *  * 参数接收:
     *      1. 散装参数:如果方法中有！！！多个参数！！！,需要使用@Param("sql参数占位符名称")
     *      2. 对象参数: 对象的属性名称要和参数占位符名称一致
     *      3. map集合: 只需保证sql中的参数名和map集合的键的名称对应上
     * @param status
     * @param companyName
     * @param brandName
     * @return
     */
//    List&lt;Brand&gt; selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
//    List&lt;Brand&gt; selectByCondition(Brand brand);
    List&lt;Brand&gt; selectByCondition(Map map);
</code></pre>
<h3>一、动态查询</h3>
<ul>
 <li>动态sql：sql语句会随着用户的输入或者外部条件的变化而变化</li>
</ul>
<pre><code class="prism language-xml">&lt;!--动态条件查询
       * if : 条件查询
        * test : 逻辑表达式
          * 产生的问题:第一个条件不需要逻辑运算符
           &lt;if test="companyName !=null and companyName !='' "&gt;
                and company_name like #{companyName}
            &lt;/if&gt;

               *解决1: 多写一个恒等式: 1=1
                   &lt;select id="selectByCondition" resultMap="brandResultMap"&gt;
                        select * from tb_brand
                        where 1=1
                            &lt;if test="status != null"&gt;
                                and status =#{status}
                            &lt;/if&gt;
                            &lt;if test="companyName !=null and companyName !='' "&gt;
                                and company_name like #{companyName}
                            &lt;/if&gt;
                          &lt;if test="brandName !=null and brandName !='' "&gt;
                              and brand_name like #{brandName};
                          &lt;/if&gt;

                    &lt;/select&gt;
              *解决二:where标签替换where

  --&gt;


 &lt;select id="selectByCondition" resultMap="brandResultMap"&gt;
        select * from tb_brand
        where
            &lt;if test="status != null"&gt;
                status =#{status}
            &lt;/if&gt;
            &lt;if test="companyName !=null and companyName !='' "&gt;
                and company_name like #{companyName}
            &lt;/if&gt;
          &lt;if test="brandName !=null and brandName !='' "&gt;
              and brand_name like #{brandName};
          &lt;/if&gt;
    &lt;/select&gt;
//上面的一段代码单单用一个if标签是不能满足需求的），存在的问题是where后面的第一个条件不需要and，所以很难满足
//使用动态sql就可以解决

  &lt;select id="selectByCondition" resultMap="brandResultMap"&gt;
        select * from tb_brand
        &lt;where&gt;
            &lt;if test="status != null"&gt;
               and  status =#{status}
            &lt;/if&gt;
            &lt;if test="companyName !=null and companyName !='' "&gt;
                and company_name like #{companyName}
            &lt;/if&gt;
            &lt;if test="brandName !=null and brandName !='' "&gt;
                and brand_name like #{brandName}
            &lt;/if&gt;
        &lt;/where&gt;

    &lt;/select&gt;
</code></pre>
<h3>一、动态查询——单条件查询</h3>
<pre><code class="prism language-xml">&lt;select id="selectByConditionSingle" resultMap="brandResultMap"&gt;
        select * from tb_brand
   where
       &lt;choose&gt; &lt;!--相当于swith--&gt;

           &lt;when test="status !=null"&gt;&lt;!--相当于case--&gt;
               status =#{status}
           &lt;/when&gt;

           &lt;when test="companyName !=null and companyName !=''"&gt;&lt;!--相当于case--&gt;
               company_name like #{companyName}
           &lt;/when&gt;

           &lt;when test="brandName !=null and brandName !=''"&gt;&lt;!--相当于case--&gt;
               brand_name like #{brandName}
           &lt;/when&gt;
           &lt;otherwise&gt; &lt;!--相当于default--&gt;
               1=1
           &lt;/otherwise&gt;
       &lt;/choose&gt;
    &lt;/select&gt;


//查询单个的
&lt;select id="selectByConditionSingle" resultMap="brandResultMap"&gt;
        select * from tb_brand
   where
       &lt;choose&gt; &lt;!--相当于swith--&gt;

           &lt;when test="status !=null"&gt;&lt;!--相当于case--&gt;
               status =#{status}
           &lt;/when&gt;

           &lt;when test="companyName !=null and companyName !=''"&gt;&lt;!--相当于case--&gt;
               company_name like #{companyName}
           &lt;/when&gt;

           &lt;when test="brandName !=null and brandName !=''"&gt;&lt;!--相当于case--&gt;
               brand_name like #{brandName}
           &lt;/when&gt;
           &lt;otherwise&gt; &lt;!--相当于defaut--&gt;
               1=1
           &lt;/otherwise&gt;
       &lt;/choose&gt;
    &lt;/select&gt;
//使用where标签的改进
&lt;select id="selectByConditionSingle" resultMap="brandResultMap"&gt;
        select * from tb_brand
        &lt;where&gt;
            &lt;choose&gt; &lt;!--相当于swith--&gt;

                &lt;when test="status !=null"&gt;&lt;!--相当于case--&gt;
                    status =#{status}
                &lt;/when&gt;

                &lt;when test="companyName !=null and companyName !=''"&gt;&lt;!--相当于case--&gt;
                    company_name like #{companyName}
                &lt;/when&gt;

                &lt;when test="brandName !=null and brandName !=''"&gt;&lt;!--相当于case--&gt;
                    brand_name like #{brandName}
                &lt;/when&gt;

            &lt;/choose&gt;
        &lt;/where&gt;
    &lt;/select&gt;
</code></pre>
<h2>二、添加数据</h2>
<h3>添加</h3>
<pre><code class="prism language-java">public void testAdd() throws IOException {

//        1.接收参数
        int status=1;
        String companyName="华为";
        String brandName ="波导";
        String description="手机中的战斗机";
        int ordered=100;

//        封装参数
        Brand brand = new Brand();
        brand.setStatus(status);
        brand.setBrandName(brandName);
        brand.setCompanyName(companyName);
        brand.setDescription(description);
        brand.setOrdered(ordered);

//        Map集合
//        Map map = new HashMap();
//        map.put("status",status);
//        map.put("companyName",companyName);
//        map.put("brandName",brandName);


//        1. 获取SqlSessionFactory对象

        String resource = "mybatis-config.xml";//配置文件的路径
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

//        2. 获取SqlSession对象
//        SqlSession sqlSession = sqlSessionFactory.openSession(); //默认开启事务，进行增删改查需要使用sqlSession.commit();手动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);//设置自动提交事务 
//        3. 获取Mapper对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);

        brandMapper.add(brand);
//        手动提交事务
        sqlSession.commit();
//        5. 释放资源
        sqlSession.close();

    }
</code></pre>
<h4>添加—–主键返回</h4>
<ul>
 <li><strong>默认</strong>没有主键返回的（即id）</li>
</ul>
<pre><code class="prism language-xml">&lt;insert id="add"&gt;
        insert into tb_brand(brand_name, company_name,ordered,description,status)
         values (#{companyName},#{brandName},#{ordered},#{description},#{status});
    &lt;/insert&gt;
    //其实当我们指向这个sql语句的时候，id值就已经存在的了，只是没有直接绑定到该对象里面，我们没有去获取而已，下面的代码就是获取id值（这里id是主键）
</code></pre>
<ul>
 <li><strong>有主键返回</strong> ：</li>
</ul>
<pre><code class="prism language-xml">//获取到主键值后，将来就可以通过该实体类对象对应的get方法得到
&lt;!--keyProperty指向主键名--&gt;
    &lt;insert id="add" useGeneratedKeys="true" keyProperty="id"&gt;
        insert into tb_brand(brand_name, company_name,ordered,description,status)
        values (#{companyName},#{brandName},#{ordered},#{description},#{status});
    &lt;/insert&gt;
</code></pre>
<h2>三、修改</h2>
<h3>三、1、修改全部字段</h3>
<pre><code class="prism language-xml">&lt;!--    修改全部字段--&gt;
    &lt;update id="update"&gt;
        update tb_brand
         set brand_name = #{brandName},
             company_name = #{companyName},
             ordered = #{ordered},
             description = #{description},
             status = #{status}
         where id =#{id};
    &lt;/update&gt;
</code></pre>
<h3>三、2、修改动态字段（将来修改哪个字段是不固定的）</h3>
<ul>
 <li>原来的：</li>
</ul>
<pre><code class="prism language-xml"> &lt;update id="update"&gt;
        update tb_brand
         set brand_name = #{brandName},
             company_name = #{companyName},
             ordered = #{ordered},
             description = #{description},
             status = #{status}
         where id =#{id};
    &lt;/update&gt;
</code></pre>
<ul>
 <li>使用<code>动态sql</code>：</li>
</ul>
<pre><code class="prism language-xml">&lt;!--    修改动态sql--&gt;
    &lt;update id="update"&gt;
        update tb_brand
         &lt;set&gt;
            &lt;if test="brandName != null and brandName !='' "&gt;
                brand_name = #{brandName},
            &lt;/if&gt;
            &lt;if test="companyName != null and companyName !=''"&gt;
                company_name = #{companyName},
            &lt;/if&gt;
            &lt;if test="ordered != null"&gt;
                ordered = #{ordered},
            &lt;/if&gt;
            &lt;if test="description != null and description != ''"&gt;
                description = #{description},
            &lt;/if&gt;
            &lt;if test="status != null"&gt;
                status = #{status}
            &lt;/if&gt;
         &lt;/set&gt;
        where id =#{id};
    &lt;/update&gt;
</code></pre>
<h2>四、删除</h2>
<h3>四、1、删除一条记录</h3>
<pre><code class="prism language-xml">&lt;!--   根据id进行 删除--&gt;
    &lt;delete id="deleteById"&gt;
        delete from tb_brand where id = #{id};
    &lt;/delete&gt;
</code></pre>
<h3>四、2、批量删除</h3>
<ol>
 <li>使用数组进行作为参数</li>
</ol>
<pre><code class="prism language-xml">&lt;!--    批量删除--&gt;
&lt;!--    mybatis会将数组参数封装到一个Map集合
            * 默认key名字：array
            * 可以用  @Param("ids")注解改变map集合的默认key（map里面的键）名称

        * 1.collection:要遍历的目标集合
        * open:循环开始时的字符;close:循环结束时的字符
        * item:遍历出的集合成员,自己定义的变量
        * separator:结合成员之间的分隔符
        * #{item}:注意与item="item"中的的值相同（即变量名要相同）
--&gt;
    &lt;delete id="deleteByIds"&gt;
        delete from tb_brand where id
          in (
              &lt;foreach collection="ids" item="id" separator="," open="(" close=")"&gt;
                  #{id}
              &lt;/foreach&gt;
               );
    &lt;/delete&gt;
</code></pre>
<h2>Mybatis参数传递</h2>
<p>Mybatis接口方法中可以接收各种各样的参数，Mybatis底层对于这些参数进行不同的封装处理方式</p>
<h3>1.单个参数的封装：</h3>
<p>-参数是 POJO类型：直接使用，实体类属性名和参数占位符名称一致</p>
<ul>
 <li>参数是 Map集合：直接使用，键名 和参数占位符一致</li>
 <li>参数是 Collection：封装为Map集合</li>
</ul>
<pre><code class="prism language-java">//两种方法
map.put("collection",collection集合);
map.put("arg0",collection集合);
</code></pre>
<ul>
 <li>参数是 List：封装为Map集合</li>
</ul>
<pre><code class="prism language-java">map.put("collection",list集合);
map.put("list",list集合);
map.put("arg0",list集合);
</code></pre>
<ul>
 <li>参数是 Array:封装为Map集合</li>
</ul>
<pre><code class="prism language-java">map.put("array",数组);
map.put("arg0",数组);
</code></pre>
<ul>
 <li>参数是 其它类型：直接使用</li>
</ul>
<h3>2.多个参数：</h3>
<p>多个参数会被封装为map集合，可以使用@param()注解，来替换map集合中默认的arg键名</p>
<pre><code class="prism language-java">//两种
map.put("arg0",参数值1);
map.put("arg1",参数值2);


map.put("param1",参数值1);
map.put("param2",参数值2);
</code></pre>
<ul>
 <li>建议：<strong>将来都使用@Param注解来修改Map集合（多个参数，系统会默认把参数封装成集合）中的默认键名，并使用修改后的名称来获取值，这样可读性更高</strong></li>
 <li>首先明确这个@Param注解是为SQL语句中参数赋值而服务的。
  <br>
  @Param的作用就是给参数命名，比如在mapper里面某方法A（int id），当添加注解后A（@Param(“userId”) int id），也就是说外部想要取出传入的id值，只需要取它的参数名userId就可以了。将参数值传如SQL语句中，通过#{userId}进行取值给SQL的参数赋值。</li>
</ul>
<h4>实例一：@Param注解基本类型的参数</h4>
<p>*</p>
<ol>
 <li>mapper中的方法：</li>
</ol>
<pre><code class="prism language-java">public User selectUser(@Param("userName") String name,@Param("password") String pwd);
</code></pre>
<p>映射到xml中的标签</p>
<pre><code class="prism language-java">&lt;select id="selectUser" resultMap="User"&gt;  
   select * from user  where user_name = #{
   userName} and user_password=#{
   password}  
&lt;/select&gt;
</code></pre>
<ul>
 <li>
  <p>其中where user_name = #{userName} and user_password = #{password}中的userName和password都是从注解@Param（）里面取出来的，取出来的值就是方法中形式参数 String name 和 String pwd的值。</p>
  <p>当使用了@Param注解来声明参数的时候，SQL语句取值使用#{}, ${}取值都可以。
   <br>
   当不使用@Param注解声明参数的时候，必须使用的是#{}来取参数。使用${}方式取值会报错。</p>
 </li>
</ul>
<h2>注解开发</h2>
<ol>
 <li>
  <p>使用注解开发会比配置文件开发更加简单方便</p>
 </li>
 <li>
  <p>查询：@Select</p>
 </li>
 <li>
  <p>添加：@Insert</p>
 </li>
 <li>
  <p>修改：@Update</p>
 </li>
 <li>
  <p>删除：@Delete
   <br>
   提示：注解开发完成简单功能，配置文件开发完成复杂功能</p>
 </li>
 <li>
  <p>xml开发，需要配置xml文件中设置</p>
 </li>
</ol>
<pre><code class="prism language-xml"> &lt;select id="selectById" resultType="user"&gt;
        select * from tb_user where id =#{id};
  &lt;/select&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F29d0ff7d8908f9a42aa42e051908ab33.png&amp;size=m" alt="">
 <br>
 注解开发：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F10da0386f477f79ec9894360196c6cbd.png&amp;size=m" alt=""></p>
<ul>
 <li><strong>使用注解来映射简单语句</strong> 会使代码显得更加简洁，但对于稍微复杂一点的语句，Java 注解不仅力不从心，还会让你本就复杂的 SQL 语句更加混乱不堪。 因此，如果你<strong>需要做一些很复杂的操作，最好用 XML 来映射语句。</strong></li>
 <li>选择何种方式来配置映射，以及认为是否应该要统一映射语句定义的形式，完全取决于你和你的团队。 换句话说，永远不要拘泥于一种方式，你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。</li>
</ul>
<p>原文链接: https://blog.csdn.net/weixin_64044840/article/details/129302790</p>]]></description><guid isPermaLink="false">/archives/06193ef2-755a-4d59-a396-aff5fdb58528</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:39:11 GMT</pubDate></item><item><title><![CDATA[【Linux】如何用shell脚本一键安装Java和Maven环境]]></title><link>https://wiki.dgfygk.com/archives/b1e98a51-bf56-4534-804b-916831e7131e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90Linux%E3%80%91%E5%A6%82%E4%BD%95%E7%94%A8shell%E8%84%9A%E6%9C%AC%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85Java%E5%92%8CMaven%E7%8E%AF%E5%A2%83&amp;url=/archives/b1e98a51-bf56-4534-804b-916831e7131e" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>Shell脚本安装环境</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_1">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#_4">脚本</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#Java_5">Java安装脚本</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_56">使用方式</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#Java_75">Java卸载脚本</a></li>
 <li><a href="https://wiki.dgfygk.com#Maven_124">Maven安装脚本</a></li>
 <li><a href="https://wiki.dgfygk.com#Maven_167">Maven卸载脚本</a></li>
</ul>
<p>
 <br>
</p>
<h2>前言</h2>
<p>无论是在云服务器上部署Java项目 还是在本地的Linux虚拟机上运行Java项目 都需要Java的环境 设置环境则需要一些繁琐的操作 为了简化并复用这些操作 我们可以封装这些操作为一个脚本 下面将展示shell脚本的具体写法 读者可以顺便复习一下Shell脚本的一些知识</p>
<h2>脚本</h2>
<h3>Java安装脚本</h3>
<pre><code class="prism language-shell">#!/bin/sh

# 从命令行参数获取JDK源文件和目标目录
if [ "$#" -ne 2 ]; then
    echo "请提供正确的参数 首位为JDK压缩包路径 次位为JDK安装路径"
    exit 1
fi

JDK_SRC="$1"
JDK_DST="$2"

# 检查JDK源文件是否存在
if [ ! -f "$JDK_SRC" ]; then
    echo "JDK 安装文件不存在: $JDK_SRC"
    exit 1
fi

# 创建目标目录
mkdir -p "$JDK_DST"

# 复制JDK压缩包到目标目录
cp "$JDK_SRC" "$JDK_DST"

# 进入目标目录
cd "$JDK_DST"

# 解压JDK压缩包
tar -zxvf "$(basename "$JDK_SRC")"

# 获取解压后的JDK目录名称
JDK_DIR=$(tar -tzf "$(basename "$JDK_SRC")" | head -1 | cut -f1 -d"/")

# 配置环境变量
echo "export JAVA_HOME=$JDK_DST/$JDK_DIR" &gt;&gt; /etc/profile
echo "export PATH=\$JAVA_HOME/bin:\$PATH" &gt;&gt; /etc/profile
echo "export CLASSPATH=\$JAVA_HOME/jre/lib/ext:\$JAVA_HOME/lib/tools.jar" &gt;&gt; /etc/profile

# 使环境变量立即生效
source /etc/profile

# 输出安装完成
echo "Java installation completed."

# 输出Java版本
java -version

# 结束脚本
exit 0
</code></pre>
<h4>使用方式</h4>
<p>首先保存脚本为文件 可以首先创建一个空白文件 之后为赋给它执行权限</p>
<pre><code class="prism language-shell">touch 文件名.sh
chmod +x 文件名.sh
</code></pre>
<p>在保证你已经上传了jdk的安装包之后 按照下面的方式进行
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb3245898755e46459834d06c19b2f8fa.png&amp;size=m" alt="">
 <br>
 如图 我在/dev-ops下放置了java文件夹 并想要解压在这里 我可以这样使用这个脚本</p>
<pre><code class="prism language-shell">sh install-java-script.sh /dev-ops/jdk-8u202-linux-x64.tar.gz /dev-ops/java
</code></pre>
<p>第一个参数是jdk的压缩包路径 而第二个是需要设置以及安装的环境变量的路径
 <br>
 <strong>注意:</strong> 执行此脚本之后仍有概率出现java -version时无法识别java命令 这时候需要再次刷新环境变量配置</p>
<pre><code class="prism language-shell"> source /etc/profile
</code></pre>
<p>如果刷新之后仍然无法看到版本 可以查看一下脚本执行是否出现了错误</p>
<h3>Java卸载脚本</h3>
<pre><code class="prism language-shell">#!/bin/sh

# 检查是否提供了JDK目标目录作为参数
if [ -z "$1" ]; then
    echo "请提供JDK目标目录作为参数！"
    exit 1
fi

# 使用第一个参数作为JDK目标目录
JDK_DST="$1"

# 获取JDK目录名称
if [ -d "$JDK_DST" ]; then
    JDK_DIR=$(ls "$JDK_DST" | grep 'jdk' | head -n 1)
else
    echo "JDK 目录不存在: $JDK_DST"
    exit 1
fi

# 删除JDK目录
echo "正在删除JDK目录..."
rm -rf "$JDK_DST/$JDK_DIR"

# 检查是否删除成功
if [ -d "$JDK_DST/$JDK_DIR" ]; then
    echo "JDK 目录删除失败."
    exit 1
else
    echo "JDK 目录已删除."
fi

# 移除环境变量配置
echo "正在移除环境变量配置..."
sed -i '/JAVA_HOME/d' /etc/profile
sed -i '/CLASSPATH/d' /etc/profile

# 重新加载环境变量
source /etc/profile

# 输出卸载完成
echo "Java remove completed."

# 结束脚本
exit 0
</code></pre>
<p>使用的方式是类似的 传入JDK安装时的目录即可</p>
<h3>Maven安装脚本</h3>
<pre><code class="prism language-shell">#!/bin/bash

# 检查参数数量
if [ "$#" -ne 3 ]; then
    echo "请提供正确参数 第一位参数为Maven压缩包 第二位为要安装到的路径 第三位为文件夹名称"
    exit 1
fi

# 从命令行参数获取值
SOURCE_ZIP="$1"
TARGET_DIR="$2"
MAVEN_DIR_NAME="$3"
MAVEN_HOME="$TARGET_DIR/$MAVEN_DIR_NAME"

# 确保目标目录存在
mkdir -p "$TARGET_DIR"

# 复制zip文件到目标目录
cp "$SOURCE_ZIP" "$TARGET_DIR"

# 进入目标目录
cd "$TARGET_DIR"

# 解压缩文件
unzip "$SOURCE_ZIP"

# 配置环境变量并写入到 /etc/profile
echo "export MAVEN_HOME=$MAVEN_HOME" &gt;&gt; /etc/profile
echo 'export PATH=$PATH:$MAVEN_HOME/bin' &gt;&gt; /etc/profile

# 使环境变量配置立即生效
source /etc/profile

# 输出安装完成信息
echo "Maven installation completed."

# 显示Maven版本
mvn -version
</code></pre>
<p>使用方式同上</p>
<h3>Maven卸载脚本</h3>
<pre><code class="prism language-shell">#!/bin/bash

# 检查是否提供了足够的参数
if [ -z "$1" ] || [ -z "$2" ]; then
    echo "请提供Maven目标目录和Maven目录名称作为参数！"
    echo "用法: $0 &lt;target_directory&gt; &lt;maven_directory_name&gt;"
    exit 1
fi

# 使用第一个参数作为TARGET_DIR
TARGET_DIR="$1"

# 使用第二个参数作为MAVEN_DIR_NAME
MAVEN_DIR_NAME="$2"

# 定义MAVEN_HOME
MAVEN_HOME="$TARGET_DIR/$MAVEN_DIR_NAME"

# 删除 Maven 目录
if [ -d "$MAVEN_HOME" ]; then
    rm -rf "$MAVEN_HOME"
    echo "Maven directory $MAVEN_HOME has been removed."
else
    echo "Maven directory $MAVEN_HOME does not exist."
fi

# 备份 /etc/profile
cp /etc/profile /etc/profile.bak

# 从 /etc/profile 中移除 Maven 环境变量
sed -i '/^export MAVEN_HOME/d' /etc/profile
sed -i '/^export PATH=\$PATH:\$MAVEN_HOME\/bin/d' /etc/profile

# 重新加载 /etc/profile
source /etc/profile

# 输出卸载完成信息
echo "Maven has been uninstalled."

# 结束脚本
exit 0
</code></pre>
<p>原文链接: https://blog.csdn.net/m0_73918768/article/details/141652775</p>]]></description><guid isPermaLink="false">/archives/b1e98a51-bf56-4534-804b-916831e7131e</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Tue, 8 Oct 2024 03:38:55 GMT</pubDate></item><item><title><![CDATA[2.1MyBatis——ORM对象关系映射]]></title><link>https://wiki.dgfygk.com/archives/3fa9931b-a780-4e57-a414-f7c8eb8df079</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=2.1MyBatis%E2%80%94%E2%80%94ORM%E5%AF%B9%E8%B1%A1%E5%85%B3%E7%B3%BB%E6%98%A0%E5%B0%84&amp;url=/archives/3fa9931b-a780-4e57-a414-f7c8eb8df079" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>2.1MyBatis——ORM对象关系映射</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1__7">1. 验证映射配置</a></li>
 <li><a href="https://wiki.dgfygk.com#2ResultTypeResultMap_33">2.ResultType和ResultMap</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#21ResultMapORM_37">2.1ResultMap是最终的ORM依据</a></li>
 <li><a href="https://wiki.dgfygk.com#22ResultTypeResultMap_40">2.2ResultType和ResultMap的使用区别</a></li>
 <li><a href="https://wiki.dgfygk.com#3_92">3.具体的转换逻辑</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#31_TypeHandle_121">3.1 TypeHandle类型转换</a></li>
 <li><a href="https://wiki.dgfygk.com#5_149">5.总结</a></li>
</ul>
<p>
 <br>
</p>
<blockquote>
 <p><a href="https://blog.csdn.net/wyy546792341/article/details/142694309"><strong>合集总览：Mybatis框架梳理</strong></a></p>
 <p>
  <br>
 </p>
 <p>概括的说，MyBatis中，对于映射关系的声明是由开发者在xml文件手动完成的。比如对查询方法而言，你需要显式声明ResultType或ResultMap，这里其实就是在定义数据库字段和Java属性之间的映射关系。
  <br>
  下面我们以简单的<code>查询方法为例</code>，探索MyBatis如何将<code>数据库字段</code>转换为具体<code>对象的字段属性</code>，即ORM的具体过程。
  <br>
  首先我们先验证ORM这一过程确实存在。</p>
</blockquote>
<ol>
 <li>
  <h2>验证映射配置</h2>
 </li>
</ol>
<pre><code class="prism language-xml">&lt;!--方法1--&gt;
&lt;select id="queryWithoutType"&gt;
    select * from user where id = #{id}
&lt;/select&gt;
&lt;!--方法2--&gt;
&lt;select id="queryWithReturnType" resultType="org.wyy.dto.User"&gt;
    select * from user where id = #{id}
&lt;/select&gt;

&lt;resultMap id="customMap" type="org.wyy.dto.User"&gt;&lt;/resultMap&gt;
&lt;!--方法3--&gt;
&lt;select id="queryWithReturnMap" resultMap="customMap"&gt;
    select * from user where id = #{id}
&lt;/select&gt;
</code></pre>
<blockquote>
 <p>这里我们定义三个相同逻辑的方法，方法1不声明映射关系。分别运行三个方法，方法2和方法3正常执行，方法1抛出异常如下：</p>
</blockquote>
<pre><code class="prism language-shell">org.mybatis.spring.MyBatisSystemException: 
nested exception is org.apache.ibatis.executor.ExecutorException: 
A query was run and no Result Maps were found for the Mapped Statement 
'org.wyy.mapper.UserMapper.queryWithoutType'.  
It's likely that neither a Result Type nor a Result Map was specified.
</code></pre>
<blockquote>
 <p>说明Mybatis中，ORM的映射转换需要在xml的每个方法中手动配置，否则无法进行查询。</p>
</blockquote>
<h2>2.ResultType和ResultMap</h2>
<blockquote>
 <p>对查询方法未做任何配置时，异常提示<code>It's likely that neither a Result Type nor a Result Map was specified.</code>,即至少需要<code>ResultType</code>和<code>ResultMap</code>其中一个,那么它们有什么关系呢？看一下源码：</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6e4bfeac5d204025b9d52b8f8452b888.png&amp;size=m" alt=""></p>
<h3>2.1ResultMap是最终的ORM依据</h3>
<blockquote>
 <p>从上图可以看出，当<code>ResultMap</code>为空但ResultType不为空时（即<code>&lt;select&gt;</code>标签配置了<code>resultType</code>属性），<code>XxMapper.xml</code>加载后会Mybatis默认创建一个后缀为<code>-Inline</code>的<code>ResultMap</code>对象，所以MyBatis中映射转换最终参照的都是<code>ResultMap</code>对象</p>
</blockquote>
<h3>2.2ResultType和ResultMap的使用区别</h3>
<blockquote>
 <p>既然最终都是使用ResultMap对象进行关系映射，为什么还要设计ResultType呢？它们在使用时的区别又是什么呢？
  <br>
  先看下ResultMap对象的结构：</p>
</blockquote>
<pre><code class="prism language-java">private Configuration configuration;
private String id;
private Class&lt;?&gt; type;
private List&lt;ResultMapping&gt; resultMappings;
private List&lt;ResultMapping&gt; idResultMappings;
private List&lt;ResultMapping&gt; constructorResultMappings;
private List&lt;ResultMapping&gt; propertyResultMappings;
private Set&lt;String&gt; mappedColumns;
private Set&lt;String&gt; mappedProperties;
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping;
</code></pre>
<blockquote>
 <p><strong>加载</strong> ：前面的查询我们提到，在MyBatis加载Mapper.xml文件时，如果某个查询方法定义了<code>resultType</code>属性，那么会为其自动生成ResultMap对象，这个ResultMap其实相当简单，只有id 和 type属性。
  <br>
  <strong>执行</strong> ：当查询方法被调用时，在<code>ResultSetHandler</code>中会进行如下判断：</p>
</blockquote>
<pre><code class="prism language-java">public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler&lt;?&gt; resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {

    if (resultMap.hasNestedResultMaps()) {

      ensureNoRowBounds();
      checkResultHandler();
      // 嵌套类型
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {

    // 简单类型
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }
</code></pre>
<blockquote>
 <p>结合<code>ResultMap对象的属性</code>和<code>ResultType自动生成的简单ResultMap对象</code>，以及这里<code>处理结果集的判断</code>，可以看出，MyBatis中将结果集映射进行了分类：简单和复杂嵌套。</p>
 <ul>
  <li>
   <p>对于简单的ResultMap，直接使用<code>&lt;select&gt;</code>标签声明的映射类型（这里是org.wyy.dto.User）创建映射器Reflector，并使用MyBatis内置的类型处理器TypeHandler（如StringTypeHandler、LongTypeHandler等）进行处理</p>
  </li>
  <li>
   <p>对于存在嵌套的类型，则需要通过自定义的ResultMap进行列名和字段的映射绑定，以及指定所需的类型转换器
    <br>
    区别和使用：</p>
  </li>
  <li>
   <p>对单表查询而言，如果数据库字段和对象的属性名一致，没必编写映射关系，直接使用ResultType即可。这也是为什么即使ResultType最终会生成ResultMap，却依然保留ResultType的原因：使用ResultType可以简化配置。</p>
  </li>
  <li>
   <p>对于查询结果集不能与对象属性一一匹配的情况，则必须通过显示定义ResultMap来声明映射关系，特殊类型可能还需要定义类型转换器</p>
  </li>
 </ul>
</blockquote>
<pre><code class="prism language-xml">&lt;resultMap id="customMap" type="org.wyy.dto.User"&gt;
    &lt;result column="" jdbcType="VARCHAR" 
            property="" javaType="" 
            typeHandler="org.apache.ibatis.type.StringTypeHandler" /&gt;
&lt;/resultMap&gt;
</code></pre>
<h2>3.具体的转换逻辑</h2>
<blockquote>
 <p>有了前面的描述，我们知道，数据库列名和对象属性之间的映射是通过ResultMap维护的。有了这层映射关系，如果是我们，该如何将数据从结果集装填到对象中呢？</p>
 <ol>
  <li>
   <p>获取当前查询方法的ResultMap，里面包含列和属性的对应关系</p>
  </li>
  <li>
   <p>使用列名获取结果集中对应的数据</p>
  </li>
  <li>
   <p>根据映射关系，将获取到的列数据赋值给对象的相应属性</p>
   <p>
    <br>
   </p>
   <p>其实MyBatis中也是这样做的：</p>
  </li>
 </ol>
</blockquote>
<pre><code class="prism language-java">private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {

    // 映射关系的获取
    List&lt;UnMappedColumnAutoMapping&gt; autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {

      // 每一列和属性的映射关系，分别遍历
      for (UnMappedColumnAutoMapping mapping : autoMapping) {

        // 通过类型处理器typeHandler，从结果集中获取列的值
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {

          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() &amp;&amp; !mapping.primitive)) {

          // 将取出的列的value设置给对应的对象属性，完成columen --&gt; property的赋值
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
</code></pre>
<h3>3.1 TypeHandle类型转换</h3>
<blockquote>
 <p>上面代码可以看到，从结果集中获取值并转化是由类型处理器完成的。MyBatis中内置了常用的类型处理器，用来完成java type 和jdbc type之间的转换</p>
</blockquote>
<pre><code class="prism language-java">// java type + jdbc type + handler
  public &lt;T&gt; void register(Class&lt;T&gt; type, JdbcType jdbcType, TypeHandler&lt;? extends T&gt; handler) {

    register((Type) type, jdbcType, handler);
  }

// 内部注册
public TypeHandlerRegistry() {

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    ...
}
</code></pre>
<blockquote>
 <p>对于无法转换的类型，则可以通过自定义类型转换器进行扩展：</p>
</blockquote>
<pre><code class="prism language-java">// column varchar  --&gt; java property LocalDate
&lt;resultMap id="customMap" type="org.wyy.dto.User"&gt;
    &lt;result column="update_time" property="updateTime" typeHandler="org.wyy.typeHandler.CustomTypeHandler"/&gt;
&lt;/resultMap&gt;

@MappedTypes(LocalDate.class)
public class CustomTypeHandler extends BaseTypeHandler&lt;LocalDate&gt; {
   }
</code></pre>
<h2>5.总结</h2>
<blockquote>
 <ol>
  <li>. Mybatis中ORM是我们通过ResultType或ResultMap手动完成关系映射的，所以ResultType和ResultMap至少需要一个，否则报错；</li>
  <li>ResultType属性简化了ORM配置，但复杂对象或对象嵌套，则必须使用ResultMap；</li>
  <li>以查询为例，转换的流程是先查询映射关系，再查询具体列的值，然后通过类型转换器转换为Java属性，最后赋值；</li>
  <li>MyBatis内置了许多类型转换器，使得大部分场景下不需要显式配置。</li>
 </ol>
</blockquote>
<p>原文链接: https://blog.csdn.net/wyy546792341/article/details/142697916</p>]]></description><guid isPermaLink="false">/archives/3fa9931b-a780-4e57-a414-f7c8eb8df079</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:37:03 GMT</pubDate></item><item><title><![CDATA[SpringBoot配置热部署devtools工具]]></title><link>https://wiki.dgfygk.com/archives/060ac7c8-9895-46f5-8860-bb949aa0a199</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E9%85%8D%E7%BD%AE%E7%83%AD%E9%83%A8%E7%BD%B2devtools%E5%B7%A5%E5%85%B7&amp;url=/archives/060ac7c8-9895-46f5-8860-bb949aa0a199" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#springbootdevtools_1">spring-boot-devtools介绍</a></li>
 <li><a href="https://wiki.dgfygk.com#devtools_6">配置热部署devtools工具</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_7">一、引入依赖</a></li>
   <li><a href="https://wiki.dgfygk.com#_15">二、配置文件</a></li>
   <li><a href="https://wiki.dgfygk.com#_25">三、设置【自动重启更新】</a></li>
   <li><a href="https://wiki.dgfygk.com#_34">效果</a></li>
  </ul></li>
</ul>
<p>
 <br>
</p>
<h3>spring-boot-devtools介绍</h3>
<p><code>spring-boot-devtools</code>是一组开发工具，其中包括热部署功能。spring-boot-devtools的热部署功能允许开发人员在开发过程中对应用程序进行快速修改并<code>立即看到变化</code>，<strong>而不需要重新启动整个应用程序</strong>。</p>
<p>Spring Boot DevTools通过监视应用程序的类文件和资源文件的变化，在检测到变化时自动重新加载应用程序。这样可以大大提高开发效率，因为开发人员可以更快地查看和测试他们的更改，而不必等待长时间的重新启动过程。</p>
<h3>配置热部署devtools工具</h3>
<h4>一、引入依赖</h4>
<pre><code class="prism language-xml"> &lt;dependency&gt;
     &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
     &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;
 &lt;/dependency&gt;
</code></pre>
<h4>二、配置文件</h4>
<p>application.yml中添加如下：</p>
<pre><code class="prism language-yml">spring:
  devtools:  # 开发工具配置
    restart:  # 重启配置
      enabled: true  # 启用自动重启功能
      additional-paths: src/main/java  # 监视的路径
</code></pre>
<h4>三、设置【自动重启更新】</h4>
<p>第一个：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fff78321e0d8aec3df9016ca61a1e5815.png&amp;size=m" alt=""></p>
<p>第二个：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fea99214ab9a28215a7ff91e4235e7eb4.png&amp;size=m" alt=""></p>
<h4>效果</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1c01d0536196b387c214f142958a991c.png&amp;size=m" alt=""></p>
<hr>
<p>❤觉得有用的可以留个关注❤</p>
<p>原文链接: https://blog.csdn.net/m0_64289188/article/details/138658318</p>]]></description><guid isPermaLink="false">/archives/060ac7c8-9895-46f5-8860-bb949aa0a199</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:34:56 GMT</pubDate></item><item><title><![CDATA[Spring Boot 3整合FFmpeg进行图片和MP3转换为视频]]></title><link>https://wiki.dgfygk.com/archives/4293c14c-c308-4f3e-bda7-cb222a57c80b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%203%E6%95%B4%E5%90%88FFmpeg%E8%BF%9B%E8%A1%8C%E5%9B%BE%E7%89%87%E5%92%8CMP3%E8%BD%AC%E6%8D%A2%E4%B8%BA%E8%A7%86%E9%A2%91&amp;url=/archives/4293c14c-c308-4f3e-bda7-cb222a57c80b" width="1" height="1" alt="" style="opacity:0;">
<p>Spring Boot 3整合FFmpeg进行图片和MP3转换为视频的示例代码如下：</p>
<p>添加FFmpeg依赖到pom.xml：</p>
<pre><code class="prism language-bash">&lt;dependency&gt;
    &lt;groupId&gt;com.github.kokorin.jaffree&lt;/groupId&gt;
    &lt;artifactId&gt;jaffree&lt;/artifactId&gt;
    &lt;version&gt;0.1.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>创建一个服务类来执行转换：</p>
<pre><code class="prism language-bash">import com.github.kokorin.jaffree.ffmpeg.FFmpeg;
import com.github.kokorin.jaffree.ffmpeg.FFmpegResult;
import org.springframework.stereotype.Service;

import java.nio.file.Paths;

@Service
public class VideoConversionService {


    public void convertImageAndAudioToVideo(String imagePath, String audioPath, String outputPath) throws Exception {

        FFmpegResult result = FFmpeg.atPath()
                .addInput(FFmpeg.input(imagePath))
                .addInput(FFmpeg.input(audioPath))
                .overrideOutputFiles(true)
                .addOutput(FFmpeg.output(outputPath)
                        .videoCodec("copy")
                        .audioCodec("copy"))
                .execute();

        if (result.getState() == FFmpegResult.State.SUCCESS) {

            System.out.println("转换成功");
        } else {

            System.out.println("转换失败");
        }
    }
}
</code></pre>
<p>在Spring Boot应用的主类或者任意一个配置类中，调用转换服务：</p>
<pre><code class="prism language-bash">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class VideoConversionRunner implements CommandLineRunner {


    @Autowired
    private VideoConversionService videoConversionService;

    @Override
    public void run(String... args) throws Exception {

        String imagePath = "path/to/image.jpg";
        String audioPath = "path/to/audio.mp3";
        String outputPath = "path/to/output.mp4";

        videoConversionService.convertImageAndAudioToVideo(imagePath, audioPath, outputPath);
    }
}
</code></pre>
<p>确保你的图片和音频文件路径是正确的，并且FFmpeg可执行文件在你的系统PATH中或者通过FFmpeg.atPath()指定。</p>
<p>以上代码假设你已经有了一个运行中的Spring Boot 3应用，并且FFmpeg已经安装在你的系统上。如果FFmpeg没有安装，你需要先下载并安装FFmpeg，或者使用Docker容器等方式来运行它。</p>
<p>原文链接: https://blog.csdn.net/iteye_10392/article/details/142618046</p>]]></description><guid isPermaLink="false">/archives/4293c14c-c308-4f3e-bda7-cb222a57c80b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:34:43 GMT</pubDate></item><item><title><![CDATA[FFmpeg教程（超级详细版）]]></title><link>https://wiki.dgfygk.com/archives/9925e034-efcb-4888-867e-3c6d6c3efe75</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=FFmpeg%E6%95%99%E7%A8%8B%EF%BC%88%E8%B6%85%E7%BA%A7%E8%AF%A6%E7%BB%86%E7%89%88%EF%BC%89&amp;url=/archives/9925e034-efcb-4888-867e-3c6d6c3efe75" width="1" height="1" alt="" style="opacity:0;">
<h2>一、参考资料</h2>
<p><a href="http://blog.360converter.com/archives/894">通过ffmpeg把图片转换成视频</a>
 <br>
 <a href="https://blog.csdn.net/Gary__123456/article/details/88742705">FFmpeg命令(一)、使用filter_complex命令拼接视频</a>
 <br>
 <a href="http://www.ruanyifeng.com/blog/2020/01/ffmpeg.html">FFmpeg 视频处理入门教程</a><a href="https://zhuanlan.zhihu.com/p/67878761">给新手的 20 多个 FFmpeg 命令示例</a>
 <br>
 <a href="https://blog.csdn.net/Lyman_Ye/article/details/80305904">FFmpeg命令行转码</a>
 <br>
 <a href="https://www.bookstack.cn/read/other-doc-cn-ffmpeg/README.md">ffmpeg 翻译文档 (ffmpeg-all 包含重要组件）</a>
 <br>
 <a href="http://ffmpeg.org/ffmpeg-filters.html#drawtext-1">FFmpeg Filters Documentation</a>
 <br>
 <a href="https://www.shangmayuan.com/a/46d1902c245842e586ddea9b.html">FFmpeg命令行滤镜使用</a>
 <br>
 <a href="https://blog.csdn.net/n66040927/article/details/84525611">ffmpeg命令行使用nvidia CUDA scaling高速转分辨率转码(libnpp)</a>
 <br>
 <a href="https://www.cnblogs.com/carle-09/p/11736390.html">FFmpeg—源码编译</a>
 <br>
 <a href="https://www.jianshu.com/p/c56d5d79ce8b">FFmpeg常用命令</a>
 <br>
 <a href="https://eyehere.net/2019/the-complete-guide-for-using-ffmpeg-in-linux/">Linux上的ffmpeg完全使用指南</a>
 <br>
 <a href="https://zhuanlan.zhihu.com/p/168240163">视频和视频帧：FFMPEG 硬件解码API介绍</a></p>
<h2>二、安装ffmpeg、ffmpy</h2>
<h3>安装ffmpeg</h3>
<pre><code class="prism language-bash"># 更新源
sudo apt update

# 添加源
sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next 

# 安装ffmpeg
sudo apt-get install ffmpeg

# 查看版本
ffmpeg -version

# 查看编码器和解码器
ffmpeg -encoders
</code></pre>
<h3>安装ffmpy</h3>
<pre><code class="prism language-bash">pip install ffmpy==0.2.2   # 需要权限就添加sudo
</code></pre>
<h2>三、关键指令</h2>
<ol>
 <li>
  <p>查看FFmpeg支持的编码器</p>
  <pre><code class="prism language-bash">ffmpeg configure -encoders
</code></pre>
 </li>
 <li>
  <p>查看FFmpeg支持的解码器</p>
  <pre><code class="prism language-bash">ffmpeg configure -decoders
</code></pre>
 </li>
 <li>
  <p>查看FFmpeg支持的通信协议</p>
  <pre><code class="prism language-bash">ffmpeg configure -protocols
</code></pre>
 </li>
 <li>
  <p>查看FFmpeg所支持的音视频编码格式、文件封装格式与流媒体传输协议</p>
  <pre><code class="prism language-bash">ffmpeg configure --help
</code></pre>
 </li>
 <li>
  <p>播放视频
   <br>
   <a href="https://www.cnblogs.com/renhui/p/8458802.html">FFmpeg命令行工具学习(二)：播放媒体文件的工具ffplay</a></p>
  <pre><code class="prism language-bash">ffplay input.mp4

# 播放完自动退出
ffplay -autoexit input.mp4
</code></pre>
 </li>
 <li>
  <p>设置视频的屏幕高宽比</p>
  <pre><code class="prism language-bash">ffmpeg -i input.mp4 -aspect 16:9 output.mp4 
</code></pre>
  <pre><code>```prism language-text
通常使用的宽高比是：
16:9
4:3
16:10
5:4
2:21:1
2:35:1
2:39:1
</code></pre>
 </li>
 <li>
  <p>编码格式转换</p>
  <p>MPEG4编码转成H264编码</p>
  <pre><code class="prism language-bash">ffmpeg -i input.mp4 -strict -2 -vcodec h264 output.mp4
</code></pre>
  <p>H264编码转成MPEG4编码</p>
  <pre><code class="prism language-bash">ffmpeg -i input.mp4 -strict -2 -vcodec mpeg4 output.mp4
</code></pre>
 </li>
</ol>
<h2>四、视频压缩</h2>
<pre><code class="prism language-bash">ffmpeg -i 2020.mp4 -vcodec h264 -vf scale=640:-2 -threads 4 2020_conv.mp4

ffmpeg -i 1579251906.mp4 -strict -2 -vcodec h264 1579251906_output.mp4
</code></pre>
<p>参数解释:</p>
<pre><code class="prism language-text">-i 2020.mp4
输入文件，源文件

2020_conv.mp4
输出文件，目标文件

-vf scale=640:-2  
改变视频分辨率，缩放到640px宽，高度的-2是考虑到libx264要求高度是偶数，所以设置成-2,让软件自动计算得出一个接近等比例的偶数高

-threads 4
4核运算
</code></pre>
<p>其他参数：</p>
<pre><code class="prism language-text">-s 1280x720 
设置输出文件的分辨率，w*h。

-b:v 
输出文件的码率，一般500k左右即可，人眼看不到明显的闪烁，这个是与视频大小最直接相关的。

-preset
指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
与 veryslow相比，placebo以极高的编码时间为代价，只换取了大概1%的视频质量提升。这是一种收益递减准则：slow 与 medium相比提升了5%~10%；slower 与 slow相比提升了5%；veryslow 与 slower相比提升了3%。
针对特定类型的源内容（比如电影、动画等），还可以使用-tune参数进行特别的优化。

-an
去除音频流。

-vn
去除视频流。

-c:a
指定音频编码器。

-c:v
指定视频编码器，libx264，libx265，H.262，H.264，H.265。
libx264：最流行的开源 H.264 编码器。
NVENC：基于 NVIDIA GPU 的 H.264 编码器。
libx265：开源的 HEVC 编码器。
libvpx：谷歌的 VP8 和 VP9 编码器。
libaom：AV1 编码器。

-vcodec copy
表示不重新编码，在格式未改变的情况采用。

-re 
以源文件固有帧率发送数据。

-minrate 964K -maxrate 3856K -bufsize 2000K 
指定码率最小为964K，最大为3856K，缓冲区大小为 2000K。

-y
不经过确认，输出时直接覆盖同名文件。

-crf
参数来控制转码，取值范围为 0~51，其中0为无损模式，18~28是一个合理的范围，数值越大，画质越差。
</code></pre>
<h2>五、视频拼接</h2>
<ol>
 <li>
  <p>将4个视频拼接成一个很长的视频（无声音）</p>
  <pre><code class="prism language-bash">ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -i 3.mp4 -filter_complex '[0:0][1:0] [2:0][3:0] concat=n=4:v=1 [v]' -map '[v]' output.mp4
</code></pre>
 </li>
 <li>
  <p>将4个视频拼接成一个很长的视频（有声音）</p>
  <pre><code class="prism language-bash">ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -filter_complex '[0:0][0:1] [1:0][1:1] [2:0][2:1] concat=n=3:v=1:a=1 [v][a]' -map '[v]' -map '[a]'  output.mp4
</code></pre>
  <p>参数解释：</p>
  <pre><code class="prism language-text">[0:0][0:1] [1:0][1:1] [2:0][2:1] 
分别表示第1个输入文件的视频、音频，第2个输入文件的视频、音频，第3个输入文件的视频、音频。

concat=n=3:v=1:a=1 
表示有3个输入文件，输出一条视频流和一条音频流。

[v][a] 
得到的视频流和音频流的名字，注意在 bash 等 shell 中需要用引号，防止通配符扩展。
</code></pre>
 </li>
 <li>
  <p>横向拼接2个视频</p>
  <pre><code class="prism language-bash">ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v]pad=iw*2:ih*1[a];[a][1:v]overlay=w" out.mp4
</code></pre>
  <p>参数解释：</p>
  <pre><code class="prism language-text">pad
将合成的视频宽高，这里iw代表第1个视频的宽，iw*2代表合成后的视频宽度加倍，ih为第1个视频的高，合成的两个视频最好分辨率一致。

overlay
覆盖，[a][1:v]overlay=w，后面代表是覆盖位置w:0。
</code></pre>
 </li>
 <li>
  <p>竖向拼接2个视频</p>
  <pre><code class="prism language-bash">ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v]pad=iw:ih*2[a];[a][1:v]overlay=0:h" out_2.mp4
</code></pre>
 </li>
 <li>
  <p>横向拼接3个视频</p>
  <pre><code class="prism language-bash">ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -filter_complex "[0:v]pad=iw*3:ih*1[a];[a][1:v]overlay=w[b];[b][2:v]overlay=2.0*w" out_v3.mp4
</code></pre>
 </li>
 <li>
  <p>竖向拼接3个视频</p>
  <pre><code class="prism language-bash">ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -filter_complex "[0:v]pad=iw:ih*3[a];[a][1:v]overlay=0:h[b];[b][2:v]overlay=0:2.0*h" out_v4.mp4
</code></pre>
 </li>
 <li>
  <p>4个视频2x2方式排列</p>
  <pre><code class="prism language-bash">ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -i 3.mp4 -filter_complex "[0:v]pad=iw*2:ih*2[a];[a][1:v]overlay=w[b];[b][2:v]overlay=0:h[c];[c][3:v]overlay=w:h" out.mp4
</code></pre>
 </li>
</ol>
<h2>六、视频帧操作</h2>
<p><a href="https://zhuanlan.zhihu.com/p/36109778">ffmpeg和H264视频的编解码</a></p>
<ol>
 <li>
  <p>查看每帧的信息</p>
  <pre><code class="prism language-bash">ffprobe -v error -show_frames gemfield.mp4 
</code></pre>
  <p>从pict_type=I可以看出这是个关键帧，然后key_frame=1 表示这是IDR frame，如果key_frame=0表示这是Non-IDR frame。</p>
 </li>
 <li>
  <p>截取视频中的某一帧</p>
  <p>把gemfield.mp4视频的第1分05秒的一帧图像截取出来。</p>
  <pre><code class="prism language-bash"># input seeking
ffmpeg -ss 00:1:05 -i gemfield.mp4 -frames:v 1 out.jpg
</code></pre>
  <pre><code>```prism language-bash
# output seeking
ffmpeg -i gemfield.mp4 -ss 00:1:05 -frames:v 1 out1.jpg
</code></pre>
  <p>参数解释：</p>
  <pre><code class="prism language-text">-frame:v 1，在video stream上截取1帧。
input seeking使用的是key frames，所以速度很快；而output seeking是逐帧decode，直到1分05秒，所以速度很慢。
</code></pre>
  <p>重要说明：</p>
  <pre><code class="prism language-text">ffmpeg截取视频帧有2种 seeking 方式，对应有2种 coding 模式：transcoding 和 stream copying（ffmpeg -c copy）。

transcoding 模式：需要 decoding + encoding 的模式，即先 decoding 再encoding。

stream copying 模式：不需要decoding + encoding的模式，由命令行选项-codec加上参数copy来指定（-c:v copy ）。在这种模式下，ffmpeg在video stream上就会忽略 decoding 和 encoding步骤。
</code></pre>
 </li>
 <li>
  <p>查看视频总帧数</p>
  <pre><code class="prism language-bash">ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 gemfield.mp4
</code></pre>
 </li>
 <li>
  <p>查看 key frame 帧数</p>
  <pre><code class="prism language-bash">ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 -skip_frame nokey gemfield.mp4
</code></pre>
 </li>
 <li>
  <p>查看 key frame 所在的时间</p>
  <pre><code class="prism language-bash">ffprobe -v error -skip_frame nokey -select_streams v:0 -show_entries frame=pkt_pts_time -of csv=print_section=0 gemfield.mp4
</code></pre>
 </li>
 <li>
  <p>查看 key frame 分布的情况</p>
  <pre><code class="prism language-bash">ffprobe -v error -show_frames gemfield.mp4 | grep pict_type
</code></pre>
 </li>
 <li>
  <p>查看 key frame 所在的帧数</p>
  <pre><code class="prism language-bash">ffprobe -v error -select_streams v -show_frames -show_entries frame=pict_type -of csv gemfield.mp4 | grep -n I | cut -d ':' -f 1
</code></pre>
 </li>
 <li>
  <p>重新设置 key frame interval</p>
  <pre><code class="prism language-bash">ffmpeg -i gemfield.mp4 -vcodec libx264 -x264-params keyint=1:scenecut=0 -acodec copy out.mp4
</code></pre>
 </li>
 <li>
  <p>查看视频波特率</p>
  <pre><code class="prism language-bash">ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 gemfield.mp4
</code></pre>
 </li>
</ol>
<h2>七、图片与视频</h2>
<h3>7.1 图片转视频（规则的名称）</h3>
<pre><code class="prism language-bash">ffmpeg -f image2 -i 'in%6d.jpg' -vcodec libx264 -r 25 -b 200k test.mp4
</code></pre>
<p>参数解释：</p>
<pre><code class="prism language-bash">-r 25 表示每秒播放25帧
-b 200k 指定码率为200k

图片的文件名为"in000000.jpg"，从0开始依次递增。
</code></pre>
<h3>7.2 图片转视频（不规则的名称）</h3>
<p>不规则图片名称转视频。</p>
<h4>7.2.1 方法一</h4>
<p><strong>不规则图片名称合成视频文件</strong>。</p>
<pre><code class="prism language-bash">ffmpeg -framerate 10 -pattern_type glob -i '*.jpg' out.mp4

cat *.png | ffmpeg -f image2pipe -i - output.mp4

参数解释：
-framerate 10：视频帧率
-pattern_type glob：Glob pattern 模糊匹配
-f image2pipe：图像管道，模糊匹配得到图片名称
</code></pre>
<h4>7.2.2 方法二</h4>
<p><strong>不规则图片名称合成视频文件</strong>。</p>
<ol>
 <li>先动手把不规则文件重命名规则图片名。</li>
</ol>
<pre><code class="prism language-python">def getTpyeFile(filelist, type):     
    res = []     
    for item in filelist:
         name, suf = os.path.splitext(item) # 文件名，后缀
         if suf == type:
             res.append(item)
     return res

pwd = os.getcwd() # 返回当前目录的绝对路径
dirs = os.listdir() # 当前目录下所有的文件名组成的数组
typefiles = getTpyeFile(dirs, '.jpg')

for i in range(0,len(typefiles)):
     os.rename(typefiles[i],"./%d.jpg" % (i)) #将文件以数字规则命令
</code></pre>
<ol>
 <li>将需要合成的图片放在txt中，通过读取txt文件合并成视频。</li>
</ol>
<pre><code class="prism language-bash">ffmpeg -f concat -i files.txt output.mp4
</code></pre>
<h3>7.3 图片格式转换</h3>
<p><a href="https://www.cnblogs.com/freedom-try/p/14077613.html">ffmpeg图片格式转换</a></p>
<p><strong>webp转换成jpg</strong></p>
<pre><code class="prism language-bash">ffmpeg -i in.webp out.jpg
</code></pre>
<p><strong>webp转换成png</strong></p>
<pre><code class="prism language-bash">ffmpeg -i in.webp out.png
</code></pre>
<p><strong>jpg转换成png</strong></p>
<pre><code class="prism language-bash">ffmpeg -i in.jpg out.png
</code></pre>
<p><strong>jpg转换成webp</strong></p>
<pre><code class="prism language-bash">ffmpeg -i in.jpg out.webp
</code></pre>
<p><strong>png转换成webp</strong></p>
<pre><code class="prism language-bash">ffmpeg -i in.png out.webp
</code></pre>
<p><strong>png转换成jpg</strong></p>
<pre><code class="prism language-bash">ffmpeg -i in.png out.jpg
</code></pre>
<h2>八、硬解码与软解码</h2>
<ol>
 <li>CPU富余、需要精准控制解码流程、有解码算法的优化、通用性要求高，直接使用软解（也就是CPU解码）;</li>
 <li>有其他编解码芯片/模组、CPU不够用，就不得不需要转向硬解码（也就是专用芯片解码）。</li>
</ol>
<p>原文链接: https://blog.csdn.net/m0_37605642/article/details/121566820</p>]]></description><guid isPermaLink="false">/archives/9925e034-efcb-4888-867e-3c6d6c3efe75</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 8 Oct 2024 03:34:24 GMT</pubDate></item><item><title><![CDATA[SpringBoot项目请求不中断动态更新代码]]></title><link>https://wiki.dgfygk.com/archives/17b4960c-f286-4583-8e17-d328f80c4cd7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E9%A1%B9%E7%9B%AE%E8%AF%B7%E6%B1%82%E4%B8%8D%E4%B8%AD%E6%96%AD%E5%8A%A8%E6%80%81%E6%9B%B4%E6%96%B0%E4%BB%A3%E7%A0%81&amp;url=/archives/17b4960c-f286-4583-8e17-d328f80c4cd7" width="1" height="1" alt="" style="opacity:0;">
<p>在开发中，有时候不停机动态更新代码热部署是一项至关重要的功能，它可以在请求不中断的情况下下更新代码。这种方式不仅提高了开发效率，还能加速测试和调试过程。本文将详细介绍如何在 Spring Boot 项目在Linux系统中实现热部署，特别关注优雅关闭功能的实现。</p>
<h3>1. 代码概述</h3>
<p>我们实现了一个简单的 Spring Boot 应用程序，它可以自动检测端口是否被占用，并在必要时切换到备用端口，然后再将目标端口程序关闭再将备用端口切换为目标端口。具体功能包括：</p>
<ul>
 <li>检查默认端口（8080）是否被占用。</li>
 <li>如果被占用，自动切换到备用端口（8086）。</li>
 <li>在 Linux 系统下，优雅地关闭占用该端口的进程。</li>
 <li>修改Tomcat端口并重启容器。</li>
</ul>
<h4>完整代码</h4>
<pre><code class="java">import com.lps.utils.PortUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

/**
 * @author 阿水
 */
@SpringBootApplication
@Slf4j
public class MybatisDemoApplication {

    private static final int DEFAULT_PORT_8080 = 8080;
    private static final int ALTERNATE_PORT_8086 = 8086;

    public static void main(String[] args) {
        boolean isNeedChangePort = PortUtil.isPortInUse(DEFAULT_PORT_8080);
        String[] newArgs = Arrays.copyOf(args, args.length + 1);
        if (isNeedChangePort) {
            log.info("端口 {} 正在使用中, 正在尝试端口切换到 {}.", DEFAULT_PORT_8080, ALTERNATE_PORT_8086);
            newArgs[newArgs.length - 1] = "--server.port=" + ALTERNATE_PORT_8086;
        }
        log.info("启动参数: {}", Arrays.toString(newArgs));
        //去除newArgs的null数据
        newArgs = Arrays.stream(newArgs).filter(Objects::nonNull).toArray(String[]::new);

        ConfigurableApplicationContext context = SpringApplication.run(MybatisDemoApplication.class, newArgs);
        //判断是否是linux系统，如果是linux系统，则尝试杀死占用8080端口的进程
        System.out.println("是否需要修改端口: "+isNeedChangePort);
        if (isNeedChangePort &amp;&amp; isLinuxOS()) {
            changePortAndRestart(context);
        }
    }

    /**
     * 如果端口占用，则尝试杀死占用8080端口的进程，并修改端口并重启服务
     *
     * @param context
     */
    private static void changePortAndRestart(ConfigurableApplicationContext context) {
        log.info("尝试杀死占用 8080 端口的进程.");
        killOldServiceInLinux();
        log.info("正在修改端口更改为 {}.", DEFAULT_PORT_8080);
        ServletWebServerFactory webServerFactory = context.getBean(ServletWebServerFactory.class);
        ServletContextInitializer servletContextInitializer = context.getBean(ServletContextInitializer.class);
        WebServer webServer = webServerFactory.getWebServer(servletContextInitializer);

        if (webServer != null) {
            log.info("停止旧服务器.");
            webServer.stop();
        }
        //((TomcatServletWebServerFactory) servletContextInitializer).setPort(DEFAULT_PORT_8080);
        ((TomcatServletWebServerFactory) webServerFactory).setPort(DEFAULT_PORT_8080);
        webServer = webServerFactory.getWebServer(servletContextInitializer);
        webServer.start();
        log.info("新服务启动成功.");
    }

    /**
     * 杀死占用 8080 端口的进程
     */
    private static void killOldServiceInLinux() {
        try {
            // 查找占用 8080 端口的进程
            String command = "lsof -t -i:" + DEFAULT_PORT_8080;
            log.info("正在执行命令: {}", command);
            Process process = Runtime.getRuntime().exec(command);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String pid;
            while ((pid = reader.readLine()) != null) {
                // 发送 SIGINT 信号以优雅关闭
                Runtime.getRuntime().exec("kill -2 " + pid);
                log.info("Killed process: {}", pid);
            }
        } catch (IOException e) {
            log.error("Failed to stop old service", e);
        }
    }

    /**
     * 判断是否是linux系统
     *
     * @return
     */
    private static boolean isLinuxOS() {
        return System.getProperty("os.name").toLowerCase().contains("linux");
    }
}
</code></pre>
<h4>工具类</h4>
<pre><code class="java">import java.io.IOException;
import java.net.ServerSocket;

/**
 * @author 阿水
 */
public class PortUtil {
    public static boolean isPortInUse(int port) {
        try (ServerSocket ignored = new ServerSocket(port)) {
            // 端口未被占用
            return false;
        } catch (IOException e) {
            // 端口已被占用
            return true;
        }
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd97916b3c7524c4a9c00b831da791080.png&amp;size=m" alt=""></p>
<p>测试效果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4af45d5c584b470d8527daa590161c33.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F49bb1d178fe14a9994d45ac0dce006d2.png&amp;size=m" alt=""></p>
<h3>2. 主要功能</h3>
<p><strong>检测端口状态</strong></p>
<p>通过 <code>PortUtil.isPortInUse()</code> 检查默认端口的使用状态。如果端口被占用，修改启动参数。</p>
<pre><code class="java">import java.io.IOException;
import java.net.ServerSocket;

/**
 * @author 阿水
 */
public class PortUtil {
    public static boolean isPortInUse(int port) {
        try (ServerSocket ignored = new ServerSocket(port)) {
            // 端口未被占用
            return false;
        } catch (IOException e) {
            // 端口已被占用
            return true;
        }
    }
}
</code></pre>
<p><strong>修改启动参数</strong></p>
<p>当发现端口被占用时，我们动态调整启动参数，以便在启动时使用新的端口。</p>
<pre><code class="java">     if (isNeedChangePort) {
            log.info("端口 {} 正在使用中, 正在尝试端口切换到 {}.", DEFAULT_PORT_8080, ALTERNATE_PORT_8086);
            newArgs[newArgs.length - 1] = "--server.port=" + ALTERNATE_PORT_8086;
        }
</code></pre>
<p><strong>优雅关闭</strong></p>
<p>在 Linux 系统中，如果检测到端口被占用，调用 <code>killOldServiceInLinux()</code> 方法，优雅地关闭占用该端口的进程。这是通过发送 <code>SIGINT</code> 信号实现的，允许应用程序进行清理工作并优雅退出。</p>
<pre><code>```java
 /**
     * 杀死占用 8080 端口的进程
     */
    private static void killOldServiceInLinux() {
        try {
            // 查找占用 8080 端口的进程
            String command = "lsof -t -i:" + DEFAULT_PORT_8080;
            log.info("正在执行命令: {}", command);
            Process process = Runtime.getRuntime().exec(command);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String pid;
            while ((pid = reader.readLine()) != null) {
                // 发送 SIGINT 信号以优雅关闭
                Runtime.getRuntime().exec("kill -2 " + pid);
                log.info("Killed process: {}", pid);
            }
        } catch (IOException e) {
            log.error("Failed to stop old service", e);
        }
    }
</code></pre>
<p>
 <br>
</p>
<h3>3. 代码实现</h3>
<p>代码的核心逻辑在 <code>changePortAndRestart()</code> 方法中实现，主要步骤包括停止当前 Web 服务器并重启。</p>
<pre><code class="java">    /**
     * 如果端口占用，则尝试杀死占用8080端口的进程，并修改端口并重启服务
     *
     * @param context
     */
    private static void changePortAndRestart(ConfigurableApplicationContext context) {
        log.info("尝试杀死占用 8080 端口的进程.");
        killOldServiceInLinux();
        log.info("正在修改端口更改为 {}.", DEFAULT_PORT_8080);
        ServletWebServerFactory webServerFactory = context.getBean(ServletWebServerFactory.class);
        ServletContextInitializer servletContextInitializer = context.getBean(ServletContextInitializer.class);
        WebServer webServer = webServerFactory.getWebServer(servletContextInitializer);

        if (webServer != null) {
            log.info("停止旧服务器.");
            webServer.stop();
        }
        //((TomcatServletWebServerFactory) servletContextInitializer).setPort(DEFAULT_PORT_8080);
        ((TomcatServletWebServerFactory) webServerFactory).setPort(DEFAULT_PORT_8080);
        webServer = webServerFactory.getWebServer(servletContextInitializer);
        webServer.start();
        log.info("新服务启动成功.");
    }
</code></pre>
<h3><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb22275d41dc340a395079796332cc793.png&amp;size=m" alt=""></h3>
<h3><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3c220f436d7941fb9e62fbfb4068fa74.png&amp;size=m" alt=""></h3>
<h3>4. 配置优雅关闭</h3>
<p>在 <code>application.yml</code> 中设置优雅关闭：</p>
<pre><code class="bash">server:
  shutdown: graceful
</code></pre>
<p>这个配置允许 Spring Boot 在接收到关闭请求时，等待当前请求完成后再停止服务。 （因此代码使用的是kill -2命令）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe6b157ef7e194977a9045c6508ebb829.png&amp;size=m" alt=""></p>
<h3>5. 小结</h3>
<p>通过以上实现，我们能够灵活应对端口占用问题，并提升开发效率。热部署功能不仅依赖于 Spring Boot 提供的丰富 API，还需要结合操作系统特性，以确保在生产环境中的稳定性和可用性。</p>
<h4>附带Window关闭端口程序代码</h4>
<p>（Window关闭程序后可能得需要sleep一下，不然还会显示端口占用）</p>
<pre><code class="java">  private static void killOldServiceInWindows() {
            try {
                // 查找占用 8080 端口的进程 ID
                ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", "netstat -ano | findstr :8080");
                Process process = builder.start();
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    String[] parts = line.trim().split("\\s+");
                    if (parts.length &gt; 4) {
                        String pid = parts[parts.length - 1];
                        // 杀死该进程
                        Runtime.getRuntime().exec("taskkill /F /PID " + pid);
                        log.info("Killed process: {}", pid);
                    }
                }
            } catch (IOException e) {
                log.error("Failed to stop old service", e);
            }
        }
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/lps12345666/article/details/142642059</p>]]></description><guid isPermaLink="false">/archives/17b4960c-f286-4583-8e17-d328f80c4cd7</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:34:01 GMT</pubDate></item><item><title><![CDATA[Spring Boot 三层架构开发模式入门]]></title><link>https://wiki.dgfygk.com/archives/e5db6311-a80e-4b87-a37a-bb1f98fa03dd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%20%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84%E5%BC%80%E5%8F%91%E6%A8%A1%E5%BC%8F%E5%85%A5%E9%97%A8&amp;url=/archives/e5db6311-a80e-4b87-a37a-bb1f98fa03dd" width="1" height="1" alt="" style="opacity:0;">
<p>在 Spring Boot 项目中，构建清晰、易于维护的代码结构至关重要。传统业务逻辑方式往往将所有代码混杂在一起，导致代码难以理解、修改和扩展。三层架构模式的出现正是为了解决这些问题，让我们深入了解它带来的改变。</p>
<h4>一、传统业务逻辑方式</h4>
<p>假设我们要实现一个简单的员工查询功能，传统方式可能会将所有代码写在 Controller 层：</p>
<pre><code class="java">@RestController
@RequestMapping("/employees")
public class EmployeeController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GetMapping("/{id}")
    public Employee getEmployee(@PathVariable Long id) {
        String sql = "SELECT * FROM employees WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper&lt;&gt;(Employee.class));
    }
}
</code></pre>
<p>
 <br>
</p>
<p>这段代码看似简洁，却隐藏着以下问题：</p>
<ul>
 <li>
  <p><strong>代码耦合度高:</strong> Controller 层直接依赖 JdbcTemplate，与数据访问细节紧密绑定。</p>
 </li>
 <li>
  <p><strong>职责不清晰:</strong> Controller 层既负责处理请求，又负责数据访问逻辑，违反了单一职责原则。</p>
 </li>
 <li>
  <p><strong>代码难以复用:</strong> 如果其他模块需要查询员工信息，只能复制这段代码，造成冗余。</p>
 </li>
 <li>
  <p><strong>难以维护和扩展:</strong> 当数据库操作发生变化时，所有涉及到该操作的代码都需要修改，增加了维护成本。</p>
 </li>
</ul>
<h4>二、三层架构模式：分层解耦，各司其职</h4>
<p>1.其实我们上述案例的处理逻辑呢，从组成上看可以分为三个部分：</p>
<ul>
 <li>
  <p>数据访问：负责业务数据的维护操作，包括增、删、改、查等操作。</p>
 </li>
 <li>
  <p>逻辑处理：负责业务逻辑处理的代码。</p>
 </li>
 <li>
  <p>请求处理、响应数据：负责，接收页面的请求，给页面响应数据。</p>
 </li>
</ul>
<p>
 <br>
</p>
<p>2.为了解决传统方式的弊端，我们引入三层架构模式，将代码划分为以下三层：</p>
<ul>
 <li>
  <p><strong>表现层（Controller）：</strong> 负责接收前端请求，调用 Service 层处理业务逻辑，并将处理结果返回给前端。</p>
 </li>
 <li>
  <p><strong>业务逻辑层（Service）：</strong> 负责处理具体的业务逻辑，例如：参数校验、业务规则判断、调用其他服务等，并调用 Repository 层进行数据操作。</p>
 </li>
 <li>
  <p><strong>数据访问层（Dao）：</strong> 负责与数据库交互，进行数据的增删改查操作，屏蔽底层数据库操作细节。</p>
 </li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fef11572a635e44a5865eb3f9e0061106.png&amp;size=m" alt=""></p>
<p>3.基于三层架构的程序执行流程：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5313e960570148ceb85fc18686127104.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p>前端发起的请求，由Controller层接收（Controller响应数据给前端）</p>
 </li>
 <li>
  <p>Controller层调用Service层来进行逻辑处理（Service层处理完后，把处理结果返回给Controller层）</p>
 </li>
 <li>
  <p>Serivce层调用Dao层（逻辑处理过程中需要用到的一些数据要从Dao层获取）</p>
 </li>
 <li>
  <p>Dao层操作文件中的数据（Dao拿到的数据会返回给Service层）</p>
 </li>
</ul>
<p><strong>可以简单的理解为：</strong> 要去一个<strong>盒子套盒子</strong> 的盒子里面取东西，得先依次<strong>由外到内</strong> 打开所有盒子，拿到东西，再由<strong>内到外把</strong>盒子还原为初始状态。</p>
<p>
 <br>
</p>
<h4>三、解耦后的代码示例：清晰、易维护的结构</h4>
<p>让我们用三层架构模式重构之前的员工查询功能：</p>
<p>开发顺序有两种，根据实际情况选择</p>
<p><strong>方式一：Controller -&gt; Service -&gt; Dao</strong></p>
<p><strong>方式二：Dao -&gt; Service -&gt; Controller</strong></p>
<p>
 <br>
</p>
<p><strong>1. 数据访问层 (Dao):</strong></p>
<pre><code class="java">@Repository
public interface EmployeeRepository extends JpaRepository&lt;Employee, Long&gt; {
    //与数据库交互
}
</code></pre>
<p>EmployeeRepository 负责直接与数据库交互，当然，复杂的SQL语句也可以通过mybatis映射文件进行操作，都是一个道理。</p>
<p><strong>2. 业务逻辑层 (Service):</strong></p>
<pre><code class="java">@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    public Employee getEmployeeById(Long id) {
        // 可以在这里添加业务逻辑，例如权限校验等
        return employeeRepository.findById(id).orElseThrow(() -&gt; new RuntimeException("Employee not found"));
    }
}
</code></pre>
<p>EmployeeService 负责处理具体的业务逻辑，例如根据 ID 查询员工信息，并处理可能出现的异常。</p>
<p><strong>3. 表现层 (Controller):</strong></p>
<pre><code class="java">@RestController
@RequestMapping("/employees")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/{id}")
    public Employee getEmployee(@PathVariable Long id) {
        return employeeService.getEmployeeById(id);
    }
}
</code></pre>
<p>EmployeeController 只负责接收请求参数，调用 EmployeeService 处理业务逻辑，并返回结果。</p>
<h4>四、三层架构模式带来的优势</h4>
<p>通过上面的代码示例，我们可以看到三层架构模式带来的好处：</p>
<ul>
 <li>
  <p><strong>降低耦合度:</strong> 各层之间通过接口交互，降低了层与层之间的依赖，提高了代码的灵活性。</p>
 </li>
 <li>
  <p><strong>提高代码复用性:</strong> 将业务逻辑封装在 Service 层，可以方便地在不同的 Controller 或其他模块中复用。</p>
 </li>
 <li>
  <p><strong>提高代码可读性和可维护性:</strong> 将代码逻辑分层，使得代码结构更加清晰，易于理解和维护。</p>
 </li>
 <li>
  <p><strong>易于扩展:</strong> 当需求发生变化时，只需要修改相应的层即可，其他层不受影响。</p>
 </li>
</ul>
<h4>五、总结</h4>
<p>三层架构模式是 Spring Boot 项目开发中常用的架构模式，它可以有效地提高代码的质量和可维护性。通过将代码逻辑分层，我们可以降低代码的耦合度，提高代码的可读性和可复用性，从而使我们的项目更加健壮和易于扩展。</p>
<p>
 <br>
</p>
<p>以上就是关于三层架构的有关知识，感谢各位看官的观看，下期见，谢谢\~</p>
<p>原文链接: https://blog.csdn.net/weixin_64178283/article/details/142718148</p>]]></description><guid isPermaLink="false">/archives/e5db6311-a80e-4b87-a37a-bb1f98fa03dd</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:33:45 GMT</pubDate></item><item><title><![CDATA[2.3MyBatis——插件机制]]></title><link>https://wiki.dgfygk.com/archives/87380a1c-6561-4797-84b1-fb0f3355f7af</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=2.3MyBatis%E2%80%94%E2%80%94%E6%8F%92%E4%BB%B6%E6%9C%BA%E5%88%B6&amp;url=/archives/87380a1c-6561-4797-84b1-fb0f3355f7af" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>2.3MyBatis——插件机制</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1_4">1.基本用法</a></li>
 <li><a href="https://wiki.dgfygk.com#2_36">2.原理探究</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#21_39">2.1加载过程</a></li>
 <li><a href="https://wiki.dgfygk.com#22_68">2.2执行过程</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#221__71">2.2.1 插件的执行点</a></li>
   <li><a href="https://wiki.dgfygk.com#222_SQL_92">2.2.2 SQL执行的几个阶段</a></li>
   <li><a href="https://wiki.dgfygk.com#223__102">2.2.3 如何梳理出执行流程</a></li>
  </ul></li>
</ul>
<p>
 <br>
</p>
<blockquote>
 <p>插件机制是一款优秀框架不可或缺的组成部分，比如spring、dubbo，还有我们要聊的Mybatis等等。所谓插件，通俗一点说，就是框架提供了一个入口，允许你通过实现框架提供的扩展接口，来进行功能增强。比如spring的前置处理器允许你在Bean创建的过程中添加自定义逻辑。</p>
 <p>
  <br>
 </p>
 <p>具体到Mybatis框架，它的核心是ORM和SQL的映射。那么映射成功的SQL在具体执行的过程中，存在许多时机，比如参数处理阶段，SQL语句处理阶段，SQL执行阶段，返回结果的处理阶段等。在这些阶段或者说执行点，如果框架提供了扩展点，那么我们就可以通过插件在相应的环节添加一些能力。</p>
</blockquote>
<h2>1.基本用法</h2>
<blockquote>
 <p>因为Mybatis的插件并不像SQL映射那样被经常使用，所以先看一下插件的基本用法。根据Mybatis官方的文档说明，只要实现Interceptor接口，然后注册插件即可。</p>
</blockquote>
<pre><code class="prism language-java">//测试使用Springboot，插件放入Springboot容器后通过ObjectProvider注入到Configuration对象中完成注册
@Component 
@Intercepts({
    // 声明作用点，即拦截哪些方法
        @Signature(
                type = Executor.class, // 这里拦截执行器的query方法
                method = "query", 
                args = {
   MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class MyPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        System.out.println("自定义插件:统计运行时长");
        long begin = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        System.out.println((end - begin) /1000 + "s");
        return result;
    }
}
</code></pre>
<blockquote>
 <p>这里定义拦截器的方式类似定义切点，<code>type</code>的常用取值为(下文会详细介绍)：</p>
 <ul>
  <li><code>Executor</code></li>
  <li><code>StatementHandler</code></li>
  <li><code>ParameterHandler</code></li>
  <li><code>ResultSetHandler</code></li>
 </ul>
 <p>这些接口涵盖了SQL执行的各个阶段。<code>method</code>取值则是<code>type</code>接口中对应的方法，如果方法存在重载，还可以通过args进行限定。</p>
</blockquote>
<h2>2.原理探究</h2>
<blockquote>
 <p>当mapper接口中的方法执行时，插件被调用并统计时长。关于Mybatis插件，使用时很自然的疑问是：它是如何被加载注册，又是如何被调用执行的（即它的设计原理是什么）？<code>如果你没有疑问</code>，那就现在发出疑问…
  <br>
  然后我们从这两点去分析和学习。</p>
</blockquote>
<h3>2.1加载过程</h3>
<blockquote>
 <p>我们知道在Mybatis中，不管是以哪种方式配置Mybatis，Mybatis的配置信息和mapper.xml的数据最终都会以Configuration对象的形式存在，Configuration它是Mybatis的核心组件之一。
  <br>
  如果不使用Springboot，集成Mybatis需要创建相应的配置文件，在配置文件中进行Mybatis的相关配置，包括插件注册。所以猜想插件信息也是被收集到Configuration对象中。我们浅浅看一下代码：</p>
</blockquote>
<pre><code class="prism language-java">// 1. configuration对象提供了添加拦截器的方法
public void addInterceptor(Interceptor interceptor) {

    interceptorChain.addInterceptor(interceptor);
  }
// 2 XML 配置文件解析时会调用addInterceptor，解析后通过add进行注册
pluginElement(root.evalNode("plugins"));// 解析plugins节点

// 3.1 springboot 项目中，通过ObjectProvider获取所有的Interceptor实现类,然后完成注册
public MybatisAutoConfiguration(ObjectProvider&lt;Interceptor[]&gt; interceptorsProvider,
      ....) {

    this.interceptors = interceptorsProvider.getIfAvailable();
    .....
  }
@Bean
@ConditionalOnMissingBean
 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

...
if (!ObjectUtils.isEmpty(this.interceptors)) {

    factory.setPlugins(this.interceptors);// 注册所有拦截器实现类
}
...
}
</code></pre>
<blockquote>
 <p>这里只需要明白一点，拦截器的实现类(即插件)，最终都会通过<code>addInterceptor</code>被添加到核心组件Configuration对象的<code>interceptorChain</code>属性中。其他的诸如SQL映射(即<code>mappedStatements</code>)、转换器等都是同样的加载逻辑。</p>
</blockquote>
<h3>2.2执行过程</h3>
<blockquote>
 <p><a href="https://blog.csdn.net/wyy546792341/article/details/142704843">2.2Mybatis——代理与SQL映射</a>这篇博客中，只聊到动态代理MapperProxy是如何将mapper接口与mapper.xml进行关联的。至于SQL的后续执行没有描述，下面我们通过研究<code>插件的执行</code>，顺带了解一下<code>SQL的执行</code>过程</p>
</blockquote>
<h4>2.2.1 插件的执行点</h4>
<blockquote>
 <p>既然是介绍插件机制，自然是以插件的执行作为切入点。由于Mybatis的执行过程中涉及多个代理对象的调用，如果在调试过程中发现程序<code>反复横跳</code>，可以先不纠结，你应该至少能发现一点：在Configuration对象中，有多个方法调用了<code>pluginAll</code></p>
</blockquote>
<pre><code class="prism language-java">public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 遍历执行插件逻辑
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }
  // newResultSetHandler newStatementHandler newExecutor

// pluginAll
public Object pluginAll(Object target) {

  for (Interceptor interceptor : interceptors) {

    target = interceptor.plugin(target);
  }
  return target;
}
</code></pre>
<blockquote>
 <p>在<code>newResultSetHandler</code> <code>newStatementHandler</code> <code>newExecutor</code> <code>newParameterHandler</code>四个方法中，都调用了<code>pluginAll</code>，也就是说在这些方法被调用时，都可以植入插件逻辑。</p>
</blockquote>
<h4>2.2.2 SQL执行的几个阶段</h4>
<blockquote>
 <p>如果想要精确控制插件在SQL执行的哪个环节生效，就必须要知道Mybatis中SQL执行分几个阶段，并且每个阶段和<code>newResultSetHandler</code> <code>newStatementHandler</code> <code>newExecutor</code> <code>newParameterHandler</code>的对应关系。
  <br>
  <strong>SQL执行的先后顺序 ：</strong></p>
 <ol>
  <li>创建执行器对象，对应 <code>newExecutor()</code></li>
  <li>创建SQL语句对象，对应 <code>newStatementHandler()</code></li>
  <li>参数处理器， 对应<code>newParameterHandler()</code></li>
  <li>结果集处理器，对应<code>newParameterHandler()</code>
   <br>
   知道了各个阶段和对应的方法，这样才能精确的控制拦截器生效的时机。比如文章开头定义的插件，它将作用在<code>newExecutor()</code>执行时，即SQL执行的最开始阶段。</li>
 </ol>
</blockquote>
<h4>2.2.3 如何梳理出执行流程</h4>
<blockquote>
 <p>前面提到，插件的执行流程中，由于涉及到多个代理对象的调用，所以调试流程不像普通的栈帧嵌套。调试过程如果觉得不顺，建议多去理解<a href="https://blog.csdn.net/wyy546792341/article/details/142704843">2.2Mybatis——代理与SQL映射</a>这篇文章中关于JDK的代理实现。<code>代理对象对接口方法的调用就是调用处理器中invoke方法的执行</code>。明白了这一点，上述的<code>插件执行点和SQL执行阶段都可以梳理出来</code>。我们简单分析一下流程：</p>
</blockquote>
<ol>
 <li>MapperProxy确定SQL映射关系后，调用MapperMethod.execute方法，后面开始执行SQL
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5655ded6397a4c94a5613d82abccc1e0.png&amp;size=m" alt=""></li>
 <li>由于使用的是<code>mybatis-spring-boot-starter</code>依赖，所以这里的<code>sqlSession</code>是<code>SqlSessionTemplate</code>实例，可以看到，<code>SqlSessionTemplate</code>类中持有的<code>sqlSessionProxy</code>就是一个JDK代理对象，所以<code>this.sqlSessionProxy.selectOne()</code>实际是调用<code>SqlSessionInterceptor</code>的<code>invoke</code>方法
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fce9210f3fd514cd5ac0d18b15b8bb0db.png&amp;size=m" alt=""></li>
 <li>继续调试，当调用拦截器链的<code>pluginAll()</code>方法时，内部是调用拦截器的<code>plugin</code>方法。MyBatis内部有<code>Plugin</code>插件类，为啥自定义插件却要实现拦截器接口呢?(<code>拦截器就是插件的逻辑部分</code>)
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa3af249745f04b5498beb0a7b62ce843.png&amp;size=m" alt=""></li>
 <li><code>Plugin</code>对象也是一个调用处理器，即上面代码中<code>Plugin.wrap</code>返回的是一个代理对象，在调用真实对象之前执行代理逻辑，这里的代理逻辑就是拦截器逻辑
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F43b023ad1fc34e53b6d9064195a23cc0.png&amp;size=m" alt=""></li>
 <li>看到这里你应该明白，方法依次返回后，<code>pluginAll</code>返回的是一个JDK代理对象，<strong>以<code>executor</code>为例，代理对象代理了<code>executor</code>，且代理的逻辑就是在<code>executor</code>执行指定方法前，调用插件逻辑（第四步<code>invoke</code>逻辑）</strong> ，这就是Mybatis插件的<strong>原理</strong> 。
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F809ef56cb4e0409cb2eaba6458ecdb4c.png&amp;size=m" alt=""></li>
</ol>
<p>原文链接: https://blog.csdn.net/wyy546792341/article/details/142713379</p>]]></description><guid isPermaLink="false">/archives/87380a1c-6561-4797-84b1-fb0f3355f7af</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:33:22 GMT</pubDate></item><item><title><![CDATA[【Maven】依赖管理，Maven仓库，Maven核心功能]]></title><link>https://wiki.dgfygk.com/archives/c88ee466-2753-477c-8407-9eccd62a1d49</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90Maven%E3%80%91%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86%EF%BC%8CMaven%E4%BB%93%E5%BA%93%EF%BC%8CMaven%E6%A0%B8%E5%BF%83%E5%8A%9F%E8%83%BD&amp;url=/archives/c88ee466-2753-477c-8407-9eccd62a1d49" width="1" height="1" alt="" style="opacity:0;">
<p><code>Maven</code> 是一个项目管理工具，基于 <code>POM</code>（<code>Project Object Model</code>，项目对象模型）的概念，<code>Maven</code> 可以通过一小段描述信息来管理项目的构建，报告和文档的项目管理工具软件</p>
<p>大白话：<code>Maven</code> 是一个项目管理工具，通过 <code>pom.xml</code> 文件的配置获取 <code>jar</code> 包，而不用手动去添加 <code>jar</code> 包</p>
<ul>
 <li>项目开发：编译、打包（jar 包）、运行。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F586c0101a61803e9270bcb330009a6fd.png&amp;size=m" alt=""></p>
<ul>
 <li>当</li>
</ul>
<h2>什么是 Maven</h2>
<p><code>Maven</code> 是一个项目管理工具，基于 <code>POM</code>（<code>Project Object Model</code>，项目对象模型）的概念，<code>Maven</code> 可以通过一小段描述信息来管理项目的构建，报告和文档的项目管理工具软件</p>
<p>大白话：Maven 是一个项目管理工具，通过 pom. xml 文件的配置获取 jar 包，而不用手动去添加 jar 包</p>
<blockquote>
 <ul>
  <li>Maven 简单，高效，可以提高我们的开发效率，减少我们的开发 bug。</li>
  <li>Maven 提供的功能非常多，在这主要体现两方面：</li>
  <li>项目构建</li>
  <li>管理依赖</li>
 </ul>
</blockquote>
<h2>创建一个 Maven 项目</h2>
<p><code>IDEA</code> 本身已经继承了 <code>Maven</code>，我们可以直接使用，无需安装</p>
<blockquote>
 <p>以下截图的 <code>IDEA</code> 版本为：<code>2024.1.2</code>，不同版本的 <code>IDEA</code> 界面展示会有不同</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F87bed86c31c290984e06c46d79e9bbfb.png&amp;size=m" alt=""></p>
<ul>
 <li>点击 <code>create</code>，就创建好了一个 <code>Maven</code> 项目</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F33ed63b07366d148c4d63c036484b2e9.png&amp;size=m" alt=""></p>
<h2>Maven 核心功能</h2>
<p>接下来我们结合项目，介绍 <code>Maven</code> 在项目开发中的作用</p>
<ol>
 <li>项目构建</li>
 <li>管理依赖</li>
</ol>
<h3>项目构建</h3>
<p><code>Maven</code> 提供了标准的，跨平台（<code>Linux</code>、<code>Windows</code>、<code>MacOS</code> 等）的自动化项目构建方式</p>
<p>当我们开发了一个项目之后，代码需要经过<strong>编译、测试、打包、发布等流程</strong> 。每次代码的修改，都需要经过这些流程，如果代码反复调试修改，这个流程就需要反复进行，就显得麻烦。而 <code>Maven</code> 给我们提供了一套简单的命令阿里完成项目的构建</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fb273c70ef95a0090c6fde35c2b915797.png&amp;size=m" alt=""></p>
<ul>
 <li>比如点击”<code>package</code>“，就可以完成项目的打包操作</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F188d3a0ef41baae1c9d02f68a9038e51.png&amp;size=m" alt=""></p>
<p>打包就是把所有的 <code>class</code> 文件，全部放在一起，打成 <code>jar</code> 包或者 <code>war</code> 包</p>
<blockquote>
 <ul>
  <li><code>jar</code> 和 <code>war</code> 包是一种压缩文件</li>
  <li><code>jar</code> 包就是把开发人员已经写好的一些代码进行打包。打包好的 <code>jar</code> 包就可以进入到其他项目中，也可以直接使用这些 <code>jar</code> 包中的类和属性</li>
  <li>另外，也可以打成可执行 <code>jar</code> 包，这样的包就可以通过 <code>java -jar</code> 命令来执行</li>
  <li><code>war</code> 包可以理解为是一个 <code>web</code> 项目，里面是项目的所有东西，通常用于网站</li>
 </ul>
</blockquote>
<h3>依赖管理</h3>
<p>上面说到，<code>Maven</code> 是一个项目管理工具，通过 <code>pom.xml</code> 文件的配置获取 <code>jar</code> 包，而不用手动去添加 <code>jar</code> 包获取的 jar 包，其实就是依赖</p>
<blockquote>
 <p><code>pom.xml</code> 就是 <code>Maven</code> 的配置文件，用以描述项目的各种信息</p>
</blockquote>
<h4>依赖配置</h4>
<p>依赖：指当前项目运行时所需要的 <code>jar</code> 包
 <br>
 比如 <code>JDBC</code>，我们需要手动下载 <code>mysql-connector-java</code> 的包，并且添加到项目中</p>
<hr>
<p>如果使用 <code>Maven</code>，我们只需要在 <code>pomxml</code> 中引入 <code>mysql-connector-java</code> 的依赖就可以了</p>
<pre><code class="prism language-html">&lt;dependencies&gt;  
    &lt;!--里面放置项目的依赖坐标，可以为多个--&gt;  
&lt;/dependencies&gt;
</code></pre>
<ol>
 <li>在 <code>pom</code> 文件 <code>&lt;dependencies&gt;</code> 标签内，添加依赖坐标</li>
</ol>
<pre><code class="prism language-html">&lt;dependencies&gt;  
    &lt;groupId&gt;mysql&lt;/groupId&gt;
    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
    &lt;version&gt;8.0.30&lt;/version&gt;  
&lt;/dependencies&gt;
</code></pre>
<ol>
 <li>
  <p>点击刷新按钮，引入新加的依赖 jar 包</p>
 </li>
 <li>
  <p>后续有添加新的 jar 包，或者修改 jar 包的版本，都需要通过该方式在项目中添加依赖<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9a1330778e54864ca5e0ac7a76644dae.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>刷新完之后，就可以在项目中看到新加入的 jar 包</p>
 </li>
</ol>
<h4>依赖传递</h4>
<p>早期我们没有使用 Maven 时，向项目中添加依赖的 jar 包，需要把所有的 jar 包都复制到项目工程下。</p>
<p>比如，A 依赖 B，B 依赖 C，那么 A 项目引入 B 的同时，也需要引入 C，如果我们手动管理这个依赖，这个过程就会比较麻烦，我们需要知道每个库都依赖哪些库，以及这些依赖之间的版本是如何关联的</p>
<blockquote>
 <ul>
  <li>比如我们要吃火锅，需要有锅、调料、食材，以及确定什么样的锅，什么样的食材</li>
  <li>比如去医院看病，需要带上以往的病例，检查结果、处方等，并且要确定带的资料是正确的。如果日期错了，或者患者错了，带少了，就需要回去重新拿</li>
 </ul>
</blockquote>
<p>但如果是使用 Maven 的话，就可以避免管理所需依赖的关系。我们只需要在 pom 文件中，定义直接依赖就可以了。由于 Maven 的依赖具有传递性，所以会自动把所依赖的其他 jar 包也一起导入</p>
<blockquote>
 <ul>
  <li>比如吃火锅，现在我们点一个海底捞外卖，直接就把所有食材都送过来了，包括什么锅、什么食材</li>
  <li>比如去医院看病，借助"互联网”，实现了信息互享，只需要带上身份证，以往的病历和检查结果就都可以看到了</li>
 </ul>
</blockquote>
<p>Maven 会把在引入一个依赖的时候，会一起把实现这个功能所需要的依赖也一起自动引入</p>
<p>这里引入 mysql，进行打包，protobuf 就是连带的依赖，需要一起引入</p>
<pre><code class="prism language-Java">&lt;dependencies&gt;  
    &lt;dependency&gt;  
        &lt;groupId&gt;mysql&lt;/groupId&gt;  
        &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
        &lt;version&gt;8.0.29&lt;/version&gt;  
    &lt;/dependency&gt;  
&lt;/dependencies&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F8b635fa08b3e441b1cf5c3a5c34ab762.png&amp;size=m" alt=""></p>
<h4>依赖排除</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F141a7b1a608fa3656b22ab44fad677b1.png&amp;size=m" alt="">
 <br>
 项目 <code>A</code> ，依赖一个项目 <code>B</code> 和一个 <code>jar</code> 包 <code>A</code></p>
<ul>
 <li><code>jar</code> 包 <code>A</code> 依赖 <code>jar</code> 包 <code>B</code>、<code>jar</code> 包 <code>C</code></li>
 <li>项目 <code>B</code> 里面是各种依赖，其中也用到了 <code>jar</code> 包 <code>C</code></li>
</ul>
<p>但是两边都需要引入 jar 包 C，此时我们在哪边进行引入？</p>
<blockquote>
 <p>一般 jar 包都是向下兼容，高版本可以兼容低版本</p>
</blockquote>
<p><strong>最短路径法：</strong>
 <br>
 看项目 <code>A</code> 到两个 <code>jar</code> 包 <code>C</code> 放入路径，哪个更短就用哪个</p>
<ul>
 <li>左边的是三步</li>
 <li>右边的是两步
  <br>
  所以就在右边引入 <code>jar</code> 包</li>
</ul>
<p>而右边的 <code>jar</code> 包 <code>C</code> 版本比左边的低，无法兼容左边的高版本 <code>jar</code> 包 <code>C</code>。此时就需要将右边的 <code>jar</code> 包 <code>C</code> 从 <code>1.0</code> 升级成 <code>2.0</code>，此时就可以进行<strong>指定依赖</strong></p>
<ul>
 <li>首先将 <code>1.0</code> 的 <code>jar</code> 包 <code>C</code> 给排除掉</li>
 <li>此时项目中就只剩下 <code>2.0</code> 的 <code>jar</code> 包 <code>C</code> 了，我们在根据路径找 <code>jar</code> 包 <code>C</code> 的时候，就只能找到 <code>2.0</code> 的 <code>jar</code> 包 <code>C</code> 了</li>
</ul>
<blockquote>
 <p>虽然在上面的图片中看起来是有结构的，谁依赖谁。但是放在项目中，都是平铺直叙的，都是放在一起用的</p>
</blockquote>
<h4>依赖排除</h4>
<p><code>enclusions</code> 就是排除一些依赖，不会默认把相关的所有依赖都进行引入</p>
<p>上面引入依赖的时候，主依赖和相关依赖都是从网上下载的。需要排除一些依赖，就将网上的地址给出来</p>
<h3>Maven Help</h3>
<p>当项⽬⽐较复杂时，我们就会有 Jar 包冲突的问题，这时候就需要去解决依赖冲突。解决冲突之前，需要先找到冲突，我们可以使⽤ Maven Help 插件来观察包和包之间的依赖关系<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F52c6e7988404649d98d88594cb93bc5f.png&amp;size=m" alt=""></p>
<ul>
 <li>如果需要排除某个依赖，就右键点击，选择 <code>Exclude</code></li>
</ul>
<h3>依赖的坐标</h3>
<p>这个代码，我们称之为"坐标”，也就是唯一的</p>
<p>在 <code>Maven</code> 中，根据 <code>groupId</code>、<code>artfactId</code>、<code>version</code> 的配置，来唯一识别一个 <code>jar</code> 包，缺一不可。当我们在 <code>pom</code> 文件中配置完依赖之后，点击刷新, <code>Maven</code> 就会根据坐标的位置，去仓库里寻找 <code>jar</code> 包，并把它下载下来，添加到项目中，这个 <code>jar</code> 包下载的地方就叫仓库</p>
<p>仓库：用来存储资源，管理各种 <code>jar</code> 包</p>
<pre><code class="prism language-java">&lt;dependency&gt;  
    &lt;groupId&gt;mysql&lt;/groupId&gt;  
    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
    &lt;version&gt;8.0.30&lt;/version&gt;  
&lt;/dependency&gt;
</code></pre>
<h2>Maven 仓库</h2>
<p><code>Maven</code> 仓库分为两种：</p>
<ol>
 <li>
  <p>本地仓库
   <br>
   仓库在你本机电脑上，是本地仓库。其他都是远程仓库</p>
 </li>
 <li>
  <p>远程仓库</p>
 </li>
</ol>
<h3>远程仓库</h3>
<h4>中央仓库</h4>
<p>全球唯一，地址为：
 <br>
 https://repo1.maven.org/maven2/springframework/</p>
<p>里面东西太多了，不好找。可以在这里面进行快速查找：
 <br>
 https://mvnrepository.com</p>
<ul>
 <li>私有服务器</li>
 <li>其他公共的</li>
</ul>
<p>Maven 版本选择哪个？</p>
<ol>
 <li>没漏洞的</li>
 <li>选择次新版本</li>
 <li>选择人多的
  <br>
  不严格，推荐是基于大家不知道用哪个的情况，对这个 jar 包不熟的情况</li>
</ol>
<hr>
<p>jar 包下载路径</p>
<ol>
 <li>先去本地仓库查找，如果没有，就去中央仓库查</li>
 <li>如果中央仓库没有，就下载失败，报错</li>
 <li>如果中央仓库有，jar 包就从中央仓库下载到本地仓库</li>
 <li>项目从本地仓库中获取</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ffeb298de5826c908c91dadee85e830fb.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F3dad7d54da8205144a65e61e6c9a809f.png&amp;size=m" alt="">
 <br>
 本地仓库原来是空的，随着使用，里面的包越来越多（从中央仓库下载过来的）</p>
<h4>私有服务器</h4>
<p>中央仓库里面的包是全世界的都可以看到，而有一些不想公开的 jar 包就放在私服中（私有服务器）</p>
<ul>
 <li>有账号和密码，公司里面可以用</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F69ded0141da8d85471313d6479f85292.png&amp;size=m" alt=""></p>
<ul>
 <li>有权限的人，一起使用的</li>
 <li>如果遇到私服中不存在的 jar 包，第一个人下载之后，后面的人就从私服中可以获取到了，无需再下载了</li>
</ul>
<h4>其他公共服务器</h4>
<ul>
 <li>阿里云</li>
 <li>华为云</li>
</ul>
<p>因为中央仓库在国外，所以下载起来会⽐较慢，所以咱们选择借助国内⼀些公开的远程仓库来下载资源</p>
<p>阿里云就相当于是把中央仓库弄了一个镜像，放在了国内，并公开给人用</p>
<hr>
<p>如果路径中有中文，修改一下 Maven 本地仓库路径</p>
<p>原文链接: https://blog.csdn.net/Yeeear/article/details/142678200</p>]]></description><guid isPermaLink="false">/archives/c88ee466-2753-477c-8407-9eccd62a1d49</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:31:26 GMT</pubDate></item><item><title><![CDATA[Junit和枚举ENUM]]></title><link>https://wiki.dgfygk.com/archives/c10839c5-cae7-43d5-9045-c5a211c005c9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Junit%E5%92%8C%E6%9E%9A%E4%B8%BEENUM&amp;url=/archives/c10839c5-cae7-43d5-9045-c5a211c005c9" width="1" height="1" alt="" style="opacity:0;">
<p>断言机制，JAVA中的断言机制是一种用于检查程序中某个条件是否为真的机制。它可以在程序运行时检查某个条件是否满足，如果不满足则会抛出AssertionError异常。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F97525f66caf423e4a064070dc64dfe64.png&amp;size=m" alt=""></p>
<p>在java中,断言机制默认是关闭的。所以会输出u。</p>
<p>断言机制只是为了用来吃调试程序的，切勿将断言写入业务逻辑中。</p>
<p>目标字节码版本（Target Bytecode Version）是指在Java编译过程中，指定生成的字节码文件的版本。Java编译器会将Java源代码编译成字节码文件，然后由Java虚拟机（JVM）来执行这些字节码文件。</p>
<p>在Java中，每个版本的JDK都对应着一种字节码版本。不同的字节码版本可能会引入新的语言特性或优化，同时也可能会不兼容之前的版本。</p>
<p>通过指定目标字节码版本，可以确保编译器生成与指定版本兼容的字节码文件。这样，在运行时，只需要相应版本的JVM即可正确执行字节码文件。</p>
<p>在Java中，可以使用以下命令来指定目标字节码版本：</p>
<pre><code>javac -target &lt;version&gt; &lt;source files&gt;
</code></pre>
<p>其中，<code>&lt;version&gt;</code>可以是以下几个值之一：</p>
<ul>
 <li>1.1：Java 1.1</li>
 <li>1.2：Java 1.2</li>
 <li>1.3：Java 1.3</li>
 <li>1.4：Java 1.4</li>
 <li>1.5：Java 5</li>
 <li>1.6：Java 6</li>
 <li>11：Java 11</li>
 <li>12：Java 12</li>
 <li>13：Java 13</li>
 <li>14：Java 14</li>
 <li>15：Java 15</li>
 <li>16：Java 16</li>
</ul>
<p>请注意，目标字节码版本的选择应根据项目的需求和目标平台来确定。</p>
<p><code>invoke</code>方法是在代理对象的方法被调用时执行的。在这个例子中，<code>invoke</code>方法会在用户通过代理对象调用目标对象的方法时执行。</p>
<h3>Junit单元测试</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F45cbe006eac1f44e2878a3dc63badc5d.png&amp;size=m" alt=""></p>
<p>Junit的断言机制，是Junit的核心</p>
<p>junit主要是测试方法的，有的方法的健壮性不高 ！</p>
<p>有的代码在Junit的单元测试中虽然没有报错，但是代码的逻辑是有错误的，输出的结果和预期的结果不一样！这个时候就可以使用Junit的断言机制了 ！</p>
<p>Generate里面有自动生成Junit单元测试的选项 ！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F322d9520f146ab40ae5738b543a5a10f.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fd11ee7c466397d94fd0cd203a26508d7.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F5a624e9739fb576d9253882081e1eba5.png&amp;size=m" alt=""></p>
<p><a href="https://so.csdn.net/so/search?q=TestNG&amp;spm=1001.2101.3001.7020" title="TestNG">TestNG</a>是一个java中的开源自动化测试框架，其灵感来自JUnit和NUnit，TestNG还涵盖了JUnit4整个核心的功能，但引入了一些新的功能，使其功能更强大，使用更方便。</p>
<p>import org.testng.annotations.AfterTest;
 <br>
 import org.testng.annotations.BeforeTest;</p>
<p>TestNG的AfterTest在测试JedisPool的时候不会报错，但是Junit的After和Before是会报错的 ！</p>
<p>@BeforeTest</p>
<p>public void setUp(){}</p>
<p>@AfterTest</p>
<p>public void tearDown(){}</p>
<p>2、TestNG常用注解
 <br>
 有必要介绍一下TestNG注解的生命周期，先看一下官网支持的注解有 ：</p>
<p>
 <br>
</p>
<p>@BeforeSuite 在该套件的所有测试运行之前运行，仅运行一次
 <br>
 @AfterSuite 在该套件的所有测试运行之后运行，仅运行一次
 <br>
 @BeforeTest 注释的方法将在属于test标签内的所有类的所有测试方法运行之前运行，一个test标签内可能有多个class（类），在当前test标签内仅运行一次
 <br>
 @AfterTest 注释的方法将在属于test标签内的所有类的所有测试方法运行之后运行，一个test标签内可能有多个class（类），在当前test标签内仅运行一次
 <br>
 @BeforeClass 在调用当前类的第一个测试方法之前运行，在当前类中仅运行一次
 <br>
 @AfterClass 在调用当前类的第一个测试方法之后运行，在当前类中仅运行一次
 <br>
 @BeforeGroups 配置方法将在组列表运行之前运行。 此方法保证在调用属于这些组中的任何第一个测试方法之前不久运行
 <br>
 @AfterGroups 此配置方法将在组列表运行之后运行。该方法保证在调用属于这些组的任何最后一个测试方法之后不久运行
 <br>
 @BeforeMethod 注释方法将在当前类中的每个测试方法之前运行
 <br>
 @AfterMethod 注释方法将在当前类中的每个测试方法之后运行
 <br>
 @Parameters 描述如何将参数传递给@Test方法
 <br>
 @DataProvider 标记一种方法来提供测试方法的数据。 注释方法必须返回一个Object [] []，其中每个Object []可以被分配给测试方法的参数列表。 要从该DataProvider接收数据的@Test方法需要使用与此注释名称相等的dataProvider名称
 <br>
 @Listeners 定义测试类上的侦听器
 <br>
 @Factory 将一个方法标记为工厂，返回TestNG将被用作测试类的对象。 该方法必须返回Object []
 <br>
 @Test 将类或方法标记为测试的一部分，此标记若放在类上，则该类所有公共方法都将被作为测试方法</p>
<h3>注解</h3>
<p>使用关键字 <code>enum</code> 定义的枚举类型，在编译期后，会转换成为一个实实在在的类，而在该类中，会存在每个在枚举类型中定义好常量的对应实例对象。</p>
<p>枚举类 ColorEnum 中自定义了一个无参构造方法。创建枚举实例就等同于调用此类的参构造器。
 <br>
 所以，3 个实例，就会调用 3 次构造方法，就会打印 3 次 “构造方法被调用”。</p>
<p>自定义构造方法是否带参数，得看枚举实例，若枚举实例中带参数（见下文），则构造方法中得带参数；否则，就不能带参数</p>
<p>枚举只是一种语法糖，最终会被编译器生成类，而枚举实例会变成静态常量。因此，从某种意义上说，jdk1.5 引入的枚举类型是枚举常量类的代码封装。当用setter() 方法进行修改值的时候，实际上是修改的一个内存中的静态变量的值，这个值原本的意义就被修改了！</p>
<p>枚举类使用了单例模式，保证了每个枚举实例的唯一性，且每个枚举实例都是静态常量。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa6193e4bd436533e7a1c6b235e0afc3d.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F5950052a0d71ac65121704a4a0970227.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fb29cd89cfa6a347452d38bcdbf5f1643.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F2eb7019b77c4572cd6a496a7197896b9.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F1a8462673e142ad3b2392fded96e36da.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/m0_74728220/article/details/142645928</p>]]></description><guid isPermaLink="false">/archives/c10839c5-cae7-43d5-9045-c5a211c005c9</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:30:01 GMT</pubDate></item><item><title><![CDATA[SpringCloud-基于Docker和Docker-Compose的项目部署]]></title><link>https://wiki.dgfygk.com/archives/f3270244-6835-4aac-9caf-71d5ccd90162</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-%E5%9F%BA%E4%BA%8EDocker%E5%92%8CDocker-Compose%E7%9A%84%E9%A1%B9%E7%9B%AE%E9%83%A8%E7%BD%B2&amp;url=/archives/f3270244-6835-4aac-9caf-71d5ccd90162" width="1" height="1" alt="" style="opacity:0;">
<h3>一、初始化环境</h3>
<h4>1. 卸载旧版本</h4>
<p>首先，卸载可能已存在的旧版本 Docker。如果您不确定是否安装过，可以直接执行以下命令：</p>
<pre><code class="prism language-bash">sudo yum remove docker \
                docker-client \
                docker-client-latest \
                docker-common \
                docker-latest \
                docker-latest-logrotate \
                docker-logrotate \
                docker-engine
</code></pre>
<h4>2. 更新系统</h4>
<p>更新系统信息：</p>
<pre><code class="prism language-bash">sudo yum update -y
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff92d29f8727b4f22a9eb124d68ec69f4.png&amp;size=m" alt=""></p>
<h4>3. 安装 Docker 所需的依赖</h4>
<pre><code class="prism language-bash">sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd67168ddf51846a9809ce10050d40de4.png&amp;size=m" alt=""></p>
<hr>
<h3>二、设置 Docker 镜像源</h3>
<p>为加快下载速度，建议使用国内的 Docker 镜像源。</p>
<h4>1. 腾讯云镜像源</h4>
<pre><code class="prism language-bash">sudo yum-config-manager \
    --add-repo \
    https://mirrors.cloud.tencent.com/docker-ce/linux/centos/docker-ce.repo
</code></pre>
<h4>2. 阿里云镜像源</h4>
<pre><code class="prism language-bash">sudo yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
</code></pre>
<h4>3. 清华大学镜像源</h4>
<pre><code class="prism language-bash">sudo yum-config-manager \
    --add-repo \
    https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo
</code></pre>
<h4>4. 官方镜像源</h4>
<p>官方镜像源，即国外镜像源，速度较慢，且可能被墙。</p>
<pre><code class="prism language-bash">sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F866130020180468dbd9e877cb4ccccf4.png&amp;size=m" alt=""></p>
<hr>
<h3>三、安装 Docker</h3>
<h4>1. 安装最新版本</h4>
<pre><code class="prism language-bash">sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3d5cc68792da42e19218f2c4ade86364.png&amp;size=m" alt=""></p>
<h4>2. 安装指定版本</h4>
<p>如果需要安装特定版本的 Docker，可以先查询可用的版本列表：</p>
<pre><code class="prism language-bash">yum list docker-ce --showduplicates | sort -r
</code></pre>
<p>根据查询结果，选择所需的版本（例如 <code>26.0.0</code>），然后执行：</p>
<pre><code class="prism language-bash">sudo yum install -y docker-ce-&lt;VERSION&gt; docker-ce-cli-&lt;VERSION&gt; containerd.io
</code></pre>
<p>将 <code>&lt;VERSION&gt;</code> 替换为实际的版本号。</p>
<h4>3. 启动并设置开机自启</h4>
<pre><code class="prism language-bash">sudo systemctl start docker
sudo systemctl enable docker
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6871a781e5ce4e049e0d128f692ebff8.png&amp;size=m" alt=""></p>
<h4>4. 验证安装结果</h4>
<pre><code class="prism language-bash">docker info
</code></pre>
<hr>
<h3>四、安装 Docker Compose</h3>
<h4>1. 添加 EPEL 源</h4>
<pre><code class="prism language-bash">sudo yum install -y epel-release
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1a93ad42decf4af4bed5ae5fd62f7e49.png&amp;size=m" alt=""></p>
<h4>2. 安装 Docker Compose</h4>
<pre><code class="prism language-bash">sudo yum install -y docker-compose
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcda248fdf58e4749b60cf3833c82ccbc.png&amp;size=m" alt=""></p>
<h4>3. 查看版本</h4>
<pre><code class="prism language-bash">docker-compose --version
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F19a240e4ad7142a0bc9a5d3f12bb6617.png&amp;size=m" alt=""></p>
<hr>
<h3>五、配置镜像加速器</h3>
<p>为了提高拉取镜像的速度，可以配置镜像加速器。</p>
<h4>1. 腾讯云镜像加速器</h4>
<p>编辑配置文件</p>
<pre><code class="prism language-bash">sudo vim /etc/docker/daemon.json
</code></pre>
<p>添加以下内容</p>
<pre><code class="prism language-json">{

  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com"
  ]
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F384738e1fa344ac794580983623cdf69.png&amp;size=m" alt=""></p>
<h4>2. 阿里云镜像加速器</h4>
<p>请参考 <a href="https://help.aliyun.com/zh/acr/user-guide/accelerate-the-pulls-of-docker-official-images">配置镜像加速器_容器镜像服务(ACR)</a> 获取您的专属加速器地址。</p>
<p>添加加速器地址</p>
<pre><code class="prism language-bash">sudo tee /etc/docker/daemon.json &lt;&lt;-'EOF'
{
    "registry-mirrors": [
        "https://&lt;您的阿里云加速器地址&gt;"
    ]
}
EOF
</code></pre>
<p>将 <code>&lt;您的阿里云加速器地址&gt;</code> 替换为实际的地址。</p>
<h4>3. 重启 Docker</h4>
<pre><code class="prism language-bash">sudo systemctl daemon-reload
sudo systemctl restart docker
</code></pre>
<h4>4. 验证配置</h4>
<pre><code class="prism language-bash">docker info
</code></pre>
<p>在输出信息中，应该能看到配置的镜像加速器。</p>
<hr>
<h3>六、上传要部署的 SpringCloud 项目</h3>
<p>根据项目需求，修改 <code>docker-compose.yml</code> 文件。将项目部署到根目录下的 <code>docker</code> 目录中。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2cd733e8ac0c446f84913a78e47d53cf.png&amp;size=m" alt=""></p>
<hr>
<h3>七、使用 Docker Compose 初始化项目</h3>
<h4>1. 设置权限</h4>
<pre><code class="prism language-bash">sudo chmod -R 777 /docker
</code></pre>
<h4>2. 进入项目目录</h4>
<pre><code class="prism language-bash">cd /docker
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F148bc332815748acb8536275cffe0ba7.png&amp;size=m" alt=""></p>
<h4>3. 创建并后台运行所有容器</h4>
<pre><code class="prism language-bash">docker-compose up -d
</code></pre>
<p>等待镜像下载、构建和容器启动完成。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3c52f2705f884424a2c673b18132f18f.png&amp;size=m" alt=""></p>
<p>您可以使用以下命令查看容器状态：</p>
<pre><code class="prism language-bash"># 查看镜像列表
docker images
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1238b52f05cb4766b2155e54256c3215.png&amp;size=m" alt=""></p>
<pre><code class="prism language-bash"># 查看所有容器
docker ps -a
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0ab2f872f2a649049c66e528f58c2912.png&amp;size=m" alt=""></p>
<pre><code class="prism language-bash"># 查看指定容器的日志
docker logs -f &lt;容器名称&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb9bc7224b0194d438fd22aadbbabc5d3.png&amp;size=m" alt="">
 <br>
 访问我们部署的项目地址。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff230982a0f2649349719e6225a5c628f.png&amp;size=m" alt=""></p>
<p>部署成功</p>
<h4>4. 更新容器</h4>
<p>重新构建并启动容器（当代码有改动时）</p>
<pre><code class="prism language-bash">docker-compose up --force-recreate --build -d &lt;服务名称&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6e8a8c54e0854e2199b4c3a2bdc17f61.png&amp;size=m" alt=""></p>
<p>将 <code>&lt;服务名称&gt;</code> 替换为 <code>docker-compose.yml</code> 中定义的服务名称。</p>
<p>重启容器（仅刷新）</p>
<pre><code class="prism language-bash">docker restart &lt;容器名称&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbcde5d5c266d45b2b8204926c3b8deb9.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/weixin_41793160/article/details/142681636</p>]]></description><guid isPermaLink="false">/archives/f3270244-6835-4aac-9caf-71d5ccd90162</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:29:50 GMT</pubDate></item><item><title><![CDATA[linux 网卡配置]]></title><link>https://wiki.dgfygk.com/archives/1c8851a8-eeeb-4879-b824-dcf7183b70b7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=linux%20%E7%BD%91%E5%8D%A1%E9%85%8D%E7%BD%AE&amp;url=/archives/1c8851a8-eeeb-4879-b824-dcf7183b70b7" width="1" height="1" alt="" style="opacity:0;">
<p>linux网卡可以通过命令和配置文件配置,如果是桌面环境还可以通过图形化界面配置.</p>
<h4>1.ifconfig(interfaces config)命令方式</h4>
<p>通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使用一些选项属性，ifconfig工具不仅可以被用来简单地获取网络接口配置信息，还可以修改这些配置(<strong>用ifconfig命令配置的网卡信息，在网卡重启后机器重启后，配置就不存在</strong>)。</p>
<h5>1.1命令格式</h5>
<pre><code>ifconfig [网络设备] [参数]
</code></pre>
<h5>1.2命令功能</h5>
<p>ifconfig 命令用来查看和配置网络设备。当网络环境发生改变时可通过此命令对网络进行相应的配置。</p>
<h5>1.3命令参数</h5>
<ol>
 <li>up 启动指定网络设备/网卡。</li>
 <li>down 关闭指定网络设备/网卡。该参数可以有效地阻止通过指定接口的IP信息流，如果想永久地关闭一个接口，我们还需要从核心路由表中将该接口的路由信息全部删除。</li>
 <li>arp 设置指定网卡是否支持ARP协议。
  <br>
  -promisc 设置是否支持网卡的promiscuous模式，如果选择此参数，网卡将接收网络中发给它所有的数据包
  <br>
  -allmulti 设置是否支持多播模式，如果选择此参数，网卡将接收网络中所有的多播数据包
  <br>
  -a 显示全部接口信息
  <br>
  -s 显示摘要信息（类似于 netstat -i）</li>
 <li>add 给指定网卡配置IPv6地址</li>
 <li>del 删除指定网卡的IPv6地址</li>
 <li>&lt;硬件地址&gt; 配置网卡最大的传输单元</li>
 <li>mtu&lt;字节数&gt; 设置网卡的最大传输单元 (bytes)</li>
 <li>netmask&lt;子网掩码&gt; 设置网卡的子网掩码。掩码可以是有前缀0x的32位十六进制数，也可以是用点分开的4个十进制数。如果不打算将网络分成子网，可以不管这一选项；如果要使用子网，那么请记住，网络中每一个系统必须有相同子网掩码。</li>
 <li>tunel 建立隧道</li>
 <li>dstaddr 设定一个远端地址，建立点对点通信</li>
 <li>-broadcast&lt;地址&gt; 为指定网卡设置广播协议</li>
 <li>-pointtopoint&lt;地址&gt; 为网卡设置点对点通讯协议</li>
 <li>multicast 为网卡设置组播标志</li>
 <li>address 为网卡设置IPv4地址</li>
 <li>txqueuelen&lt;长度&gt; 为网卡设置传输列队的长度</li>
</ol>
<h5>1.4使用实例</h5>
<h6>1.4.1显示网络设备信息（激活状态的）</h6>
<p><strong>命令</strong> :<code>ifcofig</code>
 <br>
 <strong>输出</strong>:</p>
<pre><code>[root@localhost ~]# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:50:56:BF:26:20  
          inet addr:192.168.120.204  Bcast:192.168.120.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0
          TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:596390239 (568.7 MiB)  TX bytes:2886956 (2.7 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:68 errors:0 dropped:0 overruns:0 frame:0
          TX packets:68 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2856 (2.7 KiB)  TX bytes:2856 (2.7 KiB)
</code></pre>
<p><strong>说明</strong></p>
<ul>
 <li>eth0 表示第一块网卡， 其中 HWaddr 表示网卡的物理地址，可以看到目前这个网卡的物理地址(MAC地址）是 00:50:56:BF:26:20</li>
 <li>inet addr 用来表示网卡的IP地址，此网卡的 IP地址是 192.168.120.204，广播地址， Bcast:192.168.120.255，掩码地址Mask:255.255.255.0</li>
 <li>lo 是表示主机的回坏地址，这个一般是用来测试一个网络程序，但又不想让局域网或外网的用户能够查看，只能在此台主机上运行和查看所用的网络接口。比如把 HTTPD服务器的指定到回坏地址，在浏览器输入 127.0.0.1 就能看到你所架WEB网站了。但只是您能看得到，局域网的其它主机或用户无从知道。</li>
 <li>第一行：连接类型：Ethernet（以太网）HWaddr（硬件mac地址）</li>
 <li>第二行：网卡的IP地址、子网、掩码</li>
 <li>第三行：UP（代表网卡开启状态）RUNNING（代表网卡的网线被接上）MULTICAST（支持组播）MTU:1500（最大传输单元）：1500字节</li>
 <li>第四、五行：接收、发送数据包情况统计</li>
 <li>第七行：接收、发送数据字节数统计信息。</li>
</ul>
<h6>1.4.2启动关闭指定网卡</h6>
<p><strong>命令</strong> ：
 <br>
 ifconfig eth0 up
 <br>
 ifconfig eth0 down
 <br>
 <strong>输出</strong> ：
 <br>
 <strong>说明</strong> ：
 <br>
 ifconfig eth0 up 为启动网卡eth0 ；ifconfig eth0 down 为关闭网卡eth0。ssh登陆linux服务器操作要小心，关闭了就不能开启了，除非你有多网卡。</p>
<h6>1.4.3为网卡配置和删除IPv6地址</h6>
<p><strong>命令</strong> ：
 <br>
 ifconfig eth0 add 33ffe:3240:800:1005::2/64
 <br>
 ifconfig eth0 del 33ffe:3240:800:1005::2/64
 <br>
 <strong>输出</strong> ：
 <br>
 <strong>说明</strong> ：
 <br>
 ifconfig eth0 add 33ffe:3240:800:1005::2/64 为网卡eth0配置IPv6地址；
 <br>
 ifconfig eth0 add 33ffe:3240:800:1005::2/64 为网卡eth0删除IPv6地址；
 <br>
 练习的时候，ssh登陆linux服务器操作要小心，关闭了就不能开启了，除非你有多网卡。</p>
<h6>1.4.4用ifconfig修改MAC地址</h6>
<p><strong>命令</strong> ：
 <br>
 <code>ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE</code>
 <br>
 <strong>输出</strong>：</p>
<pre><code>[root@localhost ~]# ifconfig eth0 down //关闭网卡
[root@localhost ~]# ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE //修改MAC地址
[root@localhost ~]# ifconfig eth0 up //启动网卡
[root@localhost ~]# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:AA:BB:CC:DD:EE  
          inet addr:192.168.120.204  Bcast:192.168.120.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0
          TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:596390239 (568.7 MiB)  TX bytes:2886956 (2.7 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:68 errors:0 dropped:0 overruns:0 frame:0
          TX packets:68 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2856 (2.7 KiB)  TX bytes:2856 (2.7 KiB)
[root@localhost ~]# ifconfig eth0 hw ether 00:50:56:BF:26:20 //关闭网卡并修改MAC地址
[root@localhost ~]# ifconfig eth0 up //启动网卡
[root@localhost ~]# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:50:56:BF:26:20  
          inet addr:192.168.120.204  Bcast:192.168.120.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0
          TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:596390239 (568.7 MiB)  TX bytes:2886956 (2.7 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:68 errors:0 dropped:0 overruns:0 frame:0
          TX packets:68 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2856 (2.7 KiB)  TX bytes:2856 (2.7 KiB)
</code></pre>
<h6>1.4.5配置IP地址</h6>
<p><strong>输出</strong>:</p>
<pre><code>[root@localhost ~]# ifconfig eth0 192.168.120.56
[root@localhost ~]# ifconfig eth0 192.168.120.56 netmask 255.255.255.0
[root@localhost ~]# ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255
</code></pre>
<p><strong>说明</strong> :
 <br>
 ifconfig eth0 192.168.120.56
 <br>
 给eth0网卡配置IP地：192.168.120.56
 <br>
 ifconfig eth0 192.168.120.56 netmask 255.255.255.0
 <br>
 给eth0网卡配置IP地址：192.168.120.56 ，并加上子掩码：255.255.255.0
 <br>
 ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255
 <br>
 /给eth0网卡配置IP地址：192.168.120.56，加上子掩码：255.255.255.0，加上个广播地址： 192.168.120.255</p>
<h6>1.4.6启用和关闭ARP协议</h6>
<p><strong>命令</strong> ：
 <br>
 ifconfig eth0 arp
 <br>
 ifconfig eth0 -arp
 <br>
 <strong>输出</strong>：</p>
<pre><code>[root@localhost ~]# ifconfig eth0 arp
[root@localhost ~]# ifconfig eth0 -arp
</code></pre>
<p><strong>说明</strong>
 <br>
 ifconfig eth0 arp 开启网卡eth0 的arp协议；
 <br>
 ifconfig eth0 -arp 关闭网卡eth0 的arp协议；</p>
<h6>1.4.7 设置最大传输单元</h6>
<p><strong>命令</strong> ：
 <br>
 ifconfig eth0 mtu 1500
 <br>
 <strong>输出</strong>：</p>
<pre><code>[root@localhost ~]# ifconfig eth0 mtu 1480
[root@localhost ~]# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:50:56:BF:26:1F  
          inet addr:192.168.120.203  Bcast:192.168.120.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1480  Metric:1
          RX packets:8712395 errors:0 dropped:0 overruns:0 frame:0
          TX packets:36631 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:597062089 (569.4 MiB)  TX bytes:2643973 (2.5 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:9973 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9973 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:518096 (505.9 KiB)  TX bytes:518096 (505.9 KiB)

[root@localhost ~]# ifconfig eth0 mtu 1500
[root@localhost ~]# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:50:56:BF:26:1F  
          inet addr:192.168.120.203  Bcast:192.168.120.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8712548 errors:0 dropped:0 overruns:0 frame:0
          TX packets:36685 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:597072333 (569.4 MiB)  TX bytes:2650581 (2.5 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:9973 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9973 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:518096 (505.9 KiB)  TX bytes:518096 (505.9 KiB)

[root@localhost ~]#
</code></pre>
<p>说明:
 <br>
 设置能通过的最大数据包大小为1500bytes</p>
<h4>2.配置文件方式</h4>
<p>ubuntu配置文件:<code>/etc/network/interfaces</code></p>
<pre><code>auto lo
iface lo inet loopback
auto eth0 #配置静态ip
iface eth0 inet static
address 192.168.1.100
netmask 255.255.255.0
gateway 192.168.1.1
</code></pre>
<p>centos配置文件:<code>/etc/sysconfig/network-scripts/ifcfg-eth0</code></p>
<pre><code>DEVICE=eth0(默认)
HWADDR=00:0C:29:2E:36:16(默认)
TYPE=Ethernet(默认)
UUID=XXXXXXX(默认)
ONBOOT=yes(默认为no,修改为yes意为每次reboot后 ifup eth0)
MM_CONTROLLED=yes(默认)
#BOOTPROTO=dhcp(dhcp为自动分配ip地址,我们把他注释了，在下面另外加)
BOOTPROTO=static(新添加)
IPV6INIT=no(新添加)
USERCTL=no(新添加)
IPADDR=192.168.164.100(新添加)
NETMASK=255.255.255.0(新添加)
</code></pre>
<p>service network restart重启网卡服务</p>
<h4>3.图形界面方式</h4>
<h4>添加虚拟网卡</h4>
<p>一台服务器需要设置多个ip,但又不想添加多块网卡,那就需要设置虚拟网卡.这里介绍几种方式在linux服务器上添加虚拟网卡.
 <br>
 比如向eth0中添加一块虚拟网卡:</p>
<h5>1.快速创建删除虚拟网卡</h5>
<p><code>sudo ifconfig eth0: 192.168.10.10 up</code>
 <br>
 以上的命令就可以在eth0网卡上创建一个叫eth0:0的虚拟网卡,他的地址是:192.168.1.63
 <br>
 如果不想要这个虚拟网卡了,可以使用如下命令删除:</p>
<pre><code>sudo ifconfig eth0:0 down
</code></pre>
<p>重启服务器或者网络后,虚拟网卡就没有了.</p>
<h5>2.修改网卡配置文件</h5>
<p>在ubuntu下,网卡的配置文件是<code>/etc/network/interfaces</code>,所以我们修改它:
 <br>
 <code>sudo vim /etc/network/interfaces</code>
 <br>
 在这个文件中增加如下内容并保存:</p>
<pre><code>auto eth0:0
iface eth0:0 inet static
address 192.168.10.10
netmask 255.255.255.0
#network 192.168.10.1
#broadcast 192.168.1.255
</code></pre>
<p>保存后,我们需要重启网卡(重新加载配置文件)才会生效,使用如下命令重启:<code>sudo /etc/init.d/networking restart</code>
 <br>
 他的优点是重启服务器或者网卡配置不会丢失。</p>
<h5>3.创建tag</h5>
<p>前两种方法都有一个特点，创建的网卡可有不同的ip地址，但是Mac地址相同。无法用来创建虚拟机。
 <br>
 添加虚拟网卡tap</p>
<pre><code>tunctl -b
</code></pre>
<h3>其他配置命令:</h3>
<p>显示网桥信息:<code>brctl show</code>
 <br>
 添加网桥:<code>brctl addbr virbr0</code>
 <br>
 激活网桥:<code>ip link set virbr0 up</code>
 <br>
 添加虚拟网卡tap:<code>tunctl -b</code>
 <br>
 tap0 ——-&gt; 执行上面使命就会生成一个tap,后缀从0，1，2依次递增
 <br>
 激活创建的tap:<code>ip link set tap0 up</code>
 <br>
 将tap0虚拟网卡添加到指定网桥上:<code>brctl addif br0 tap0</code></p>
<p>给网桥配制ip地址:<code>ifconfig virbr1 169.254.251.4 up</code>
 <br>
 将virbr1网桥上绑定的网卡eth5解除:</p>
<pre><code>brctl delif virb1 eth5                                                  ```   
给virbr1网桥添加网卡eth6:`brctl addif virbr1 eth6  `   
</code></pre>
<p>原文链接: https://blog.csdn.net/sjw890821sjw/article/details/141387858</p>]]></description><guid isPermaLink="false">/archives/1c8851a8-eeeb-4879-b824-dcf7183b70b7</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Tue, 8 Oct 2024 03:28:53 GMT</pubDate></item><item><title><![CDATA[vue项目 部署到nginx 上刷新页面显示404]]></title><link>https://wiki.dgfygk.com/archives/84cc7630-75cb-4a0f-a07b-2b197163dde9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%E9%A1%B9%E7%9B%AE%20%E9%83%A8%E7%BD%B2%E5%88%B0nginx%20%E4%B8%8A%E5%88%B7%E6%96%B0%E9%A1%B5%E9%9D%A2%E6%98%BE%E7%A4%BA404&amp;url=/archives/84cc7630-75cb-4a0f-a07b-2b197163dde9" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue项目部署到Nginx服务器上时，遇到刷新页面显示404的问题，通常是因为Nginx无法正确地处理Vue路由（尤其是单页面应用（SPA）中的前端路由）。Vue应用的路由是前端路由，依赖于JavaScript来动态解析URL，当直接访问除根路径外的URL时，Nginx默认会尝试在服务器上找到对应的文件或目录，如果找不到就会返回404错误。</p>
<p>为了解决这个问题，你需要配置Nginx，使得所有请求都重定向到Vue应用的入口文件（通常是<code>index.html</code>），这样无论请求什么URL，Vue的路由都能正确处理。以下是一个基本的Nginx配置示例，用于解决这个问题：</p>
<pre><code class="bash">server {  
    listen       80;  
    server_name  yourdomain.com; # 替换为你的域名或IP  

    location / {  
        root   /usr/share/nginx/html; # Vue应用打包后的文件存放的目录  
        try_files $uri $uri/ /index.html; # 尝试找到请求的文件或目录，如果找不到，则返回index.html  
    }  

    # 其他配置...  

    error_page   500 502 503 504  /50x.html;  
    location = /50x.html {  
        root   /usr/share/nginx/html;  
    }  
}
</code></pre>
<p>这里的关键是<code>try_files</code>指令，它按照列出的顺序检查文件是否存在，如果文件或目录不存在，则最后会返回<code>/index.html</code>。由于Vue应用是SPA，所有路由都应该在前端处理，因此返回<code>index.html</code>是合适的。</p>
<p>确保你的Vue应用已经通过<code>npm run build</code>（或类似的命令）构建完成，并且所有的构建文件（包括<code>index.html</code>）都已经被放置在Nginx的<code>root</code>指令指定的目录中。</p>
<p>如果你正在使用Vue CLI 3或更高版本，并且使用<code>vue-cli-service build</code>来构建你的项目，它默认会生成一个<code>dist/</code>目录，你需要确保Nginx的<code>root</code>指令指向这个目录（或者将其内容复制到Nginx的默认目录）。</p>
<p>最后，不要忘记重启Nginx服务以使配置生效。你可以使用<code>sudo systemctl restart nginx</code>（对于使用systemd的系统）或其他相应的命令来重启Nginx。</p>
<p>原文链接: https://blog.csdn.net/qq_30517167/article/details/140692087</p>]]></description><guid isPermaLink="false">/archives/84cc7630-75cb-4a0f-a07b-2b197163dde9</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Tue, 8 Oct 2024 03:28:42 GMT</pubDate></item><item><title><![CDATA[vscode如何连接github_gitee远程仓库详细步骤（ssh+https）最全最详细]]></title><link>https://wiki.dgfygk.com/archives/2088c1ee-097f-445a-901f-cccf4b67e7f3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vscode%E5%A6%82%E4%BD%95%E8%BF%9E%E6%8E%A5github_gitee%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93%E8%AF%A6%E7%BB%86%E6%AD%A5%E9%AA%A4%EF%BC%88ssh%2Bhttps%EF%BC%89%E6%9C%80%E5%85%A8%E6%9C%80%E8%AF%A6%E7%BB%86&amp;url=/archives/2088c1ee-097f-445a-901f-cccf4b67e7f3" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_5">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#_9">一、准备工作</a>
  <br>
  <em> 
   <br>
  </em>
  <ul>
   <li><a href="https://wiki.dgfygk.com#1git_10">1.安装git</a></li>
   <li><a href="https://wiki.dgfygk.com#2git_16">2.配置git用户名和邮箱</a></li>
   <li><a href="https://wiki.dgfygk.com#3github_28">3.在github中创建一个新的仓库</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#httpsgithub_31">二、通过https连接github</a>
  <br>
  <em> 
   <br>
  </em>
  <ul>
   <li><a href="https://wiki.dgfygk.com#1vscode_32">1.在vscode上选择远程源</a></li>
   <li><a href="https://wiki.dgfygk.com#2_40">2.代码暂存提交和推送</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#sshgithub_51">三、通过ssh连接github</a>
  <br>
  <em> 
   <br>
  </em>
  <ul>
   <li><a href="https://wiki.dgfygk.com#1_52">1.生成公钥和私钥</a></li>
   <li><a href="https://wiki.dgfygk.com#2config_57">2.设置config文件</a></li>
   <li><a href="https://wiki.dgfygk.com#3ssh_68">3.配置ssh免密登录</a></li>
   <li><a href="https://wiki.dgfygk.com#4_74">4.远程仓库初始化</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_85">总结</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<h2>前言</h2>
<p>对于喜欢写代码，但是在本地电脑上存放项目太麻烦且容易误删的小伙伴们，远程仓库便对我们代码管理有着很好的帮助，例如github/gitee等远程仓库，不仅可以方便地管理代码，包括上传代码、下载代码、分支管理、合并代码等操作，还可以与他人协同开发提升效率，本文主要对vscode连接远程仓库的两种方式进行详细步骤说明，希望能够帮助有需要的小伙伴们</p>
<hr>
<h2>一、准备工作</h2>
<h4>1.安装git</h4>
<p>VSCode连接GitHub需要先安装Git。Git是一个版本控制系统，它允许你跟踪文件的变化并协同工作。VSCode使用Git来连接和管理GitHub上的代码仓库。首先进入Git的官网下载（<a href="https://git-scm.com/downloads">点击跳转官网下载地址</a>）<strong>不同系统和版本的电脑自行选择</strong> ，这里我是windows x64选择这个
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9714985e2f30be8ba12b474a206fdb1f.png%23pic_center&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F05ba15e6a0280dff3492357c01b5bd01.png%23pic_center&amp;size=m" alt="">
 <br>
 下载完成点击安装，<strong>这里一般都是默认选项，不需要更改</strong> （文件路径可以自行修改），安装完成后右击桌面任何一个文件夹，出现这两个选项就是安装成功了
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff7ec6929de0b1edaec342dea926623a0.png&amp;size=m" alt=""></p>
<h4>2.配置git用户名和邮箱</h4>
<p>选中右击一个文件夹，点击<strong>git bash here</strong> （相当于在这个文件夹目录下打开的命令终端）
 <br>
 设置全局<strong>用户名</strong> 和<strong>邮箱</strong>：</p>
<pre><code class="prism language-c">git config --global user.name "用户名"//设置全局的 Git 用户名
git config --global user.email "邮箱地址" //设置全局的 Git 用户邮箱。
</code></pre>
<p>用户名可以随便设置，邮箱输入自己的邮箱即可，配置完成后，输入以下命令查看是否设置成功</p>
<pre><code class="prism language-c">git config --list
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0a4fafde3e89124460709d8016e48a94.png%23pic_center&amp;size=m" alt=""></p>
<h4>3.在github中创建一个新的仓库</h4>
<p>进入自己的github主页，选择创建一个新仓库，写上仓库名字，如果希望别人访问到你的github仓库就选择public，如果不希望就选择private，点击创建
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77946ca1d06d26650e1c107bb897869a.png%23pic_center&amp;size=m" alt=""></p>
<h2>二、通过https连接github</h2>
<h4>1.在vscode上选择远程源</h4>
<p>在vscode中找到这个选项，我这里是已经配置过的，没配置过的点击<strong>new repository</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffbfd8ccc9f8ddaa04a2a50033bf580ef.png%23pic_center&amp;size=m" alt="">
 <br>
 点击之后会在右边主状态栏出现这个选项
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F60f9e2927fc72d2ec09134557e67e28b.png%23pic_center&amp;size=m" alt="">
 <br>
 点击之后会跳转到浏览器进行github账户登录验证，输入账号的密码就行，有的登录过github会自动进行账号身份识别，选择用户就可以了，选择完之后就可以看到我们创建的仓库了，点击需要添加的库，输入你的仓库名字，仓库就配置完成了
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F70b88c6f0310a6134e205cd4b2b21171.png%23pic_center&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd44777ffcc9033b0b10e83ff076ff3ba.png%23pic_center&amp;size=m" alt=""></p>
<h4>2.代码暂存提交和推送</h4>
<p>当我们编写，修改代码<strong>ctrl+s</strong> 保存之后，在源代码管理器就会有这样的提示<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8c31227d313a63d160af523b6f6238cd.png%23pic_center&amp;size=m" alt="">
 <br>
 点击+号，添加到暂存的更改中，在上方输入框输入本次代码提交的简述，例如commit，update等，点击提交
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1cfbfbac500f8fee4bfe49c6adc81bca.png%23pic_center&amp;size=m" alt="">
 <br>
 提交之后此时代码是在vscode的缓存区里，点击右上角三个点，点击推送，代码就成功被提交上了
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd8d09f540feb5802cb6e366fa6e1b183.png%23pic_center&amp;size=m" alt="">
 <br>
 进入github仓库查看提交是否成功，大功告成！！！
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff9442a1d7ec423eb9a5dc5acfa94c2d2.png%23pic_center&amp;size=m" alt=""></p>
<h2>三、通过ssh连接github</h2>
<h4>1.生成公钥和私钥</h4>
<p>首先选择一个文件夹，右击 git bash here，在命令行输入命令，按下三次回车生成一个**.ssh文件夹**，一般在<strong>用户的user</strong> 根目录下，文件夹中包括名为id_rsa的私钥文件和一个名为id_rsa.pub的公钥文件</p>
<pre><code class="prism language-c"> ssh-keygen -t rsa -C "邮箱地址"//按下三次回车，生成公钥和私钥两个文件
</code></pre>
<h4>2.设置config文件</h4>
<p>找到.ssh文件夹后，在里面<strong>新建一个名为config的文件</strong>，不需要后缀名，然后在里面写入（不要忘记保存）：</p>
<pre><code class="prism language-c">Host github.com
HostName ssh.github.com  
User git
Port 443
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe28d4daa94da47c10201c7da5f64929b.png%23pic_center&amp;size=m" alt=""></p>
<h4>3.配置ssh免密登录</h4>
<p>将id_rsa.pub的公钥文件的所有内容复制，进入github设置里找到配置ssh的选项
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbba98ed7a3e494bcc2df331a0e82628d.png%23pic_center&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fac22f9c5eaa732573648b10a30946590.png%23pic_center&amp;size=m" alt="">
 <br>
 将复制的公钥内容粘贴到key中，title里可以随意取个名字，点击add ssh key就配置成功了
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8f0873dd12d9c7f20f51facf0c59119b.png%23pic_center&amp;size=m" alt=""></p>
<h4>4.远程仓库初始化</h4>
<p>进入vscode工作区，选中需要添加到仓库里的文件夹，右击终端打开，在终端里输入以下命令：</p>
<pre><code class="prism language-c">git init//仓库初始化
git add README.md//添加readme.md文件
git commit -m "first commit//提交
git branch -M main//构建分支
git remote add origin git@github.com:zhouwenbin888/ceshi.git//连接到远程仓库
git push -u origin main//推送
</code></pre>
<p><strong>进行一次命令行提交代码之后</strong>，vscode会自动通过ssh连接到远程仓库，之后推送代码就可以使用vscode界面进行提交代码了（与https推送的方式一样，这里不多赘述）</p>
<h2>总结</h2>
<p>本文详细说明了vscode连接远程仓库的两种方式的步骤，小伙伴们可以根据自己的需要按照步骤一步一步去配置就好了，有什么问题欢迎评论区留言讨论，喜欢的小伙伴<strong>点点收藏点点关注</strong>，感谢大家</p>
<p>原文链接: https://blog.csdn.net/Bin_niB/article/details/135915738</p>]]></description><guid isPermaLink="false">/archives/2088c1ee-097f-445a-901f-cccf4b67e7f3</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Tue, 8 Oct 2024 03:28:21 GMT</pubDate></item><item><title><![CDATA[超级完整的 Git的下载、安装、配置与使用 以及命令]]></title><link>https://wiki.dgfygk.com/archives/4c119694-626b-4efb-b90a-556288078b8e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%B6%85%E7%BA%A7%E5%AE%8C%E6%95%B4%E7%9A%84%20Git%E7%9A%84%E4%B8%8B%E8%BD%BD%E3%80%81%E5%AE%89%E8%A3%85%E3%80%81%E9%85%8D%E7%BD%AE%E4%B8%8E%E4%BD%BF%E7%94%A8%20%E4%BB%A5%E5%8F%8A%E5%91%BD%E4%BB%A4&amp;url=/archives/4c119694-626b-4efb-b90a-556288078b8e" width="1" height="1" alt="" style="opacity:0;">
<h2>Git的下载、安装、配置与使用 以及命令</h2>
<h3>一、Git下载安装</h3>
<h4>1、访问git官方下载网址，<a href="https://git-scm.com/downloads">点击这里</a>，然后根据自己的电脑系统，下载对应的安装包：</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3c861cb6c6f6ea1b096c966445f21a7c.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdd2b14cbf4f9ff6f486b53371de69aba.png&amp;size=m" alt=""></p>
<h4>2、在<a href="https://registry.npmmirror.com/binary.html?path=git-for-windows/">淘宝镜像网站</a> 下载对应的安装包：</h4>
<blockquote>
 <p><code>注： 如果由于官网下载速度过于缓慢，而且我还下载报错： 无法下载，需要授权。</code></p>
</blockquote>
<p><strong>可在git下载的<a href="https://registry.npmmirror.com/binary.html?path=git-for-windows/">淘宝镜像网站</a> 下载</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F11e429272a808bc51dde1502d788156d.png&amp;size=m" alt=""></p>
<h3>二、Git安装步骤</h3>
<h4>1、双击Git-2.35.1-64-bit.exe，</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6041dc0749cda2b861ecf17bc256d3b4.png&amp;size=m" alt=""></p>
<h4>2、下载完之后双击安装，然后点击next</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6076ea3f8588a4bca997e083f93b9a9f.png&amp;size=m" alt=""></p>
<h4>3、选择安装的目录</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fba35e376228040cd060d1eeb714977f3.png&amp;size=m" alt=""></p>
<h4>4、选择组件，默认下一步</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2e50b619ef7afa71182c19632ca31dce.png&amp;size=m" alt=""></p>
<h4>4、创建开始菜单目录，默认下一步</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F790fe895658c63a9cb7a7c0c23bdb868.png&amp;size=m" alt=""></p>
<h4>5、选择Git使用的默认编辑器</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7628c356f2a2c6950257498f422704fe.png&amp;size=m" alt=""></p>
<h4>6、配置PATH环境</h4>
<blockquote>
 <p>可以选择第一项，也可以选择第二项。根据英文解释，第一项是最安全的选择，因为PATH根本不会被修改，你可以使用Git
  <br>
  Bash的Git命令行工具；第二项也被认为是安全的，它只向PATH添加一些最小的Git包以避免使用可选的Unix工具混淆环境。你将能够从Git
  <br>
  Bash和Windows命令提示符中使用Git。这里我选择第一项。</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8cd9399c2e42bf0dc85535b5e26a4dc1.png&amp;size=m" alt=""></p>
<h4>6、选择HTTPS传输后端，默认下一步</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa58a075f7347844567df2327280a1158.png&amp;size=m" alt=""></p>
<h4>7、配置行结束转换，默认下一步</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7e5af89bb4ba479985a2f2b126fe5793.png&amp;size=m" alt=""></p>
<h4>8、配置终端模拟器以与Git Bash一起使用，默认下一步</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faa42ad5e76d9f4775c2c1b4b461171d8.png&amp;size=m" alt=""></p>
<h4>9、配置额外选项，默认设置，点击install</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdaae7d893dca455e0426f968b4fe36d3.png&amp;size=m" alt=""></p>
<h4>10、正在安装</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fed2034d7fb30eb60f3944bcc6904dbbb.png&amp;size=m" alt=""></p>
<h4>11、安装完成</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6efc602ee048a851cc7e74f96d9bded5.png&amp;size=m" alt=""></p>
<h4>12、检验是否安装成功</h4>
<p>在桌面鼠标点击右键，可以看到Git GUI Here和Git Bash Here就说明安装成功啦！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5638888b1dabcee5862fcbba07969b27.png&amp;size=m" alt=""></p>
<h3>三、Git的环境配置</h3>
<h4>1、点击上图的Git bash Here 进行账号配置</h4>
<blockquote>
 <p><strong>GitHub、Gitee、Gitlabe 等 上注册一个账号，注册好后，桌面右键选择Git Bash Here，进行账号配置，命令如下：</strong></p>
</blockquote>
<pre><code class="prism language-bash"># 配置用户名("username"是自己的用户名)
git config --global user.name "username"
# 配置邮箱("username@email.com"是注册账号时所用的邮箱)
git config --global user.email "username@email.com"
# 配置完成后检查配置：
git config -l
# 或执行完以上命令后，查看是否配置成功。
git config --global --list
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbe7eea78ee107482d47aa6fc6ac18069.png&amp;size=m" alt=""></p>
<p><strong>此时会在C:\Users\Administrator目录下生成.gitconfig配置文件(此文件不能删除)；</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6411f97dd74f65723aef1286220a0181.png&amp;size=m" alt="">
 <br>
 <strong>查看.gitconfig配置文件里的内容 ，<code>就和 'git config --global --list' 命令查询到的内容一样</code>；</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcf8365e35694967accbde48549f5b692.png&amp;size=m" alt=""></p>
<h4>2、配置 ssh keys</h4>
<blockquote>
 <p><strong>为什么Github、Gitee或GitLab需要SSH Key呢？</strong>
  <br>
  <code>因为GitLab需要识别出你推送的提交确实是你推送的，而不是别人冒充的，而Git支持SSH协议，所以，GitLab只要知道了你的公钥，就可以确认只有你自己才能推送。</code>
  <br>
  当然，Github、Gitee或GitLab允许你添加多个Key。假定你有若干电脑，你一会儿在公司提交，一会儿在家里提交，只要把每台电脑的Key都添加到GitLab，就可以在每台电脑上往GitLab推送了。</p>
</blockquote>
<p><strong>1、继续，执行: ssh-keygen -t rsa ，(<code>注意ssh-keygen无空格</code>)，生成SSH(你的电脑与Gitee通信的安全连接)</strong></p>
<pre><code class="prism language-bash"># 生成ssh,命令执行后，连敲3次回车键
ssh-keygen -t rsa
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5e433e549ccf6a2ab8d0950c61953b34.png&amp;size=m" alt=""></p>
<p><code>"ssh-keygen带空格" 会出现如下问题：</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb968c837ab7606e9a5067cd3384b4e1d.png&amp;size=m" alt=""></p>
<p><strong>2、执行完后到系统盘users目录(win: C:\Users\你的用户名.ssh\），查看生成的ssh文件</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff0ad3f6287720e0980f01923c5ecce89.png&amp;size=m" alt=""></p>
<p><strong>在Git窗口中输入如下命令，可以查看公钥和私钥</strong></p>
<pre><code class="prism language-bash"># 查看公钥
 cat ~/.ssh/id_rsa.pub
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc0b245f253f71ceb727974d9e14cccc2.png&amp;size=m" alt=""></p>
<p><strong>3、将公钥（ id_rsa.pub），添加到Github、Gitee或GitLab平台中，这里以 GitLab、Github 为例：</strong></p>
<p><strong>GitLab平台：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb425a4fd0db109ce1da4295c8c9aae1d.png&amp;size=m" alt=""></p>
<p><strong>Github平台：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa75d8e5a2b8341c847c80478203ce0b3.png&amp;size=m" alt=""></p>
<p><strong>4、登陆Github、Gitee或GitLab，打开"settings”，“SSH Keys"页面：</strong></p>
<blockquote>
 <p>然后，点"Add SSH Key”，填上任意Title，在Key文本框里粘贴id_rsa.pub文件的内容：</p>
</blockquote>
<p><strong>GitLab平台：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4f126f99c9fbe4871423c86d97672a77.png&amp;size=m" alt=""></p>
<p><strong>Github平台：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F366a02b89e57efd61e683d5415a180ab.png&amp;size=m" alt=""></p>
<blockquote>
 <p>点"Add Key”，你就应该看到已经添加的Key：</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8062f70875c451bf27603837c10568e8.png&amp;size=m" alt=""></p>
<p><strong>4、测试配置是否成功</strong></p>
<p>在Git Bash中输入</p>
<pre><code class="prism language-bash">ssh -T git@github.com
</code></pre>
<p><strong>SSH警告</strong></p>
<blockquote>
 <p>当你第一次使用Git的命令连接GitHub时，会得到一个警告：</p>
</blockquote>
<pre><code class="prism language-bash">The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?
</code></pre>
<blockquote>
 <p>这是因为Git使用SSH连接，而SSH连接在第一次验证GitHub服务器的Key时，需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器，输入yes回车即可。</p>
</blockquote>
<p>Git会输出一个警告，告诉你已经把GitHub的Key添加到本机的一个信任列表里了：</p>
<pre><code class="prism language-bash">Warning: Permanently added 'github.com' (RSA) to the list of known hosts.
</code></pre>
<h3>四、Git的使用</h3>
<h4>1、Git 使用 ssh链接 下载源码</h4>
<pre><code class="prism language-bash"># 下载GitHub上的源码
git clone 需要下载的ssh链接  
</code></pre>
<p><strong>这里使用GitHub 为例（Gitee或GitLab方式也差不多）</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8e35270f09dbb7f2e90e4928a4c220cf.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9971821da87d4dfca687484a6954f723.png&amp;size=m" alt=""></p>
<h4>2、使用Git 上传本地文件代码</h4>
<h5><strong>1）、创建本地Git项目</strong></h5>
<p><strong>1.1、在本地新建一个文件夹，用来存放需要上传的文件。右击进行入到git Bash界面</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F713b7bb0ea0ba35d3f1f167c060e2f1e.png&amp;size=m" alt="">
 <br>
 <strong>1.2、把这个文件夹变成Git可管理的仓库</strong></p>
<pre><code class="prism language-bash"># 把这个文件夹变成Git可管理的仓库
git init
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa35ccddd44d2fbcb5194b95900e37503.png&amp;size=m" alt=""></p>
<p><strong>1.3、此时文件夹内会多一个.git文件夹</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffbe0343ba2bffa3edb1220ad4d3632ff.png&amp;size=m" alt=""></p>
<h5>2)、创建GitHub 仓库项目**</h5>
<p><strong>2.1、在GitHub上新建一个项目</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1482eb13a1a16086f9950ab4a85275ba.png&amp;size=m" alt=""></p>
<p><strong>2.2、增加对我们在github上创建的仓库的管理</strong></p>
<p><code>下面两种连接方法选择其中一个就行了</code></p>
<pre><code class="prism language-bash">git remote add origin git@github.com:mzc421/Android-python-socket.git

git remote add origin https://github.com/mzc421/Android-python-socket.git
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffb5b9296ba63ff2dd929b077f0b42ef8.png&amp;size=m" alt=""></p>
<blockquote>
 <p><code>下面的步骤是包括后面提交新的代码时都在重复操作的</code></p>
</blockquote>
<h4>3、使用Git 查看当前文件夹的状态</h4>
<pre><code class="prism language-bash">#查看当前文件夹的状态
git status
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F98e3ca1da09e6fb41097634446a815d2.png&amp;size=m" alt=""></p>
<h4>4、将项目全部添加到仓库上（增加命令）</h4>
<pre><code class="prism language-bash">#将项目全部添加到仓库上，添加完后可以这次使用git status 确定是否添加成功
git add .
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2b3f6a32eed092ec2022b580c65e87db.png&amp;size=m" alt=""></p>
<p><code>添加完后可以这次使用git status 确定是否添加成功</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fce350fb09bd0813269313a0c0d3cf2d7.png&amp;size=m" alt=""></p>
<h4>5、将项目提交到仓库中（提交命令）</h4>
<p>-m 后面跟提示信息，这个提示信息一定要写的，方便记录我们提交的说明。</p>
<pre><code class="prism language-bash"># 将项目提交到仓库中
git commit -m "写下你这次提交文件的提交说明"
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbd8ec3cc1f638366024daae9942c1243.png&amp;size=m" alt=""></p>
<h4>6、将文件上传到GitHub仓库中**</h4>
<p><code>初次使用会让你进行登录git，文件会上传到仓库中master分支中了。</code></p>
<pre><code class="prism language-bash">git push origin master 
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F06d91e483aa002e75666bea8d591ec82.png&amp;size=m" alt=""></p>
<h3>五、<a href="https://blog.csdn.net/GoodburghCottage/article/details/128833300?csdn_share_tail=%7B%22type%22:%22blog%22,%22rType%22:%22article%22,%22rId%22:%22128833300%22,%22source%22:%22GoodburghCottage%22%7D">在使用IDEA进行Git 操作无法Pull 的问题</a></h3>
<h3>六、Git命令</h3>
<h4><strong>1、查看、添加、提交、删除、找回，重置修改文件#</strong></h4>
<pre><code class="prism language-bash">git help &lt;command&gt; # 显示command的help  

git show # 显示某次提交的内容 git show $id  

git co -- &lt;file&gt; # 抛弃工作区修改  

git co . # 抛弃工作区修改  

git add &lt;file&gt; # 将工作文件修改提交到本地暂存区  

git add . # 将所有修改过的工作文件提交暂存区  

git rm &lt;file&gt; # 从版本库中删除文件  

git rm &lt;file&gt; --cached # 从版本库中删除文件，但不删除文件  

git reset &lt;file&gt; # 从暂存区恢复到工作文件  

git reset -- . # 从暂存区恢复到工作文件  

git reset --hard # 恢复最近一次提交过的状态，即放弃上次提交后的所有本次修改  

git ci &lt;file&gt; git ci . git ci -a # 将git add, git rm和git ci等操作都合并在一起做　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　git ci -am "some comments"  

git ci --amend # 修改最后一次提交记录  

git revert &lt;$id&gt; # 恢复某次提交的状态，恢复动作本身也创建次提交对象  

git revert HEAD # 恢复最后一次提交的状态  
</code></pre>
<h4><strong>2、查看文件diff</strong></h4>
<pre><code class="prism language-bash">git help &lt;command&gt; # 显示command的help  

git show # 显示某次提交的内容 git show $id  

git co -- &lt;file&gt; # 抛弃工作区修改  

git co . # 抛弃工作区修改  

git add &lt;file&gt; # 将工作文件修改提交到本地暂存区  

git add . # 将所有修改过的工作文件提交暂存区  

git rm &lt;file&gt; # 从版本库中删除文件  

git rm &lt;file&gt; --cached # 从版本库中删除文件，但不删除文件  

git reset &lt;file&gt; # 从暂存区恢复到工作文件  

git reset -- . # 从暂存区恢复到工作文件  

git reset --hard # 恢复最近一次提交过的状态，即放弃上次提交后的所有本次修改  

git ci &lt;file&gt; git ci . git ci -a # 将git add, git rm和git ci等操作都合并在一起做　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　git ci -am "some comments"  

git ci --amend # 修改最后一次提交记录  

git revert &lt;$id&gt; # 恢复某次提交的状态，恢复动作本身也创建次提交对象  

git revert HEAD # 恢复最后一次提交的状态  
</code></pre>
<h4><strong>3、查看提交记录</strong></h4>
<pre><code class="prism language-bash">git log git log &lt;file&gt; # 查看该文件每次提交记录  

git log -p &lt;file&gt; # 查看每次详细修改内容的diff  

git log -p -2 # 查看最近两次详细修改内容的diff  

git log --stat #查看提交统计信息  

tig

Mac上可以使用tig代替diff和log，brew install tig
</code></pre>
<h4><strong>4、Git 本地分支管理，查看、切换、创建和删除分支</strong></h4>
<pre><code class="prism language-bash">git br -r # 查看远程分支  

git br &lt;new_branch&gt; # 创建新的分支  

git br -v # 查看各个分支最后提交信息  

git br --merged # 查看已经被合并到当前分支的分支  

git br --no-merged # 查看尚未被合并到当前分支的分支  

git co &lt;branch&gt; # 切换到某个分支  

git co -b &lt;new_branch&gt; # 创建新的分支，并且切换过去  

git co -b &lt;new_branch&gt; &lt;branch&gt; # 基于branch创建新的new_branch  

git co $id # 把某次历史提交记录checkout出来，但无分支信息，切换到其他分支会自动删除  

git co $id -b &lt;new_branch&gt; # 把某次历史提交记录checkout出来，创建成一个分支  

git br -d &lt;branch&gt; # 删除某个分支  

git br -D &lt;branch&gt; # 强制删除某个分支 (未被合并的分支被删除的时候需要强制)  
分支合并和reba

git merge &lt;branch&gt; # 将branch分支合并到当前分支  

git merge origin/master --no-ff # 不要Fast-Foward合并，这样可以生成merge提交  

git rebase master &lt;branch&gt; # 将master rebase到branch，相当于： git co &lt;branch&gt; &amp;&amp; git rebase master &amp;&amp; git co master &amp;&amp; git merge &lt;branch&gt;  
Git补丁管理(方便在多台机器上开发同步时用)

git merge &lt;branch&gt; # 将branch分支合并到当前分支  

git merge origin/master --no-ff # 不要Fast-Foward合并，这样可以生成merge提交  

git rebase master &lt;branch&gt; # 将master rebase到branch，相当于： git co &lt;branch&gt; &amp;&amp; git rebase master &amp;&amp; git co master &amp;&amp; git merge &lt;branch&gt;  
</code></pre>
<h4><strong>5、Git暂存管</strong></h4>
<pre><code class="prism language-bash">git stash # 暂存  

git stash list # 列所有stash  

git stash apply # 恢复暂存的内容  

git stash drop # 删除暂存区  
Git远程分支管理

git pull # 抓取远程仓库所有分支更新并合并到本地  

git pull --no-ff # 抓取远程仓库所有分支更新并合并到本地，不要快进合并  

git fetch origin # 抓取远程仓库更新  

git merge origin/master # 将远程主分支合并到本地当前分支  

git co --track origin/branch # 跟踪某个远程分支创建相应的本地分支  

git co -b &lt;local_branch&gt; origin/&lt;remote_branch&gt; # 基于远程分支创建本地分支，功能同上  
git push # push所有分支

git push origin master # 将本地主分支推到远程主分支  

git push -u origin master # 将本地主分支推到远程(如无远程主分支则创建，用于初始化远程仓库)  

git push origin &lt;local_branch&gt; # 创建远程分支， origin是远程仓库名  

git push origin &lt;local_branch&gt;:&lt;remote_branch&gt; # 创建远程分支  

git push origin :&lt;remote_branch&gt; #先删除本地分支(git br -d &lt;branch&gt;)，然后再push删除远程分支  
</code></pre>
<h4>6、Git远程仓库管</h4>
<pre><code class="prism language-bash">git remote -v # 查看远程服务器地址和仓库名称  

git remote show origin # 查看远程服务器仓库状态  

git remote add origin git@ github:robbin/robbin_site.git # 添加远程仓库地址  

git remote set-url origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址(用于修改远程仓库地址) git remote rm &lt;repository&gt; # 删除远程仓库 
</code></pre>
<h4><strong>7、创建远程仓库</strong></h4>
<pre><code class="prism language-bash">git clone --bare robbin_site robbin_site.git # 用带版本的项目创建纯版本仓库  

scp -r my_project.git git@ git.csdn.net:~ # 将纯仓库上传到服务器上  

mkdir robbin_site.git &amp;&amp; cd robbin_site.git &amp;&amp; git --bare init # 在服务器创建纯仓库  

git remote add origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址  

git push -u origin master # 客户端首次提交  

git push -u origin develop # 首次将本地develop分支提交到远程develop分支，并且track  

git remote set-head origin master # 设置远程仓库的HEAD指向master分支
</code></pre>
<p>原文链接: https://blog.csdn.net/GoodburghCottage/article/details/128749666</p>]]></description><guid isPermaLink="false">/archives/4c119694-626b-4efb-b90a-556288078b8e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Tue, 8 Oct 2024 03:27:51 GMT</pubDate></item><item><title><![CDATA[【Linux】进程间通信——System V共享内存]]></title><link>https://wiki.dgfygk.com/archives/673db7cf-15f1-44c6-9167-2e4177ffdaab</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90Linux%E3%80%91%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1%E2%80%94%E2%80%94System%20V%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98&amp;url=/archives/673db7cf-15f1-44c6-9167-2e4177ffdaab" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E6%A6%82%E5%BF%B5%E5%92%8C%E5%8E%9F%E7%90%86">一、概念和原理</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81API%E4%BB%8B%E7%BB%8D%E4%B8%8E%E4%BD%BF%E7%94%A8">二、API介绍与使用</a></p>
<p><a href="https://wiki.dgfygk.com#2.1%20shmget">2.1 shmget</a></p>
<p><a href="https://wiki.dgfygk.com#2.2%20ftok">2.2 ftok</a></p>
<p><a href="https://wiki.dgfygk.com#2.3%20shmat">2.3 shmat</a></p>
<p><a href="https://wiki.dgfygk.com#2.4%20shmdt">2.4 shmdt</a></p>
<p><a href="https://wiki.dgfygk.com#2.5%20shmctl">2.5 shmctl</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E5%BC%80%E5%A7%8B%E9%80%9A%E4%BF%A1">三、开始通信</a></p>
<hr>
<p>
 <br>
</p>
<h2>一、概念和原理</h2>
<p>之前提到，进程间通信的本质就是<strong>让两个不同的进程能够看到同一份资源</strong>。因为进程具有独立性，不同进程间无法直接的进行数据的传递，需要操作系统提供对应的资源。</p>
<p>前面学习的管道，就是操作系统提供的资源，两个进程打开同一个管道，就能够进行通信。</p>
<p>除了管道，<strong>共享内存</strong> 也是进程间通信的一种方式，并且是<strong>最快的方式</strong>。其原理是<strong>将一段物理内存映射到两个进程的地址空间</strong>，这样两个进程就能够共享这段内存，并通过共享内存来进行数据传递，并且不再涉及内核。</p>
<p>进程间数据的传递不再涉及内核，因此进程在传递数据时就无需再执行对应的系统调用进入内核了，直接通过操作共享内存来进行通信，效率大大提高。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7ad529b00a594fa593e0673d66a58805.png&amp;size=m" alt=""></p>
<p>（操作系统首先申请一块共享内存，然后通过两个进程的页表将共享内存挂接到进程地址空间上）</p>
<p>共享内存可用于进程间通信，那么物理内存中肯定存在不止一块共享内存。既然共享内存由操作系统提供，那么我们可以猜想：操作系统一定要对这些共享内存进行管理。如何管理？<strong>先描述再组织</strong></p>
<p>因此内核中也有用于描述共享内存的结构体，操作系统通过对结构体的组织进行共享内存的管理</p>
<p>System V进程间通信方式除了System V共享内存，还有<strong>System V消息队列</strong> 和<strong>System V 信号量</strong>，这三种IPC方式的API和实现上都非常相似，包括内核中相似的结构体，和命名、使用方式相似的函数</p>
<p>System V消息队列和System V 信号量在后面的文章中会讲到，这里先贴出System V共享内存的内核结构体，后续大家可以将其与另外两种方式的内核结构体进行对比</p>
<pre><code class="cpp">struct shmid_ds {
    struct ipc_perm shm_perm; /* operation perms */
    int shm_segsz; /* size of segment (bytes) */
    __kernel_time_t shm_atime; /* last attach time */
    __kernel_time_t shm_dtime; /* last detach time */
    __kernel_time_t shm_ctime; /* last change time */
    __kernel_ipc_pid_t shm_cpid; /* pid of creator */
    __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
    unsigned short shm_nattch; /* no. of current attaches */
    unsigned short shm_unused; /* compatibility */
    void* shm_unused2; /* ditto - used by DIPC */
    void* shm_unused3; /* unused */
};
</code></pre>
<p>其中ipc_perm结构体：</p>
<pre><code class="cpp">struct ipc_perm {
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and
                                       SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};
</code></pre>
<p>
 <br>
</p>
<hr>
<h2>二、API介绍与使用</h2>
<h3>2.1 shmget</h3>
<pre><code class="cpp">#include &lt;sys/ipc.h&gt;
#include &lt;sys/shm.h&gt;

int shmget(key_t key, size_t size, int shmflg);
</code></pre>
<p>shmget函数用于创建共享内存，若创建成功会返回<strong>shmid</strong>，即<strong>共享内存标识符</strong>； 创建失败返回-1</p>
<p>其中：</p>
<ul>
 <li>key：类型为key_t，内核中标识共享内存的唯一标识符，需要用户生成</li>
 <li>size：共享内存的大小</li>
 <li>shmflg：权限位。其中IPC_CREAT表示如果申请的共享内存不存在就创建，存在就获取并返回；IPC_CREAT\|IPC_EXCL表示如果申请的共享内存不存在就创建，存在就出错并返回</li>
</ul>
<p>key是一个<strong>整型变量</strong> ，在内核中具有唯一性，两个进程<strong>通过key</strong>来访问同一块共享内存。key用于在操作系统中标识一块共享内存的唯一性，而shmid用于用户层的行为，只在进程内标识该共享内存唯一性</p>
<p>用户要创建key，可以使用ftok函数</p>
<p>
 <br>
</p>
<h3>2.2 ftok</h3>
<pre><code class="cpp">#include &lt;sys/types.h&gt;
#include &lt;sys/ipc.h&gt;

key_t ftok(const char *pathname, int proj_id);
</code></pre>
<p>ftok函数用于创建共享内存的key，创建成功返回key，失败返回-1</p>
<p>其中pathname和proj_id由用户自由指定，函数内通过特定的算法对参数进行计算并生成对应的具有唯一性的key。而因为参数都由用户指定，所以用户可以<strong>通过约定对应的pathname和proj_id，实现在两个进程中获取到相同的key</strong>，从而访问到同一块共享内存。</p>
<p>接下来我们实践一下</p>
<p>shared.hpp：</p>
<pre><code class="cpp">//shared.hpp
#include &lt;iostream&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/shm.h&gt;
#include &lt;sys/ipc.h&gt;
#include &lt;string&gt;

using namespace std;

//用户自己约定
const string path = "/home/Eristic";
const int proj_id = 0x1234;

key_t GetKey()
{
    key_t key = ftok(path.c_str(), proj_id); //创建key
    if(key &lt; 0)
    {
        perror("ftok error");
        exit(1);
    }
    printf("create key success, key = %d\n", key); //打印key的值
    return key;
}

int GetSharedMem()
{
    key_t key = GetKey(); //获取key
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL); //创建共享内存
    if(shmid &lt; 0)
    {
        perror("shmget error");
        exit(1);
    }
    printf("create shared memory success, shmid = %d\n", shmid); //打印shmid的值
    return shmid;
}
</code></pre>
<p>processa.cc：</p>
<pre><code class="cpp">//processa.cc
#include "shared.hpp"

int main()
{
    int shmid = GetSharedMem();
    return 0;
}
</code></pre>
<p>编译并运行程序，结果如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F99177faf6ca8449cab7b71e2c6999fd0.png&amp;size=m" alt=""></p>
<p>可以看到我们的key和共享内存已经创建成功了，并且key和shmid是不同的值</p>
<p>再次运行程序，结果变为了：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9946777db7114e94a9707b6b2562012d.png&amp;size=m" alt=""></p>
<p>此时key创建成功，但共享内存创建失败。这是因为我们创建共享内存时权限位设置为IPC_CREAT\|IPC_EXCL，如果共享内存存在就出错并返回，说明进程退出后共享内存仍然存在</p>
<p>实际上，<strong>共享内存的生命周期是随内核的</strong>，也就是除非内核重启，共享内存会一直存在直到用户主动关闭</p>
<p>要查看系统中所有的共享内存，我们可以输入 <strong><em>ipcs -m</em></strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa79e7871da2e4cc0b16ff48b3386aeb6.png&amp;size=m" alt=""></p>
<p>要删除共享内存，我们可以输入<strong><em>ipcrm -m + shmid</em></strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcf3f44e66eed40fbb124d49e2ade1094.png&amp;size=m" alt=""></p>
<p>其中perms是共享内存的权限位，nattch是与该共享内存关联的进程数，size是共享内存大小</p>
<p>关于共享内存的大小，建议设置为4096的整数倍，因为操作系统分配内存是按照4KB分配的，即使用户将共享内存的大小设置为4097，实际大小是4096*2（虽然查看共享内存时还是显示4097）</p>
<p>要设置共享内存的权限，我们可以在shmget函数的shmflg参数中加上，例如：</p>
<pre><code class="cpp">int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
</code></pre>
<p>删除原来的共享内存，重新编译并运行程序后，再次查看系统中的共享内存：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe604bd816f2c4c7ebe07c2c22bfcb4ec.png&amp;size=m" alt=""></p>
<p>可以看到随着我们对shmget函数的修改，perms变为了666</p>
<p>并且会发现每次创建共享内存，key是保持不变的，但shmid会改变</p>
<p>
 <br>
</p>
<h3>2.3 shmat</h3>
<pre><code class="cpp">#include &lt;sys/types.h&gt;
#include &lt;sys/shm.h&gt;

void *shmat(int shmid, const void *shmaddr, int shmflg);
</code></pre>
<p>shmat函数用于将对应的共享内存连接到进程的地址空间，成功会返回一个地址，是共享内存映射的起始地址，失败则返回-1</p>
<p>其中：</p>
<ul>
 <li>shmaddr：可指定共享内存映射的位置，如果设置为nullptr则由系统自动决定</li>
 <li>shmflg：设置为SHM_REMAP，如果共享内存待映射的地址已经有了映射，则重新映射，否则报错；设置为SHM_RDONLY代表以只读方式与共享内存建立关联；若设置为0则按照共享内存的默认权限</li>
</ul>
<p>同样是得到一块内存空间，同样是返回值为void*，让你想到了什么？</p>
<p>是不是很像malloc，申请一段空间，具体的用途由用户决定，并按照用途来进行类型转换</p>
<p>实践一下：</p>
<pre><code class="cpp">//processa.cc
#include "shared.hpp"

int main()
{
    int shmid = GetSharedMem();
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    sleep(10); //便于在程序退出前观测现象
    return 0;
}
</code></pre>
<p>编译并运行代码，在程序未退出时观测共享内存的nattch：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F382e46cc9e1d44b88cbc825f2ac548eb.png&amp;size=m" alt=""></p>
<p>可以看到此时进程已经与共享内存关联</p>
<p>
 <br>
</p>
<h3>2.4 shmdt</h3>
<pre><code class="cpp">#include &lt;sys/types.h&gt;
#include &lt;sys/shm.h&gt;

int shmdt(const void *shmaddr);
</code></pre>
<p>shmdt函数用于去除进程与共享内存的关联，成功返回0，失败返回-1</p>
<p>我们只需要将关联共享内存时返回的地址传入shmdt函数即可</p>
<p>我们继续在程序中加入shmdt函数，并观察现象：</p>
<pre><code class="cpp">//processa.cc
#include "shared.hpp"

int main()
{
    int shmid = GetSharedMem();
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    printf("shmat done\n");
    sleep(5);
    shmdt(shmaddr);
    printf("shmdt done\n");
    sleep(5);
    return 0;
}
</code></pre>
<p>可以看到shmat函数执行后，共享内存的nattch变为1，shmdt去关联后又变为0</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc6af27e52924422aaf3b6f86bb0b4ba6.gif&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3>2.5 shmctl</h3>
<pre><code class="cpp">#include &lt;sys/ipc.h&gt;
#include &lt;sys/shm.h&gt;

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
</code></pre>
<p>shmctl函数用于控制共享内存，在大部分cmd操作下成功返回0，失败返回-1</p>
<p>其中：</p>
<ul>
 <li>cmd：需要做的操作。IPC_STAT为将共享内存的结构体信息拷贝出来；IPC_SET可修改共享内存的uid、gid及mode；IPC_RMID将共享内存的引用计数减少，若引用计数为0则共享内存被删除</li>
 <li>buf：类型为shmid_ds，即共享内存的内核结构体。IPC_RMID操作中可设置为nullptr</li>
</ul>
<p>我们继续加入shmctl函数，在去关联后直接删除共享内存，看看现象如何：</p>
<pre><code class="cpp">//processa.cc
#include "shared.hpp"

int main()
{
    int shmid = GetSharedMem();
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    printf("shmat done\n");
    sleep(5);
    shmdt(shmaddr);
    printf("shmdt done\n");
    sleep(5);
    shmctl(shmid, IPC_RMID, nullptr);
    printf("shmctl done\n");
    sleep(5);
    return 0;
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0eca6728d0134f66acdbb51f18e9d7ce.gif&amp;size=m" alt=""></p>
<p>可以看到，由于我们选择了IPC_RMID操作，shmctl函数执行完毕后共享内存被删除</p>
<p>
 <br>
</p>
<hr>
<h2>三、开始通信</h2>
<p>到目前位置，我们已经初步了解如何创建并关联共享内存了，接下来我们来简单实现两个进程之间的通信</p>
<p>processa.cc：</p>
<pre><code class="cpp">#include "shared.hpp"

int main()
{
    int shmid = CreateSharedMem();
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);

    while(true)
    {
        cout &lt;&lt; "process b say:" &lt;&lt; shmaddr &lt;&lt; endl;
        sleep(1);
    }

    shmdt(shmaddr);
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}
</code></pre>
<p>processb.cc：</p>
<pre><code class="cpp">#include "shared.hpp"

int main()
{
    int shmid = GetSharedMem();
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);

    while(true)
    {
        cout &lt;&lt; "Please enter# ";
        fgets(shmaddr, 4096, stdin);
    }

    shmdt(shmaddr);
    return 0;
}
</code></pre>
<p>因为创建和删除共享内存的工作在processa.cc中已经完成了，所以processb.cc中只需要获取共享内存即可，无需再进行创建和删除</p>
<p>shared.hpp</p>
<pre><code class="cpp">#include &lt;iostream&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/shm.h&gt;
#include &lt;sys/ipc.h&gt;
#include &lt;string&gt;
#include &lt;unistd.h&gt;

using namespace std;

//用户自己约定
const string path = "/home/Eristic";
const int proj_id = 0x1234;

key_t GetKey()
{
    key_t key = ftok(path.c_str(), proj_id); //创建key
    if(key &lt; 0)
    {
        perror("ftok error");
        exit(1);
    }
    printf("create key success, key = %d\n", key); //打印key的值
    return key;
}

int SharedMem(int flag)
{
    key_t key = GetKey(); //获取key
    int shmid = shmget(key, 4096, flag);
    if(shmid &lt; 0)
    {
        perror("shmget error");
        exit(1);
    }
    printf("create shared memory success, shmid = %d\n", shmid); //打印shmid的值
    return shmid;
}

int CreateSharedMem() //创建全新的共享内存
{
    return SharedMem(IPC_CREAT | IPC_EXCL | 0666);
}

int GetSharedMem() //获取已有的共享内存
{
    return SharedMem(IPC_CREAT);
}
</code></pre>
<p>编译并运行：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc6db94a3ac874f95bb6a5db638ea501a.gif&amp;size=m" alt=""></p>
<p>需要注意，共享内存是<strong>没有同步互斥</strong>之类的保护机制的，也就是说如果多个进程同时对共享内存进行读写操作可能导致冲突发生</p>
<p>因此，如果要实现对数据的保护，可以使用锁或者信号量等机制，或者与管道共同结合使用来让共享内存实现同步与互斥</p>
<p>完.</p>
<p>原文链接: https://blog.csdn.net/Eristic0618/article/details/142183129</p>]]></description><guid isPermaLink="false">/archives/673db7cf-15f1-44c6-9167-2e4177ffdaab</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Tue, 8 Oct 2024 03:27:24 GMT</pubDate></item><item><title><![CDATA[Linux如何修改时间和时区？]]></title><link>https://wiki.dgfygk.com/archives/94b690c7-8afe-40bc-8550-04693cf7af80</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E5%A6%82%E4%BD%95%E4%BF%AE%E6%94%B9%E6%97%B6%E9%97%B4%E5%92%8C%E6%97%B6%E5%8C%BA%EF%BC%9F&amp;url=/archives/94b690c7-8afe-40bc-8550-04693cf7af80" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>Linux 修改时间 修改时区</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#_3">时间修改</a></li>
 <li><a href="https://wiki.dgfygk.com#_16">时区修改</a></li>
</ul>
<p>
 <br>
</p>
<h3>时间修改</h3>
<pre><code class="prism language-shell">#查看时间
[root@localhost ~]# date
2024年 07月 08日 星期一 17:55:48 JST

#设置时间 例：2024-07-08 18:00:00
[root@localhost ~]# date -s "2024-07-08 18:00:00"
2024年 07月 08日 星期一 18:00:00 JST
</code></pre>
<h3>时区修改</h3>
<pre><code class="prism language-shell">#查看时区时间
[root@localhost ~]# date -R
Mon, 08 Jul 2024 17:04:20 +0900
</code></pre>
<p>这里显示+0900代表使用的是东九区的时区，咱们要</p>
<blockquote>
 <p>东八区 UTC+8
  <br>
  馬來西亞、菲律賓、新加坡、中國:上海、臺灣、成都、澳門、香港
  <br>
  东九区 UTC+9
  <br>
  日本、韩国、东京、首尔、札幌、大阪、东帝汶</p>
</blockquote>
<pre><code class="prism language-shell">#修改时区
[root@localhost ~]# tzselect
</code></pre>
<pre><code>```prism language-bash
Please identify a location so that time zone rules can be set correctly.
Please select a continent or ocean.
 1) Africa
 2) Americas
 3) Antarctica
 4) Arctic Ocean
 5) Asia
 6) Atlantic Ocean
 7) Australia
 8) Europe
 9) Indian Ocean
10) Pacific Ocean
11) none - I want to specify the time zone using the Posix TZ format.
#? 5    
</code></pre>
<p>这里选择 5) Asia 亚洲</p>
<pre><code class="prism language-bash">Please select a country.
 1) Afghanistan       18) Israel            35) Palestine
 2) Armenia       19) Japan         36) Philippines
 3) Azerbaijan        20) Jordan            37) Qatar
 4) Bahrain       21) Kazakhstan        38) Russia
 5) Bangladesh        22) Korea (North)     39) Saudi Arabia
 6) Bhutan        23) Korea (South)     40) Singapore
 7) Brunei        24) Kuwait            41) Sri Lanka
 8) Cambodia          25) Kyrgyzstan        42) Syria
 9) China         26) Laos          43) Taiwan
10) Cyprus        27) Lebanon           44) Tajikistan
11) East Timor        28) Macau         45) Thailand
12) Georgia       29) Malaysia          46) Turkmenistan
13) Hong Kong         30) Mongolia          47) United Arab Emirates
14) India         31) Myanmar (Burma)       48) Uzbekistan
15) Indonesia         32) Nepal         49) Vietnam
16) Iran          33) Oman          50) Yemen
17) Iraq          34) Pakistan
#? 9
</code></pre>
<p>这里选择 9) China <strong>中国🇨🇳</strong></p>
<pre><code class="prism language-bash">Please select one of the following time zone regions.
1) east China - Beijing, Guangdong, Shanghai, etc.
2) Heilongjiang (except Mohe), Jilin
3) central China - Sichuan, Yunnan, Guangxi, Shaanxi, Guizhou, etc.
4) most of Tibet &amp; Xinjiang
5) west Tibet &amp; Xinjiang
#? 1
</code></pre>
<p>这里选择 1) east China - Beijing, Guangdong, Shanghai, etc. 北京时间</p>
<pre><code class="prism language-bash">The following information has been given:

    China
    east China - Beijing, Guangdong, Shanghai, etc.

Therefore TZ='Asia/Shanghai' will be used.
Local time is now:  Wed Jul 10 11:08:15 CST 2024.
Universal Time is now:  Wed Jul 10 03:08:15 UTC 2024.
Is the above information OK?
1) Yes
2) No
#? 1
</code></pre>
<p>这里选择 1) 确认修改</p>
<pre><code class="prism language-bash">You can make this change permanent for yourself by appending the line
    TZ='Asia/Shanghai'; export TZ
to the file '.profile' in your home directory; then log out and log in again.

Here is that TZ value again, this time on standard output so that you
can use the /usr/bin/tzselect command in shell scripts:
Asia/Shanghai
</code></pre>
<p>这段文字告诉我们可以讲时区写入配置文件，自此之后时区将永久生效，不易失效。</p>
<pre><code class="prism language-bash"># 编辑配置文件
vi ~/.bash_profile
</code></pre>
<pre><code>```prism language-properties
# 添加以下两行内容，保存并退出
TZ='Asia/Shanghai'
export TZ
</code></pre>
<pre><code>```prism language-bash
# 执行以下命令来生效刚刚修改的配置文件
source ~/.bash_profile
</code></pre>
<p>经过以上步骤之后我们再查询一下时区信息</p>
<pre><code class="prism language-bash">[root@localhost ~]# date -R
Wed, 10 Jul 2024 11:12:10 +0800
</code></pre>
<p>大功告成，已经切换为中国时区 🎉🎉🎉🎉🎉</p>
<p>原文链接: https://blog.csdn.net/qq_43800290/article/details/140318287</p>]]></description><guid isPermaLink="false">/archives/94b690c7-8afe-40bc-8550-04693cf7af80</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Tue, 8 Oct 2024 03:27:14 GMT</pubDate></item><item><title><![CDATA[CentOS 修改服务器登录密码的完整指南]]></title><link>https://wiki.dgfygk.com/archives/2487bb5a-ed40-446c-8a02-12596c5f6326</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=CentOS%20%E4%BF%AE%E6%94%B9%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%99%BB%E5%BD%95%E5%AF%86%E7%A0%81%E7%9A%84%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97&amp;url=/archives/2487bb5a-ed40-446c-8a02-12596c5f6326" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p><strong>个人名片</strong>
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2F402a907e12694df5a34f8f266385f3d2.png%23pic_center&amp;size=m" alt="">
  <br>
  🎓<strong>作者简介</strong> ：java领域优质创作者
  <br>
  🌐<strong>个人主页</strong> ：<a href="https://blog.csdn.net/weixin_44976692?type=blog">码农阿豪</a>
  <br>
  📞<strong>工作室</strong> ：新空间代码工作室（提供各种软件服务）
  <br>
  💌<strong>个人邮箱</strong> ：[2435024119@qq.com]
  <br>
  📱<strong>个人微信</strong> ：15279484656
  <br>
  🌐<strong>个人导航网站</strong> ：www.forff.top
  <br>
  💡座右铭：总有人要赢。为什么不能是我呢？</p>
</blockquote>
<ul>
 <li>专栏导航：</li>
</ul>
<blockquote>
 <p><strong>码农阿豪系列专栏导航</strong>
  <br>
  <strong><a href="https://blog.csdn.net/weixin_44976692/category_12053689.html?spm=1001.2014.3001.5482">面试专栏</a></strong> ：收集了java相关高频面试题，面试实战总结🍻🎉🖥️
  <br>
  <strong><a href="https://blog.csdn.net/weixin_44976692/category_12561762.html?spm=1001.2014.3001.5482">Spring5系列专栏</a></strong> ：整理了Spring5重要知识点与实战演练，有案例可直接使用🚀🔧💻
  <br>
  <strong><a href="https://blog.csdn.net/weixin_44976692/category_12580216.html?spm=1001.2014.3001.5482">Redis专栏</a></strong> ：Redis从零到一学习分享，经验总结，案例实战💐📝💡
  <br>
  <strong><a href="https://blog.csdn.net/weixin_44976692/category_12533392.html?spm=1001.2014.3001.5482">全栈系列专栏</a></strong>：海纳百川有容乃大，可能你想要的东西里面都有🤸🌱🚀</p>
</blockquote>
<p>
 <br>
</p>
<h4>目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_CentOS__19">使用 CentOS 修改服务器登录密码的完整指南</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_23">一、引言</a></li>
 <li><a href="https://wiki.dgfygk.com#_27">二、基本概念</a></li>
 <li><a href="https://wiki.dgfygk.com#_35">三、修改密码的步骤</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#31__CentOS__37">3.1 登录到 CentOS 服务器</a></li>
   <li><a href="https://wiki.dgfygk.com#32__47">3.2 修改当前用户的密码</a></li>
   <li><a href="https://wiki.dgfygk.com#33__67">3.3 修改其他用户的密码</a></li>
   <li><a href="https://wiki.dgfygk.com#34__77">3.4 密码复杂性检查</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_87">四、解决密码复杂性问题</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#41__89">4.1 使用更强的密码</a></li>
   <li><a href="https://wiki.dgfygk.com#42__102">4.2 禁用密码复杂性检查</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_pam_pwquality__106">禁用 `pam_pwquality` 模块的配置</a></li>
   <li><a href="https://wiki.dgfygk.com#_pam__128">临时修改 `pam` 模块配置</a></li>
   <li><a href="https://wiki.dgfygk.com#43__149">4.3 修改成功后恢复配置</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_153">五、总结</a></li>
</ul>
<p>
 <br>
</p>
<h2>使用 CentOS 修改服务器登录密码的完整指南</h2>
<p>在日常使用 Linux 服务器时，修改用户登录密码是维护系统安全性的重要环节。本文将详细介绍如何在 CentOS 系统中修改用户密码，包括可能遇到的常见问题及其解决方案。</p>
<h3>一、引言</h3>
<p>在 CentOS 等 Linux 系统中，密码是保护用户账户及系统数据的第一道防线。因此，定期更改密码和使用强密码是系统管理员的基本职责。本文将为您提供一个系统化的指南，帮助您顺利完成密码的修改，并解决在此过程中可能遇到的各种问题。</p>
<h3>二、基本概念</h3>
<p>在开始之前，我们需要了解一些基本概念：</p>
<ol>
 <li><strong>用户账户</strong>: Linux 系统中每个用户都有一个账户和相应的密码。账户用于访问系统的权限和资源。</li>
 <li><strong>密码复杂性</strong>: 密码复杂性是指密码必须满足的强度要求，以保护账户不受未经授权的访问。</li>
 <li><strong>字典检查</strong>: 许多 Linux 系统在密码设置时会进行字典检查，确保新密码不与常见的词汇相同，以增强安全性。</li>
</ol>
<h3>三、修改密码的步骤</h3>
<h4>3.1 登录到 CentOS 服务器</h4>
<p>首先，您需要通过 SSH 或直接登录到 CentOS 服务器。可以使用以下命令：</p>
<pre><code class="prism language-bash">ssh user@your_server_ip
</code></pre>
<p>确保您替换 <code>user</code> 和 <code>your_server_ip</code> 为您的实际用户名和服务器的 IP 地址。</p>
<h4>3.2 修改当前用户的密码</h4>
<p>如果您希望修改当前登录用户的密码，执行以下命令：</p>
<pre><code class="prism language-bash">passwd
</code></pre>
<p>系统会提示您输入当前密码和新密码：</p>
<pre><code class="prism language-plaintext">Changing password for user user.
Current password:  (输入当前密码)
New password:      (输入新密码)
Retype new password: (再次输入新密码)
passwd: all authentication tokens updated successfully.
</code></pre>
<p>在输入新密码时，请确保其符合系统的复杂性要求。</p>
<h4>3.3 修改其他用户的密码</h4>
<p>如果您是 <code>root</code> 用户或有 <code>sudo</code> 权限，您可以修改其他用户的密码。假设要修改用户 <code>username</code> 的密码，您可以使用：</p>
<pre><code class="prism language-bash">sudo passwd username
</code></pre>
<p>同样，系统将提示输入新密码并进行确认。</p>
<h4>3.4 密码复杂性检查</h4>
<p>在某些情况下，您可能会遇到以下错误提示：</p>
<pre><code class="prism language-plaintext">BAD PASSWORD: The password fails the dictionary check - it is based on a dictionary word.
</code></pre>
<p>这意味着您输入的密码过于简单，未能通过系统的安全检查。解决这一问题有以下几种方法。</p>
<h3>四、解决密码复杂性问题</h3>
<h4>4.1 使用更强的密码</h4>
<p>为了解决上述问题，您可以设置更复杂的密码。强密码应包含以下元素：</p>
<ul>
 <li>大小写字母（例如：<code>A</code>、<code>z</code>）</li>
 <li>数字（例如：<code>0</code>-<code>9</code>）</li>
 <li>特殊字符（例如：<code>@</code>、<code>#</code>、<code>!</code>）</li>
 <li>长度：确保密码至少为 <strong>8-12 个字符</strong>。</li>
</ul>
<p><strong>示例密码</strong>：</p>
<ul>
 <li><code>S3cur3P@ssw0rd!</code></li>
 <li><code>Adm1n#2024</code></li>
</ul>
<h4>4.2 禁用密码复杂性检查</h4>
<p>在特殊情况下，您可能希望设置简单密码，而不想让系统检查复杂性。在这种情况下，可以暂时禁用系统的密码复杂性检查。</p>
<h5>禁用 <code>pam_pwquality</code> 模块的配置</h5>
<ol>
 <li>
  <p><strong>打开配置文件</strong>:</p>
  <p>使用编辑器打开 <code>/etc/security/pwquality.conf</code> 文件：</p>
  <pre><code class="prism language-bash">sudo vi /etc/security/pwquality.conf
</code></pre>
 </li>
 <li>
  <p><strong>修改参数</strong>:</p>
  <p>修改或添加以下参数以降低密码复杂度要求：</p>
  <pre><code class="prism language-plaintext">minlen = 6         # 密码最短长度
minclass = 0       # 取消对密码需要包含字符类型（如大写字母、小写字母、数字、特殊字符）的限制
dictcheck = 0      # 关闭字典检查
</code></pre>
 </li>
 <li>
  <p><strong>保存并退出</strong>后，再次尝试更改密码。</p>
 </li>
</ol>
<h5>临时修改 <code>pam</code> 模块配置</h5>
<ol>
 <li>
  <p><strong>编辑 PAM 文件</strong>:</p>
  <p>修改 <code>/etc/pam.d/password-auth</code> 和 <code>/etc/pam.d/system-auth</code> 文件。</p>
  <pre><code class="prism language-bash">sudo vi /etc/pam.d/password-auth
sudo vi /etc/pam.d/system-auth
</code></pre>
 </li>
 <li>
  <p><strong>注释掉相关行</strong>:</p>
  <p>找到如下行并将其注释掉或移除：</p>
  <pre><code class="prism language-plaintext">password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
</code></pre>
 </li>
 <li>
  <p><strong>保存退出后，系统将不再强制检查密码复杂度。</strong></p>
 </li>
</ol>
<h4>4.3 修改成功后恢复配置</h4>
<p>如果您临时禁用了密码复杂度检查，建议在修改完密码后，恢复 <code>/etc/security/pwquality.conf</code> 或 <code>/etc/pam.d/password-auth</code> 和 <code>/etc/pam.d/system-auth</code> 中的配置，以确保系统安全性。</p>
<h3>五、总结</h3>
<p>在 CentOS 系统中，修改用户密码的过程相对简单，但密码的复杂性要求对用户来说可能是一个挑战。通过使用强密码和定期更改密码，您可以有效地提升系统的安全性。</p>
<p>如果您在设置密码时遇到困难，可以根据本文提供的解决方案进行排查和修复。希望这篇指南能够帮助您顺利完成 CentOS 的密码修改，同时维护系统的安全性。</p>
<p>保持系统安全是每位管理员的责任，定期检查用户权限和密码策略，确保没有安全漏洞。通过良好的管理习惯和定期审查，您可以将系统的安全性提升到一个新的水平。</p>
<p>感谢您阅读本指南！如果您有任何问题或想法，欢迎在评论区留言。**</p>
<p>原文链接: https://blog.csdn.net/weixin_44976692/article/details/142615831</p>]]></description><guid isPermaLink="false">/archives/2487bb5a-ed40-446c-8a02-12596c5f6326</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Tue, 8 Oct 2024 03:27:03 GMT</pubDate></item><item><title><![CDATA[虚拟机Ubuntu忘记密码]]></title><link>https://wiki.dgfygk.com/archives/2a0de07b-46ec-4182-8425-f1ac40f5527f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%99%9A%E6%8B%9F%E6%9C%BAUbuntu%E5%BF%98%E8%AE%B0%E5%AF%86%E7%A0%81&amp;url=/archives/2a0de07b-46ec-4182-8425-f1ac40f5527f" width="1" height="1" alt="" style="opacity:0;">
<h3>Ubuntu重置密码</h3>
<p>因为前几天虚拟机上刚安装的Ubuntu系统，密码忘记了，这次留个笔记记录一下。</p>
<p>有两种情况：</p>
<h5>1、第一种</h5>
<p>1、虚拟机上的Ubuntu开启，按住Shift键，直到出现下面的界面。(记住，一开启系统就长按Shift)</p>
<blockquote>
 <p>如果没有出现下面的界面，直接关闭Ubuntu，重新启动。
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7ce99ac978015cbfa4cd045ca60f674b.png&amp;size=m" alt=""></p>
</blockquote>
<p>2、选中上图框选的第二项，按下<code>e</code>键。</p>
<p>3、选中 recovery mode 这一项，按下 <code>e</code>键
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9d9d26543d4f49bb71f3a4083b40737b.png&amp;size=m" alt=""></p>
<p>3、在 linux /boot/vmlinuz… 这一行的最后面添加<code>quiet splash rw init=/bin/bash</code>，然后Ctrl+x直接运行
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc98719dc73cbe92edc7dc7693b8e4505.png&amp;size=m" alt=""></p>
<p>4、运行后系统直接进入root mode，此时运行passwd命令更改密码。</p>
<p>5、移动到root，按下Enter键。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0cb7b6d76930e85c4a66ca77c44fc91b.png&amp;size=m" alt=""></p>
<p>6、可以通过下面命令来更改密码。</p>
<pre><code class="prism language-bash">cat /etc/passwd # 查看用户
passwd 用户名  # 修改对应用户密码
</code></pre>
<p>7、更改root密码。</p>
<pre><code class="prism language-bash">passwd root
</code></pre>
<p>8、根据页面提示输入密码。</p>
<h5>2、第二种</h5>
<p>1、虚拟机上的Ubuntu开启，按住Shift键，直到出现下面的界面。(记住，一开启系统就长按Shift)
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7ce99ac978015cbfa4cd045ca60f674b.png&amp;size=m" alt=""></p>
<p>2、按下<code>e</code>键，你会进入到下面的这个页面中。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd97d7803242000ef9c5afd61549131fb.png&amp;size=m" alt=""></p>
<p>3、第四步、将"ro recovery nomodeset"处的"ro"改成"rw”，然后按下<code>Ctrl+X</code>键。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fce15d44c04541483281d4b8e973aad7f.png&amp;size=m" alt=""></p>
<p>4、然后你又会回到这个页面(应该是多次重启或者开关机导致卡bug了)，和第一种情况同理。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffbf25d5ea18905f2bb90a565c4c8fc6c.png&amp;size=m" alt=""></p>
<p>5、后面的步骤就和前面的步骤一致。</p>
<blockquote>
 <p>如果后面弹出程序延迟执行，可以选择再次重启。</p>
</blockquote>
<p>原文链接: https://blog.csdn.net/feichiraki/article/details/139749061</p>]]></description><guid isPermaLink="false">/archives/2a0de07b-46ec-4182-8425-f1ac40f5527f</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Tue, 8 Oct 2024 03:26:51 GMT</pubDate></item><item><title><![CDATA[Spring Boot + MyBatis 项目中常用注解详解（万字长篇解读）]]></title><link>https://wiki.dgfygk.com/archives/f04e6c9f-c70d-4d6b-87a3-8c3a3289237a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%20%2B%20MyBatis%20%E9%A1%B9%E7%9B%AE%E4%B8%AD%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3%E8%AF%A6%E8%A7%A3%EF%BC%88%E4%B8%87%E5%AD%97%E9%95%BF%E7%AF%87%E8%A7%A3%E8%AF%BB%EF%BC%89&amp;url=/archives/f04e6c9f-c70d-4d6b-87a3-8c3a3289237a" width="1" height="1" alt="" style="opacity:0;">
<h2>Spring Boot + MyBatis 项目中常用注解详解</h2>
<p>在现代Java开发中，<strong>Spring Boot</strong> 和<strong>MyBatis</strong>是两大热门框架，广泛应用于构建高效、可维护的企业级应用。两者结合使用，可以充分发挥各自的优势，提高开发效率和代码质量。在这个过程中，注解（Annotations）起到了关键作用。本文将详细、全面地讲解在Spring Boot + MyBatis项目中常用的注解及其含义和作用，并通过分类整理，帮助开发者更好地理解和运用这些工具。</p>
<p>
 <br>
</p>
<h4>目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#Spring_Boot__MyBatis__0">Spring Boot + MyBatis 项目中常用注解详解</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#Spring_Boot__8">Spring Boot 常用注解</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_12">核心注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#SpringBootApplication_14">@SpringBootApplication</a></li>
   <li><a href="https://wiki.dgfygk.com#_29">组件标识注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#RestController_33">@RestController</a></li>
   <li><a href="https://wiki.dgfygk.com#Controller_51">@Controller</a></li>
   <li><a href="https://wiki.dgfygk.com#Service_69">@Service</a></li>
   <li><a href="https://wiki.dgfygk.com#Repository_88">@Repository</a></li>
   <li><a href="https://wiki.dgfygk.com#Component_107">@Component</a></li>
   <li><a href="https://wiki.dgfygk.com#_123">依赖注入注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#Autowired_127">@Autowired</a></li>
   <li><a href="https://wiki.dgfygk.com#Qualifier_150">@Qualifier</a></li>
   <li><a href="https://wiki.dgfygk.com#Inject_166">@Inject</a></li>
   <li><a href="https://wiki.dgfygk.com#_181">配置注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#Configuration_185">@Configuration</a></li>
   <li><a href="https://wiki.dgfygk.com#Bean_202">@Bean</a></li>
   <li><a href="https://wiki.dgfygk.com#Value_219">@Value</a></li>
   <li><a href="https://wiki.dgfygk.com#ConfigurationProperties_237">@ConfigurationProperties</a></li>
   <li><a href="https://wiki.dgfygk.com#_255">事务管理注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#Transactional_259">@Transactional</a></li>
   <li><a href="https://wiki.dgfygk.com#Web_276">Web层注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_280">请求映射注解</a>
    <br>
    *
    <ul>
     <li><a href="https://wiki.dgfygk.com#RequestMapping_284">@RequestMapping</a></li>
     <li><a href="https://wiki.dgfygk.com#GetMapping_302">@GetMapping</a></li>
     <li><a href="https://wiki.dgfygk.com#PostMapping_315">@PostMapping</a></li>
     <li><a href="https://wiki.dgfygk.com#PutMapping_328">@PutMapping</a></li>
     <li><a href="https://wiki.dgfygk.com#DeleteMapping_341">@DeleteMapping</a></li>
    </ul></li>
   <li><a href="https://wiki.dgfygk.com#_354">参数绑定注解</a>
    <br>
    *
    <ul>
     <li><a href="https://wiki.dgfygk.com#PathVariable_358">@PathVariable</a></li>
     <li><a href="https://wiki.dgfygk.com#RequestParam_371">@RequestParam</a></li>
     <li><a href="https://wiki.dgfygk.com#RequestBody_384">@RequestBody</a></li>
     <li><a href="https://wiki.dgfygk.com#ResponseBody_397">@ResponseBody</a></li>
    </ul></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#MyBatis__415">MyBatis 常用注解</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#Mapper_419">Mapper接口注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#Mapper_421">@Mapper</a></li>
   <li><a href="https://wiki.dgfygk.com#SQL_436">SQL操作注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#Select_440">@Select</a></li>
   <li><a href="https://wiki.dgfygk.com#Insert_455">@Insert</a></li>
   <li><a href="https://wiki.dgfygk.com#Update_471">@Update</a></li>
   <li><a href="https://wiki.dgfygk.com#Delete_486">@Delete</a></li>
   <li><a href="https://wiki.dgfygk.com#_501">参数绑定注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#Param_503">@Param</a></li>
   <li><a href="https://wiki.dgfygk.com#_518">结果映射注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#Results__Result_522">@Results 和 @Result</a></li>
   <li><a href="https://wiki.dgfygk.com#_542">关联映射注解</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#One__Many_546">@One 和 @Many</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_566">综合示例</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_570">项目结构</a></li>
   <li><a href="https://wiki.dgfygk.com#1__590">1. 主类</a></li>
   <li><a href="https://wiki.dgfygk.com#2__604">2. 实体类</a></li>
   <li><a href="https://wiki.dgfygk.com#3_Mapper__616">3. Mapper 接口</a></li>
   <li><a href="https://wiki.dgfygk.com#4__640">4. 服务类</a></li>
   <li><a href="https://wiki.dgfygk.com#5__671">5. 控制器类</a></li>
   <li><a href="https://wiki.dgfygk.com#6__722">6. 配置文件</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_741">总结</a></li>
 <li><a href="https://wiki.dgfygk.com#_747">附录：常用注解对照表</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<h3>Spring Boot 常用注解</h3>
<p>Spring Boot 提供了丰富的注解来简化开发过程。以下将这些注解按照功能进行分类，便于理解和应用。</p>
<h4>核心注解</h4>
<h5>@SpringBootApplication</h5>
<p><strong>含义与作用</strong> ：<code>@SpringBootApplication</code> 是一个复合注解，包含了 <code>@Configuration</code>、<code>@EnableAutoConfiguration</code> 和 <code>@ComponentScan</code>。用于标注主类，启用Spring Boot的自动配置和组件扫描功能。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {

        SpringApplication.run(MyApplication.class, args);
    }
}
</code></pre>
<h4>组件标识注解</h4>
<p>这些注解用于标识不同层次的组件，便于Spring进行自动扫描和管理。</p>
<h5>@RestController</h5>
<p><strong>含义与作用</strong> ：<code>@RestController</code> 是 <code>@Controller</code> 和 <code>@ResponseBody</code> 的组合注解，用于标识控制器类，适用于构建RESTful Web服务。所有方法返回的对象会自动序列化为JSON或XML。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@RestController
@RequestMapping("/api")
public class UserController {


    @GetMapping("/users")
    public List&lt;User&gt; getAllUsers() {

        // 返回用户列表
    }
}
</code></pre>
<h5>@Controller</h5>
<p><strong>含义与作用</strong> ：<code>@Controller</code> 用于标识控制器类，通常与视图解析器（如Thymeleaf）配合使用，适用于传统的MVC模式。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Controller
public class HomeController {


    @GetMapping("/")
    public String home(Model model) {

        model.addAttribute("message", "Hello World");
        return "home";
    }
}
</code></pre>
<h5>@Service</h5>
<p><strong>含义与作用</strong> ：<code>@Service</code> 用于标识业务逻辑层的组件，增强代码的可读性和可维护性。它也是一个 <code>@Component</code>，因此会被Spring扫描和管理。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Service
public class UserService {


    @Autowired
    private UserRepository userRepository;

    public List&lt;User&gt; getAllUsers() {

        return userRepository.findAll();
    }
}
</code></pre>
<h5>@Repository</h5>
<p><strong>含义与作用</strong> ：<code>@Repository</code> 用于标识数据访问层的组件，通常与持久化技术（如JPA、MyBatis）结合使用。它还提供了异常转换功能，将持久层异常转换为Spring的统一异常。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Repository
public class UserRepository {


    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List&lt;User&gt; findAll() {

        // 查询数据库
    }
}
</code></pre>
<h5>@Component</h5>
<p><strong>含义与作用</strong> ：<code>@Component</code> 是一个通用的Spring管理的组件注解，用于标识任何Spring管理的Bean。其他注解（如 <code>@Service</code>、<code>@Repository</code>、<code>@Controller</code>）都是 <code>@Component</code> 的特化。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Component
public class Utility {


    public void performTask() {

        // 执行任务
    }
}
</code></pre>
<h4>依赖注入注解</h4>
<p>这些注解用于实现Spring的依赖注入机制，简化Bean的管理和使用。</p>
<h5>@Autowired</h5>
<p><strong>含义与作用</strong> ：<code>@Autowired</code> 用于自动装配Bean，可以应用于构造函数、字段或Setter方法。Spring会根据类型自动注入匹配的Bean。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Service
public class OrderService {


    @Autowired
    private OrderRepository orderRepository;

    // 或者通过构造函数注入
    // private final OrderRepository orderRepository;
    //
    // @Autowired
    // public OrderService(OrderRepository orderRepository) {

    //     this.orderRepository = orderRepository;
    // }
}
</code></pre>
<h5>@Qualifier</h5>
<p><strong>含义与作用</strong> ：<code>@Qualifier</code> 与 <code>@Autowired</code> 一起使用，用于在多个Bean候选者中指定要注入的具体Bean。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Service
public class PaymentService {


    @Autowired
    @Qualifier("creditCardPayment")
    private PaymentProcessor paymentProcessor;
}
</code></pre>
<h5>@Inject</h5>
<p><strong>含义与作用</strong> ：<code>@Inject</code> 是JSR-330规范的注解，与 <code>@Autowired</code> 类似，用于实现依赖注入。功能上与 <code>@Autowired</code> 基本相同，但不支持 <code>required</code> 属性。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Service
public class NotificationService {


    @Inject
    private EmailService emailService;
}
</code></pre>
<h4>配置注解</h4>
<p>用于定义和管理应用的配置和Bean。</p>
<h5>@Configuration</h5>
<p><strong>含义与作用</strong> ：<code>@Configuration</code> 用于标识配置类，类似于传统的XML配置文件。可以在配置类中定义Bean，并进行依赖注入的配置。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Configuration
public class AppConfig {


    @Bean
    public RestTemplate restTemplate() {

        return new RestTemplate();
    }
}
</code></pre>
<h5>@Bean</h5>
<p><strong>含义与作用</strong> ：<code>@Bean</code> 用于在 <code>@Configuration</code> 类中定义Bean，Spring会管理这些Bean的生命周期和依赖。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Configuration
public class DataSourceConfig {


    @Bean
    public DataSource dataSource() {

        // 配置数据源
    }
}
</code></pre>
<h5>@Value</h5>
<p><strong>含义与作用</strong> ：<code>@Value</code> 用于注入配置文件中的属性值，可以注入到字段、方法参数或构造函数参数中。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Component
public class AppProperties {


    @Value("${app.name}")
    private String appName;

    @Value("${app.version}")
    private String appVersion;
}
</code></pre>
<h5>@ConfigurationProperties</h5>
<p><strong>含义与作用</strong> ：<code>@ConfigurationProperties</code> 用于将配置文件中的属性映射到一个Java对象中，适用于大量相关属性的绑定。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {


    private String name;
    private String version;

    // getters and setters
}
</code></pre>
<h4>事务管理注解</h4>
<p>用于声明事务管理，确保数据一致性和完整性。</p>
<h5>@Transactional</h5>
<p><strong>含义与作用</strong> ：<code>@Transactional</code> 用于声明事务管理，适用于需要事务控制的方法或类。可以指定事务的传播行为、隔离级别、回滚规则等。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Service
public class PaymentService {


    @Transactional
    public void processPayment(Order order) {

        // 执行支付逻辑
    }
}
</code></pre>
<h4>Web层注解</h4>
<p>用于处理Web请求和响应，简化控制器的开发。</p>
<h5>请求映射注解</h5>
<p>这些注解用于定义HTTP请求的路径和方法，简化路由配置。</p>
<h6>@RequestMapping</h6>
<p><strong>含义与作用</strong> ：<code>@RequestMapping</code> 用于定义请求的URL路径和HTTP方法，适用于类级别和方法级别。它是一个通用的映射注解，可以通过 <code>method</code> 属性指定具体的HTTP方法。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@RestController
@RequestMapping("/api")
public class UserController {


    @RequestMapping(value = "/users", method = RequestMethod.GET)
    public List&lt;User&gt; getAllUsers() {

        // 返回用户列表
    }
}
</code></pre>
<h6>@GetMapping</h6>
<p><strong>含义与作用</strong> ：<code>@GetMapping</code> 是 <code>@RequestMapping</code> 的快捷方式，用于处理HTTP GET请求。适用于方法级别，简化代码。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@GetMapping("/users")
public List&lt;User&gt; getAllUsers() {

    // 返回用户列表
}
</code></pre>
<h6>@PostMapping</h6>
<p><strong>含义与作用</strong> ：<code>@PostMapping</code> 是 <code>@RequestMapping</code> 的快捷方式，用于处理HTTP POST请求。适用于方法级别，简化代码。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@PostMapping("/users")
public ResponseEntity&lt;Void&gt; createUser(@RequestBody User user) {

    // 创建用户
}
</code></pre>
<h6>@PutMapping</h6>
<p><strong>含义与作用</strong> ：<code>@PutMapping</code> 是 <code>@RequestMapping</code> 的快捷方式，用于处理HTTP PUT请求。适用于方法级别，简化代码。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@PutMapping("/users/{id}")
public ResponseEntity&lt;Void&gt; updateUser(@PathVariable Long id, @RequestBody User user) {

    // 更新用户
}
</code></pre>
<h6>@DeleteMapping</h6>
<p><strong>含义与作用</strong> ：<code>@DeleteMapping</code> 是 <code>@RequestMapping</code> 的快捷方式，用于处理HTTP DELETE请求。适用于方法级别，简化代码。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@DeleteMapping("/users/{id}")
public ResponseEntity&lt;Void&gt; deleteUser(@PathVariable Long id) {

    // 删除用户
}
</code></pre>
<h5>参数绑定注解</h5>
<p>这些注解用于将HTTP请求中的参数绑定到控制器方法的参数上。</p>
<h6>@PathVariable</h6>
<p><strong>含义与作用</strong> ：<code>@PathVariable</code> 用于绑定URL路径中的变量到方法参数，常用于RESTful API中的资源标识。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {

    // 根据ID获取用户
}
</code></pre>
<h6>@RequestParam</h6>
<p><strong>含义与作用</strong> ：<code>@RequestParam</code> 用于绑定HTTP请求中的参数到方法参数，适用于查询参数或表单参数。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@GetMapping("/search")
public List&lt;User&gt; searchUsers(@RequestParam String name, @RequestParam int age) {

    // 根据姓名和年龄搜索用户
}
</code></pre>
<h6>@RequestBody</h6>
<p><strong>含义与作用</strong> ：<code>@RequestBody</code> 用于将HTTP请求体中的JSON或XML数据绑定到方法参数，常用于处理POST和PUT请求中的数据。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@PostMapping("/users")
public ResponseEntity&lt;Void&gt; createUser(@RequestBody User user) {

    // 创建用户
}
</code></pre>
<h6>@ResponseBody</h6>
<p><strong>含义与作用</strong> ：<code>@ResponseBody</code> 用于将方法返回的对象序列化为JSON或XML并写入HTTP响应体，通常与 <code>@Controller</code> 一起使用。<code>@RestController</code> 已经包含了 <code>@ResponseBody</code> 的功能。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Controller
public class UserController {


    @GetMapping("/users")
    @ResponseBody
    public List&lt;User&gt; getAllUsers() {

        // 返回用户列表
    }
}
</code></pre>
<h3>MyBatis 常用注解</h3>
<p>MyBatis 是一个优秀的持久层框架，通过映射语句将数据库操作与Java方法关联。MyBatis提供了一系列注解，简化了XML映射文件的使用。</p>
<h4>Mapper接口注解</h4>
<h5>@Mapper</h5>
<p><strong>含义与作用</strong> ：<code>@Mapper</code> 用于标识MyBatis的Mapper接口，Spring会自动扫描并生成代理对象。通常与 <code>@MapperScan</code> 配合使用。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(@Param("id") Long id);
}
</code></pre>
<h4>SQL操作注解</h4>
<p>这些注解用于定义具体的SQL语句，关联到Mapper接口的方法上，实现数据库的CRUD操作。</p>
<h5>@Select</h5>
<p><strong>含义与作用</strong> ：<code>@Select</code> 用于标注查询语句，关联到接口的方法上，实现数据库的查询操作。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Select("SELECT * FROM users")
    List&lt;User&gt; getAllUsers();
}
</code></pre>
<h5>@Insert</h5>
<p><strong>含义与作用</strong> ：<code>@Insert</code> 用于标注插入语句，关联到接口的方法上，实现数据库的插入操作。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insertUser(User user);
}
</code></pre>
<h5>@Update</h5>
<p><strong>含义与作用</strong> ：<code>@Update</code> 用于标注更新语句，关联到接口的方法上，实现数据库的更新操作。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Update("UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}")
    void updateUser(User user);
}
</code></pre>
<h5>@Delete</h5>
<p><strong>含义与作用</strong> ：<code>@Delete</code> 用于标注删除语句，关联到接口的方法上，实现数据库的删除操作。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Delete("DELETE FROM users WHERE id = #{id}")
    void deleteUser(@Param("id") Long id);
}
</code></pre>
<h4>参数绑定注解</h4>
<h5>@Param</h5>
<p><strong>含义与作用</strong> ：<code>@Param</code> 用于给方法参数命名，以便在SQL语句中引用。特别适用于有多个参数的方法。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Select("SELECT * FROM users WHERE name = #{name} AND age = #{age}")
    List&lt;User&gt; getUsersByNameAndAge(@Param("name") String name, @Param("age") int age);
}
</code></pre>
<h4>结果映射注解</h4>
<p>这些注解用于定义查询结果与Java对象属性的映射关系，适用于字段名与属性名不一致的情况。</p>
<h5>@Results 和 @Result</h5>
<p><strong>含义与作用</strong> ：<code>@Results</code> 和 <code>@Result</code> 用于定义查询结果与Java对象属性的映射关系，适用于字段名与属性名不一致的情况。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Select("SELECT id, user_name, email_address FROM users WHERE id = #{id}")
    @Results({

        @Result(property = "id", column = "id"),
        @Result(property = "name", column = "user_name"),
        @Result(property = "email", column = "email_address")
    })
    User getUserById(@Param("id") Long id);
}
</code></pre>
<h4>关联映射注解</h4>
<p>用于定义对象之间的一对一或一对多关系，适用于复杂的对象关系映射。</p>
<h5>@One 和 @Many</h5>
<p><strong>含义与作用</strong> ：<code>@One</code> 和 <code>@Many</code> 用于定义一对一和一对多的关联关系，适用于复杂的对象关系映射。</p>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-java">@Mapper
public interface OrderMapper {


    @Select("SELECT * FROM orders WHERE id = #{id}")
    @Results({

        @Result(property = "id", column = "id"),
        @Result(property = "user", column = "user_id", 
                one = @One(select = "com.example.mapper.UserMapper.getUserById"))
    })
    Order getOrderById(@Param("id") Long id);
}
</code></pre>
<h3>综合示例</h3>
<p>为了更好地理解上述注解的应用，下面通过一个简单的用户管理系统示例，展示Spring Boot与MyBatis如何协同工作。</p>
<h4>项目结构</h4>
<pre><code>src
├── main
│   ├── java
│   │   └── com.example.demo
│   │       ├── DemoApplication.java
│   │       ├── controller
│   │       │   └── UserController.java
│   │       ├── service
│   │       │   └── UserService.java
│   │       ├── mapper
│   │       │   └── UserMapper.java
│   │       └── model
│   │           └── User.java
│   └── resources
│       └── application.properties
</code></pre>
<h4>1. 主类</h4>
<pre><code class="prism language-java">@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);
    }
}
</code></pre>
<p><strong>说明</strong> ：<code>@MapperScan</code> 指定MyBatis的Mapper接口扫描路径，确保<code>@Mapper</code>接口能够被正确识别和代理。</p>
<h4>2. 实体类</h4>
<pre><code class="prism language-java">public class User {

    private Long id;
    private String name;
    private String email;

    // getters and setters
}
</code></pre>
<h4>3. Mapper 接口</h4>
<pre><code class="prism language-java">@Mapper
public interface UserMapper {


    @Select("SELECT * FROM users")
    List&lt;User&gt; getAllUsers();

    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(@Param("id") Long id);

    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insertUser(User user);

    @Update("UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}")
    void updateUser(User user);

    @Delete("DELETE FROM users WHERE id = #{id}")
    void deleteUser(@Param("id") Long id);
}
</code></pre>
<h4>4. 服务类</h4>
<pre><code class="prism language-java">@Service
public class UserService {


    @Autowired
    private UserMapper userMapper;

    public List&lt;User&gt; getAllUsers() {

        return userMapper.getAllUsers();
    }

    public User getUserById(Long id) {

        return userMapper.getUserById(id);
    }

    public void createUser(User user) {

        userMapper.insertUser(user);
    }

    public void updateUser(User user) {

        userMapper.updateUser(user);
    }

    public void deleteUser(Long id) {

        userMapper.deleteUser(id);
    }
}
</code></pre>
<h4>5. 控制器类</h4>
<pre><code class="prism language-java">@RestController
@RequestMapping("/api/users")
public class UserController {


    @Autowired
    private UserService userService;

    // 获取所有用户
    @GetMapping
    public List&lt;User&gt; listUsers() {

        return userService.getAllUsers();
    }

    // 根据ID获取用户
    @GetMapping("/{id}")
    public ResponseEntity&lt;User&gt; getUser(@PathVariable Long id) {

        User user = userService.getUserById(id);
        if(user != null){

            return ResponseEntity.ok(user);
        } else {

            return ResponseEntity.notFound().build();
        }
    }

    // 创建用户
    @PostMapping
    public ResponseEntity&lt;Void&gt; createUser(@RequestBody User user) {

        userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    // 更新用户
    @PutMapping("/{id}")
    public ResponseEntity&lt;Void&gt; updateUser(@PathVariable Long id, @RequestBody User user) {

        user.setId(id);
        userService.updateUser(user);
        return ResponseEntity.ok().build();
    }

    // 删除用户
    @DeleteMapping("/{id}")
    public ResponseEntity&lt;Void&gt; deleteUser(@PathVariable Long id) {

        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}
</code></pre>
<h4>6. 配置文件</h4>
<pre><code class="prism language-properties">spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.configuration.map-underscore-to-camel-case=true

# 显示SQL语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
</code></pre>
<p><strong>说明</strong>：</p>
<ul>
 <li><code>map-underscore-to-camel-case=true</code>：自动将数据库下划线命名转换为Java的驼峰命名。</li>
 <li><code>log-impl</code>：设置MyBatis的日志实现，这里使用标准输出日志。</li>
</ul>
<h3>总结</h3>
<p>在Spring Boot + MyBatis项目中，注解的使用极大地简化了配置和开发流程。通过合理运用各种注解，可以实现代码的高内聚低耦合，提升项目的可维护性和扩展性。本文按照功能对Spring Boot和MyBatis中的常用注解进行了分类讲解，并通过一个综合示例展示了它们的实际应用。希望能帮助开发者在实际项目中更好地理解和运用这些注解，构建高效、稳定的应用系统。</p>
<hr>
<h3>附录：常用注解对照表</h3>
<p>| 注解类别 | 注解名称 | 作用 |
 <br>
 |—————|—————————-|————————|
 <br>
 | <strong>核心注解</strong> | <code>@SpringBootApplication</code> | 标注主类，启用自动配置和组件扫描 |
 <br>
 | <strong>组件标识注解</strong> | <code>@RestController</code> | 标识REST控制器，返回JSON/XML数据 |
 <br>
 | | <code>@Controller</code> | 标识MVC控制器，返回视图 |
 <br>
 | | <code>@Service</code> | 标识业务逻辑层组件 |
 <br>
 | | <code>@Repository</code> | 标识数据访问层组件，提供异常转换 |
 <br>
 | | <code>@Component</code> | 通用组件注解 |
 <br>
 | <strong>依赖注入注解</strong> | <code>@Autowired</code> | 自动装配Bean |
 <br>
 | | <code>@Qualifier</code> | 指定注入的具体Bean |
 <br>
 | | <code>@Inject</code> | 实现依赖注入（JSR-330规范） |
 <br>
 | <strong>配置注解</strong> | <code>@Configuration</code> | 标识配置类 |
 <br>
 | | <code>@Bean</code> | 定义Bean |
 <br>
 | | <code>@Value</code> | 注入配置属性值 |
 <br>
 | | <code>@ConfigurationProperties</code> | 绑定配置属性到Java对象 |
 <br>
 | <strong>事务管理注解</strong> | <code>@Transactional</code> | 声明事务管理 |
 <br>
 | <strong>Web层注解</strong> | <code>@RequestMapping</code> | 定义请求路径和方法 |
 <br>
 | | <code>@GetMapping</code> | 处理GET请求 |
 <br>
 | | <code>@PostMapping</code> | 处理POST请求 |
 <br>
 | | <code>@PutMapping</code> | 处理PUT请求 |
 <br>
 | | <code>@DeleteMapping</code> | 处理DELETE请求 |
 <br>
 | | <code>@PathVariable</code> | 绑定URL路径变量到方法参数 |
 <br>
 | | <code>@RequestParam</code> | 绑定请求参数到方法参数 |
 <br>
 | | <code>@RequestBody</code> | 绑定请求体到方法参数 |
 <br>
 | | <code>@ResponseBody</code> | 将方法返回值序列化到响应体 |
 <br>
 | <strong>MyBatis注解</strong> | <code>@Mapper</code> | 标识Mapper接口 |
 <br>
 | | <code>@Select</code> | 定义SELECT语句 |
 <br>
 | | <code>@Insert</code> | 定义INSERT语句 |
 <br>
 | | <code>@Update</code> | 定义UPDATE语句 |
 <br>
 | | <code>@Delete</code> | 定义DELETE语句 |
 <br>
 | | <code>@Param</code> | 命名方法参数 |
 <br>
 | | <code>@Results</code> | 定义结果集映射 |
 <br>
 | | <code>@Result</code> | 定义单个结果映射 |
 <br>
 | | <code>@One</code> | 定义一对一关联 |
 <br>
 | | <code>@Many</code> | 定义一对多关联 |</p>
<p>通过对这些注解的深入理解和正确使用，可以有效提升Spring Boot + MyBatis项目的开发效率和代码质量。</p>
<hr>
<p><code>摊牌了，不装了，整理不易，我想要你手中的赞和关注🌼🌼🌼</code></p>
<p>原文链接: https://blog.csdn.net/2301_79858914/article/details/142682369</p>]]></description><guid isPermaLink="false">/archives/f04e6c9f-c70d-4d6b-87a3-8c3a3289237a</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:26:32 GMT</pubDate></item><item><title><![CDATA[Nacos集群部署]]></title><link>https://wiki.dgfygk.com/archives/c81d17c7-ff14-4fb3-94aa-2016df345df0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Nacos%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2&amp;url=/archives/c81d17c7-ff14-4fb3-94aa-2016df345df0" width="1" height="1" alt="" style="opacity:0;">
<p>系统环境：Centos7</p>
<p>Nacos版本：1.4.1—-<a href="https://objects.githubusercontent.com/github-production-release-asset-2e65be/137451403/e86c1e80-571d-11eb-9c3b-749b45cafe90?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=releaseassetproduction%2F20241006%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20241006T122443Z&amp;X-Amz-Expires=300&amp;X-Amz-Signature=f2151e0de545c9ab5e26601e025d6690b9578a8771ea043bfc759d41a320a7ac&amp;X-Amz-SignedHeaders=host&amp;response-content-disposition=attachment%3B%20filename%3Dnacos-server-1.4.1.tar.gz&amp;response-content-type=application%2Foctet-stream" title="下载地址">下载地址</a></p>
<p>Nginx版本：1.27.2—<a href="https://nginx.org/download/nginx-1.27.2.tar.gz" title="下载地址">下载地址</a></p>
<p>Mysql版本：8.0.39—<a href="https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-linux-glibc2.28-x86_64.tar.xz" title="下载地址">下载地址</a></p>
<p>Maven版本：3.2.2—<a href="https://archive.apache.org/dist/maven/binaries/apache-maven-3.2.2-bin.tar.gz" title="下载地址">下载地址</a></p>
<p>Nginx安装步骤—<a href="https://blog.csdn.net/qq_33384191/article/details/141170090" title="CentOS7安装nginx【只看这一篇就够了】_centos7 安装nginx-CSDN博客">CentOS7安装nginx【只看这一篇就够了】_centos7 安装nginx-CSDN博客</a></p>
<p>MySql安装步骤—<a href="https://blog.csdn.net/xujianflying/article/details/129647318" title="MySQL 8 安装教程（CentOS7）_centos7安装mysql8-CSDN博客">MySQL 8 安装教程（CentOS7）_centos7安装mysql8-CSDN博客</a></p>
<p>Maven安装步骤</p>
<p>解压文件 tar xzf apache-maven-3.2.2-bin.tar.gz -C /opt</p>
<p>创建sh文件，并添加内容 vi /etc/profile.d/maven.sh</p>
<p>export M2_HOME=/opt/apache-maven-3.2.2-bin</p>
<p>export PATH=${M2_HOME}/bin:${PATH}</p>
<p>使sh文件可执行并加载新的环境变量</p>
<p>sudo chmod +x /etc/profile.d/maven.sh</p>
<p>source /etc/profile.d/maven.sh</p>
<p>验证maven环境</p>
<p>mvn -version</p>
<p>Nacos集群配置</p>
<p>解压nacos文件</p>
<p>修改conf/application.properties</p>
<p>修改startup.sh</p>
<p>在conf中增加cluster.conf文件</p>
<p>配置完之后，使用bin/startup.sh启动</p>
<p>原文链接: https://blog.csdn.net/Dragonlongbo/article/details/142747968</p>]]></description><guid isPermaLink="false">/archives/c81d17c7-ff14-4fb3-94aa-2016df345df0</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Tue, 8 Oct 2024 03:26:11 GMT</pubDate></item><item><title><![CDATA[计算机网络：物理层 —— 数据的传输方式]]></title><link>https://wiki.dgfygk.com/archives/428e561d-2f52-448a-8be5-8799558e1a13</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%EF%BC%9A%E7%89%A9%E7%90%86%E5%B1%82%20%E2%80%94%E2%80%94%20%E6%95%B0%E6%8D%AE%E7%9A%84%E4%BC%A0%E8%BE%93%E6%96%B9%E5%BC%8F&amp;url=/archives/428e561d-2f52-448a-8be5-8799558e1a13" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#_3">传输方式</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_5">串行传输</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_9">串行传输方式</a></li>
   <li><a href="https://wiki.dgfygk.com#_15">特点</a></li>
   <li><a href="https://wiki.dgfygk.com#_23">应用</a></li>
   <li><a href="https://wiki.dgfygk.com#_31">并行传输</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_41">特点</a></li>
   <li><a href="https://wiki.dgfygk.com#_51">应用</a></li>
   <li><a href="https://wiki.dgfygk.com#_57">网卡的串/并转换</a></li>
   <li><a href="https://wiki.dgfygk.com#_64">同步传输</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_70">同步时钟频率的误差问题</a></li>
   <li><a href="https://wiki.dgfygk.com#_82">特点</a></li>
   <li><a href="https://wiki.dgfygk.com#_90">应用</a></li>
   <li><a href="https://wiki.dgfygk.com#_98">异步传输</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_106">特点</a></li>
   <li><a href="https://wiki.dgfygk.com#_114">应用</a></li>
   <li><a href="https://wiki.dgfygk.com#_122">单向通信</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_126">特点</a></li>
   <li><a href="https://wiki.dgfygk.com#_136">应用</a></li>
   <li><a href="https://wiki.dgfygk.com#_146">双向交替通信</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_154">特点</a></li>
   <li><a href="https://wiki.dgfygk.com#_164">应用</a></li>
   <li><a href="https://wiki.dgfygk.com#_176">双向同时通信</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_182">特点</a></li>
   <li><a href="https://wiki.dgfygk.com#_192">应用</a></li>
  </ul></li>
</ul>
<p>
 <br>
</p>
<h3>传输方式</h3>
<h4>串行传输</h4>
<p>串行传输是一种数据传输方式，指的是<strong>逐位地按照顺序传输数据</strong>。在串行传输中，数据位逐个按照一定的顺序进行传输，可以通过单条线路或信道进行。</p>
<h5>串行传输方式</h5>
<ul>
 <li>
  <p><strong>逐位传输</strong>：串行传输以逐位的方式传输数据，将数据位一个接一个地发送或接收。</p>
 </li>
 <li>
  <p><strong>时序依赖</strong> ：由于数据位逐个传输，发送端和接收端的时钟信号需要同步，确保数据的正确传输和解析。因此，串行传输中常常需要引入<strong>时钟同步机制</strong>。</p>
 </li>
</ul>
<h5>特点</h5>
<ul>
 <li>
  <p><strong>线路简化</strong>：相比并行传输，串行传输通常只需要使用一条线路进行数据传输，减少了线束的复杂性和电路数量。这使得串行传输适用于长距离传输和高速传输。</p>
 </li>
 <li>
  <p><strong>成本和空间节省</strong>：由于只需要较少的线路和接口，串行传输在成本和空间上相对较优。</p>
 </li>
 <li>
  <p><strong>灵活性</strong>：串行传输在布线和控制上相对简单，可以适应复杂系统的连接需求。</p>
 </li>
</ul>
<h5>应用</h5>
<ul>
 <li>
  <p><strong>串行接口</strong>：串行传输常用于各种串行接口，如串口（如RS-232/RS-485）、USB（Universal Serial Bus）、Ethernet 等。这些接口通常用于计算机、通信设备和外部设备之间的数据传输。</p>
 </li>
 <li>
  <p><strong>高速通信</strong>：串行传输在高速通信领域具有广泛应用，如光纤通信、串行ATA（SATA）等。由于串行传输可以实现较高的传输速率，适用于长距离和大带宽的数据传输。</p>
 </li>
 <li>
  <p><strong>无线通信</strong>：在无线通信中，串行传输常用于无线模块和传感器之间的数据传输，如蓝牙、Zigbee等。</p>
 </li>
</ul>
<h4>并行传输</h4>
<p>并行传输是另一种数据传输方式，与串行传输相对。在并行传输中，<strong>数据的多位同时传输，每个数据位都使用单独的线路进行传输</strong>。</p>
<p>并行传输通常需要在发送端和接收端之间建立严格的时钟同步，以确保数据位的同步传输。<strong>发送端和接收端的时钟信号需要保持一致</strong>，以便同时将多个数据位传输。</p>
<p><strong>若比特在单条数据传输线路上的数据传输速率相同，则并行传输的数据传输速率为串行传输的数据传输速率的 n 倍</strong> （n 为并行传输所采用的传输线路的数量，即<strong>数据总线宽度</strong>）</p>
<p>常用的数据总线宽度有 <code>8位</code>，<code>16位</code>，<code>32位</code>，<code>64位</code></p>
<h5>特点</h5>
<ul>
 <li>
  <p><strong>高速传输</strong>：由于并行传输可以同时传输多个数据位，因此在相同的传输时间内，可以传输更多的数据量，达到更高的传输速率。</p>
 </li>
 <li>
  <p><strong>复杂性</strong>：由于每个数据位都需要使用单独的线路进行传输，因此并行传输的线束和接口相对较为复杂，会增加成本和布线难度。一般仅用于短距离传输</p>
 </li>
</ul>
<p><strong>并行传输在需要高数据吞吐量和低延迟的应用中具有优势</strong>。它可以同时传输多个数据位，实现高速数据传输和并行计算。</p>
<p>由于并行传输需要使用多条线路，并且要求发送端和接收端的时钟同步，因此会增加成本、复杂性和对系统协同性能的要求。</p>
<h5>应用</h5>
<ul>
 <li>
  <p><strong>并行接口</strong>：并行传输常用于各种并行接口，例如并行打印口（Centronics）、并行总线（如PCI）、并行存储器接口等。这些接口通常用于高速数据传输和连接外设，可以同时传输多个数据位和控制信号。</p>
 </li>
 <li>
  <p><strong>高性能计算</strong>：在高性能计算领域，例如超级计算机、并行处理器等，也常使用并行传输来实现高速数据传输和协同计算。</p>
 </li>
</ul>
<h4>网卡的串/并转换</h4>
<p>计算机中的网卡同时具有串行传输和并行传输方式，当计算机通过其内部的网卡，将数据<strong>发送</strong> 到传输线路上时，网卡进行<strong>并/串转换</strong> ；当计算机通过网卡从传输线路上<strong>接收</strong> 数据时，网卡进行<strong>串/并转换</strong>。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F27d8fe6c6ce34fceb2a25d784a26383c.png&amp;size=m" alt=""></p>
<h4>同步传输</h4>
<p>同步传输是一种数据传输方式，指的是<strong>数据的发送和接收端在数据传输过程中保持同步</strong>，以确保数据的正确传输和解析。</p>
<p>同步传输在数据通信和系统互联中具有重要作用。它<strong>通过保持发送和接收端的时序一致，达到高效、可靠和准确的数据传输</strong>。在选择同步传输方式时，需要考虑传输的距离、数据速率、传输环境和具体需求等因素。</p>
<h5>同步时钟频率的误差问题</h5>
<p>在不采取其他措施的情况下，发送与接收双方的同步时钟频率无法达到严格同步，在数据传输过程中，会导致收发双方时钟频率的误差积累。传输大量数据时，会造成比特信号采样时刻的严重偏移，导致接收方对比特信号的误判。</p>
<p>为实现收发双方的时钟同步，可以采用如下方法：</p>
<ul>
 <li>
  <p><strong>外同步</strong> ：在收发双方之间增加一条时钟信号线。发送端在发送数据信号时还要发送一路<strong>时钟信号</strong>，同步数据传输的节奏和速率。</p>
  <p>通过保持时钟信号的一致性，确保发送和接收端的数据处理步调一致，使数据能够准确无误地传输。</p>
 </li>
 <li>
  <p><strong>内同步</strong>：发送端将时钟信号编码到发送数据中一起逐步发送（例如曼彻斯特编码），而接收端根据同步时钟来接收和解析数据。</p>
 </li>
</ul>
<h5>特点</h5>
<ul>
 <li>
  <p><strong>高效可靠</strong>：同步传输能够保持数据传输的高效性和可靠性，因为发送和接收端的数据处理步调同步，减少了数据传输中的误码率和丢失率。</p>
 </li>
 <li>
  <p><strong>时序一致</strong>：在同步传输中，数据发送和接收的时序是一致的，保持了数据的完整性和准确性。</p>
 </li>
 <li>
  <p><strong>速率可控</strong>：同步传输允许发送端和接收端在传输过程中对传输速率进行控制和协商，以适应不同的传输需求。</p>
 </li>
</ul>
<h5>应用</h5>
<ul>
 <li>
  <p><strong>同步串行接口</strong> ：同步串行接口（Synchronous Serial Interface）是一种常见的同步传输方式，如 <code>I2C、SPI、S/PDIF</code> 等。这些接口通常用于短距离的数据传输，如芯片之间的通信和外设的连接。</p>
 </li>
 <li>
  <p><strong>同步通信协议</strong> ：在通信中，同步传输常用于数据通信协议中的数据帧传输，如同步通信协议 <code>HDLC、SDLC、PPP</code> 等。这些协议通过统一的时钟信号来控制数据的传输和解析，确保数据的可靠传输。</p>
 </li>
 <li>
  <p><strong>同步串行总线</strong> ：同步串行总线如 <code>PCI Express</code>（PCIe）和 <code>USB 3.0</code> 都是同步传输的标准，它们通过提供同步时钟来控制数据的传输速度和节奏，以满足高速数据传输和设备互联的需求。</p>
 </li>
</ul>
<h4>异步传输</h4>
<p>异步传输与同步传输相对。在异步传输中，数据的发送和接收端不需要保持严格的时钟同步，而是在<strong>每个数据字节之间使用起始位和停止位来定界和同步数据</strong>。</p>
<p><strong>字节之间异步，即字节之间的时间间隔不固定。字节内的每个比特仍然要同步，即各比特的持续时间是相同的</strong> 。这种方式允许<strong>发送和接收端在不同的时钟频率下操作</strong>，提供了更大的灵活性和兼容性。</p>
<p>异步传输相对于同步传输来说，由于需要在每个数据字节之间包含起始位和停止位，<strong>传输的效率可能会稍低</strong>。同时，异步传输还需要保持发送和接收端的波特率（Baud Rate）一致，以确保数据的有效传输和解析。</p>
<h5>特点</h5>
<ul>
 <li>
  <p><strong>起始位与停止位</strong>：在异步传输中，每个数据字节的传输都以一个起始位来标识数据的开始，并以一个或多个停止位来标识数据的结束。这些起始位和停止位提供了传输数据的定界和同步信号，使接收端能够正确解析数据。</p>
 </li>
 <li>
  <p><strong>逐字节传输</strong>：异步传输是逐字节地传输数据，不需要在整个数据流中保持严格的时钟同步。这使得异步传输更加灵活和适应不同设备间的传输速率差异。</p>
 </li>
 <li>
  <p><strong>低复杂性</strong>：相比同步传输，异步传输不需要建立严格的时钟同步，减少了传输设备和电路的复杂性和成本。</p>
 </li>
</ul>
<h5>应用</h5>
<ul>
 <li>
  <p><strong>串口通信</strong> ：异步传输常用于串行通信接口，如 <code>RS-232</code> 和 <code>RS-485</code> 等。这些接口通常用于短距离的数据通信，如计算机与外围设备（如调制解调器、打印机、传感器等）之间的通信。</p>
 </li>
 <li>
  <p><strong>无线通信</strong>：在无线通信中，异步传输常用于小数据传输，如蓝牙、红外线通信等。这些技术使用异步传输来实现设备间的简单数据交换。</p>
 </li>
 <li>
  <p><strong>图形用户界面</strong>：异步传输也常用于计算机的图形用户界面（GUI），例如在用户输入文本时，异步传输可以实时地将按键字符传送到计算机系统中。</p>
 </li>
</ul>
<h4>单向通信</h4>
<p>单向通信（单工通信）是一种数据传输方式，表示<strong>数据流只在一个方向上进行传输</strong> 。在单向通信中，<strong>信息只能从发送方传输到接收方</strong>，没有反向的数据流，只需要一条信道。</p>
<h5>特点</h5>
<ul>
 <li>
  <p><strong>单向流动</strong>：数据流只在一个方向上进行传输，发送方向接收方。</p>
 </li>
 <li>
  <p><strong>无反馈</strong>：由于没有反向的数据流，发送方无法获得关于数据是否已经被成功接收的确认信息。</p>
 </li>
 <li>
  <p><strong>简单性</strong>：相对于双向通信，单向通信在协议设计和实现上较为简单。</p>
 </li>
</ul>
<p>单向通信在一些场景中可以提供简单且高效的解决方案，但也存在一些限制。由于缺乏反馈机制，发送方无法知晓数据是否已成功接收，可能需要采取其他机制来确保数据的完整性和可靠性。</p>
<h5>应用</h5>
<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>
</ul>
<h4>双向交替通信</h4>
<p>双向交替通信（半双工通信）是指的是<strong>数据在两个方向上进行传输，每个方向的数据传输是交替进行的</strong> ，<strong>发送和接收数据不能同时进行</strong>。</p>
<p>在双向交替通信中，<strong>发送方和接收方交替地在一定的时间间隔内发送和接收数据</strong>。这种通信模式可以实现双方之间的双向数据交互和实时通信。</p>
<p>在设计和实现双向交替通信时，需要考虑通信协议、时序同步、冲突检测和处理等方面的问题，以确保通信的可靠性和实时性。</p>
<h5>特点</h5>
<ul>
 <li>
  <p><strong>交替传输</strong>：数据在发送方和接收方之间交替传输，每个方向的数据传输是按照一定的时间序列进行的。</p>
 </li>
 <li>
  <p><strong>双向性</strong>：双向交替通信允许数据在两个方向上进行传输，发送方和接收方都可以发送和接收数据。</p>
 </li>
 <li>
  <p><strong>实时性</strong>：由于双向交替通信的特性，可以实现实时的数据交互，双方可以及时进行沟通和交流。</p>
 </li>
</ul>
<p>双向交替通信可以在实时通信和双向数据交互的场景中发挥作用。它允许双方交替地发送和接收数据，实现实时的双向通信。但需要确保双方之间的协调和同步，以避免冲突或数据丢失的问题。</p>
<h5>应用</h5>
<ul>
 <li>
  <p><strong>电话通信</strong>：传统电话通信就是一种双向交替通信的应用。在电话通话中，每个通话方都可以交替地发送和接收语音数据，实现双向的实时通信。</p>
 </li>
 <li>
  <p><strong>对讲机</strong>：对讲机是一种常见的双向通信设备，用户可以交替地按下对讲按钮来发送和接收信息，实现实时的双向交流。</p>
 </li>
 <li>
  <p><strong>无线电通信</strong>：无线电通信中，例如无线电对讲机和无线电调度系统，也常使用双向交替通信模式来实现双方的双向通信。</p>
 </li>
 <li>
  <p><strong>数据链路通信</strong>：在数据通信中，例如以太网和无线局域网（WLAN），双向交替通信被用于数据链路的双工通信，实现数据的双向传输和交互。</p>
 </li>
 <li>
  <p><strong>单工电台</strong>：单工指的是双向交替通信</p>
 </li>
</ul>
<h4>双向同时通信</h4>
<p>双向同时通信（双工通信）是指的是<strong>数据在两个方向上同时进行传输</strong> ，<strong>发送方和接收方可以同时发送和接收数据</strong>。</p>
<p>在双向同时通信中，发送方和接收方可以同时进行数据传输，而不需要等待对方的响应。这种通信模式可以实现<strong>双向的高效数据交互和实时通信</strong>。</p>
<h5>特点</h5>
<ul>
 <li>
  <p><strong>同时传输</strong>：数据在发送方和接收方之间同时传输，双方可以同时发送和接收数据，不需要等待对方的响应。</p>
 </li>
 <li>
  <p><strong>双向性</strong>：双向同时通信允许数据在两个方向上进行传输，发送方和接收方都可以同时进行数据传输。</p>
 </li>
 <li>
  <p><strong>高效性</strong>：由于双向同时通信的特性，可以实现双方之间的高效数据交互，提高通信效率。</p>
 </li>
</ul>
<p>双向同时通信可以在需要高效双向数据传输和实时通信的场景中发挥作用。它允许双方同时进行数据传输，提高通信效率和实时性，但也需要考虑数据冲突、资源竞争和协调等问题，确保数据传输的可靠性和正确性。</p>
<h5>应用</h5>
<ul>
 <li>
  <p><strong>实时视频通信</strong>：在实时视频通信中，例如视频会议和视频聊天应用，双方可以同时进行视频和音频数据的传输和接收，实现实时的双向通信。</p>
 </li>
 <li>
  <p><strong>即时通讯</strong>：即时通讯应用，如即时消息、聊天应用等，可以利用双向同时通信实现用户之间的快速消息交流。</p>
 </li>
 <li>
  <p><strong>VoIP通信</strong>：在语音通信中，例如利用 VoIP（Voice over Internet Protocol）进行电话或语音通话时，双方可以同时进行语音数据传输，实现双向的实时通信。</p>
 </li>
 <li>
  <p><strong>收发器通信</strong>：在一些特殊的通信设备中，例如收发器，双向同时通信被用于同时发送和接收数据，实现双向通信。</p>
 </li>
</ul>
<p>原文链接: https://blog.csdn.net/Zachyy/article/details/142741702</p>]]></description><guid isPermaLink="false">/archives/428e561d-2f52-448a-8be5-8799558e1a13</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 8 Oct 2024 03:26:00 GMT</pubDate></item><item><title><![CDATA[缓存基础----本地缓存、分布式缓存以及多级缓存]]></title><link>https://wiki.dgfygk.com/archives/f49f0398-bae9-49ba-a466-c1512461b840</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BC%93%E5%AD%98%E5%9F%BA%E7%A1%80----%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BC%93%E5%AD%98%E4%BB%A5%E5%8F%8A%E5%A4%9A%E7%BA%A7%E7%BC%93%E5%AD%98&amp;url=/archives/f49f0398-bae9-49ba-a466-c1512461b840" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E7%BC%93%E5%AD%98%E7%9A%84%E6%A6%82%E5%BF%B5%EF%BC%88%E4%BB%80%E4%B9%88%E6%98%AF%E7%BC%93%E5%AD%98%EF%BC%89">一、缓存的概念（什么是缓存）</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8%E7%BC%93%E5%AD%98%EF%BC%88%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8redis%E4%BD%9C%E4%B8%BA%E7%BC%93%E5%AD%98%EF%BC%89">二、为什么要用缓存（为什么要用redis作为缓存）</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E7%BC%93%E5%AD%98%E7%9A%84%E5%88%86%E7%B1%BB%E6%9C%89%E5%93%AA%E4%BA%9B">三、缓存的分类有哪些</a></p>
<p><a href="https://wiki.dgfygk.com#1%E3%80%81%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98">1、本地缓存</a></p>
<p><a href="https://wiki.dgfygk.com#2%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E7%BC%93%E5%AD%98">2、分布式缓存</a></p>
<p><a href="https://wiki.dgfygk.com#3%E3%80%81%E5%A4%9A%E7%BA%A7%E7%BC%93%E5%AD%98">3、多级缓存</a></p>
<hr>
<p>像MySql等传统的关系型数据库已经不能适用于所有的业务场景，比如电商系统的秒杀场景，APP首页的访问流量高峰场景，很容易造成关系型数据库的瘫痪，随着缓存技术的出现很好的解决了这个问题。</p>
<h3>一、缓存的概念（什么是缓存）</h3>
<p>缓存就是把访问量较高的热点数据从传统的关系型数据库中加载到内存中，当用户再次访问热点数据时是从内存中加载，减少了对数据库的访问量，解决了高并发场景下容易造成数据库宕机的问题。</p>
<h3>二、为什么要用缓存（为什么要用redis作为缓存）</h3>
<p>针对于这个问题要从两个方面去考虑，一个是应用系统的高并发场景，另一个就是应用系统的高性能情况。</p>
<p>1、高性能情况：用户第一次访问数据时，缓存中没有数据，要从数据库中获取数据，因为是从磁盘中拿数据，读取数据的过程比较慢。拿到数据后将数据存储在缓存中，用户第二次访问数据时，可以从缓存中直接获取，因为缓存是直接操作内存的，访问数据速度比较快。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4bba0f0acfa2935cd8449ef4abf635c6.png&amp;size=m" alt=""></p>
<p>2、高并发场景下：操作缓存能够承受的并发访问量是远远大于访问数据库的，比如redis，它的读的速度是110000次/s，写的速度是81000次/s。所以说将数据库中访问量高的数据存储到缓存中，用户请求的时候直接访问数据库，不必访问数据库，提高应用程序的并发量。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8196f8b9f40ba6e10bc81c819645be4b.png&amp;size=m" alt=""></p>
<h3>三、缓存的分类有哪些</h3>
<p>缓存基本上分为三类：本地缓存、分布式缓存、多级缓存。根据缓存和应用程序是否属于同一个进程，将缓存分为本地缓存和分布式缓存。基于本地缓存和分布式缓存都有各自的优点和缺点，后面又出现了多级缓存的概念。</p>
<h4>1、本地缓存</h4>
<p><strong>本地缓存的概念：</strong>本地缓存是指和应用程序在同一个进程内的内存空间去存储数据，数据的读写都是在同一个进程内完成的。</p>
<p><strong>本地缓存优点：</strong>读取速度快，但是不能进行大数据量存储。</p>
<p>本地缓存不需要远程网络请求去操作内存空间，没有额外的性能消耗，所以读取速度快。但是由于本地缓存占用了应用进程的内存空间，比如java进程的jvm内存空间，故不能进行大数据量存储。</p>
<p><strong>本地缓存缺点</strong>：</p>
<p>（1）、应用程序集群部署时，会存在数据更新问题（数据更新不一致）</p>
<p>本地缓存一般只能被同一个应用进程的程序访问，不能被其他应用程序进程访问。在单体应用集群部署时，如果数据库有数据需要更新，就要同步更新不同服务器节点上的本地缓存的数据来保证数据的一致性，但是这种操作的复杂度高，容易出错。可以基于redis的发布/订阅机制来实现各个部署节点的数据同步更新。</p>
<p>（2）、数据会随着应用程序的重启而丢失</p>
<p>因为本地缓存的数据是存储在应用进程的内存空间的，所以当应用进程重启时，本地缓存的数据会丢失。</p>
<p><strong>本地缓存的实现：</strong></p>
<p>（1）、缓存存储的数据一般都是key-value键值对的数据结构，在java语言中，常用的字典实现包括 HashMap 和 ConcurretHashMap。</p>
<p>（2）、除了上面说的实现方式以外，也可以用Guava、Ehcache以及Caffeine等封装好的工具包来实现本地缓存。</p>
<h4>2、分布式缓存</h4>
<p><strong>分布式缓存概念：</strong>分布式缓存是独立部署的服务进程，并且和应用程序没有部署在同一台服务器上。所以是需要通过远程网络请求来完成分布式缓存的读写操作，并且分布式缓存主要应用在应用程序集群部署的环境下。</p>
<p><strong>分布式缓存优点：</strong></p>
<p>（1）、支持大数据量存储</p>
<p>分布式缓存是独立部署的进程，拥有自身独自的内存空间，不需要占用应用程序进程的内存空间，并且还支持横向扩展的集群方式部署，所以可以进行大数据量存储。</p>
<p>（2）、数据不会随着应用程序重启而丢失</p>
<p>分布式缓存和本地缓存不同，拥有自身独立的内存空间，不会受到应用程序进程重启的影响，在应用程序重启时，分布式缓存的存储数据仍然存在。</p>
<p>（3）、数据集中存储，保证数据的一致性</p>
<p>当应用程序采用集群方式部署时，集群的每个部署节点都有一个统一的分布式缓存进行数据的读写操作，所以不会存在像本地缓存中数据更新问题，保证了不同服务器节点的 数据一致性。</p>
<p>（4）、数据读写分离，高性能，高可用</p>
<p>分布式缓存一般支持数据副本机制，实现读写分离，可以解决高并发场景中的数据读写性能问题。而且在多个缓存节点冗余存储数据，提高了缓存数据的可用性，避免某个缓存节点宕机导致数据不可用问题。</p>
<p><strong>分布式缓存缺点：</strong></p>
<p>（1）、数据跨网络传输，读写性能不如本地缓存</p>
<p>分布式缓存是一个独立的服务进程，并且和应用程序进程不在同一台机器上，所以数据的读写要通过远程网络请求，这样相对于本地缓存的数据读写，性能要低一些。</p>
<p><strong>分布式缓存的实现：</strong></p>
<p>分布式缓存的典型实现包括 MemCached 和 Redis。</p>
<h4>3、多级缓存</h4>
<p>基于本地缓存和分布式缓存的优缺点，多级缓存应运而生，在实际的业务开发中一般也是采用多级缓存。注意：本地缓存一般存储更新频率低，访问频率高数据，分布式缓存一般存储更新频率很高的数据。</p>
<p><strong>多级缓存的请求流程：</strong>本地缓存作为一级缓存，分布式缓存作为二级缓存；当用户获取数据时，先从一级缓存中获取数据，如果一级缓存有数据则返回数据，否则从二级缓存中获取数据。如果二级缓存中有数据则更新一级缓存，然后将数据返回客户端。如果二级缓存没有数据则去数据库查询数据，然后更新二级缓存，接着再更新一级缓存，最后将数据返回给客户端。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F769c72851c214c4eab70c51e1c446a8a.png&amp;size=m" alt=""></p>
<p><strong>多级缓存的实现：</strong>可以使用Guava或者Caffeine作为一级缓存，Redis作为二级缓存。</p>
<p>注意：在应用程序集群部署时，如果数据库的数据有更新的情况，一级缓存的数据更新容易出现数据不一致的情况。因为是集群部署，多个部署节点实现一级缓存数据更新难度比较大，不过我们可以通过Redis的消息发布/订阅机制来实现多个节点缓存数据一致性问题。</p>
<p>原文链接: https://blog.csdn.net/you18131371836/article/details/118212987</p>]]></description><guid isPermaLink="false">/archives/f49f0398-bae9-49ba-a466-c1512461b840</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Tue, 8 Oct 2024 03:25:38 GMT</pubDate></item><item><title><![CDATA[java 获取redis缓存中的数据]]></title><link>https://wiki.dgfygk.com/archives/6f406460-89bc-4f79-9747-355a5c7abdd7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%20%E8%8E%B7%E5%8F%96redis%E7%BC%93%E5%AD%98%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE&amp;url=/archives/6f406460-89bc-4f79-9747-355a5c7abdd7" width="1" height="1" alt="" style="opacity:0;">
<p>编写redis工具类</p>
<pre><code class="java">package com.chuangci.cxcsp.common.utils;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @Description Redis操作工具类
 * https://blog.csdn.net/qq_37132495/article/details/114692613
 * @Date 2021-07-01 14:04
 * @Created by Lxq
 */
@Component
public final class RedisUtils {

    @Autowired
    private RedisTemplate&lt;Object, Object&gt; redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time) {
        try {
            if (time &gt; 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null &amp;&amp; key.length &gt; 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection&lt;String&gt;) CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {
        try {
            if (time &gt; 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta &lt; 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta &lt; 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map&lt;Object, Object&gt; hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map&lt;String, Object&gt; map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map&lt;String, Object&gt; map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time &gt; 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time &gt; 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public Set&lt;Object&gt; sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time &gt; 0){
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List&lt;Object&gt; lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index&gt;=0时， 0 表头，1 第二个元素，依次类推；index&lt;0时，-1，表尾，-2倒数第二个元素，依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time &gt; 0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List&lt;Object&gt; value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List&lt;Object&gt; value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time &gt; 0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }

}
</code></pre>
<p>注解redis实例</p>
<pre><code class="java">    @Autowired
    private RedisUtils redisUtils;
</code></pre>
<pre><code>```java
redisUtils.get(balance:orgId:+"orgid");
</code></pre>
<p>调用redis工具类中的get方法，根据key获取对应的缓存数据值</p>
<p>如果经常用到redis做缓存，可以写一个key持久类来获取key，这样便于后期阅读</p>
<pre><code class="java">redisUtils.get(SmsCacheConst.BALANCE_ORG_ID);
</code></pre>
<pre><code>```java
package com.chuangci.cxcsp.smsapi.constant;

/**
 * @Description SMS模块缓存键值常量
 * @Date 2021-06-01 18:26
 * @Created by Lxq
 */
public class SmsCacheConst {

    /**商家应用信息缓存键值*/
    //public static final String CLINETAPP_APPID = "mms:clientApp:appid";
    public static final String CLINETAPP_ID = "mms:clientApp:id";
    /**通道缓存键值*/
    public static final String CHANNEL_ID = "mms:masGdZw:channel";
    /**缓存中的黑名单库*/
    public static final String BLACK_IDCODE = "mms:black";
    /**缓存中的黑名单小库*/
    public static final String BLACK_MINI_IDCODE = "mms:blackMini";


    /**Redis缓存中白名单号码的键值 */
    public static final String WHITE_MOBILES = "SmsWhiteMobile";

    /**子任务回调有关信息缓存
     * qw.select("id","id_send","org_id","client_app_id","submit_time");*/
    public static final String SENDSUB_SERIALNUM = "sendSub:serNum" ;

    /**
     * 机构所剩余额，需和机构ID组合使用
     */
    public static final String BALANCE_ORG_ID = "balance:orgId:";

    /**
     * 机构预付费返还数量，需和机构ID组合使用
     */
    public static final String BALANCE_RETURN_ORG_ID = "balance:return:orgId:";


    /**
     * 机构预付费返还数量，需和机构ID组合使用
     */
    public static final String BALANCE_SEND_ORG_ID = "balance:send:orgId:";


}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/m0_47104939/article/details/121747127</p>]]></description><guid isPermaLink="false">/archives/6f406460-89bc-4f79-9747-355a5c7abdd7</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 8 Oct 2024 03:25:24 GMT</pubDate></item><item><title><![CDATA[授权码机制 V2.1]]></title><link>https://wiki.dgfygk.com/archives/shou-quan-ma-ji-zhi-v2.1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8E%88%E6%9D%83%E7%A0%81%E6%9C%BA%E5%88%B6%20V2.1&amp;url=/archives/shou-quan-ma-ji-zhi-v2.1" width="1" height="1" alt="" style="opacity:0;">
<p style="">大家好，我是机灵鹤。</p>
<p style="line-height: 1.8em">根据读者朋友们反馈的问题和建议，对 <code>授权码 V2.0</code> 版本做了一些优化。</p>
<p style="line-height: 1.8em"><span fontsize="" color=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sjhl.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<p style="line-height: 1.8em">优化内容主要解决了以下几个问题：</p>
<ol>
 <li>
  <p style="">优化了授权机制中的时间校验逻辑，避免用户通过回调本地时间来绕过授权机制的问题。</p>
 </li>
 <li>
  <p style="">封装和简化了授权接口，开发者可以更方便地接入到自己的程序中。</p>
 </li>
</ol>
<h1 style="" id="1.-%E6%8E%88%E6%9D%83%E6%97%B6%E9%97%B4%E6%A0%A1%E9%AA%8C%E9%80%BB%E8%BE%91%E4%BC%98%E5%8C%96"><strong><span style="font-size: 20px; color: rgb(60, 112, 198)">1. 授权时间校验逻辑优化</span></strong></h1>
<p style="line-height: 1.8em">授权码机制 V2.0 是属于 <code>离线校验</code> 的授权机制。</p>
<p style="line-height: 1.8em">由于程序校验时，获取的是机器的 <code>本地时间</code>，所以用户在授权到期以后，<span style="font-size: 16px; color: rgb(74, 74, 74)">可以</span>通过回调本地时间，轻松绕过授权机制，继续使用软件。</p>
<p style="line-height: 1.8em">为此，我通过网上查阅资料，跟一些读者朋友交流讨论，找到了一个相对可行的解决方案。</p>
<p style="line-height: 1.8em">下面跟大家分享一下。</p>
<h2 style="" id="1.1-%E6%9C%89%E7%BD%91%E7%BB%9C%E6%97%B6%E4%BC%98%E5%85%88%E8%8E%B7%E5%8F%96%E5%9C%A8%E7%BA%BF%E6%97%B6%E9%97%B4"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">1.1 有网络时优先获取在线时间</span></strong></h2>
<p style="line-height: 1.8em">虽然授权码机制 V2.0 是离线校验，但是运行程序的电脑并不一定是断网的。</p>
<p style="line-height: 1.8em">所以，我们可以在有网络的情况下，优先使用在线时间，断网情况下，再使用本地时间。</p>
<p style="line-height: 1.8em">示例代码如下：</p>
<pre><code>import&nbsp;http.client
import&nbsp;time

def&nbsp;getTime():
&nbsp;&nbsp;&nbsp;&nbsp;try:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;使用网络时间
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;conn&nbsp;=&nbsp;http.client.HTTPConnection('www.baidu.com')
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;conn.request("GET",&nbsp;"/")
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;=&nbsp;conn.getresponse()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ts&nbsp;=&nbsp;&nbsp;r.getheader('date')
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ltime=&nbsp;time.strptime(ts[5:25],&nbsp;"%d&nbsp;%b&nbsp;%Y&nbsp;%H:%M:%S")
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;int(time.mktime(ltime)+8*60*60)
&nbsp;&nbsp;&nbsp;&nbsp;except:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;使用本地时间
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;int(time.time())
</code></pre>
<p style="line-height: 1.8em">网络时间从 <code>百度</code> 官方网站的响应头中获取。</p>
<p style="line-height: 1.8em">一来不用担心网站关停导致时间获取失败；二来也不用担心网站提供错误的时间数据。</p>
<h2 style="" id="1.2-%E6%8E%88%E6%9D%83%E6%96%87%E4%BB%B6%E8%AE%B0%E5%BD%95%E4%B8%8A%E6%AC%A1%E4%BD%BF%E7%94%A8%E6%97%B6%E9%97%B4"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">1.2 授权文件记录上次使用时间</span></strong></h2>
<p style="line-height: 1.8em">通过 1.1 的方法，我们可以在用户机器有网络的情况下，获取到准确的时间。</p>
<p style="line-height: 1.8em">但是如果用户运行程序前手动断网，或者电脑本身就不联网，该怎么办？</p>
<p style="line-height: 1.8em">为此，我们想到了，在授权文件中记录程序 <code>上次使用时间</code>，如果发现 <code>当前时间</code> 早于上次使用时间，则说明用户调整过本地时间。</p>
<p style="line-height: 1.8em">校验逻辑示意图如下：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-amkr.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.8em">在每次启动程序时，读取上次使用时间 <code>lastUseTime</code>，与当前时间 <code>curTime</code> 比较，</p>
<ul>
 <li>
  <p style="line-height: 1.8em">若 <code>curTime</code> 早于 <code>lastUseTime</code>，说明时间异常，用户有可能回调系统时间了。</p>
 </li>
 <li>
  <p style="line-height: 1.8em">若 <code>curTime</code> 晚于授权到期时间 <code>endTime</code>，说明授权到期了。</p>
 </li>
 <li>
  <p style="line-height: 1.8em">若 <code>curTime</code> 在 <code>lastUseTime ~ endTime</code> 之间，可以正常使用。此时将 <code>curTime</code> 写入配置，替换 <code>laseUseTime</code>。</p>
 </li>
</ul>
<p style="line-height: 1.8em">这样 <code>lastUseTime</code> <span style="font-size: 16px; color: rgb(74, 74, 74)">会不断逼近</span> <code>endTime</code> ，直到超过使用期限。</p>
<p style="line-height: 1.8em">虽然用户可以微调系统时间（每次调到不早于上次使用时间）延长使用时间，但总体来说，授权使用时间还是被限定在一个可控的范围内。</p>
<h2 style="" id="1.3-%E6%8E%88%E6%9D%83%E7%A0%81%E6%9C%AC%E8%BA%AB%E6%B7%BB%E5%8A%A0%E6%97%B6%E6%95%88"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">1.3 授权码本身添加时效</span></strong></h2>
<p style="line-height: 1.8em">聪明的你可能想到了。</p>
<p style="line-height: 1.8em">既然 <code>上次使用时间</code> 是记录在本地配置中的，那如果用户在授权到期以后，先删掉授权文件，再回调时间，然后用原先的授权码进行授权，岂不是又可以无限重复使用了？</p>
<p style="line-height: 1.8em">为了解决这个问题，我想到了 <code>给授权码本身添加时效</code>。</p>
<p style="line-height: 1.8em">简单来说，授权码中会额外携带一条时间信息：<code>授权码</code>到期时间。</p>
<p style="line-height: 1.8em">授权码到期时间控制的是授权码的有效时间，授权码需要在此之前使用，逾期失效，需要重新申请。</p>
<p style="line-height: 1.8em">举个例子：</p>
<blockquote>
 <p style="line-height: 1.8em">假设当前时间是 <code>2024-10-1 00:00:00</code>。</p>
 <p style="line-height: 1.8em">现在生成了一个授权码，这个授权码携带的时间信息为：</p>
 <ul>
  <li>
   <p style="">授权到期时间：<code>2024-11-1 00:00:00</code></p>
  </li>
  <li>
   <p style="">授权码到期时间：<code>2024-10-1 00:10:00</code></p>
  </li>
 </ul>
 <p style="line-height: 1.8em">也就是说，用户需要在 <code>10 分钟</code> 之内使用授权码激活程序，逾期授权码就会失效，需要重新申请。</p>
 <p style="line-height: 1.8em">而在成功授权激活后，用户可以在 <code>1 个月</code> 内正常使用程序。</p>
</blockquote>
<p style="line-height: 1.8em">这样，授权码添加有效期之后，用户也就无法在授权到期以后，使用原来的授权码重新激活使用了。</p>
<p style="line-height: 1.8em">以上方案虽然并不能保证可以彻底解决问题，但是也可以在一定程度上提高用户作弊的门槛了。</p>
<h1 style="" id="2.-%E5%BC%80%E5%8F%91%E8%80%85%E6%8E%A5%E5%8F%A3%E5%B0%81%E8%A3%85"><strong><span style="font-size: 20px; color: rgb(60, 112, 198)">2. 开发者接口封装</span></strong></h1>
<p style="line-height: 1.8em">不少读者跟我沟通和反馈过，就是授权码机制 V2.0 是挺好的，但是他们的程序各式各样，如何在他们的程序里无缝接入授权机制，却也难住了不少人。</p>
<p style="line-height: 1.8em">于是，我将之前的代码部分重构，封装好接口，提供给开发者调用。</p>
<h2 style="" id="2.1-%E6%9C%BA%E5%99%A8%E7%A0%81%E7%94%9F%E6%88%90%E6%8E%A5%E5%8F%A3"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">2.1 机器码生成接口</span></strong></h2>
<p style="line-height: 1.8em">机器码是通过 <code>主板序列号</code>，<code>Mac 地址</code>，<code>硬盘序列号</code>，<code>CPU序列号</code> 通过一定算法计算得到的。</p>
<p style="line-height: 1.8em">在跟读者的沟通中得知，他们的用户由于使用虚拟机/需要重装系统/更换硬盘等等情况，部分硬件参数会经常变动，导致机器码经常变化。</p>
<p style="line-height: 1.8em">所以在机器码生成接口中，我将四个参数设置成可选项，开发者可根据实际情况，选择合适的硬件参数来生成机器码。</p>
<p style="line-height: 1.8em"><strong>函数原型</strong>：</p>
<pre><code>def&nbsp;getMachineCode(useMac&nbsp;=&nbsp;True,&nbsp;useCpu&nbsp;=&nbsp;True,&nbsp;useDisk&nbsp;=&nbsp;True,&nbsp;useBoard&nbsp;=&nbsp;True)
</code></pre>
<p style="line-height: 1.8em"><strong>参数说明</strong>：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 931px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 831px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="100" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">参数</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="831" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">说明</p>
    </th>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">useMac</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="831" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">生成机器码时，是否使用 Mac 地址，默认为 True</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">useCpu</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="831" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">生成机器码时，是否使用 CPU 序列号，默认为 True</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">useDisk</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="831" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">生成机器码时，是否使用硬盘序列号，默认为 True</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">useBoard</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="831" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">生成机器码时，是否使用主板序列号，默认为 True</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="line-height: 1.8em">注：四个参数须至少有一个为 True，否则生成的机器码为固定值，无法与机器一一对应。</p>
<p style="line-height: 1.8em"><strong>返回说明</strong>：</p>
<p style="line-height: 1.8em">正常情况下，会返回一段 32 位长度的字符串，形如：<code>EE51BF4CADD3899E037C24F218CCFD8F</code> 。</p>
<h2 style="" id="2.2-%E6%8E%88%E6%9D%83%E6%A0%A1%E9%AA%8C%E6%8E%A5%E5%8F%A3"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">2.2 授权校验接口</span></strong></h2>
<p style="line-height: 1.8em">开发者可以调用此接口来进行授权校验。</p>
<p style="line-height: 1.8em">在校验成功的同时，会自动更新本地配置文件中的上次使用时间信息。</p>
<p style="line-height: 1.8em"><strong>函数原型</strong>：</p>
<pre><code>def&nbsp;CheckAuth()
</code></pre>
<p style="line-height: 1.8em"><strong>返回说明</strong>：</p>
<p style="line-height: 1.8em">校验通过时，函数会返回下述 Json 数据：</p>
<pre><code>{
&nbsp;&nbsp;&nbsp;&nbsp; "errCode":&nbsp;10000,
&nbsp;    "errInfo":&nbsp;"授权验证通过，欢迎使用。授权有效期截至：2024-10-03 14:30:41"
}
</code></pre>
<p style="line-height: 1.8em">校验失败时，会返回如下的 Json 数据。</p>
<pre><code>{
&nbsp;    "errCode":&nbsp;10001,
&nbsp;    "errInfo":&nbsp;"[错误码 10001]&nbsp;软件未授权，请使用授权码激活后使用。"
}
</code></pre>
<p style="line-height: 1.8em"><strong>参数说明</strong>：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 936px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 836px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="100" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">参数</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="836" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">说明</p>
    </th>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10000</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">成功</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10001</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">软件未授权</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10002</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权文件损坏</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10003</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权码与本机的机器码不匹配</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10004</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权码已过期</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10005</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">本地时间异常</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10006</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">网络请求超时</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10007</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权码解析失败</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10008</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="836" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">此授权码已失效</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h2 style="" id="2.3-%E6%8E%88%E6%9D%83%E6%BF%80%E6%B4%BB%E6%8E%A5%E5%8F%A3"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">2.3 授权激活接口</span></strong></h2>
<p style="line-height: 1.8em">开发者可以调用此接口来进行授权激活。</p>
<p style="line-height: 1.8em">在激活成功的同时，会在本地创建授权配置文件。</p>
<p style="line-height: 1.8em"><strong>函数原型</strong>：</p>
<pre><code>def&nbsp;Activate(activeCode)
</code></pre>
<p style="line-height: 1.8em"><strong>参数说明</strong>：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 907px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 807px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="100" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">参数</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="807" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">说明</p>
    </th>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">activeCode</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="807" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权码</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="line-height: 1.8em"><strong>返回说明</strong>：</p>
<p style="line-height: 1.8em">校验通过时，函数会返回下述 Json 数据：</p>
<pre><code>{
&nbsp;&nbsp;&nbsp;&nbsp; "errCode":&nbsp;10000,
&nbsp;    "errInfo":&nbsp;"授权验证通过，欢迎使用。授权有效期截至：2024-10-03 14:30:41"
}
</code></pre>
<p style="line-height: 1.8em">校验失败时，会返回如下的 Json 数据。</p>
<pre><code>{
&nbsp;    "errCode":&nbsp;10001,
&nbsp;    "errInfo":&nbsp;"[错误码 10001]&nbsp;软件未授权，请使用授权码激活后使用。"
}
</code></pre>
<p style="line-height: 1.8em"><strong>参数说明</strong>：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 972px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 872px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="100" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">参数</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="872" style="font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">
     <p style="">说明</p>
    </th>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10000</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">成功</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10001</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">软件未授权</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10002</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权文件损坏</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10003</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权码与本机的机器码不匹配</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10004</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权码已过期</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10005</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">本地时间异常</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10006</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">网络请求超时</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(248, 248, 248);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10007</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">授权码解析失败</p>
    </td>
   </tr>
   <tr style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 255, 255);width: auto;height: auto;">
    <td colspan="1" rowspan="1" colwidth="100" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">10008</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="872" style="min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">
     <p style="">此授权码已失效</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h1 style="" id="3.-%E5%9C%A8%E7%A8%8B%E5%BA%8F%E4%B8%AD%E6%8E%A5%E5%85%A5%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E7%A4%BA%E4%BE%8B"><strong><span style="font-size: 20px; color: rgb(60, 112, 198)">3. 在程序中接入授权机制示例</span></strong></h1>
<h2 style="" id="3.1-%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">3.1 示例代码</span></strong></h2>
<pre><code>#&nbsp;导入授权模块
from&nbsp;myAuthMgr&nbsp;import&nbsp;myAuthMgr,&nbsp;ErrorCode

def&nbsp;main():
&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;授权校验的逻辑写在最开始，校验通过了再执行你自己的代码
&nbsp;&nbsp;&nbsp;&nbsp;checkInfo&nbsp;=&nbsp;myAuthMgr.CheckAuth()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;调用校验接口，授权校验
&nbsp;&nbsp;&nbsp;&nbsp;print(checkInfo["errInfo"])
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;checkInfo["errCode"]&nbsp;!=&nbsp;ErrorCode.SUCCESS:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;返回码不是&nbsp;SUCCESS（10000），说明校验失败
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("请联系管理员获得授权码，机器码：",&nbsp;myAuthMgr.machine_code)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;机器码
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;keyCode&nbsp;=&nbsp;input("请输入激活码：")
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;activeInfo&nbsp;=&nbsp;myAuthMgr.Activate(keyCode)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;调用激活接口，进行授权激活
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print(activeInfo["errInfo"])
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;activeInfo["errCode"]&nbsp;!=&nbsp;ErrorCode.SUCCESS:&nbsp;&nbsp;#&nbsp;返回码不是&nbsp;SUCCESS（10000），说明激活失败
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return

&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;以下是你自己的程序代码
&nbsp;&nbsp;&nbsp;&nbsp;print("Hello&nbsp;World")


if&nbsp;__name__&nbsp;==&nbsp;"__main__":
&nbsp;&nbsp;&nbsp;&nbsp;main()
</code></pre>
<h2 style="" id="3.2-%E8%BF%90%E8%A1%8C%E6%95%88%E6%9E%9C"><strong><span style="font-size: 18px; color: rgb(60, 112, 198)">3.2 运行效果</span></strong></h2>
<p style="line-height: 1.8em">首次运行程序（未授权）。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ffnl.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.8em">输入授权码，激活成功。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-igcb.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.8em">再次启动程序，通过读取本地授权文件，校验通过。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lxvp.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.8em">回调本地时间后，识别到本地时间异常，校验失败。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dgus.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.8em">超过授权码的有效期，授权码失效，需要重新生成。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ttdh.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h1 style="" id=""></h1>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/shou-quan-ma-ji-zhi-v2.1</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 7 Oct 2024 02:28:17 GMT</pubDate></item><item><title><![CDATA[区块链的基本原理和优势]]></title><link>https://wiki.dgfygk.com/archives/09d1b391-7ded-4d38-bd74-6d7b115d576c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E5%92%8C%E4%BC%98%E5%8A%BF&amp;url=/archives/09d1b391-7ded-4d38-bd74-6d7b115d576c" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F63fc2f4cf6e8e917750fb791d5feb20b.png&amp;size=m" alt=""> <strong>人不走空</strong></p>
<p>
 <br>
</p>
<h3>🌈<strong>个人主页：</strong><a href="https://blog.csdn.net/double222222?type=blog" title="人不走空">人不走空</a><strong></strong></h3>
<h3><strong>💖系列专栏：</strong><a href="https://blog.csdn.net/double222222/category_12487653.html?spm=1001.2014.3001.5482" title="算法专题">算法专题</a><strong></strong></h3>
<h3><strong>⏰诗词歌赋：</strong>斯是陋室，惟吾德馨</h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0ec3abdfc89a8b19151ff2e81eccf218.gif&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc7bd4674d82bfbb6db1729f01e1a18f4.png&amp;size=m" alt=""></p>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%F0%9F%8C%88%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5%EF%BC%9A%E4%BA%BA%E4%B8%8D%E8%B5%B0%E7%A9%BA%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0">🌈个人主页：人不走空</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%92%96%E7%B3%BB%E5%88%97%E4%B8%93%E6%A0%8F%EF%BC%9A%E7%AE%97%E6%B3%95%E4%B8%93%E9%A2%98">💖系列专栏：算法专题</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%8F%B0%E8%AF%97%E8%AF%8D%E6%AD%8C%E8%B5%8B%EF%BC%9A%E6%96%AF%E6%98%AF%E9%99%8B%E5%AE%A4%EF%BC%8C%E6%83%9F%E5%90%BE%E5%BE%B7%E9%A6%A8">⏰诗词歌赋：斯是陋室，惟吾德馨</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86">一、区块链的基本原理</a></p>
<p><a href="https://wiki.dgfygk.com#1.1%20%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E7%BB%93%E6%9E%84">1.1 区块链的结构</a></p>
<p><a href="https://wiki.dgfygk.com#1.2%20%E5%8E%BB%E4%B8%AD%E5%BF%83%E5%8C%96">1.2 去中心化</a></p>
<p><a href="https://wiki.dgfygk.com#1.3%20%E5%8A%A0%E5%AF%86%E4%B8%8E%E5%AE%89%E5%85%A8">1.3 加密与安全</a></p>
<p><a href="https://wiki.dgfygk.com#1.4%20%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6%EF%BC%88Smart%20Contracts%EF%BC%89">1.4 智能合约（Smart Contracts）</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E4%BC%98%E5%8A%BF">二、区块链的优势</a></p>
<p><a href="https://wiki.dgfygk.com#2.1%20%E5%8E%BB%E4%B8%AD%E5%BF%83%E5%8C%96">2.1 去中心化</a></p>
<p><a href="https://wiki.dgfygk.com#2.2%20%E6%95%B0%E6%8D%AE%E9%80%8F%E6%98%8E%E5%92%8C%E4%B8%8D%E5%8F%AF%E7%AF%A1%E6%94%B9">2.2 数据透明和不可篡改</a></p>
<p><a href="https://wiki.dgfygk.com#2.3%20%E5%AE%89%E5%85%A8%E6%80%A7">2.3 安全性</a></p>
<p><a href="https://wiki.dgfygk.com#2.4%20%E9%AB%98%E6%95%88%E7%9A%84%E4%BA%A4%E6%98%93%E5%A4%84%E7%90%86">2.4 高效的交易处理</a></p>
<p><a href="https://wiki.dgfygk.com#2.5%20%E8%87%AA%E5%8A%A8%E5%8C%96%E5%92%8C%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6">2.5 自动化和智能合约</a></p>
<p><a href="https://wiki.dgfygk.com#2.6%20%E6%8F%90%E5%8D%87%E4%BF%A1%E4%BB%BB%E5%92%8C%E9%80%8F%E6%98%8E%E5%BA%A6">2.6 提升信任和透明度</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8">三、区块链的实际应用</a></p>
<p><a href="https://wiki.dgfygk.com#3.1%20%E9%87%91%E8%9E%8D%E6%9C%8D%E5%8A%A1">3.1 金融服务</a></p>
<p><a href="https://wiki.dgfygk.com#3.2%20%E4%BE%9B%E5%BA%94%E9%93%BE%E7%AE%A1%E7%90%86">3.2 供应链管理</a></p>
<p><a href="https://wiki.dgfygk.com#3.3%20%E5%8C%BB%E7%96%97%E5%81%A5%E5%BA%B7">3.3 医疗健康</a></p>
<p><a href="https://wiki.dgfygk.com#3.4%20%E6%94%BF%E5%BA%9C%E5%92%8C%E5%85%AC%E5%85%B1%E6%9C%8D%E5%8A%A1">3.4 政府和公共服务</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E5%8C%BA%E5%9D%97%E9%93%BE%E7%9A%84%E6%8C%91%E6%88%98%E4%B8%8E%E6%9C%AA%E6%9D%A5%E5%B1%95%E6%9C%9B">四、区块链的挑战与未来展望</a></p>
<p><a href="https://wiki.dgfygk.com#4.1%20%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7">4.1 可扩展性</a></p>
<p><a href="https://wiki.dgfygk.com#4.2%20%E8%83%BD%E6%BA%90%E6%B6%88%E8%80%97">4.2 能源消耗</a></p>
<p><a href="https://wiki.dgfygk.com#4.3%20%E6%B3%95%E8%A7%84%E5%92%8C%E5%90%88%E8%A7%84%E6%80%A7">4.3 法规和合规性</a></p>
<p><a href="https://wiki.dgfygk.com#4.4%20%E6%8A%80%E6%9C%AF%E5%92%8C%E5%AE%9E%E7%8E%B0%E5%A4%8D%E6%9D%82%E6%80%A7">4.4 技术和实现复杂性</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%E7%BB%93%E8%AE%BA">五、结论</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BD%9C%E8%80%85%E5%85%B6%E4%BB%96%E4%BD%9C%E5%93%81%EF%BC%9A">作者其他作品：</a></p>
<hr>
<p>
 <br>
</p>
<h4>一、区块链的基本原理</h4>
<p>区块链（Blockchain）是一种分布式账本技术（Distributed Ledger Technology, DLT），最早被比特币的创造者中本聪（Satoshi Nakamoto）在2008年的白皮书中提出。区块链通过去中心化的方式，确保数据在不同参与者之间的一致性和安全性。以下是区块链的核心组成部分及其工作原理。</p>
<h5>1.1 区块链的结构</h5>
<p>区块链由多个"区块”（Blocks）组成，这些区块按时间顺序线性连接形成一条链，每个区块包含了一组交易记录。</p>
<ul>
 <li><strong>区块</strong>：每个区块包含区块头和区块体。区块头包括区块的元数据，如前一个区块的哈希值（Hash）、时间戳和随机数（Nonce），这些信息确保了区块之间的连接和数据的不可篡改性。区块体则存储了交易数据。</li>
 <li><strong>链</strong>：区块通过哈希值连接，形成一个连续的链条。每个区块包含上一个区块的哈希值，这一特性确保了区块链的完整性和安全性，即使一个区块被篡改，整个链条都会受到影响，从而被发现。</li>
</ul>
<h5>1.2 去中心化</h5>
<p>传统的数据库系统通常依赖于中心化的服务器进行数据存储和管理，而区块链则采用去中心化的架构，所有的参与者（节点）都有一个完整的账本副本。</p>
<ul>
 <li><strong>节点（Nodes）</strong>：区块链网络由多个节点组成，每个节点都存储一个完整的区块链副本。所有节点协同工作以验证和记录新的交易。</li>
 <li><strong>共识机制（Consensus Mechanism）</strong>：为了保证数据在分布式网络中的一致性，区块链采用了共识机制（如工作量证明（Proof of Work, PoW）、权益证明（Proof of Stake, PoS）等），这些机制帮助节点在没有中心控制的情况下达成一致。</li>
</ul>
<h5>1.3 加密与安全</h5>
<p>区块链利用密码学技术确保数据的安全和隐私：</p>
<ul>
 <li><strong>哈希函数（Hash Function）</strong>：哈希函数将任意长度的输入数据转换成固定长度的输出（哈希值）。在区块链中，哈希值用于唯一标识每个区块，并确保数据不可篡改。</li>
 <li><strong>公钥和私钥（Public Key and Private Key）</strong>：区块链采用公钥加密系统，用户使用私钥进行签名以证明他们的身份和授权，公钥则用于验证签名的合法性。</li>
</ul>
<h5>1.4 智能合约（Smart Contracts）</h5>
<p>智能合约是一种运行在区块链上的自执行代码，它们自动执行和验证合约条款。智能合约的应用扩展了区块链的功能，超越了简单的价值转移，使其能够支持更复杂的应用场景。</p>
<h4>二、区块链的优势</h4>
<p>区块链技术之所以受到广泛关注，主要是因为其具备以下优势：</p>
<h5>2.1 去中心化</h5>
<p>区块链是一个去中心化的系统，这意味着没有单一的控制中心，数据由网络中的所有节点共同维护和管理。这种架构具有以下优点：</p>
<ul>
 <li><strong>抗审查性</strong>：由于没有中央机构控制，区块链上的数据不易被审查或操控。</li>
 <li><strong>降低信任成本</strong>：在传统系统中，参与者需要信任中央机构或第三方，但在区块链系统中，信任是由数学和共识机制保证的，这大大降低了信任成本。</li>
</ul>
<h5>2.2 数据透明和不可篡改</h5>
<p>区块链提供了一种透明且不可篡改的数据存储方式：</p>
<ul>
 <li><strong>透明性</strong>：所有的交易记录在区块链上是公开可见的，任何人都可以验证交易的合法性和完整性。</li>
 <li><strong>不可篡改性</strong>：区块链的结构和加密技术使得一旦数据被记录在区块上，就很难被篡改。要更改某个区块的数据，必须同时修改该区块之后所有区块的哈希值，这是极其困难且代价高昂的。</li>
</ul>
<h5>2.3 安全性</h5>
<p>区块链系统的安全性由其分布式架构和密码学技术保障：</p>
<ul>
 <li><strong>分布式存储</strong>：由于区块链的数据分布在多个节点上，即使某些节点受到攻击，数据仍然可以在其他节点上保持安全和完整。</li>
 <li><strong>加密技术</strong>：区块链广泛使用公钥加密、哈希函数等密码学技术来保护数据和交易的安全性。</li>
</ul>
<h5>2.4 高效的交易处理</h5>
<p>尽管区块链网络中所有节点都参与交易验证，但在实际应用中，区块链可以实现高效的交易处理，尤其是与传统的跨境支付和清算系统相比：</p>
<ul>
 <li><strong>无中介交易</strong>：区块链允许点对点的直接交易，减少了中介环节，从而降低了交易成本和时间。</li>
 <li><strong>快速结算</strong>：在区块链系统中，交易可以在几分钟内得到确认，这比传统金融系统的结算速度要快得多。</li>
</ul>
<h5>2.5 自动化和智能合约</h5>
<p>智能合约使区块链能够自动执行和验证复杂的交易和协议，具有以下优势：</p>
<ul>
 <li><strong>自动执行</strong>：智能合约可以在预定条件满足时自动执行，无需人为干预，这提高了效率和准确性。</li>
 <li><strong>减少纠纷</strong>：智能合约的条款和执行是透明且不可篡改的，这减少了合同执行中的争议和不确定性。</li>
</ul>
<h5>2.6 提升信任和透明度</h5>
<p>区块链的透明性和安全性使得它成为提升信任的理想工具：</p>
<ul>
 <li><strong>可信审计</strong>：区块链上的交易记录是公开且不可篡改的，外部审计人员可以轻松地验证交易的真实性和完整性。</li>
 <li><strong>增强合作</strong>：在供应链、金融和跨组织合作中，区块链可以提供透明且可信的数据共享平台，增强各方的合作意愿和效率。</li>
</ul>
<h4>三、区块链的实际应用</h4>
<p>区块链的独特优势使其在多个领域得到了广泛的应用：</p>
<h5>3.1 金融服务</h5>
<ul>
 <li><strong>跨境支付</strong>：区块链技术能够简化和加速跨境支付，降低汇款费用，并提高交易透明度。</li>
 <li><strong>去中心化金融（DeFi）</strong>：通过智能合约，区块链支持一系列金融服务，如贷款、借贷、交易和保险，消除了传统金融系统中的中介。</li>
</ul>
<h5>3.2 供应链管理</h5>
<ul>
 <li><strong>透明度和可追溯性</strong>：区块链能够记录产品从生产到销售的每个环节，提高供应链的透明度和可追溯性。</li>
 <li><strong>防伪和质量控制</strong>：通过区块链，可以验证产品的来源和真伪，有效防止假冒伪劣产品。</li>
</ul>
<h5>3.3 医疗健康</h5>
<ul>
 <li><strong>电子健康记录</strong>：区块链可以安全地存储和共享患者的健康记录，确保数据的隐私和完整性。</li>
 <li><strong>药品追踪</strong>：通过区块链，药品的生产和流通过程可以得到全程追踪，减少假药和过期药品的风险。</li>
</ul>
<h5>3.4 政府和公共服务</h5>
<ul>
 <li><strong>数字身份管理</strong>：区块链可以提供安全且不可篡改的数字身份验证，简化公民的身份认证流程。</li>
 <li><strong>透明选举系统</strong>：区块链能够提供安全和透明的电子投票系统，防止选票篡改和欺诈。</li>
</ul>
<h4>四、区块链的挑战与未来展望</h4>
<p>尽管区块链技术有很多优势，但它也面临一些挑战和问题：</p>
<h5>4.1 可扩展性</h5>
<p>当前的区块链系统，如比特币和以太坊，处理交易的速度和容量有限，难以支持大规模应用。</p>
<ul>
 <li><strong>交易吞吐量</strong>：比特币的交易处理能力大约为每秒7笔交易，而以太坊大约为每秒15笔交易，这远低于传统支付系统（如VISA）的每秒数千笔交易能力。</li>
 <li><strong>扩展解决方案</strong>：为解决可扩展性问题，出现了许多解决方案，如分片技术（Sharding）、闪电网络（Lightning Network）和侧链（Sidechains），这些技术正在逐步提高区块链的性能和可扩展性。</li>
</ul>
<h5>4.2 能源消耗</h5>
<p>区块链的共识机制，尤其是工作量证明（PoW），消耗大量能源，这引发了对环境影响的担忧。</p>
<ul>
 <li><strong>高能耗问题</strong>：比特币挖矿消耗了大量的电力资源，导致了碳排放和能源浪费的问题。</li>
 <li><strong>替代方案</strong>：为了减少能耗，许多区块链项目正在研究和采用更高效的共识机制，如权益证明（PoS）和拜占庭容错（BFT）算法。</li>
</ul>
<h5>4.3 法规和合规性</h5>
<p>区块链的匿名性和去中心化特性使其在监管和法律合规方面面临挑战。</p>
<ul>
 <li><strong>监管挑战</strong>：区块链交易的匿名性可能被用于洗钱和非法活动，这需要建立有效的监管机制来防范风险。</li>
 <li><strong>法律合规</strong>：不同国家和地区对区块链技术的法律框架各不相同，需要建立统一的标准和法规来确保全球区块链应用的合法合规性。</li>
</ul>
<h5>4.4 技术和实现复杂性</h5>
<p>区块链技术的开发和实现需要高水平的技术知识和经验，许多企业在实施区块链项目时面临技术挑战。</p>
<ul>
 <li><strong>技术复杂性</strong>：区块链系统的设计和开发涉及多种复杂的技术，包括密码学、分布式计算和智能合约开发。</li>
 <li><strong>实施成本</strong>：开发和部署区块链解决方案的成本较高，尤其是在涉及大规模企业应用时。</li>
</ul>
<h4>五、结论</h4>
<p>区块链技术作为一种革命性的创新，正在逐步改变各行各业的运作方式。它的去中心化、透明性和安全性为数据管理和交易处理带来了新的可能性。尽管在可扩展性、能耗和法规等方面面临挑战，但随着技术的不断进步和应用的日益广泛，区块链的未来发展前景仍然十分广阔。通过不断探索和创新，区块链有潜力在金融、供应链、医疗健康、政府服务等多个领域实现深远的变革和价值提升。</p>
<p>如果你有更多关于区块链或其他技术的兴趣或问题，欢迎随时提问！</p>
<p>
 <br>
</p>
<h4>参考文献</h4>
<ol>
 <li>Nakamoto, S. (2008). Bitcoin: A Peer-to-Peer Electronic Cash System. Retrieved from https://bitcoin.org/bitcoin.pdf</li>
 <li>Swan, M. (2015). Blockchain: Blueprint for a New Economy. O'Reilly Media.</li>
 <li>Tapscott, D., \&amp; Tapscott, A. (2016). Blockchain Revolution: How the Technology Behind Bitcoin Is Changing Money, Business, and the World. Portfolio.</li>
 <li>Mougayar, W. (2016). The Business Blockchain: Promise, Practice, and Application of the Next Internet Technology. Wiley.</li>
 <li>Antonopoulos, A. M. (2014). Mastering Bitcoin: Unlocking Digital Cryptocurrencies. O'Reilly Media.</li>
</ol>
<p>这些参考文献和资源为本文提供了全面和深入的见解，帮助我们更好地理解区块链技术的原理、优势和挑战。</p>
<hr>
<h2>作者其他作品：</h2>
<p><a href="https://cyberspace.blog.csdn.net/article/details/136338757?spm=1001.2014.3001.5502" title="【Java】Spring循环依赖：原因与解决方法">【Java】Spring循环依赖：原因与解决方法</a></p>
<p><a href="https://cyberspace.blog.csdn.net/article/details/136336417?spm=1001.2014.3001.5502" title="OpenAI Sora来了，视频生成领域的GPT-4时代来了">OpenAI Sora来了，视频生成领域的GPT-4时代来了</a></p>
<p>[[Java·算法·简单] LeetCode 14. 最长公共前缀 详细解读](https://cyberspace.blog.csdn.net/article/details/136200396?spm=1001.2014.3001.5502 “[Java·算法·简单] LeetCode 14. 最长公共前缀 详细解读”)</p>
<p><a href="https://cyberspace.blog.csdn.net/article/details/136318553?spm=1001.2014.3001.5502" title="【Java】深入理解Java中的static关键字">【Java】深入理解Java中的static关键字</a></p>
<p>[[Java·算法·简单] LeetCode 28. 找出字a符串中第一个匹配项的下标 详细解读](https://cyberspace.blog.csdn.net/article/details/136292910?spm=1001.2014.3001.5502 “[Java·算法·简单] LeetCode 28. 找出字a符串中第一个匹配项的下标 详细解读”)</p>
<p><a href="https://cyberspace.blog.csdn.net/article/details/136256523?spm=1001.2014.3001.5502" title="了解 Java 中的 AtomicInteger 类">了解 Java 中的 AtomicInteger 类</a></p>
<p><a href="https://cyberspace.blog.csdn.net/article/details/136275183?spm=1001.2014.3001.5502" title="算法题 — 整数转二进制，查找其中1的数量">算法题 — 整数转二进制，查找其中1的数量</a></p>
<p><a href="https://cyberspace.blog.csdn.net/article/details/136223146?spm=1001.2014.3001.5502" title="深入理解MySQL事务特性：保证数据完整性与一致性">深入理解MySQL事务特性：保证数据完整性与一致性</a></p>
<p><a href="https://cyberspace.blog.csdn.net/article/details/136102269?spm=1001.2014.3001.5502" title="Java企业应用软件系统架构演变史">Java企业应用软件系统架构演变史</a></p>
<p>原文链接: https://blog.csdn.net/double222222/article/details/139543787</p>]]></description><guid isPermaLink="false">/archives/09d1b391-7ded-4d38-bd74-6d7b115d576c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Mon, 30 Sep 2024 09:12:06 GMT</pubDate></item><item><title><![CDATA[【电脑组装】✈️从配置拼装到安装系统组装自己的台式电脑]]></title><link>https://wiki.dgfygk.com/archives/49b64485-6426-43d3-bf85-0b011928b350</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%94%B5%E8%84%91%E7%BB%84%E8%A3%85%E3%80%91%E2%9C%88%EF%B8%8F%E4%BB%8E%E9%85%8D%E7%BD%AE%E6%8B%BC%E8%A3%85%E5%88%B0%E5%AE%89%E8%A3%85%E7%B3%BB%E7%BB%9F%E7%BB%84%E8%A3%85%E8%87%AA%E5%B7%B1%E7%9A%84%E5%8F%B0%E5%BC%8F%E7%94%B5%E8%84%91&amp;url=/archives/49b64485-6426-43d3-bf85-0b011928b350" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F62d50c9c737547b5ab8f6434f1af171e.webp&amp;size=m" alt=""></p>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%B8%E5%89%8D%E8%A8%80">🍸前言</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%BB%E4%B8%80%E3%80%81%E5%8F%B0%E5%BC%8F%E7%94%B5%E8%84%91%E5%9F%BA%E6%9C%AC%E7%BB%84%E6%88%90">🍻一、台式电脑基本组成</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%BA%E4%BA%8C%E3%80%81%E7%BB%84%E8%A3%85">🍺二、组装</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%B9%E4%B8%89%E3%80%81%E5%AE%89%E8%A3%85%E7%B3%BB%E7%BB%9F">🍹三、安装系统</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%91%8B%E5%9B%9B%E3%80%81%E7%B3%BB%E7%BB%9F%E8%AE%BE%E7%BD%AE">👋四、系统设置</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%91%80%E4%BA%94%E3%80%81%E7%AB%A0%E6%9C%AB">👀五、章末</a></p>
<hr>
<p>
 <br>
</p>
<h4><strong>🍸</strong>前言</h4>
<p>小伙伴们大家好，上篇文章分享了在平时开发的时候遇到的一种项目整合情况，微服务项目中常见的一种方式，就是将工具类提取到公共服务中，别的项目通过引入sdk的方式引入公共服务使用工具类，好处就是方便统一管理，以及适合微服务扩展，文章链接如下：</p>
<p><a href="https://blog.csdn.net/TM007_/article/details/142023542?spm=1001.2014.3001.5501" title="【微服务】⭐️华为云obs功能抽取到公共服务，供所有项目使用-CSDN博客">【微服务】⭐️华为云obs功能抽取到公共服务，供所有项目使用-CSDN博客</a></p>
<p>这次就先不做编程技术上的分享了，来看点不一样的，硬件组装！因为鄙人到前段时间为止使用的还是大学战损版笔记本，性能包括存储方面已经不能满足日常的使用了，所以着手准备组装一个台式笔记本，成果图如下</p>
<p>（没错，黑神话悟空是我下载下来测试电脑性能用的（认真）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff88634f73a6d4e84aa0dc7173d3b9951.jpeg&amp;size=m" alt=""></p>
<p>：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe25e7922c1444eb4a1ad3af0001a3e2a.jpeg&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb0e65765e409460eb8d37e2e8b0503c1.jpeg&amp;size=m" alt=""></p>
<h4><strong>🍻</strong>一、台式电脑基本组成</h4>
<p>就我这次组装而言用到的基本组件中有如下这些：</p>
<p>cpu、cpu 主板、内存条、固态硬盘、散热器、电源、显卡、机箱 还有外设 键盘、鼠标、显示屏</p>
<p>其中比较重要的就是前面8个零件，因为彼此之间需要兼容所以购买时需要格外注意，鄙人就遇到了不兼容的问题，后文会提及，整体的配置如下（纯个人网上查询借鉴搭配，非专业人士）：</p>
<p>|——–|——————————|
 <br>
 | | 型号 |
 <br>
 | cpu | AMD 锐龙 R5 7500F |
 <br>
 | cpu 主板 | 华硕（ASUS）PRIME B650M-K |
 <br>
 | 内存条 | 金百达DDR5银爵 6000MHz 16G*2 |
 <br>
 | 固态硬盘 | WD 西数固态硬盘 1t |
 <br>
 | 散热器 | 雅浚 b3pro 台式电脑 e3v2 风冷【AMD专用】 |
 <br>
 | 电源 | 威刚XPG魔核600W |
 <br>
 | 显卡 | 讯景 RX6750GRE 10G 雪狼版 |
 <br>
 | 机箱 | Tt刚影透EX海景机房 |</p>
<h4><strong>🍺</strong>二、组装</h4>
<p>首次组装的话，需要注意的细节还是很多的，建议先看一下组装教学视频，B站上搜"台式机组装” 自行对其所需；</p>
<p>这里分享下个人对这几个组件的拼装流程</p>
<p>1.cpu装到主板上的对应位置；这里要注意不要平凡挪动cpu芯片，因为上面的胶针碰歪了板子相当于废了，更不要大力出奇迹！不然你懂得</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F62c8e2e751434c5ba63b9b5cc05b1e58.jpeg&amp;size=m" alt=""></p>
<p>2.内存条安装到主板上；这里要注意内存条的呆口，并将内存条接口两侧的小卡扣打开（如果有），对准呆口后再按压，会听见清脆的一声"咔”，说明安装成功</p>
<p>3.固态硬盘安装到主板上；这里要注意固态硬盘的接口是需要螺丝固定的，固定的时候不需要拧太紧，固定住即可；</p>
<p>4.散热器安装到主板上；我使用的是风冷的，一开始买错了，没注意看是区分 cpu 型号的，好像英特尔对应的散热器的固定支架是正方形的，我主板上是长方形的，找商家问了下是买错了。。。</p>
<p>5.主板固定到机箱；将机箱外面侧板拆开，将主板放到对应的位置，要检查对应的位置有没有螺母，也是固定就好，不需要拧太紧；另外有主板送的接口盘，就一个铁片，对主板上的对外接口起一个保护作用的，也装上（一般直接卡在机箱侧面就行，注意分上下，下方一般有三个音频圆形接口）</p>
<p>6.电源装到机箱；也是机箱侧面的位置，用螺丝固定即可</p>
<p>7.数据线连接；这里的线比较多，但是跟着视频做一遍还是挺友好的，因为每个线上都有英文标志</p>
<p>8.显卡安装到主板上；固定好的主板上找到显卡的金手指卡槽，也是有防呆口，对准后卡进入即可，另外需要螺丝固定显卡的侧边，并且将机箱侧面对应显卡一些外设接口的位置清理出来，给显卡插上电源即可</p>
<p>以上机箱就组装好了，下面是连接显示屏，并且测试主机是否连接正确</p>
<p>1.现在的屏幕一般使用 hdmi 接口连接，将买显示器送的连接线一头对准显卡侧面的 hdmi 卡槽，另外一头接到显示屏上的卡槽；将显示屏的电源线也插上</p>
<p>2.先将显示屏打开，然后开启主机电源（要先将买的电源从0置为1），如果线路都没问题的话应该会进入设备默认的系统页面，一堆英文和一个系统标识</p>
<p>注：这里我在安装的时候遇到了问题</p>
<p>显示屏显示无信号：重试了几次还是解决不了，网上找到一个可能得原因是hdmi没有接对，有独立显卡的话就要装到显卡上的hdmi卡槽（独立显卡就是你自己另外买了显卡，注意看的话主板上也有一个hdmi接口，叫做集成接口，有独立显卡的话，不会扫描集成接口的），没错，我的就插错了；更换接口后还是不行，这是看到显卡上有两个相同的电源卡槽，但是我只插了一个，问了下商家需要两个插满，重试后，解决问题，成功进入系统默认页面</p>
<h4><strong>🍹</strong>三、安装系统</h4>
<p>1.工具准备</p>
<p>U盘，可联网电脑</p>
<p>注：这里使用的U盘改装为启动盘要格式化数据的，所以建议提前备份好其中的数据或者另外买一个容量&gt;=8g的</p>
<p>2.系统选择以及资源下载</p>
<p>我使用过的系统有好几个了，比如，windows,linux,mac,unix，本地最后安装的是windows,还是图省事</p>
<p>资源下载的话就到微软官网搜自己想要的版本下载，根据操作提示一路next制作一个安装介质，根据网速不同，所需要的时间也不同，我在本地用了二十几分钟（中间还报错了一次，并且要求C盘可用容量&gt;8G）</p>
<p>3.启动盘制作好后，可以将其插到组装好的机箱后面的主板卡槽上，开启电脑，连续点击 delete 键（可能型号不一样快捷键也不一样，没关系，上网搜一下对应型号的快捷键即可），进入安装流程，根据微软的安装提示，一步步往下设置即可；</p>
<p>其中在自定义分区的时候可能会有点小问题，我安装的时候遇到一个格式不兼容的问题，b站上将问题描述搜索下，根据一个视频，敲了几个命令，重新设置了磁盘，接着去自定义分区就没问题了</p>
<h4>👋四、系统设置</h4>
<p>激活：这次图省事，直接去淘宝买了一个激活码，两分钟搞定</p>
<p>个性化设置一些主题，图标，色暖之类的即可；另外本地没有接网线，使用的是无线网卡</p>
<p>之后也可以使用驱动精灵检查下有没有需要更新的配置</p>
<p>音响 排线 …</p>
<h4>👀五、章末</h4>
<p>文章就到这里了\~</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6b0b4646483f4241b5bde73e76340204.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/TM007_/article/details/142307239</p>]]></description><guid isPermaLink="false">/archives/49b64485-6426-43d3-bf85-0b011928b350</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Mon, 30 Sep 2024 09:11:50 GMT</pubDate></item><item><title><![CDATA[这是一款网络工程师电脑必装的软件：SecureCRT，从零安装到精通，看本文足够了！]]></title><link>https://wiki.dgfygk.com/archives/c256e5be-79b0-4a13-ad29-a62663ada2e1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%BF%99%E6%98%AF%E4%B8%80%E6%AC%BE%E7%BD%91%E7%BB%9C%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%94%B5%E8%84%91%E5%BF%85%E8%A3%85%E7%9A%84%E8%BD%AF%E4%BB%B6%EF%BC%9ASecureCRT%EF%BC%8C%E4%BB%8E%E9%9B%B6%E5%AE%89%E8%A3%85%E5%88%B0%E7%B2%BE%E9%80%9A%EF%BC%8C%E7%9C%8B%E6%9C%AC%E6%96%87%E8%B6%B3%E5%A4%9F%E4%BA%86%EF%BC%81&amp;url=/archives/c256e5be-79b0-4a13-ad29-a62663ada2e1" width="1" height="1" alt="" style="opacity:0;">
<p>你好，这里是网络技术联盟站，我是瑞哥。</p>
<p>SecureCRT 是一款广泛使用的终端仿真程序，由 VanDyke Software 公司开发。它旨在为网络管理员、工程师和开发人员提供安全、可靠的终端仿真和文件传输功能。SecureCRT 支持多种协议，包括 SSH、Telnet、Serial、RLogin 等，能够满足不同用户的需求。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb4a1f63c3e7f63fd803020baaecb80cf.jpeg&amp;size=m" alt=""></p>
<p>SecureCRT 最早于 1995 年发布，最初只支持 Windows 平台。随着网络技术的发展和用户需求的变化，SecureCRT 不断更新和改进。如今，SecureCRT 已经成为业界公认的领先终端仿真工具，拥有广泛的用户基础和丰富的功能特性。VanDyke Software 公司也在不断推出新版本，增加新功能和优化用户体验。</p>
<p>目录：</p>
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#SecureCRT_11">SecureCRT的用途和优势</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_17">高安全性</a></li>
   <li><a href="https://wiki.dgfygk.com#_21">多平台支持</a></li>
   <li><a href="https://wiki.dgfygk.com#_25">强大的会话管理</a></li>
   <li><a href="https://wiki.dgfygk.com#_29">灵活的脚本和自动化</a></li>
   <li><a href="https://wiki.dgfygk.com#_33">高度可定制化</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_39">适用场景</a></li>
 <li><a href="https://wiki.dgfygk.com#_SecureCRT_48">下载 SecureCRT</a></li>
 <li><a href="https://wiki.dgfygk.com#SecureCRT_70">安装SecureCRT</a></li>
 <li><a href="https://wiki.dgfygk.com#_94">基本使用</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_114">认识菜单栏</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#1_File_120">1. 文件（File）</a></li>
   <li><a href="https://wiki.dgfygk.com#2_Edit_128">2. 编辑（Edit）</a></li>
   <li><a href="https://wiki.dgfygk.com#3_View_134">3. 查看（View）</a></li>
   <li><a href="https://wiki.dgfygk.com#4_Session_140">4. 会话（Session）</a></li>
   <li><a href="https://wiki.dgfygk.com#5_Tools_145">5. 工具（Tools）</a></li>
   <li><a href="https://wiki.dgfygk.com#6_Help_151">6. 帮助（Help）</a></li>
   <li><a href="https://wiki.dgfygk.com#_155">侧边栏</a></li>
   <li><a href="https://wiki.dgfygk.com#_190">设置和管理会话</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_194">创建新会话</a></li>
   <li><a href="https://wiki.dgfygk.com#_208">使用标签页管理多个会话</a></li>
   <li><a href="https://wiki.dgfygk.com#_218">常用快捷键</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_231">命令行基本操作</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_246">复制粘贴操作</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_248">选择文本</a></li>
   <li><a href="https://wiki.dgfygk.com#_254">复制文本</a></li>
   <li><a href="https://wiki.dgfygk.com#_259">粘贴文本</a></li>
   <li><a href="https://wiki.dgfygk.com#_264">自定义终端外观和配色</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_268">更改字体</a></li>
   <li><a href="https://wiki.dgfygk.com#_276">更改配色方案</a></li>
   <li><a href="https://wiki.dgfygk.com#_284">使用预定义配色方案</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_294">文件传输功能</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#Xmodem_302">Xmodem</a></li>
   <li><a href="https://wiki.dgfygk.com#Ymodem_307">Ymodem</a></li>
   <li><a href="https://wiki.dgfygk.com#Zmodem_312">Zmodem</a></li>
   <li><a href="https://wiki.dgfygk.com#KERMIT_317">KERMIT</a></li>
   <li><a href="https://wiki.dgfygk.com#YmodemG_322">Ymodem-G</a></li>
   <li><a href="https://wiki.dgfygk.com#ASCII_327">ASCII</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_336">高级键盘和鼠标操作</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_340">多窗口管理</a></li>
   <li><a href="https://wiki.dgfygk.com#_349">分屏和布局</a></li>
   <li><a href="https://wiki.dgfygk.com#_358">鼠标右键菜单</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_364">其他有用的设置</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_366">复制粘贴自定义</a></li>
   <li><a href="https://wiki.dgfygk.com#_370">关键字突出显示自定义</a></li>
   <li><a href="https://wiki.dgfygk.com#_384">保存会话日志</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_390">手动保存日志</a></li>
   <li><a href="https://wiki.dgfygk.com#_402">自动保存日志</a></li>
   <li><a href="https://wiki.dgfygk.com#_417">高级设置</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_427">写在最后</a></li>
</ul>
<p>
 <br>
</p>
<h3>SecureCRT的用途和优势</h3>
<p>SecureCRT 的主要用途包括远程系统管理、网络设备配置、开发环境访问以及安全文件传输。</p>
<p>它具有以下优势：</p>
<h4>高安全性</h4>
<p>SecureCRT 支持多种加密协议和身份验证方式，如 SSH1、SSH2、Kerberos、密码、公共密钥等，确保数据传输的安全性。它还提供了多重身份验证（MFA）支持，进一步提高了安全性。</p>
<h4>多平台支持</h4>
<p>SecureCRT 支持 Windows、macOS 和 Linux 操作系统，用户可以在不同平台上获得一致的使用体验。这对于跨平台工作的 IT 专业人员和开发人员尤为重要。</p>
<h4>强大的会话管理</h4>
<p>SecureCRT 提供了全面的会话管理功能。用户可以创建、保存和组织多个会话，方便快速访问常用连接。标签页功能允许用户在一个窗口中管理多个会话，提升工作效率。</p>
<h4>灵活的脚本和自动化</h4>
<p>SecureCRT 支持多种脚本语言（如 VBScript、Python 和 Perl），用户可以编写脚本来自动化常见任务，如登录、配置和文件传输。这有助于减少重复性工作，提高工作效率。</p>
<h4>高度可定制化</h4>
<p>SecureCRT 允许用户自定义终端外观、键盘映射、快捷键和颜色方案，满足不同用户的个性化需求。用户可以根据自己的工作习惯调整界面和功能，以获得最佳使用体验。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Febbbfa482ef888673c1f5f4b886ea342.png&amp;size=m" alt=""></p>
<h3>适用场景</h3>
<p>SecureCRT 广泛应用于以下场景：</p>
<ul>
 <li><strong>网络设备管理</strong>：网络管理员使用 SecureCRT 配置和管理路由器、交换机、防火墙等网络设备。</li>
 <li><strong>远程系统访问</strong>：开发人员和系统管理员通过 SecureCRT 远程访问和管理服务器，进行开发、调试和维护工作。</li>
 <li><strong>文件传输</strong>：用户可以使用 SecureCRT 的 SFTP 功能在本地和远程系统之间传输文件，确保数据传输的安全性和可靠性。</li>
 <li><strong>自动化任务</strong>：通过脚本编写，用户可以自动化完成一些常见任务，如定时备份、日志收集等，提高工作效率。</li>
</ul>
<h3>下载 SecureCRT</h3>
<p>下载地址：</p>
<pre><code>https://www.vandyke.com/download/index.html
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fefddb189313ef88fbc0ec6903138fdc7.png&amp;size=m" alt=""></p>
<p>这里选择64位的：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb6fee970aadbfdf130e836cdfce0a076.png&amp;size=m" alt=""></p>
<p>这里选择"中国"后直接下载：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffb5dca17d641ed4afa17f8719e1d83bb.png&amp;size=m" alt=""></p>
<p>下载后的文件：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa0137d8ff31f2caf038bb15f0a55f6a3.png&amp;size=m" alt=""></p>
<h3>安装SecureCRT</h3>
<p>双击开始安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc816c9d41be1fcda63889a9bcc8cee21.png&amp;size=m" alt=""></p>
<p>一直点击【Next】就行，跟装普通的软件一样。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa0a56689f5b03908beb76578d0d45649.png&amp;size=m" alt=""></p>
<p>到这个界面的时候，记得不要选择默认的"Complete”，不然会默认装到C盘去。需要点击一下"Custom"自定义安装：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F986d94f0fccac40f18afd3b35b500025.png&amp;size=m" alt=""></p>
<p>选择你存放软件的地方。</p>
<p>然后再一直下一步，直到安装完成：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd8ad51ba8d617e8e65d528c81389766f.png&amp;size=m" alt=""></p>
<p>把这几个取消勾选：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F819939f4974da3680ea8f15431ac59fc.png&amp;size=m" alt=""></p>
<h3>基本使用</h3>
<p>双击打开<code>SecureCRT 9.5</code>：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1c43e6f487cd506796d1c17bbceae44a.png&amp;size=m" alt=""></p>
<p>SecureCRT 的主窗口包含以下几个主要部分：</p>
<ul>
 <li><strong>菜单栏</strong>：位于窗口顶部，包含文件、编辑、视图、会话、工具、帮助等菜单项，提供各种功能和设置的入口。</li>
 <li><strong>工具栏</strong>：位于菜单栏下方，包含常用操作的快捷按钮，如新建会话、打开会话、保存会话等。</li>
 <li><strong>会话管理器</strong>：位于窗口左侧，显示所有已创建的会话和文件夹，用户可以通过它快速访问和管理会话。</li>
 <li><strong>终端窗口</strong>：位于窗口中央，显示当前连接的终端界面，用户可以在此输入命令并查看输出。</li>
 <li><strong>状态栏</strong>：位于窗口底部，显示当前会话的连接状态、协议类型、连接时间等信息。</li>
</ul>
<p>SecureCRT 支持标签页功能，用户可以在一个窗口中打开多个会话。每个标签页对应一个会话，标签页上显示会话名称和状态。</p>
<p>在会话管理器中右键点击会话，选择"Properties"可以打开配置面板。用户可以在配置面板中调整会话的各项设置，如连接参数、外观和行为等。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff1009e86c2332ea6e8813313af1af2ec.png&amp;size=m" alt=""></p>
<h4>认识菜单栏</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9482357c133553ddace5f91e130f6269.png&amp;size=m" alt=""></p>
<p>用的比较多的就是：</p>
<h5>1. 文件（File）</h5>
<ul>
 <li><strong>连接（Connect）</strong>：打开连接对话框，选择或创建新的连接。</li>
 <li><strong>断开连接（Disconnect）</strong>：断开当前连接。</li>
 <li><strong>快速连接（Quick Connect）</strong>：快速建立一个新的连接。</li>
 <li><strong>关闭（Close）</strong>：关闭当前会话窗口。</li>
 <li><strong>保存设置（Save Settings）</strong>：保存当前配置和设置。</li>
 <li><strong>退出（Exit）</strong>：退出SecureCRT应用程序。</li>
</ul>
<h5>2. 编辑（Edit）</h5>
<ul>
 <li><strong>复制（Copy）</strong>：复制选中的文本到剪贴板。</li>
 <li><strong>粘贴（Paste）</strong>：粘贴剪贴板中的内容到当前会话。</li>
 <li><strong>查找（Find）</strong>：在当前会话中查找指定文本。</li>
 <li><strong>选项（Options）</strong>：设置文本编辑相关选项。</li>
</ul>
<h5>3. 查看（View）</h5>
<ul>
 <li><strong>工具栏（Toolbars）</strong>：显示或隐藏工具栏。</li>
 <li><strong>状态栏（Status Bar）</strong>：显示或隐藏状态栏。</li>
 <li><strong>显示日志（View Log）</strong>：查看会话的日志。</li>
 <li><strong>快速同步（Synchronize）</strong>：同步滚动多个会话窗口。</li>
</ul>
<h5>4. 会话（Session）</h5>
<ul>
 <li><strong>发送ASCII文件（Send ASCII File）</strong>：发送一个ASCII文件到当前连接。</li>
 <li><strong>打印会话（Print Session）</strong>：打印当前会话的内容。</li>
 <li><strong>日志记录（Logging）</strong>：配置和启用会话日志记录。</li>
</ul>
<h5>5. 工具（Tools）</h5>
<ul>
 <li><strong>全局选项（Global Options）</strong>：设置SecureCRT的全局选项。</li>
 <li><strong>会话选项（Session Options）</strong>：配置当前会话的选项。</li>
 <li><strong>键映射编辑器（Keymap Editor）</strong>：编辑键映射配置。</li>
 <li><strong>脚本（Scripts）</strong>：执行和管理脚本。</li>
</ul>
<h5>6. 帮助（Help）</h5>
<ul>
 <li><strong>内容（Contents）</strong>：查看SecureCRT的帮助文档。</li>
 <li><strong>关于（About）</strong>：查看SecureCRT的版本信息和版权信息。</li>
</ul>
<h4>侧边栏</h4>
<ol>
 <li><strong>Session Manager（会话管理器）</strong>：</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9f870b9c016b01f9540f5cd4c561ee79.png&amp;size=m" alt=""></p>
<p>Session Manager 是一个非常有用的面板，它列出了你所有的保存会话。你可以在这里创建新的会话，编辑现有会话的属性，或者直接从列表中选择一个会话并连接到它。</p>
<p>它通常位于 <code>SecureCRT</code> 主窗口的左侧，可以通过拖拽来固定或解除固定位置。</p>
<ol>
 <li><strong>Command Manager（命令管理器）</strong>：</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe9f732aefbc78beb35d1f44c480dd6fe.png&amp;size=m" alt=""></p>
<p>Command Manager 是一个功能强大的特性，允许你组织和执行预定义的命令。你可以创建命令集合，将它们分组，并且通过简单的点击或热键来执行。</p>
<p>这对于自动化日常任务非常有帮助，比如定期运行脚本或发送复杂的命令序列。</p>
<ol>
 <li><strong>Active Sessions（活动会话）</strong>：</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F39764b6a7fe220ae4dd25e036284c77d.png&amp;size=m" alt=""></p>
<p>Active Sessions 面板显示了当前所有打开的会话。这包括通过标签页打开的会话。</p>
<p>你可以使用这个面板来在不同的活动会话之间切换，或者关闭不需要的会话。</p>
<p>首次运行 SecureCRT 时，会出现初始配置向导，帮助用户完成基本设置。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F56799afa0ef4c58e73b52f668cbdf685.png&amp;size=m" alt=""></p>
<ol>
 <li><strong>选择协议</strong>：选择你常用的连接协议（如 SSH、Telnet、Serial）。</li>
 <li><strong>配置会话参数</strong>：根据所选协议，输入目标主机的 IP 地址或主机名，以及端口号。</li>
 <li><strong>设置身份验证方式</strong>：选择合适的身份验证方式（如密码、公共密钥、GSSAPI 等），并输入相关信息。</li>
 <li><strong>保存会话</strong>：设置会话名称和保存位置，便于日后快速访问。</li>
</ol>
<h4>设置和管理会话</h4>
<p>SecureCRT 提供了强大的会话管理功能，用户可以方便地创建、编辑、组织和删除会话。</p>
<h5>创建新会话</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb36339447ad8541463e1ec69968dfb50.png&amp;size=m" alt=""></p>
<ol>
 <li><strong>打开会话管理器</strong>：在 SecureCRT 界面左侧，点击会话管理器按钮，打开会话管理器面板。</li>
 <li><strong>新建会话</strong>：点击"File"菜单，选择"New Session”，或者在会话管理器中右键选择"New Session”。</li>
 <li><strong>选择协议</strong>：在新建会话向导中，选择连接协议（如 SSH2、Telnet、Serial 等）。</li>
 <li><strong>配置参数</strong>：输入目标主机的 IP 地址或主机名，设置端口号和身份验证信息。</li>
 <li><strong>保存会话</strong>：设置会话名称，选择保存位置，点击"Finish"完成。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa1874407d50b68e7e8fc7af4e05976b2.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff93fd5e4be95a4a3d76342780ed34fd9.png&amp;size=m" alt=""></p>
<h5>使用标签页管理多个会话</h5>
<p>SecureCRT 支持标签页功能，用户可以在一个窗口中管理多个会话，提升工作效率。</p>
<ol>
 <li><strong>打开新标签页</strong> ：在已打开的会话窗口中，点击"File"菜单，选择"New Tab”，或使用快捷键 <code>Ctrl + T</code>（Windows）或 <code>Command + T</code>（macOS）。</li>
 <li><strong>切换标签页</strong> ：使用鼠标点击标签页，或使用快捷键 <code>Ctrl + Tab</code>（Windows）或 <code>Command + Option + 左/右箭头</code>（macOS）在标签页之间切换。</li>
 <li><strong>关闭标签页</strong> ：点击标签页右侧的"X"按钮，或使用快捷键 <code>Ctrl + W</code>（Windows）或 <code>Command + W</code>（macOS）关闭当前标签页。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F04d360e311323bd2ce3b6f668e59fb35.png&amp;size=m" alt=""></p>
<h4>常用快捷键</h4>
<p>SecureCRT 提供了丰富的快捷键，帮助用户快速执行各种操作。</p>
<ul>
 <li><strong>新建会话</strong> ：<code>Ctrl + N</code>（Windows） / <code>Command + N</code>（macOS）</li>
 <li><strong>打开新标签页</strong> ：<code>Ctrl + T</code>（Windows） / <code>Command + T</code>（macOS）</li>
 <li><strong>切换标签页</strong> ：<code>Ctrl + Tab</code>（Windows） / <code>Command + Option + 左/右箭头</code>（macOS）</li>
 <li><strong>关闭标签页</strong> ：<code>Ctrl + W</code>（Windows） / <code>Command + W</code>（macOS）</li>
 <li><strong>复制</strong> ：<code>Ctrl + C</code>（Windows） / <code>Command + C</code>（macOS）</li>
 <li><strong>粘贴</strong> ：<code>Ctrl + V</code>（Windows） / <code>Command + V</code>（macOS）</li>
 <li><strong>查找</strong> ：<code>Ctrl + F</code>（Windows） / <code>Command + F</code>（macOS）</li>
 <li><strong>保存会话配置</strong> ：<code>Ctrl + S</code>（Windows） / <code>Command + S</code>（macOS）</li>
</ul>
<h3>命令行基本操作</h3>
<p>在终端窗口中，用户可以直接输入命令并按下 <code>Enter</code> 键执行。例如，输入 <code>ls</code> 命令列出当前目录下的文件和文件夹。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F949a6d5d3c0e0ab453c6359927c7a89b.png&amp;size=m" alt=""></p>
<ul>
 <li><strong>复制</strong> ：用鼠标选中文本后，右键点击选择"Copy"或使用快捷键 <code>Ctrl + C</code>（Windows）/ <code>Command + C</code>（macOS）复制选中文本。</li>
 <li><strong>粘贴</strong> ：右键点击选择"Paste"或使用快捷键 <code>Ctrl + V</code>（Windows）/ <code>Command + V</code>（macOS）粘贴剪贴板内容到终端窗口。</li>
</ul>
<p>可以使用 <code>clear</code> 命令清除终端窗口的显示内容，或使用快捷键 <code>Ctrl + L</code>。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdcc3105eee741faf18355d276d7e4c2c.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7cc29a29002827650aad955a5fb40ecb.png&amp;size=m" alt=""></p>
<h4>复制粘贴操作</h4>
<h5>选择文本</h5>
<ul>
 <li><strong>单击选择</strong>：用鼠标左键单击并拖动选择文本。</li>
 <li><strong>双击选择</strong>：双击单词选中整个单词。</li>
 <li><strong>三击选择</strong>：三击行选中整行文本。</li>
</ul>
<h5>复制文本</h5>
<ul>
 <li><strong>右键菜单</strong>：选中文本后，右键点击选择"Copy”。</li>
 <li><strong>快捷键</strong> ：选中文本后，使用快捷键 <code>Ctrl + C</code>（Windows）/ <code>Command + C</code>（macOS）。</li>
</ul>
<h5>粘贴文本</h5>
<ul>
 <li><strong>右键菜单</strong>：右键点击选择"Paste”。</li>
 <li><strong>快捷键</strong> ：使用快捷键 <code>Ctrl + V</code>（Windows）/ <code>Command + V</code>（macOS）。</li>
</ul>
<h4>自定义终端外观和配色</h4>
<p>SecureCRT 允许用户自定义终端的外观和配色，以满足个性化需求。</p>
<h5>更改字体</h5>
<ol>
 <li><strong>打开会话属性</strong>：在会话管理器中，右键点击会话，选择"Properties”。</li>
 <li><strong>选择终端外观</strong>：在会话属性窗口中，选择"Appearance"选项卡。</li>
 <li><strong>设置字体</strong>：点击"Font"按钮，选择合适的字体、字体样式和大小，点击"OK"保存设置。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff4502d3f2b32969e1f399382c1d6c406.png&amp;size=m" alt=""></p>
<h5>更改配色方案</h5>
<ol>
 <li><strong>打开会话属性</strong>：在会话管理器中，右键点击会话，选择"Properties”。</li>
 <li><strong>选择终端外观</strong>：在会话属性窗口中，选择"Appearance"选项卡。</li>
 <li><strong>设置配色方案</strong>：点击"Edit"按钮，在配色方案编辑器中调整前景色、背景色、光标颜色等，点击"OK"保存设置。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F347f6b4d2e9ac92477554159443c3deb.png&amp;size=m" alt=""></p>
<h5>使用预定义配色方案</h5>
<p>SecureCRT 提供了一些预定义的配色方案，用户可以快速应用这些配色方案：</p>
<ol>
 <li><strong>打开会话属性</strong>：在会话管理器中，右键点击会话，选择"Properties”。</li>
 <li><strong>选择终端外观</strong>：在会话属性窗口中，选择"Appearance"选项卡。</li>
 <li><strong>选择配色方案</strong>：在"Color Scheme"下拉菜单中选择一个预定义的配色方案，点击"OK"保存设置。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F04adb7e0411e651a2530723494c3ef27.png&amp;size=m" alt=""></p>
<h3>文件传输功能</h3>
<p>SecureCRT 提供了强大的文件传输功能，支持安全地在本地系统和远程系统之间传输文件。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F21fe298bbf3506917dd5063e54fb6278.png&amp;size=m" alt=""></p>
<p>从上面的图，我们看到SecureCRT可以利用Xmodem、Ymodem、Zmodem以及KERMIT、Ymodem-G、ASCII等传输协议进行传输，那么这几种传输协议有啥区别呢？</p>
<h4>Xmodem</h4>
<ul>
 <li><strong>Xmodem</strong> 是最早期的文件传输协议之一，最初设计用于通过调制解调器进行文件传输。</li>
 <li>它以128字节的数据块进行传输，使用CRC校验来确保数据的准确性。</li>
 <li>Xmodem适合于低速连接，因为它简单并且可靠。</li>
</ul>
<h4>Ymodem</h4>
<ul>
 <li><strong>Ymodem</strong> 是Xmodem的增强版本，支持更大的数据块（通常是1024字节），从而提高了传输速度。</li>
 <li>Ymodem引入了批量文件传输能力，可以一次传输多个文件，而无需为每个文件重新建立连接。</li>
 <li>Ymodem还支持目录列表和断点续传。</li>
</ul>
<h4>Zmodem</h4>
<ul>
 <li><strong>Zmodem</strong> 是Ymodem的进一步改进，它优化了数据压缩和错误恢复机制。</li>
 <li>Zmodem可以自动检测错误并重传，同时支持断点续传和文件压缩，这使得它在高延迟或低带宽连接下也非常有效。</li>
 <li>它使用滑动窗口协议，这意味着它可以同时传输多个数据块，进一步提高了传输效率。</li>
</ul>
<h4>KERMIT</h4>
<ul>
 <li><strong>KERMIT</strong> 协议是一种高度可靠的文件传输协议，特别适合在不可靠的网络上传输数据。</li>
 <li>它支持错误检测和纠正，数据压缩，以及断点续传。</li>
 <li>KERMIT协议还能够智能地选择最佳传输速率，以适应当前的网络状况。</li>
</ul>
<h4>Ymodem-G</h4>
<ul>
 <li><strong>Ymodem-G</strong> 是Ymodem的变体，专注于提高数据传输速度。</li>
 <li>它通过使用更大的数据块和改进的错误检测机制来实现这一点。</li>
 <li>Ymodem-G也支持文件压缩和断点续传。</li>
</ul>
<h4>ASCII</h4>
<ul>
 <li><strong>ASCII</strong> 模式实际上并不是一种文件传输协议，而是一种数据编码方式。</li>
 <li>当使用ASCII模式传输时，二进制数据会被转换为可打印的ASCII字符，这在早期的文本传输中很常见。</li>
 <li>ASCII模式的传输速度通常比二进制模式慢，因为转换过程会增加额外的开销，但它可以确保数据的可读性。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fee1ff990cc03aff9f816e649483bd34c.png&amp;size=m" alt=""></p>
<p>这里可以修改上传和下载的默认目录。</p>
<h3>高级键盘和鼠标操作</h3>
<p>SecureCRT 提供了多种高级键盘和鼠标操作，帮助用户更高效地操作和管理会话。</p>
<h4>多窗口管理</h4>
<p>SecureCRT 支持在同一窗口中打开多个会话标签页，并提供了多种方式进行快速切换和管理。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F25d1a8a266e003d237f503ba1f400c6d.png&amp;size=m" alt=""></p>
<ul>
 <li><strong>快捷键</strong> ：使用 <code>Ctrl + Tab</code>（Windows）或 <code>Command + Option + 左/右箭头</code>（macOS）在标签页之间切换。</li>
 <li><strong>鼠标操作</strong>：通过鼠标点击标签页来切换会话。</li>
</ul>
<h4>分屏和布局</h4>
<p>SecureCRT 允许用户将窗口分割成多个区域，每个区域可以显示不同的会话内容，提升工作效率。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9008b85ad5cb18c24bf315250143b488.png&amp;size=m" alt=""></p>
<ul>
 <li><strong>分屏操作</strong>：在主窗口中右键点击选择"Split Horizontally"或"Split Vertically"来分割窗口。</li>
 <li><strong>布局管理</strong>：通过窗口菜单中的布局选项来管理和调整分屏布局。</li>
</ul>
<h4>鼠标右键菜单</h4>
<p>SecureCRT 提供了丰富的鼠标右键菜单选项，用于快速访问常用功能和操作。</p>
<ul>
 <li><strong>会话管理</strong>：通过鼠标右键菜单可以快速打开、关闭、重命名会话，以及管理会话属性和设置。</li>
</ul>
<h3>其他有用的设置</h3>
<h4>复制粘贴自定义</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5e4b9b6d5e42f7358ad46fed730c27d0.png&amp;size=m" alt=""></p>
<h4>关键字突出显示自定义</h4>
<p>这里可以新增一个高亮的配置：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fae2c663e4c9e692264d680febae16563.png&amp;size=m" alt=""></p>
<p>这里我们新建一个名为"wljslmz"的高亮配置，匹配到"wljslmz"字符的时候高亮为玫红色。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb8476d9b06420a1027b23a619c57cc65.png&amp;size=m" alt=""></p>
<p>设置完成后，你再输入带有"wljslmz"字符的时候，就能自动高亮，是不是很好用！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3e5e8d15460831581d83b1b7a48f8c4c.png&amp;size=m" alt=""></p>
<h4>保存会话日志</h4>
<p>在 <code>SecureCRT</code> 中保存会话日志可以通过手动或自动的方式进行。</p>
<p>以下是两种主要的保存日志的方法：</p>
<h5>手动保存日志</h5>
<p>手动保存日志适用于临时或特定情况下的日志记录。步骤如下：</p>
<ol>
 <li>打开会话，开始你的工作。</li>
 <li>当你想要开始记录日志时，转到 <code>File</code> 菜单，然后选择 <code>Log Session...</code>。</li>
 <li>在弹出的对话框中，指定日志文件的保存位置和文件名。</li>
 <li>点击 <code>Save</code> 开始记录日志。</li>
 <li>当你完成日志记录时，再次选择 <code>File</code> 菜单下的 <code>Stop Logging</code> 来结束日志记录。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff1f013f221658172db4e3a8516a0e890.png&amp;size=m" alt=""></p>
<h5>自动保存日志</h5>
<p>自动保存日志可以确保每次会话都会被记录，无需每次手动开启。设置步骤如下：</p>
<ol>
 <li>打开 <code>Options</code> 菜单，然后选择 <code>Global Options</code>。</li>
 <li>在 <code>Global Options</code> 对话框中，选择 <code>Logging</code> 类别。</li>
 <li>在 <code>General</code> 选项卡下，勾选 <code>Record session to file</code> 选项。</li>
 <li>设置 <code>Log File Location</code> 和 <code>Log File Name</code>，你可以使用变量（如 <code>%Y</code>, <code>%M</code>, <code>%D</code>, <code>%S</code> 等）来构建动态的文件名和路径。</li>
 <li>根据需要调整其他设置，如 <code>Log File Size</code>、<code>Roll Over</code> 行为等。</li>
 <li>点击 <code>OK</code> 应用更改。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F11487dd82582b8f9aff08c0d9856e1c3.png&amp;size=m" alt=""></p>
<p>之后，每当一个新的会话开始时，<code>SecureCRT</code> 将自动创建并开始记录日志文件，按照你所设定的规则进行命名和存储。</p>
<h5>高级设置</h5>
<p>在 <code>Advanced</code> 设置中，你可以进一步定制日志记录的内容和格式：</p>
<ul>
 <li>选择会话日志的格式（纯文本、HTML、XML等）。</li>
 <li>决定记录方式（所有数据、只记录命令行输入、只记录屏幕显示）。</li>
 <li>设置日志文件的大小限制和滚动策略。</li>
</ul>
<p>通过上述设置，你可以确保 <code>SecureCRT</code> 会话的所有关键活动都被记录下来，方便后续分析或存档。</p>
<h3>写在最后</h3>
<p>最后给大家介绍一下目前SecureCRT最新的版本：<strong>SecureCRT 9.5.2</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc27d376a1f36d474606de862099215bf.png&amp;size=m" alt=""></p>
<p>SecureCRT 9.5新增的功能：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F82778fc2879bb0b2ef3c6b7fb1e8862c.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <p><strong>新 macOS 平台支持</strong></p>
 </li>
 <li>
  <p><code>SecureCRT</code> 现在支持 macOS 14（代号Sonoma），这意味着用户可以在最新版本的 macOS 上无缝使用 <code>SecureCRT</code>。</p>
 </li>
 <li>
  <p><strong>增强的凭证管理</strong></p>
 </li>
 <li>
  <p>用户可以将一组保存的凭证（如用户名和密码）映射到按钮、命令或键盘快捷键，从而快速地将凭证发送到会话中，简化了登录流程。</p>
 </li>
 <li>
  <p>凭证的修改也变得更加直观，只需单击映射的按钮即可调用修改保存的密码对话框。</p>
 </li>
 <li>
  <p><strong>默认 RDP 会话（仅限 Windows）</strong></p>
 </li>
 <li>
  <p><code>SecureCRT</code> 的 Windows 版本现在允许用户配置 RDP 会话的默认设置，这意味着 RDP 选项可以独立于 <code>SecureCRT</code> 的默认会话进行自定义，提供了更多的灵活性。</p>
 </li>
</ol>
<p>怎么样，你现在用的版本是多少？</p>
<p>原文链接: https://blog.csdn.net/weixin_43025343/article/details/140504390</p>]]></description><guid isPermaLink="false">/archives/c256e5be-79b0-4a13-ad29-a62663ada2e1</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Mon, 30 Sep 2024 09:11:37 GMT</pubDate></item><item><title><![CDATA[如何实现一套键盘鼠标控制两台计算机（Mouse Without Borders快速上手教程）]]></title><link>https://wiki.dgfygk.com/archives/3bb15048-633e-4d0b-917b-a4332752fa7c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E5%A5%97%E9%94%AE%E7%9B%98%E9%BC%A0%E6%A0%87%E6%8E%A7%E5%88%B6%E4%B8%A4%E5%8F%B0%E8%AE%A1%E7%AE%97%E6%9C%BA%EF%BC%88Mouse%20Without%20Borders%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E6%95%99%E7%A8%8B%EF%BC%89&amp;url=/archives/3bb15048-633e-4d0b-917b-a4332752fa7c" width="1" height="1" alt="" style="opacity:0;">
<h3>需求背景</h3>
<p>当我们需要同时使用一台主机和一台笔记本的时候，如果使用两套键盘和鼠标分别操作各自的系统，非常地不便捷且非常占据桌面空间。那么如何使用一套键盘鼠标控制两台电脑呢？</p>
<h3>需求实现</h3>
<h4>软件说明</h4>
<p>我们可以使用微软官方的一款软件<strong>Mouse Without Borders</strong> 来实现这个需求。这个软件可以实现一套键鼠操作多台（最多4台）设备，<strong>设备间相当于多屏扩展但都是独立运行</strong> 。并且它还可以<strong>跨电脑剪切、复制粘贴文件</strong>……</p>
<h4>下载软件</h4>
<p>微软官方下载链接：<a href="https://www.microsoft.com/en-us/download/details.aspx?id=35460" title="Download Microsoft Garage Mouse without Borders from Official Microsoft Download Center">Download Microsoft Garage Mouse without Borders from Official Microsoft Download Center</a></p>
<p>点击Download下载</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F55afcde3f67642469dbaaf3bd05e0afc.png&amp;size=m" alt=""></p>
<h4>安装软件</h4>
<p>双击进行安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa91ab8fa924d49a8a4448d85c3d3e46b.png&amp;size=m" alt=""></p>
<p>点击Next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F22ddb600027e4e60bafed76d52fbeb68.png&amp;size=m" alt=""></p>
<p>勾选I accept…然后点击Install</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F17bc32794b1742409e26543bb1eabf80.png&amp;size=m" alt=""></p>
<p>点击Finish，完成安装</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9c3501b81cb4453987c2cb0b9968fabf.png&amp;size=m" alt=""></p>
<p><strong>需要连接的设备都需要按照这个步骤安装该软件</strong></p>
<h4><strong>设备连接</strong></h4>
<h5><strong>设备A操作</strong></h5>
<p>设备A和设备B都已经安装好之后，在<strong>设备A</strong>上打开该软件，如图下所示，第一次安装使用该软件的话，先点击NO</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F618fde3d2ae148cdb5d24e0aa416592d.png&amp;size=m" alt=""></p>
<p>然后它会生成图下所示的界面，这个密钥有点复杂，不用该密钥。点击左上角的<strong>Skip</strong>跳过</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5fd5e6bec3624813961025b0689bb129.png&amp;size=m" alt=""></p>
<p>点击 是</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb92f16c96e714ae58b9dab27f17f99f1.png&amp;size=m" alt=""></p>
<p>进入到如下界面，我们勾选<strong>Show text</strong> 之后，<strong>密钥</strong> 就显示出来，我们设置一个长度至少为16位的比较好输入的密钥，然后点击下方的<strong>Apply</strong>按钮</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F81ab6297704642b688706069999ad96d.png&amp;size=m" alt=""></p>
<h5>设备B操作</h5>
<p>然后打开<strong>设备B</strong> 的软件，图下所示界面，点击<strong>YES</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F08d810a7f0c44e2b94f019be9aef7940.png&amp;size=m" alt=""></p>
<p>进入如下界面，上方输入框<strong>输入设备A设置好的密钥</strong>，下方输入<strong>设备A的主机名</strong>，然后点击<strong>LINK</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F360239769ef24c7787cdb3da3848fce9.png&amp;size=m" alt=""></p>
<p>点击<strong>NEXT</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F32431ba4e2934d4e9819ecf4ff958f6a.png&amp;size=m" alt=""></p>
<p>点击<strong>DONE</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F083f2015b2834a0397596715a11a06da.png&amp;size=m" alt=""></p>
<p>进入如下界面，点击<strong>Apply</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd515c3c39281411ca80e6e696523dabd.png&amp;size=m" alt=""></p>
<p>如图下所示，代表连接已成功。此时设备A在左边，设备B在右边，可以尝试移动鼠标，把鼠标移动到设备A屏幕右边边缘即可转移到设备B上使用，键盘也可以在设备B上操作。同时，两套设备还可以实现文件的<strong>剪切、复制粘贴</strong>等功能，非常便捷\~</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff8c0d23d39514185a3914a5984e399a6.png&amp;size=m" alt=""></p>
<h4>设置开机自启或者关闭开机自启</h4>
<p><strong>win+R</strong> 打开运行，输入<strong>services.msc</strong> ，打开<strong>windows服务</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F12afc9c6622340ccbe2f5cf96fd40c74.png&amp;size=m" alt=""></p>
<p>双击打开<strong>Mouse without Borders Service</strong>服务</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fdc4489b1c2bb4cf485f05166f643223b.png&amp;size=m" alt=""></p>
<p>如果启动类型是自动的话，那么这个软件就是开机自启；如果不想开机自启，就把这里设置为手动即可。点击<strong>应用</strong> ，然后点击<strong>确定</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8824c4c9490e4406b8762ef5de145ca8.png&amp;size=m" alt=""></p>
<h3>官方FAQ</h3>
<p>其余细节问题可以参考官方FAQ，在这里不做赘述啦。参考链接如下：</p>
<p><a href="https://answers.microsoft.com/en-us/windows/forum/all/mouse-without-borders-setupuser-guide-tips-tricks/0523308d-3406-4273-b86e-bef28aa6b50d" title="鼠标无国界设置/用户指南、提示、技巧和常见问题解答 - Microsoft 社区">鼠标无国界设置/用户指南、提示、技巧和常见问题解答 - Microsoft 社区</a></p>
<p>原文链接: https://blog.csdn.net/Tony1154/article/details/140216064</p>]]></description><guid isPermaLink="false">/archives/3bb15048-633e-4d0b-917b-a4332752fa7c</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Mon, 30 Sep 2024 09:11:22 GMT</pubDate></item><item><title><![CDATA[【机房-网络设备运维常识总结（2024-8-2更新）】]]></title><link>https://wiki.dgfygk.com/archives/a060784d-c39f-4e3c-b264-856151db7287</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%9C%BA%E6%88%BF-%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E8%BF%90%E7%BB%B4%E5%B8%B8%E8%AF%86%E6%80%BB%E7%BB%93%EF%BC%882024-8-2%E6%9B%B4%E6%96%B0%EF%BC%89%E3%80%91&amp;url=/archives/a060784d-c39f-4e3c-b264-856151db7287" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>机房-网络设备运维常识总结</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_1">一、服务器构造</a></li>
 <li><a href="https://wiki.dgfygk.com#_13">二、服务器组件拆装</a></li>
 <li><a href="https://wiki.dgfygk.com#_23">三、网络设备及耗材介绍</a></li>
 <li><a href="https://wiki.dgfygk.com#Xshell_40">四、使用Xshell进行交换机管理</a></li>
 <li><a href="https://wiki.dgfygk.com#BIOS_46">五、通过服务器进入BIOS进行基础配置</a></li>
</ul>
<p>
 <br>
</p>
<h2>一、服务器构造</h2>
<p>1、服务器高度：1U=4.445cm，常见服务器高度为1U、2U。
 <br>
 2、服务器前面板：指示灯（健康灯、UID灯）、电源按钮、前置USB口（可插鼠标、键盘）、VGA口（接显示器）、硬盘、通风进气口（热气可从后面板排出）、标签粘贴处（可贴IP、SN号、资产号等）、光驱模块（部分服务器有）。
 <br>
 3、服务器后面板：UID灯（可用于定位服务器，防止误操作）、USB口、ILO口（远程带外口，用来管理服务器）、扩展模块（GPU、光网卡）、后置硬盘（用来装服务器的操作系统）、网口模块、VGA口、电源接口、串口、带外管理口（Mgnt）。
 <br>
 4、前面板知识点补充：
 <br>
 ①插硬盘的顺序应该按照logo下规定的顺序插（例如0-11代表有12块硬盘，顺序为蛇形）；
 <br>
 ②硬盘指示灯会在插上硬盘后显示状态；
 <br>
 ③状态灯：常以闪烁状态代表运转；④UID灯按下亮，再按灭；⑤电源按钮：短按开机，长按关机。
 <br>
 5、后面板知识点补充：
 <br>
 ①系统硬盘：HDD1、HDD2；
 <br>
 ②电源模块：PSU1、PSU2（按模块旁把手可快速插拔）；
 <br>
 ③扩展槽位SLOT1、SLOT2。</p>
<h2>二、服务器组件拆装</h2>
<p>部分组件拆装要点：
 <br>
 ①盖板：扳手开启，盖板后有主板相关信息（部分可能有2款型号不同主板，需拆后自行判断是哪种）；
 <br>
 ②RAID卡、PCIE扩展槽（插网卡或者显卡）；
 <br>
 ③风扇插拔（有的风扇是一组，有的是独立的，主板上有对应的风扇接口）；
 <br>
 ④内存条：两侧白色按钮按下拔出，插入时用力按下；
 <br>
 ⑤CPU：拆螺丝时通常以"对角"的顺序拆；
 <br>
 ⑥硬盘：通常有2.5英寸、3.5英寸，当硬盘大小和硬盘槽不符时，需要接硬盘架适配，装硬盘时确保标签朝上；
 <br>
 ⑦内存卡：安装时按照背板图示顺序进行安装（通常是1A1 2A1 1B1 2B1…）；
 <br>
 ⑧CPU：安装时确保散热器和CPU背板上同时涂有硅脂（较高一面朝向前板面，较低一面朝后板面）</p>
<h2>三、网络设备及耗材介绍</h2>
<p>服务器开启时声音大，过段时间声音就变小的原因：服务器内部风扇轴承刚启动时润滑油时凝固状态，运转一段时间后，润滑油融化。
 <br>
 <strong>1、交换机</strong>
 <br>
 ①“G"千兆交换机、“TF万兆交换机、电口交换机、光口交换机；
 <br>
 ②光纤连接的是核心交换机，接入交换机连接的是服务器，确定交换机主要靠SN号。
 <br>
 <strong>2、光纤</strong>
 <br>
 描述光纤时以"品牌+单/双模+长度+类型"的方式
 <br>
 ①OFD架（光纤配线架）、光缆终端盒、光纤耦合器；
 <br>
 ②光纤内含有2根线，可分成不同类型：SC LC ST FC MPO等光纤接口，不同的光纤两端类型可以有多种组合，也可以根据光模块的类型选择不同的的光纤线；
 <br>
 ③单模光纤、多模光纤、光纤跳线；
 <br>
 ⑤光模块（光信号和电信号转换），单模光纤配单模模块，多模光纤配多模模块；
 <br>
 ⑥单模模块（处于运营商光纤口至核心交换机之间），缩写：SM，波长1310nm或1550nm，例如：XFP-10GB-LR 蓝色 10km、XFP-10GB-ER 红色 40km、XFP-10GB-IR 白色 80km；多模模块（处于交换机之间，或交换机到服务器之间），缩写MM，波长850nm，例如：SFP-1GB-SR 黑色 600m；单模模块+单模光纤=长距传输，多模模块+多模光纤=交换机到服务器短距传输；传输距离：SR 几百米、LR 10km、ER 40km、IR 80km。
 <br>
 <strong>3、网线</strong>
 <br>
 ①根据颜色区分，例如：蓝色是带外管理线，红色是业务线；
 <br>
 ②其他区分方法：五类线，100Mbps，百兆，150MHz，CAT5；超五类线，250Mbps，百/千兆，150MHz，CAT5e；六类线，Gbps，千兆，MHz，CAT6；超六类线，10Gbps，万兆，600MHz，CAT7；
 <br>
 ③屏蔽线防止电磁干扰（因为铜缆传输会产生电磁干扰）；
 <br>
 ④千兆T568A线序：（端1）绿白、绿、橙白、蓝、蓝白、橙、棕白、棕，（端2）橙白、橙、绿白、棕白、棕、绿、蓝、蓝白；千兆T568B线序：（端1）橙白、橙、绿白、蓝、蓝白、绿、棕白、棕，（端2）绿白、绿、橙白、棕白、棕、橙、蓝、蓝白；</p>
<h2>四、使用Xshell进行交换机管理</h2>
<p>1、可下载Xmanager集合包，内含Xshell。
 <br>
 2、使用console线连接交换机（console线一端是RJ45，一端是串口接头），若使用的笔记本没有串口，则需使用串口转接头（串口转USB）。
 <br>
 3、（windows系统）检查设备的连接情况：计算机-管理-设备管理器-端口-显示"COM3”。
 <br>
 4、在Xshell内，新建会话，协议：SERIAL，Port：COM3，Baud Rate（波特率）：9600，Data Bits（数据位）：8位，Stop Bits（停止位）：1。
 <br>
 5、若无法进入配置界面，考虑是否是线有问题或者是无驱动。</p>
<h2>五、通过服务器进入BIOS进行基础配置</h2>
<p>通过进入服务器BIOS可以查看服务器硬件信息和一些基础设置等。
 <br>
 <strong>1、Main主菜单</strong>
 <br>
 ①Setup Utility：设置实用程序；②PXE：预引导执行环境；③Boot Manager：启动管理器；④Process Type：处理器型号；⑤Node Number：CPU数量；⑥Total Memory：内存大小。</p>
<p>原文链接: https://blog.csdn.net/weixin_46398572/article/details/140877048</p>]]></description><guid isPermaLink="false">/archives/a060784d-c39f-4e3c-b264-856151db7287</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Mon, 30 Sep 2024 09:10:46 GMT</pubDate></item><item><title><![CDATA[【RIP 路由信息协议】]]></title><link>https://wiki.dgfygk.com/archives/df0102f9-ec0b-4084-bcd7-e0ea910026e2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90RIP%20%E8%B7%AF%E7%94%B1%E4%BF%A1%E6%81%AF%E5%8D%8F%E8%AE%AE%E3%80%91&amp;url=/archives/df0102f9-ec0b-4084-bcd7-e0ea910026e2" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>RIP 路由信息协议</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_1">一、概述</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_2">1、定义</a></li>
 <li><a href="https://wiki.dgfygk.com#2_5">2、版本</a></li>
 <li><a href="https://wiki.dgfygk.com#3_9">3、优缺点</a></li>
 <li><a href="https://wiki.dgfygk.com#_16">二、工作原理</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_17">1、实现原理</a></li>
 <li><a href="https://wiki.dgfygk.com#2_22">2、收敛机制</a></li>
 <li><a href="https://wiki.dgfygk.com#_24">三、防止环路</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_25">1、水平分割</a></li>
 <li><a href="https://wiki.dgfygk.com#2_27">2、毒性逆转</a></li>
 <li><a href="https://wiki.dgfygk.com#3_29">3、抑制计时器</a></li>
 <li><a href="https://wiki.dgfygk.com#4__31">4、 触发更新</a></li>
</ul>
<p>
 <br>
</p>
<h2>一、概述</h2>
<h3>1、定义</h3>
<p>1.RIP基于<strong>距离矢量</strong> 算法，用于自治系统<strong>内部</strong> 的路由信息传递，使用<strong>跳数</strong> 作为主要的度量值来衡量到达目的网络的路由距离。
 <br>
 2.RIP协议通过<strong>UDP报文</strong> 进行路由信息的交换，使用的端口号为<strong>520</strong> ，允许最大跳数为<strong>15</strong> ，大于15的目的地被认为是<strong>不可达</strong>，当路径的跳数超过15，这条路径才从路由表中删除。</p>
<h3>2、版本</h3>
<p>1.RIPv1是最早期的版本，<strong>不支持</strong> 可变长度子网掩码VLSM，使用<strong>广播</strong> 形式发送更新消息。
 <br>
 2.RIPv2 <strong>支持VLSM</strong> ，可以使用<strong>组播</strong> 形式发送更新消息，提高了网络带宽的使用效率。
 <br>
 3.RIPng则是专为<strong>IPv6网络</strong>设计的版本。</p>
<h3>3、优缺点</h3>
<p><strong>优点</strong>
 <br>
 配置简单，易于理解和实施，特别适合<strong>小型网络</strong> 。
 <br>
 <strong>缺点</strong>
 <br>
 1.<strong>收敛速度慢</strong> ，不适合大型网络。
 <br>
 2.最大的跳数限制为15跳，超过则认为不可达，这在一定程度上<strong>限制了适用范围</strong> 。
 <br>
 3.RIP协议还会<strong>占用较多网络带宽</strong>，因为在路由更新期间会发送大量的信息。</p>
<h2>二、工作原理</h2>
<h3>1、实现原理</h3>
<p>1.RIP <strong>周期性</strong> 地向<strong>相邻</strong> 路由器<strong>广播</strong> 路由表，每个路由器都会维护一个路由表，记录到达各个目的地的最佳路径。
 <br>
 2.RIP协议规定，路由器之间每隔一段固定时间（默认为<strong>30</strong> 秒）会<strong>交换</strong> 一次路由信息。
 <br>
 3.当网络拓扑<strong>发生变化</strong> 时，路由器会根据新的路由信息更新自己的路由表，这一过程称为<strong>路由收敛</strong> 。
 <br>
 4.RIP路由协议用更新UNPDATES和请求REQUESTS这两种分组来传输信息，并且在给相邻路由器发出路由信息时，都会给每个路径加上<strong>内部距离</strong>。</p>
<h3>2、收敛机制</h3>
<p>路由器由于<strong>不知道网络全局情况</strong> ，必须依靠<strong>相邻</strong> 路由器来获取网络的可达信息，而且路由选择更新信息在网络上传播慢，距离向量路由选择算法有一个<strong>慢收敛</strong>问题，这个问题将导致不一致性产生。</p>
<h2>三、防止环路</h2>
<h3>1、水平分割</h3>
<p>1.路由器打开接口<strong>记录</strong> 路径是<strong>从哪个接口来</strong> 的，并且<strong>不向</strong> 此接口<strong>回传</strong>此路径。</p>
<h3>2、毒性逆转</h3>
<p>路由器从某个接口上<strong>接收</strong> 到某个网段的路由信息之后，将这个网段的<strong>跳数设为无限大</strong> 再发送出去，收到此种的路由信息后，接收方路由器会<strong>立刻抛弃</strong> 该路由，而<strong>不等待老化时间到</strong>。</p>
<h3>3、抑制计时器</h3>
<p>路由<strong>信息无效后</strong> 一段时间内，这条路由都处于<strong>抑制状态</strong> ，即在一定时间内<strong>不再接收</strong>关于同一目的地址的路由更新。</p>
<h3>4、 触发更新</h3>
<p>若网络中<strong>没有变化</strong> ，则按通常的<strong>30秒</strong>间隔发送更新信息；若有变化，则立即发送其新的路由表。</p>
<p>原文链接: https://blog.csdn.net/weixin_46398572/article/details/141161047</p>]]></description><guid isPermaLink="false">/archives/df0102f9-ec0b-4084-bcd7-e0ea910026e2</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Mon, 30 Sep 2024 09:10:26 GMT</pubDate></item><item><title><![CDATA[win10删除鼠标右键选项]]></title><link>https://wiki.dgfygk.com/archives/14901b87-14a5-460b-88dd-fa47f0e472f3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=win10%E5%88%A0%E9%99%A4%E9%BC%A0%E6%A0%87%E5%8F%B3%E9%94%AE%E9%80%89%E9%A1%B9&amp;url=/archives/14901b87-14a5-460b-88dd-fa47f0e472f3" width="1" height="1" alt="" style="opacity:0;">
<h4>鼠标右键菜单时，发现里面的选项特别多，找一下属性，半天找不到。删除一些不常用的选项，让右键菜单变得干净整洁。</h4>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fdd66bce8b1394046b3b077e0b5c4285b.png&amp;size=m" alt=""></p>
<h4>1、按下键盘上的”<strong>win+R</strong> “组合按键，调出”<strong>运行</strong> “对话框，输入”<strong>regedit</strong> “命令，点击”<strong>确定</strong>“按钮；</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fce441bb4bbf542d1abd89de99757221d.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>2、展开”<strong>HKEY_CLASSES_ROOT\</strong>”</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F861ea51aa4f14a649625bba9939cce9b.png&amp;size=m" alt=""></p>
<h4>3、找到”<strong>Directory</strong> “文件夹，进入下面的”<strong>shell</strong> “和”<strong>shellX</strong>“文件夹；</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa81a2da2ad614083a9ba4f41e8de40e2.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<h4>4、在”<strong>shell</strong>“文件夹就可以看到你所有右键的设置项次了，右键单击你要删除的右键选项， 选择删除。</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8c341b1241d44ad385e86b8d80febbc3.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbaf54d3daf09450bb29312a5d3b2b99c.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>瘦身后的右键</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb16e56e85b704c2ca5e7021dba1687f2.png&amp;size=m" alt=""></p>
<hr>
<hr>
<hr>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe0bca0cd2c23456b86cbbd21383f532d.jpeg&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/wochunyang/article/details/140465329</p>]]></description><guid isPermaLink="false">/archives/14901b87-14a5-460b-88dd-fa47f0e472f3</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Mon, 30 Sep 2024 09:10:03 GMT</pubDate></item><item><title><![CDATA[红米 ax6000 刷ImmortalWrt软路由官方分区教程]]></title><link>https://wiki.dgfygk.com/archives/84639974-2ed7-4e1f-a4ee-e40dbd8f97c5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BA%A2%E7%B1%B3%20ax6000%20%E5%88%B7ImmortalWrt%E8%BD%AF%E8%B7%AF%E7%94%B1%E5%AE%98%E6%96%B9%E5%88%86%E5%8C%BA%E6%95%99%E7%A8%8B&amp;url=/archives/84639974-2ed7-4e1f-a4ee-e40dbd8f97c5" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p><strong>刷机前的准备工作，我们需要把红米ax6000的wan口连接到现有网络中。并且在ax6000的lan口上连接上一台电脑。建议用网线将电脑和路由器连接起来，这样刷机会更稳定一些。</strong></p>
</blockquote>
<h3>一、系统降级</h3>
<p>首先要确定下路由器系统的版本，如果不是可以解锁的版本，那么需要在"系统设置"里，把当前系统版本手动降级为1.6.0。
 <br>
 <a href="https://download.csdn.net/download/cid1233/89683801">miwifi_rb06_firmware_1.0.60固件</a>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcb657c83db5e424baff7554262eb5cda.png%23pic_center&amp;size=m" alt="">
 <br>
 如果当前版本已经是可解锁的则可以忽略这一步。目前可解锁的版本号有1.0.60；1.0.48；1.0.28
 <br>
 路由器在刷机之前最好能初始化一次，并且设置成路由模式。</p>
<h3>二、获取stok</h3>
<p>登录到路由器的后台，在地址栏上方会生成一串stok的数值。我们需要把stok等于后的这串数字复制下来，这串数字是解锁ssh的关键，不过在每个机器上生成的值都不同，而且每次重启路由器以后这个值都会改变。所以每次路由器重启后，我们都需要重新复制一下这串代码。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe4e1973e18a649f09c673b885ca78405.png%23pic_center&amp;size=m" alt=""></p>
<h3>三、打开红米AX6000的开发者模式</h3>
<p>首先我们需要解锁下ax6000的开发者模式</p>
<p>1、复制以下代码</p>
<blockquote>
 <p>http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3B%20zz%3D%24%28dd%20if%3D%2Fdev%2Fzero%20bs%3D1%20count%3D2%202%3E%2Fdev%2Fnull%29%20%3B%20printf%20%27%A5%5A%25c%25c%27%20%24zz%20%24zz%20%7C%20mtd%20write%20-%20crash%20%3B%20</p>
</blockquote>
<p>注意把stok={token} 的字符{token}替换为路由器生成的stok值。</p>
<p>然后把代码复制到浏览器的地址栏里再回车，看到返回过来这样一串字符就表示代码注入成功了。</p>
<p>2、粘贴命令重启路由器</p>
<blockquote>
 <p>http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3b%20reboot%20%3b%20</p>
</blockquote>
<p>同样需要替换stok=后边的字符。
 <br>
 代码注入成功之后，网页同样会返回这样一串字符并且开始重启路由器。</p>
<h3>四、设置路由器的Bdata参数</h3>
<p>1、我们稍等两分钟等路由器重启好了之后，再次登录到路由器的后台。这时需要重新复制一下stok，因为此时路由器重启后stok的值已经改变。接下来的步骤是设置Bdata参数来永久开启telnet，在新打开的浏览器地址栏中输入以下代码。</p>
<blockquote>
 <p>http://192.168.31.1/cgi-bin/luci/;stok={token}/api/misystem/set_sys_time?timezone=%20%27%20%3B%20bdata%20set%20telnet_en%3D1%20%3B%20bdata%20set%20ssh_en%3D1%20%3B%20bdata%20set%20uart_en%3D1%20%3B%20bdata%20commit%20%3B%20</p>
</blockquote>
<p>同样将stok=token中的{token}替换成路由器新的stok即可</p>
<p>2、再次在浏览器里输入以下代码来重启路由器：</p>
<blockquote>
 <p>http://192.168.31.1/cgi-bin/luci/;stok=token/api/misystem/set_sys_time?timezone=%20%27%20%3b%20reboot%20%3b%20</p>
</blockquote>
<h3>五、登录telnet开启ssh</h3>
<p>现在我们已经开启了telnet，可以用telnet登录到路由器的后台。
 <br>
 我们打开Termius，选择telnet登录的方式。输入路由器ip地址 默认是192.168.31.1（这里是不用输入用户和密码）点击ok登录到telnet。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2eb1bf1374de4443b82d72dc009648ea.png%23pic_center&amp;size=m" alt="">
 <br>
 接下来我们需要复制粘贴一些代码到telnet里执行即可。</p>
<p>1.修改root密码为admin（不修改也可以通过SN计算默认密码)这里我们修改一下</p>
<pre><code class="prism language-bash">echo -e 'admin\nadmin' | passwd root
</code></pre>
<p>2、固化SSH</p>
<pre><code class="prism language-bash">bdata set boot_wait=on
bdata commit
nvram set ssh_en=1
nvram set telnet_en=1
nvram set uart_en=1
nvram set boot_wait=on
nvram commit
sed -i 's/channel=.*/channel="debug"/g' /etc/init.d/dropbear
/etc/init.d/dropbear restart
</code></pre>
<p>输入命令后没有反馈信息，不用担心，已经执行成功了。</p>
<p>3、永久开启SSH的代码（这样即使路由器重启也不会影响SSH）注意这步需要路由器能够联网。</p>
<pre><code class="prism language-bash">mkdir /data/auto_ssh &amp;&amp; cd /data/auto_ssh
curl -O https://cdn.jsdelivr.net/gh/lemoeo/AX6S@main/auto_ssh.sh
chmod +x auto_ssh.sh
uci set firewall.auto_ssh=include
uci set firewall.auto_ssh.type='script'
uci set firewall.auto_ssh.path='/data/auto_ssh/auto_ssh.sh'
uci set firewall.auto_ssh.enabled='1'
uci commit firewall
</code></pre>
<p>4、接下来还需要修改时区设置，输入：</p>
<pre><code class="prism language-bash">uci set system.@system[0].timezone='CST-8'
uci set system.@system[0].webtimezone='CST-8'
uci set system.@system[0].timezoneindex='2.84'
uci commit
</code></pre>
<p>5、最后一步，关闭开发/调试模式。在提示符后输入:</p>
<pre><code class="prism language-bash">mtd erase crash
</code></pre>
<p>6、然后输入reboot重启路由</p>
<pre><code class="prism language-bash">reboot
</code></pre>
<p>成功登录ssh之后，我们就相当于获取了路由器的最高权限。</p>
<h3>刷机教程</h3>
<p>先用ssh的方式登录到路由器。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff393664c4fb74c0384e34a1fd653afc4.png%23pic_center&amp;size=m" alt=""></p>
<h4>一、刷入过渡固件</h4>
<p>输入代码：</p>
<pre><code class="prism language-bash">cat /proc/cmdline
</code></pre>
<p>这一步需要路由器是正常联网的，首先 ssh 连接上红米 ax6000 ，执行上面的命令，查看返回的 firmware 等于 0 还是 1。</p>
<p>如果是 0 执行</p>
<pre><code class="prism language-bash">nvram set boot_wait=on
nvram set uart_en=1
nvram set flag_boot_rootfs=1
nvram set flag_last_success=1
nvram set flag_boot_success=1
nvram set flag_try_sys1_failed=0
nvram set flag_try_sys2_failed=0
nvram commit
cd /tmp
curl -L https://share.qust.me/d/%E8%B7%AF%E7%94%B1%E5%99%A8/redmi-ax6000/initramfs-factory.ubi -o initramfs-factory.ubi
ubiformat /dev/mtd9 -y -f /tmp/initramfs-factory.ubi
reboot -f
</code></pre>
<p>如果是 1 执行</p>
<pre><code class="prism language-bash">nvram set boot_wait=on
nvram set uart_en=1
nvram set flag_boot_rootfs=0
nvram set flag_last_success=0
nvram set flag_boot_success=1
nvram set flag_try_sys1_failed=0
nvram set flag_try_sys2_failed=0
nvram commit
cd /tmp
curl -L https://share.qust.me/d/%E8%B7%AF%E7%94%B1%E5%99%A8/redmi-ax6000/initramfs-factory.ubi -o initramfs-factory.ubi
ubiformat /dev/mtd8 -y -f /tmp/initramfs-factory.ubi
reboot -f
</code></pre>
<p>复制执行完就会重启进入过渡固件，过渡固件的</p>
<p>管理ip：192.168.15.1
 <br>
 用户名和密码：root/password
 <br>
 无线wifi名称：OpenWrt
 <br>
 无线wifi密码：password</p>
<h4>二、从过渡固件刷入 ImmortalWrt</h4>
<p>在刷写前需要设置下 ENV ，打开Termius登入到SSH，同样登录用户名 root，密码 password（输入密码不会显示输完回车即可）。</p>
<pre><code class="prism language-bash">fw_setenv boot_wait on
fw_setenv uart_en 1
fw_setenv flag_boot_rootfs 0
fw_setenv flag_last_success 1
fw_setenv flag_boot_success 1
fw_setenv flag_try_sys1_failed 8
fw_setenv flag_try_sys2_failed 8
</code></pre>
<p>然后打开系统-备份与升级，选择刷写固件。浏览里找到下载好的 openwrt 固件，选择上传。上传好后选择取消勾选保留当前配置然后选择继续。等待刷写完成自动重启，就会进入新的 openwrt 固件。</p>
<p>下载地址：
 <br>
 istore界面21.02新版https://sssddddff.lanzoup.com/b019sthje 密码:6wnv</p>
<p>18.06版本
 <br>
 官方版https://sssddddff.lanzoup.com/iTQAh10y22oj
 <br>
 大分区uboot版本 https://sssddddff.lanzoup.com/i5i0e10y235g</p>
<p>新的 openwrt 固件后台地址是 192.168.6.1 用户名和密码依然是 root 和 password，默认的网口 1 是 wan 口，剩下的都是 lan 口。</p>
<p>目前这个 openwrt 的固件已经集成了 hwnat，也就是硬件加速，这是我喜欢硬路由的地方，正常上网使用像是 NAT、PPOE 性能和效率都非常高，跑满正常 1000M 宽带 CPU 几乎不动。CPU 性能当然依然足够 1000M 外网，coremark 两万七千多分数，这个上篇也说过。</p>
<p>如果你想从当前 openwrt 固件刷到别的版本，同样是在系统-备份与升级，下载好的 openwrt 固件，选择上传，然后选择刷写固件，不勾选保留当前配置即可。当然如果只是同一个作者的固件只是更新版本，一般也可以保留配置直接升级的。</p>
<h3>刷回官方</h3>
<p>如果不想使用 openwrt 固件了或者一不小心刷错了无法开机，都可以是使用官方的修复工具刷回原厂，当然前提是你没修改过路由器的分区。</p>
<p>首先下载好官方修复工具和官方固件，使用小米路由器修复工具需要提前退出 Windows 自带的杀毒，我更推荐大家去下载安装个火绒这样更安全方便。</p>
<p>路由器进入恢复模式，路由器先用针按住后面的 reset 恢复按钮，再插上电源，持续顶住大约 8s，等到黄色指示灯变为黄色闪烁，就可以松开 reset 按钮，然后用网线连接 Windows 电脑，路由器这边连接 lan 口就可以，红米 ax6000 没有标 lan wan ，接 234 都可以。</p>
<p>打开解压好的小米路由器修复工具，要是有防火墙提示选择允许即可，然后选择本地上传下载好的官方固件 ；然后网卡选择：请选择与路由器相连的以太网网卡； 现在再点下一步就会自动开始刷机，看到发送完成就说明刷机完成了。然后我们等待路由黄灯变为蓝灯闪烁。我们就可以拔下电源再重新插上路由器，这样就完成了，路由器就回到了官方的固件。</p>
<p>重置、升级、刷机后如何恢复 SSH
 <br>
 无论你是升级了系统、还是恢复了出厂、还是刷了别的版本的系统或 openwrt 再刷回来，telnet 其实都是打开的，因为已经写在了 bdata 分区。不过 telnet 密码会恢复成默认的，我们需要通过路由器后台右下角的 SN 号来计算。这里有两个网站直接在线计算（安全的离线计算）：</p>
<p><a href="https://www.oxygen7.cn/miwifi/">Oxygen7</a></p>
<p><a href="https://miwifi.dev/ssh">Miwifidev</a></p>
<p>telnet 连上以后，执行：</p>
<pre><code class="prism language-bash">sed -i 's/channel=.*/channel="debug"/g' /etc/init.d/dropbear
/etc/init.d/dropbear restart
</code></pre>
<p>就开启了 ssh，ssh 默认密码就是上面计算出来的一样。</p>
<p>原文链接: https://blog.csdn.net/cid1233/article/details/141617112</p>]]></description><guid isPermaLink="false">/archives/84639974-2ed7-4e1f-a4ee-e40dbd8f97c5</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Mon, 30 Sep 2024 09:09:47 GMT</pubDate></item><item><title><![CDATA[HP笔记本电脑进入BIOS的方法主要有两种，它们使用场合不同]]></title><link>https://wiki.dgfygk.com/archives/d0e8c265-868a-4571-9020-a012c19e5395</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=HP%E7%AC%94%E8%AE%B0%E6%9C%AC%E7%94%B5%E8%84%91%E8%BF%9B%E5%85%A5BIOS%E7%9A%84%E6%96%B9%E6%B3%95%E4%B8%BB%E8%A6%81%E6%9C%89%E4%B8%A4%E7%A7%8D%EF%BC%8C%E5%AE%83%E4%BB%AC%E4%BD%BF%E7%94%A8%E5%9C%BA%E5%90%88%E4%B8%8D%E5%90%8C&amp;url=/archives/d0e8c265-868a-4571-9020-a012c19e5395" width="1" height="1" alt="" style="opacity:0;">
<p>BIOS（基本输入输出系统）是一种实用程序，它在你按下电源按钮后启动并加载操作系统。无论是要更新HP笔记本电脑的BIOS系统，还是清除前一个系统中的错误，第一步都是进入BIOS实用程序。</p>
<p>在按键输入BIOS设置并对其进行修改之前，最好了解在HP笔记本电脑上输入BIOS实用程序的确切过程。</p>
<p>在本指南中，我们列出了两种方法，你可以通过热键或Windows高级开始菜单恢复设置在HP笔记本电脑上输入BIOS。如果你无法进入BIOS，我们还启动了一些基本的故障排除选项。</p>
<h2><strong>如何在HP笔记本电脑上进入BIOS</strong></h2>
<p>无论你的HP笔记本电脑中有一个令人恼火的错误，想要修改引导顺序，还是更改任何其他硬件设置——首先，你都需要进入BIOS。以下是在HP Windows笔记本电脑上输入BIOS的两种方法。让我们开始：</p>
<h3><strong>通过热键进入BIOS</strong>​</h3>
<p>你可以访问BIOS实用程序的热键因品牌而异。<strong>例如，对于Acer、ASUS和Dell，你需要按F2；HP按F10或Esc，联想F1，以及三星的F1/F2/F3。</strong></p>
<p>1、闭HP笔记本电脑。几秒钟后，按下电源按钮打开笔记本电脑。</p>
<p>2、在启动屏幕上，按F10或Esc键数次，直到启动菜单出现在屏幕上。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc060f33134d8d194c2e4c91d666f1393.png&amp;size=m" alt=""></p>
<p>3、使用键盘上的向下键点击启动菜单中的"BIOS设置”。你将访问屏幕上的BIOS实用程序。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fce4643189a16c763fe8a822483fb6933.png&amp;size=m" alt=""></p>
<p>​</p>
<p>注意：在大多数HP笔记本电脑中，<strong>只需在启动屏幕上按一次F10键即可访问BIOS实用程序</strong>。但在其他制造商的某些型号中，你需要多次按下所需的热键。</p>
<h3><strong>通过Windows 10开始菜单进入BIOS</strong></h3>
<p>有时，启动屏幕只需1到2秒就可以消失在你面前——这要归功于先进的技术，使事情变得比以往任何时候都快。在这种情况下，按下热键将无效，你需要从Windows"开始"菜单访问BIOS实用程序。</p>
<p>1、从「开始」菜单转到Windows的"设置”。</p>
<p>2、点击"设置"中的"更新和安全”，然后选择"恢复”。单击"恢复"部分下的"立即重新启动”。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F216c85ec9dd218deb3661b8e594d769b.png&amp;size=m" alt=""></p>
<p>3、之后，你的电脑将重新启动。从屏幕菜单中，选择故障排除。</p>
<p>单击高级选项，然后选择UEFI固件设置。通过这种方式，你可以在HP笔记本电脑上进入BIOS。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6c7f943ad755d92f6c4cf893eb090f69.png&amp;size=m" alt=""></p>
<h2><strong>进入BIOS疑难解答</strong></h2>
<p>如前所述，快速启动可能会阻止你进入BIOS实用程序，因为你无法快速按下热键。因此，你可以始终禁用快速启动选项，以匹配按下热键的正确时间。以下是操作方法：</p>
<p>1、转到"控制面板"中的"电源选项”，然后单击"选择电源按钮的功能”。</p>
<p>​2、点击"更改当前不可用的设置"并查找快速启动选项以禁用它。</p>
<h2><strong>结论</strong></h2>
<p>BIOS是硬件和软件之间的桥梁。它可以管理笔记本电脑或PC的所有输入和输出设备，并在你按下电源按钮后加载操作系统。因此，要修改引导顺序、管理硬件设置和更改CMOS选项，你需要访问BIOS实用程序。你可以按热键（F10或Esc）或通过Windows恢复选项在HP笔记本电脑上输入BIOS。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/wyxtx/article/details/135175459</p>]]></description><guid isPermaLink="false">/archives/d0e8c265-868a-4571-9020-a012c19e5395</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Mon, 30 Sep 2024 09:09:22 GMT</pubDate></item><item><title><![CDATA[SSL vpn easy connect 选路连接失败，可能当前连接网络异常，请稍后重试]]></title><link>https://wiki.dgfygk.com/archives/12623875-6da2-4c10-8ae3-0b0631fe3ca1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SSL%20vpn%20easy%20connect%20%E9%80%89%E8%B7%AF%E8%BF%9E%E6%8E%A5%E5%A4%B1%E8%B4%A5%EF%BC%8C%E5%8F%AF%E8%83%BD%E5%BD%93%E5%89%8D%E8%BF%9E%E6%8E%A5%E7%BD%91%E7%BB%9C%E5%BC%82%E5%B8%B8%EF%BC%8C%E8%AF%B7%E7%A8%8D%E5%90%8E%E9%87%8D%E8%AF%95&amp;url=/archives/12623875-6da2-4c10-8ae3-0b0631fe3ca1" width="1" height="1" alt="" style="opacity:0;">
<p>选路连接失败，可能当前连接网络异常，请稍后重试</p>
<p>怎么办？</p>
<p>1）出现"选路连接失败，可能当前连接网络异常，请稍后重试"的提示信息，请确认服务器地址是否正确。</p>
<p>2)在电脑端使用VPN如出现"VPN已连接，但是流量为"0”，也访问不了校内应用"的问题，使用Windows客户端上使用的"SSL VPN诊断修复工具'进行快速修复，下载链接： <a href="https://download.sangfor.com.cn/download/product/sslvpn/SangforHelperToolInstaller.exe" title="https://download.sangfor.com.cn/download/product/sslvpn/SangforHelperToolInstaller.exe">https://download.sangfor.com.cn/download/product/sslvpn/SangforHelperToolInstaller.exe</a></p>
<p>3)如方案2还不能解决，请卸载当前版本，<strong>删除C:\Program Files (x86)\Sangfor 这个文件夹</strong>，然后<strong>重新安装最新版本</strong>，一般即可解决。</p>
<p>4）“SSL VPN诊断修复工具"修复时报错”<em>ECAGENT修复失败</em> “，“<em>未能正常连接ECAGENT</em>时"怎么办呢？</p>
<p><a href="https://bbs.sangfor.com.cn/forum.php?mod=viewthread&amp;tid=118989" title="WIN10 未能正常连接ecagent怎么办 - SSL VPN/EMM - 深信服社区">WIN10 未能正常连接ecagent怎么办 - SSL VPN/EMM - 深信服社区</a></p>
<p>|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-|
 <br>
 | 您好，麻烦您做一下以下排查： 1、换其他的电脑登录同一个帐号测试是否正常，便于定位是帐号的问题还是终端电脑的问题，如果换了其他的电脑可以正常使用的话，那么定位是终端电脑的问题，请执行下面的操作 2、如果是windows系统，建议您用windows环境诊断修复工具扫描测试，相关工具和文档链接如下： windows环境诊断修复工具链接：<a href="http://download.sangfor.com.cn/d" title="http://download.sangfor.com.cn/d">http://download.sangfor.com.cn/d</a> … erToolInstaller.exe windows环境诊断修复工具文档： <a href="https://bbs.sangfor.com.cn/plugin.php?id=sangfor_databases:index&amp;mod=viewdatabase&amp;tid=37745" title="https://bbs.sangfor.com.cn/plugi ... abase&amp;tid=37745">https://bbs.sangfor.com.cn/plugi … abase\&amp;tid=37745</a> 3、请检查防火墙和杀毒软件是否有退出，如果没有的话，建议可以退出一下再进行安装EasyConnect客户端进行测试 4、右击以管理员身份运行安装测试。 5、需要麻烦您核对一下VPN设备版本是多少，win10系统要求VPN设备版本7.1及以上才支持，如果版本不是7.1需要将VPN设备进行升级，或者加载支持包解决 6、如果版本是支持的，建议您使用下面的安装包测试一下 VPN设备版本是6.9-7.6.6R1：<a href="http://download.sangfor.com.cn/download/product/sslvpn/EasyConnectInstaller_6.9-7.6.6R1.exe" title="http://download.sangfor.com.cn/d ... ler_6.9-7.6.6R1.exe">http://download.sangfor.com.cn/d … ler_6.9-7.6.6R1.exe</a> VPN设备版本是7.6.7及以上：<a href="http://download.sangfor.com.cn/download/product/sslvpn/EasyConnectInstaller_7.6.7" title="http://download.sangfor.com.cn/d ... nectInstaller_7.6.7">http://download.sangfor.com.cn/d … nectInstaller_7.6.7</a>及以上版本.exe 如果以上方法均已排查还是未解决您的问题的话，那么您的问题需要工程师远程协助，麻烦您关注下私信内容，并私信提供下相关的信息，这边协调工程师远程协助您处理 谢谢！ |</p>
<p>原文链接: https://blog.csdn.net/fanghongxia2008/article/details/140505442</p>]]></description><guid isPermaLink="false">/archives/12623875-6da2-4c10-8ae3-0b0631fe3ca1</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Mon, 30 Sep 2024 09:09:12 GMT</pubDate></item><item><title><![CDATA[小米路由器刷OpenWrt教程]]></title><link>https://wiki.dgfygk.com/archives/c2970659-f50e-4218-8744-b05e09c067c8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E7%B1%B3%E8%B7%AF%E7%94%B1%E5%99%A8%E5%88%B7OpenWrt%E6%95%99%E7%A8%8B&amp;url=/archives/c2970659-f50e-4218-8744-b05e09c067c8" width="1" height="1" alt="" style="opacity:0;">
<h2>小米路由器刷OpenWrt教程</h2>
<h4>思路</h4>
<p>使用红米路由器AC2100刷机到OpenWrt，扩展一些路由器插件功能，主路由LAN口接红米路由器LAN口，开放无线功能及LAN口给其它设备连接，同时其它设备可自定义网关指定到OpenWrt来进行上网。</p>
<p>步骤：（全程关闭360 安全管家 火绒等杀毒软件）</p>
<h6>一、路由器开启SSH</h6>
<p>1、红米AC2100路由器版本降级值2.0.7*版本或者找根牙签RESET重置一下路由器；
 <br>
 重置后一般默认路由器ip为192.168.31.1，按引导进入后获取登陆后路由器web页面的stok值，打开新页面执行如下两段链接注入脚本开启SSH</p>
<pre><code class="prism language-powershell">http://192.168.31.1/cgi-bin/luci/;stok=7a8f1b4cd5f316ec3a413ecfe3da92bd/api/misystem/set_config_iotdev?bssid=Xiaomi&amp;user_id=longdike&amp;ssid=-h%3B%20nvram%20set%20ssh_en%3D1%3B%20nvram%20commit%3B%20sed%20-i%2

http://192.168.31.1/cgi-bin/luci/;stok=7a8f1b4cd5f316ec3a413ecfe3da92bd/api/misystem/set_config_iotdev?bssid=Xiaomi&amp;user_id=longdike&amp;ssid=-h%3B%20echo%20-e%20'admin%5Cnadmin'%20%7C%20passwd%20root%3B
</code></pre>
<p>其中stok替换成路由器登陆后浏览器地址栏对应的32位MD5值。
 <br>
 执行后，路由器的22端口打开了，打不开的自行排查指令是否复制有误，stoke是否有效，系统版本是否不对，使用winpcap或者本地虚拟机或者WSL等环境，反正只要能在本地执行SSH命令即可。
 <br>
 在linux环境执行ssh root@192.168.31.1或者Shell、SecureCRT环境连接路由器22端口，账号root 密码admin
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1012c3a163fe1095e546a348218ec762.png&amp;size=m" alt=""></p>
<h6>二、刷入breed软件</h6>
<p>刷入breed的目的是为了防止直接刷系统导致路由器变砖，相当于一个比较耐折腾的系统，后续刷机失败等都可以通过重置回到这个软件里，既可以刷回原来的官方路由器系统也可以刷其他系统，因此很多教程都会优先推荐路由器先刷breed，再刷其他系统固件。
 <br>
 Breed官网下载对应的Breed文件：breed-mt7621-xiaomi-r3g.bin
 <br>
 https://breed.hackpascal.net/breed-mt7621-xiaomi-r3g.bin，如果是其他路由器的在这个网站上自行查找。
 <br>
 拷贝至路由器里面，通过SCP，或者连接xftp文件上传等方式，拷贝到/tmp/目录下
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdd21c95dc41e770aaff6ae08a647a2d2.png&amp;size=m" alt="">进入路由器执行命令，写入breed文件
 <br>
 nvram set uart_en=1\&amp;\&amp;nvram set bootdelay=5\&amp;\&amp;nvram set flag_try_sys1_failed=1\&amp;\&amp;nvram commit
 <br>
 mtd -r write breed-mt7621-xiaomi-r3g.bin Bootloader
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2e052a21542550d3f97d83d1f217fc8f.png&amp;size=m" alt="">等待路由器重启
 <br>
 <strong>拔掉路由器电源，按住reset同时接上电源等双黄灯闪烁后松开</strong> , 浏览器输入 192.168.1.1 即可进入breed，
 <br>
 <strong>建议先把WLAN进线口网线拔了</strong> ，光猫大多数都是192.168.1.1，避免两个IP冲突，路由器网线和电脑连着即可，注意，一定是先断电源再按reset，然后再接电源等双黄灯闪烁后松开，输入192.168.1.1不是这个页面重复之前的步骤。
 <br>
 注意：一定是先按reset再接电源，不然会恢复到小米路由器默认的出厂设置，breed白刷，又得重复之前的步骤
 <br>
 重启后进入192.168.1.1
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff7c573361bc79d49ed41bb1eb700a6a9.png&amp;size=m" alt="">
 <br>
 到这里已经刷入breed了，修改两个设置
 <br>
 环境变量编辑：增加xiaomi.r3g.bootfw 2报错
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F583a57e673b70f35983df4ab93fc9b59.png&amp;size=m" alt="">
 <br>
 小米 R3G Bdata增加两个值，开启breed的ssh连接。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1a47fadb3454bc603c2baf066e2df33b.png&amp;size=m" alt="">
 <br>
 固件更新，刷入openwrt相关固件
 <br>
 小米固件地址：
 <br>
 https://openwrt.org/toh/xiaomi/xiaomi_redmi_router_ac2100#tab__using_javascript_in_the_web_console
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6c116b58827c7430804d841e5346a263.png&amp;size=m" alt="">
 <br>
 下载官方的固件，一个xx-kernel1 一个xx-rootfs0.bin文件，闪存布局选择小米R3G OpenWrt</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F10f9890d970ef774122e18dd0de6454c.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F655a3bb7a26ff3b1fbc0fe54f3dafc2a.png&amp;size=m" alt="">
 <br>
 点击更新，重启后进入192.168.1.1，openwrt刷机完成。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe24b435dcd5e78bea250fca3568ebedf.png&amp;size=m" alt=""></p>
<h6>三、刷入OpenWrt固件</h6>
<p>通过System刷新固件xxx-sysupgrade
 <br>
 选择OpenWRT的sysupgrade固件 刷新，固件也来自于官网
 <br>
 https://openwrt.org/toh/xiaomi/xiaomi_redmi_router_ac2100
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F885e7d3c6f31eac0f08bafeae980fa1e.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdf0e9be33dd0ea8b23814b40baa39367.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F79a1477175f37bc0e7c9f1ff496adc15.png&amp;size=m" alt="">
 <br>
 刷新后重新进入系统
 <br>
 选择自己对应的版本，下载中文语言包，能联网也可以直接搜索install安装
 <br>
 https://downloads.openwrt.org/releases/
 <br>
 https://downloads.openwrt.org/releases/2x.0x.x/packages/x86_64/luci/
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3960ea4d44dddab320a71dabae314e44.png&amp;size=m" alt="">
 <br>
 System-software - Upload Package 选择下载的语言包，更新，更新后刷新页面。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa734a96e36b6053566b332e6fc7d87f6.png&amp;size=m" alt="">
 <br>
 由于后续操作可能存在违规现象，就不展示了，自行搜索相关插件。</p>
<p>参考</p>
<p>原文链接: https://blog.csdn.net/qq_15708851/article/details/139738100</p>]]></description><guid isPermaLink="false">/archives/c2970659-f50e-4218-8744-b05e09c067c8</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 30 Sep 2024 09:08:13 GMT</pubDate></item><item><title><![CDATA[四，接口类型和网络协议]]></title><link>https://wiki.dgfygk.com/archives/09e5edbc-5988-4802-b873-79466917e84f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9B%9B%EF%BC%8C%E6%8E%A5%E5%8F%A3%E7%B1%BB%E5%9E%8B%E5%92%8C%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE&amp;url=/archives/09e5edbc-5988-4802-b873-79466917e84f" width="1" height="1" alt="" style="opacity:0;">
<h3>四，接口类型和网络协议</h3>
<p>在前后端开发中，常用的接口类型/协议主要涉及网络协议和应用程序接口（API）两个方面。以下是对这些常用接口类型/协议的详细归纳：</p>
<h4>一、网络协议</h4>
<p>网络协议是前后端通信的基础，它们定义了数据如何在计算机网络中传输和交换。在前后端开发中，常用的网络协议包括：</p>
<ol>
 <li>
  <p><strong>HTTP（Hypertext Transfer Protocol）</strong>：</p>
 </li>
 <li>
  <p>是一种用于在客户端和服务器之间传输超文本的应用层协议，常用于Web浏览器与Web服务器之间的通信。</p>
 </li>
 <li>
  <p>支持多种请求方法，如GET、POST、PUT、DELETE等，用于实现数据的增删改查等操作。</p>
 </li>
 <li>
  <p>HTTP/1.1和HTTP/2是当前广泛使用的版本，其中HTTP/2引入了头部压缩、服务器推送等特性，提高了传输效率。</p>
 </li>
 <li>
  <p><strong>HTTPS（HTTP Secure）</strong>：</p>
 </li>
 <li>
  <p>是HTTP的加密版本，通过SSL/TLS协议对HTTP传输的数据进行加密和解密，提供了数据传输的安全性。</p>
 </li>
 <li>
  <p>在前后端通信中，特别是涉及敏感信息（如用户密码、支付信息等）时，应优先使用HTTPS协议。</p>
 </li>
 <li>
  <p><strong>WebSocket</strong>：</p>
 </li>
 <li>
  <p>是一种在单个TCP连接上进行全双工通讯的协议，常用于实现实时通信功能。</p>
 </li>
 <li>
  <p>WebSocket协议使得客户端和服务器之间的数据交换变得更加简单和高效，适合需要实时互动的应用场景。</p>
 </li>
</ol>
<h4>二、应用程序接口（API）</h4>
<p>API是前后端之间数据交换的桥梁，定义了双方交互的方式和规则。在前后端开发中，常用的API接口类型包括：</p>
<ol>
 <li>
  <p><strong>RESTful API</strong>：</p>
 </li>
 <li>
  <p>基于REST（Representational State Transfer）架构风格设计的API，是目前最流行的API设计风格之一。</p>
 </li>
 <li>
  <p>RESTful API使用HTTP协议，通过不同的请求方法（GET、POST、PUT、DELETE等）和URL路径来表示不同的操作和资源。</p>
 </li>
 <li>
  <p>RESTful API具有轻量级、跨平台、跨语言的特点，易于实现和维护。</p>
 </li>
 <li>
  <p><strong>GraphQL</strong>：</p>
 </li>
 <li>
  <p>一种用于查询和操作数据的查询语言，它允许客户端精确地指定它们需要的数据，而不是像REST那样返回整个数据集。</p>
 </li>
 <li>
  <p>GraphQL API提供了一种更高效、更灵活的数据获取方式，特别适用于需要高度定制化和实时性强的应用场景。</p>
 </li>
 <li>
  <p><strong>RPC（远程过程调用）API</strong>：</p>
 </li>
 <li>
  <p>支持多种协议（如HTTP、TCP、UDP等）和多种数据传输方式（如Json、XML、Binary、Protobuf等），可以像调用本地方法一样调用远程服务器上的方法。</p>
 </li>
 <li>
  <p>RPC API在分布式系统和微服务架构中应用广泛，但相对于RESTful API和GraphQL来说，其实现和维护复杂度较高。</p>
 </li>
</ol>
<h4>三、其他常用协议和接口</h4>
<p>除了上述提到的协议和接口外，还有一些其他在前后端开发中常用的协议和接口，如：</p>
<ul>
 <li><strong>JSON-RPC</strong>：一种基于JSON（JavaScript Object Notation）的远程过程调用协议，它允许跨语言调用远程服务。</li>
 <li><strong>SOAP（Simple Object Access Protocol）</strong>：一种基于XML的协议，用于在计算机网络中交换结构化信息，但相对于RESTful API来说较为笨重和复杂。</li>
 <li><strong>Socket.IO</strong>：一个基于WebSocket的库，提供了更丰富的功能和更好的兼容性，适用于需要实时通信和推送功能的Web应用。</li>
</ul>
<p>综上所述，前后端开发中常用的接口类型/协议主要包括HTTP/HTTPS、WebSocket等网络协议以及RESTful API、GraphQL、RPC等API接口类型。这些协议和接口共同构成了前后端之间数据交换的基石，支撑着各种应用的正常运行。</p>
<p>原文链接: https://blog.csdn.net/2401_83447580/article/details/141528823</p>]]></description><guid isPermaLink="false">/archives/09e5edbc-5988-4802-b873-79466917e84f</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 30 Sep 2024 09:07:31 GMT</pubDate></item><item><title><![CDATA[Spring Boot中使用ThreadPoolTaskScheduler实现轻量级多线程定时任务]]></title><link>https://wiki.dgfygk.com/archives/5cdf1004-0029-49dc-adca-d51205ae47fa</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%E4%B8%AD%E4%BD%BF%E7%94%A8ThreadPoolTaskScheduler%E5%AE%9E%E7%8E%B0%E8%BD%BB%E9%87%8F%E7%BA%A7%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1&amp;url=/archives/5cdf1004-0029-49dc-adca-d51205ae47fa" width="1" height="1" alt="" style="opacity:0;">
<h3>引言</h3>
<p>在Java开发中，Spring Boot提供了多种方式来执行定时任务，如<code>@Scheduled</code>注解和<code>TaskScheduler</code>。当需要执行多线程定时任务时，<code>ThreadPoolTaskScheduler</code>是一个轻量级的解决方案。本文将通过一个具体的业务场景，介绍如何使用<code>ThreadPoolTaskScheduler</code>来实现多线程定时任务。</p>
<h3>业务场景</h3>
<p>假设我们需要开发一个电商平台，该平台需要定期执行以下任务：</p>
<ol>
 <li>
  <p>库存检查：每天检查库存量，如果低于阈值，则自动触发补货。</p>
 </li>
 <li>
  <p>订单审核：每小时审核新订单，自动确认有效订单。</p>
 </li>
</ol>
<p>这些任务需要并行执行，以提高效率。</p>
<h3>实现步骤</h3>
<h4>1. 配置ThreadPoolTaskScheduler</h4>
<p>首先，我们需要配置<code>ThreadPoolTaskScheduler</code>。在Spring Boot应用中，我们可以创建一个配置类来实现这一点。</p>
<p>java</p>
<pre><code>import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ThreadPoolTaskScheduler taskScheduler) {
        taskScheduler.setPoolSize(10); // 设置线程池大小
        taskScheduler.setThreadNamePrefix("task-scheduler-");
        taskScheduler.initialize();
    }
}
</code></pre>
<h4>2. 创建定时任务</h4>
<p>接下来，我们创建具体的定时任务。我们将实现两个服务：<code>InventoryCheckService</code>和<code>OrderAuditService</code>。</p>
<p>java</p>
<pre><code>import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class InventoryCheckService {

    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
    public void checkInventory() {
        System.out.println("执行库存检查任务");
        // 库存检查逻辑
    }
}
</code></pre>
<p>java</p>
<pre><code>import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class OrderAuditService {

    @Scheduled(cron = "0 0 * * * ?") // 每小时执行
    public void auditOrders() {
        System.out.println("执行订单审核任务");
        // 订单审核逻辑
    }
}
</code></pre>
<h4>3. 多线程执行定时任务</h4>
<p>为了使定时任务并行执行，我们可以在<code>Scheduled</code>注解中设置<code>taskScheduler</code>属性。</p>
<p>java</p>
<pre><code>import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class InventoryCheckService {

    @Autowired
    private TaskScheduler taskScheduler;

    @Scheduled(cron = "0 0 1 * * ?", taskScheduler = "threadPoolTaskScheduler")
    public void checkInventory() {
        taskScheduler.execute(() -&gt; {
            System.out.println("执行库存检查任务");
            // 库存检查逻辑
        });
    }
}
</code></pre>
<p>java</p>
<pre><code>import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderAuditService {

    @Autowired
    private TaskScheduler taskScheduler;

    @Scheduled(cron = "0 0 * * * ?", taskScheduler = "threadPoolTaskScheduler")
    public void auditOrders() {
        taskScheduler.execute(() -&gt; {
            System.out.println("执行订单审核任务");
            // 订单审核逻辑
        });
    }
}
</code></pre>
<h4>4. 启动定时任务</h4>
<p>确保您的Spring Boot应用已经启用了定时任务的支持，通过在启动类上添加<code>@EnableScheduling</code>注解。</p>
<p>java</p>
<pre><code>import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
</code></pre>
<p>
 <br>
</p>
<p><strong>异常处理</strong></p>
<p>在处理定时任务中的异常情况时，确保系统稳定性的关键在于正确地捕获和处理这些异常。以下是一些最佳实践：</p>
<h4>1. 使用<code>try-catch</code>块</h4>
<p>确保每个定时任务都被<code>try-catch</code>块包裹，以便在任务执行过程中捕获任何未预料到的异常。</p>
<p>java</p>
<pre><code>@Scheduled(fixedRate = 5000)
public void performTask() {
    try {
        // 任务逻辑
    } catch (Exception e) {
        // 异常处理逻辑
        logger.error("Error executing scheduled task", e);
    }
}
</code></pre>
<h4>2. 记录异常</h4>
<p>当异常发生时，记录异常信息对于调试和监控系统状态非常重要。</p>
<p>java</p>
<pre><code>import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyScheduledTask {

    private static final Logger logger = LoggerFactory.getLogger(MyScheduledTask.class);

    @Scheduled(cron = "0 * * * * *")
    public void scheduledTask() {
        try {
            // 任务逻辑
        } catch (Exception ex) {
            logger.error("Scheduled task failed", ex);
        }
    }
}
</code></pre>
<h4>3. 重试机制</h4>
<p>对于某些类型的错误，可以实施重试策略。例如，如果一个任务因为网络问题失败，可能在稍后重试时会成功。</p>
<p>java</p>
<pre><code>@Scheduled(cron = "0 * * * * *")
public void scheduledTaskWithRetry() {
    try {
        // 任务逻辑
    } catch (TransientException te) {
        // 可以重试的异常
        scheduledTaskWithRetry(); // 重试逻辑
    } catch (Exception e) {
        // 非重试异常
        logger.error("Scheduled task failed", e);
    }
}
</code></pre>
<h4>4. 错误隔离</h4>
<p>如果可能，隔离可能导致异常的任务，以防止它们影响其他任务。</p>
<p>java</p>
<pre><code>@Scheduled(cron = "0 * * * * *")
public void isolatedTask() {
    Thread taskThread = new Thread(() -&gt; {
        try {
            // 任务逻辑
        } catch (Exception e) {
            logger.error("Isolated scheduled task failed", e);
        }
    });
    taskThread.setUncaughtExceptionHandler((thread, throwable) -&gt; logger.error("Thread failed", throwable));
    taskThread.start();
}
</code></pre>
<h4>5. 任务持久化</h4>
<p>对于关键任务，可以考虑将任务持久化到数据库中，以便在系统崩溃后能够重新启动任务。</p>
<p>java</p>
<pre><code>@Scheduled(cron = "0 * * * * *")
public void persistentTask() {
    try {
        // 任务逻辑
    } catch (Exception e) {
        logger.error("Persistent scheduled task failed", e);
        // 将任务状态标记为失败，并持久化
    }
}
</code></pre>
<h4>6. 使用<code>@Async</code>进行异步处理</h4>
<p>使用<code>@Async</code>注解将任务提交到<code>Executor</code>，这样可以异步处理任务，并避免阻塞主线程。</p>
<p>java</p>
<pre><code>@Async
@Scheduled(cron = "0 * * * * *")
public void asyncScheduledTask() {
    try {
        // 任务逻辑
    } catch (Exception e) {
        logger.error("Async scheduled task failed", e);
    }
}
</code></pre>
<h4>7. 超时机制</h4>
<p>为任务设置合理的超时时间，防止某些任务长时间占用资源。</p>
<p>java</p>
<pre><code>@Scheduled(cron = "0 * * * * *")
public void timedTask() {
    try {
        // 任务逻辑
    } catch (TimeoutException te) {
        logger.warn("Scheduled task timed out", te);
    }
}
</code></pre>
<h4>8. 监控和报警</h4>
<p>实现监控机制，当任务失败时触发报警，以便及时响应。</p>
<p>java</p>
<pre><code>@Scheduled(cron = "0 * * * * *")
public void monitoredTask() {
    try {
        // 任务逻辑
    } catch (Exception e) {
        logger.error("Monitored scheduled task failed", e);
        // 发送报警
    }
}
</code></pre>
<h4>9. 回滚机制</h4>
<p>对于涉及事务的任务，确保在异常时能够进行回滚。</p>
<p>java</p>
<pre><code>@Transactional
@Scheduled(cron = "0 * * * * *")
public void transactionalTask() {
    try {
        // 任务逻辑
    } catch (Exception e) {
        logger.error("Transactional scheduled task failed", e);
        // 事务回滚
    }
}
</code></pre>
<h4>10. 错误恢复策略</h4>
<p>制定错误恢复策略，比如重试、重置状态、通知管理员等。</p>
<p>java</p>
<pre><code>@Scheduled(cron = "0 * * * * *")
public void recoveryTask() {
    try {
        // 任务逻辑
    } catch (Exception e) {
        recoverFromError(); // 自定义恢复逻辑
    }
}
</code></pre>
<p>通过实施上述策略，可以显著提高定时任务的稳定性和可靠性，确保系统在面对异常情况时能够保持稳定运行。</p>
<h3>结论</h3>
<p>通过使用<code>ThreadPoolTaskScheduler</code>，我们可以轻松地在Spring Boot应用中实现轻量级的多线程定时任务。这种方法不仅提高了任务执行的效率，而且使代码结构更加清晰。</p>
<p>原文链接: https://blog.csdn.net/h356363/article/details/142628509</p>]]></description><guid isPermaLink="false">/archives/5cdf1004-0029-49dc-adca-d51205ae47fa</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:48:18 GMT</pubDate></item><item><title><![CDATA[深入理解MySQL InnoDB中的B+索引机制]]></title><link>https://wiki.dgfygk.com/archives/d53fbf91-5682-40d5-bd03-6cc4f66caf0a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3MySQL%20InnoDB%E4%B8%AD%E7%9A%84B%2B%E7%B4%A2%E5%BC%95%E6%9C%BA%E5%88%B6&amp;url=/archives/d53fbf91-5682-40d5-bd03-6cc4f66caf0a" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81InnoDB%E4%B8%AD%E7%9A%84B%2B%20%E6%A0%91%E7%B4%A2%E5%BC%95%E4%BB%8B%E7%BB%8D">一、InnoDB中的B+ 树索引介绍</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E8%81%9A%E7%B0%87%E7%B4%A2%E5%BC%95">二、聚簇索引</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%B8%80%EF%BC%89%E4%BD%BF%E7%94%A8%E8%AE%B0%E5%BD%95%E4%B8%BB%E9%94%AE%E5%80%BC%E7%9A%84%E5%A4%A7%E5%B0%8F%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F">（一）使用记录主键值的大小进行排序</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%E9%A1%B5%E5%86%85%E8%AE%B0%E5%BD%95%E6%8E%92%E5%BA%8F">页内记录排序</a></p>
<p><a href="https://wiki.dgfygk.com#%E9%A1%B5%E4%B9%8B%E9%97%B4%E7%9A%84%E6%8E%92%E5%BA%8F">页之间的排序</a></p>
<p><a href="https://wiki.dgfygk.com#%E7%9B%AE%E5%BD%95%E9%A1%B9%E9%A1%B5%E7%9A%84%E6%8E%92%E5%BA%8F">目录项页的排序</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%BA%8C%EF%BC%89%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9%E5%AD%98%E5%82%A8%E5%AE%8C%E6%95%B4%E7%9A%84%E7%94%A8%E6%88%B7%E8%AE%B0%E5%BD%95">（二）叶子节点存储完整的用户记录</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%E6%95%B0%E6%8D%AE%E5%8D%B3%E7%B4%A2%E5%BC%95">数据即索引</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%87%AA%E5%8A%A8%E5%88%9B%E5%BB%BA">自动创建</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%B8%89%EF%BC%89%E8%81%9A%E7%B0%87%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9">（三）聚簇索引的优缺点</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95">三、二级索引</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%B8%80%EF%BC%89%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95%E7%9A%84%E7%89%B9%E7%82%B9">（一）二级索引的特点</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9F%BA%E4%BA%8E%E9%9D%9E%E4%B8%BB%E9%94%AE%E5%88%97%E6%8E%92%E5%BA%8F">基于非主键列排序</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9%E5%AD%98%E5%82%A8%E9%83%A8%E5%88%86%E6%95%B0%E6%8D%AE">叶子节点存储部分数据</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%BA%8C%EF%BC%89%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B">（二）二级索引的工作流程</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%B8%89%EF%BC%89%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9">（三）二级索引的优缺点</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95">四、联合索引</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%B8%80%EF%BC%89%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95%E7%9A%84%E7%89%B9%E7%82%B9">（一）联合索引的特点</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%A4%9A%E5%88%97%E6%8E%92%E5%BA%8F%E8%A7%84%E5%88%99">多列排序规则</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95%E7%9A%84%E7%BB%84%E6%88%90">联合索引的组成</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%BA%8C%EF%BC%89%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95%E4%B8%8E%E5%8D%95%E5%88%97%E7%B4%A2%E5%BC%95%E7%9A%84%E5%8C%BA%E5%88%AB">（二）联合索引与单列索引的区别</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95">联合索引</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8D%95%E5%88%97%E7%B4%A2%E5%BC%95">单列索引</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E4%B8%89%EF%BC%89%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9">（三）联合索引的优缺点</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%88%E5%9B%9B%EF%BC%89%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BD%BF%E7%94%A8%E5%BB%BA%E8%AE%AE">（四）联合索引的使用建议</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%E6%80%BB%E7%BB%93">五、总结</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE%E3%80%81%E4%B9%A6%E7%B1%8D%E5%8F%8A%E9%93%BE%E6%8E%A5">参考文献、书籍及链接</a></p>
<hr>
<p>干货分享，感谢您的阅读！</p>
<p>在现代数据库系统中，索引是提高数据检索速度的关键机制之一。InnoDB作为MySQL的默认存储引擎，采用了高效的B+树结构来实现其索引功能。这种结构不仅确保了数据的快速检索，还支持高效的插入、更新和删除操作。理解InnoDB中的B+树索引对于数据库优化和性能调优至关重要。</p>
<p>为了更好地理解 InnoDB 中 B+ 树索引的工作机制，我们从创建一个示例表index_demo开始，并通过详细的示意图展示记录在页中的存储结构及索引的作用。</p>
<pre><code class="sql">CREATE TABLE index_demo (
    c1 INT,
    c2 INT,
    c3 CHAR(1),
    PRIMARY KEY (c1)
) ROW_FORMAT = Compact;
</code></pre>
<p>这个表中有两个 INT 类型的列 <code>c1</code> 和 <code>c2</code>，一个 CHAR(1) 类型的列 <code>c3</code>，并且 <code>c1</code> 列为主键。表的行格式为 Compact。其基础可见：</p>
<h2>一、<strong>InnoDB中的</strong> B+ 树<strong>索引介绍</strong></h2>
<p>B+ 树索引是一种自平衡的树结构，其节点分为内部节点和叶子节点：</p>
<ul>
 <li><strong>内部节点（Internal Nodes）</strong> ：用于索引导航，存储键值和指向子节点的指针。</li>
 <li><strong>叶子节点（Leaf Nodes）</strong> ：存储实际的数据记录或指向数据记录的指针（称为记录指针）。</li>
</ul>
<p>在 B+ 树中，所有的数据记录都存储在叶子节点中，而内部节点仅用于存储键值和导航信息。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0cce9cde6fea4b9a80d575eb9a96494e.png&amp;size=m" alt=""></p>
<p>不论是存放用户记录的数据页，还是存放目录项记录的数据页，我们都把它们存放到B+树这个数据结构中，所以我们也称这些数据页为节点。</p>
<p>从图中可以看出来，我们的实际用户记录都存放在B+树的最底层的节点上，这些节点也被称为叶子节点或叶节点，其余用来存放目录项的节点称为非叶子节点或者内节点，其中B+树最上面的那个节点也称为根节点。</p>
<p><strong>依据</strong> <a href="https://zyfcodes.blog.csdn.net/article/details/130464048" title="InnoDB存储引擎B+树的树高推导">InnoDB存储引擎B+树的树高推导</a><strong>：当树高为4时，可以存放200百多亿行数据。这样的数据容量，可以满足绝大部分应用的需求，因此我们可以说在绝大部分应用中，B+树高度为3或4就可以满足数据存储的需求。</strong> B+树这种<strong>高扇出低树高</strong>的特征，也大大的提高了主键查询性能。</p>
<h2>二、聚簇索引</h2>
<p>在InnoDB存储引擎中，聚簇索引（Clustered Index）是数据存储和索引的一种特殊而重要的结构。聚簇索引主要特点：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1c32c581d0294db68874e87c02cb50e3.png&amp;size=m" alt=""></p>
<h3>（一）使用记录主键值的大小进行排序</h3>
<p>聚簇索引通过主键值对记录和页进行排序，这涉及三个方面：</p>
<h4><strong>页内记录排序</strong></h4>
<p>在每个页内，记录按照主键值的大小顺序排成一个单向链表，确保了页内记录的有序性，方便快速查找。页内的记录被划分成若干个组，每个组中主键值最大的记录在页内的偏移量会被当作槽依次存放在页目录中（当然Supermum记录比任何用户记录都大），我们可以在页目录内通过二分法定位到主键列等于某个值的记录。</p>
<h4><strong>页之间的排序</strong></h4>
<p>存放用户记录的页按照页内记录的主键大小顺序排成一个双向链表。这种结构使得范围查询和顺序扫描更加高效。</p>
<h4><strong>目录项页的排序</strong></h4>
<p>存放目录项记录的页根据页内目录项记录的主键大小顺序排成一个双向链表。不同层次的页同样遵循这种排序规则，确保树的平衡性和查询效率。</p>
<h3>（二）叶子节点存储完整的用户记录</h3>
<p>B+树的叶子节点存储的是完整的用户记录，即包括所有列的值（包括隐藏列），在InnoDB中，叶子节点不仅仅是索引，还包含了实际的数据记录。这种特性使得聚簇索引与普通索引有所不同。</p>
<h4><strong>数据即索引</strong></h4>
<p><strong>聚簇索引中的叶子节点存储了完整的用户记录，因此聚簇索引就是数据的存储方式。</strong> 换句话说，<strong>索引即数据，数据即索引。</strong></p>
<h4><strong>自动创建</strong></h4>
<p>在InnoDB存储引擎中，聚簇索引会自动为每个表创建，并且不需要在MySQL语句中显式使用<code>INDEX</code>语句去创建。通常情况下，聚簇索引是基于表的主键创建的。</p>
<h3>（三）聚簇索引的优缺点</h3>
<p>| 聚簇索引的优点 | 聚簇索引的缺点 |
 <br>
 |————————————————-|——————————————————|
 <br>
 | <strong>快速数据访问</strong>：由于数据和索引存储在一起，基于主键的查询非常高效，不需要额外的索引查找。 | <strong>插入和删除成本较高</strong>：由于需要维护数据的有序性，插入和删除操作可能需要移动大量记录，导致性能开销。 |
 <br>
 | <strong>有序数据存储</strong>：记录按照主键顺序存储，适合范围查询和顺序扫描，提高查询性能。 | <strong>更新成本较高</strong>：如果更新操作导致主键变化，会引发记录的重新定位和页的重新排序，影响性能。 |</p>
<p>聚簇索引是InnoDB存储引擎中一种关键的索引类型，通过主键排序和存储完整用户记录，提供了高效的数据访问和有序的数据存储。在优化数据库性能时，理解和合理使用聚簇索引可以显著提升查询和数据操作的效率。具体优化可见：<a href="https://zyfcodes.blog.csdn.net/article/details/105360974" title="MySQL索引性能优化分析">MySQL索引性能优化分析</a>。</p>
<h2>三、二级索引</h2>
<p><code>聚簇索引</code>只能在搜索条件是主键值时才能发挥作用，因为<code>B+</code>树中的数据都是按照主键进行排序的。那如果我们想以别的列作为搜索条件该咋办呢？难道只能从头到尾沿着链表依次遍历记录么？不，我们可以多建几棵<code>B+</code>树，不同的<code>B+</code>树中的数据采用不同的排序规则。比方说我们用<code>c2</code>列的大小作为数据页、页中记录的排序规则，再建一棵<code>B+</code>树，效果如下图所示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0afb7ba735234eb281cbc4467add421a.png&amp;size=m" alt=""></p>
<p>在InnoDB存储引擎中，除了聚簇索引（Clustered Index），我们还可以使用二级索引（Secondary Index）来提高非主键列上的查询性能。二级索引是一种基于非主键列的B+树结构，用于快速定位数据记录。</p>
<h3>（一）二级索引的特点</h3>
<h4><strong>基于非主键列排序</strong></h4>
<p><strong>二级索引的B+树结构基于指定的非主键列进行排序，这包括以下几个方面：</strong></p>
<ul>
 <li><strong>页内记录排序</strong>：在每个页内，记录按照指定列（例如c2列）的大小顺序排成一个单向链表。</li>
 <li><strong>页之间的排序</strong>：存放用户记录的页按照页内记录的指定列顺序排成一个双向链表。这种结构便于快速范围查询和顺序扫描。</li>
 <li><strong>目录项页的排序</strong>：存放目录项记录的页根据页内目录项记录的指定列顺序排成一个双向链表，不同层次的页同样遵循这种排序规则。</li>
</ul>
<h4><strong>叶子节点存储部分数据</strong></h4>
<p>与聚簇索引不同，二级索引的叶子节点存储的是索引列和主键列的值，而不是完整的用户记录。这种设计减少了存储空间的占用，但在查询过程中需要进行回表操作以获取完整的用户记录。</p>
<h3>（二）二级索引的工作流程</h3>
<p>假设我们创建了一个基于c2列的二级索引，并通过c2列的值查找某些记录，以查找<code>c2</code>列的值为<code>4</code>的记录为例，查找过程如下：：</p>
<ol>
 <li>
  <p><strong>确定目录项记录页</strong></p>
  <p>从根页面开始，根据c2列的值<code>4</code>定位到目录项记录所在的页，通过页44快速定位到目录项记录所在的页为页42（因为<code>2 &lt; 4 &lt; 9</code>）。</p>
 </li>
 <li>
  <p><strong>通过目录项记录页确定用户记录真实所在的页</strong></p>
  <p>在页42中，根据c2列的值确定实际存储用户记录的页。由于c2列没有唯一性约束，值为4的记录可能分布在多个数据页中。最终确定实际存储用户记录的页在页34和页35中（因为<code>2 &lt; 4 ≤ 4</code>）。</p>
 </li>
 <li>
  <p><strong>在真实存储用户记录的页中定位到具体的记录</strong></p>
  <p>在页34和页35中定位到具体的记录，但二级索引的叶子节点中仅存储c2列和主键列c1的值。</p>
 </li>
 <li>
  <p><strong>回表操作</strong></p>
  <p>根据主键值到聚簇索引中查找完整的用户记录。这个过程称为回表操作，即从二级索引定位到主键，再通过主键在聚簇索引中查找完整记录。</p>
 </li>
</ol>
<h3>（三）二级索引的优缺点</h3>
<p>| 二级索引的优点 | 二级索引的缺点 |
 <br>
 |———————————————–|————————————–|
 <br>
 | <strong>提高查询效率</strong>：基于非主键列的查询可以利用二级索引快速定位数据，减少全表扫描的开销。 | <strong>回表操作</strong>：查询完整记录时需要回表操作，增加了一次I/O开销。 |
 <br>
 | <strong>灵活性</strong>：可以为多个列创建二级索引，提升多种查询条件下的性能。 | <strong>占用空间</strong>：虽然叶子节点不存储完整记录，但仍会占用额外的存储空间。 |</p>
<p>二级索引通过基于非主键列排序和存储索引列与主键列的值，为非主键列的查询提供了高效的解决方案。然而，由于叶子节点仅存储部分数据，查询完整记录时需要回表操作。因此，合理使用和配置二级索引，对于提升数据库查询性能至关重要。 具体优化可见：<a href="https://zyfcodes.blog.csdn.net/article/details/105360974" title="MySQL索引性能优化分析">MySQL索引性能优化分析</a>。</p>
<h2>四、联合索引</h2>
<p>在InnoDB存储引擎中，联合索引（Composite Index）是一种基于多个列的索引，用于提高复杂查询的效率。联合索引通过对多个列进行排序，能够更有效地处理包含多个条件的查询。</p>
<p>同时以多个列的大小作为排序规则，也就是同时为多个列建立索引，比方说我们想让<code>B+</code>树按照<code>c2</code>和<code>c3</code>列的大小进行排序，这个包含两层含义：</p>
<ul>
 <li>先把各个记录和页按照<code>c2</code>列进行排序。</li>
 <li>在记录的<code>c2</code>列相同的情况下，采用<code>c3</code>列进行排序</li>
</ul>
<p>为<code>c2</code>和<code>c3</code>列建立的索引的示意图如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F484c553ce73b4ef0b2347ba2980693ae.png&amp;size=m" alt=""></p>
<p>如图所示，我们需要注意一下几点：</p>
<ul>
 <li>
  <p>每条<code>目录项记录</code>都由<code>c2</code>、<code>c3</code>、<code>页号</code>这三个部分组成，各条记录先按照<code>c2</code>列的值进行排序，如果记录的<code>c2</code>列相同，则按照<code>c3</code>列的值进行排序。</p>
 </li>
 <li>
  <p><code>B+</code>树叶子节点处的用户记录由<code>c2</code>、<code>c3</code>和主键<code>c1</code>列组成。</p>
 </li>
</ul>
<h3>（一）联合索引的特点</h3>
<h4><strong>多列排序规则</strong></h4>
<p>联合索引按照多个列的值进行排序，其排序规则包括以下两个层次：</p>
<ul>
 <li><strong>第一列排序</strong>：首先按照第一个指定列（例如c2列）的值进行排序。</li>
 <li><strong>第二列排序</strong>：在第一列相同的情况下，按照第二个指定列（例如c3列）的值进行排序。</li>
</ul>
<p>在这个结构中，每个目录项记录由c2、c3和页号组成，叶子节点存储c2、c3和主键c1。</p>
<h4><strong>联合索引的组成</strong></h4>
<ul>
 <li><strong>目录项记录</strong>：每条目录项记录由c2、c3和页号组成，先按照c2列排序，如果c2列相同，则按照c3列排序。</li>
 <li><strong>叶子节点记录</strong>：叶子节点处的用户记录包含c2、c3和主键c1列。这种结构使得查询包含c2和c3列的条件时更加高效。</li>
</ul>
<h3>（二）联合索引与单列索引的区别</h3>
<h4><strong>联合索引</strong></h4>
<ul>
 <li>建立联合索引会生成一棵B+树，该树按照c2和c3列进行排序。</li>
 <li>查询时，如果使用c2和c3作为条件，能够快速定位记录，减少查询时间。</li>
</ul>
<h4><strong>单列索引</strong></h4>
<ul>
 <li>为c2和c3分别建立索引会生成两棵独立的B+树，每棵树分别按照c2或c3进行排序。</li>
 <li>查询时，如果只使用c2或c3作为条件，可以利用相应的索引。但如果同时使用c2和c3作为条件，可能需要进行多次索引查找和合并操作，增加查询开销。</li>
</ul>
<h3>（三）联合索引的优缺点</h3>
<p>| 联合索引的优点 | 联合索引的缺点 |
 <br>
 |——————————————|———————————————————|
 <br>
 | <strong>高效的多列查询</strong>：联合索引能够显著提高包含多个列条件的查询性能。 | <strong>插入和维护成本较高</strong>：由于需要对多个列进行排序和维护，插入和更新操作可能较慢。 |
 <br>
 | <strong>减少单列索引的数量</strong>：通过一个联合索引代替多个单列索引，可以节省存储空间。 | <strong>部分匹配限制</strong>：联合索引在查询中只能高效利用前缀列，如果查询条件不包括索引的最左列，索引的利用率会降低。 |</p>
<h3>（四）联合索引的使用建议</h3>
<p><strong>前缀匹配原则</strong></p>
<p>联合索引在查询中按照列的顺序生效，因此查询条件应尽量包括索引的最左列（即前缀列）。例如，创建了(c2, c3)的联合索引后，查询条件包含c2或(c2, c3)时能够有效利用索引。</p>
<p><strong>适用场景</strong></p>
<p>联合索引适用于需要同时基于多个列进行查询的场景。例如，在电商系统中，可以为商品类别和价格区间创建联合索引，以优化相关查询。</p>
<p>联合索引是InnoDB中一种重要的索引类型，通过对多个列进行排序和索引，提高了多列查询的性能。与单列索引相比，联合索引在处理复杂查询时更加高效。然而，合理的索引设计和使用对于优化数据库性能至关重要。理解联合索引的工作原理和最佳实践，可以帮助我们更好地利用MySQL数据库。 具体优化可见：<a href="https://zyfcodes.blog.csdn.net/article/details/105360974" title="MySQL索引性能优化分析">MySQL索引性能优化分析</a>。</p>
<h2>五、总结</h2>
<p>InnoDB中的索引是提高数据检索效率的关键。本文介绍了三种主要索引类型：</p>
<ol>
 <li><strong>聚簇索引</strong> ：基于主键排序存储完整的用户记录，适合快速主键查询和范围查询。</li>
 <li><strong>二级索引</strong> ：基于非主键列排序，提升非主键查询性能，但需要回表操作。</li>
 <li><strong>联合索引</strong> ：基于多个列排序，适用于复杂查询，能够显著提升多列条件查询的效率。</li>
</ol>
<p>通过合理使用和配置这些索引，能有效提升数据库查询和数据操作的性能。理解索引的工作机制和最佳实践，对于优化MySQL数据库性能至关重要。</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<h2>参考文献、书籍及链接</h2>
<ul>
 <li>《MySQL技术内幕：InnoDB存储引擎》（第2版）：<a href="https://book.douban.com/subject/24708143/" title="MySQL技术内幕 (豆瓣)">MySQL技术内幕 (豆瓣)</a></li>
 <li><a href="https://relph1119.github.io/mysql-learning-notes/#/mysql/06-%E5%BF%AB%E9%80%9F%E6%9F%A5%E8%AF%A2%E7%9A%84%E7%A7%98%E7%B1%8D-B+%E6%A0%91%E7%B4%A2%E5%BC%95?id=%E7%B4%A2%E5%BC%95" title="《MySQL 是怎样运行的：从根儿上理解 MySQL》">《MySQL 是怎样运行的：从根儿上理解 MySQL》</a></li>
 <li>《Inside InnoDB: The InnoDB Storage Engine》：<a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html" title="MySQL :: MySQL 8.0 Reference Manual :: 15 The InnoDB Storage Engine">MySQL :: MySQL 8.0 Reference Manual :: 15 The InnoDB Storage Engine</a></li>
 <li>《InnoDB: The Ultimate Guide》：<a href="https://www.percona.com/blog/2018/06/05/innodb-the-ultimate-guide/" title="https://www.percona.com/blog/2018/06/05/innodb-the-ultimate-guide/">https://www.percona.com/blog/2018/06/05/innodb-the-ultimate-guide/</a></li>
 <li>《InnoDB Storage Engine Internals》：<a href="https://mariadb.com/kb/en/innodb-storage-engine-internals/" title="https://mariadb.com/kb/en/innodb-storage-engine-internals/">https://mariadb.com/kb/en/innodb-storage-engine-internals/</a></li>
 <li><a href="https://www.cnblogs.com/wade-luffy/p/6289917.html#_label1" title="InnoDB的数据页结构">InnoDB的数据页结构</a></li>
 <li><a href="https://zyfcodes.blog.csdn.net/article/details/130464048" title="InnoDB存储引擎B+树的树高推导_b+树一般多少层-CSDN博客">InnoDB存储引擎B+树的树高推导_b+树一般多少层-CSDN博客</a></li>
 <li><a href="https://zyfcodes.blog.csdn.net/article/details/105360974" title="MySQL索引性能优化分析_mysql索引和性能分析(实战)-CSDN博客">MySQL索引性能优化分析_mysql索引和性能分析(实战)-CSDN博客</a></li>
</ul>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/xiaofeng10330111/article/details/142446131</p>]]></description><guid isPermaLink="false">/archives/d53fbf91-5682-40d5-bd03-6cc4f66caf0a</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Mon, 30 Sep 2024 06:48:06 GMT</pubDate></item><item><title><![CDATA[spring boot 项目如何使用jasypt加密]]></title><link>https://wiki.dgfygk.com/archives/2378b46a-1495-40ab-9820-702f42d8b3c4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=spring%20boot%20%E9%A1%B9%E7%9B%AE%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8jasypt%E5%8A%A0%E5%AF%86&amp;url=/archives/2378b46a-1495-40ab-9820-702f42d8b3c4" width="1" height="1" alt="" style="opacity:0;">
<p>1.首先，添加jasypt依赖</p>
<pre><code class="java">    &lt;dependency&gt;
      &lt;groupId&gt;com.github.ulisesbocchio&lt;/groupId&gt;
      &lt;artifactId&gt;jasypt-spring-boot-starter&lt;/artifactId&gt;
      &lt;version&gt;2.1.0&lt;/version&gt;
    &lt;/dependency&gt;
</code></pre>
<p>2.然后win+r，cmd调出窗口，添加加密指令</p>
<pre><code class="java">java -cp E:\Softeare\apache-maven-3.6.0\repository\org\jasypt\jasypt\1.9.2\jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="root" password=123456 algorithm=PBEWithMD5AndDES
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3e4a8fd5ce60456380e7c4689e7807fb.png&amp;size=m" alt=""></p>
<p>3.在yml文件中添加jasypt密钥</p>
<pre><code class="java">jasypt:
  encryptor:
    password: 123456
</code></pre>
<p>4.将数据库密码修改成ENC(fBKD1fUlxJhw4PqWuh5oQQ==)（fBKD1fUlxJhw4PqWuh5oQQ==是加密后的密码），添加ENC()可以自动解密。</p>
<pre><code class="java">  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url:
    username: ${DATASOURCE_USERNAME}
    password: ENC(fBKD1fUlxJhw4PqWuh5oQQ==)
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/m0_58798123/article/details/142560837</p>]]></description><guid isPermaLink="false">/archives/2378b46a-1495-40ab-9820-702f42d8b3c4</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:47:51 GMT</pubDate></item><item><title><![CDATA[SpringCloud 配置 feign.hystrix.enabled_ true 不生效]]></title><link>https://wiki.dgfygk.com/archives/11f2f2c9-6418-4884-9a5e-bab9b0846802</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud%20%E9%85%8D%E7%BD%AE%20feign.hystrix.enabled_%20true%20%E4%B8%8D%E7%94%9F%E6%95%88&amp;url=/archives/11f2f2c9-6418-4884-9a5e-bab9b0846802" width="1" height="1" alt="" style="opacity:0;">
<h3>SpringCloud 配置 feign.hystrix.enabled: true 不生效的原因</h3>
<p>feign 启用 hystrix
 <br>
 feign 默认没有启用 hystrix，添加配置，启用 hystrix
 <br>
 feign.hystrix.enabled=true
 <br>
 application.yml 添加配置</p>
<pre><code class="prism language-bash">feign:
  hystrix:
    enabled: true
</code></pre>
<p>启用 hystrix 后，访问服务,未发生降级</p>
<p>解决办法：
 <br>
 feign.hystrix.enabled: true 是老版本的配置, 新版的配置是</p>
<pre><code class="prism language-bash">feign:
  circuitbreaker:
    enabled: true
</code></pre>
<p>修改之后，配置生效
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F97ffe0eef5cb4990b451258d3ac8f5d4.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F02d412d35dee4e93b920d34842e0355a.png&amp;size=m" alt="">
 <br>
 pom文件依赖版本</p>
<pre><code class="prism language-bash"> &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-netflix-hystrix&lt;/artifactId&gt;
            &lt;version&gt;2.2.9.RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/weixin_58069198/article/details/142627854</p>]]></description><guid isPermaLink="false">/archives/11f2f2c9-6418-4884-9a5e-bab9b0846802</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:47:40 GMT</pubDate></item><item><title><![CDATA[Docker打包部署springboot项目（win）]]></title><link>https://wiki.dgfygk.com/archives/46d957cc-c602-46e9-ba8e-cb26bb4f5b61</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Docker%E6%89%93%E5%8C%85%E9%83%A8%E7%BD%B2springboot%E9%A1%B9%E7%9B%AE%EF%BC%88win%EF%BC%89&amp;url=/archives/46d957cc-c602-46e9-ba8e-cb26bb4f5b61" width="1" height="1" alt="" style="opacity:0;">
<p>以下环境皆基于Windos 。 手把手保姆级教程。</p>
<p>打包成docker镜像的前置条件是得有docker环境，下面使用桌面版本。</p>
<h4>一、下载Docker桌面版本</h4>
<p>地址：<a href="https://www.docker.com/products/docker-desktop" title="Docker Desktop - Docker">Docker Desktop - Docker</a></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F166e9b54fc592bd6d82033405c3fd2ad.png&amp;size=m" alt=""></p>
<p>下载完成，双击安装即可：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8c6c1cd9a6b45907143973241f456fee.png&amp;size=m" alt=""></p>
<p>安装完成</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe2ecd8d9f590d35ea0d290cb509db41a.png&amp;size=m" alt=""></p>
<h4>二、准备好一个springboot项目</h4>
<p>新建一个项目</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3589a3af254a6365fd99cc268293f9d9.png&amp;size=m" alt=""></p>
<p>勾选上这个web依赖 ，然后点完成</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F016e5361270b73bd46a842cfd0d79e45.png&amp;size=m" alt=""> 为方便测试编辑主启动文件为可访问的路径</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F42b6b974a0d5e129f2c052c58d989c6e.png&amp;size=m" alt=""></p>
<p>启动测试看看是否正常</p>
<p>浏览器访问 正常启动</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6073a999d16105d7ce3f3265a02c8d91.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>三、打包并打包成docker镜像</h4>
<p>执行maven的package打包命令 打成jar包</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffc10db46e2997676891f0389a06f6390.png&amp;size=m" alt=""></p>
<h4>四、编写Dockfile文件</h4>
<p>把jar包随便拷贝到一个目录，并新建一个Dockerfile文件，目录如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7b96dd6c46ac67b59eb33a7e12acc4a6.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>Dockerfile文件内容如下：</p>
<pre><code>FROM openjdk:8
ADD docker-demo-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
</code></pre>
<h4>五、执行打包镜像命令</h4>
<p>首先进入到上面的目录</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc6f59898e22d900f2e91ec9017268991.png&amp;size=m" alt=""></p>
<p>查看docker是否正常启动</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fab7f1be54b357d8f5a98a7b4e94af5ca.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>执行命令（需要先把桌面的docker启动起来）</p>
<pre><code>docker build -f Dockerfile -t dkdemo:v1.0.0 .
</code></pre>
<p>-f 指定Dockerfile文件</p>
<p>-t 指定镜像名称与版本号</p>
<p>如果打包成镜像比较慢 可以设置加速源</p>
<h4>镜像加速源</h4>
<p>| 镜像加速器 | 镜像加速器地址 |
 <br>
 |—————|——————————————-|
 <br>
 | Docker 中国官方镜像 | https://registry.docker-cn.com |
 <br>
 | DaoCloud 镜像站 | http://f1361db2.m.daocloud.io |
 <br>
 | Azure 中国镜像 | https://dockerhub.azk8s.cn |
 <br>
 | 科大镜像站 | https://docker.mirrors.ustc.edu.cn |
 <br>
 | 阿里云 | https://<your_code>.mirror.aliyuncs.com | 
  <br>| 七牛云 | https://reg-mirror.qiniu.com | 
  <br>| 网易云 | https://hub-mirror.c.163.com | 
  <br>| 腾讯云 | https://mirror.ccs.tencentyun.com |</your_code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F93525a04c64615b5e268c2aedb66d813.png&amp;size=m" alt=""></p>
<p>第一次打包镜像有点慢 耐心等待完成（后续打包比较快）</p>
<p>（耗时接近20min）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd29a5d1735f2b1f2fa18e56a68238c6b.png&amp;size=m" alt=""></p>
<p>查看镜像</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc88ca99902dc0383988014cafe541a59.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<h4>六、运行</h4>
<pre><code>docker run -p 8088:8080 --name dkdemo1 dktest:v1.0.2
</code></pre>
<p>说明：-p 8088:8080 指定宿主机端口8088映射到docker容器端口8080</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcab573fe620ba779a19a8f237ba2d694.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>浏览器访问 正常 标识正常启动</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F44edb4ecd6b8542e85f1e9d43f257d59.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>也可以通过桌面程序查看启动</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F02811c2aaf3a2f6d99a7bcc0c086bf82.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F09458a27040dd38ece74e62471cf8739.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>备注：</p>
<p>说明下Dockerfile文件里面的指令EXPOSE</p>
<p>测试的感觉没啥用，只是起到一个标记的作用，对程序运行无影响，仅仅是标记作用</p>
<p>修改文件后重新打包</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F86a8cef3ba81a1e0ea18d8637f1e7f33.jpeg&amp;size=m" alt=""></p>
<p>对运行不影响</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F334eb6fd12140bcb494f05352f2edb7c.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/xingsfdz/article/details/126624286</p>]]></description><guid isPermaLink="false">/archives/46d957cc-c602-46e9-ba8e-cb26bb4f5b61</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Mon, 30 Sep 2024 06:47:28 GMT</pubDate></item><item><title><![CDATA[Docker入门教程（详细）]]></title><link>https://wiki.dgfygk.com/archives/e85c23fa-f0a9-4226-82dd-aaef09404614</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Docker%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B%EF%BC%88%E8%AF%A6%E7%BB%86%EF%BC%89&amp;url=/archives/e85c23fa-f0a9-4226-82dd-aaef09404614" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81Docker%E6%A6%82%E8%BF%B0">一、Docker概述</a></p>
<p><a href="https://wiki.dgfygk.com#1.1%20Docker%20%E4%B8%BA%E4%BB%80%E4%B9%88%E5%87%BA%E7%8E%B0%EF%BC%9F">1.1 Docker 为什么出现？</a></p>
<p><a href="https://wiki.dgfygk.com#1.2%20Dorker%E5%8E%86%E5%8F%B2">1.2 Dorker历史</a></p>
<p><a href="https://wiki.dgfygk.com#1.3%20%E8%83%BD%E5%81%9A%E4%BB%80%E4%B9%88">1.3 能做什么</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%8A%80%E6%9C%AF%EF%BC%9A%EF%BC%88%E9%80%9A%E8%BF%87%20%E8%BD%AF%E4%BB%B6%20%E6%A8%A1%E6%8B%9F%E7%9A%84%E5%85%B7%E6%9C%89%E5%AE%8C%E6%95%B4%20%E7%A1%AC%E4%BB%B6%20%E7%B3%BB%E7%BB%9F%E5%8A%9F%E8%83%BD%E7%9A%84%E3%80%81%E8%BF%90%E8%A1%8C%E5%9C%A8%E4%B8%80%E4%B8%AA%E5%AE%8C%E5%85%A8%20%E9%9A%94%E7%A6%BB%20%E7%8E%AF%E5%A2%83%E4%B8%AD%E7%9A%84%E5%AE%8C%E6%95%B4%20%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%EF%BC%89">虚拟机技术：（通过 软件 模拟的具有完整 硬件 系统功能的、运行在一个完全 隔离 环境中的完整 计算机系统）</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%AE%B9%E5%99%A8%E5%8C%96%E6%8A%80%E6%9C%AF%EF%BC%9A%EF%BC%88%E5%AE%B9%E5%99%A8%E5%8C%96%E6%8A%80%E6%9C%AF%E4%B8%8D%E6%98%AF%E6%A8%A1%E6%8B%9F%E7%9A%84%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%EF%BC%89">容器化技术：（容器化技术不是模拟的一个完整的操作系统）</a></p>
<p><a href="https://wiki.dgfygk.com#Devops%EF%BC%88%E5%BC%80%E5%8F%91%EF%BC%8C%E8%BF%90%E7%BB%B4%EF%BC%89%EF%BC%9A">Devops：（开发，运维）</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%C2%A0Docker%E5%AE%89%E8%A3%85%C2%A0">二、 Docker安装</a></p>
<p><a href="https://wiki.dgfygk.com#2.1%20%E5%9F%BA%E6%9C%AC%E7%BB%84%E6%88%90">2.1 基本组成</a></p>
<p><a href="https://wiki.dgfygk.com#2.2%20%E5%AE%89%E8%A3%85Docker">2.2 安装Docker</a></p>
<p><a href="https://wiki.dgfygk.com#2.3%20%E9%98%BF%E9%87%8C%E4%BA%91%E9%95%9C%E5%83%8F%E5%8A%A0%E9%80%9F">2.3 阿里云镜像加速</a></p>
<p><a href="https://wiki.dgfygk.com#2.4%C2%A0%E5%9B%9E%E9%A1%BEhello-world%E6%B5%81%E7%A8%8B">2.4 回顾hello-world流程</a></p>
<p><a href="https://wiki.dgfygk.com#2.5%20%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86">2.5 底层原理</a></p>
<p><a href="https://wiki.dgfygk.com#2.5.1%20docker%E6%98%AF%E6%80%8E%E4%B9%88%E5%B7%A5%E4%BD%9C%E7%9A%84%EF%BC%9F">2.5.1 docker是怎么工作的？</a></p>
<p><a href="https://wiki.dgfygk.com#2.5.2%20docker%E4%B8%BA%E4%BB%80%E4%B9%88%E6%AF%94VM%EF%BC%88%E8%99%9A%E6%8B%9F%E6%9C%BA%EF%BC%89%E5%BF%AB%EF%BC%9F">2.5.2 docker为什么比VM（虚拟机）快？</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81Docker%E7%9A%84%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4">三、Docker的常用命令</a></p>
<p><a href="https://wiki.dgfygk.com#3.1%20%E5%B8%AE%E5%8A%A9%E5%91%BD%E4%BB%A4">3.1 帮助命令</a></p>
<p><a href="https://wiki.dgfygk.com#3.2%20%E9%95%9C%E5%83%8F%E5%91%BD%E4%BB%A4">3.2 镜像命令</a></p>
<p><a href="https://wiki.dgfygk.com#3.2.1%20docker%20images%C2%A0">3.2.1 docker images</a></p>
<p><a href="https://wiki.dgfygk.com#3.2.2%20docker%20search%EF%BC%88%E6%90%9C%E7%B4%A2%E9%95%9C%E5%83%8F%EF%BC%89">3.2.2 docker search（搜索镜像）</a></p>
<p><a href="https://wiki.dgfygk.com#3.2.3%20docker%20pull%EF%BC%88%E4%B8%8B%E8%BD%BD%E9%95%9C%E5%83%8F%EF%BC%89">3.2.3 docker pull（下载镜像）</a></p>
<p><a href="https://wiki.dgfygk.com#3.2.4%C2%A0%20docker%20rmi%EF%BC%88%E5%88%A0%E9%99%A4%E9%95%9C%E5%83%8F%EF%BC%89">3.2.4 docker rmi（删除镜像）</a></p>
<p><a href="https://wiki.dgfygk.com#3.3%20%E5%AE%B9%E5%99%A8%E5%91%BD%E4%BB%A4">3.3 容器命令</a></p>
<p><a href="https://wiki.dgfygk.com#3.3.1%C2%A0%E6%96%B0%E5%BB%BA%E5%AE%B9%E5%99%A8%E5%B9%B6%E5%90%AF%E5%8A%A8">3.3.1 新建容器并启动</a></p>
<p><a href="https://wiki.dgfygk.com#3.3.2%20%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89%E8%BF%90%E8%A1%8C%E7%9A%84%E5%AE%B9%E5%99%A8">3.3.2 列出所有运行的容器</a></p>
<p><a href="https://wiki.dgfygk.com#3.3.3%20%E9%80%80%E5%87%BA%E5%AE%B9%E5%99%A8">3.3.3 退出容器</a></p>
<p><a href="https://wiki.dgfygk.com#header21">3.3.4 删除容器</a></p>
<p><a href="https://wiki.dgfygk.com#3.3.5%20%E5%90%AF%E5%8A%A8%E5%92%8C%E5%81%9C%E6%AD%A2%E5%AE%B9%E5%99%A8%E7%9A%84%E6%93%8D%E4%BD%9C">3.3.5 启动和停止容器的操作</a></p>
<p><a href="https://wiki.dgfygk.com#4.%E5%B8%B8%E7%94%A8%E5%85%B6%E4%BB%96%E5%91%BD%E4%BB%A4">3.4 常用其他命令</a></p>
<p><a href="https://wiki.dgfygk.com#4.1.1%C2%A0%20%E5%90%8E%E5%8F%B0%E5%90%AF%E5%8A%A8">3.4.1 后台启动容器</a></p>
<p><a href="https://wiki.dgfygk.com#3.4.2%20%E6%9F%A5%E7%9C%8B%E6%97%A5%E5%BF%97">3.4.2 查看日志</a></p>
<p><a href="https://wiki.dgfygk.com#3.4.3%C2%A0%E6%9F%A5%E7%9C%8B%E5%AE%B9%E5%99%A8%E4%B8%AD%E8%BF%9B%E7%A8%8B%E7%9A%84%E4%BF%A1%E6%81%AF">3.4.3 查看容器中进程的信息</a></p>
<p><a href="https://wiki.dgfygk.com#header27">3.4.4 查看镜像的元数据</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A03.4.5%20%E8%BF%9B%E5%85%A5%E5%BD%93%E5%89%8D%E6%AD%A3%E5%9C%A8%E8%BF%90%E8%A1%8C%E7%9A%84%E5%AE%B9%E5%99%A8">3.4.5 进入当前正在运行的容器</a></p>
<p><a href="https://wiki.dgfygk.com#3.4.6%C2%A0%20%E4%BB%8E%E5%AE%B9%E5%99%A8%E5%86%85%E6%8B%B7%E8%B4%9D%E6%96%87%E4%BB%B6%E5%88%B0%E4%B8%BB%E6%9C%BA">3.4.6 从容器内拷贝文件到主机</a></p>
<p><a href="https://wiki.dgfygk.com#3.5%20%E5%B0%8F%E7%BB%93%20%EF%BC%88%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E6%B1%87%E6%80%BB%EF%BC%89">3.5 小结 （常用命令汇总）</a></p>
<p><a href="https://wiki.dgfygk.com#3.6%20%E7%BB%83%E4%B9%A0%E5%AE%89%E8%A3%85Nginx">3.6 练习安装Nginx</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A03.6%20Docker%E5%8F%AF%E8%A7%86%E5%8C%96%E7%95%8C%E9%9D%A2%E5%B7%A5%E5%85%B7">3.6 Docker可视化界面工具</a></p>
<p><a href="https://wiki.dgfygk.com#3.6.1%C2%A0%20%E4%BB%80%E4%B9%88%E6%98%AFportainer%20%3F">3.6.1 什么是portainer ?</a></p>
<p><a href="https://wiki.dgfygk.com#3.6.2%20%E5%AE%89%E8%A3%85">3.6.2 安装</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81Docker%E9%95%9C%E5%83%8F">四、Docker镜像</a></p>
<p><a href="https://wiki.dgfygk.com#4.1%20%E9%95%9C%E5%83%8F%E6%98%AF%E4%BB%80%E4%B9%88">4.1 镜像是什么</a></p>
<p><a href="https://wiki.dgfygk.com#4.2%20%E9%95%9C%E5%83%8F%E5%8A%A0%E9%80%9F%E5%8E%9F%E7%90%86">4.2 镜像加速原理</a></p>
<p><a href="https://wiki.dgfygk.com#4.2.1%20UnionFS%EF%BC%88%E8%81%94%E5%90%88%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%EF%BC%89">4.2.1 UnionFS（联合文件系统）</a></p>
<p><a href="https://wiki.dgfygk.com#4.2.2%20Docker%20%E9%95%9C%E5%83%8F%E5%8A%A0%E8%BD%BD%E5%8E%9F%E5%9E%8B">4.2.2 Docker 镜像加载原型</a></p>
<p><a href="https://wiki.dgfygk.com#4.3%20%E5%88%86%E5%B1%82%E7%90%86%E8%A7%A3">4.3 分层理解</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A04.4%20commit%E9%95%9C%E5%83%8F%C2%A0">4.4 commit镜像</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%20%E5%AE%B9%E5%99%A8%E6%95%B0%E6%8D%AE%E5%8D%B7">五、 容器数据卷</a></p>
<p><a href="https://wiki.dgfygk.com#5.1%20%E4%BB%80%E4%B9%88%E6%98%AF%E5%AE%B9%E5%99%A8%E6%95%B0%E6%8D%AE%E5%8D%B7%EF%BC%9F">5.1 什么是容器数据卷？</a></p>
<p><a href="https://wiki.dgfygk.com#5.2%20%E4%BD%BF%E7%94%A8%E6%95%B0%E6%8D%AE%E5%8D%B7">5.2 使用数据卷</a></p>
<p><a href="https://wiki.dgfygk.com#5.3%20%E5%AE%89%E8%A3%85MySQL">5.3 安装MySQL</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A05.4%20%E5%8C%BF%E5%90%8D%E6%8C%82%E8%BD%BD%E5%92%8C%E5%85%B7%E5%90%8D%E6%8C%82%E8%BD%BD">5.4 匿名挂载和具名挂载</a></p>
<p><a href="https://wiki.dgfygk.com#5.5%20Dockerfile">5.5 Dockerfile</a></p>
<p><a href="https://wiki.dgfygk.com#5.6%20%E6%95%B0%E6%8D%AE%E5%8D%B7%E5%AE%B9%E5%99%A8">5.6 数据卷容器</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%85%AD%E3%80%81DockerFile">六、DockerFile</a></p>
<p><a href="https://wiki.dgfygk.com#6.1%20%E4%BB%80%E4%B9%88%E6%98%AFDockerFile%20%EF%BC%9F">6.1 什么是DockerFile ？</a></p>
<p><a href="https://wiki.dgfygk.com#6.2%20%E6%90%AD%E5%BB%BA%E6%AD%A5%E9%AA%A4">6.2 搭建步骤</a></p>
<p><a href="https://wiki.dgfygk.com#6.3%20DockerFile%E7%9A%84%E5%91%BD%E4%BB%A4">6.3 DockerFile的命令</a></p>
<p><a href="https://wiki.dgfygk.com#6.4%20%E6%B5%8B%E8%AF%95">6.4 测试</a></p>
<p><a href="https://wiki.dgfygk.com#6.5%20%E5%B0%8F%E7%BB%93">6.5 小结</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%83%E3%80%81%20Docker%20%E7%BD%91%E7%BB%9C">七、 Docker 网络</a></p>
<p><a href="https://wiki.dgfygk.com#7.1%20docker0">7.1 docker0</a></p>
<p><a href="https://wiki.dgfygk.com#7.2%20link">7.2 link</a></p>
<p><a href="https://wiki.dgfygk.com#header71">高可用</a></p>
<p><a href="https://wiki.dgfygk.com#7.3%20%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BD%91%E7%BB%9C">7.3 自定义网络</a></p>
<p><a href="https://wiki.dgfygk.com#7.3.1%20%E6%9F%A5%E7%9C%8B%E6%89%80%E6%9C%89%E7%9A%84docker%E7%BD%91%E7%BB%9C">7.3.1 查看所有的docker网络</a></p>
<p><a href="https://wiki.dgfygk.com#7.3.2%C2%A0%C2%A0%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%BC%8F">7.3.2 网络模式</a></p>
<p><a href="https://wiki.dgfygk.com#7.3.3%20%E6%B5%8B%E8%AF%95">7.3.3 测试</a></p>
<p><a href="https://wiki.dgfygk.com#7.4%20%E7%BD%91%E7%BB%9C%E8%BF%9E%E9%80%9A">7.4 网络连通</a></p>
<hr>
<p>
 <br>
</p>
<p>
 <br>
</p>
<h2>一、Docker概述</h2>
<h3>1.1 Docker 为什么出现？</h3>
<p>个人理解：</p>
<blockquote>
 <p>开发了一个项目 可以在本机运行 但是如果版本更新 可能就会导致服务不可使用 后期我们维护起来就很繁琐 因为对于每一个机器 我们都得进行环境的部署呀 配置呀什么的</p>
 <p>比如： 我在window系统下开发了一个项目（jar+redis+ES+Kafka+…） 但是我现在准备部署到服务器上去 因为不能夸平台 所以我还得重新去按照配置环境什么的 但是docker 就可以让我们 直接将项目打包然后部署上线了 不用一个个单独去维护。</p>
</blockquote>
<h3>1.2 Dorker历史</h3>
<blockquote>
 <p>2010年，几个搞IT的年轻人，就在美国成立了一家公司<strong>dotcloud</strong> 做一些pass的云计算服务！
  <br>
  LXC有关的容器技术！他们将自己的技术（容器化技术）命名就是Docker！
  <br>
  Docker刚刚诞生的时候，没有引起行业的注意！（dotCloud就活不下去）
  <br>
  <strong>开源</strong> （开放源代码）
  <br>
  2013年，Docker开源！
  <br>
  Docker越来越多的人发现了docker的优点！就火了，Docker每个月都会更新一个版本！
  <br>
  2014年4月9日，Docker1.0发布！
  <br>
  Docker为什么这么火？<strong>十分的轻巧</strong>
  <br>
  在容器技术出来之前，我们都是使用虚拟机技术！
  <br>
  虚拟机：在window中装一个Vmware，通过这个软件我们可以虚拟出来一台或者多台电脑！（很笨重）
  <br>
  虚拟机也是属于虚拟化技术，Docker容器技术，也是一种虚拟化技术！</p>
</blockquote>
<h3>1.3 能做什么</h3>
<blockquote>
 <h4><strong>虚拟机技术</strong>：（通过 软件 模拟的具有完整 硬件 系统功能的、运行在一个完全 隔离 环境中的完整 计算机系统）</h4>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa3c08713190d357b9ab535566a7b0226.jpeg&amp;size=m" alt=""></p>
</blockquote>
<p>虚拟机的缺点：</p>
<ul>
 <li>资源占用多</li>
 <li>冗余步骤多</li>
 <li>启动慢</li>
</ul>
<blockquote>
 <h4>容器化技术：（容器化技术不是模拟的一个完整的操作系统）</h4>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1b89214b4f0d73c67ef203f409b236e3.jpeg&amp;size=m" alt=""></p>
</blockquote>
<p>Docker和虚拟机技术的区别：</p>
<ul>
 <li>传统的虚拟机，可以虚拟出一条硬件，运行一个完整的操作系统，在这个操作系统上安装和运行所需的软件</li>
 <li>容器内的应用可以直接运行在宿主 主机的内核中，容器没有自己的内核，也不用虚拟硬件 （轻便）</li>
 <li>每个容器是相互隔离的，每个容器内都有属于自己的文件系统，之间互不影响。</li>
</ul>
<blockquote>
 <h4>Devops：（开发，运维）</h4>
 <p><strong>1. 应用于更快速的交付和部署</strong></p>
</blockquote>
<ul>
 <li>传统：通过大量的帮助文档，安装程序！</li>
 <li>Docker：打包镜像发布测试，一键运行！</li>
</ul>
<p><strong>2. 更便捷的升级和扩缩容</strong></p>
<ul>
 <li>通过使用Docker，部署应用 如同搭积木一样！</li>
</ul>
<p><strong>3. 更简单的系统运维</strong></p>
<ul>
 <li>使用容器化之后，开发和测试环境是高度一致的</li>
</ul>
<p><strong>4.更高效的计算资源利用</strong></p>
<ul>
 <li>Docker是内核级别的虚拟化，可以在一个物理机上运行很多的容器，让服务器的性能可以压榨到极致！</li>
</ul>
<hr>
<h2>二、 Docker安装</h2>
<h3>2.1 基本组成</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe841778324ac69e7b63543c3c0028959.jpeg&amp;size=m" alt=""></p>
<p><strong>镜像（Image）：</strong></p>
<blockquote>
 <p>docker镜像就好比一个模板，我们可以通过这个模板来创建容器服务，tomcat镜像===&gt;run==&gt;tomcat01容器（提供服务器），通过这个镜像可以创建多个容器（最终服务运行或者项目运行就是在容器中的）。</p>
</blockquote>
<p><strong>容器（container）：</strong></p>
<blockquote>
 <p>docker利用容器技术，独立运行一个或者一组应用 通过镜像来创建</p>
 <p>启动，停止，删除，基本命令！
  <br>
  目前就可以把这个容器理解为就是一个简易的linux系统</p>
</blockquote>
<p><strong>仓库（repository）：</strong></p>
<blockquote>
 <p>仓库就是存放 镜像（image）的地方！</p>
 <p>仓库又可以分为 公有仓库和私有仓库</p>
</blockquote>
<p>
 <br>
</p>
<h3>2.2 安装Docker</h3>
<blockquote>
 <p>环境查看</p>
 <pre><code># 系统内核是 4.18 以上的
[root@JWei_0124 /]# uname -r
4.18.0-348.7.1.el8_5.x86_64

# 查看系统版本
[root@JWei_0124 /]# cat /etc/os-release 
NAME="CentOS Linux"
VERSION="8 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Linux 8 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-8"
CENTOS_MANTISBT_PROJECT_VERSION="8"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="8"
</code></pre>
 <p>安装</p>
</blockquote>
<p><a href="https://cloud.tencent.com/document/product/213/46000" title="云服务器 搭建 Docker-最佳实践-文档中心-腾讯云 (tencent.com)">云服务器 搭建 Docker-最佳实践-文档中心-腾讯云 (tencent.com)</a></p>
<pre><code class="bash"># 1.卸载旧的版本
yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
# 2.需要的安装包
yum install -y yum-utils
# 3.设置镜像的仓库
yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo # 默认是从国外的。
yum-config-manager \
    --add-repo \
    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 推荐使用阿里云的。
# 安装容器之前，更新yum软件包索引。
yum makecache fast
# 4.安装容器相关的。docker-ce（社区版）docker-ee（企业版）
yum install docker-ce docker-ce-cli containerd.io
# 5.启动docker
systemctl start docker
# 6.使用docker version查看是否安装成功
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F196201053b012b5f5e213c994ef437a5.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<pre><code># 7.测试hello-world
docker run hello-world
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb6a3290469227e31a8b72a227d02f3e5.jpeg&amp;size=m" alt=""></p>
<pre><code># 8.查看一下下载的这个hello-world镜像
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F07e2a61d37523f2e319fc0a23a1d34d3.png&amp;size=m" alt=""></p>
<blockquote>
 <p>了解：卸载docker</p>
</blockquote>
<pre><code class="bash"># 1.卸载依赖
yum remove docker-ce docker-ce-cli containerd.io
# 2.删除资源
rm -rf /var/lib/docker
rm -rf /var/lib/containerd
# /var/lib/docker  docker的默认工作路径
</code></pre>
<p>
 <br>
</p>
<h3>2.3 阿里云镜像加速</h3>
<ol>
 <li>登录阿里云，找到容器服务。</li>
 <li>找到镜像加速地址。</li>
 <li>配置使用。
  <br>
  四个命令，依次执行即可：</li>
</ol>
<pre><code class="bash">1.
sudo mkdir -p /etc/docker
2.
sudo tee /etc/docker/daemon.json &lt;&lt;-'EOF'
{
"registry-mirrors": ["https://xxx.xxx.xxx.com"]
}
EOF

3.
sudo systemctl daemon-reload
4.
sudo systemctl restart docker
</code></pre>
<p><a href="https://cloud.tencent.com/document/product/1207/45596" title="腾讯云看这里">腾讯云看这里</a></p>
<h3>2.4 回顾hello-world流程</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb6a3290469227e31a8b72a227d02f3e5.jpeg&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc963a74699ff075974a77b3079b99975.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<h3>2.5 底层原理</h3>
<h4>2.5.1 docker是怎么工作的？</h4>
<blockquote>
 <p>Docker 是一个Client-Server结构的系统，Docker的<strong>守护进程</strong>运行在主机上。</p>
 <p>
  <br>
 </p>
 <p>通过Socket从客户端访问！</p>
 <p>
  <br>
 </p>
 <p>DockerServer 接收到Docker-Client的指令，就会执行这个命令！</p>
</blockquote>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F559c1a1c8ee9a42db804b3ad0cf9c8d8.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>2.5.2 docker为什么比VM（虚拟机）快？</h4>
<ol>
 <li>Docker有着比虚拟机更少的抽象层。</li>
 <li>docker利用的是宿主机的内核，vm需要是Guest OS。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fed334b904a3ed287a96711130b9d9735.png&amp;size=m" alt=""></p>
<p>新建一个容器的时候，docker不需要像虚拟机一样重新加载一个操作系统内核，避免引导。
 <br>
 虚拟机是加载GuestOS，分钟级别的，而docker是利用宿主机的操作系统，省略了这个复杂的过程，秒级！</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<hr>
<h2>三、Docker的常用命令</h2>
<h3>3.1 帮助命令</h3>
<pre><code class="bash">docker version        # 显示docker的版本信息
docker info              # 显示docker的系统信息，包括镜像和容器的数量
docker 命令 --help         # 帮助命令
</code></pre>
<p>帮助文档的地址：<a href="https://docs.docker.com/reference/" title="Reference documentation | Docker Documentation">Reference documentation \| Docker Documentation</a></p>
<p>
 <br>
</p>
<h3>3.2 镜像命令</h3>
<h4>3.2.1 docker images</h4>
<blockquote>
 <ol>
  <li><code>REPOSITORY 镜像的仓库源</code></li>
  <li><code>TAG 镜像的标签</code></li>
  <li><code>IMAGE ID 镜像的id</code></li>
  <li><code>CREATED 镜像的创建时间</code></li>
  <li><code>SIZE 镜像的大小</code></li>
  <li><code># 命令参数可选项</code></li>
  <li><code>-a, --all # 显示所有镜像 (docker images -a)</code></li>
  <li><code>-q, --quiet # 仅显示镜像id (docker images -q)</code></li>
 </ol>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fef4d057f6ef3bd68ed2ef9fce4137101.png&amp;size=m" alt=""></p>
</blockquote>
<h4>3.2.2 docker search（搜索镜像）</h4>
<blockquote>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc810dae54ba0e1b618335ca3f44774a0.png&amp;size=m" alt=""></p>
 <p><code>解释</code></p>
 <ol>
  <li><code>命令参数可选项 (通过搜索来过滤)</code></li>
  <li><code>--filter=STARS=3000 # 搜索出来的镜像就是stars大于3000的</code></li>
 </ol>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F260388379066eaadc1db17301cf7005a.png&amp;size=m" alt=""></p>
</blockquote>
<h4>3.2.3 docker pull（下载镜像）</h4>
<pre><code class="bash"># 下载镜像：docker pull 镜像名[:tag]
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker pull mysql
Using default tag: latest            # 如果不写tag，默认就是latest，最新的版本
latest: Pulling from library/mysql
72a69066d2fe: Pull complete            # 分层下载，docker image的核心，联合文件下载
93619dbc5b36: Pull complete
99da31dd6142: Pull complete
626033c43d70: Pull complete
37d5d7efb64e: Pull complete
ac563158d721: Pull complete
d2ba16033dad: Pull complete
688ba7d5c01a: Pull complete
00e060b6d11d: Pull complete
1c04857f594f: Pull complete
4d7cfa90e6ea: Pull complete
e0431212d27d: Pull complete
Digest: sha256:e9027fe4d91c0153429607251656806cc784e914937271037f7738bd5b8e7709 #签名
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest        # 真实地址
# 两个命令是等价的
docker pull mysql
docker pull docker.io/library/mysql:latest
# 指定版本下载
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
72a69066d2fe: Already exists        # 联合文件下载，已经存在的资源可以共用
93619dbc5b36: Already exists
99da31dd6142: Already exists
626033c43d70: Already exists
37d5d7efb64e: Already exists
ac563158d721: Already exists
d2ba16033dad: Already exists
0ceb82207cd7: Pull complete
37f2405cae96: Pull complete
e2482e017e53: Pull complete
70deed891d42: Pull complete
Digest: sha256:f2ad209efe9c67104167fc609cca6973c8422939491c9345270175a300419f94
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
</code></pre>
<h4>3.2.4 docker rmi（删除镜像）</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9bb5b22edfb1c7434830ba57f2126e76.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<pre><code class="bash">[root@JWei_0124 //]# docker rmi -f 镜像id                    # 删除指定的镜像
[root@JWei_0124 //]# docker rmi -f 镜像id 镜像id 镜像id    # 删除多个镜像（空格分隔）
[root@JWei_0124 //]# docker rmi -f $(docker images -aq)    # 删除全部的镜像
</code></pre>
<h3>3.3 容器命令</h3>
<p>说明：我们有了镜像才可以创建容器，linux，下载一个centos 镜像来测试学习。</p>
<pre><code class="bash">docker pull centos
</code></pre>
<h4>3.3.1 新建容器并启动</h4>
<pre><code class="bash">docker run [可选参数] image
# 参数说明
--name="name"        容器名字：用来区分容器
-d                    后台方式运行：相当于nohup
-it                    使用交互式运行：进入容器查看内容
-p                    指定容器的端口（四种方式）小写字母p
    -p ip:主机端口：容器端口
    -p 主机端口：容器端口
    -p 容器端口
    容器端口
-P                     随机指定端口（大写字母P）
# 测试：启动并进入容器
[root@JWei_0124 module]# docker run -it centos /bin/bash
[root@f8fad61a6c96 /]# ls  # 查看容器内的centos（基础版本，很多命令都是不完善的）
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

# 从容器中退回到主机
[root@f8fad61a6c96 /]# exit
exit
[root@JWei_0124 module]# 
[root@JWei_0124 /]# ls
bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var  www
</code></pre>
<h4>3.3.2 列出所有运行的容器</h4>
<pre><code class="bash">docker ps    # 列出当前正在运行的容器
# 命令参数可选项
-a        # 列出当前正在运行的容器+历史运行过的容器
-n=?    # 显示最近创建的容器（可以指定显示几条，比如-n=1）
-q        # 只显示容器的编号

[root@JWei_0124 //]# docker ps 
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[root@JWei_0124 //]# docker ps -a
CONTAINER ID   IMAGE          COMMAND       CREATED         STATUS                          PORTS     NAMES
f8fad61a6c96   centos         "/bin/bash"   2 minutes ago   Exited (0) About a minute ago             epic_greider
b4b5e50d9889   centos         "/bin/bash"   2 minutes ago   Exited (0) 2 minutes ago                  suspicious_mendeleev
321c5e25bca9   feb5d9fea6a5   "/hello"      2 hours ago     Exited (0) 2 hours ago                    wonderful_saha
[root@JWei_0124 //]# docker ps -a -n=1
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS                          PORTS     NAMES
f8fad61a6c96   centos    "/bin/bash"   2 minutes ago   Exited (0) About a minute ago             epic_greider
[root@JWei_0124 //]# 
</code></pre>
<h4>3.3.3 退出容器</h4>
<pre><code class="bash">exit        # 容器直接停止，并退出
ctrl+P+Q    # 容器不停止，退出
[root@JWei_0124 //]# docker run -it centos /bin/bash  //交互式进入
[root@68b68a9576e0 /]# [root@JWei_0124 //]#  //按快捷键 自动输入
[root@JWei_0124 //]# docker ps
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS         PORTS     NAMES
68b68a9576e0   centos    "/bin/bash"   2 minutes ago   Up 2 minutes             peaceful_jemison
[root@JWei_0124 //]# 
</code></pre>
<h4>3.3.4 删除容器</h4>
<pre><code class="bash">docker rm 容器id                    # 删除容器（不能删除正在运行的容器）如果要强制删除：docker rm -f 容器id
docker rm -f $(docker ps -aq)        # 删除全部容器
docker ps -a -q|xargs docker rm        # 删除所有容器
</code></pre>
<h4>3.3.5 启动和停止容器的操作</h4>
<pre><code class="XML">docker start 容器id        # 启动容器
docker restart 容器id    # 重启容器
docker stop 容器id        # 停止当前正在运行的容器
docker kill 容器id        # 强制停止当前容器
</code></pre>
<h3>3.4 常用其他命令</h3>
<h4>3.4.1 后台启动容器</h4>
<pre><code class="bash"># 命令docker run -d 镜像名
[root@JWei_0124 //]# docker run -d centos
5b06d0d14b3312e589a411dd9ae15589dc9321f771e5615b7ae26e85017de080
# 问题：docker ps发现centos停止了
# 常见的坑：docker容器使用后台运行，就必须要有要一个前台进程，docker发现没有应用，就会自动停止。
# 比如：nginx，容器启动后，发现自己没有提供服务，就会立刻停止，就是没有程序了
</code></pre>
<h4>3.4.2 查看日志</h4>
<pre><code class="bash">docker logs -tf --tail 容器id
# 自己编写一段shell脚本
docker run -d centos /bin/sh -c "while true;do echo kuangshen;sleep 1;done"
[root@JWei_0124 //]# docker logs -tf  容器id
[root@JWei_0124 //]# docker logs -tf --tail 10  容器id
# 显示日志
-tf                        # 显示日志
--tail number    # 要显示的日志条数
</code></pre>
<h4>3.4.3 查看容器中进程的信息</h4>
<pre><code class="bash"># 命令 docker top 容器id 
[root@JWei_0124 ~]# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS      NAMES
25eb9d70b2b4   redis     "docker-entrypoint.s..."   About a minute ago   Up About a minute   6379/tcp   awesome_chatterjee
[root@JWei_0124 ~]# docker top 25eb9d70b2b4
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
systemd+            181442              181422              0                   09:47               ?                   00:00:00            redis-server *:6379
</code></pre>
<h4>3.4.4 查看镜像的元数据</h4>
<p><code>命令docker inspect 容器id</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F340655dfb111974e9ee042d43d006165.png&amp;size=m" alt=""></p>
<h4>3.4.5 进入当前正在运行的容器</h4>
<pre><code class="bash"># 我们通常容器都是使用后台方式运行的，需要进入容器，修改一些配置
# 命令
docker exec -it 容器id /bin/bash
# 测试
[root@JWei_0124 ~]# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
88d23bcbe1f2   centos    "/bin/sh -c 'while t..."   13 minutes ago   Up 13 minutes             silly_lichterman
[root@JWei_0124 ~]# docker exec -it 88d23bcbe1f2 /bin/bash
[root@88d23bcbe1f2 /]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 08:23 ?        00:00:00 /bin/sh -c while true;do echo kuangshen;sleep 1;done
root       841     0  0 08:37 pts/0    00:00:00 /bin/bash
root       858     1  0 08:37 ?        00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1
root       859   841  0 08:37 pts/0    00:00:00 ps -ef
# 方式二
docker attach 容器id
# 测试
[root@JWei_0124  ~]# docker attach 88d23bcbe1f2
正在执行当前的代码...
# docker exec        # 进入容器后开启一个新的终端，可以再里面操作（常用）
# docker attach        # 进入容器正在执行的终端，不会启动新的进程。
</code></pre>
<h4>3.4.6 从容器内拷贝文件到主机</h4>
<pre><code class="bash">docker cp 容器id:容器内路径 目的主机的路径
[root@iZbp13qr3mm4ucsjumrlgqZ home]# ll
total 0
[root@iZbp13qr3mm4ucsjumrlgqZ home]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[root@iZbp13qr3mm4ucsjumrlgqZ home]# docker run -it centos /bin/bash
[root@6eda31ad7987 /]# [root@iZbp13qr3mm4ucsjumrlgqZ home]# docker ps
CONTAINER ID   IMAGE     COMMAND       CREATED          STATUS          PORTS     NAMES
6eda31ad7987   centos    "/bin/bash"   17 seconds ago   Up 16 seconds             stoic_kepler
# 进入到容器内部
[root@iZbp13qr3mm4ucsjumrlgqZ home]# docker attach 6eda31ad7987
[root@6eda31ad7987 /]# ls
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@6eda31ad7987 /]# cd /home/
[root@6eda31ad7987 home]# ls
# 在容器的/home路径下创建test.java文件
[root@6eda31ad7987 home]# touch test.java
[root@6eda31ad7987 home]# ls
test.java
[root@6eda31ad7987 home]# exit
exit
[root@iZbp13qr3mm4ucsjumrlgqZ home]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[root@iZbp13qr3mm4ucsjumrlgqZ home]# docker ps -a
CONTAINER ID   IMAGE     COMMAND       CREATED              STATUS                      PORTS     NAMES
6eda31ad7987   centos    "/bin/bash"   About a minute ago   Exited (0) 28 seconds ago             stoic_kepler
# 将文件拷贝出来到主机上（在主机上执行该命令）
[root@iZbp13qr3mm4ucsjumrlgqZ home]# docker cp 6eda31ad7987:/home/test.java /home
[root@iZbp13qr3mm4ucsjumrlgqZ home]# ls
test.java
# 拷贝是一个手动过程，未来我们使用 -v 卷的技术，可以实现，自动同步（容器内的/home路径和主机上的/home路径打通）
</code></pre>
<p>
 <br>
</p>
<h3>3.5 小结 （常用命令汇总）</h3>
<p><a href="https://blog.csdn.net/qq_54729417/article/details/127945665?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22127945665%22%2C%22source%22%3A%22qq_54729417%22%7D" title="docker常用命令_JWei_7的博客-CSDN博客">docker常用命令_JWei_7的博客-CSDN博客</a></p>
<p><a href="https://blog.csdn.net/huwh_/article/details/71308119" title="Docker常用命令原理图_胡伟煌的博客-CSDN博客">Docker常用命令原理图_胡伟煌的博客-CSDN博客</a> 这个是一个大佬写的原理图！！！ yyds</p>
<p>
 <br>
</p>
<h4>3.6 练习安装Nginx</h4>
<ol>
 <li>
  <p>搜索镜像：docker search nginx (建议去dockerHub上去搜索)</p>
 </li>
 <li>
  <p>下载镜像：docker pull nginx</p>
 </li>
 <li>
  <p>启动镜像</p>
 </li>
</ol>
<pre><code class="bash"> # -d 后台运行
 # --name="nginx01"    给容器命名
 # -p 宿主机端口:容器内部端口

[root@JWei_0124 ~]# docker run -d --name nginx01 -p 3344:80 nginx
a96ced70d4c970b8b77d48b218c682a9efd090f89503b9b5bb402c345e77762f
[root@JWei_0124 ~]# docker ps 
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS         PORTS                                   NAMES
a96ced70d4c9   nginx     "/docker-entrypoint...."   10 seconds ago   Up 9 seconds   0.0.0.0:3344-&gt;80/tcp, :::3344-&gt;80/tcp   nginx01
</code></pre>
<ol>
 <li>测试访问</li>
</ol>
<pre><code class="bash">[root@JWei_0124 ~]# curl localhost:3344
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome to nginx!&lt;/title&gt;
&lt;style&gt;
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;
&lt;p&gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&lt;/p&gt;

&lt;p&gt;For online documentation and support please refer to
&lt;a href="http://nginx.org/"&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;
Commercial support is available at
&lt;a href="http://nginx.com/"&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
[root@JWei_0124 ~]# 
</code></pre>
<ol>
 <li>接口暴露</li>
</ol>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9412355a1e11c221597ccf853b2ab426.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3>3.6 Docker可视化界面工具</h3>
<h4>3.6.1 什么是portainer ?</h4>
<p>Docker图形化界面管理工具！提供一个后台面板供我们操作！</p>
<p>类似于 宝塔面板</p>
<h4>3.6.2 安装</h4>
<p>8088 外网访问端口 9000 是内部映射端口</p>
<pre><code class="bash">docker run -d -p 8088:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
</code></pre>
<p>访问测试：<a href="http://ip:8088/" title="http://ip:8088">http://ip:8088</a></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffb377d03db72fc9529b6d5b2d2273ffa.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>选择本地的：Local</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb85df53e368680c439603d4f05c875e4.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>进入之后的面板</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F73e489b8505443a1902cebae98aad9ba.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<hr>
<h2>四、Docker镜像</h2>
<h3>4.1 镜像是什么</h3>
<blockquote>
 <p>镜像是一种轻量级，可执行化的独立软件包，用来打包软件运行环境和基于运行环境开发的软件，它包含运行某个软件锁需要的所有内容，包括代码，运行时（库，环境变量和配置文件）</p>
 <p>所有的应用 直接打包docker 镜像 就可以直接跑起来！</p>
</blockquote>
<h3>4.2 镜像加速原理</h3>
<h4>4.2.1 UnionFS（联合文件系统）</h4>
<p>我们下载的时候看到的一层层就是这个！</p>
<blockquote>
 <p>UnionFS（联合文件系统）：Union文件系统（UnionFS）是一种分层、轻量级并且高性能的文件系统，它支持对文件系统的修改作为一次提交来一层层的叠加，同时可以将不同目录挂载到同一个虚拟文件系统下（unite several directories into a single virtual filesystem）。Union 文件系统是Docker 镜像的基础。镜像可以通过分层来进行继承，基于基础镜像（没有父镜像），可以制作各种具体的应用镜像。</p>
</blockquote>
<p><strong>特性：</strong>一次同时加载多个文件系统，但从外面看起来，只能看到一个文件系统，联合加载会把各层文件系统叠加起来，这样最终的文件系统会包含所有底层的文件和目录。</p>
<h4>4.2.2 Docker 镜像加载原型</h4>
<p>docker的镜像实际上由一层一层的文件系统组成，这种层级的文件系统UnionFS。</p>
<p>
 <br>
</p>
<blockquote>
 <p><strong>bootfs（boot file system）</strong>主要包含bootloader和kernel，bootloader主要是引导加载kernel，Linux刚启动时会加载bootfs文件系统，在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的，包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了，此时内存的使用权已由bootfs转交给内核，此时系统也会卸载bootfs。
  <br>
  <strong>rootfs（root file system）</strong> ，在bootfs之上。包含的就是典型Linux系统中的/dev，/proc，/bin，/etc等标准目录和文件。
  <br>
  rootfs就是各种不同的操作系统发行版，比如Ubuntu，Centos等等。</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0589875ffdf48da8f60e61cea09bd79f.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F244ca65399988aad7819c5e465b849d2.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<blockquote>
 <p>平时我们安装进虚拟机的CentOS都是好几个G，为什么Docker这里才200M？</p>
</blockquote>
<p>对于一个精简的OS，rootfs可以很小，只需要包含最基本的命令，工具和程序库就可以了，因为底层直接用Host的kernel，自己只需要提供rootfs就可以了。
 <br>
 由此可见对于不同的linux发行版，bootfs基本是一致的，rootfs会有差别，因此不同的发行版可以公用bootfs。
 <br>
 （虚拟机是分钟级，容器是秒级！）</p>
<p>
 <br>
</p>
<h3>4.3 分层理解</h3>
<blockquote>
 <p>分层镜像</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe51fbbd6de3477abf31e5e9125ac5da6.png&amp;size=m" alt=""></p>
<p>我们可以去下载一个镜像，注意观察下载的日志输出，可以看到是一层一层的在下载！</p>
<blockquote>
 <p>Docker镜像要采用这种分层的好处？</p>
</blockquote>
<p><strong>资源共享</strong>有多个镜像都从相同的Base镜像构建而来，那么宿主机只需在磁盘上保留一份base镜像，同时内存中也只需要加载一份base镜像，这样就可以为所有的容器服务了，而且镜像的每一层都可以被共享。</p>
<p>
 <br>
</p>
<p>所有的Docker 镜像都起始于一个基础镜像层，当进行修改或增加新的内容时，就会在当前镜像层之上，创建新的镜像层。
 <br>
 举一个简单的例子，假如基于Ubuntu Linux 16.04创建一个新的镜像，这就是新镜像的第一层；如果在该镜像中添加Python包，就会在基础镜像层之上创建第二个镜像层；如果继续添加一个安全补丁，就会创建第三个镜像层。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6bfe275bfb1a5616607dd187b336a878.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>简单来说 就是 原本有的 就共享 没有的咱就下载 更新了 就替换新的</p>
<p>
 <br>
</p>
<p><strong>特点：</strong></p>
<p>Docker镜像都是只读的，当容器启动时，一个新的可写层被加载到镜像的顶部！
 <br>
 这一层就是我们通常说的容器层，容器之下的都叫镜像层！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F99b2f7fb29f89ba6d9f0c0dc9a5e287a.png&amp;size=m" alt=""></p>
<h3>4.4 commit镜像</h3>
<pre><code class="bash"># 提交容器成为一个新的副本
docker commit
# 命令和git原理类似
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名:[TAG]
</code></pre>
<blockquote>
 <h1>1.启动一个默认的tomcat。</h1>
 <h1>2.发现这个默认的tomcat是没有webapps应用，镜像的原因，官方的镜像默认webapps下面是没有文件的。</h1>
 <h1>3.我自己拷贝进去了基本的文件。</h1>
 <h1>4.将我们操作过的容器通过commit提交为一个镜像！我们以后就使用我们修改过的镜像即可，这就是我们自己的一个修改的镜像。</h1>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3eca003f25096d1dc9315de8d252603f.png&amp;size=m" alt=""></p>
 <p>
  <br>
 </p>
 <p>小结： commit镜像就是游戏存档 tag是版本版本信息</p>
</blockquote>
<p>
 <br>
</p>
<hr>
<p>
 <br>
</p>
<h2>五、 容器数据卷</h2>
<h3>5.1 什么是容器数据卷？</h3>
<p>Docker容器数据卷，即Docker Volume（卷）。</p>
<blockquote>
 <p>当Docker容器运行的时候，会产生一系列的数据文件，这些数据文件会在关闭Docker容器时，直接消失的。但是其中产生部分的数据内容，我们是希望能够把它给保存起来，另作它用的。</p>
 <p>关闭Docker容器=删除内部除了image底层数据的其他全部内容，即删库跑路</p>
</blockquote>
<p>所以我们期望：</p>
<blockquote>
 <p>将应用与运行的环境打包形成容器运行，伴随着容器运行产生的数据，我们希望这些数据能够持久化。
  <br>
  希望容器之间也能够实现数据的共享、</p>
</blockquote>
<p>
 <br>
</p>
<ul>
 <li>Docker容器产生的数据同步到本地,这样关闭容器的时候，数据是在本地的，不会影响数据的安全性。</li>
 <li>docker的容器卷技术也就是将容器内部目录和本地目录进行一个同步，即挂载。</li>
</ul>
<p><strong>总结： 容器的持久化和同步化操作，容器之间也是可以数据共享的（但是注意挂载不是等同于同步！！！）</strong></p>
<h3>5.2 使用数据卷</h3>
<p><strong>使用命令来挂载 -v</strong></p>
<p>主机目录和容器内的目录是映射关系</p>
<pre><code class="bash">docker run -it -v 主机目录:容器内目录 镜像名 /bin/bash
# 测试，查看容器信息
docker inspect 容器id
</code></pre>
<ul>
 <li>mounts 加载的文件系统</li>
 <li>source 是源地址 就是 当前你这个docker里面的地址目录</li>
 <li>destination 是 这个容器的目录</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffcdb9d60f51c7fad6b89df02b6d06c57.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fafd1daf36fb5f08f0f5ea1995487efe1.png&amp;size=m" alt=""></p>
<blockquote>
 <p>通过 -v挂载目录后 会对这个俩个目录中的数据进行 双向绑定（bind）</p>
</blockquote>
<p>在容器的/home文件夹下，新建test.java文件，会同步到主机的/home/ceshi文件夹下。
 <br>
 <code>删除操作也是同步的；双向绑定，保证两边文件夹下的数据始终是一直的。</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F33c47946097a0f8c7ab198119e7f4095.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>停止容器后，在主机的/home/ceshi文件夹下，修改文件或新增文件，启动容器，查看容器的/home文件夹，发现容器内的数据依旧是同步的</p>
<ol>
 <li>停止容器。</li>
 <li>宿主机上修改文件。</li>
 <li>启动容器。</li>
 <li>容器内的数据依旧是同步的。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5700f6d5180126aab96188caa44d1ec5.png&amp;size=m" alt=""></p>
<p>好处：我们以后修改只需要在本地修改即可，容器内会自动同步！</p>
<p>
 <br>
</p>
<h3>5.3 安装MySQL</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F056aeff240202aeae69ac25f65d08178.png&amp;size=m" alt=""></p>
<pre><code class="bash"># 获取镜像
docker pull mysql:5.7
# 运行容器，需要做数据目录挂载。（安装启动mysql，注意：需要配置密码）
# 官方启动mysql
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
# 我们启动mysql（-e是环境配置）
[root@JWei_0124 home]# docker run -d -p 8081:3306
 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql 
-e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:8.0.27
# 启动成功之后，我们在本地使用navicat来接测试一下。
# navicat连接到服务器的8081端口和容器内的3306映射，这个时候我们就可以连接上了！
# 在本地测试创建一个数据库，查看一下我们映射的路径是否ok！（OK的）
</code></pre>
<p>在navicat上 创建一个数据库</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5ff47aaa67a310f6b682bdc9ae0ef766.png&amp;size=m" alt=""></p>
<p>查看本地</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5535812c37216bd9e4aebc2776b95f16.png&amp;size=m" alt=""></p>
<p>现在 删除 容器</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2a4bbed0a1694a2250d443f15e69cf75.png&amp;size=m" alt=""></p>
<p>发现，我们挂载到本地的数据卷依旧没有丢失，这就实现了容器数据持久化功能！</p>
<h3>5.4 匿名挂载和具名挂载</h3>
<pre><code class="bash"># 匿名挂载
docker run -d -p --name nginx01 -v /etc/nginx nginx
# 查看所有的volume的情况
[root@JWei_0124 ~]# docker volume ls
DRIVER    VOLUME NAME
local     964b8e505f12f65fb23fd21f05cfa9ecd6c2c6b2ca89c0e44f168bb017dfabd6
# 这种就是匿名挂载：我们在-v挂载目录时，只写了容器内的路径，没有写容器外的路径。
# 具名挂载
[root@JWei_0124 ~]# docker run -d -p 3344:80 --name nginx02 -v juming-nginx:/etc/nginx nginx
[root@JWei_0124 home]# docker volume ls
DRIVER    VOLUME NAME
local     1be3512d772b7af8543c35141d5bbbfe29549dabf0babb7ce8693833387de41d
local     58ba3799ae59416c2b34d0672dfa848d158006f840bdb28b41ed463ed0a15599
# 通过 -v 卷名:容器内的路径（具名挂载）
# 查看一下这个卷
</code></pre>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa010d116c021d1fe9f80b928c32306b0.png&amp;size=m" alt=""></p>
<p>所有的docker容器内的卷，没有指定目录的情况下都是在<code>/var/lib/docker/volumes/xxxx/_data"</code>（xxxx是卷名）
 <br>
 我们通过具名挂载可以方便的找到我们的一个卷，<code>大多数情况在使用的具名挂载</code></p>
<pre><code class="bash"># 如何确定是具名挂载，还是匿名挂载，还是指定路径挂载
-v 容器内的路径                # 匿名挂载
-v 卷名:容器内的路径        # 具名挂载
-v /宿主机路径:容器内路径    # 指定路径挂载
</code></pre>
<p>拓展</p>
<pre><code class="bash"># 通过 -v 容器内的路径:ro    rw    改变读写权限
ro    read only    # 只读
rw    read write    # 可读可写
# 一旦设置了容器权限，容器对我们挂载出来的内容就有了限定。
docker run -d -p 3344:80 --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
docker run -d -p 3344:80 --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
# 只要看到ro就说明这个路径只能通过宿主机来操作，容器内部是无法操作！
</code></pre>
<p>
 <br>
</p>
<h3>5.5 Dockerfile</h3>
<p>dockerfile 就是用来构建 docker 镜像的构建文件。 命令脚本！</p>
<p>通过这个脚本可以生成镜像，镜像是一层一层的，脚本一个一个的命令，每个命令都是一层！</p>
<pre><code class="bash"># 创建一个dockerfile文件，名字可以随机，建议dockerfile
# 文件中的内容：指令都是大写
FROM centos
VOLUME ["volume01","volume02"]
CMD echo "-----end-----"
CMD /bin/bash
# 这里的每个命令，就是镜像的一层。
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1d09b1f30207a4e1f3819310bad43adb.png&amp;size=m" alt=""></p>
<p>启动容器：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4368163e222080942c30341955b866b4.png&amp;size=m" alt=""></p>
<p>这个卷根外部一定有一个同步的目录！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5196a59aefe899ff64a050fe5bd0f7ca.png&amp;size=m" alt=""></p>
<p>查看一下卷挂载的路径：docker inspect 容器id</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5d1b22d27599d2580e430fe1559cf467.png&amp;size=m" alt=""></p>
<p>测试一下文件是否同步出去：在容器的volume01文件夹下创建文件，查看宿主机对应目录是否同步成功。
 <br>
 <code>这种方式我们未来使用的十分多，因为我们通常会构建自己的镜像！</code>
 <br>
 如果构建镜像时候没有挂载卷，就需要自己手动镜像挂载目录！</p>
<p>
 <br>
</p>
<h3>5.6 数据卷容器</h3>
<p>多个容器同步数据（临时认父）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F65e153888926d92ed1162393ca2c13a2.png&amp;size=m" alt=""></p>
<blockquote>
 <p><code>启动3个容器，通过我们刚才自己的写镜像启动。</code></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F76749d8285fa4f649087837b2e9f7f72.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F60d8842ccc9becc074a697bbc0c822bf.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff7ec2d8531871f289d4475adc7fa0ab9.png&amp;size=m" alt=""></p>
<blockquote>
 <p>三个容器的数据是 互相拷贝的 删除任意一个 其余的容器都可以看到那些数据 当然 所以的容器都删了 也就会删除了 除非做了持久化映射</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F29218daed740070049d5ddf1aa887f8f.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F915661f0017e1bfaff9b721c8f656fb1.png&amp;size=m" alt=""></p>
<blockquote>
 <p>多个mysql实现数据共享：</p>
 <p>
  <br>
 </p>
 <pre><code>[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker run -d -p 7777:3306 -v 
/home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e 
MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker run -d -p 7777:3306 -e
 MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7
# 这个时候，可以实现两个容器数据同步！
</code></pre>
</blockquote>
<p><strong>结论：</strong>
 <br>
 容器之间配置信息的传递，数据卷容器的生命同期一直持续到没有容器使用为止。
 <br>
 但是一旦你持久化到了本地，这个时候，本地的数据是不会删除的！</p>
<p>
 <br>
</p>
<hr>
<p>
 <br>
</p>
<h2>六、DockerFile</h2>
<h3>6.1 什么是DockerFile ？</h3>
<blockquote>
 <p>dockerfile是用来构建docker镜像的文件！命令参数脚本！</p>
</blockquote>
<p>构建步骤：
 <br>
 1、编写一个dockerfile文件
 <br>
 2、docker build 构建成为一个镜像
 <br>
 3、docker run运行镜像
 <br>
 4、docker push发布镜像（DockerHub、阿里云镜像仓库！）</p>
<h3>6.2 搭建步骤</h3>
<p>1、每个保留关键字（指令）都是必须是大写字母
 <br>
 2、执行从上到下顺序执行
 <br>
 3、# 表示注释
 <br>
 4、每一个指令都会创建提交一个新的镜像层，并提交！</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0338d8b2eed5c1714c42f8850b42da31.png&amp;size=m" alt=""></p>
<p>dockerfile是面向开发的，我们以后要发布项目，做镜像，就需要编写dockerfile文件，这个文件十分简单！</p>
<p>DockerFile：构建文件，定义了一切的步骤，源代码。
 <br>
 Dockerlmages：通过DockerFile构建生成的镜像，最终发布和运行的产品。
 <br>
 Docker容器：容器就是镜像运行起来提供服务的。</p>
<h3>6.3 DockerFile的命令</h3>
<pre><code>FROM        # 基础镜像，一切从这里开始构建
MAINTAINER    # 镜像是谁写的：姓名+邮箱
RUN            # 镜像构建的时候需要运行的命令
ADD            # 步骤：tomcat镜像，这个tomcat压缩包！添加内容
WORKDIR        # 镜像的工作目录
VOLUME        # 挂载的目录
EXPOSE        # 暴露端口配置
CMD            # 指定这个容器启动的时候要运行的命令，只有最后一个会生效，可被替代
ENTRYPOINT    # 指定这个容器启动的时候要运行的命令，可以追加命令
ONBUILD        # 当构建一个被继承DockerFile这个时候就会运行ONBUILD的指令。触发指令。
COPY        # 类似ADD，将我们文件拷贝到镜像中
ENV            # 构建的时候设置环境变量！
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7192340f11caa9cb6e52f49cc8810082.jpeg&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3>6.4 测试</h3>
<blockquote>
 <p>Docker Hub中99%镜像都是从这个基础镜像过来的FROM scratch，然后配置需要的软件和配置来进行的构建。</p>
</blockquote>
<pre><code class="vbnet"># 1. 编写dockerfile的文件
FROM centos:7
MAINTAINER sywl&lt;xxx@qq.com&gt;
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "-----end-----"
CMD /bin/bash
# 2. 通过这个文件构建镜像
# 命令：docker build -f dockerfile文件路径 -t 镜像名:[tag]
docker build -f mydockerfile-centos -t mycentos:0.1 .
Successfully built 285c2064af01
Successfully tagged mycentos:0.1
# 3. 测试运行
</code></pre>
<p>之前的原生的centos7：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6f119959415e33e2ba7815acd307eb6.jpeg&amp;size=m" alt=""></p>
<p>我们增加之后的镜像：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd6b905a67dbf935ecc3a4884702ce890.jpeg&amp;size=m" alt=""></p>
<p>我们可以列出本地进行的变更历史：docker history 镜像id</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc856d5003c059b71af9163b556e46d24.jpeg&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<blockquote>
 <p>CMD和ENTRYPOINT区别</p>
</blockquote>
<pre><code class="vbnet">CMD            # 指定这个容器启动的时候要运行的命令，只有最后一个会生效，可被替代
ENTRYPOINT     # 指定这个容器启动的时候要运行的命令，可以追加命令
</code></pre>
<p>测试CMD</p>
<pre><code class="vbnet"># 1. 编写dockerfile文件
[root@iZbp13qr3mm4ucsjumrlgqZ dockerfile]# vim mydockerfile-cmd-test
[root@iZbp13qr3mm4ucsjumrlgqZ dockerfile]# cat mydockerfile-cmd-test
FROM centos:7
CMD ["ls","-a"]
# 2. 构建镜像
[root@iZbp13qr3mm4ucsjumrlgqZ dockerfile]# docker build -f mydockerfile-cmd-test -t cmdtest .
# 3. run运行，发现我们的"ls -a"命令生效、执行
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0364d0e1563aadeb91f5744344c14758.jpeg&amp;size=m" alt=""></p>
<pre><code class="vbnet"># 4. 我们先追加一个命令"l",构成"ls -al"命令，发现报错
[root@iZbp13qr3mm4ucsjumrlgqZ dockerfile]# docker run ec0d2dd226b3 -l
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "-l": executable file not found in $PATH: unknown.
ERRO[0000] error waiting for container: context canceled
# 原因：CMD命令的情况下，"-l"替换了CMD["1s"，"-a"]命令，因为"-l"不是命令，所以报错！
</code></pre>
<p>测试ENTRYPOINT</p>
<pre><code class="vbnet"># 1. 编写dockerfile文件
[root@iZbp13qr3mm4ucsjumrlgqZ dockerfile]# vim mydockerfile-entrypoint-test
[root@iZbp13qr3mm4ucsjumrlgqZ dockerfile]# cat mydockerfile-entrypoint-test
FROM centos:7
ENTRYPOINT ["ls","-a"]
# 2. 构建镜像
[root@iZbp13qr3mm4ucsjumrlgqZ dockerfile]# docker build -f mydockerfile-entrypoint-test -t entrypointtest .
# 3. run运行，发现我们的"ls -a"命令生效、执行
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9bcb5f9adb5e8c50a034596a82c4fb19.jpeg&amp;size=m" alt=""></p>
<pre><code class="vbnet"># 4. 我们先追加一个命令"l",构成"ls -al"命令，发现命令生效、执行
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3419139d26f113d25dcce2d3ac942e63.jpeg&amp;size=m" alt=""></p>
<pre><code class="vbnet"># 原因：ENTRYPOINT命令的情况下，"-l"追加在ENTRYPOINT ["1s"，"-a"]命令后面，得到"ls -al"的命令，所以命令正常执行！
# （我们的追加命令，是直接拼接在我们的ENTRYPOINT命令的后面）
</code></pre>
<p>
 <br>
</p>
<h3>6.5 小结</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdeda3bd54e604739d2802a6a5b61060c.jpeg&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<hr>
<h2>七、 Docker 网络</h2>
<h3>7.1 docker0</h3>
<p>清空所有的环境</p>
<blockquote>
 <p>测试</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa4ed0b61e393d697267febf802f9116f.jpeg&amp;size=m" alt=""></p>
<p>三个</p>
<blockquote>
 <h1>问题：docker是如何处理容器网络访问的？</h1>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2823b9cafaa5813de6bb43691965de05.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<pre><code class="vbnet">[root@JWei_0124 ~]# docker run -d -P --name tomcat01 tomcat
# 查看容器的内部网络地址ip addr，发现容器启动的时候会得到一个eth0@if119的ip地址（docker分配的）
[root@JWei_0124~]# docker exec -it tomcat01 ip addr
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
118: eth0@if119: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
# 思考：liunx能不能ping通容器内部？（linux可以ping通容器内部）
[root@JWei_0124~]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.059 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.046 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.057 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.045 ms
</code></pre>
<blockquote>
 <p>我们每启动一个docker容器，docker就会给docker容器分配一个ip，我们只要安装了ddcker，就会有一个网卡docker0</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fab01c8487a1765fb967c6e7e446451e3.png&amp;size=m" alt=""></p>
<p>在启动一个容器测试 发现又多了一对网卡</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9af21628e6f3d6c59bf59a3a7bbedcdf.png&amp;size=m" alt=""></p>
<blockquote>
 <p>所以 我们可以发现 容器带来的网卡 都是一对一对的</p>
 <p><code>veth-pair 就是一对的虚拟设备接口，他们都是成对出现的，一端连着协议，一端彼此相连。</code></p>
 <p><code>正因为有这个特性，evth-pair充当一个桥梁，连接各种虚拟网络设备的。</code></p>
 <p>
  <br>
 </p>
 <p>openstack<code>，</code>Docker<code>容器之间的连接，OVS的连接，都是使用</code><strong>veth-pair</strong><code>技术。</code></p>
</blockquote>
<p>我们来测试下tomcat01和tomcat02是否可以ping通！（可以ping通）</p>
<pre><code class="vbnet"> [root@JWei_0124~]# docker exec -it tomcat02 ping 172.17.0.2
 PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
 64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.094 ms
 64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.055 ms
 # 结论：容器和容器之间是可以互相ping通的！
</code></pre>
<p>
 <br>
</p>
<p>绘制一个网络模型图</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff74468a76981cdfa0b325e8cddfc5bc8.png&amp;size=m" alt=""></p>
<p><strong>结论：tomcat01和tomcat02是公用的一个路由器，dockero。 
  <br>所有的容器不指定网络的情况下，都是docker0路由的，docker会给我们的容器分配一个默认的可用IP</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F240d506d3ef237cf525a93aeb5976d1a.jpeg&amp;size=m" alt=""></p>
<p>Docker中的所有的网络接口都是虚拟的。虚拟的转发效率高！（内网传递文件！）
 <br>
 只要容器删除，对应网桥一对就没了！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff0d948db1ed0689ec345fbe6812e9443.jpeg&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3>7.2 link</h3>
<blockquote>
 <h4>高可用</h4>
 <p>我们编写了一个微服务，database url=ip:，项目不重启，数据库ip换掉了，我们希望可以处理这个问题，可以名字来进行访问容器？</p>
</blockquote>
<pre><code class="vbnet"># 通过服务名ping不通；如何解决？
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker exec -it tomcat02 ping tomcat01
ping: tomcat01: Name or service not known
# 通过--link可以解决网络连接问题。
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker run -d -P --name tomcat03 --link tomcat02 tomcat:7.0
2393eecb870e5755068ea8b7d8bdcdd0f1ff110534c3359384413677c651bec4
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.085 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.055 ms
# 反向可以ping通吗？（不可以）
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker exec -it tomcat02 ping tomcat03
ping: tomcat03: Name or service not known
</code></pre>
<p>探究：docker network inspect networkID (docker network ls可以查看networkID)</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F308adda47feddfb3fd884cec241c5911.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2804f8355ef27f4149c336e33dd085e4.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3ff9079d4fb6d6d048657a358ebc372e.jpeg&amp;size=m" alt=""></p>
<pre><code class="vbnet"># 查看
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker exec -it tomcat03 cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3      tomcat02 20398a94efa7
172.17.0.4      2393eecb870e
</code></pre>
<blockquote>
 <p>—link 就是我们在hosts配置中增加了一个"172.17.0.3 tomcat02 20398a94efa7”
  <br>
  我们现在玩Docker已经不建议使用—link了！
  <br>
  自定义网络！不适用docker0！
  <br>
  docker0问题：他不支持容器名连接访问！</p>
</blockquote>
<h3>7.3 自定义网络</h3>
<h4>7.3.1 查看所有的docker网络</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe3a8ee47811ae3d41f2e8e0d13593492.png&amp;size=m" alt=""></p>
<h4>7.3.2 网络模式</h4>
<ul>
 <li>bridge：桥接 docker（默认，自己创建也使用bridge桥接模式）</li>
 <li></li>
 <li>none：不配置网络</li>
 <li>host：和主机共享网络</li>
 <li>container：容器网络连通！（用的少！局限很大）</li>
</ul>
<h4>7.3.3 测试</h4>
<pre><code class="vbnet"># 我们直接启动的命令--net bridge（这个就是我们的docker0）；默认带上这个参数的，以下两种启动方式效果一致。
docker run -d -P --name tomcat01 tomcat
docker run -d -P --name tomcato1 --het bridge tomcat
# docker0特点：默认，域名不能访问，--1ink可以打通连接！
# 我们可以自定义一个网络！
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
7254ffccbdf5   bridge    bridge    local
45610891738f   host      host      local
266acd66473c   mynet     bridge    local
7795cbc2686c   none      null      local
</code></pre>
<p>我们自己的网络就创建好了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff9b4f782cb9c0d6a92d8e16a6ec178f1.jpeg&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>启动两个容器测试：</p>
<pre><code class="vbnet">[root@JWei_0124 ~]# docker run -d -P --name tomcat-net-01 --net mynet tomcat:7.0
[root@JWei_0124~]# docker run -d -P --name tomcat-net-02 --net mynet tomcat:7.0
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc522c9c62e9eb046d9206e25f45a7f65.jpeg&amp;size=m" alt=""></p>
<pre><code>```vbnet
# 不使用--link，ping名字也可以ping通。tomcat-net-01 ping tomcat-net-02可以ping通
[root@JWei_0124~]# docker exec -it tomcat-net-01 ping tomcat-net-02
PING tomcat-net-02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.067 ms
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.056 ms
# 不使用--link，ping名字也可以ping通。tomcat-net-02 ping tomcat-net-01可以ping通
[root@JWei_0124~]# docker exec -it tomcat-net-02 ping tomcat-net-01
PING tomcat-net-01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.056 ms
</code></pre>
<p>我们自定义的网络docker都已经帮我们维护好了对应的关系，<strong>推荐我们平时这样使用网络</strong>！</p>
<p>好处：
 <br>
 redis -不同的集群使用不同的网络，保证集群是安全和健康的
 <br>
 mysql -不同的集群使用不同的网络，保证集群是安全和健康的</p>
<p>
 <br>
</p>
<h3>7.4 网络连通</h3>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F59aac815b591c7063a4280727b2bead5.jpeg&amp;size=m" alt=""></p>
<pre><code class="vbnet"># tomcat01在docker0网络下，tomcat-net-01在mynet网络下；
# tomcat01 ping tomcat-net-01是ping不通的
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker exec -it tomcat01 ping tomcat-net-01
ping: tomcat-net-01: Name or service not known
</code></pre>
<p>连接网段</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff2e2a468730597a77466460c213111a3.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb3a31597601dd173f9ddc934b9bf2688.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<pre><code class="vbnet"># 测试：打通tomcat01连接mynet
docker network connect mynet tomcat01
# 连通之后就是将tomcat01放到了mynet网络下
# 一个容器两个ip地址！I
# 阿里云服务：公网ip和私网ip
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1bbbf4b3c42f36ece75290fbeb3a219f.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<pre><code class="vbnet"># 连接ok
[root@iZbp13qr3mm4ucsjumrlgqZ ~]# docker exec -it tomcat01 ping tomcat-net-01
PING tomcat-net-01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.065 ms
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.052 ms
</code></pre>
<p>假设要跨网络操作，就需要使用docker network connect连通！。。。。</p>
<p>原文链接: https://blog.csdn.net/qq_54729417/article/details/127913536</p>]]></description><guid isPermaLink="false">/archives/e85c23fa-f0a9-4226-82dd-aaef09404614</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Mon, 30 Sep 2024 06:47:11 GMT</pubDate></item><item><title><![CDATA[Java中的文件系统操作：如何高效地处理文件的读写]]></title><link>https://wiki.dgfygk.com/archives/c02b844a-6102-4e59-b3ea-56674ed64ba2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E7%9A%84%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E5%A4%84%E7%90%86%E6%96%87%E4%BB%B6%E7%9A%84%E8%AF%BB%E5%86%99&amp;url=/archives/c02b844a-6102-4e59-b3ea-56674ed64ba2" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Java中的文件系统操作：如何高效地处理文件的读写</strong></p>
<p>大家好，我是微赚淘客返利系统3.0的小编，是个冬天不穿秋裤，天冷也要风度的程序猿！在Java中，文件系统操作是与用户交互和数据存储的重要环节。有效地处理文件的读写，不仅可以提升程序的性能，还能改善用户体验。本文将详细介绍Java中的文件系统操作，包括基本的文件读写方法、使用NIO提升性能，以及处理大文件的技巧。</p>
<p><strong>一、基本的文件读写</strong></p>
<p>在Java中，最常用的文件读写方式是通过<code>java.io</code>包下的类。以下是一个简单的示例，展示如何使用<code>FileInputStream</code>和<code>FileOutputStream</code>进行文件的读写操作。</p>
<pre><code class="prism language-java">package cn.juwatech.fileio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileIOExample {

    public static void main(String[] args) {

        String sourceFile = "source.txt";
        String destFile = "destination.txt";

        // 写入文件
        try (FileOutputStream fos = new FileOutputStream(destFile)) {

            String data = "Hello, Java File I/O!";
            fos.write(data.getBytes());
            System.out.println("Data written to file successfully.");
        } catch (IOException e) {

            e.printStackTrace();
        }

        // 读取文件
        try (FileInputStream fis = new FileInputStream(sourceFile)) {

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {

                System.out.print(new String(buffer, 0, bytesRead));
            }
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>在上面的代码中，我们首先使用<code>FileOutputStream</code>将数据写入文件，然后使用<code>FileInputStream</code>读取文件内容。<code>try-with-resources</code>语句确保在操作结束后自动关闭流，避免资源泄露。</p>
<p><strong>二、使用BufferedReader和BufferedWriter</strong></p>
<p>在进行文本文件操作时，使用<code>BufferedReader</code>和<code>BufferedWriter</code>可以有效提高性能。它们通过缓冲区来减少直接的磁盘操作次数，从而提高读写效率。</p>
<p>以下是使用<code>BufferedReader</code>和<code>BufferedWriter</code>的示例：</p>
<pre><code class="prism language-java">package cn.juwatech.fileio;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedFileIOExample {

    public static void main(String[] args) {

        String filePath = "example.txt";

        // 写入文件
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {

            writer.write("Hello, Buffered Writer!");
            writer.newLine();
            writer.write("This is a line of text.");
            System.out.println("Data written using BufferedWriter.");
        } catch (IOException e) {

            e.printStackTrace();
        }

        // 读取文件
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {

            String line;
            while ((line = reader.readLine()) != null) {

                System.out.println("Read line: " + line);
            }
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>在此示例中，<code>BufferedWriter</code>用于高效写入文本，而<code>BufferedReader</code>用于逐行读取文件，提升了读取效率。</p>
<p><strong>三、Java NIO的使用</strong></p>
<p>Java NIO（New Input/Output）是Java 1.4引入的一个新特性，它提供了更高效的文件读写方法。NIO通过通道和缓冲区的方式处理文件，支持非阻塞I/O操作。</p>
<p>以下是使用NIO进行文件读写的示例：</p>
<pre><code class="prism language-java">package cn.juwatech.nio;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class NIOFileExample {

    public static void main(String[] args) {

        Path sourcePath = Paths.get("source.txt");
        Path destPath = Paths.get("destination_nio.txt");

        // 写入文件
        String data = "Hello, NIO File I/O!";
        try {

            Files.write(destPath, data.getBytes());
            System.out.println("Data written using NIO.");
        } catch (IOException e) {

            e.printStackTrace();
        }

        // 读取文件
        try {

            String content = new String(Files.readAllBytes(sourcePath));
            System.out.println("Read content: " + content);
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>在这个示例中，<code>Files.write()</code>和<code>Files.readAllBytes()</code>方法提供了简便的文件写入和读取方式。NIO的优点在于它能够处理更大文件时表现更好，因为它可以有效地进行批量读取和写入操作。</p>
<p><strong>四、处理大文件的技巧</strong></p>
<p>处理大文件时，内存消耗和性能是两个主要关注点。以下是一些处理大文件的技巧：</p>
<ol>
 <li>
  <p><strong>使用流式处理</strong> ：对于非常大的文件，避免将整个文件加载到内存中。可以使用<code>BufferedReader</code>逐行读取，或者使用NIO的<code>Files.lines()</code>方法。</p>
  <pre><code class="prism language-java">package cn.juwatech.nio;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class StreamFileExample {

   public static void main(String[] args) {

       Path path = Paths.get("largefile.txt");

       try (Stream&lt;String&gt; lines = Files.lines(path)) {

           lines.forEach(System.out::println);
       } catch (IOException e) {

           e.printStackTrace();
       }
   }
}
</code></pre>
 </li>
 <li>
  <p><strong>使用内存映射文件</strong>：Java NIO支持内存映射文件，允许将文件的部分内容映射到内存中进行处理，适合处理非常大的文件。</p>
  <pre><code class="prism language-java">package cn.juwatech.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.nio.file.Paths;

public class MemoryMappedFileExample {

   public static void main(String[] args) {

       try (FileChannel channel = FileChannel.open(Paths.get("largefile.txt"), StandardOpenOption.READ)) {

           long size = channel.size();
           MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);

           // 逐字节读取
           for (int i = 0; i &lt; size; i++) {

               System.out.print((char) buffer.get());
           }
       } catch (IOException e) {

           e.printStackTrace();
       }
   }
}
</code></pre>
 </li>
 <li>
  <p><strong>批量处理</strong>：如果需要将大量数据写入文件，考虑使用批量写入技术，避免频繁的磁盘I/O操作。</p>
  <pre><code class="prism language-java">package cn.juwatech.fileio;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BatchWriteExample {

   public static void main(String[] args) {

       String filePath = "batch_output.txt";

       try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {

           for (int i = 0; i &lt; 100000; i++) {

               writer.write("Line " + i);
               writer.newLine();
               if (i % 1000 == 0) {

                   writer.flush(); // 每1000行强制写入
               }
           }
           System.out.println("Batch write completed.");
       } catch (IOException e) {

           e.printStackTrace();
       }
   }
}
</code></pre>
 </li>
</ol>
<p>在上述示例中，我们通过<code>BufferedWriter</code>的<code>flush()</code>方法实现了批量写入，从而减少频繁的磁盘写入操作，提高了写入性能。</p>
<p><strong>五、总结</strong></p>
<p>在Java中，高效的文件读写操作不仅依赖于合适的API选择，还需要合理的设计和优化策略。通过使用<code>BufferedReader</code>、<code>BufferedWriter</code>以及NIO，能够有效提升文件操作的性能，尤其是在处理大文件时，避免内存溢出和性能瓶颈。合理运用这些技巧，可以为Java后端开发提供更稳定、更高效的文件系统操作能力。</p>
<p>本文著作权归聚娃科技微赚淘客系统开发者团队，转载请注明出处！</p>
<p>原文链接: https://blog.csdn.net/u010405836/article/details/142425357</p>]]></description><guid isPermaLink="false">/archives/c02b844a-6102-4e59-b3ea-56674ed64ba2</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:46:53 GMT</pubDate></item><item><title><![CDATA[idea快速实现将SpringBoot项目打包Docker镜像并部署]]></title><link>https://wiki.dgfygk.com/archives/f0681e59-eda9-438c-bf28-5819730d392f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=idea%E5%BF%AB%E9%80%9F%E5%AE%9E%E7%8E%B0%E5%B0%86SpringBoot%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85Docker%E9%95%9C%E5%83%8F%E5%B9%B6%E9%83%A8%E7%BD%B2&amp;url=/archives/f0681e59-eda9-438c-bf28-5819730d392f" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#font_color005BB7_1dockerfont_1">1.修改docker的配置文件</a></li>
 <li><a href="https://wiki.dgfygk.com#font_color005BB7_2font_31">2.配置端口开放</a></li>
 <li><a href="https://wiki.dgfygk.com#font_color005BB7_3IDEADockerfont_51">3.IDEA安装Docker插件</a></li>
 <li><a href="https://wiki.dgfygk.com#font_color005BB7_4IDEAdockerfont_58">4.IDEA配置docker</a></li>
 <li><a href="https://wiki.dgfygk.com#font_color005BB7_5SpringBootDockerfont_66">5.SpringBoot整合Docker配置</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#font_color2196F351_pomfont_67">5.1 安装pom依赖</a></li>
 <li><a href="https://wiki.dgfygk.com#font_color2196F352_buildfont_200">5.2 build镜像</a></li>
 <li><a href="https://wiki.dgfygk.com#font_color2196F353_font_225">5.3 启动镜像</a></li>
</ul>
<p>
 <br>
</p>
<h2>1.修改docker的配置文件</h2>
<p>修改文件信息路径如下：
 <br>
 /etc/docker/daemon.json
 <br>
 在配置文件中添加以下内容：</p>
<pre><code> "hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F334d3999e9c6542396f433dc8de63436.png%23pic_center&amp;size=m" alt=""></p>
<p>注意：如果没有这个daemon.json，就自己在/etc/docker/目录下创建一个</p>
<pre><code>touch daemon.json
</code></pre>
<p>tcp是开启远程访问，unix是本机访问，不开启本机访问，在服务器上使用时会有以下错误：</p>
<blockquote>
 <p>Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?</p>
</blockquote>
<p>修改完成之后依次执行以下命令：</p>
<pre><code>#重新加载配置文件                  
systemctl daemon-reload 
# 重启服务 
systemctl restart docker.service 
# 查看端口是否开启 默认端口2375 
netstat -anp|grep 2375
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5f16627f20466c0cb6def069df728d23.png%23pic_center&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdb7fbc6018d7eaaf7893bc76ea81ede5.png%23pic_center&amp;size=m" alt=""></p>
<h2>2.配置端口开放</h2>
<p>依次执行以下命令</p>
<pre><code>添加指定需要开放的端口：
firewall-cmd --zone=public --add-port=2375/tcp --permanent
重载入添加的端口：
firewall-cmd --reload
查询指定端口是否开启成功：
firewall-cmd --query-port=2375/tcp
</code></pre>
<p>注意：以上配置全是在防火墙开启的情况下进行，如果未开启系统防火墙，则无需以上配置，比如我的服务器就 没有配置防火墙，运行时则显示以下信息：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7d7df97dab74db0a5c30fbe6cea0479d.png%23pic_center&amp;size=m" alt=""></p>
<p>最后我们看看我们的配置是否生效</p>
<pre><code>curl http://127.0.0.1:2375/info
</code></pre>
<p>如果出现 以下信息则说明配置成功
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F243e735dbae7253b95db9955a6b278f7.png%23pic_center&amp;size=m" alt=""></p>
<h2>3.IDEA安装Docker插件</h2>
<p>如果你的idea版本较高，则内置了docker插件
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa75f36623adad444aebc9d956d119fac.png%23pic_center&amp;size=m" alt=""></p>
<p>如果没有也不要担心，我们自行安装即可
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2c43a9978c4d6f50ea17ff192960f0a4.png%23pic_center&amp;size=m" alt=""></p>
<h2>4.IDEA配置docker</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F19a07276e6a2aa52cbde33995299eb23.png%23pic_center&amp;size=m" alt=""></p>
<p>注意：我上面的192.168.1.2是我自己的服务器Ip，只需修改成你的docker所在服务器ip即可
 <br>
 点击Apply之后会在你的service中弹出
 <br>
 点击连接，我们就会发现我们docker配置的容器信息
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa650fd389506842132e77f5589bdf77a.png%23pic_center&amp;size=m" alt=""></p>
<h2>5.SpringBoot整合Docker配置</h2>
<h3>5.1 安装pom依赖</h3>
<p>安装插件：Docker-maven-plugin</p>
<pre><code class="prism language-xml"> &lt;!--docker-maven-plugin插件打包--&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;com.spotify&lt;/groupId&gt;
                &lt;artifactId&gt;docker-maven-plugin&lt;/artifactId&gt;
                &lt;version&gt;1.0.0&lt;/version&gt;
                &lt;configuration&gt;
                    &lt;!--镜像名称--&gt;
                    &lt;imageName&gt;${project.artifactId}&lt;/imageName&gt;
                    &lt;!--指定标签--&gt;
                    &lt;imageTags&gt;
                        &lt;imageTag&gt;latest&lt;/imageTag&gt;
                    &lt;/imageTags&gt;
                    &lt;!--基础镜像jdk1.8--&gt;
                    &lt;baseImage&gt;java&lt;/baseImage&gt;
                    &lt;!--制作者提供本人信息--&gt;
                    &lt;maintainer&gt;ninesun@qq.com&lt;/maintainer&gt;
                    &lt;!--切换到Root目录--&gt;
                    &lt;workdir&gt;/ROOT&lt;/workdir&gt;
                    &lt;cmd&gt;["java", "-version"]&lt;/cmd&gt;
                    &lt;entryPoint&gt;["java", "-jar", "${project.build.finalName}.jar"]&lt;/entryPoint&gt;

                    &lt;!--指定DockerFile路径--&gt;
                    &lt;!--                    &lt;dockerDirectory&gt;${project.basedir}/src/main/docker&lt;/dockerDirectory&gt;--&gt;

                    &lt;!--指定远程docker api地址--&gt;
                    &lt;dockerHost&gt;http://192.168.1.2:2375&lt;/dockerHost&gt;

                    &lt;!-- 这里是复制 jar 包到 docker 容器指定目录配置 --&gt;
                    &lt;resources&gt;
                        &lt;resource&gt;
                            &lt;targetPath&gt;/ROOT&lt;/targetPath&gt;
                            &lt;!--用于指定需要复制的根目录--&gt;
                            &lt;directory&gt;${project.build.directory}&lt;/directory&gt;
                            &lt;!--用于指定需要复制的jar文件--&gt;
                            &lt;include&gt;${project.build.finalName}.jar&lt;/include&gt;
                        &lt;/resource&gt;
                    &lt;/resources&gt;
                &lt;/configuration&gt;
            &lt;/plugin&gt;
</code></pre>
<p>我所有的maven配置如下：</p>
<pre><code class="prism language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;parent&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;2.6.6&lt;/version&gt;
        &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
    &lt;/parent&gt;
    &lt;groupId&gt;com.example&lt;/groupId&gt;
    &lt;artifactId&gt;docker&lt;/artifactId&gt;
    &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
    &lt;name&gt;docker&lt;/name&gt;
    &lt;description&gt;docker&lt;/description&gt;
    &lt;properties&gt;
        &lt;java.version&gt;11&lt;/java.version&gt;
        &lt;maven.test.skip&gt;true&lt;/maven.test.skip&gt;
        &lt;maven.javadoc.skip&gt;true&lt;/maven.javadoc.skip&gt;
    &lt;/properties&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;
            &lt;!--docker-maven-plugin插件打包--&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;com.spotify&lt;/groupId&gt;
                &lt;artifactId&gt;docker-maven-plugin&lt;/artifactId&gt;
                &lt;version&gt;1.0.0&lt;/version&gt;
                &lt;configuration&gt;
                    &lt;!--镜像名称--&gt;
                    &lt;imageName&gt;${project.artifactId}&lt;/imageName&gt;
                    &lt;!--指定标签--&gt;
                    &lt;imageTags&gt;
                        &lt;imageTag&gt;latest&lt;/imageTag&gt;
                    &lt;/imageTags&gt;
                    &lt;!--基础镜像jdk1.8--&gt;
                    &lt;baseImage&gt;java&lt;/baseImage&gt;
                    &lt;!--制作者提供本人信息--&gt;
                    &lt;maintainer&gt;ninesun@qq.com&lt;/maintainer&gt;
                    &lt;!--切换到Root目录--&gt;
                    &lt;workdir&gt;/ROOT&lt;/workdir&gt;
                    &lt;cmd&gt;["java", "-version"]&lt;/cmd&gt;
                    &lt;entryPoint&gt;["java", "-jar", "${project.build.finalName}.jar"]&lt;/entryPoint&gt;

                    &lt;!--指定DockerFile路径--&gt;
                    &lt;!--                    &lt;dockerDirectory&gt;${project.basedir}/src/main/docker&lt;/dockerDirectory&gt;--&gt;

                    &lt;!--指定远程docker api地址--&gt;
                    &lt;dockerHost&gt;http://192.168.1.2:2375&lt;/dockerHost&gt;

                    &lt;!-- 这里是复制 jar 包到 docker 容器指定目录配置 --&gt;
                    &lt;resources&gt;
                        &lt;resource&gt;
                            &lt;targetPath&gt;/ROOT&lt;/targetPath&gt;
                            &lt;!--用于指定需要复制的根目录--&gt;
                            &lt;directory&gt;${project.build.directory}&lt;/directory&gt;
                            &lt;!--用于指定需要复制的jar文件--&gt;
                            &lt;include&gt;${project.build.finalName}.jar&lt;/include&gt;
                        &lt;/resource&gt;
                    &lt;/resources&gt;
                &lt;/configuration&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;

&lt;/project&gt;
</code></pre>
<p>注意：在我们拉取插件成功之后，我们会发现目录结构变成如下所示 ：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9d403ed234a1d36101c7369758254720.png%23pic_center&amp;size=m" alt=""></p>
<p>我们需要删除一个启动类，否则会导致打包失败，我将DockerApplication直接删除了</p>
<h3>5.2 build镜像</h3>
<p>在build镜像之前，我们需要先进行项目的打包操作
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5160f56b0807c48d9492b95ae5ef84c9.png%23pic_center&amp;size=m" alt=""></p>
<p>通过cmd进入到我们的项目目录
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F32a9995d92031f41fc6e5af4170b6ee6.png%23pic_center&amp;size=m" alt=""></p>
<p>执行</p>
<pre><code>mvn docker:build
</code></pre>
<p>出现 以下信息表示打包成功
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6b7992bde7d99a9603e2d8bd1495f496.png%23pic_center&amp;size=m" alt=""></p>
<p>接下来回到我们的idea，可以看到
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F302fdb0b6a624f46c3ec86c9477eb35a.png%23pic_center&amp;size=m" alt=""></p>
<p>这个就是我们打包好的镜像
 <br>
 在服务器上执行</p>
<pre><code>docker images
</code></pre>
<p>也可以看到该镜像的信息
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F50e6d4a7408597bbe844859c691f091d.png%23pic_center&amp;size=m" alt=""></p>
<h3>5.3 启动镜像</h3>
<p>在我们服务器内执行</p>
<pre><code>docker run -d --name idea-docker-test -p 8089:8080 docker
</code></pre>
<p>注：我之所以暴露出8089端口，只要是因为8080冲突了，自己看自己的情况改就好
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fefefe920ffdb93b0d947fee6d2ecfb20.png%23pic_center&amp;size=m" alt=""></p>
<p>接下来我们访问一下我们写的测试接口：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff1ffc257900a3d05bb80d58f6e9f73f1.png%23pic_center&amp;size=m" alt=""></p>
<p>可以看到已经部署成功，同时也可访问成功了
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe65717ef6583763693771bc7ede4316c.png%23pic_center&amp;size=m" alt=""></p>
<p>我们可以看到在 Idea里也可看到我们刚刚启动成功的镜像以及输出的日志了</p>
<p>至此idea部署SpringBoot至docker就结束啦</p>
<p>原文链接: https://blog.csdn.net/zhiyikeji/article/details/124127443</p>]]></description><guid isPermaLink="false">/archives/f0681e59-eda9-438c-bf28-5819730d392f</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:46:42 GMT</pubDate></item><item><title><![CDATA[什么是架构]]></title><link>https://wiki.dgfygk.com/archives/f3afd442-5723-4c5f-a61e-84108c2308d0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AF%E6%9E%B6%E6%9E%84&amp;url=/archives/f3afd442-5723-4c5f-a61e-84108c2308d0" width="1" height="1" alt="" style="opacity:0;">
<h3><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F00028e9aa745a8663616e25dbf05066f.png%23clientId%3Dubb75cc02-ad03-4%5C%26crop%3D0%5C%26%23pic_centercrop%3D0%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D559%5C%26id%3Duddad6e79%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D838%5C%26originWidth%3D1011%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D94892%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Du375c8557-af1a-4fae-bc13-710cee4ed34%5C%26title%3D%25E6%259E%25B6%25E6%259E%2584%25E6%2580%259D%25E7%25BB%25B4%25E8%25BD%25AE%25E5%25BB%2593%5C%26width%3D674&amp;size=m" alt=""></h3>
<h3>一、前言</h3>
<blockquote>
 <p>架构，又名软件架构，是有关软件整体结构与组件的抽象描述，用于指导大型软件系统各个方面的设计。架构描述语言（ADL）用于描述软件的体系架构。</p>
</blockquote>
<p><strong>软件架构（software architecture）</strong>是一系列相关的抽象模式，用于指导大型软件系统各个方面的设计。 软件架构是一个系统的<strong>草图</strong> 。软件架构描述的对象是<strong>直接构成系统</strong> 的抽象组件。各个组件之间的连接则<strong>明确</strong> 和<strong>相对细致</strong> 地描述组件之间的通讯。在实现阶段，这些抽象组件被细化为实际的组件，比如具体<strong>某个类</strong> 或者<strong>对象</strong> 。在面向对象领域中，组件之间的连接通常用<strong>接口</strong> (计算机科学)来实现。 软件体系结构是构建计算机软件实践的基础。与建筑师设定建筑项目的<strong>设计原则</strong> 和<strong>目标</strong> ，作为绘图员画图的基础一样，一个<strong>软件架构师</strong> 或者<strong>系统架构师</strong> 陈述软件构架以作为满足不同客户需求的实际系统设计方案的基础。</p>
<h3>二、架构设计目标</h3>
<p>在进行软件系统构架时，软件本身有其要达到的目标，软件架构设计要达到如下的 8 个目标：</p>
<ol>
 <li><strong>可靠性（Reliable）</strong> 。软件系统对于用户的<strong>商业经营</strong> 和<strong>管理</strong>来说极为重要，因此软件系统必须非常可靠。</li>
 <li><strong>安全性（Secure）</strong> 。软件系统所承担的<strong>交易</strong>的商业价值极高，系统的安全性非常重要。</li>
 <li><strong>可扩展性（Scalable）</strong> 。软件必须能够在用户的<strong>使用率</strong> 、用户的<strong>数目增加很快</strong> 的情况下，保持合理的<strong>性能</strong>。只有这样，才能适应用户的市场扩展得可能性。</li>
 <li><strong>可定制化（Customizable）</strong> 。同样的一套软件，可以根据<strong>客户群的不同</strong> 和<strong>市场需求的变化</strong>进行调整。</li>
 <li><strong>可伸缩 （Extensible）</strong> 。在新技术出现的时候，一个软件系统应当允许<strong>导入</strong> 新技术，从而对现有系统进行<strong>功能</strong> 和<strong>性能</strong>的扩展。</li>
 <li><strong>可维护性（Maintainable）</strong> 。软件系统的维护包括两方面，一是<strong>排除</strong> 现有的错误，二是将<strong>新的软件需求</strong>反映到现有系统中去。一个易于维护的系统可以有效地降低技术支持的花费。</li>
 <li><strong>客户体验（Customer Experience）</strong> 。软件系统必须<strong>易于</strong>使用。</li>
 <li><strong>市场时机（Time to Market）</strong> 。软件<strong>用户</strong> 要面临同业竞争，软件<strong>提供商</strong> 也要面临同业竞争。以最快的速度争夺市场先机非常重要。</li>
</ol>
<h3>三、架构分类</h3>
<p>通常按不同的关注角度上分，可以将架构类型分为三种：</p>
<ol>
 <li><strong>逻辑架构</strong>：软件中元件之间的关系，比如一个系统的架构在抽象层面，分解为表示层、业务逻辑处理层、数据元件层，如下面图所示，将各元件职责分离且划分简单抽象的单元构建的基本架构。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F180d553ff12d023e97cc59b66e7afac1.png%23crop%3D0%5C%26crop%3D0%5C%26%23pic_centercrop%3D1%5C%26crop%3D1%5C%26from%3Durl%5C%26id%3Dvu2Gw%5C%26margin%3D%255Bobject%2520Object%255D%5C%26originHeight%3D323%5C%26originWidth%3D446%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dfalse%5C%26status%3Ddone%5C%26style%3Dnone%5C%26title%3D&amp;size=m" alt=""></p>
<ol>
 <li>
  <p><strong>物理架构</strong> ：是对软件元件怎么部署到硬件上。比如下图中所描述的物理架构图是一个终端到服务器分布式部署的物理架构，图中所有的元件都是物理设备，该架构包含WEB服务器、代理服务器、应用服务器、存储服务器。
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F81154a9a6368a3a9aa45bfe93fd544ee.png%23pic_center&amp;size=m" alt=""></p>
 </li>
 <li>
  <p><strong>系统架构</strong> ：是软件系统的功能性和非功能性特征的体现，而功能性特征的原则是怎么把整个系统做拆分，将各个元件各司其职，完成某一功能为事件。非功能性特征包括可扩展性、可靠性、可用性、健壮性、灵活性、性能等。如下图中描述的是一个系统的构建和设计需要满足的要求。系统架构的设计要求架构师具备软件和硬件的功能和性能的过硬知识，这一工作无疑是架构设计工作中最为困难的工作。
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3b74d4cee03addef28eb0662f75cd10f.png%23pic_center&amp;size=m" alt=""></p>
 </li>
</ol>
<h3>四、架构边界划分</h3>
<p>架构通常是<strong>抽象</strong> 的，但其边界是可被约束的，在构建实际的系统架构中，我们可以将架构边界划分为<strong>软件元件</strong> 和<strong>设计</strong> 两个关键要素决定。从每一个角度上看，都可以看到架构中的这两要素，也就是元件的划分和设计。
 <br>
 首先，一个软件系统中的元件首先是<strong>逻辑元件</strong> 。这些逻辑元件如何放到<strong>硬件</strong> 上，以及这些元件如何为整个系统的可扩展性、可靠性、强壮性、灵活性、性能等做出贡献，是非常重要的信息。其次，进行软件设计需要做出的决定中，必然会包括<strong>逻辑结构</strong> 、<strong>物理结构</strong> ，以及它们如何<strong>影响</strong> 到系统的所有<strong>非功能性</strong> 特征。这些决定中会有很多是一旦做出，就很难更改的。为了讨论和分析软件架构，必须首先<strong>定义架构</strong> 表示方式，即描述架构重要方面的方式。
 <br>
 我们决定以多种架构视图来表示软件架构，<strong>架构视图模型</strong> 都有哪些将在下面进行说明。每种架构视图针对于开发流程中的涉众（例如最终用户、设计人员、管理人员、系统工程师、维护人员等）所关注的特定方面。架构视图显示了软件架构如何<strong>分解</strong> 为构件，以及构件如何由<strong>连接器</strong> 连接来产生有用的形式 ，由此记录主要的结构设计决策。这些设计决策必须基于<strong>需求</strong> 以及<strong>功能</strong> 、补充和其他方面的<strong>约束</strong> 。而这些决策又会在<strong>较低层次</strong> 上为需求和将来的设计决策施加进一步的约束。</p>
<h3>五、架构视图模型有哪些</h3>
<p>架构由许多不同的架构视图来表示，这些视图本质上是以图形方式来重点说明"在架构方面具有重要意义"的模型元素。该视图集称为”<strong>4+1 视图模型</strong>“。它包括：</p>
<ol>
 <li><strong>用例视图：</strong>包括<strong>用例</strong> 和<strong>场景</strong> ，这些用例和场景包括在架构方面具有重要意义的行为、类或技术风险。它是<strong>用例模型</strong>的子集。</li>
 <li><strong>逻辑视图：</strong>包括最重要的设计类、从这些设计<strong>类</strong> 到<strong>包</strong> 和<strong>子系统</strong> 的组织形式，以及从这些包和子系统到层的组织形式。它还包括一些<strong>用例实现</strong> 。它是<strong>设计模型</strong>的子集。</li>
 <li><strong>实施视图：</strong>包括实施模型及其从<strong>模块</strong> 到<strong>包</strong> 和<strong>层</strong> 的组织形式的概览。 同时还描述了将逻辑视图中的包和类向实施视图中的包和模块分配的情况。它是<strong>实施模型</strong>的子集。</li>
 <li><strong>进程视图：</strong>包括所涉及<strong>任务</strong> （进程和线程）的描述，它们的交互和配置，以及将设计<strong>对象</strong> 和<strong>类</strong> 向任务的分配情况。只有在系统具有<strong>很高</strong> 程度的并行时，才需要该视图。在 Rational Unified Process 中，它是<strong>设计模型</strong>的子集。</li>
 <li><strong>配置视图：</strong>包括对最典型的平台配置的各种<strong>物理节点</strong> 的描述以及将<strong>任务</strong> （来自进程视图）向物理节点分配的情况。只有在<strong>分布式系统</strong> 中才需要该视图。它是<strong>部署模型</strong>的一个子集。</li>
</ol>
<p>针对上面提到的几种视图模型中，我们可以构建其它视图来表达需要特别关注的不同方面，如：用户界面视图、安全视图、数据视图等。对于<strong>简单系统</strong> 来说，可以省略 <strong>4+1 视图模型</strong> 中的一些视图。
 <br>
 虽然以上视图可以表示系统的整体设计，但架构只同以下几个具体方面相关： <strong>模型的结构</strong> ，即<strong>组织模式</strong> ，例如分层。
 <br>
 架构视图在本质上是整体设计的<strong>抽象</strong> 或<strong>简化</strong> ，它们通过<strong>舍弃</strong> 具体<strong>细节</strong>来突出重要的特征。在考虑以下方面时，这些特征非常重要：</p>
<ul>
 <li>系统演进，即进入下一个开发周期。</li>
 <li>在产品线环境下复用架构或架构的一部分。</li>
 <li>评估补充质量，例如性能、可用性、可移植性和安全性。</li>
 <li>向团队或开发商分配开发工作。</li>
 <li>决定是否包括商业构件。</li>
 <li>插入范围更广的系统。</li>
</ul>
<h3>六、架构设计原则</h3>
<p>通常我们在对系统或程序做设计过程中并不是一味追求完美和为了架构而去做架构。为了解决软件架构在设计时，可根据传统软件规则约束设计出符合实际业务场景的适用性架构，并且软件架构的设计不是一成不变的，将会随着满足客户需求为意愿或其它内外部因素的影响，我们需要动态和适当的调整架构。
 <br>
 <strong>基本的软件架构的设计一般需要符合以下 6 个架构设计原则（简称：SOLID）：</strong></p>
<ol>
 <li><strong>单一职责（SRP）</strong>：是指在一个类或组件中永远只负责做独有的业务或一件事，保持职责足够单一，各司其职，不受外部因素所干扰，职责清晰，便于维护。</li>
 <li><strong>开闭原则（OCP）</strong>：是指类或组件的扩展开放，修改关闭原则，实现业务之间耦合性降低和提高了可扩展性。</li>
 <li><strong>里氏替换原则（LSP）</strong>：是指子类和超类或基类之间的关系，子类的实例可以替换为超类或基类任何实例，并且在此过程中程序不会受到影响，而反过来则不行，会使程序异常。</li>
 <li><strong>迪米特原则（LKP）</strong>：是指类或组件中所依赖的对象（实体）降到最低，是该类的依赖最少了解，通常也被称为最少知识原则。</li>
 <li><strong>接口隔离原则（ISP）</strong>：是指不需要把不必要的接口强加给用户使用，尽量保证接口和模块之间的耦合降到最低，也称为接口最少提供原则。</li>
 <li><strong>依赖倒置原则（DIP）</strong>：是指类与组件之间或层与层之间的关系，顶层不应该依赖于底层，而更应该依赖于抽象层，抽象层不应该依赖于具体的实现，更应该依赖于底层，说明的是内部结构的变动并不会影响到顶层的变化，尽量避免跨层调用和强依赖关系，提高可扩展性。</li>
</ol>
<p><strong>软件架构设计时您还需要遵守的编程原则（规范）：</strong></p>
<ol>
 <li><strong>不要重复你自己（Don't repeat yourself - DRY）</strong></li>
</ol>
<p>不要让重复的代码到处都是，要让它们能够复用，所以要尽可能地组件化封装。</p>
<ol>
 <li><strong>保持简单与傻瓜（Keep it simple and stupid - KISS）</strong></li>
</ol>
<p>不要让系统变得复杂，界面简洁，功能实用，操作方便，要让它足够的简单，足够的傻瓜。</p>
<ol>
 <li><strong>高内聚与低耦合（High Cohesion and Low Coupling - HCLC）</strong></li>
</ol>
<p>模块内部需要做到内聚度高，模块之间需要做到耦合度低。</p>
<ol>
 <li><strong>约定优于配置（Convention over Configuration - COC）</strong></li>
</ol>
<p>尽量让约定来减少配置，这样才能提高开发效率，尽量做到"零配置”。很多组件或框架都是这样做的。</p>
<ol>
 <li><strong>命令查询分离（Command Query Separation - CQS）</strong></li>
</ol>
<p>在定义接口时，要做到哪些是命令，哪些是查询，要将它们分离，而不要揉到一起。</p>
<ol>
 <li><strong>关注点分离（Separation of Concerns - SOC）</strong></li>
</ol>
<p>将一个复杂的问题分离为多个简单的问题，然后逐个解决这些简单的问题，那么这个复杂的问题就解决了。难就难在如何进行分离。</p>
<ol>
 <li><strong>契约式设计（Design by Contract - DBC）</strong></li>
</ol>
<p>模块或系统之间的交互，都是基于契约（接口或抽象）的，而不要依赖于具体实现。该原则建议我们要面向契约编程。</p>
<ol>
 <li><strong>你不需要它（You aren't gonna need it - YAGNI）</strong>
  <br>
  不要一开始就把系统设计得非常复杂，不要陷入"过度设计"的深渊。应该让系统足够的简单，而却又不失扩展性，这是其中的难点。</li>
</ol>
<h3>七、架构的演变过程</h3>
<p>软件架构的设计，从最初系统的架构到不断的调整和探索的过程中，演变出一系列的架构模型。在此过程中经历了需求和业务的不断挑战和洗礼所产生的衍生架构，为了满足用户需求和实际业务场景去对架构上做出重新调整，实现新的系统架构设计上的本质提升。
 <br>
 <strong>由于在架构上从不同角度上看会产生不同的粗细模型，但在本质上粗略的分为如下几种架构：</strong></p>
<ul>
 <li><strong>单体架构</strong>：单体架构是传统的软件架构模型，也是最简单、最容易被人所理解的架构，比如：分层。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F78b9eae752de6a2f143575edfdee969c.png%23clientId%3Du18ca0a5d-4290-4%5C%26crop%3D0%5C%26%23pic_centercrop%3D0%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D215%5C%26id%3Du4c6d3d4b%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D322%5C%26originWidth%3D436%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D5955%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Du01e433d5-7eeb-4274-aca2-1d3e9bff4f8%5C%26title%3D%25E7%25AE%2580%25E5%258D%2595%25E7%259A%2584%25E4%25B8%2589%25E5%25B1%2582%25E6%259E%25B6%25E6%259E%2584%5C%26width%3D290.6666666666667&amp;size=m" alt=""></p>
<ul>
 <li><strong>分布式架构</strong>：分布式架构是系统和组件元件的聚合，将整个系统分布式部署在服务器上，通过一种技术手段把这些系统连接上，使各系统或模块能够相互协作处理程序操作，比如：分布式集群架构、分布式缓存架构等。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F798193ebe64dfbb46e1a4b49d8aaceb6.png%23clientId%3Du18ca0a5d-4290-4%5C%26crop%3D0%5C%26%23pic_centercrop%3D0%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D348%5C%26id%3Du2fea34d3%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D522%5C%26originWidth%3D640%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D30595%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Du456ae578-dec3-4aaf-989f-686ee2c9d97%5C%26title%3D%25E5%2588%2586%25E5%25B8%2583%25E5%25BC%258F%25E7%25BC%2593%25E5%25AD%2598%25E6%259E%25B6%25E6%259E%2584%5C%26width%3D426.6666666666667&amp;size=m" alt=""></p>
<ul>
 <li><strong>微服务架构</strong>：微服务架构的全称是Microservice Architecture，微服务架构是现代化流行的服务化治理架构，是通过业务/组件或功能维度上去划分的架构模型，能够将大的系统分解为各个小的服务，再将这些拆分的服务联系起来，服务之间的相互协作，互不影响，提高了系统的可扩展性、可维护性和容错性。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F7738124398d7306e8c0d7ce3eace110a.png%23clientId%3Du18ca0a5d-4290-4%5C%26crop%3D0%5C%26crop%3D0%5C%26%23pic_centercrop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D568%5C%26id%3Dudb74e1f3%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D852%5C%26originWidth%3D712%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D53815%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Due88b93d9-7238-4368-8962-91adf423e94%5C%26title%3D%25E5%25BE%25AE%25E6%259C%258D%25E5%258A%25A1%25E6%259E%25B6%25E6%259E%2584%5C%26width%3D474.6666666666667&amp;size=m" alt=""></p>
<ul>
 <li><strong>Serverless架构</strong>：Serverless的全称是Serverless computing无服务器运算，又被称为函数即服务（Function-as-a-Service，缩写为 FaaS），是云计算的一种模型。以平台即服务（PaaS）为基础，无服务器运算提供一个微型的架构，终端客户不需要部署、配置或管理服务器服务，代码运行所需要的服务器服务皆由云端平台来提供。 国内外比较出名的产品有Tencent Serverless、Aliyun Serverless、AWS Lambda、Microsoft Azure Functions 等。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fffbfb2efabee2f5d8fc57ebcb9203f24.png%23clientId%3Du18ca0a5d-4290-4%5C%26crop%3D0%5C%26crop%3D0%5C%26%23pic_centercrop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D381%5C%26id%3Du880cccfc%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D572%5C%26originWidth%3D866%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D43780%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Du06eeb9ea-34ca-4cdf-9200-8c2ebb2907b%5C%26title%3DServerless%25E6%259E%25B6%25E6%259E%2584%5C%26width%3D577.3333333333334&amp;size=m" alt=""></p>
<ul>
 <li>
  <p><strong>Service Mesh架构</strong> ：Service Mesh是一个基础设施层，用于处理服务间通信。云原生应用有着复杂的服务拓扑，服务网格保证请求在这些拓扑中可靠地穿梭。在实际应用当中，服务网格通常是由一系列轻量级的网络代理组成的，它们与应用程序部署在一起，但对应用程序透明。
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe89872d5d66ec97b9c33f04c67dc9428.png%23pic_center&amp;size=m" alt=""></p>
 </li>
 <li>
  <p><strong>Istio架构</strong>：Istio扩展了Kubernetes，使用强大的Envoy服务代理建立了一个可编程的、应用程序感知的网络。与Kubernetes和传统工作负载一起工作，Istio为复杂的部署带来了标准的、通用的流量管理、遥测和安全性。</p>
 </li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F5a3ea3b06ee254e0bd1d1f4bcb1b2901.png%23clientId%3Du18ca0a5d-4290-4%5C%26crop%3D0%5C%26crop%3D0%5C%26%23pic_centercrop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D497%5C%26id%3Due5df5d75%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D745%5C%26originWidth%3D1221%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D176136%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Due21c2440-b290-4d89-b292-0757cad9f32%5C%26title%3DIstio%25E6%259E%25B6%25E6%259E%2584%5C%26width%3D814&amp;size=m" alt=""></p>
<h3>八、架构设计思想</h3>
<p><strong>1、对系统架构理解</strong>
 <br>
 由于架构是抽象的，关于系统架构目前没有一个明确的定义。有从规划、实现与步骤角度去了解，有从架构分类方面去定义。这次我提的理念是：系统架构的目标是解决利益相关者的关注点。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9d60490eec445a9b0cfb671a7f5b62a8.png%23clientId%3Du18ca0a5d-4290-4%5C%26crop%3D0%5C%26crop%3D0%23pic_center%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D509%5C%26id%3Du648c1e4c%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D763%5C%26originWidth%3D969%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dfalse%5C%26size%3D420128%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Du209f17b2-6df6-48b6-b970-369f75019b5%5C%26title%3D%5C%26width%3D646&amp;size=m" alt=""></p>
<ul>
 <li>每个系统都有一个架构，架构由架构元素以及相互之间的关系构成，系统是为了满足利益相关者的需求构建的；</li>
 <li>利益相关者都有自己的关注点；</li>
 <li>架构由架构文档描述，并且描述了一系列的架构视角，而这些视角其实就是去解决利益相关者的关注点。</li>
</ul>
<p><strong>2、对系统关注点理解</strong>
 <br>
 系统关注点通常是从系统架构中在不同视野上分为功能性关注点和非功能性关注点，而往往我们在对系统做架构设计时忽略了最需要关注的非功能性关注点，下面就着重分析功能性和非功能性关注点所具备的基本原则和规范。
 <br>
 <strong>处理功能性关注点，需要注意以下 3 个原则，并且通过模型方式来进行实现。</strong>
 <br>
 <strong>1- 架构设计基本原则：S - O - L - I - D</strong>
 <br>
 SRP：单一职责原则
 <br>
 OCP：开闭原则
 <br>
 LSP：里氏替换原则
 <br>
 LKP：迪米特原则
 <br>
 ISP：接口隔离原则
 <br>
 DIP：依赖倒置原则
 <br>
 <strong>2- KISS</strong>
 <br>
 KEEP IT SIMPLE AND STUPID（保持简单与傻瓜）
 <br>
 <strong>3- 高内聚、低耦合</strong>
 <br>
 <strong>4- 模型的 3 个层次</strong>
 <br>
 <strong>第一层是代码层面</strong> ：就是惯用的代码，策略是指一套API、方法等。
 <br>
 <strong>第二层是设计模式</strong> ：主要GOF提出来的<strong>23种</strong> 常用的设计模式。立足于高级抽象层面进行探讨，而非设计标注或 者编程语言，能够大大降低系统复杂度。
 <br>
 <strong>第三层是架构模式</strong> ：它是一个通用的，可复用的解决方案。比如：分层模式、微服务模式等。
 <br>
 <strong>非功能性关注点，一般应用于对系统的扩展性方面做设计的。</strong>
 <br>
 <strong>1-高可用性</strong>
 <br>
 首先需要先了解一个分布式理论原则：CAP理论，在分布式系统中，一致性、可用性和分区容错性。三者不可兼得。在设计系统架构时是无法同时满足CAP这三个特性，那么我们怎么样来做权衡选择呢？选择的关键就是取决于您的业务场景。对于现在的互联网应用，特点就是机器数量庞大，部署节点分散，网络故障是常态。可用性是必须保证的，通常一般业务系统会在C和A之间做权衡，但为了保证系统的高可用性，其实本质上就放弃了C，选择AP。
 <br>
 <strong>保证系统的高可用方式有以下几种：</strong>
 <br>
 <strong>隔离</strong> ：发生故障的时候，能限定它传播的范围和影响范围。就应该对其进行隔离，不会出现雪球效应。隔离 包含：进程隔离、线程隔离、集群隔离、机房隔离、读写隔离、动静隔离、爬虫隔离。
 <br>
 <strong>限流</strong> ：限流的目的是通过对并发访问进行限速或者一个时间窗口内的请求进行限速来保护系统常见的限流算 法，有令牌桶和漏桶算法。
 <br>
 <strong>预案、降级</strong> ：目的也是对我们的应用系统的一种保护。在不牺牲核心功能，或者牺牲一定的用户体验的情况 下，保证我们的服务还是可用的。在降级之前我们的准备工作是预案的准备。还要使用配置中心实现 开关配置。
 <br>
 <strong>2-高性能性</strong>
 <br>
 <strong>缓存</strong> ：浏览器或者APP客户端缓存、CDN缓存、接入层缓存(如：Nginx缓存)、应用层缓存(堆内缓存、对外 缓存、磁盘缓存)、分布式缓存。
 <br>
 <strong>异步并发</strong> ：当用户请求达到服务端时，比如商品详情页，它涉及到很多属性，由于目前在微服务这个大环境 下，就会存在大量的HTTP，SOA服务的调用。如果是使用同步调用来获取数据，这个线程都是 出于阻塞状态，降低了系统的吞吐量。这个时候我们就需要使用异步来进行提升吞吐量了。目前 Java这方面的开源框架有： HttpAsyncClient、gRPC、Thrift等。
 <br>
 <strong>扩容</strong> ：业务量级越来越大，单台服务器无法处理这么大的业务流量，这就需要分而治之的思想来对待了。扩 容包括单体应用垂直扩容和水平扩容。垂直扩容：就是对硬件资源进行提升，比如CPU和内存。水平 扩容：就是增加更多的应用镜像，通过负载均衡来分摊压力。
 <br>
 最后涉及到高性能的比如数据层索引优化、性能测试等。
 <br>
 <strong>3-高伸缩性</strong>
 <br>
 通过很少的改动，甚至只是增加一些硬件设备，就能实现整个系统能力的线性增长，实现高吞吐量和低延迟 性能。
 <br>
 对于可伸缩性和纯粹的性能调优，是有区别的：可伸缩性，它主要是对高性能，低成本和可维护性等诸多因素的综合考虑和平衡。普通的性能优化，更多的只是单台机器的性能指标的优化。但是它们有个共同点，就是根据系统的特点在吞吐量和延迟之间进行一个侧重选择。
 <br>
 <strong>3、康威定律</strong>
 <br>
 康威定律是马尔文康威1967年提出的：“设计系统的架构受制于产生这些设计的组织的沟通结构。“通俗 的来说：产品必然是其（人员）组织沟通结构的缩影。
 <br>
 <strong>康威定律可总结为四个定律：</strong>
 <br>
 <strong>第一定律</strong> ：组织沟通方式会通过系统设计表达出来。
 <br>
 <strong>第二定律</strong> ：时间再多一件事情也不可能做的完美，但总有时间做完一件事情。
 <br>
 <strong>第三定律</strong> ：线型系统和线型组织架构间有潜在的异质同态特性。
 <br>
 <strong>第四定律</strong> ：大的系统组织总是比小系统更倾向于分解。</p>
<h3>九、架构总结</h3>
<p>通过阅读了本文相信你们对架构有了初步的认知和了解，但想成为架构师必须具备足够的<strong>技术</strong> 和<strong>业务</strong> 能力，下面我就以个人对架构的理解和总结说一下个人感触，关于架构总结我从 <strong>3 个</strong> 维度去阐明，具体如下图：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F6bf520b89b149bcd5345619bd804e0d8.png%23clientId%3Dub5ebafda-cf73-4%5C%26crop%3D0%5C%26crop%3D0%23pic_center%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D199%5C%26id%3Dub15f2a1a%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D299%5C%26originWidth%3D461%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dfalse%5C%26size%3D19787%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Dufa244987-4c53-445b-8b1c-9fcfff15116%5C%26title%3D%5C%26width%3D307.3333333333333&amp;size=m" alt="">
 <br>
 <strong>架构认知</strong> ：我个人对架构的认知在于<strong>万物皆架构</strong> ，在<strong>生活</strong> 或<strong>虚拟世界</strong> 里都存在着独有的架构，好比在现实生活中的楼房、交通轨道、手机等，都离不开本身着重的架构设计，而在虚拟世界里有应用软件、通信软件和游戏等，但这些基本上都是环绕在我们脑海里对架构的<strong>初步认知</strong> ，还尚未成熟，因为<strong>架构是一种思想</strong> ，也是<strong>抽象</strong> 的，并不被人所理解。
 <br>
 <strong>架构理解</strong> ：通过我对架构上的认知，也触发了我本身对架构上的理解。<strong>架构的设计不在于精，而在于弹性适用性。</strong>假如我们对架构的理解是在于<strong>技术性</strong> 的满足，那么就会让你<strong>陷入</strong> 技术的海洋，到最后就会迷失方向，一直站在目前的方位。对于产生这样的结果，是因为没有<strong>真正</strong> 的去<strong>认知</strong> 和<strong>理解</strong> 架构的本质。那么架构的本质是什么呢？应该怎么做才能往正确的架构方向靠近呢？无疑这些问题就成为了我们脑海里在探索答案的过程中，对于刚提到的 <strong>2 个</strong> 问题我会在下面进行说明。
 <br>
 <strong>1、架构的本质是什么呢？</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F283c70eb8876cdb2422edd04206bc9a4.png%23clientId%3Dub5ebafda-cf73-4%5C%26crop%3D0%5C%26crop%3D0%23pic_center%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D107%5C%26id%3Du599ace30%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D160%5C%26originWidth%3D776%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dfalse%5C%26size%3D7867%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Du53385651-8336-40f7-867a-d43f00d9b6b%5C%26title%3D%5C%26width%3D517.3333333333334&amp;size=m" alt=""></p>
<pre><code>                                            - 架构师是技术和业务之间的桥梁；
                                            - 架构师不能只顾技术，不懂业务；
                                            - 架构师很容易两头不讨好。
</code></pre>
<p><strong>2、应该怎么做才能往正确的架构方向靠近呢？</strong>
 <br>
 首先，需要从<strong>架构思维</strong> 上的转变，如下图：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fef3b98a92103bed2e1abeff334b8c8cd.png%23clientId%3Duaf3b0e3f-80d5-4%5C%26crop%3D0%5C%26crop%3D0%23pic_center%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D109%5C%26id%3Du798c1a81%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D164%5C%26originWidth%3D898%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D10690%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Duc29d16aa-5938-4bb4-ae90-22d8d499da4%5C%26title%3D%25E6%259E%25B6%25E6%259E%2584%25E6%2580%259D%25E7%25BB%25B4%25E6%25A8%25A1%25E5%259E%258B%5C%26width%3D598.6666666666666&amp;size=m" alt="">
 <br>
 然后，还要从实际出发点上去看，应该怎么去做，需要哪些方面的经验和方法，如下图：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F5d3660657ce6c44b04dd30606693ef74.png%23clientId%3Duaf3b0e3f-80d5-4%5C%26crop%3D0%5C%26crop%3D0%23pic_center%5C%26crop%3D1%5C%26crop%3D1%5C%26from%3Dpaste%5C%26height%3D120%5C%26id%3Dub9cc2b8d%5C%26margin%3D%255Bobject%2520Object%255D%5C%26name%3Dimage.png%5C%26originHeight%3D180%5C%26originWidth%3D912%5C%26originalType%3Dbinary%5C%26ratio%3D1%5C%26rotation%3D0%5C%26showTitle%3Dtrue%5C%26size%3D15140%5C%26status%3Ddone%5C%26style%3Dnone%5C%26taskId%3Dub153f3b5-16c2-46d3-86c0-97dcfe55bfd%5C%26title%3D%25E6%259E%25B6%25E6%259E%2584%25E8%2583%25BD%25E5%258A%259B%25E6%25A8%25A1%25E5%259E%258B%5C%26width%3D608&amp;size=m" alt="">
 <br>
 <strong>架构启发</strong>：从我个人经历架构行业以来，一直对于架构上的认知、理解给予我很多启发。现在我从个人的角度出发去说明架构设计中的思想，其实，架构致力于很多方面、也以各种形式存在。在做架构设计时，我们基本上会从各方面角度去思考，比如用户需求、业务切入点、数据量级等，那么通常所说的架构设计，都是为了满足或解决实际业务问题的。我个人所理解架构总结出一句话就是：架构设计不在于完美，也不在于复杂，而在于架构足够简单并且能够适用于业务，便于能够让人去理解。</p>
<hr>
<blockquote>
 <p>最后，若大家有想往架构这方面去提升的，我会致力于架构方面的经验持续输出，为大家输出高质量文章，麻烦大家动动小手指点个赞，收藏+关注不迷路哦\~</p>
</blockquote>
<p>原文链接: https://blog.csdn.net/weixin_43322048/article/details/127572403</p>]]></description><guid isPermaLink="false">/archives/f3afd442-5723-4c5f-a61e-84108c2308d0</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Mon, 30 Sep 2024 06:44:50 GMT</pubDate></item><item><title><![CDATA[Spring Boot 整合MyBatis-Plus 实现多层次树结构的异步加载功能]]></title><link>https://wiki.dgfygk.com/archives/27f0ea74-4f49-4a6b-9e13-81f835974901</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%20%E6%95%B4%E5%90%88MyBatis-Plus%20%E5%AE%9E%E7%8E%B0%E5%A4%9A%E5%B1%82%E6%AC%A1%E6%A0%91%E7%BB%93%E6%9E%84%E7%9A%84%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E5%8A%9F%E8%83%BD&amp;url=/archives/27f0ea74-4f49-4a6b-9e13-81f835974901" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#1_2">1，前言</a></li>
 <li><a href="https://wiki.dgfygk.com#2_6">2，什么是多层次树结构？</a></li>
 <li><a href="https://wiki.dgfygk.com#3_9">3，异步加载的意义</a></li>
 <li><a href="https://wiki.dgfygk.com#4_13">4，技术选型与实现思路</a></li>
 <li><a href="https://wiki.dgfygk.com#5_22">5，具体案例</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#51_27">5.1，项目结构</a></li>
   <li><a href="https://wiki.dgfygk.com#52pomxml_49">5.2，项目配置（pom.xml）</a></li>
   <li><a href="https://wiki.dgfygk.com#53applicationyml_120">5.3，配置文件（application.yml）</a></li>
   <li><a href="https://wiki.dgfygk.com#54Mapper_137">5.4，实体类与Mapper接口</a></li>
   <li><a href="https://wiki.dgfygk.com#55Service_170">5.5，Service层</a></li>
   <li><a href="https://wiki.dgfygk.com#56controller_206">5.6，controller</a></li>
   <li><a href="https://wiki.dgfygk.com#57_232">5.7，前端页面与异步加载实现</a></li>
   <li><a href="https://wiki.dgfygk.com#58_311">5.8，数据库结构</a></li>
  </ul></li>
</ul>
<p>
 <br>
</p>
<h3>1，前言</h3>
<p>在使用 Spring Boot 和 MyBatis-Plus 实现多层次树结构的异步加载策略时，理解其基本原理和技术背景是至关重要的。本文将详细探讨如何通过 Spring Boot 的 RESTful API 和 MyBatis-Plus 的强大功能实现多层次树结构的异步加载，并展示如何使用 Thymeleaf、Bootstrap 和 JavaScript 实现前端的动态渲染。</p>
<h3>2，什么是多层次树结构？</h3>
<p>多层次树结构是许多应用场景中的常见需求，尤其是在分类管理、组织结构、权限管理等场景中。例如，电商平台中的商品分类可以有多个层级，从根类别到具体商品的详细分类，形成一个递归的树形结构。为了有效管理和展示这些数据，通常需要对其进行异步加载，即在用户需要时才加载具体的层级数据，而不是一次性加载所有数据。这不仅能够减少初始数据加载的时间，还可以提高用户体验。</p>
<h3>3，异步加载的意义</h3>
<p>在处理大型树结构时，性能是一个非常重要的考量因素。一次性加载所有层级的数据不仅可能会导致数据传输过大，还会引发前端页面的性能问题。异步加载策略通过在用户展开某个节点时，动态加载该节点下的子节点数据，有效地减少了数据传输量，提高了页面响应速度。这种方法尤其适用于需要处理大量数据并且层级较多的场景。</p>
<h3>4，技术选型与实现思路</h3>
<p>为了实现上述功能，我们将采用 Spring Boot 构建后端 API，使用 MyBatis-Plus 处理数据库操作，并通过前端的 Thymeleaf 模板、Bootstrap 进行 UI 展示。具体实现步骤包括：</p>
<ol>
 <li><strong>数据库设计</strong>: 创建一个 category 表，包含 id、parent_id、name 等字段，用于存储分类的层次结构。</li>
 <li><strong>后端实现</strong>: 使用 Spring Boot 构建 RESTful API，通过 MyBatis-Plus 进行数据查询。后端 API 将支持根据 parent_id 查询子节点数据，提供给前端进行异步加载。</li>
 <li><strong>前端实现</strong>: 使用 Thymeleaf 模板引擎生成 HTML 页面，并通过 Bootstrap 提供的组件美化页面。通过 JavaScript 实现异步加载功能，当用户点击某个分类节点时，发送请求加载其子分类数据，并动态渲染到页面上。</li>
 <li><strong>代码示例与配置</strong>: 文章中将提供完整的代码示例，包括 Spring Boot 项目配置、MyBatis-Plus 的 Mapper 和 Service 实现，以及前端 HTML、JavaScript 代码，帮助开发者快速理解和实现多层次树结构的异步加载。本篇文章将深入讲解如何使用 Spring Boot3.3 和 MyBatis-Plus 联合实现多层次树结构的异步加载，并提供完整的代码示例。</li>
</ol>
<h3>5，具体案例</h3>
<p>本章节将深入讲解如何使用 Spring Boot3.3 和 MyBatis-Plus 联合实现多层次树结构的异步加载，并提供完整的代码示例。运行效果：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5d2352b965fd4b618ea96944cb7014da.png&amp;size=m" alt=""></p>
<h4>5.1，项目结构</h4>
<p>我们将构建一个Spring Boot项目，使用MyBatis-Plus进行数据库操作，并结合Thymeleaf模板引擎在前端展示树结构。以下是项目的基本结构：</p>
<pre><code class="prism language-xml">springboot-tree-async
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.icoderoad.treeasync
│   │   │       ├── controller
│   │   │       ├── entity
│   │   │       ├── mapper
│   │   │       ├── service
│   │   │       └── serviceImpl
│   │   ├── resources
│   │   │   ├── templates
│   │   │   ├── application.yml
│   └── test
│       └── java
│           └── com.icoderoad.treeasync
└── pom.xml
</code></pre>
<h4>5.2，项目配置（pom.xml）</h4>
<p>首先，我们需要配置pom.xml，包括Spring Boot和MyBatis-Plus的依赖：</p>
<pre><code class="prism language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;parent&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;3.3.3&lt;/version&gt;
        &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
    &lt;/parent&gt;
    &lt;groupId&gt;com.icoderoad&lt;/groupId&gt;
    &lt;artifactId&gt;treeasync&lt;/artifactId&gt;
    &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
    &lt;name&gt;treeasync&lt;/name&gt;
    &lt;description&gt;Demo project for Spring Boot&lt;/description&gt;

    &lt;properties&gt;
        &lt;java.version&gt;17&lt;/java.version&gt;
         &lt;mybatis-plus-boot-starter.version&gt;3.5.7&lt;/mybatis-plus-boot-starter.version&gt;
        &lt;mybatis-spring.version&gt;3.0.3&lt;/mybatis-spring.version&gt;
    &lt;/properties&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
            &lt;optional&gt;true&lt;/optional&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.baomidou&lt;/groupId&gt;
            &lt;artifactId&gt;mybatis-plus-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;${mybatis-plus-boot-starter.version}&lt;/version&gt;
        &lt;/dependency&gt;

         &lt;dependency&gt;
            &lt;groupId&gt;org.mybatis&lt;/groupId&gt;
            &lt;artifactId&gt;mybatis-spring&lt;/artifactId&gt;
            &lt;version&gt;${mybatis-spring.version}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- 数据库驱动依赖 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-j&lt;/artifactId&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;

&lt;/project&gt;
</code></pre>
<h4>5.3，配置文件（application.yml）</h4>
<p>接下来，我们配置application.yml，指定数据源及MyBatis-Plus的配置。</p>
<pre><code class="prism language-yaml">spring:
  datasource:
    url: jdbc:mysql://localhost:3306/tree_db?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  global-config:
    db-config:
      id-type: auto
</code></pre>
<h4>5.4，实体类与Mapper接口</h4>
<p>我们使用一个简单的Category实体来表示树结构中的节点：</p>
<pre><code class="prism language-java">package com.icoderoad.treeasync.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("category")
public class Category {

    @TableId(type = IdType.AUTO)
    private Long id;
    private Long parentId;
    private String name;
}
</code></pre>
<p>CategoryMapper接口定义了数据库操作：</p>
<pre><code class="prism language-java">package com.icoderoad.treeasync.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.icoderoad.treeasync.entity.Category;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CategoryMapper extends BaseMapper&lt;Category&gt; {

}
</code></pre>
<h4>5.5，Service层</h4>
<p>在Service层，我们实现了获取树结构节点的逻辑：</p>
<pre><code class="prism language-java">package com.icoderoad.treeasync.service;

import com.icoderoad.treeasync.entity.Category;
import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;

public interface CategoryService extends IService&lt;Category&gt; {

    List&lt;Category&gt; getChildren(Long parentId);
}


package com.icoderoad.treeasync.service.impl;

import com.icoderoad.treeasync.entity.Category;
import com.icoderoad.treeasync.mapper.CategoryMapper;
import com.icoderoad.treeasync.service.CategoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CategoryServiceImpl extends ServiceImpl&lt;CategoryMapper, Category&gt; implements CategoryService {


    @Override
    public List&lt;Category&gt; getChildren(Long parentId) {

        return lambdaQuery().eq(Category::getParentId, parentId).list();
    }
}
</code></pre>
<h4>5.6，controller</h4>
<p>controller用来处理前端的异步请求：</p>
<pre><code class="prism language-java">package com.icoderoad.treeasync.controller;

import com.icoderoad.treeasync.entity.Category;
import com.icoderoad.treeasync.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/category")
public class CategoryController {


    @Autowired
    private CategoryService categoryService;

    @GetMapping("/children/{parentId}")
    public List&lt;Category&gt; getChildren(@PathVariable Long parentId) {

        return categoryService.getChildren(parentId);
    }
}
</code></pre>
<h4>5.7，前端页面与异步加载实现</h4>
<p>前端使用Thymeleaf模板结合Bootstrap和JavaScript实现树结构的展示与异步加载：在 src/main/resources/templates 目录下创建 index.html 模板文件。</p>
<pre><code class="prism language-javascript">&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;分类树结构&lt;/title&gt;
    &lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"&gt;
    &lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.5/font/bootstrap-icons.min.css"&gt;
    &lt;style&gt;
        .tree-item {

            cursor: pointer;
            padding-left: 1rem;
        }

        .tree-icon {

            margin-right: 0.5rem;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class="container"&gt;
    &lt;h2 class="my-4"&gt;分类树结构&lt;/h2&gt;
    &lt;ul id="categoryTree" class="list-group"&gt;
        &lt;!-- 根节点会从服务器加载并插入到这里 --&gt;
    &lt;/ul&gt;
&lt;/div&gt;

&lt;script src="https://code.jquery.com/jquery-3.5.1.min.js"&gt;&lt;/script&gt;
&lt;script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
    $(document).ready(function () {

        // 加载根节点
        loadTreeNodes(0, $('#categoryTree'));

        // 动态加载节点
        function loadTreeNodes(parentId, ulElement) {

            $.ajax({

                url: '/category/children/' + parentId,
                method: 'GET',
                success: function (data) {

                    $.each(data, function (index, category) {

                        const li = $('&lt;li&gt;').addClass('list-group-item tree-item');
                        const icon = $('&lt;i&gt;').addClass('bi bi-chevron-right tree-icon');
                        const span = $('&lt;span&gt;').text(category.name);

                        li.append(icon).append(span).data('id', category.id);
                        ulElement.append(li);

                        // 点击展开或折叠
                        li.on('click', function (e) {

                            e.stopPropagation();
                            const iconElement = $(this).find('.tree-icon');
                            const childrenUl = $(this).find('ul');

                            if (childrenUl.length === 0) {

                                // 加载子节点
                                const newUl = $('&lt;ul&gt;').addClass('list-group ml-3');
                                loadTreeNodes($(this).data('id'), newUl);
                                $(this).append(newUl);
                                iconElement.removeClass('bi-chevron-right').addClass('bi-chevron-down');
                            } else {

                                // 切换展开/折叠
                                childrenUl.toggle();
                                iconElement.toggleClass('bi-chevron-right bi-chevron-down');
                            }
                        });
                    });
                }
            });
        }
    });
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h4>5.8，数据库结构</h4>
<p>数据库表category的SQL DDL语句如下：</p>
<pre><code class="prism language-sql">CREATE TABLE `category` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `parent_id` bigint DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
</code></pre>
<p>初始化分类表数据：</p>
<pre><code class="prism language-sql">-- 插入第一层数据（根节点）
INSERT INTO category (parent_id, name) VALUES (0, '电子产品');
INSERT INTO category (parent_id, name) VALUES (0, '家用电器');
INSERT INTO category (parent_id, name) VALUES (0, '时尚服饰');

-- 插入第二层数据
INSERT INTO category (parent_id, name) VALUES (1, '手机');
INSERT INTO category (parent_id, name) VALUES (1, '笔记本电脑');
INSERT INTO category (parent_id, name) VALUES (2, '冰箱');
INSERT INTO category (parent_id, name) VALUES (2, '洗衣机');
INSERT INTO category (parent_id, name) VALUES (3, '男装');
INSERT INTO category (parent_id, name) VALUES (3, '女装');

-- 插入第三层数据
INSERT INTO category (parent_id, name) VALUES (4, '智能手机');
INSERT INTO category (parent_id, name) VALUES (4, '功能手机');
INSERT INTO category (parent_id, name) VALUES (5, '游戏笔记本');
INSERT INTO category (parent_id, name) VALUES (5, '超极本');
INSERT INTO category (parent_id, name) VALUES (6, '双门冰箱');
INSERT INTO category (parent_id, name) VALUES (6, '单门冰箱');
INSERT INTO category (parent_id, name) VALUES (7, '滚筒洗衣机');
INSERT INTO category (parent_id, name) VALUES (7, '波轮洗衣机');
INSERT INTO category (parent_id, name) VALUES (8, '休闲装');
INSERT INTO category (parent_id, name) VALUES (8, '正装');
INSERT INTO category (parent_id, name) VALUES (9, '连衣裙');
INSERT INTO category (parent_id, name) VALUES (9, '上衣');

-- 插入第四层数据
INSERT INTO category (parent_id, name) VALUES (10, '安卓手机');
INSERT INTO category (parent_id, name) VALUES (10, '苹果手机');
INSERT INTO category (parent_id, name) VALUES (13, '变形笔记本');
INSERT INTO category (parent_id, name) VALUES (13, '传统笔记本');
INSERT INTO category (parent_id, name) VALUES (17, '办公室连衣裙');
INSERT INTO category (parent_id, name) VALUES (17, '休闲连衣裙');
</code></pre>
<p>通过一个简单的分类树示例展示了如何在前端逐步加载节点，避免一次性加载大量数据带来的性能问题。</p>
<p>原文链接: https://blog.csdn.net/weixin_43025151/article/details/142528017</p>]]></description><guid isPermaLink="false">/archives/27f0ea74-4f49-4a6b-9e13-81f835974901</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:44:22 GMT</pubDate></item><item><title><![CDATA[Spring Boot实现定时任务调度]]></title><link>https://wiki.dgfygk.com/archives/52883d5c-c78c-4090-9d85-9374803b98b6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%E5%AE%9E%E7%8E%B0%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6&amp;url=/archives/52883d5c-c78c-4090-9d85-9374803b98b6" width="1" height="1" alt="" style="opacity:0;">
<p>在业务系统中，定时任务是非常常见的需求，例如定时对订单状态进行更新、定时生成销售报表、自动化库存管理等。Spring Boot 提供了非常方便的定时任务调度功能，并且结合线程池技术，我们可以高效地执行多个定时任务，保证系统的扩展性和高可用性。
 <br>
 本文将深入探讨如何使用 Spring Boot 实现定时任务调度，特别是结合线程池技术，确保多个任务能够并发安全地执行。此外，我们还将展示如何利用 cron 表达式灵活定义定时任务的触发时间，提供详细的示例以及电商交易系统中的实际应用。</p>
<hr>
<h3>1. 定时任务调度的必要性</h3>
<p>在电商交易系统中，定时任务的应用场景非常广泛，包括但不限于以下几种：</p>
<ol>
 <li><strong>订单状态更新</strong>：定时检测用户的支付状态，超时未支付的订单自动取消。</li>
 <li><strong>自动清理任务</strong>：定期清理过期的购物车信息或未完成的订单。</li>
 <li><strong>库存管理</strong>：定期检查库存状态，生成库存预警。</li>
 <li><strong>生成报表</strong>：每天、每周或每月定时生成销售报表，发送给管理人员。</li>
</ol>
<p>为了应对这些任务，系统需要一个可靠的定时任务调度器，并且在多任务并发执行时，必须保证系统的性能和稳定性。这就需要引入线程池技术来控制并发任务的执行。</p>
<hr>
<h3>2. Spring Boot 定时任务基础</h3>
<p>Spring Boot 提供了非常简便的定时任务支持。通过 <code>@EnableScheduling</code> 注解，我们可以轻松地开启定时任务调度功能，而通过 <code>@Scheduled</code> 注解，我们可以指定任务的执行时间和频率。</p>
<h4>2.1 启用定时任务</h4>
<p>首先，我们需要在 Spring Boot 应用的主类上添加 <code>@EnableScheduling</code> 注解，启用定时任务调度。</p>
<pre><code>package com.example.ecommerce;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class EcommerceApplication {

    public static void main(String[] args) {
        SpringApplication.run(EcommerceApplication.class, args);
    }
}
</code></pre>
<h4>2.2 使用 <code>@Scheduled</code> 注解</h4>
<p><code>@Scheduled</code> 注解可以定义任务的执行时间。它支持多种形式的定时表达式，其中最灵活的方式是使用 cron 表达式。</p>
<pre><code>package com.example.ecommerce.task;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class OrderStatusCheckTask {

    /**
     * 每隔1分钟检查一次订单状态
     */
    @Scheduled(cron = "0 */1 * * * ?")
    public void checkOrderStatus() {
        System.out.println("执行定时任务：检查订单状态");
        // 订单状态检查逻辑
    }
}
</code></pre>
<p>上面的示例中，<code>checkOrderStatus()</code> 方法每隔1分钟执行一次，cron 表达式 <code>0 */1 * * * ?</code> 表示每分钟的第0秒执行一次。</p>
<hr>
<h3>3. 结合线程池技术优化定时任务</h3>
<p>Spring Boot 的定时任务调度默认是串行执行的，也就是说，如果一个任务没有执行完毕，其他定时任务将无法开始。对于电商交易系统这样复杂的场景，多任务并发执行是非常有必要的，因此我们需要结合线程池来优化定时任务的执行。</p>
<h4>3.1 启用异步线程池</h4>
<p>Spring 提供了 <code>@EnableAsync</code> 注解用于启用异步任务支持，我们可以结合线程池让多个定时任务并发执行。</p>
<p>首先，在配置类中启用异步支持，并配置一个自定义线程池。</p>
<pre><code>package com.example.ecommerce.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
@EnableAsync
public class TaskSchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10); // 线程池大小
        scheduler.setThreadNamePrefix("TaskScheduler-"); // 线程名称前缀
        scheduler.setAwaitTerminationSeconds(60); // 等待所有任务完成的最大时间
        scheduler.setWaitForTasksToCompleteOnShutdown(true); // 在关闭时等待任务完成
        return scheduler;
    }
}
</code></pre>
<p>在这里，我们定义了一个 <code>ThreadPoolTaskScheduler</code>，该线程池用于处理定时任务。线程池的大小设置为 10，表示最多可以同时执行 10 个定时任务。你可以根据系统的实际负载和需求调整线程池的大小。</p>
<h4>3.2 使用线程池并发执行定时任务</h4>
<p>接下来，我们将前面的 <code>OrderStatusCheckTask</code> 定时任务与线程池结合，让它在多线程环境中执行。</p>
<pre><code>package com.example.ecommerce.task;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class OrderStatusCheckTask {

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;

    /**
     * 每隔1分钟检查一次订单状态
     */
    @Scheduled(cron = "0 */1 * * * ?")
    public void checkOrderStatus() {
        taskScheduler.submit(() -&gt; {
            System.out.println("执行定时任务：检查订单状态");
            // 订单状态检查逻辑
        });
    }
}
</code></pre>
<p>在这个示例中，<code>checkOrderStatus()</code> 方法中的任务被提交给了我们自定义的线程池 <code>taskScheduler</code>，因此该任务将会并发执行，而不会影响其他定时任务的正常运行。</p>
<hr>
<h3>4. 支持动态配置定时任务</h3>
<p>电商交易系统中，任务的执行时间可能需要根据实际需求进行动态调整。例如，我们希望能够通过数据库或配置文件来管理定时任务的执行时间，而不是在代码中写死。为此，我们可以引入动态配置定时任务的机制。</p>
<h4>4.1 表结构设计</h4>
<p>我们可以通过数据库表来保存定时任务的配置信息，并允许系统在运行时加载这些配置，动态调整任务的执行时间。</p>
<h5>任务表 <code>scheduled_task</code></h5>
<pre><code>CREATE TABLE scheduled_task (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    task_name VARCHAR(255) NOT NULL COMMENT '任务名称',
    cron_expression VARCHAR(255) NOT NULL COMMENT 'Cron 表达式',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '任务状态: 1-启用, 0-禁用',
    last_run_time TIMESTAMP COMMENT '上次运行时间',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
);
</code></pre>
<p>字段说明：</p>
<ul>
 <li><code>task_name</code>：定时任务的名称。</li>
 <li><code>cron_expression</code>：任务的 cron 表达式。</li>
 <li><code>status</code>：任务的状态，1 表示启用，0 表示禁用。</li>
 <li><code>last_run_time</code>：任务上次运行的时间。</li>
 <li><code>created_at</code> 和 <code>updated_at</code>：记录任务的创建和更新时间。</li>
</ul>
<h4>4.2 动态加载任务配置</h4>
<p>我们可以通过数据库查询定时任务的配置信息，并动态调整任务的执行时间。</p>
<pre><code>package com.example.ecommerce.service;

import com.example.ecommerce.model.ScheduledTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DynamicTaskService {

    @Autowired
    private TaskScheduler taskScheduler;

    @Autowired
    private ScheduledTaskRepository taskRepository;

    /**
     * 加载所有启用的定时任务
     */
    public void loadTasks() {
        List&lt;ScheduledTask&gt; tasks = taskRepository.findAllByStatus(1);
        for (ScheduledTask task : tasks) {
            taskScheduler.schedule(() -&gt; executeTask(task), new CronTrigger(task.getCronExpression()));
        }
    }

    /**
     * 执行定时任务
     */
    private void executeTask(ScheduledTask task) {
        System.out.println("执行任务：" + task.getTaskName());
        // 执行具体任务逻辑
    }
}
</code></pre>
<p>在这个服务中，我们从数据库中加载所有启用的定时任务，并通过 <code>taskScheduler.schedule()</code> 方法使用 cron 表达式动态调度这些任务。</p>
<h4>4.3 定时任务的动态控制</h4>
<p>我们还可以通过 REST 接口或者管理后台来动态控制任务的启用和禁用。通过更新数据库中的 <code>status</code> 字段，就可以控制任务是否执行。定时任务调度器会根据最新的配置动态调整任务的执行状态。</p>
<hr>
<h3>5. 电商交易系统中的定时任务场景示例</h3>
<h4>5.1 订单状态检查定时任务</h4>
<p>在电商交易系统中，订单的状态管理是非常重要的。用户下单后，如果在一定时间内未支付订单，需要自动取消订单。我们可以通过定时任务来实现这个功能。</p>
<pre><code>package com.example.ecommerce.task;

import com.example.ecommerce.model.Order;
import com.example.ecommerce.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class OrderStatusCheckTask {

    @Autowired
    private OrderService orderService;

    /**
     * 每隔5分钟检查一次未支付订单状态
     */
    @Scheduled(cron = "0 */5 * * * ?")
    public void checkUnpaidOrders() {
        System.out.println("执行定时任务：检查未支付订单");
        List&lt;Order&gt; unpaidOrders = orderService.getUnpaidOrders();
        for (Order order : unpaidOrders) {
            if (order.isOverdue()) {
                orderService.cancelOrder(order);
                System.out.println("取消超时未支付订单：" + order.getId());
            }
        }
    }
}
</code></pre>
<p>通过这个定时任务，我们可以每隔 5 分钟检查一次未支付的订单，并自动取消超时未支付的订单。</p>
<h4>5.2 库存预警定时任务</h4>
<p>对于库存管理，电商系统可以设置一个定时任务，每天检查库存状态，并生成库存预警，提醒管理人员补充库存。</p>
<pre><code>package com.example.ecommerce.task;

import com.example.ecommerce.model.Product;
import com.example.ecommerce.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class InventoryCheckTask {

    @Autowired
    private InventoryService inventoryService;

    /**
     * 每天早上8点检查库存状态
     */
    @Scheduled(cron = "0 0 8 * * ?")
    public void checkInventory() {
        System.out.println("执行定时任务：检查库存状态");
        List&lt;Product&gt; lowStockProducts = inventoryService.getLowStockProducts();
        for (Product product : lowStockProducts) {
            System.out.println("库存不足产品：" + product.getName());
            // 发送库存预警
        }
    }
}
</code></pre>
<p>这个任务每天早上8点执行，检查库存状态，并生成库存预警，确保电商平台的库存充足。</p>
<hr>
<h3>6. 异步和并发控制</h3>
<p>为了避免定时任务之间互相影响，或者由于单个任务执行过长导致的阻塞，我们可以将每个定时任务放到线程池中异步执行，从而提高任务的执行效率和系统的响应能力。</p>
<p>通过将定时任务提交给线程池执行，即使某个任务耗时较长，也不会影响其他任务的正常执行。</p>
<hr>
<p>原文链接: https://blog.csdn.net/weixin_39996520/article/details/142440890</p>]]></description><guid isPermaLink="false">/archives/52883d5c-c78c-4090-9d85-9374803b98b6</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:44:06 GMT</pubDate></item><item><title><![CDATA[SpringCloud Gateway 打印请求响应日志、跨域全局配置]]></title><link>https://wiki.dgfygk.com/archives/3408d074-b6cd-4db4-8b2e-510534e40423</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud%20Gateway%20%E6%89%93%E5%8D%B0%E8%AF%B7%E6%B1%82%E5%93%8D%E5%BA%94%E6%97%A5%E5%BF%97%E3%80%81%E8%B7%A8%E5%9F%9F%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE&amp;url=/archives/3408d074-b6cd-4db4-8b2e-510534e40423" width="1" height="1" alt="" style="opacity:0;">
<p>version：spring-cloud 2021.0.1，spring-boot 2.6.3，spring-cloud-alibaba 2021.0.1.0</p>
<p>SpringCloudGateway中Post请求参数只能读取一次。
 <br>
 这是因为Gateway网关默认使用的是SpringWebflux，解决这个问题需要容重新构造一个request来替换原先的request。</p>
<p>CacheBodyGlobalFilter这个全局过滤器把原有的request请求中的body内容读出来，并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装，重写getBody方法，并把包装后的请求放到过滤器链中传递下去。这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时，实际上就是调用的重载后的getBody方法，获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。</p>
<p>//
 <br>
 过滤器的Ordered.HIGHEST_PRECEDENCE，即最高优先级的过滤器。优先级设置高的原因是某些系统内置的过滤器可能也会去读body。</p>
<p>
 <br>
</p>
<h5>Request Log</h5>
<pre><code class="java">@Configuration
public class RequestLogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        //String path = request.getPath().value();
        String path = request.getPath().pathWithinApplication().value();//打印请求路径
        String requestUrl = this.getOriginalRequestUrl(exchange);//打印请求url
        String method = request.getMethodValue();
        //cors
        HttpHeaders headers = request.getHeaders();

        log.info("--&gt; method: {} url: {} header: {}", method, requestUrl, headers);
        if ("POST".equals(method)) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -&gt; {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        String bodyString = new String(bytes, StandardCharsets.UTF_8);
                        log.info("--&gt; {}", bodyString);
                        //log.info("--&gt; {}", formatStr(bodyString)); //formData
                        exchange.getAttributes().put("POST_BODY", bodyString);
                        DataBufferUtils.release(dataBuffer);
                        Flux&lt;DataBuffer&gt; cachedFlux = Flux.defer(() -&gt; {
                            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                            return Mono.just(buffer);
                        });

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(request) {
                            @Override
                            public Flux&lt;DataBuffer&gt; getBody() { return cachedFlux; }
                        };

                        return chain.filter(exchange.mutate().request(mutatedRequest).build());
                    });
        } else if ("GET".equals(method)) {
            MultiValueMap&lt;String, String&gt; queryParams = request.getQueryParams();
            log.info("请求参数：" + queryParams);
            return chain.filter(exchange);
        }
        return chain.filter(exchange);
    }

    private String getOriginalRequestUrl(ServerWebExchange exchange) {
        ServerHttpRequest req = exchange.getRequest();
        LinkedHashSet&lt;URI&gt; uris = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        URI requestUri = uris.stream().findFirst().orElse(req.getURI());
        MultiValueMap&lt;String, String&gt; queryParams = req.getQueryParams();
        //打印 /api/rest/feign/order/detail
        // return UriComponentsBuilder.fromPath(requestUri.getRawPath()).queryParams(queryParams).build().toUriString();

        return requestUri.toString(); // http://localhost:8091/api/rest/feign/order/detail
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
    /**
     * 去掉FormData 空格,换行和制表符
     */
    private static String formatStr(String str){
        if (str != null &amp;&amp; str.length() &gt; 0) {
            Pattern p = Pattern.compile("\\s*|\t|\r|\n");
            Matcher m = p.matcher(str);
            return m.replaceAll("");
        }
        return str;
    }}
</code></pre>
<p>
 <br>
</p>
<p><strong>Response Log</strong>
 <br>
 响应报文内容分段传输导致不全的解决方法：
 <br>
 1.大报文对fluxBody流循环拼接处理，把fluxBody.map变为fluxBody.buffer().map，从而可以foreach循环Body体了。</p>
<p>2.排除Excel导出。</p>
<p>3.对JSON统一格式处理，日期统一格式处理</p>
<pre><code class="java">@Configuration
public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode != HttpStatus.OK) {
                return chain.filter(exchange);//降级处理返回数据
            }
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                @Override
                public Mono&lt;Void&gt; writeWith(Publisher&lt;? extends DataBuffer&gt; body) {
                    if (body instanceof Flux) {
                        Flux&lt;? extends DataBuffer&gt; fluxBody = Flux.from(body);

                        return super.writeWith(fluxBody.buffer().map(dataBuffers -&gt; {

                            // 合并多个流集合，解决返回体分段传输
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer buff = dataBufferFactory.join(dataBuffers);
                            byte[] content = new byte[buff.readableByteCount()];
                            buff.read(content);
                            DataBufferUtils.release(buff);//释放掉内存

                            //排除Excel导出，不是application/json不打印。若请求是上传图片则在最上面判断。
                            MediaType contentType = originalResponse.getHeaders().getContentType();
                            if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
                                return bufferFactory.wrap(content);
                            }

                            // 构建返回日志
                            String joinData = new String(content);
                            String result = modifyBody(joinData);
                            List&lt;Object&gt; rspArgs = new ArrayList&lt;&gt;();
                            rspArgs.add(originalResponse.getStatusCode().value());
                            rspArgs.add(exchange.getRequest().getURI());
                            rspArgs.add(result);
                            log.info("&lt;-- {} {}\n{}", rspArgs.toArray());

                            getDelegate().getHeaders().setContentLength(result.getBytes().length);
                            return bufferFactory.wrap(result.getBytes());
                        }));
                    } else {
                        log.error("&lt;-- {} 响应code异常", getStatusCode());
                    }
                    return super.writeWith(body);
                }
            };
            return chain.filter(exchange.mutate().response(decoratedResponse).build());

        } catch (Exception e) {
            log.error("gateway log exception.\n" + e);
            return chain.filter(exchange);
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    //返回统一的JSON日期数据 2024-02-23 11:00， null转空字符串
    private String modifyBody(String jsonStr){
        JSONObject json = JSON.parseObject(jsonStr, Feature.AllowISO8601DateFormat);
        JSONObject.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm";
        return JSONObject.toJSONString(json, (ValueFilter) (object, name, value) -&gt; value == null ? "" : value, SerializerFeature.WriteDateUseDateFormat);
    }
}


//    public void setJSONObjectWriteNullStringAsEmpty() {
//    JSON.toJSONString(data,SerializerFeature.WriteNullStringAsEmpty);
//    JSONObject jsonObj = JSON.parseObject(data, Feature.AllowISO8601DateFormat);
//    JSON.toJSONString(data,SerializerFeature.DisableCircularReferenceDetect);
//    JSONObject.DEFFAULT_DATE_FORMAT ="yyyy-MM-dd HH:mm";
//    JSONObject.toJSONString(jsonObject,SerializerFeature.WriteDateUseDateFormat);
//
//    String dataJson = JSON.toJSONString(data, (ValueFilter) (object, name, value) -&gt; {
//        log.info("data:{} ", data);
//
//        log.info("object:{}, name:{}, value:{}", object, name, value);
//        if (value == null) {
//            return "";
//        }
//        return value;
//    });
//    JSONObject jsonObject = new JSONObject();
//    把json对象转换成字节数组
//    byte[] bits = data.getBytes(StandardCharsets.UTF_8);
//    DataBuffer buffer = originalResponse.bufferFactory().wrap(bits);
//    originalResponse.writeWith(Mono.just(buffer));
//    }

}
</code></pre>
<p>
 <br>
</p>
<h5>CorsWebFilterConfig 跨域配置</h5>
<p>Gateway跨域处理
 <br>
 https://blog.csdn.net/weixin_43730516/article/details/127040628</p>
<p>
 <br>
</p>
<pre><code class="java">@Configuration
public class CorsWebFilterConfig {
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfigurationSource corsConfigurationSource = new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(ServerWebExchange serverWebExchange) {
                CorsConfiguration corsConfig = new CorsConfiguration();
                corsConfig.addAllowedHeader("*");
                corsConfig.addAllowedMethod("*");
                corsConfig.addAllowedOriginPattern("*");
                corsConfig.setMaxAge(1800L);
                corsConfig.setAllowCredentials(true);
                return corsConfig;
            }
        };
        return new CorsWebFilter(corsConfigurationSource);
} }
</code></pre>
<p>
 <br>
</p>
<hr>
<p>版权声明：本文为博主原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接和本声明。</p>
<p>原文链接：https://blog.csdn.net/qq_19636353/article/details/126759522</p>
<p>原文链接: https://blog.csdn.net/fanhaifeng66/article/details/142547585</p>]]></description><guid isPermaLink="false">/archives/3408d074-b6cd-4db4-8b2e-510534e40423</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:43:22 GMT</pubDate></item><item><title><![CDATA[【软件架构】软件的十二种架构简介]]></title><link>https://wiki.dgfygk.com/archives/bcbcc775-9bca-4ef7-9308-25952f3f3bd9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84%E3%80%91%E8%BD%AF%E4%BB%B6%E7%9A%84%E5%8D%81%E4%BA%8C%E7%A7%8D%E6%9E%B6%E6%9E%84%E7%AE%80%E4%BB%8B&amp;url=/archives/bcbcc775-9bca-4ef7-9308-25952f3f3bd9" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>软件的十二种架构简介</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#12_1">一、软件的12种架构</a>
  <br>
  <em> 
   <br>
  </em>
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__Monolithic_Architecture_4">1. 单体架构 (Monolithic Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#2__Layered_Architecture_8">2. 分层架构 (Layered Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#3__EventDriven_Architecture_12">3. 事件驱动架构 (Event-Driven Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#4__Microservices_Architecture_16">4. 微服务架构 (Microservices Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#5__ServiceOriented_Architecture_SOA_20">5. 服务导向架构 (Service-Oriented Architecture, SOA)</a></li>
   <li><a href="https://wiki.dgfygk.com#6__ClientServer_Architecture_24">6. 客户端-服务器架构 (Client-Server Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#7__PeertoPeer_Architecture_28">7. 对等网络架构 (Peer-to-Peer Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#8__Serverless_Architecture_32">8. 无服务器架构 (Serverless Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#9_CQRS_Command_Query_Responsibility_Segregation_36">9. CQRS (Command Query Responsibility Segregation)</a></li>
   <li><a href="https://wiki.dgfygk.com#10__Clean_Architecture_40">10. 清洁架构 (Clean Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#11_Microkernel_Architecture_45">11.微内核架构 (Microkernel Architecture)</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_47">描述</a></li>
   <li><a href="https://wiki.dgfygk.com#_50">适用场景</a></li>
   <li><a href="https://wiki.dgfygk.com#12_Distributed_Architecture_54">12.分布式架构 (Distributed Architecture)</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_56">描述</a></li>
   <li><a href="https://wiki.dgfygk.com#_59">适用场景</a></li>
   <li><a href="https://wiki.dgfygk.com#_64">微内核与分布式架构的区别</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#12_70">二、软件的12种架构的优缺点</a>
  <br>
  <em> 
   <br>
  </em>
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__73">1. 单体架构</a></li>
   <li><a href="https://wiki.dgfygk.com#2__77">2. 分层架构</a></li>
   <li><a href="https://wiki.dgfygk.com#3__81">3. 事件驱动架构</a></li>
   <li><a href="https://wiki.dgfygk.com#4__85">4. 微服务架构</a></li>
   <li><a href="https://wiki.dgfygk.com#5__SOA_89">5. 服务导向架构 (SOA)</a></li>
   <li><a href="https://wiki.dgfygk.com#6__93">6. 客户端-服务器架构</a></li>
   <li><a href="https://wiki.dgfygk.com#7__97">7. 对等网络架构</a></li>
   <li><a href="https://wiki.dgfygk.com#8__101">8. 无服务器架构</a></li>
   <li><a href="https://wiki.dgfygk.com#9_CQRS_105">9. CQRS</a></li>
   <li><a href="https://wiki.dgfygk.com#10__109">10. 清洁架构</a></li>
   <li><a href="https://wiki.dgfygk.com#11__113">11. 微内核架构</a></li>
   <li><a href="https://wiki.dgfygk.com#12__117">12. 分布式架构</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#12_122">三、软件的12种架构的实现原理及举例</a>
  <br>
  <em> 
   <br>
  </em>
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__125">1. 单体架构</a></li>
   <li><a href="https://wiki.dgfygk.com#2__129">2. 分层架构</a></li>
   <li><a href="https://wiki.dgfygk.com#3__133">3. 事件驱动架构</a></li>
   <li><a href="https://wiki.dgfygk.com#4__137">4. 微服务架构</a></li>
   <li><a href="https://wiki.dgfygk.com#5__SOA_141">5. 服务导向架构 (SOA)</a></li>
   <li><a href="https://wiki.dgfygk.com#6__145">6. 客户端-服务器架构</a></li>
   <li><a href="https://wiki.dgfygk.com#7__149">7. 对等网络架构</a></li>
   <li><a href="https://wiki.dgfygk.com#8__153">8. 无服务器架构</a></li>
   <li><a href="https://wiki.dgfygk.com#9_CQRS_157">9. CQRS</a></li>
   <li><a href="https://wiki.dgfygk.com#10__Clean_Architecture_162">10. 清洁架构 (Clean Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#11__Microkernel_Architecture_166">11. 微内核架构 (Microkernel Architecture)</a></li>
   <li><a href="https://wiki.dgfygk.com#12__Distributed_Architecture_170">12. 分布式架构 (Distributed Architecture)</a></li>
  </ul></li>
</ul>
<p>
 <br>
</p>
<h2>一、软件的12种架构</h2>
<p>软件架构是关于制定软件系统的高层结构的一系列原则和准则。它涉及到软件元素的定义、它们之间的关系，以及它们与环境的交互方式。软件架构对于确保系统的可扩展性、性能、可维护性和安全性至关重要。以下是一些常见的软件架构风格：</p>
<h4>1. 单体架构 (Monolithic Architecture)</h4>
<ul>
 <li><strong>描述</strong>：所有软件组件都运行在同一个进程或服务中，通常是传统的软件开发方式。</li>
 <li><strong>适用场景</strong>：小型应用或者简单的应用。</li>
</ul>
<h4>2. 分层架构 (Layered Architecture)</h4>
<ul>
 <li><strong>描述</strong>：软件被分为多个层次，如表示层、业务逻辑层、持久层等，每层只与相邻的层次通信。</li>
 <li><strong>适用场景</strong>：大多数企业应用。</li>
</ul>
<h4>3. 事件驱动架构 (Event-Driven Architecture)</h4>
<ul>
 <li><strong>描述</strong>：组件之间通过事件进行通信，事件由事件生成者发布，事件消费者订阅并响应这些事件。</li>
 <li><strong>适用场景</strong>：高度解耦的系统，如实时数据处理和异步工作流。</li>
</ul>
<h4>4. 微服务架构 (Microservices Architecture)</h4>
<ul>
 <li><strong>描述</strong>：将应用程序划分为一组小的、松散耦合的服务，每个服务实现特定的业务功能，并通过轻量级通信机制（通常是 HTTP RESTful API）相互协作。</li>
 <li><strong>适用场景</strong>：大型复杂应用，需要高度的可扩展性和灵活性。</li>
</ul>
<h4>5. 服务导向架构 (Service-Oriented Architecture, SOA)</h4>
<ul>
 <li><strong>描述</strong>：强调可复用的服务组件，服务之间通过定义良好的接口和契约（通常使用 SOAP 协议）进行通信。</li>
 <li><strong>适用场景</strong>：需要大量业务流程整合和服务重用的企业应用。</li>
</ul>
<h4>6. 客户端-服务器架构 (Client-Server Architecture)</h4>
<ul>
 <li><strong>描述</strong>：分为客户端（请求服务）和服务器（提供服务）两部分，二者通过网络进行通信。</li>
 <li><strong>适用场景</strong>：几乎所有的网络应用。</li>
</ul>
<h4>7. 对等网络架构 (Peer-to-Peer Architecture)</h4>
<ul>
 <li><strong>描述</strong>：每个节点既是客户端又是服务器，节点之间直接进行资源分享和通信，没有中央服务器。</li>
 <li><strong>适用场景</strong>：文件共享、加密货币等去中心化应用。</li>
</ul>
<h4>8. 无服务器架构 (Serverless Architecture)</h4>
<ul>
 <li><strong>描述</strong>：开发者编写的代码运行在无状态的计算容器中，由云服务提供商动态管理机器资源。通常与函数即服务（Function as a Service, FaaS）模式相关联。</li>
 <li><strong>适用场景</strong>：事件驱动的应用、微服务、快速开发和部署小型服务。</li>
</ul>
<h4>9. CQRS (Command Query Responsibility Segregation)</h4>
<ul>
 <li><strong>描述</strong>：将应用程序的操作分为命令（执行操作）和查询（获取数据），使得系统可以更灵活地优化和扩展读写操作。</li>
 <li><strong>适用场景</strong>：复杂业务逻辑，对读写性能有不同要求的应用。</li>
</ul>
<h4>10. 清洁架构 (Clean Architecture)</h4>
<ul>
 <li><strong>描述</strong>：通过将软件分解为多个层，每一层只依赖于更内层的策略和概念，从而实现关注点分离，提高系统的可维护性和可测试性。</li>
 <li><strong>适用场景</strong> ：需要长期维护和扩展的大型应用。
  <br>
  微内核架构和分布式架构是软件设计中的另外两种重要架构风格，它们在特定的场景下有着独特的应用和优势。</li>
</ul>
<h4>11.微内核架构 (Microkernel Architecture)</h4>
<h5>描述</h5>
<p>微内核架构区分了基本的系统功能和可扩展的应用功能，核心系统提供最基本的操作，而其他功能则通过插件或模块的形式添加。这种架构的关键在于一个小型的内核负责提供最基础和通用的功能（如低级内存管理、设备驱动等），而更高级的功能（如用户界面、应用程序等）则作为独立的组件或服务存在，这些组件在运行时与微内核通信。</p>
<h5>适用场景</h5>
<ul>
 <li>操作系统：许多现代操作系统采用了微内核架构，以便于系统的维护和扩展。</li>
 <li>应用软件：需要高度可扩展和可定制的大型软件系统，比如企业级软件，可以根据不同客户的需求添加或修改功能模块。</li>
</ul>
<h4>12.分布式架构 (Distributed Architecture)</h4>
<h5>描述</h5>
<p>在分布式架构中，系统的不同部分分布在网络上的多个节点上，这些节点协同工作，对外表现为一个统一的系统。分布式架构的关键在于它能够提供高可用性、可扩展性、和容错能力。通过在多台机器上分布运算和存储任务，分布式架构能够处理大量数据和高并发请求。</p>
<h5>适用场景</h5>
<ul>
 <li>大数据处理：像 Apache Hadoop 和 Apache Spark 这样的系统就是为了处理分布在多个节点上的大规模数据集而设计的。</li>
 <li>微服务：微服务架构通常在分布式环境中实现，每个微服务运行在自己的环境中，相互之间通过网络通信。</li>
 <li>云计算和云服务：云平台和服务（如 AWS、Azure、Google Cloud Platform）本质上是分布式架构，提供了各种计算资源和服务。</li>
</ul>
<h4>微内核与分布式架构的区别</h4>
<ul>
 <li><strong>关注点</strong>：微内核架构关注于如何将系统核心功能与高级功能分离，以实现系统的灵活性和可扩展性。分布式架构关注于如何在多个网络节点间分配系统组件，以实现高可用性、可扩展性和容错性。</li>
 <li><strong>应用场景</strong>：微内核架构多应用于需要高度模块化和可扩展性的系统设计中，而分布式架构则广泛应用于需要处理大量数据和高并发请求的场景。</li>
</ul>
<p>每种架构风格都有其优势和局限性，选择哪一种架构取决于具体的项目需求、团队的技能以及系统未来的发展方向。</p>
<h2>二、软件的12种架构的优缺点</h2>
<p>概括每种架构风格的优点和缺点可以帮助在选择合适的架构时做出更加明智的决策。以下是上述提及的12种架构风格的优缺点简述：</p>
<h4>1. 单体架构</h4>
<ul>
 <li><strong>优点</strong>：简单易懂；部署方便；开发、测试流程简化。</li>
 <li><strong>缺点</strong>：可扩展性差；随着系统复杂度增加，维护难度升高；技术债务累积快。</li>
</ul>
<h4>2. 分层架构</h4>
<ul>
 <li><strong>优点</strong>：组织清晰，层次分明；促进关注点分离；提高了可维护性。</li>
 <li><strong>缺点</strong>：层与层之间紧密耦合，难以修改；性能损耗。</li>
</ul>
<h4>3. 事件驱动架构</h4>
<ul>
 <li><strong>优点</strong>：高度解耦；易于扩展；提高系统响应性。</li>
 <li><strong>缺点</strong>：事件追踪和调试困难；复杂事件和错误处理。</li>
</ul>
<h4>4. 微服务架构</h4>
<ul>
 <li><strong>优点</strong>：服务独立部署和扩展；技术栈灵活；促进团队自治。</li>
 <li><strong>缺点</strong>：开发和管理复杂度增加；服务间通信成本；数据一致性挑战。</li>
</ul>
<h4>5. 服务导向架构 (SOA)</h4>
<ul>
 <li><strong>优点</strong>：重用性强；灵活性好；易于集成和替换服务组件。</li>
 <li><strong>缺点</strong>：性能开销；复杂度高；管理和治理挑战。</li>
</ul>
<h4>6. 客户端-服务器架构</h4>
<ul>
 <li><strong>优点</strong>：模型简单；易于理解和实施；明确的职责划分。</li>
 <li><strong>缺点</strong>：服务器瓶颈和单点故障问题；可伸缩性挑战。</li>
</ul>
<h4>7. 对等网络架构</h4>
<ul>
 <li><strong>优点</strong>：去中心化，提高容错性；可伸缩性强；资源共享效率高。</li>
 <li><strong>缺点</strong>：管理和安全挑战；性能不可预测；复杂的网络协议。</li>
</ul>
<h4>8. 无服务器架构</h4>
<ul>
 <li><strong>优点</strong>：运维负担低；成本根据使用量而定；自动扩展。</li>
 <li><strong>缺点</strong>：冷启动问题；运行时环境限制；长期成本可能高。</li>
</ul>
<h4>9. CQRS</h4>
<ul>
 <li><strong>优点</strong>：清晰分离读写操作；提高性能和伸缩性；简化复杂业务逻辑。</li>
 <li><strong>缺点</strong>：增加系统复杂度；数据一致性挑战；实现成本高。</li>
</ul>
<h4>10. 清洁架构</h4>
<ul>
 <li><strong>优点</strong>：提高了可维护性和可测试性；关注点分离；独立于框架。</li>
 <li><strong>缺点</strong>：实现复杂；学习曲线陡峭；可能导致过度工程。</li>
</ul>
<h4>11. 微内核架构</h4>
<ul>
 <li><strong>优点</strong>：系统灵活性和可扩展性高；核心逻辑简单，稳定性强。</li>
 <li><strong>缺点</strong>：插件开发和维护成本高；性能开销；通信复杂度。</li>
</ul>
<h4>12. 分布式架构</h4>
<ul>
 <li><strong>优点</strong>：高可用性和可扩展性；容错性好；资源利用率高。</li>
 <li><strong>缺点</strong>：开发和测试复杂；网络延迟和数据一致性问题；运维挑战增加。</li>
</ul>
<p>选择适合的架构风格时，需要</p>
<h2>三、软件的12种架构的实现原理及举例</h2>
<p>了解每种架构风格的实现原理对于选择最适合项目需求的架构至关重要。下面详细解释这些架构的实现原理，并给出相应的例子：</p>
<h4>1. 单体架构</h4>
<ul>
 <li><strong>实现原理</strong>：单体架构将所有功能集成到一个独立的应用程序中，通常由一个数据库支持。它可以是一组紧密耦合的代码模块，通常按功能分层。</li>
 <li><strong>例子</strong>：传统的电商网站，所有功能（产品展示、订单处理、支付、用户管理）都在同一个应用程序中实现。</li>
</ul>
<h4>2. 分层架构</h4>
<ul>
 <li><strong>实现原理</strong>：分层架构将应用程序分为几个层次，每一层只与其上下直接相邻的层次通信。典型的分层包括表示层、业务逻辑层、持久层等。</li>
 <li><strong>例子</strong>：Web应用程序，其中前端作为表示层，后端服务作为业务逻辑层，数据库作为持久层。</li>
</ul>
<h4>3. 事件驱动架构</h4>
<ul>
 <li><strong>实现原理</strong>：在事件驱动架构中，组件通过事件进行通信。事件生产者发布事件，而事件消费者订阅并响应这些事件。这种架构允许高度解耦和动态扩展。</li>
 <li><strong>例子</strong>：实时股票交易系统，其中股票价格更新作为事件发布，交易算法作为事件的消费者。</li>
</ul>
<h4>4. 微服务架构</h4>
<ul>
 <li><strong>实现原理</strong>：将复杂应用程序分解为一组小的、独立的服务，每个服务实现特定的功能，并通过轻量级协议（通常是HTTP REST）通信。</li>
 <li><strong>例子</strong>：视频流平台，每个服务负责一个独立功能，如用户认证、视频编码、推荐等。</li>
</ul>
<h4>5. 服务导向架构 (SOA)</h4>
<ul>
 <li><strong>实现原理</strong>：在SOA中，功能被封装为独立的、可重用的服务，服务之间通过网络调用进行通信，通常使用SOAP或REST协议。</li>
 <li><strong>例子</strong>：企业级应用，如ERP系统，其中不同的模块（人力资源、财务、库存）作为独立的服务实现。</li>
</ul>
<h4>6. 客户端-服务器架构</h4>
<ul>
 <li><strong>实现原理</strong>：客户端发送请求到服务器，服务器处理请求并返回响应。这种架构通常涉及到一个服务器和多个客户端。</li>
 <li><strong>例子</strong>：电子邮件系统，客户端软件用于读写邮件，服务器负责邮件的存储和转发。</li>
</ul>
<h4>7. 对等网络架构</h4>
<ul>
 <li><strong>实现原理</strong>：在对等网络中，每个节点既充当客户端又充当服务器，共享资源而无需中心协调器。</li>
 <li><strong>例子</strong>：BitTorrent文件共享系统，用户分享文件的片段，同时下载其他用户的文件片段。</li>
</ul>
<h4>8. 无服务器架构</h4>
<ul>
 <li><strong>实现原理</strong>：开发者编写的函数在完全管理的环境中运行，无需考虑服务器。云提供商根据需求动态分配资源。</li>
 <li><strong>例子</strong>：网站访问计数器，每次网站被访问时触发一个函数来增加计数器，无需持续运行的服务器。</li>
</ul>
<h4>9. CQRS</h4>
<ul>
 <li><strong>实现原理</strong>：将应用程序的读操作（查询）和写操作（命令）分离，可以独立地优化查询和更新。</li>
 <li><strong>例子</strong>：电子商务网站，其中商品浏览（查询）和商品购买（命令）通过不同的</li>
 <li>接着上文，继续解释剩余的三种架构风格的实现原理及举例：</li>
</ul>
<h4>10. 清洁架构 (Clean Architecture)</h4>
<ul>
 <li><strong>实现原理</strong>：清洁架构将系统分为多个层，每一层只依赖于更内层的策略和概念。它强调的是使用依赖倒置原则来分离关注点，使得业务逻辑与外部因素（如数据库和框架）解耦。</li>
 <li><strong>例子</strong>：在线银行应用，其中核心业务逻辑（如转账和余额查询）独立于外部系统（如数据库和Web服务）。这样，即使更换了数据库或更改了用户界面技术，核心业务逻辑也无需改动。</li>
</ul>
<h4>11. 微内核架构 (Microkernel Architecture)</h4>
<ul>
 <li><strong>实现原理</strong>：微内核架构中，系统的核心功能（微内核）提供最基础的操作，而其他功能（如用户界面、设备驱动）作为应用程序或插件运行在微内核之上。这种架构使得系统易于扩展和维护，因为可以独立于核心系统添加或更新功能。</li>
 <li><strong>例子</strong>：操作系统，如 macOS 和 Windows，它们具有处理基础设备输入输出、内存管理等核心功能的微内核，同时支持用户通过安装软件和驱动来扩展功能。</li>
</ul>
<h4>12. 分布式架构 (Distributed Architecture)</h4>
<ul>
 <li><strong>实现原理</strong>：分布式架构通过在网络上分布计算和存储任务到多个物理或虚拟节点来工作，利用这些节点之间的通信和协作来实现系统的整体功能。这种架构的关键是高可用性、可扩展性和容错能力。</li>
 <li><strong>例子</strong>：云计算平台，如 Amazon Web Services (AWS) 或 Google Cloud Platform (GCP)，它们通过在全球多个数据中心分布资源，为用户提供计算、存储和网络服务。这些服务利用分布式架构的优势，确保了高效率和弹性。</li>
</ul>
<p>这12种架构风格展示了软件设计的多样性，每种风格都有其适用场景和特定的优势。在选择架构时，重要的是要根据项目的具体需求、团队的能力、预期的系统规模和性能要求来决定最适合的架构模式。</p>
<p>原文链接: https://blog.csdn.net/weixin_46453070/article/details/137043357</p>]]></description><guid isPermaLink="false">/archives/bcbcc775-9bca-4ef7-9308-25952f3f3bd9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Mon, 30 Sep 2024 06:43:10 GMT</pubDate></item><item><title><![CDATA[宝塔面板安装教程（2024年更新版）]]></title><link>https://wiki.dgfygk.com/archives/9113a07d-da3c-4df2-8c87-dc1d6f7e89a0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B%EF%BC%882024%E5%B9%B4%E6%9B%B4%E6%96%B0%E7%89%88%EF%BC%89&amp;url=/archives/9113a07d-da3c-4df2-8c87-dc1d6f7e89a0" width="1" height="1" alt="" style="opacity:0;">
<p>随着互联网的飞速发展，网站的建设与维护已经成为许多企业和个人的必备技能。而宝塔面板作为一款功能强大、操作简便的服务器运维软件，深受广大用户的喜爱。本教程将详细介绍如何在自己的VPS上安装<a href="https://www.buildwebsite.top/index.php/2024/04/25/bt-install-2024/">宝塔面板</a>，并帮助用户轻松搭建网站。</p>
<p>一、安装前准备</p>
<p>在安装宝塔面板之前，我们需要做好以下准备工作：</p>
<p>购买并设置VPS
 <br>
 首先，我们需要购买一台VPS服务器。在选择VPS时，建议考虑服务器的性能、稳定性以及价格等因素。购买完成后，我们需要根据服务商的指引进行VPS的基本设置，如设置密码、安装操作系统等。</p>
<p>下载Xshell软件
 <br>
 为了方便管理VPS，我们需要下载并安装Xshell软件。Xshell是一款强大的终端模拟软件，支持SSH连接，可以让我们通过命令行的方式远程操作VPS。</p>
<p>二、安装宝塔面板</p>
<p>接下来，我们将开始安装宝塔面板。请按照以下步骤进行操作：</p>
<p>打开Xshell并连接VPS
 <br>
 打开Xshell软件，点击"新建"按钮创建一个新的连接配置。在配置中填写VPS的IP地址、端口号（默认为22）、用户名和密码等信息。然后点击"连接"按钮，等待连接成功。</p>
<p>登录VPS
 <br>
 连接成功后，我们将看到命令行界面。此时，我们需要输入VPS的用户名和密码进行登录。通常情况下，用户名默认为"root”。</p>
<p>复制并粘贴安装命令
 <br>
 登录成功后，我们需要复制<a href="https://www.buildwebsite.top/index.php/2024/04/25/bt-install-2024/">宝塔面板</a>的安装命令并粘贴到命令行中执行。请根据您的VPS操作系统类型选择相应的命令进行复制和粘贴：</p>
<p>对于CentOS系统：</p>
<pre><code class="prism language-bash">yum install -y wget &amp;&amp; wget -O install.sh http://download.bt.cn/install/install_6.0.sh &amp;&amp; bash install.sh
</code></pre>
<p>对于Ubuntu/Deepin系统：</p>
<pre><code class="prism language-bash">wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh &amp;&amp; sudo bash install.sh
</code></pre>
<p>对于Debian系统：</p>
<pre><code class="prism language-bash">wget -O install.sh http://download.bt.cn/install/install-debian_6.0.sh &amp;&amp; sudo bash install.sh
</code></pre>
<p>粘贴命令后，按回车键执行。在执行过程中，您可能需要输入"yes"以确认安装，或者根据提示输入其他相关信息。</p>
<p>等待安装完成
 <br>
 安装过程可能需要一些时间，具体时间取决于您的VPS性能和网络速度。请耐心等待安装完成。在安装过程中，您可以看到命令行界面上的安装进度提示。</p>
<p>获取登录信息
 <br>
 安装完成后，您将看到宝塔面板的登录地址、账号和密码等信息。请务必将这些信息保存好，以便日后登录管理。</p>
<p>三、登录宝塔面板并配置环境</p>
<p>打开浏览器登录宝塔面板
 <br>
 使用保存的登录地址在浏览器中打开宝塔面板的登录页面。输入之前获取的账号和密码进行登录。</p>
<p>配置服务器环境
 <br>
 登录成功后，您将看到宝塔面板的管理界面。在界面中，您可以进行各种服务器配置和管理操作。首先，我们需要配置网站环境。</p>
<p>宝塔面板提供了丰富的环境配置选项，包括Web服务器、数据库、FTP服务器等。根据您的需求选择相应的环境进行配置。通常情况下，我们可以选择安装Nginx作为Web服务器，MySQL或MariaDB作为数据库，以及安装FTP服务器以方便文件上传和下载。</p>
<p>在配置过程中，您需要根据提示进行相应的设置，如设置数据库的用户名和密码、指定FTP服务器的端口号等。请确保按照您的实际需求进行配置，并妥善保存相关配置信息。</p>
<p>添加网站并绑定域名
 <br>
 配置好环境后，我们可以开始添加网站并绑定域名。在宝塔面板中，选择"网站"菜单，然后点击"添加网站"按钮。在弹出的窗口中，填写网站的域名、根目录等信息，并点击"提交"按钮。</p>
<p>如果您还没有购买域名，可以先使用临时域名进行测试。在添加网站后，您可以在宝塔面板中管理网站的各种设置，如修改网站配置、上传文件、安装网站程序等。</p>
<p>四、注意事项</p>
<p>在安装和配置宝塔面板的过程中，有几点需要注意的事项：</p>
<p>防火墙设置
 <br>
 如果您的VPS使用的是腾讯云、阿里云或华为云等云服务提供商，请确保在云服务商的管理后台中开放了宝塔面板所需的端口。这样可以确保您能够正常访问和管理宝塔面板。</p>
<p>安全设置
 <br>
 在安装和配置过程中，请务必注意安全问题。建议定期更新宝<a href="https://www.buildwebsite.top/index.php/2024/04/25/bt-install-2024/">塔面板和服务器</a>上的软件版本，以修复潜在的安全漏洞。同时，设置复杂的密码并定期更换密码也是保障安全的重要措施。</p>
<p>原文链接: https://blog.csdn.net/Y1258429182/article/details/138198582</p>]]></description><guid isPermaLink="false">/archives/9113a07d-da3c-4df2-8c87-dc1d6f7e89a0</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Mon, 30 Sep 2024 06:42:40 GMT</pubDate></item><item><title><![CDATA[Spring MVC返回数据 总结]]></title><link>https://wiki.dgfygk.com/archives/a05255cf-2a22-413c-8536-1d2353838643</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20MVC%E8%BF%94%E5%9B%9E%E6%95%B0%E6%8D%AE%20%E6%80%BB%E7%BB%93&amp;url=/archives/a05255cf-2a22-413c-8536-1d2353838643" width="1" height="1" alt="" style="opacity:0;">
<ol>
 <li>
  <h2>简介</h2>
 </li>
</ol>
<p>Spring MVC提供了返回数据的简化支持。</p>
<ol>
 <li>
  <h2>返回JSON</h2>
 </li>
</ol>
<p>返回JSON数据给前端步骤如下：
 <br>
 pom.xml中导入json依赖：</p>
<pre><code class="prism language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-core&lt;/artifactId&gt;
    &lt;version&gt;2.15.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>配置类中加入@EnableWebMvc注解：</p>
<pre><code class="prism language-java">...
@EnableWebMvc
public class MVCConfig {

...
}
</code></pre>
<p>编写业务类：</p>
<pre><code class="prism language-java">@GetMapping("data")
@ResponseBody//直接返回给前端
public Product data(){

    Product product = new Product();
    product.setName("good1");
    product.setId(1);
    return product;//返回product类直接被转成json
}

@GetMapping("datas")
@ResponseBody//直接返回给前端
public List&lt;Product&gt; detail(){

    Product product = new Product();
    product.setName("good2");
    product.setId(2);

    List&lt;Product&gt; products = new ArrayList&lt;&gt;();
    products.add(product);
    return products;//返回products集合类直接被转成json的数组形式
}
</code></pre>
<p>
 <br>
</p>
<ol>
 <li>
  <h2>返回静态资源</h2>
 </li>
</ol>
<p>1.将图片加入webapp/imgs下(别放到WEB-INF里就行)
 <br>
 2.将html静态页面加入webapp/pages下(别放到WEB-INF里就行)
 <br>
 3.在配置类中加入：</p>
<pre><code class="prism language-java">@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {

    configurer.enable();//静态资源查找使能
}
</code></pre>
<p>4.访问: http://localhost:8080/pages/1.html
 <br>
 5.访问: http://localhost:8080/imgs/1.png</p>
<p>原文链接: https://blog.csdn.net/a827143452/article/details/142469383</p>]]></description><guid isPermaLink="false">/archives/a05255cf-2a22-413c-8536-1d2353838643</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:42:27 GMT</pubDate></item><item><title><![CDATA[【网站架构部署与优化】Tomcat优化、Tomcat多实例部署]]></title><link>https://wiki.dgfygk.com/archives/e941f73c-7135-4b8a-9779-78fabb18c4a2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%BD%91%E7%AB%99%E6%9E%B6%E6%9E%84%E9%83%A8%E7%BD%B2%E4%B8%8E%E4%BC%98%E5%8C%96%E3%80%91Tomcat%E4%BC%98%E5%8C%96%E3%80%81Tomcat%E5%A4%9A%E5%AE%9E%E4%BE%8B%E9%83%A8%E7%BD%B2&amp;url=/archives/e941f73c-7135-4b8a-9779-78fabb18c4a2" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#Tomcat_1">Tomcat优化</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#Tomcat__3">Tomcat 配置文件参数优化</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_17">示例配置</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#Tomcat_JVM__60">Tomcat JVM 参数配置</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_73">参数解释</a></li>
   <li><a href="https://wiki.dgfygk.com#_97">注意</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_101">总结</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#Tomcat__serverxml_106">Tomcat 配置文件参数优化 (`server.xml`)</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#1__108">1. 线程池参数</a></li>
   <li><a href="https://wiki.dgfygk.com#2__112">2. 网络连接参数</a></li>
   <li><a href="https://wiki.dgfygk.com#3__116">3. 性能优化参数</a></li>
   <li><a href="https://wiki.dgfygk.com#Java_JVM_121">Java 虚拟机（JVM）调优</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#1__123">1. 堆内存设置</a></li>
   <li><a href="https://wiki.dgfygk.com#2__126">2. 新生代和老年代设置</a></li>
   <li><a href="https://wiki.dgfygk.com#3__130">3. 垃圾收集器设置</a></li>
   <li><a href="https://wiki.dgfygk.com#4__134">4. 其他性能优化参数</a></li>
   <li><a href="https://wiki.dgfygk.com#_138">常见错误及解决方法</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#Tomcat_149">Tomcat多实例部署</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_JDKTomcat_151">1. 安装JDK和Tomcat</a></li>
 <li><a href="https://wiki.dgfygk.com#2_Tomcat_163">2. 配置Tomcat环境变量</a></li>
 <li><a href="https://wiki.dgfygk.com#3_Tomcat_183">3. 修改Tomcat配置文件</a></li>
 <li><a href="https://wiki.dgfygk.com#4__194">4. 更新启动和停止脚本</a></li>
 <li><a href="https://wiki.dgfygk.com#5_Tomcat_219">5. 启动Tomcat实例</a></li>
 <li><a href="https://wiki.dgfygk.com#6__227">6. 浏览器访问测试</a></li>
</ul>
<p>
 <br>
</p>
<h2>Tomcat优化</h2>
<p>高并发、资源利用效率和稳定性等因素。为了提升 Tomcat 的运行效率和稳定性，我们可以从操作系统、Tomcat 配置文件以及 Java 虚拟机（JVM）三个方面进行优化。</p>
<h3>Tomcat 配置文件参数优化</h3>
<p>Tomcat 的配置文件主要是 <code>conf/server.xml</code>，其中 <code>&lt;Connector&gt;</code> 标签是优化的重点之一。以下是一些常用的优化参数及其解释：</p>
<ul>
 <li><strong><code>redirectPort</code></strong>：当 HTTP 连接器接收到 HTTPS 请求时，重定向到的端口。</li>
 <li><strong><code>maxThreads</code></strong>：Tomcat 可创建的最大线程数，即支持的最大并发连接数。默认值可能较低，根据服务器硬件和负载情况调整。</li>
 <li><strong><code>minSpareThreads</code></strong>：Tomcat 启动时初始化的线程数，即最小空闲线程数。</li>
 <li><strong><code>maxSpareThreads</code></strong>：最大备用线程数，一般不需要设置，默认为-1（无限制）。</li>
 <li><strong><code>processorCache</code></strong> ：线程池中的线程在空闲时会被缓存起来，以便快速响应新的请求。这个值可以根据 <code>maxThreads</code> 设定或设为-1。</li>
 <li><strong><code>URIEncoding</code></strong>：Tomcat 容器的 URL 编码格式，通常设置为 UTF-8。</li>
 <li><strong><code>connectionTimeout</code></strong>：网络连接超时时间，单位毫秒。</li>
 <li><strong><code>enableLookups</code></strong> ：是否反查域名。为了提高性能，应设置为 <code>false</code>。</li>
 <li><strong><code>disableUploadTimeout</code></strong> 和 <strong><code>connectionUploadTimeout</code></strong>：与上传文件相关的超时设置。</li>
 <li><strong><code>acceptCount</code></strong>：当所有处理请求的线程都在使用时，可接受的最大请求队列长度。</li>
 <li><strong><code>maxKeepAliveRequests</code></strong>：长连接的最大请求数。</li>
 <li><strong><code>compression</code></strong> 、<strong><code>compressionMinSize</code></strong> 、<strong><code>compressableMimeType</code></strong>：控制是否对响应进行 GZIP 压缩及其相关设置。</li>
</ul>
<h4>示例配置</h4>
<p>以下是一个示例配置，展示了如何在 <code>server.xml</code> 文件中调整 <code>&lt;Connector&gt;</code> 的参数：</p>
<pre><code class="prism language-bash">vim /usr/local/tomcat/conf/server.xml
</code></pre>
<pre><code>```prism language-xml
&lt;Connector port="8080" protocol="HTTP/1.1" 
           connectionTimeout="20000" 
           redirectPort="8443"
           &lt;!-- 以下配置项在第71行之后插入 --&gt;
           minSpareThreads="50" 
           &lt;!-- 设置Tomcat启动时创建的最小空闲线程数，确保始终有一定数量的线程准备处理请求 --&gt;
           enableLookups="false" 
           &lt;!-- 禁用DNS查询，提高处理请求的速度 --&gt;
           disableUploadTimeout="true" 
           &lt;!-- 禁用上传超时，允许长时间上传文件 --&gt;
           acceptCount="300" 
           &lt;!-- 设置当所有线程都在处理请求时，允许的最大连接队列长度 --&gt;
           maxThreads="500" 
           &lt;!-- 设置Tomcat能够创建的最大线程数，即最大并发处理能力 --&gt;
           processorCache="500"
           &lt;!-- 设置处理器缓存大小，用于提升并发请求处理能力 --&gt;
           URIEncoding="UTF-8" 
           &lt;!-- 设置URL编码格式为UTF-8，确保正确处理各种字符集 --&gt;
           maxKeepAliveRequests="100"
           &lt;!-- 设置一个长连接上允许的最大请求数，超过此数将关闭连接 --&gt;
           compression="on" 
           &lt;!-- 启用响应数据的GZIP压缩，减少传输数据量 --&gt;
           compressionMinSize="2048" 
           &lt;!-- 设置响应报文的最小压缩大小，只有大于此值的报文才会被压缩 --&gt;
           compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/gif,image/jpg,image/png"
           &lt;!-- 指定可以进行压缩的MIME类型 --&gt;
/&gt;
</code></pre>
<p><strong>查看 Tomcat 线程：</strong>
 <br>
 在 Linux 系统中，你可以使用 <code>ps</code> 命令结合 <code>-T</code> 和 <code>-p</code> 选项来查看指定 Tomcat 进程的线程信息：</p>
<pre><code class="prism language-bash">ps -T -p &lt;PID&gt;
</code></pre>
<p>这里 <code>&lt;PID&gt;</code> 是 Tomcat 进程的 ID。这个命令会列出该进程下所有的线程及其相关信息。
 <br>
 你的配置和解释已经很详细了，但有一些地方需要根据现代JVM（特别是从Java 8开始）的实际情况进行调整和澄清。下面是对你原始配置和说明的详细整理及更新：</p>
<h3>Tomcat JVM 参数配置</h3>
<p>对于2C4G（2核CPU，4GB内存）的服务器环境，以下是一个推荐的JVM配置示例，适用于Tomcat运行在Java 8或更高版本上：</p>
<pre><code class="prism language-bash">vim /usr/local/tomcat/bin/catalina.sh
</code></pre>
<p>配置添加在 Tomcat 的 bin 目录下 catalina.sh 里，位置在 cygwin=false 前。</p>
<pre><code class="prism language-bash"># 在 catalina.sh 中设置 JAVA_OPTS
JAVA_OPTS="$JAVA_OPTS -server -Xms2048m -Xmx2048m -Xmn768m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/tomcat/temp/oom.hprof -XX:ParallelGCThreads=2 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Djava.awt.headless=true -XX:+DisableExplicitGC"

# 确保这行在 cygwin=false 之前
cygwin=false
</code></pre>
<h4>参数解释</h4>
<ul>
 <li><strong>-server</strong>：启用Server模式的JVM，适用于长时间运行的服务端应用。一定要作为第一个参数，在多个CPU时性能佳。</li>
 <li><strong>-Xms</strong>：堆内存的初始大小，是分配JVM的初始内存，默认为物理内存的1/64。一般来讲，此值设的大点，程序会启动的快一点。</li>
 <li><strong>-Xmx</strong>：堆内存的最大大小，是分配JVM的最大内存，默认为物理内存的1/4。如果程序运行需要占用更多的内存，超出了这个设置值，就会抛出OutOfMemory异常。</li>
</ul>
<blockquote>
 <ul>
  <li>默认空余堆内存小于40%时，JVM就会增大堆直到-Xmx的最大限制；空余堆内存大于70%时，JVM会减少堆直到 -Xms的最小限制。</li>
  <li>因此建议-Xms与-Xmx设成一样的值，均设为物理内存的一半。其目的是为了能够在java垃圾回收机制清理完堆区内存后不需要重新计算堆区内存的大小而浪费资源。</li>
 </ul>
</blockquote>
<ul>
 <li><strong>-Xmn</strong>：堆内新生代的大小，通过这个值也可以得到老生代的大小：-Xmx减去-Xmn。官方推荐配置为整个堆的 3/8。</li>
</ul>
<blockquote>
 <ul>
  <li>堆区进一步细化分为：新生代、中生代、老生代。</li>
  <li>java中每新new一个对象所占用的内存空间就是新生代的空间，当java垃圾回收机制对堆区进行资源回收后，那些新生代中没有被回收的资源将被转移到中生代，中生代的被转移到老生代。</li>
  <li>整个JVM堆大小 = 新生代大小 + 老生代大小 + 永久代大小</li>
 </ul>
</blockquote>
<ul>
 <li><strong>-Xss</strong>：设置每个线程可使用的内存大小，即栈的大小。一般情况下，设置256k就足够了，此配置将会影响此进程中并发线程数的大小。</li>
 <li><strong>-XX:ParallelGCThreads</strong> ：配置并行收集器的线程数，即：同时有多少个线程一起进行垃圾回收。当 CPU 数量小于8，此值建议配置等于 CPU 数量。
  <br>
  -<strong>XX:PermSize</strong> ：设置非堆内存初始值，即持久代内存大小，默认是物理内存的1/4
  <br>
  -<strong>XX:MaxPermSize</strong>：最大非堆内存的大小，即最大持久代内存大小，默认是物理内存的1/4</li>
</ul>
<blockquote>
 <ul>
  <li>非堆区内存是不会被java垃圾回收机制进行处理的，且最大堆内存与最大非堆内存的和不能超出操作系统的可用内存。</li>
  <li>XMX和XMS设置一样大，MaxPermSize和PermSize设置一样大，这样可以减轻伸缩堆大小带来的压力。</li>
 </ul>
</blockquote>
<ul>
 <li><strong>-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath</strong>：当发生内存溢出时，自动导出堆转储文件，有助于分析内存泄漏问题。</li>
 <li><strong>-XX:+UseG1GC</strong>：从Java 7 Update 4及更高版本开始，推荐使用G1垃圾收集器（Garbage-First）。它比CMS收集器（在Java 9中被标记为废弃）提供了更好的性能和更短的停顿时间。</li>
 <li><strong>-XX:MaxGCPauseMillis</strong>：尝试控制垃圾收集的最大停顿时间（以毫秒为单位）。注意，这是一个目标值，JVM会尽力实现，但不保证总是能达到。</li>
 <li><strong>-Djava.awt.headless=true</strong>：对于不需要图形用户界面的服务器环境，这个设置可以避免AWT尝试加载图形库。</li>
 <li><strong>-XX:+DisableExplicitGC</strong> ：禁用显式垃圾收集调用，防止<code>System.gc()</code>被误调用，这可能导致不必要的性能下降。</li>
</ul>
<h4>注意</h4>
<ul>
 <li><strong>永久代（PermGen）</strong> ：从Java 8开始，永久代（PermGen space）被元空间（Metaspace）所取代。因此，<code>-XX:PermSize</code> 和 <code>-XX:MaxPermSize</code> 参数不再使用，取而代之的是 <code>-XX:MetaspaceSize</code> 和 <code>-XX:MaxMetaspaceSize</code>。对于大多数应用来说，默认设置（元空间大小根据需要动态增长）就足够了，除非你有特殊需求。</li>
 <li><strong>CMS收集器</strong>：在Java 9及更高版本中，CMS收集器已被标记为废弃，并在Java 14中被完全移除。因此，如果你计划使用较新版本的Java，请考虑使用G1或其他收集器。</li>
 <li><strong>内存分配</strong>：确保JVM的内存设置不会超出物理内存的限制，否则可能会导致系统交换（swapping），这会严重影响性能。</li>
</ul>
<h3>总结</h3>
<p><a href="https://wiki.dgfygk.com/../../_resources/tomcat%E4%BC%98%E5%8C%96.docx">tomcat优化.docx</a></p>
<h4>Tomcat 配置文件参数优化 (<code>server.xml</code>)</h4>
<p>在 Tomcat 的主配置文件 <code>server.xml</code> 中，可以调整多个参数以优化性能。以下是一些常用的优化参数及其说明：</p>
<h5>1. 线程池参数</h5>
<ul>
 <li><strong>maxThreads</strong>: 最大线程数，默认值为 200。根据服务器负载和业务需求调整。</li>
 <li><strong>minSpareThreads</strong>: 最小空闲线程数，默认值为 10。可根据实际情况调整以减少创建新线程的开销。</li>
 <li><strong>maxSpareThreads</strong>: 最大备用线程数，默认值为 -1（无限制）。一般不需要设置，除非有特殊需求。</li>
</ul>
<h5>2. 网络连接参数</h5>
<ul>
 <li><strong>URIEncoding</strong> : 指定 Tomcat 容器的 URL 编码格式，如 <code>UTF-8</code>。</li>
 <li><strong>connectionTimeout</strong>: 网络连接超时时间，单位毫秒，默认值为 20000 毫秒。</li>
 <li><strong>acceptCount</strong>: 当所有请求处理线程都忙时，可接受的连接队列长度，默认值为 100。</li>
</ul>
<h5>3. 性能优化参数</h5>
<ul>
 <li><strong>enableLookups</strong> : 是否通过反查域名来获取主机名，建议设置为 <code>false</code> 以提高性能。</li>
 <li><strong>compression</strong> : 是否对响应数据进行 GZIP 压缩，建议设置为 <code>on</code> 或 <code>force</code> 以减少传输数据量。</li>
 <li><strong>compressionMinSize</strong>: 压缩响应的最小值，默认为 2048 字节。</li>
 <li><strong>compressableMimeType</strong>: 指定需要压缩的 MIME 类型。</li>
</ul>
<h4>Java 虚拟机（JVM）调优</h4>
<p>Tomcat 作为 Java 程序，运行在 JVM 上，因此 JVM 的调优对 Tomcat 性能至关重要。以下是一些常用的 JVM 启动参数：</p>
<h5>1. 堆内存设置</h5>
<ul>
 <li><strong>-Xms</strong> : 设置 JVM 初始化堆内存大小，建议与 <code>-Xmx</code> 设置相同值以避免内存波动。</li>
 <li><strong>-Xmx</strong>: 设置 JVM 最大堆内存大小，建议设置为物理内存的 50% 左右。</li>
</ul>
<h5>2. 新生代和老年代设置</h5>
<ul>
 <li><strong>-XX:NewSize</strong> 和 <strong>-XX:MaxNewSize</strong>: 分别设置新生代初始大小和最大大小。</li>
 <li><strong>-Xmn</strong> : 新生代大小（Eden + 2*Survivor），可替代 <code>-XX:NewSize</code> 和 <code>-XX:MaxNewSize</code>。</li>
 <li><strong>-XX:SurvivorRatio</strong>: Eden 区与 Survivor 区的比例。</li>
</ul>
<h5>3. 垃圾收集器设置</h5>
<ul>
 <li><strong>-XX:+UseSerialGC</strong>: 使用串行垃圾收集器。</li>
 <li><strong>-XX:+UseParallelGC</strong> 和 <strong>-XX:+UseParallelOldGC</strong>: 使用并行垃圾收集器，分别针对新生代和老年代。</li>
 <li><strong>-XX:+UseConcMarkSweepGC</strong> 和相关参数: 使用 CMS 垃圾收集器，适用于对停顿时间敏感的应用。</li>
</ul>
<h5>4. 其他性能优化参数</h5>
<ul>
 <li><strong>-XX:+UseBiasedLocking</strong>: 启用偏向锁优化。</li>
 <li><strong>-XX:+DisableExplicitGC</strong> : 禁止程序中显式调用 <code>System.gc()</code>。</li>
 <li><strong>-XX:+UseCMSInitiatingOccupancyOnly</strong> 和 <strong>-XX:CMSInitiatingOccupancyFraction</strong>: 控制 CMS 垃圾收集器的触发时机。</li>
</ul>
<h4>常见错误及解决方法</h4>
<ol>
 <li><strong>Java Heap Space 溢出</strong></li>
 <li><strong>错误</strong> : <code>java.lang.OutOfMemoryError: Java heap space</code></li>
 <li><strong>解决方法</strong> : 调整 <code>-Xms</code> 和 <code>-Xmx</code> 的值，增加 JVM 堆内存大小。</li>
 <li><strong>PermGen Space 溢出</strong></li>
 <li><strong>错误</strong> : <code>java.lang.OutOfMemoryError: PermGen space</code></li>
 <li><strong>解决方法</strong> : 在 JDK 8 之前，使用 <code>-XX:MaxPermSize</code> 设置永久代大小；JDK 8 及以上版本已移除永久代，使用元空间（Metaspace）。</li>
 <li><strong>StackOverflowError</strong></li>
 <li><strong>错误</strong> : <code>java.lang.StackOverflowError</code></li>
 <li><strong>解决方法</strong>: 检查并修改导致栈溢出的代码，如减少递归深度、优化函数调用等。</li>
</ol>
<h2>Tomcat多实例部署</h2>
<p>部署多个Tomcat实例可以在同一台服务器上运行多个独立的Web应用，提高资源利用率和系统灵活性。</p>
<h3>1. 安装JDK和Tomcat</h3>
<p>首先，确保已安装JDK。然后下载并解压Tomcat到指定目录，并创建两个Tomcat实例的目录结构。</p>
<pre><code class="prism language-bash">cd /opt
tar zxvf apache-tomcat-9.0.16.tar.gz
mkdir /usr/local/tomcat
mv apache-tomcat-9.0.16 /usr/local/tomcat/tomcat1
cp -a /usr/local/tomcat/tomcat1 /usr/local/tomcat/tomcat2
</code></pre>
<h3>2. 配置Tomcat环境变量</h3>
<p>编辑<code>/etc/profile.d/tomcat.sh</code>文件，为每个Tomcat实例设置独立的环境变量。</p>
<pre><code class="prism language-bash">vim /etc/profile.d/tomcat.sh
# tomcat1
export CATALINA_HOME1=/usr/local/tomcat/tomcat1
export CATALINA_BASE1=/usr/local/tomcat/tomcat1
export TOMCAT_HOME1=/usr/local/tomcat/tomcat1

# tomcat2
export CATALINA_HOME2=/usr/local/tomcat/tomcat2
export CATALINA_BASE2=/usr/local/tomcat/tomcat2
export TOMCAT_HOME2=/usr/local/tomcat/tomcat2
</code></pre>
<p>使环境变量生效：</p>
<pre><code class="prism language-bash">source /etc/profile.d/tomcat.sh
</code></pre>
<h3>3. 修改Tomcat配置文件</h3>
<p>编辑第二个Tomcat实例的<code>server.xml</code>文件，确保各实例的端口号不冲突。</p>
<pre><code class="prism language-bash">vim /usr/local/tomcat/tomcat2/conf/server.xml
# 修改Server端口
&lt;Server port="8006" shutdown="SHUTDOWN"&gt;
# 修改HTTP Connector端口
&lt;Connector port="8081" protocol="HTTP/1.1"&gt;
# 修改AJP Connector端口
&lt;Connector port="8010" protocol="AJP/1.3" redirectPort="8443" /&gt;
</code></pre>
<h3>4. 更新启动和停止脚本</h3>
<p>编辑每个Tomcat实例的<code>startup.sh</code>和<code>shutdown.sh</code>文件，添加相应的环境变量。</p>
<pre><code class="prism language-bash"># 对于tomcat1
vim /usr/local/tomcat/tomcat1/bin/startup.sh
export CATALINA_BASE=$CATALINA_BASE1
export CATALINA_HOME=$CATALINA_HOME1
export TOMCAT_HOME=$TOMCAT_HOME1

vim /usr/local/tomcat/tomcat1/bin/shutdown.sh
export CATALINA_BASE=$CATALINA_BASE1
export CATALINA_HOME=$CATALINA_HOME1
export TOMCAT_HOME=$TOMCAT_HOME1

# 对于tomcat2
vim /usr/local/tomcat/tomcat2/bin/startup.sh
export CATALINA_BASE=$CATALINA_BASE2
export CATALINA_HOME=$CATALINA_HOME2
export TOMCAT_HOME=$TOMCAT_HOME2

vim /usr/local/tomcat/tomcat2/bin/shutdown.sh
export CATALINA_BASE=$CATALINA_BASE2
export CATALINA_HOME=$CATALINA_HOME2
export TOMCAT_HOME=$TOMCAT_HOME2
</code></pre>
<h3>5. 启动Tomcat实例</h3>
<p>分别启动两个Tomcat实例，并检查端口监听情况。</p>
<pre><code class="prism language-bash">/usr/local/tomcat/tomcat1/bin/startup.sh
/usr/local/tomcat/tomcat2/bin/startup.sh

netstat -natp | grep java
</code></pre>
<h3>6. 浏览器访问测试</h3>
<p>通过浏览器访问两个Tomcat实例，验证是否正常运行。</p>
<pre><code class="prism language-plaintext">http://192.168.80.101:8080
http://192.168.80.101:8081
</code></pre>
<p><strong>注意</strong></p>
<ul>
 <li>确保每个Tomcat实例的端口号唯一，避免冲突。</li>
 <li>在修改配置文件和脚本时，务必小心谨慎，防止误操作导致服务中断。</li>
 <li>定期检查和更新Tomcat实例及其依赖库，确保系统安全性和稳定性。</li>
</ul>
<p>原文链接: https://blog.csdn.net/Karoku/article/details/142564255</p>]]></description><guid isPermaLink="false">/archives/e941f73c-7135-4b8a-9779-78fabb18c4a2</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:42:13 GMT</pubDate></item><item><title><![CDATA[Spring AOP异步操作实现]]></title><link>https://wiki.dgfygk.com/archives/37572907-eec3-4abc-8b32-52040da32e6f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20AOP%E5%BC%82%E6%AD%A5%E6%93%8D%E4%BD%9C%E5%AE%9E%E7%8E%B0&amp;url=/archives/37572907-eec3-4abc-8b32-52040da32e6f" width="1" height="1" alt="" style="opacity:0;">
<p>在Spring框架中，AOP（面向切面编程）提供了一种非常灵活的方式来增强应用程序的功能。异步操作是现代应用程序中常见的需求，尤其是在处理耗时任务时，它可以帮助我们提高应用程序的响应性和吞吐量。Spring提供了一种简单的方式来实现异步操作，通过使用@Async注解和Spring AOP。</p>
<ol>
 <li>
  <h2>引入依赖</h2>
 </li>
</ol>
<p>首先，确保你的项目中引入了Spring AOP和异步支持的依赖。如果你使用的是Maven，可以在pom.xml文件中添加以下依赖：</p>
<pre><code>xml

&lt;dependencies&gt;
    &lt;!-- Spring AOP --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-aop&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!-- Spring Boot异步支持 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<ol>
 <li>
  <h2>开启异步支持</h2>
 </li>
</ol>
<p>在你的Spring Boot应用的主类上添加@EnableAsync注解，以开启异步支持：</p>
<pre><code>java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }
}
</code></pre>
<ol>
 <li>
  <h2>创建异步方法</h2>
 </li>
</ol>
<p>定义一个服务类，并在需要异步执行的方法上添加@Async注解：</p>
<pre><code>java

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {

    @Async
    public void executeAsyncTask() {
        // 这里是耗时任务的逻辑
        System.out.println("执行异步任务");
    }
}
</code></pre>
<ol>
 <li>
  <h2>调用异步方法</h2>
 </li>
</ol>
<p>在你的控制器或其他服务类中调用这个异步方法：</p>
<pre><code>java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public String executeAsync() {
        asyncService.executeAsyncTask();
        return "异步任务已提交";
    }
}
</code></pre>
<ol>
 <li>
  <h2>配置线程池</h2>
 </li>
</ol>
<p>默认情况下，Spring使用一个简单的线程池来执行异步任务。你可以通过定义一个Executor来自定义线程池：</p>
<pre><code>java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("MyAsync-");
        executor.initialize();
        return executor;
    }
}
</code></pre>
<p>在@Async注解中指定自定义的线程池：</p>
<pre><code>java

@Async("taskExecutor")
public void executeAsyncTask() {
    // 这里是耗时任务的逻辑
    System.out.println("执行异步任务");
}
</code></pre>
<ol>
 <li>
  <h2>注意事项</h2>
 </li>
 <li>
  <p>确保方法所在的类被Spring管理（即被@Service, @Component等注解标注）。</p>
 </li>
 <li>
  <p>异步方法必须返回void或Future类型。</p>
 </li>
 <li>
  <p>异步方法不能有final修饰符，因为它们需要被代理。</p>
 </li>
 <li>
  <p>异步方法调用不应该在同一个类中进行，因为这样不会触发代理。</p>
 </li>
</ol>
<p>通过以上步骤，你可以在Spring应用程序中实现异步操作，从而提高应用程序的性能和用户体验。</p>
<p>原文链接: https://blog.csdn.net/h356363/article/details/142628522</p>]]></description><guid isPermaLink="false">/archives/37572907-eec3-4abc-8b32-52040da32e6f</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 06:41:47 GMT</pubDate></item><item><title><![CDATA[Java中的文件系统操作：如何高效地处理文件的读写]]></title><link>https://wiki.dgfygk.com/archives/17b030bd-3346-473f-8185-a42c2a6634f0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E7%9A%84%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E5%A4%84%E7%90%86%E6%96%87%E4%BB%B6%E7%9A%84%E8%AF%BB%E5%86%99&amp;url=/archives/17b030bd-3346-473f-8185-a42c2a6634f0" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Java中的文件系统操作：如何高效地处理文件的读写</strong></p>
<p>大家好，我是微赚淘客返利系统3.0的小编，是个冬天不穿秋裤，天冷也要风度的程序猿！在Java中，文件系统操作是与用户交互和数据存储的重要环节。有效地处理文件的读写，不仅可以提升程序的性能，还能改善用户体验。本文将详细介绍Java中的文件系统操作，包括基本的文件读写方法、使用NIO提升性能，以及处理大文件的技巧。</p>
<p><strong>一、基本的文件读写</strong></p>
<p>在Java中，最常用的文件读写方式是通过<code>java.io</code>包下的类。以下是一个简单的示例，展示如何使用<code>FileInputStream</code>和<code>FileOutputStream</code>进行文件的读写操作。</p>
<pre><code class="prism language-java">package cn.juwatech.fileio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileIOExample {

    public static void main(String[] args) {

        String sourceFile = "source.txt";
        String destFile = "destination.txt";

        // 写入文件
        try (FileOutputStream fos = new FileOutputStream(destFile)) {

            String data = "Hello, Java File I/O!";
            fos.write(data.getBytes());
            System.out.println("Data written to file successfully.");
        } catch (IOException e) {

            e.printStackTrace();
        }

        // 读取文件
        try (FileInputStream fis = new FileInputStream(sourceFile)) {

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {

                System.out.print(new String(buffer, 0, bytesRead));
            }
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>在上面的代码中，我们首先使用<code>FileOutputStream</code>将数据写入文件，然后使用<code>FileInputStream</code>读取文件内容。<code>try-with-resources</code>语句确保在操作结束后自动关闭流，避免资源泄露。</p>
<p><strong>二、使用BufferedReader和BufferedWriter</strong></p>
<p>在进行文本文件操作时，使用<code>BufferedReader</code>和<code>BufferedWriter</code>可以有效提高性能。它们通过缓冲区来减少直接的磁盘操作次数，从而提高读写效率。</p>
<p>以下是使用<code>BufferedReader</code>和<code>BufferedWriter</code>的示例：</p>
<pre><code class="prism language-java">package cn.juwatech.fileio;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedFileIOExample {

    public static void main(String[] args) {

        String filePath = "example.txt";

        // 写入文件
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {

            writer.write("Hello, Buffered Writer!");
            writer.newLine();
            writer.write("This is a line of text.");
            System.out.println("Data written using BufferedWriter.");
        } catch (IOException e) {

            e.printStackTrace();
        }

        // 读取文件
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {

            String line;
            while ((line = reader.readLine()) != null) {

                System.out.println("Read line: " + line);
            }
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>在此示例中，<code>BufferedWriter</code>用于高效写入文本，而<code>BufferedReader</code>用于逐行读取文件，提升了读取效率。</p>
<p><strong>三、Java NIO的使用</strong></p>
<p>Java NIO（New Input/Output）是Java 1.4引入的一个新特性，它提供了更高效的文件读写方法。NIO通过通道和缓冲区的方式处理文件，支持非阻塞I/O操作。</p>
<p>以下是使用NIO进行文件读写的示例：</p>
<pre><code class="prism language-java">package cn.juwatech.nio;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class NIOFileExample {

    public static void main(String[] args) {

        Path sourcePath = Paths.get("source.txt");
        Path destPath = Paths.get("destination_nio.txt");

        // 写入文件
        String data = "Hello, NIO File I/O!";
        try {

            Files.write(destPath, data.getBytes());
            System.out.println("Data written using NIO.");
        } catch (IOException e) {

            e.printStackTrace();
        }

        // 读取文件
        try {

            String content = new String(Files.readAllBytes(sourcePath));
            System.out.println("Read content: " + content);
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>在这个示例中，<code>Files.write()</code>和<code>Files.readAllBytes()</code>方法提供了简便的文件写入和读取方式。NIO的优点在于它能够处理更大文件时表现更好，因为它可以有效地进行批量读取和写入操作。</p>
<p><strong>四、处理大文件的技巧</strong></p>
<p>处理大文件时，内存消耗和性能是两个主要关注点。以下是一些处理大文件的技巧：</p>
<ol>
 <li>
  <p><strong>使用流式处理</strong> ：对于非常大的文件，避免将整个文件加载到内存中。可以使用<code>BufferedReader</code>逐行读取，或者使用NIO的<code>Files.lines()</code>方法。</p>
  <pre><code class="prism language-java">package cn.juwatech.nio;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class StreamFileExample {

   public static void main(String[] args) {

       Path path = Paths.get("largefile.txt");

       try (Stream&lt;String&gt; lines = Files.lines(path)) {

           lines.forEach(System.out::println);
       } catch (IOException e) {

           e.printStackTrace();
       }
   }
}
</code></pre>
 </li>
 <li>
  <p><strong>使用内存映射文件</strong>：Java NIO支持内存映射文件，允许将文件的部分内容映射到内存中进行处理，适合处理非常大的文件。</p>
  <pre><code class="prism language-java">package cn.juwatech.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.nio.file.Paths;

public class MemoryMappedFileExample {

   public static void main(String[] args) {

       try (FileChannel channel = FileChannel.open(Paths.get("largefile.txt"), StandardOpenOption.READ)) {

           long size = channel.size();
           MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);

           // 逐字节读取
           for (int i = 0; i &lt; size; i++) {

               System.out.print((char) buffer.get());
           }
       } catch (IOException e) {

           e.printStackTrace();
       }
   }
}
</code></pre>
 </li>
 <li>
  <p><strong>批量处理</strong>：如果需要将大量数据写入文件，考虑使用批量写入技术，避免频繁的磁盘I/O操作。</p>
  <pre><code class="prism language-java">package cn.juwatech.fileio;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BatchWriteExample {

   public static void main(String[] args) {

       String filePath = "batch_output.txt";

       try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {

           for (int i = 0; i &lt; 100000; i++) {

               writer.write("Line " + i);
               writer.newLine();
               if (i % 1000 == 0) {

                   writer.flush(); // 每1000行强制写入
               }
           }
           System.out.println("Batch write completed.");
       } catch (IOException e) {

           e.printStackTrace();
       }
   }
}
</code></pre>
 </li>
</ol>
<p>在上述示例中，我们通过<code>BufferedWriter</code>的<code>flush()</code>方法实现了批量写入，从而减少频繁的磁盘写入操作，提高了写入性能。</p>
<p><strong>五、总结</strong></p>
<p>在Java中，高效的文件读写操作不仅依赖于合适的API选择，还需要合理的设计和优化策略。通过使用<code>BufferedReader</code>、<code>BufferedWriter</code>以及NIO，能够有效提升文件操作的性能，尤其是在处理大文件时，避免内存溢出和性能瓶颈。合理运用这些技巧，可以为Java后端开发提供更稳定、更高效的文件系统操作能力。</p>
<p>本文著作权归聚娃科技微赚淘客系统开发者团队，转载请注明出处！</p>
<p>原文链接: https://blog.csdn.net/u010405836/article/details/142425357</p>]]></description><guid isPermaLink="false">/archives/17b030bd-3346-473f-8185-a42c2a6634f0</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Mon, 30 Sep 2024 02:10:47 GMT</pubDate></item><item><title><![CDATA[【数据结构】排序算法系列——希尔排序（附源码+图解）]]></title><link>https://wiki.dgfygk.com/archives/897755cd-2a8e-4e3d-b457-f569309f1dcf</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E3%80%91%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E7%B3%BB%E5%88%97%E2%80%94%E2%80%94%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F%EF%BC%88%E9%99%84%E6%BA%90%E7%A0%81%2B%E5%9B%BE%E8%A7%A3%EF%BC%89&amp;url=/archives/897755cd-2a8e-4e3d-b457-f569309f1dcf" width="1" height="1" alt="" style="opacity:0;">
<h3>希尔排序</h3>
<h4>算法思想</h4>
<p><strong>希尔排序（Shell Sort）</strong>是一种<strong>改进的插入排序算法</strong> ，希尔排序的创造者Donald Shell想出了这个极具创造力的改进。其时间复杂度取决于<strong>步长序列（gap）</strong>的选择。<strong>我们在插入排序</strong>中，会发现是对整体数据直接进行了统一的插入排序，每个数据之间的间隙是<strong>1</strong> ，这里的<strong>1</strong> 指的就是步长序列<strong>gap</strong> 。在<strong>希尔排序</strong> 中，我们会将整体数据一分为多份，进行散布式的插入排序，这时候每一个子序列之间的间隙就是<strong>gap</strong> ——那么事实上我们也可以将插入排序就看成是<strong>gap=1</strong>的希尔排序。</p>
<p>我们来具体分析希尔排序的算法步骤：</p>
<ul>
 <li>将待排序序列分为若干个序列，每个序列的间距n（gap）需要相同</li>
 <li>将这些子序列分别进行插入排序</li>
 <li>不断减小这个间距</li>
</ul>
<p>那么我们减小这个间距的目的是什么呢？</p>
<p>当<strong>gap &gt; 1</strong> 时我们可以称为<strong>预排序</strong> ，目的是<strong>让数组更接近于有序</strong> 。当<strong>gap = 1</strong> 时，数组已经接近有序的了，就整体而言，最后一次整体的插入排序就可以大大提高效率——我们从插入排序的时间复杂度分析也可以看出，<strong>越接近有序，插入排序的效率就越高</strong>，从而可以达到优化的效果。</p>
<h4>图解</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4f3d6ca502bddeb2b466cd2c2054be55.gif%23pic_center&amp;size=m" alt=""></p>
<p>可以看到每次减小gap的规律是将原先的gap/2，但事实上这只是其中一种处理方法，并不说明这是最优解。</p>
<h4>C语言代码分析</h4>
<pre><code class="prism language-c">//与插入排序类似，只是插入排序的间隔是1，而希尔排序的间隔是gap

//第一种思想：依次排序
//排完一组后，再排下一组
void ShellSort1(int arr[], int n)
{

    int gap = 3;//任意一个想要的间隔
    for (int j; j &lt; gap; j++)
    {

        for (int i = gap; i &lt; n; i += gap)
        {


            int end = i - gap;
            int tmp = arr[end + gap];
            while (end &gt;= 0)
            {

                if (tmp &gt;= arr[end])
                {

                    arr[end + gap] = tmp;
                    end -= gap;
                }
                else
                {

                    break;
                }
            }
            arr[end + gap] = tmp;
        }
    }


}

//第二种思想：多组并排
void ShellSort2(int arr[], int n)
{

    int gap = 3;//任意一个想要的间隔

        for (int i = gap; i &lt; n-gap; i ++)
        {


            int end = i;
            int tmp = arr[i + gap];
            while (end &gt;= 0)
            {

                if (tmp &gt;= arr[end])
                {

                    arr[end + gap] = tmp;
                    end -= gap;
                }
                else
                {

                    break;
                }
            }
            arr[end + gap] = tmp;
        }
}

//gap越大，跳得越快，但一次排下来最无序
//gap越小，跳得越慢，但一次排下来更有序
</code></pre>
<h4>注意</h4>
<p>希尔排序实际上是个相当复杂的排序算法，这主要是跟它的步长序列gap到底该如何取、后续应该减小有关。这其中涉及到很多的数学分析以及数学公式，我们可以参考严蔚敏老师的解读：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F00717b7e9ffc4a319673fdae72109b9a.png&amp;size=m" alt=""></p>
<p>以及殷人昆老师：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe6ce73fb955947638cad066c4ac99cce.png&amp;size=m" alt=""></p>
<p>所以，本篇文章仅对其基本的算法思想和代码编写进行解析，如有兴趣深究希尔排序，各位读者们可以自行上网搜索有关知识\~</p>
<h4>时间复杂度</h4>
<p>一般情况下，希尔排序的时间复杂度可以表示为：</p>
<ul>
 <li>最好情况（已排序的情况）：<strong>O(n log n)</strong></li>
 <li>平均情况：取决于步长序列的选择，通常为**O(n<sup>1.3</sup>)-O(n<sup>2</sup>)**之间。</li>
 <li>最坏情况：<strong>O(n<sup>2</sup>)</strong></li>
</ul>
<p>希尔排序通过逐步减少步长来实现排序，<strong>初始的大步长使得数组元素可以较快地达到部分有序状态，最终通过小步长的插入排序完成排序</strong>。所以时间复杂度的具体分析也就取决于步长序列。</p>
<p>这里针对平均情况，我们进行一下简单的具体分析：</p>
<p>希尔排序的平均情况时间复杂度是比较复杂的。在实际应用中，常见的步长序列如<strong>希尔建议的序列</strong> （1, 3, 7, …, 2^k-1）或者<strong>Hibbard序列</strong> （1, 3, 7, 15, …, 2<sup>k-1</sup>）等，它们的时间复杂度通常就在**O(n<sup>1.3</sup>)-O(n<sup>2</sup>)**之间，这是经过数学算出来的结果。这些序列被设计为逐渐减小，从而在较早阶段快速减少逆序对的数量，然后在最后阶段完成排序。</p>
<p>总体来说，希尔排序的性能<strong>高度依赖于步长序列</strong> 的选择。<strong>良好的步长序列可以显著改善排序的效率</strong>，使得平均情况下的时间复杂度能够在O(n^1.3)左右，而不好的选择则可能导致接近最坏情况的性能。</p>
<h4>稳定性</h4>
<p>鉴于希尔排序会改变前后元素的相对位置，所以：<strong>不稳定</strong></p>
<p>原文链接: https://blog.csdn.net/Skrrapper/article/details/141891511</p>]]></description><guid isPermaLink="false">/archives/897755cd-2a8e-4e3d-b457-f569309f1dcf</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 08:46:00 GMT</pubDate></item><item><title><![CDATA[【算法】贪心+堆排序实现大根堆及标准库容器类的融合使用]]></title><link>https://wiki.dgfygk.com/archives/e840f94e-eafb-4a0f-aa6c-42f9839dbffd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%AE%97%E6%B3%95%E3%80%91%E8%B4%AA%E5%BF%83%2B%E5%A0%86%E6%8E%92%E5%BA%8F%E5%AE%9E%E7%8E%B0%E5%A4%A7%E6%A0%B9%E5%A0%86%E5%8F%8A%E6%A0%87%E5%87%86%E5%BA%93%E5%AE%B9%E5%99%A8%E7%B1%BB%E7%9A%84%E8%9E%8D%E5%90%88%E4%BD%BF%E7%94%A8&amp;url=/archives/e840f94e-eafb-4a0f-aa6c-42f9839dbffd" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>📢博客主页：<a href="https://blog.csdn.net/2301_77954967?spm=1011.2444.3001.5343">https://blog.csdn.net/2301_779549673</a>
  <br>
  📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正！
  <br>
  📢本文由 <strong><em>JohnKi</em></strong> 原创，首发于 CSDN🙉
  <br>
  📢未来很长，值得我们全力奔赴更美好的生活✨</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2F9393ea55f0d5414da8e09bd55eb82f4f.gif%23pic_center&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2Fb55982b2fa5b47dda351a719b325c9e1.gif%23pic_center&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_20">📢前言</a></li>
 <li><a href="https://wiki.dgfygk.com#_75">🏳️‍🌈一、什么是贪心</a></li>
 <li><a href="https://wiki.dgfygk.com#_100">🏳️‍🌈二、大根堆算法概述</a></li>
 <li><a href="https://wiki.dgfygk.com#_114">🏳️‍🌈三、深入剖析大根堆实现细节</a></li>
 <li><a href="https://wiki.dgfygk.com#_194">🏳️‍🌈四、标准库容器类的融合使用</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#vector__set_201">❤️一、vector 与 set</a></li>
 <li><a href="https://wiki.dgfygk.com#vectormap_215">🧡二、vector与map</a></li>
 <li><a href="https://wiki.dgfygk.com#listqueue_237">💛三、list与queue</a></li>
 <li><a href="https://wiki.dgfygk.com#dequestack_258">💚四、deque与stack</a></li>
 <li><a href="https://wiki.dgfygk.com#_264">💙</a></li>
 <li><a href="https://wiki.dgfygk.com#_268">👥总结</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<h2>📢前言</h2>
<p><strong>先看题</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fef15d7e117264bd8801e281ee53461d4.png&amp;size=m" alt=""></p>
<blockquote>
 <p>这个算法，是要<strong>通过循环</strong> 找到所有元素中最大的那个<strong>偶元素</strong> ，对其除以2，然后再找这之后的最大的那个<strong>偶元素</strong>以此类推</p>
</blockquote>
<p>笔者的经历不多，第一次做的时候没有想到 <code>大根堆</code> 的算法，只是用 <code>sort</code> 将每次除以2后的整个数组进行降序排序，这样的作法</p>
<ul>
 <li>使用 <code>sort</code> 对数组进行排序后，虽然可以确定数组中的元素大小顺序，但在每次选择偶数进行减半操作时，需要遍历整个数组来找到合适的偶数，效率较低。</li>
 <li>对于一个较大的数组，排序后可能需要花费较多的时间来找到下一个要进行减半操作的偶数，尤其是当操作次数较多时，这种遍历的方式会变得非常耗时。</li>
</ul>
<p>这不符合贪心的要求，但使用 <code>大根堆</code> 的方法，在进行操作的过程中，能够实时地根据当前数组的状态进行调整。每次操作后，只需要对受影响的元素进行堆的调整操作，时间复杂度相对较低。</p>
<p>因此这题代码解析如下</p>
<pre><code class="prism language-cpp">#define _CRT_SECURE_NO_WARNINGS 1

#include&lt;iostream&gt;
#include&lt;queue&gt;

using namespace std;

typedef long long LL;

int main()
{

    int n, k;
    cin &gt;&gt; n &gt;&gt; k;
    priority_queue&lt;LL&gt; heap; // 优先队列，可以理解为大根堆
    LL tmp;
    LL ans = 0;
    for (int i = 0; i &lt; n; i++)
    {

        cin &gt;&gt; tmp;
        ans += tmp;
        if (tmp % 2 == 0)
            heap.push(tmp);
    }

    while (k-- &amp;&amp; !heap.empty())
    {

        LL t = heap.top() / 2;
        ans -= t;
        heap.pop();
        if (t % 2 == 0) heap.push(t);
    }
    printf("%lld", ans);

    return 0;
}
</code></pre>
<hr>
<h2>🏳️‍🌈一、什么是贪心</h2>
<p>贪心算法是一种在每一步选择中都采取当前状态下的最优决策的算法设计策略。</p>
<p>贪心算法有以下几个特点：</p>
<p><strong>一、局部最优选择</strong></p>
<p>在每一步决策时，贪心算法总是选择当前看起来最优的选项，而不考虑整体的最优解是否一定能够通过这些局部最优选择得到。这种局部最优选择是基于某种特定的贪心策略，例如选择当前价值最大的物品、选择距离最近的节点等。</p>
<p>例如，在找零钱问题中，如果要使用最少的硬币找零，贪心策略可能是每次选择面值尽可能大的硬币。比如要找零 63 元，有 1 元、5 元、10 元、20 元、50 元的硬币，贪心算法会先选择一个 50 元硬币，然后再选择一个 10 元硬币，接着选择一个 1 元、1 元、1 元硬币，共使用了五枚硬币。</p>
<p><strong>二、不一定得到全局最优解</strong></p>
<p>虽然贪心算法在很多情况下能够快速得到一个可行解，但它不能保证一定能得到全局最优解。这是因为贪心算法只考虑了局部最优，而没有考虑到所有可能的情况。</p>
<p>例如，在背包问题中，如果物品可以分割，使用贪心算法按照物品的单位价值（价值/重量）从大到小选择物品放入背包，可能会得到一个接近最优解的结果，但不一定是全局最优解。而对于 0-1 背包问题（物品不可分割），贪心算法通常不能得到最优解。</p>
<p><strong>三、适用性和高效性</strong></p>
<p>贪心算法适用于一些具有特定性质的问题，这些问题通常具有贪心选择性质和最优子结构性质。贪心选择性质是指通过局部最优选择可以得到全局最优解的一个必要条件；最优子结构性质是指问题的最优解可以由子问题的最优解组合而成。</p>
<p>贪心算法通常比较简单直观，实现起来相对容易，并且在很多情况下具有较高的效率。因为它不需要进行复杂的搜索和回溯，只需要在每一步做出一个局部最优选择即可。</p>
<p>总的来说，贪心算法是一种在特定问题中能够快速得到可行解的算法策略，但需要谨慎使用，并且在使用时需要分析问题是否满足贪心选择性质和最优子结构性质，以确保得到的解是合理的。</p>
<h2>🏳️‍🌈二、大根堆算法概述</h2>
<p><strong>（一）贪心算法与大根堆</strong>
 <br>
 贪心算法在大根堆构建中有着巧妙的运用。以力扣中的一些问题为例，比如在解决可达到最远建筑问题时，贪心算法通过局部最优解的选择，逐步逼近全局最优解。在大根堆 + 贪心算法的场景中，如神偷 Jacky 在楼顶穿梭的问题，当遇到下一个楼顶大于当前楼顶爬不上去的时候，优先选择丢砖头，保留梯子以备更需要的时候使用。这体现了贪心算法在大根堆构建中的思想，即每次选择当前最优的决策，以达到最终的目标。</p>
<p><strong>（二）队列结合大根堆</strong>
 <br>
 队列可以与大根堆结合，实现优先级队列等功能。例如在手机上玩游戏的时候，如果有来电，系统应该优先处理打进来的电话，这就引入了优先级队列这种数据结构。例如在解决最优的方式安排会议问题时，按照会议结束时间建立比较器，将会议放入小顶堆；循环所有会议，符合限制开始时间时结果加一，然后限制开始时间后移到当前会议的结束时间。</p>
<p><strong>（三）堆排序实现大根堆</strong>
 <br>
 堆排序在构建大根堆过程中有着重要的作用。大根堆就是对于每一个元素，它的值大于等于它的孩子节点。大根堆保存在一个数组中，第 <code>0</code> 个元素作为堆的根。堆排序主要分为两步：</p>
<ul>
 <li>构建大根堆和调整大根堆。构建大根堆时我们是自底向上的进行比较，使得每次构建的堆都是大根堆；</li>
 <li>当构建完毕后，每次则是自顶向下的进行比较，保证形成新的大根堆。</li>
</ul>
<p>例如在对一个无序数列进行递增排序时，先按照完全二叉树的层序顺序，插入叶子结点，然后比较该叶子结点与其父结点，如果比父结点大，则与父结点交换，此时可以保证当前的子树是大根堆。重复这个过程，直到所有数都添加至这个树中，完成大根堆的构建。接着进行调整大根堆的操作，将根结点与树中最后一个结点进行交换，并将这个最大值剔除这个树，对剩下的数进行调整，自顶向下的进行判断，如果根结点比两个孩子结点的最大值要小，则把最大值与根结点交换，重复这个过程，直到这个树为空，此时我们可以得到一个有序的数列。</p>
<h2>🏳️‍🌈三、深入剖析大根堆实现细节</h2>
<p>在考试时我们可以直接使用库函数 <strong>queue</strong> 中的 <strong>priority_queue</strong> 优先队列来实现大根堆，但是因为我们要充分理解大根堆的实现方式我们</p>
<ol>
 <li><strong>堆的概念</strong>：堆是一颗完全二叉树，若根节点索引从 0 开始编号，对于大根堆，若：Ki &gt;= K2i+1 且 Ki &gt;= K2i+2，i =0，1，2…，则称为大堆。将根节点最大的堆叫做最大堆或大根堆。</li>
 <li><strong>存储方式</strong>：堆是一颗完全二叉树，因此可以采用顺序方式进行存储。对于非完全二叉树，则不适合使用顺序方式进行存储，因为为了能够还原二叉树，空间中必须要存储空节点，就会导致空间利用率比较低。</li>
 <li><strong>创建过程</strong>：给定一个任意整型数组，将其调整为最大堆。核心思路是从最后一个非叶子节点开始进行元素下沉操作，不断向根节点倒着向下调整，慢慢的将左右子树调整为最大堆。</li>
</ol>
<pre><code class="prism language-cpp">// 模拟实现 priority_heap
template&lt;typename T&gt;
class my_priority_heap // 优先队列，大根堆
{

public:
    // 尾插，实现自动大根堆
    void push(T val)
    {

        heap.push_back(val);
        adjustup(heap.size() - 1);
    }

    // 获得堆顶的值
    T top()
    {

        if (heap.size() == 0)
            return T();
        return heap[0];
    }


    // 头删，实现自动大根堆
    void pop()
    {

        if (heap.size() == 0)
            return;
        heap[0] = heap.back();
        heap.pop_back();// vector 在创建的时候可能会开辟空间，不能直接 size-- 会造成内存泄漏 
        adjustdown(0);
    }

    // swap 实现
    void swap(int i, int j)
    {

        T tmp = heap[i];
        heap[i] = heap[j];
        heap[j] = tmp;
    }

private:
    vector&lt;T&gt; heap;
    int parent(int index) {
    return (index - 1) / 2; }// 方法 - 获得父节点
    int leftchild(int index) {
    return (index * 2) + 1; } // 方法 - 获得左子节点
    int rightchild(int index)    {
    return (index * 2) + 2; } // 方法 - 获得右子节点

    // 向上调整
    void adjustup(int index)
    {

        if (index &gt; 0 &amp;&amp; (heap[index] &gt; heap[parent(index)]))
        {

            swap(index, parent(index));
            index = parent(index);
            adjustup(index);
        }
    }

    // 向下调整
    void adjustdown(int index)
    {

        int largest = index;
        int left = leftchild(index);
        int right = rightchild(index);
        if (left &lt; heap.size() &amp;&amp; heap[left] &gt; heap[largest]) largest = left;
        if (right &lt; heap.size() &amp;&amp; heap[right] &gt; heap[largest]) largest = right;
        if (largest != index)
        {

            swap(heap[largest], heap[index]);
            adjustdown(largest);
        }
    }
};
</code></pre>
<h2>🏳️‍🌈四、标准库容器类的融合使用</h2>
<p>在这里大根堆的模拟实现是利用了 vector 和 heap 两种容器，实现了有队列的删对头，填队尾的能力，也就是优先队列</p>
<p>可见标准库容器是可以融合使用贯通的，以此来达到某种目的</p>
<p>举几个例子</p>
<h3>❤️一、vector 与 set</h3>
<ol>
 <li><strong>数据去重与排序：</strong>
  <br>
  可以先使用vector存储数据，然后将其内容复制到set中。由于set的特性是自动排序且不允许重复元素，这样可以快速实现对数据的去重和排序。</li>
</ol>
<p>示例代码：</p>
<pre><code class="prism language-cpp">   std::vector&lt;int&gt; vec = {
   3, 1, 2, 3, 4, 1};
   std::set&lt;int&gt; s(vec.begin(), vec.end());
   // s 现在是 {1, 2, 3, 4}，已排序且无重复元素。
</code></pre>
<ol>
 <li><strong>高效查找与随机访问结合：</strong>
  <br>
  如果需要频繁进行随机访问，可以使用vector。而当需要快速查找特定元素是否存在时，可以利用set。
  <br>
  例如，在处理大量数据时，可以先将数据存储在vector中，然后根据需要在特定操作中使用set进行快速查找。</li>
</ol>
<h3>🧡二、vector与map</h3>
<ol>
 <li><strong>关联数据存储与快速访问：</strong>
  <br>
  vector可以存储一系列数据，而map可以建立数据之间的关联关系。例如，可以用vector存储对象列表，然后用map建立对象的某个属性与对象在vector中的索引的映射，以便快速根据属性值查找对应的对象。</li>
</ol>
<p>示例代码：</p>
<pre><code class="prism language-cpp">   std::vector&lt;Person&gt; people;
   people.push_back(Person("Alice", 25));
   people.push_back(Person("Bob", 30));

   std::map&lt;std::string, int&gt; nameToIndex;
   for (int i = 0; i &lt; people.size(); ++i) {

       nameToIndex[people[i].name] = i;
   }

   int index = nameToIndex["Bob"];
   if (index!= -1) {

       std::cout &lt;&lt; "Bob's age is " &lt;&lt; people[index].age &lt;&lt; std::endl;
   }
</code></pre>
<h3>💛三、list与queue</h3>
<ol>
 <li><strong>灵活的链表操作与队列行为：</strong>
  <br>
  list提供了高效的插入和删除操作，可以作为底层存储结构。然后，可以通过封装list来实现一个具有队列行为的容器，例如使用push_back在尾部插入元素，使用pop_front在头部删除元素，模拟队列的先进先出（FIFO）特性。</li>
</ol>
<p>示例代码：</p>
<pre><code class="prism language-cpp">   std::list&lt;int&gt; dataList;
   // 模拟队列操作
   dataList.push_back(1);
   dataList.push_back(2);
   dataList.push_back(3);

   while (!dataList.empty()) {

       int frontElement = dataList.front();
       std::cout &lt;&lt; frontElement &lt;&lt; " ";
       dataList.pop_front();
   }
</code></pre>
<h3>💚四、deque与stack</h3>
<ol>
 <li><strong>双端队列作为栈使用：</strong>
  <br>
  deque支持在两端进行高效的插入和删除操作。可以将deque封装成一个栈，只在一端进行插入（push_back）和删除（pop_back）操作，实现栈的后进先出（LIFO）特性。</li>
</ol>
<p>示例代码：
 <br>
 <strong>加粗样式</strong></p>
<h3>💙</h3>
<hr>
<h2>👥总结</h2>
<hr>
<p>本篇博文对 <strong>大根堆 及 标准库容器类的融合使用</strong> 做了一个较为详细的介绍，不知道对你有没有帮助呢</p>
<p>觉得博主写得还不错的三连支持下吧！会继续努力的\~</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2Fccd277ddb2e84277b6970d9cc24da8bd.jpeg%23pic_center&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/2301_77954967/article/details/142521951</p>]]></description><guid isPermaLink="false">/archives/e840f94e-eafb-4a0f-aa6c-42f9839dbffd</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 08:45:43 GMT</pubDate></item><item><title><![CDATA[mysql中B+树的数据存储]]></title><link>https://wiki.dgfygk.com/archives/9c55702c-e067-4c09-b065-f9966d8d2579</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=mysql%E4%B8%ADB%2B%E6%A0%91%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8&amp;url=/archives/9c55702c-e067-4c09-b065-f9966d8d2579" width="1" height="1" alt="" style="opacity:0;">
<h4>B+树索引基础分析</h4>
<ul>
 <li>
  <p><strong>B+树</strong> <strong>的定义</strong> B+树是一种自平衡的树形数据结构，常用于数据库和操作系统的索引结构。它具有以下特点：</p>
 </li>
 <li>
  <p>所有数据都存储在叶子节点，非叶子节点仅存储键值和子节点的指针。</p>
 </li>
 <li>
  <p>叶子节点之间通过指针相互连接，形成一个有序链表，便于范围查询。</p>
 </li>
 <li>
  <p>每个节点可以拥有多个键值，键值之间是有序的。</p>
 </li>
 <li>
  <p><strong>B+树</strong> <strong>的特点</strong></p>
 </li>
 <li>
  <p><strong>高扇出性</strong>：B+树的每个内部节点可以拥有大量子节点，这减少了树的高度，从而减少了查找数据时的磁盘I/O次数。</p>
 </li>
 <li>
  <p><strong>节点的有序性</strong>：B+树的节点内部的键值是有序的，这使得B+树可以高效地进行范围查询。</p>
 </li>
 <li>
  <p><strong>数据的聚集性</strong>：由于所有数据都存储在叶子节点，并且叶子节点之间通过指针相连，数据在物理存储上是聚集的，这有助于提高查询效率。</p>
 </li>
 <li>
  <p><strong>查询性能稳定</strong>：由于B+树的高度较低，查询性能不会因为数据量的增加而显著下降。</p>
 </li>
 <li>
  <p><strong>B+树</strong> <strong>在数据库中的应用</strong></p>
 </li>
 <li>
  <p><strong>索引结构</strong>：B+树作为数据库索引结构，可以显著提高数据检索的速度。它允许数据库快速定位到数据所在的磁盘块。</p>
 </li>
 <li>
  <p><strong>范围查询</strong>：由于B+树的叶子节点形成了有序链表，它特别适合执行范围查询，如"检索所有年龄在20到30岁之间的用户”。</p>
 </li>
 <li>
  <p><strong>聚集索引</strong> <strong>与二级索引</strong>：</p>
  <ul>
   <li>
    <p><strong>聚集索引</strong>：在聚集索引中，非叶子节点存储的是数据记录的键值和指向子节点的指针，叶子节点包含了完整的数据记录。聚集索引通常对应表的主键。</p>
   </li>
   <li>
    <p><strong>二级索引</strong>：二级索引的非叶子节点存储的是键值和对应的主键值，叶子节点存储的是主键值和指向聚集索引的指针。这样，通过二级索引可以快速定位到主键，然后再通过主键在聚集索引中检索到完整的数据记录。</p>
   </li>
  </ul>
 </li>
</ul>
<p>
 <br>
</p>
<p>以下是B+树索引结构的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F189db8fb54a6459e9f690c5ab1919e47.png&amp;size=m" alt="">在这个图中，B+树的根节点和非叶子节点仅存储键值和子节点的指针，而所有的数据记录都存储在叶子节点中，并且叶子节点之间通过链表指针相互连接。这种结构使得B+树在数据库索引中非常高效。</p>
<p>
 <br>
</p>
<h4>noDB存储引擎概述</h4>
<ul>
 <li>
  <p><strong>InnoDB存储引擎简介</strong> InnoDB是MySQL数据库的默认存储引擎之一，以其高性能、可靠性和ACID事务支持而闻名。InnoDB支持行级锁定和外键约束，提供了高并发的数据访问能力。InnoDB存储引擎的特点包括：</p>
 </li>
 <li>
  <p><strong>ACID事务</strong>：确保数据库事务的原子性、一致性、隔离性和持久性。</p>
 </li>
 <li>
  <p><strong>行级锁定</strong>：提高并发性能，减少锁冲突。</p>
 </li>
 <li>
  <p><strong>外键</strong> <strong>约束</strong>：支持数据库的完整性和数据的一致性。</p>
 </li>
 <li>
  <p><strong>崩溃恢复</strong>：通过日志文件和恢复机制，确保数据库在崩溃后能够恢复到一致状态。</p>
 </li>
 <li>
  <p><strong>数据存储方式</strong> InnoDB存储引擎将数据存储在磁盘上的表空间中，表空间由数据文件组成。数据文件包含了多个数据页，每个数据页是数据库I/O操作的基本单位。InnoDB的数据存储方式包括：</p>
 </li>
 <li>
  <p><strong>按页存储</strong>：数据以页为单位存储，每个数据页默认大小为16KB。</p>
 </li>
 <li>
  <p><strong>聚簇索引</strong>：每个表都有一个聚簇索引，基于主键构建，如果没有显式指定主键，则基于第一列（不包含NULL值）构建。</p>
 </li>
 <li>
  <p><strong>二级索引</strong>：除了聚簇索引外，还可以有多个基于非主键列的二级索引。</p>
 </li>
 <li>
  <p><strong>数据页的概念</strong> 数据页是InnoDB存储引擎中数据存储的基本单元，每个数据页包含以下组成部分：</p>
 </li>
 <li>
  <p><strong>文件头(File Header)</strong>：存储页的基本信息。</p>
 </li>
 <li>
  <p><strong>页头(Page Header)</strong>：包含页的状态信息。</p>
 </li>
 <li>
  <p><strong>最小和最大记录(Infimum和Supremum)</strong>：两个虚拟的伪记录，分别表示页中的最小记录和最大记录。</p>
 </li>
 <li>
  <p><strong>用户记录(User Records)</strong>：实际存储的行记录。</p>
 </li>
 <li>
  <p><strong>空闲空间(Free Space)</strong>：页中尚未使用的空间。</p>
 </li>
 <li>
  <p><strong>页目录(Page Directory)</strong>：存储用户记录的相对位置，起到索引作用。</p>
 </li>
 <li>
  <p><strong>文件尾(File Trailer)</strong>：用于校验页是否完整。</p>
 </li>
</ul>
<p>数据页内的记录按照主键顺序组织成单向链表，以支持高效的插入和删除操作。页目录的存在允许快速定位到页内的特定记录，提高查询效率。</p>
<p>
 <br>
</p>
<p>以下是InnoDB数据页结构的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb37c40384c224d3580eed80ed7e14176.png&amp;size=m" alt="">在这个图中，数据页由多个部分组成，每个部分都有其特定的功能和作用。页目录的存在使得在数据页内查找特定记录变得更加高效。</p>
<ol>
 <li></li>
</ol>
<h4>数据页结构分析</h4>
<p><strong>数据页的组成部分</strong>：</p>
<ol>
 <li>
  <p><strong>文件头（File Header）</strong></p>
 </li>
 <li>
  <p>位置：数据页的最开始部分。</p>
 </li>
 <li>
  <p>功能：存储关于数据页的基本信息，如页的编号、类型等。</p>
 </li>
 <li>
  <p><strong>页头（Page Header）</strong></p>
 </li>
 <li>
  <p>位置：紧接着文件头。</p>
 </li>
 <li>
  <p>功能：包含页的状态信息，如页的事务ID、锁定状态、页的脏状态（是否被修改）等。</p>
 </li>
 <li>
  <p><strong>最小和最大记录（Infimum和Supremum）</strong></p>
 </li>
 <li>
  <p>位置：页头之后。</p>
 </li>
 <li>
  <p>功能：两个虚拟的伪记录，分别表示页中的最小记录和最大记录，用于页内记录的边界定义。</p>
 </li>
 <li>
  <p><strong>用户记录（User Records）</strong></p>
 </li>
 <li>
  <p>位置：紧随最小和最大记录之后。</p>
 </li>
 <li>
  <p>功能：实际存储的行数据记录，按照主键顺序组织成单向链表。</p>
 </li>
 <li>
  <p><strong>空闲空间（Free Space）</strong></p>
 </li>
 <li>
  <p>位置：用户记录之后，页目录之前。</p>
 </li>
 <li>
  <p>功能：表示页中尚未使用的空间，用于新记录的插入。</p>
 </li>
 <li>
  <p><strong>页目录（Page Directory）</strong></p>
 </li>
 <li>
  <p>位置：空闲空间之后。</p>
 </li>
 <li>
  <p>功能：存储用户记录的相对位置索引，允许快速定位到页内特定记录的位置。</p>
 </li>
 <li>
  <p><strong>文件尾（File Trailer）</strong></p>
 </li>
 <li>
  <p>位置：数据页的最后部分。</p>
 </li>
 <li>
  <p>功能：包含页的校验信息，用于确保页的完整性和一致性。</p>
 </li>
</ol>
<p><strong>各组成部分的功能和作用</strong>：</p>
<ul>
 <li>
  <p><strong>文件头</strong> 和<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>
<p>
 <br>
</p>
<p>以下是InnoDB数据页结构的Mermaid图形化展示，突出了各组成部分的功能：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2a40114260dc491797108b7dba90cd31.png&amp;size=m" alt="">在这个图中，数据页的每个组成部分都通过其功能和作用与其他部分相连，形成了一个完整的数据页结构。</p>
<p>
 <br>
</p>
<h4>B+树索引的组织方式分析</h4>
<p><strong>主键</strong> <strong>索引和非主键索引（二级索引）</strong>：</p>
<ul>
 <li>
  <p><strong>主键</strong> <strong>索引（</strong> <strong>Primary Key</strong> <strong>Index）</strong></p>
 </li>
 <li>
  <p>每个表只能有一个主键索引，通常基于表的主键列创建。</p>
 </li>
 <li>
  <p>主键索引是聚簇索引（Clustered Index），意味着非叶子节点存储键值和子节点指针，而叶子节点存储键值和对应的完整数据记录。</p>
 </li>
 <li>
  <p><strong>非</strong> <strong>主键</strong> <strong>索引（</strong> <strong>Secondary</strong> <strong>Index）</strong></p>
 </li>
 <li>
  <p>表中可以有多个非主键索引，通常基于表中的非主键列创建。</p>
 </li>
 <li>
  <p>非主键索引是非聚簇索引，其叶子节点不直接存储数据记录，而是存储键值和对应的主键值。</p>
 </li>
 <li>
  <p><strong>聚簇索引</strong> <strong>（</strong> <strong>Clustered Index</strong> <strong>）和二级索引（</strong> <strong>Secondary</strong> <strong>Index）的区别</strong>：</p>
 </li>
 <li>
  <p><strong>存储结构</strong></p>
 </li>
 <li>
  <p>聚簇索引的叶子节点包含完整的数据记录。</p>
 </li>
 <li>
  <p>二级索引的叶子节点只包含键值和对应的主键值。</p>
 </li>
 <li>
  <p><strong>查询效率</strong></p>
 </li>
 <li>
  <p>通过聚簇索引查询数据时，可以直接在叶子节点获取完整的数据记录，查询效率较高。</p>
 </li>
 <li>
  <p>通过二级索引查询数据时，首先需要通过二级索引找到主键值，然后使用主键值在聚簇索引中进行查找，这个过程称为"回表”，查询效率相对较低。</p>
 </li>
 <li>
  <p><strong>物理存储</strong></p>
 </li>
 <li>
  <p>聚簇索引的物理存储是按照主键顺序组织的，数据在物理上聚集在一起。</p>
 </li>
 <li>
  <p>二级索引的物理存储是独立的，与聚簇索引分开。</p>
 </li>
 <li>
  <p><strong>创建规则</strong></p>
 </li>
 <li>
  <p>聚簇索引由表的主键自动创建，如果没有显式指定主键，InnoDB会根据第一列（不包含NULL值）创建聚簇索引。</p>
 </li>
 <li>
  <p>二级索引是在需要基于非主键列进行查询优化时创建的。</p>
 </li>
 <li>
  <p><strong>使用场景</strong></p>
 </li>
 <li>
  <p>聚簇索引适用于基于主键的查询，如根据主键进行数据检索。</p>
 </li>
 <li>
  <p>二级索引适用于基于非主键列的查询，如根据某个特定列进行数据检索。</p>
 </li>
</ul>
<p>以下是聚簇索引和二级索引的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6f6451d76db448ecab401f4a50900045.png&amp;size=m" alt="">在这个图中，聚簇索引和二级索引都指向表的数据结构，但它们的叶子节点存储的内容不同。通过二级索引查询数据时，需要进行额外的回表操作，即先在二级索引中找到主键值，然后在聚簇索引中查找完整的数据记录。</p>
<ol>
 <li></li>
</ol>
<h4>B+树索引的查询过程分析</h4>
<p><strong>索引节点</strong> <strong>的组织结构</strong>：</p>
<p>B+树的索引组织结构是由多个层级组成的，每一层都是一个节点集合，具有以下特点：</p>
<ul>
 <li>
  <p>根节点位于树的最顶层，可能包含多个键值和子节点指针。</p>
 </li>
 <li>
  <p>非叶子节点包含键值和指向子节点的指针，但不存储实际的数据记录。</p>
 </li>
 <li>
  <p>叶子节点存储实际的数据记录或指向数据记录的指针（在聚簇索引中），或存储键值和对应的主键值（在二级索引中）。</p>
 </li>
 <li>
  <p>所有节点（包括根节点和内部节点）都可能包含多个键值，这些键值是有序排列的，以便进行高效的查找操作。</p>
 </li>
</ul>
<p><strong>查询过程中的</strong> <strong>二分查找</strong> <strong>法</strong>：</p>
<h4>B+树索引的查询过程通常遵循以下步骤：</h4>
<ul>
 <li>
  <p><strong>从根节点开始</strong>：查询操作从B+树的根节点开始，根节点包含了指向子节点的指针和键值范围。</p>
 </li>
 <li>
  <p><strong>定位到相应的子节点</strong>：使用二分查找法在根节点的键值列表中找到合适的子节点。二分查找法可以快速定位到键值范围，从而确定进入哪个子节点。</p>
 </li>
 <li>
  <p><strong>遍历到</strong> <strong>叶子节点</strong>：根据找到的子节点，继续使用二分查找法在子节点中查找，逐步向下遍历到叶子节点。</p>
 </li>
 <li>
  <p><strong>在</strong> <strong>叶子节点</strong> <strong>中查找</strong>：到达叶子节点后，继续使用二分查找法在叶子节点中的记录上进行查找，直到找到目标记录或确定记录不存在。</p>
 </li>
 <li>
  <p><strong>回表操作</strong>：如果查询是通过二级索引进行的，找到主键值后，还需要使用主键值在聚簇索引中进行一次回表查找，以获取完整的数据记录。</p>
 </li>
</ul>
<p>
 <br>
</p>
<p>以下是B+树索引查询过程的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F902907bfb9064db88096a0f1e92dbef0.png&amp;size=m" alt="">在这个图中，查询过程从根节点开始，通过二分查找法逐步定位到叶子节点，并在叶子节点中查找目标记录。如果查询的是二级索引，则需要进行回表操作到聚簇索引以获取完整的数据记录。</p>
<ol>
 <li></li>
</ol>
<h4>页目录和记录的关系分析</h4>
<p><strong>页目录的作用</strong>：</p>
<p>页目录是InnoDB数据页中的一个关键组成部分，它的作用类似于书籍的目录，提供了一种快速定位数据页内用户记录的方法。页目录的主要作用包括：</p>
<ul>
 <li>
  <p><strong>索引作用</strong>：页目录存储了用户记录的索引信息，包括记录的相对位置，使得可以快速跳转到页内的特定记录。</p>
 </li>
 <li>
  <p><strong>提高查询效率</strong>：通过页目录，可以避免线性地扫描整个数据页来查找记录，从而显著提高查询效率。</p>
 </li>
 <li>
  <p><strong>组织记录</strong>：页目录组织了数据页内的记录，使得记录不是简单的物理连续存储，而是通过逻辑上的索引关系进行组织。</p>
 </li>
 <li>
  <p><strong>支持</strong> <strong>二分查找</strong>：页目录中的记录索引允许使用二分查找算法快速定位到记录，进一步加快查找速度。</p>
 </li>
 <li>
  <p><strong>通过页目录快速定位记录</strong>：</p>
 </li>
 <li>
  <p><strong>确定记录位置</strong>：首先，通过页目录可以快速确定记录在数据页中的大致位置。页目录中的索引信息指向了数据页内不同记录的起始点。</p>
 </li>
 <li>
  <p><strong>使用</strong> <strong>二分查找</strong>：在页目录中，记录索引是有序排列的。利用二分查找算法，可以在页目录中快速定位到目标记录的索引项。</p>
 </li>
 <li>
  <p><strong>跳转到记录</strong>：一旦在页目录中确定了记录的索引项，就可以直接跳转到数据页中该记录的实际存储位置，从而获取记录数据。</p>
 </li>
 <li>
  <p><strong>减少I/O操作</strong>：通过页目录的快速定位，可以减少磁盘I/O操作的次数，因为不需要读取整个数据页来查找记录。</p>
 </li>
</ul>
<p>
 <br>
</p>
<p>以下是页目录和记录关系的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fce50988146204248ab7f25e9588a579e.png&amp;size=m" alt="">在这个图中，页目录包含了指向数据页内不同用户记录的索引项。查询操作通过使用二分查找在页目录中快速定位到特定的记录索引项，然后跳转到数据页中相应的用户记录，从而实现了快速定位记录的目的。</p>
<p>
 <br>
</p>
<h4>B+树索引的优化分析</h4>
<p><strong>索引失效的情况</strong>：</p>
<ul>
 <li>
  <p><strong>全值匹配失效</strong>：当查询条件不使用索引列的全部值时，可能导致索引失效。例如，如果索引是多列索引，但查询只使用了前几列。</p>
 </li>
 <li>
  <p><strong>使用非等值查询</strong> ：使用<code>LIKE</code>操作符进行非等值查询（如<code>LIKE '%value%'</code>）通常会导致索引失效。</p>
 </li>
 <li>
  <p><strong>类型不匹配</strong>：如果查询条件的数据类型与索引列的数据类型不一致，可能导致索引失效。</p>
 </li>
 <li>
  <p><strong>隐式转换</strong>：查询中的隐式类型转换，如将字符串转换为数字，可能导致索引失效。</p>
 </li>
 <li>
  <p><strong>函数操作</strong> ：在索引列上使用函数或计算表达式，如<code>DATE(column)</code>，可能导致索引失效。</p>
 </li>
 <li>
  <p><strong>NULL值查询</strong>：某些情况下，查询包含NULL值的列可能导致索引失效。</p>
 </li>
</ul>
<p><strong>索引优化策略</strong>：</p>
<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>：在某些情况下，使用索引扩展（如MySQL的INDEX EXTENDED）来包含更多的列。</p>
 </li>
 <li>
  <p><strong>索引压缩</strong>：使用索引压缩技术减少索引的大小，提高索引的I/O性能。</p>
 </li>
 <li>
  <p><strong>监控和调整</strong>：定期监控查询性能和索引使用情况，根据实际使用情况调整索引策略。</p>
 </li>
 <li>
  <p><strong>部分索引</strong>：对于大型表，考虑创建部分索引，只对满足特定条件的行创建索引。</p>
 </li>
 <li>
  <p><strong>使用合适的索引类型</strong>：根据数据特性和查询需求，选择合适的索引类型，如B-Tree、Hash、R-Tree等。</p>
 </li>
</ul>
<h4>图形化展示</h4>
<p>以下是B+树索引优化策略的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F95a0845d0d454f3e976d1b7e8c101729.png&amp;size=m" alt="">在这个图中，B+树索引优化涉及多个策略，每个策略都有其具体的方法和目的，共同目标是提高数据库查询性能和减少不必要的资源消耗。</p>
<ol>
 <li></li>
</ol>
<h4>InnoDB的内存管理分析</h4>
<p><strong>InnoDB如何使用</strong> <strong>内存</strong>：</p>
<ol>
 <li>
  <p><strong>缓冲池（Buffer Pool）</strong>：</p>
 </li>
 <li>
  <p>缓冲池是InnoDB内存管理的核心，用于存储数据页和索引页，减少磁盘I/O操作。</p>
 </li>
 <li>
  <p>缓冲池中的数据页可以是"干净"的（没有修改）或"脏"的（已被修改但尚未写回磁盘）。</p>
 </li>
 <li>
  <p><strong>日志缓冲（</strong> <strong>Log</strong> <strong>Buffer）</strong>：</p>
 </li>
 <li>
  <p>日志缓冲用于暂存事务日志，在事务提交时写入磁盘，保证事务的持久性。</p>
 </li>
 <li>
  <p><strong>额外</strong> <strong>内存</strong> <strong>池（Additional Memory Pool）</strong>：</p>
 </li>
 <li>
  <p>InnoDB使用额外的内存池来分配内存，用于存储临时表、工作表等。</p>
 </li>
 <li>
  <p><strong>自适应哈希索引（Adaptive Hash Index）</strong>：</p>
 </li>
 <li>
  <p>可选的内存结构，用于加速等值查询，通过哈希表提高查找效率。</p>
 </li>
 <li>
  <p><strong>锁信息（Lock Information）</strong>：</p>
 </li>
 <li>
  <p>内存用于存储行级锁和表级锁的状态信息。</p>
 </li>
 <li>
  <p><strong>数据字典（Data Dictionary）</strong>：</p>
 </li>
 <li>
  <p>存储有关表结构和元数据的信息。</p>
 </li>
</ol>
<p><strong>内存</strong> <strong>管理对性能的影响</strong>：</p>
<ol>
 <li>
  <p><strong>减少I/O操作</strong>：</p>
 </li>
 <li>
  <p>缓冲池减少了对磁盘的访问次数，因为频繁访问的数据页可以保留在内存中。</p>
 </li>
 <li>
  <p><strong>提高缓存命中率</strong>：</p>
 </li>
 <li>
  <p>通过有效的内存管理，可以提高缓存命中率，从而提高数据库查询和更新的性能。</p>
 </li>
 <li>
  <p><strong>快速事务处理</strong>：</p>
 </li>
 <li>
  <p>日志缓冲和缓冲池的协同工作，可以加快事务的提交过程，减少写延迟。</p>
 </li>
 <li>
  <p><strong>避免</strong> <strong>内存</strong> <strong>碎片</strong>：</p>
 </li>
 <li>
  <p>InnoDB的内存管理策略有助于避免内存碎片，保持内存的连续性，提高内存使用效率。</p>
 </li>
 <li>
  <p><strong>提升并发性能</strong>：</p>
 </li>
 <li>
  <p>锁信息的内存管理有助于有效控制并发访问，减少锁争用。</p>
 </li>
 <li>
  <p><strong>自适应性能优化</strong>：</p>
 </li>
 <li>
  <p>自适应哈希索引可以根据查询模式动态调整，优化查询性能。</p>
 </li>
 <li>
  <p><strong>内存</strong> <strong>不足时的性能下降</strong>：</p>
 </li>
 <li>
  <p>如果内存不足，InnoDB可能需要频繁地将脏页刷新到磁盘，这会降低性能。</p>
 </li>
</ol>
<h4>图形化展示</h4>
<p>以下是InnoDB内存管理的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fadf5fb7d1f7d4f1d8d3f0014d8ed0b44.png&amp;size=m" alt="">在这个图中，InnoDB的内存管理涉及多个组件，每个组件都对数据库性能有直接影响。通过有效的内存管理，可以显著提高数据库操作的速度和效率。</p>
<h4>事务和锁机制分析</h4>
<p><strong>事务</strong> <strong>的概念和特性</strong>：</p>
<p><strong>事务定义</strong>：</p>
<ul>
 <li>事务是数据库操作的一个单元，可以包含一个或多个数据库操作（如插入、更新、删除）。</li>
</ul>
<p><strong>ACID特性</strong>：</p>
<ul>
 <li>
  <p><strong>原子性（Atomicity）</strong>：事务中的所有操作要么全部完成，要么全部不完成，不会结束在中间某个点。</p>
 </li>
 <li>
  <p><strong>一致性（Consistency）</strong>：事务必须保证数据库从一个一致的状态转移到另一个一致的状态。</p>
 </li>
 <li>
  <p><strong>隔离性（Isolation）</strong>：并发执行的事务之间不会互相影响。</p>
 </li>
 <li>
  <p><strong>持久性（Durability）</strong>：一旦事务提交，它对数据库的改变就是永久性的，即使系统发生故障也不会丢失。</p>
 </li>
</ul>
<p><strong>事务的生命周期</strong>：</p>
<ul>
 <li>事务从开始到结束会经历几个阶段：开始、活动、提交、回滚和结束。</li>
</ul>
<p><strong>InnoDB中的锁机制</strong>：</p>
<ul>
 <li>
  <p><strong>行级锁</strong>：</p>
 </li>
 <li>
  <p>InnoDB支持行级锁定，可以锁定涉及特定行的事务，提供较高的并发性。</p>
 </li>
 <li>
  <p><strong>表级锁</strong>：</p>
 </li>
 <li>
  <p>除了行级锁，InnoDB也可以使用表级锁，但通常由MySQL服务器层实现。</p>
 </li>
 <li>
  <p><strong>间隙锁（</strong> <strong>Gap</strong> <strong>Locks）</strong>：</p>
 </li>
 <li>
  <p>锁定一个范围内的记录，但不包括记录本身，用于防止其他事务插入间隙。</p>
 </li>
 <li>
  <p><strong>记录锁（Record Locks）</strong>：</p>
 </li>
 <li>
  <p>直接锁定涉及的特定记录。</p>
 </li>
 <li>
  <p><strong>意向锁（Intention Locks）</strong>：</p>
 </li>
 <li>
  <p>表明事务打算在更高级别上请求锁，分为意向共享锁和意向排它锁。</p>
 </li>
 <li>
  <p><strong>自增锁（Auto-increment Locks）</strong>：</p>
 </li>
 <li>
  <p>用于管理自增字段的值，防止多个事务同时插入新的行。</p>
 </li>
 <li>
  <p><strong>临键锁（Next-Key Locks）</strong>：</p>
 </li>
 <li>
  <p>结合了记录锁和间隙锁，锁定一个记录和它前面的间隙。</p>
 </li>
 <li>
  <p><strong>锁的</strong> <strong>兼容性</strong>：</p>
 </li>
 <li>
  <p>InnoDB定义了锁的兼容性规则，决定了不同事务可以同时持有的锁类型。</p>
 </li>
 <li>
  <p><strong>死锁</strong> <strong>检测</strong>：</p>
 </li>
 <li>
  <p>InnoDB具有死锁检测机制，可以发现并解决死锁问题。</p>
 </li>
</ul>
<h4>图形化展示</h4>
<p>以下是InnoDB事务和锁机制的Mermaid图形化展示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fef0994656a824ed194cd3a62fbde9f87.png&amp;size=m" alt="">在这个图中，InnoDB的事务和锁机制是确保数据库操作正确性和并发控制的关键部分。事务的ACID特性确保了操作的可靠性，而锁机制则管理了并发事务之间的资源访问，防止数据冲突和不一致。</p>
<h4>总结</h4>
<h5>B+树索引的优势和适用场景：</h5>
<ol>
 <li>
  <p><strong>优势</strong>：</p>
 </li>
 <li>
  <p><strong>高效的读写操作</strong>：B+树的高度较低，减少了磁盘I/O次数，提高了读写效率。</p>
 </li>
 <li>
  <p><strong>有序的数据组织</strong>：数据在B+树叶子节点中有序排列，便于进行范围查询和排序操作。</p>
 </li>
 <li>
  <p><strong>灵活的索引结构</strong>：支持聚簇索引和二级索引，适应不同的查询需求。</p>
 </li>
 <li>
  <p><strong>高效的缓存利用</strong>：B+树结构适合于分页存储，提高了内存缓存的利用率。</p>
 </li>
 <li>
  <p><strong>适用场景</strong>：</p>
 </li>
 <li>
  <p><strong>大量数据的存储</strong>：适用于需要存储大量数据的场景，如OLTP（在线事务处理）系统。</p>
 </li>
 <li>
  <p><strong>频繁的范围查询</strong>：适合需要频繁执行范围查询和排序操作的业务。</p>
 </li>
 <li>
  <p><strong>高并发环境</strong>：在高并发访问的情况下，B+树索引可以提供稳定的查询性能。</p>
 </li>
</ol>
<h5>InnoDB存储引擎的特点和使用建议：</h5>
<ol>
 <li>
  <p><strong>特点</strong>：</p>
 </li>
 <li>
  <p><strong>支持ACID事务</strong>：保证事务的原子性、一致性、隔离性和持久性。</p>
 </li>
 <li>
  <p><strong>行级锁定</strong>：提高了并发性能，减少了锁争用。</p>
 </li>
 <li>
  <p><strong>外键</strong> <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> <strong>聚簇索引</strong>：优先使用主键或唯一索引作为聚簇索引，以提高数据访问速度。</p>
 </li>
 <li>
  <p><strong>避免大范围更新</strong>：避免对大量数据进行频繁的更新操作，以减少缓冲池中脏页的写回。</p>
 </li>
 <li>
  <p><strong>合理设计索引</strong>：根据查询模式设计合适的索引，避免冗余索引，减少索引维护开销。</p>
 </li>
 <li>
  <p><strong>监控和优化</strong>：定期监控InnoDB的性能指标，根据实际情况进行优化调整。</p>
 </li>
 <li>
  <p><strong>使用事务</strong>：在进行数据库操作时，充分利用InnoDB的事务特性，保证数据操作的一致性和可靠性。</p>
 </li>
</ol>
<p>
 <br>
</p>
<p>以下是B+树索引优势和InnoDB特点的Mermaid图形化总结：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3ffb7adc4fd24406a2e00d37873c6750.png&amp;size=m" alt="">在这个图中，B+树索引和InnoDB存储引擎的优势与特点被清晰地展示出来，以及它们在不同场景下的应用建议。</p>
<p>原文链接: https://blog.csdn.net/z3551906947/article/details/141071389</p>]]></description><guid isPermaLink="false">/archives/9c55702c-e067-4c09-b065-f9966d8d2579</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Sun, 29 Sep 2024 08:45:05 GMT</pubDate></item><item><title><![CDATA[【数据结构】排序算法---直接插入排序]]></title><link>https://wiki.dgfygk.com/archives/c98540cb-01e0-4baf-85ee-4a2f832b3383</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E3%80%91%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95---%E7%9B%B4%E6%8E%A5%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F&amp;url=/archives/c98540cb-01e0-4baf-85ee-4a2f832b3383" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcb42c86711344b038ce377cc4cc85f82.gif%23pic_center&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1__4">1. 定义</a></li>
 <li><a href="https://wiki.dgfygk.com#2__15">2. 算法步骤</a></li>
 <li><a href="https://wiki.dgfygk.com#3__31">3. 动图演示</a></li>
 <li><a href="https://wiki.dgfygk.com#4__36">4. 性质</a></li>
 <li><a href="https://wiki.dgfygk.com#5__54">5. 算法分析</a></li>
 <li><a href="https://wiki.dgfygk.com#6__58">6. 代码实现</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#C_60">C语言</a></li>
 <li><a href="https://wiki.dgfygk.com#Python_77">Python</a></li>
 <li><a href="https://wiki.dgfygk.com#Java_91">Java</a></li>
 <li><a href="https://wiki.dgfygk.com#C_125">C++</a></li>
 <li><a href="https://wiki.dgfygk.com#Go_141">Go</a></li>
 <li><a href="https://wiki.dgfygk.com#7__158">7. 折半插入排序</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#C_166">代码实现——C++</a></li>
 <li><a href="https://wiki.dgfygk.com#_181">结语</a></li>
</ul>
<p>
 <br>
</p>
<ol>
 <li>
  <h2>定义</h2>
 </li>
</ol>
<p>直接插入排序是一种简单直观的排序算法。它的工作原理为将待排列元素划分为「已排序」和「未排序」两部分，每次从「未排序的」元素中选择一个插入到「已排序的」元素中的正确位置。</p>
<p>直接插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴，但它的原理应该是最容易理解的了，因为只要打过扑克牌的人都应该能够秒懂。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F71a029095e3449459d9d46203f6a27a7.gif%23pic_center&amp;size=m" alt=""></p>
<p>直接插入排序和冒泡排序一样，也有一种优化算法，叫做<strong>折半插入</strong>。</p>
<ol>
 <li>
  <h2>算法步骤</h2>
 </li>
</ol>
<p>一般来说，直接插入排序都采用in-place在数组上实现。具体算法描述如下：</p>
<ol>
 <li>
  <p>从第一个元素开始，该元素可以认为已经被排序；</p>
 </li>
 <li>
  <p>取出下一个元素，在已经排序的元素序列中从后向前扫描；</p>
 </li>
 <li>
  <p>如果该元素（已排序）大于新元素，将该元素移到下一位置；</p>
 </li>
 <li>
  <p>重复步骤3，直到找到已排序的元素小于或者等于新元素的位置；</p>
 </li>
 <li>
  <p>将新元素插入到该位置后；</p>
 </li>
 <li>
  <p>重复步骤2\~5。</p>
 </li>
 <li>
  <h2>动图演示</h2>
 </li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6b26cc0edb504aae9e9b1769c1ceb113.gif%23pic_center&amp;size=m" alt=""></p>
<ol>
 <li>
  <h2>性质</h2>
 </li>
</ol>
<p><strong>稳定性</strong>：</p>
<p>直接插入排序是一种稳定的排序算法。</p>
<p><strong>空间复杂度</strong>：</p>
<p>直接插入排序的空间复杂度为 O ( 1 ) O(1) O(1)。</p>
<p><strong>时间复杂度</strong>：</p>
<ul>
 <li>
  <p>元素集合越接近有序，直接插入排序算法的时间效率越高。</p>
 </li>
 <li>
  <p>直接插入排序的最优时间复杂度为 O ( n ) O(n) O(n)，在数列几乎有序时效率很高。</p>
 </li>
 <li>
  <p>直接插入排序的最坏时间复杂度和平均时间复杂度都为 O ( n 2 ) O(n^2) O(n2)。</p>
 </li>
 <li>
  <h2>算法分析</h2>
 </li>
</ul>
<p>直接插入排序在实现上，通常采用in-place排序（即只需用到O(1)的额外空间的排序），因而在从后向前扫描过程中，需要反复把已排序元素逐步向后挪位，为最新元素提供插入空间。</p>
<ol>
 <li>
  <h2>代码实现</h2>
 </li>
</ol>
<h3>C语言</h3>
<pre><code class="prism language-c">void insertion_sort(int arr[], int len){

        int i,j,key;
        for (i=1;i&lt;len;i++){

                key = arr[i];
                j=i-1;
                while((j&gt;=0) &amp;&amp; (arr[j]&gt;key)) {

                        arr[j+1] = arr[j];
                        j--;
                }
                arr[j+1] = key;
        }
}
</code></pre>
<h3>Python</h3>
<pre><code class="prism language-python">def insertionSort(arr):
    for i in range(len(arr)):
        preIndex = i-1
        current = arr[i]
        while preIndex &gt;= 0 and arr[preIndex] &gt; current:
            arr[preIndex+1] = arr[preIndex]
            preIndex-=1
        arr[preIndex+1] = current
    return arr
</code></pre>
<h3>Java</h3>
<pre><code class="prism language-java">public class InsertSort implements IArraySort {


    @Override
    public int[] sort(int[] sourceArray) throws Exception {

        // 对 arr 进行拷贝，不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        // 从下标为1的元素开始选择合适的位置插入，因为下标为0的只有一个元素，默认是有序的
        for (int i = 1; i &lt; arr.length; i++) {


            // 记录要插入的数据
            int tmp = arr[i];

            // 从已经排序的序列最右边的开始比较，找到比其小的数
            int j = i;
            while (j &gt; 0 &amp;&amp; tmp &lt; arr[j - 1]) {

                arr[j] = arr[j - 1];
                j--;
            }

            // 存在比其小的数，插入
            if (j != i) {

                arr[j] = tmp;
            }

        }
        return arr;
    }
}
</code></pre>
<h3>C++</h3>
<pre><code class="prism language-c">void insertion_sort(int arr[],int len){

        for(int i=1;i&lt;len;i++){

                int key=arr[i];
                int j=i-1;
                while((j&gt;=0) &amp;&amp; (key&lt;arr[j])){

                        arr[j+1]=arr[j];
                        j--;
                }
                arr[j+1]=key;
        }
}
</code></pre>
<h3>Go</h3>
<pre><code class="prism language-go">func insertionSort(arr []int) []int {

        for i := range arr {

                preIndex := i - 1
                current := arr[i]
                for preIndex &gt;= 0 &amp;&amp; arr[preIndex] &gt; current {

                        arr[preIndex+1] = arr[preIndex]
                        preIndex -= 1
                }
                arr[preIndex+1] = current
        }
        return arr
}
</code></pre>
<ol>
 <li>
  <h2>折半插入排序</h2>
 </li>
</ol>
<p>直接插入排序还可以通过<strong>二分算法</strong>优化性能，在排序元素数量较多时优化的效果比较明显。</p>
<p><strong>时间复杂度：</strong></p>
<p>折半插入排序与直接插入排序的基本思想是一致的，折半插入排序仅对插入排序时间复杂度中的常数进行了优化，所以优化后的时间复杂度仍然不变。</p>
<h3>代码实现——C++</h3>
<pre><code class="prism language-c">void insertion_sort(int arr[], int len) {

  if (len &lt; 2) return;
  for (int i = 1; i != len; ++i) {

    int key = arr[i];
    auto index = upper_bound(arr, arr + i, key) - arr;
    // 使用 memmove 移动元素，比使用 for 循环速度更快，时间复杂度仍为 O(n)
    memmove(arr + index + 1, arr + index, (i - index) * sizeof(int));
    arr[index] = key;
  }
}
</code></pre>
<h2>结语</h2>
<p>今天的分享到这里就结束啦！如果觉得文章还不错的话，可以三连支持一下。</p>
<p>也可以点点关注，避免以后找不到我哦！</p>
<p><a href="https://blog.csdn.net/2301_80191662?type=lately">Crossoads主页</a>还有很多有趣的文章，欢迎小伙伴们前去点评，您的支持就是作者前进的动力！</p>
<blockquote>
 <p>带你初步了解排序算法：<a href="https://blog.csdn.net/2301_80191662/article/details/142211265">https://blog.csdn.net/2301_80191662/article/details/142211265</a>
  <br>
  直接插入排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142300973">https://blog.csdn.net/2301_80191662/article/details/142300973</a>
  <br>
  希尔排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142302553">https://blog.csdn.net/2301_80191662/article/details/142302553</a>
  <br>
  直接选择排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142312028">https://blog.csdn.net/2301_80191662/article/details/142312028</a>
  <br>
  堆排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142312338">https://blog.csdn.net/2301_80191662/article/details/142312338</a>
  <br>
  冒泡排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142324131">https://blog.csdn.net/2301_80191662/article/details/142324131</a>
  <br>
  快速排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142324307">https://blog.csdn.net/2301_80191662/article/details/142324307</a>
  <br>
  归并排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142350640">https://blog.csdn.net/2301_80191662/article/details/142350640</a>
  <br>
  计数排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142350741">https://blog.csdn.net/2301_80191662/article/details/142350741</a>
  <br>
  桶排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142375338">https://blog.csdn.net/2301_80191662/article/details/142375338</a>
  <br>
  基数排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142375592">https://blog.csdn.net/2301_80191662/article/details/142375592</a>
  <br>
  十大经典排序算法总结与分析：<a href="https://blog.csdn.net/2301_80191662/article/details/142211564">https://blog.csdn.net/2301_80191662/article/details/142211564</a></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2035e539424d4dda9c654a983a264452.gif%23pic_center&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/2301_80191662/article/details/142300973</p>]]></description><guid isPermaLink="false">/archives/c98540cb-01e0-4baf-85ee-4a2f832b3383</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:44:51 GMT</pubDate></item><item><title><![CDATA[数据结构之LRUCache]]></title><link>https://wiki.dgfygk.com/archives/a0b04d90-e900-412f-9d29-a0616da56d8e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B9%8BLRUCache&amp;url=/archives/a0b04d90-e900-412f-9d29-a0616da56d8e" width="1" height="1" alt="" style="opacity:0;">
<p>找往期文章包括但不限于本期文章中不懂的知识点：</p>
<blockquote>
 <p><strong>个人主页：</strong> <a href="https://blog.csdn.net/2301_80854132?type=blog" title="我要学编程(ಥ_ಥ)-CSDN博客">我要学编程(ಥ_ಥ)-CSDN博客</a></p>
 <p><strong>所属专栏：</strong><strong></strong><strong></strong><strong></strong><strong></strong><strong></strong><strong></strong><strong></strong><a href="https://blog.csdn.net/2301_80854132/category_12650676.html" title="数据结构（Java版）">数据结构（Java版）</a><strong></strong><strong></strong><strong></strong><strong></strong><strong></strong><strong></strong><strong></strong><strong></strong></p>
</blockquote>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#LRU%20Cache%E7%9A%84%E6%A6%82%E5%BF%B5%C2%A0">LRU Cache的概念</a></p>
<p><a href="https://wiki.dgfygk.com#LRU%20Cache%E7%9A%84%E5%AE%9E%E7%8E%B0">LRU Cache的实现</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0LRU%20Cache">模拟实现LRU Cache</a></p>
<p><a href="https://wiki.dgfygk.com#%E7%9B%B8%E5%85%B3%E7%BB%83%E4%B9%A0%C2%A0">相关练习</a></p>
<hr>
<h2>LRU Cache的概念</h2>
<p><strong>LRU是Least Recently Used的缩写，意思是最近最少使用，它是一种Cache替换算法</strong> 。什么是Cache？狭义的Cache指的是位于CPU和主存间的快速RAM（random-access memory的缩写，即我们日常生活中所说的运行内存），通常它不像系统主存那样使用DRAM技术，而使用昂贵但较
 <br>
 快速的SRAM技术。广义上的Cache指的是位于速度相差较大的两种硬件之间，用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache，内存与硬盘之间也有Cache，乃至在硬盘与网络之间也有某种意义上的Cache一称为lnternetl临时文件夹或网络内容缓存等。
 <br>
 Cache的容量有限，因此当Cache的容量用完后，而又有新的内容需要添加进来时，就需要挑选并舍弃原有的部分内容，从而腾出空间来放新内容。LRUCache的替换原则就是将最近最少使用的内容替换掉。其实，LRU译成最久未使用会更形象，因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。</p>
<h2>LRU Cache的实现</h2>
<p>实现LRU Cache的方法和思路很多，但是要保持高效实现O(1)的put和get，那么使用双向链表和哈希表的搭配是最高效和经典的。使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除，使用哈希表是因为哈希表的增删查改也是O(1)。</p>
<p>在Java的集合类中有一个类和LRU Cache的实现类似——LinkedHashMap，其底层也是哈希表+双向链表。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9ce1cf54592f4c7daa8dbd25b9b3384e.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F09224a722da94be19384af01005c4d25.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6d9acb3c08f4487281f31d59105ab322.png&amp;size=m" alt=""></p>
<p>每次使用get方法，访问数据后，会把数据放到当前双向链表的最后。
 <br>
 当accessOrder为true时，get方法和put方法都会调用recordAccess方法使得最近使用的Entry移到双向链表的末尾；当accessOrder为默认值false时，从源码中可以看出recordAccess方法什么也不会做。</p>
<h2>模拟实现LRU Cache</h2>
<p>模式实现LRU Cache主要就是实现两个方法：put 和 get。</p>
<p><strong>前期准备：</strong></p>
<p>哈希表+双向链表：</p>
<pre><code class="java">    // 节点
    static class ListNode{
        public int key;
        public int val;
        public ListNode prev;
        public ListNode next;

        public ListNode(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
    // 这个哈希表中存放的是一个一个的节点，快速查找
    public HashMap&lt;Integer, ListNode&gt; hashMap;
    // 带头节点和尾节点便于修改，速度非常快
    public ListNode head;
    public ListNode tail;
    public int capacity; // LRU Cache容量
    public int usedSize; // 记录使用个数

    public LRUCache(int capacity) {
        this.hashMap = new HashMap&lt;&gt;();
        this.head = new ListNode(-1, -1);
        this.tail = new ListNode(-1, -1);
        this.head.next = this.tail;
        this.tail.prev = this.head;
        this.capacity = capacity;
    }
</code></pre>
<p><strong>实现 put 方法：</strong></p>
<p>思路： 首先，得在哈希表中查找是否存在该key。如果存在，只需要更新其 val值即可，然后再把这个节点移动到链表的尾部（先删除，在尾插）；如果不存在，就新实例化一个节点，然后再尾插到链表尾部，接着usedSize++，检查容量是否满了。满了就得删除头结点（最近最久未被使用）</p>
<p>代码实现：</p>
<pre><code class="java">    public void put(int key, int val) {
        // 首先得查找hashmap中是否有这个元素
        ListNode node = hashMap.get(key);
        if (node == null) {
            ListNode newNode = new ListNode(key, val);
            hashMap.put(key, newNode); // 哈希表存一份
            usedSize++; // 个数++
            addToTail(newNode); // 把该节点移到链表末尾
            if (usedSize &gt; capacity) {
                // 移除链表的头节点
                ListNode head = moveHead();
                // 将哈希表中的元素移除
                hashMap.remove(head.key);
                usedSize--;
            }
        } else {
            // 存在这个元素，更新val即可
            node.val = val; // 这里就已经把哈希表中的val更新了
            moveTail(node);
        }
    }

    private ListNode moveHead() {
        ListNode del = head.next;
        head.next = del.next;
        del.next.prev = head;
        return del;
    }

    private void moveTail(ListNode node) {
        // 先删除node节点，在尾插到末尾
        removeNode(node);
        addToTail(node);
    }

    private void addToTail(ListNode node) {
        node.next = tail;
        node.prev = tail.prev;
        tail.prev.next = node;
        tail.prev = node;
    }

    private void removeNode(ListNode newNode) {
        newNode.prev.next = newNode.next;
        newNode.next.prev = newNode.prev;
    }
</code></pre>
<p><strong>实现 get 方法：</strong></p>
<p>思路： 直接在哈希表中查找。如果不存在，返回-1；如果存在，就返回其对应的val，并在此之前把这个节点移动到链表的尾部。</p>
<p>代码实现：</p>
<pre><code class="java">    public Integer get(int key) {
        if (hashMap.get(key) == null) {
            return -1;
        } else {
            // 把这个移到链表尾部
            moveTail(hashMap.get(key));
            return hashMap.get(key).val;
        }
    }
</code></pre>
<h2>相关练习</h2>
<p><a href="https://leetcode.cn/problems/lru-cache/description/" title="146. LRU缓存">146. LRU缓存</a></p>
<p>题目：</p>
<blockquote>
 <p>请你设计并实现一个满足 <a href="https://baike.baidu.com/item/LRU" title="LRU (最近最少使用) 缓存">LRU (最近最少使用) 缓存</a> 约束的数据结构。</p>
 <p>实现 <code>LRUCache</code> 类：</p>
 <ul>
  <li><code>LRUCache(int capacity)</code> 以 <strong>正整数</strong> 作为容量 <code>capacity</code> 初始化 LRU 缓存</li>
  <li><code>int get(int key)</code> 如果关键字 <code>key</code> 存在于缓存中，则返回关键字的值，否则返回 <code>-1</code> 。</li>
  <li><code>void put(int key, int value)</code> 如果关键字 <code>key</code> 已经存在，则变更其数据值 <code>value</code> ；如果不存在，则向缓存中插入该组 <code>key-value</code> 。如果插入操作导致关键字数量超过 <code>capacity</code> ，则应该 <strong>逐出</strong> 最久未使用的关键字。</li>
 </ul>
 <p>函数 <code>get</code> 和 <code>put</code> 必须以 <code>O(1)</code> 的平均时间复杂度运行。</p>
 <p>
  <br>
 </p>
 <p><strong>示例：</strong></p>
 <pre><code>输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废，缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废，缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4
</code></pre>
 <p><strong>提示：</strong></p>
 <ul>
  <li><code>1 &lt;= capacity &lt;= 3000</code></li>
  <li><code>0 &lt;= key &lt;= 10000</code></li>
  <li><code>0 &lt;= value &lt;= 105</code></li>
  <li>最多调用 <code>2 * 105</code> 次 <code>get</code> 和 <code>put</code></li>
 </ul>
</blockquote>
<p>思路一：就是直接手动实现一个LRU Cache，也就是我们模拟实现LRU Cache的代码。</p>
<p>思路二：在Java中LInkedHashMap是类似于LRU Cache的，因此我们直接用这个来写也是可以的。</p>
<pre><code class="java">class LRUCache extends LinkedHashMap&lt;Integer, Integer&gt;{
    private int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75F, true);
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    // 注意：这个是用来判断是否需要移除元素的，因此一定要重写（默认是false）
    @Override
    protected boolean removeEldestEntry(Map.Entry&lt;Integer, Integer&gt; eldest) {
        return size() &gt; capacity; 
    }
}
</code></pre>
<p>好啦！本期 数据结构之LRUCache 的学习之旅就到此结束啦！我们下一期再一起学习吧！</p>
<p>原文链接: https://blog.csdn.net/2301_80854132/article/details/141409321</p>]]></description><guid isPermaLink="false">/archives/a0b04d90-e900-412f-9d29-a0616da56d8e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:44:38 GMT</pubDate></item><item><title><![CDATA[XGBOOST算法Python实现（保姆级）]]></title><link>https://wiki.dgfygk.com/archives/87e51b4d-931b-4296-9b1a-7a6317e58fb6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=XGBOOST%E7%AE%97%E6%B3%95Python%E5%AE%9E%E7%8E%B0%EF%BC%88%E4%BF%9D%E5%A7%86%E7%BA%A7%EF%BC%89&amp;url=/archives/87e51b4d-931b-4296-9b1a-7a6317e58fb6" width="1" height="1" alt="" style="opacity:0;">
<p><strong>摘要</strong></p>
<p>XGBoost算法（eXtreme Gradient Boosting）在目前的Kaggle、数学建模和大数据应用等竞赛中非常流行。本文将会从XGBOOST算法原理、Python实现、敏感性分析和实际应用进行详细说明。</p>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#0%20%E7%BB%AA%E8%AE%BA">0 绪论</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E6%9D%90%E6%96%99%E5%87%86%E5%A4%87">一、材料准备</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E7%AE%97%E6%B3%95%E5%8E%9F%E7%90%86">二、算法原理</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E7%AE%97%E6%B3%95Python%E5%AE%9E%E7%8E%B0">三、算法Python实现</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.1%20%E6%95%B0%E6%8D%AE%E5%8A%A0%E8%BD%BD">3.1 数据加载</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.2%20%E5%B0%86%E7%9B%AE%E6%A0%87%E5%8F%98%E9%87%8F%E7%9A%84%E5%AE%9A%E7%B1%BB%E6%95%B0%E6%8D%AE%E5%88%86%E7%B1%BB%E7%BC%96%E7%A0%81">3.2 将目标变量的定类数据分类编码</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.3%C2%A0%E5%B0%86%E6%95%B0%E6%8D%AE%E5%88%86%E4%B8%BA%E8%AE%AD%E7%BB%83%E6%95%B0%E6%8D%AE%E5%92%8C%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE">3.3 将数据分为训练数据和测试数据</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.4%E8%AE%AD%E7%BB%83XGBOOST%E6%A8%A1%E5%9E%8B">3.4训练XGBOOST模型</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.5%20%E6%B5%8B%E8%AF%95%E6%A8%A1%E5%9E%8B">3.5 测试模型</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.6%20%E8%BE%93%E5%87%BA%E6%A8%A1%E5%9E%8B%E7%9A%84%E9%A2%84%E6%B5%8B%E6%B7%B7%E6%B7%86%E7%9F%A9%E9%98%B5%EF%BC%88%E7%BB%93%E6%9E%9C%E7%9F%A9%E9%98%B5%EF%BC%89">3.6 输出模型的预测混淆矩阵（结果矩阵）</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.7%20%E8%BE%93%E5%87%BA%E6%A8%A1%E5%9E%8B%E5%87%86%E7%A1%AE%E7%8E%87">3.7 输出模型准确率</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.8%20%E7%BB%98%E5%88%B6%E6%B7%B7%E6%B7%86%E7%9F%A9%E9%98%B5%E5%9B%BE">3.8 绘制混淆矩阵图</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%C2%A03.9%20%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0%E4%BB%A3%E7%A0%81">3.9 完整实现代码</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%203.10%20%E7%BB%93%E6%9E%9C%E8%BE%93%E5%87%BA%E7%A4%BA%E4%BE%8B">3.10 结果输出示例</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%20XGBOOST%E7%AE%97%E6%B3%95%E7%9A%84%E6%95%8F%E6%84%9F%E6%80%A7%E5%88%86%E6%9E%90%E5%92%8C%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8">四、 XGBOOST算法的敏感性分析和实际应用</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%204.1%20%E6%95%8F%E6%84%9F%E6%80%A7%E5%88%86%E6%9E%90">4.1 敏感性分析</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%204.2%20%E7%AE%97%E6%B3%95%E5%BA%94%E7%94%A8">4.2 算法应用</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%E7%BB%93%E8%AE%BA">五、结论</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%85%AD%E3%80%81%E5%A4%87%E6%B3%A8">六、备注</a></p>
<p>
 <br>
</p>
<h2><strong>0</strong> <strong>绪论</strong></h2>
<p>数据挖掘和数学建模等比赛中，除了算法的实现，还需要对数据进行较为合理的预处理，包括缺失值处理、异常值处理、定类数据特征编码和冗余特征的删除等等，本文默认读者的数据均已完成数据预处理，如有需要，后续会将数据预处理的方法也进行发布。</p>
<h2><strong>一、材料准备</strong></h2>
<p>Python编译器：Pycharm社区版或个人版等</p>
<p>训练数据集：此处使用2022年数维杯国际大学生数学建模竞赛C题的附件数据为例。</p>
<p>数据处理：经过初步数据清洗和相关性分析等操作得到初步的特征，并利用决策树进行特征重要性分析，完成二次特征降维，得到'CDRSB_bl', 'PIB_bl', 'FBB_bl'三个自变量特征，DX_bl为分类特征。</p>
<p>
 <br>
</p>
<h2><strong>二、算法原理</strong></h2>
<p>XGBOOST算法基于决策树的集成方法，主要采用了Boosting的思想，是Gradient Boosting算法的扩展，并使用梯度提升技术来提高模型的准确性和泛化能力。</p>
<p>首先将基分类器层层叠加，然后每一层在训练的时候，对前一层基分类器分错的样本，给予更高的权重，XGBOOST的目标函数为：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2f6807a3def2fb0da50d4c5a444f6673.png&amp;size=m" alt=""></p>
<p>(1)</p>
<p>其中，<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4258dd4448e2090fce4491347a171de3.png&amp;size=m" alt="">为损失函数；<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F268162a57c5f3a6645301af6d80bd537.png&amp;size=m" alt="">为正则项，用于控制树的复杂度；<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3ca4e41c8dee3a210089d3eaeff0e091.png&amp;size=m" alt="">为常数项，<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fad978c0974c40422842717c3028927e5.png&amp;size=m" alt="">为新树的预测值<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F253bb7cab3ce5dcb3effc31b482fa39f.png&amp;size=m" alt="">，它是将树的个数的结果进行求和。</p>
<h2><strong>三、算法</strong> <strong>Python</strong> <strong>实现</strong></h2>
<h3><strong>3.1 数据加载</strong></h3>
<p>此处导入本文所需数据，DataX为自变量数据，DataY为目标变量数据（DX_bl）。</p>
<pre><code class="python">import pandas as pd
X = pd.DataFrame(pd.read_excel('DataX.xlsx')).values  # 输入特征
y = pd.DataFrame(pd.read_excel('DataY.xlsx')).values  # 目标变量
</code></pre>
<h3>3.2 将目标变量的定类数据分类编码</h3>
<p>此处仅用0-4来代替五类数据，因为此处仅做预测，并不涉及相关性分析等其他操作，所以普通的分类编码就可以。如果需要用来做相关性分析或其他计算型操作，建议使用独热编码（OneHot- Encoding）。</p>
<pre><code class="python">from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y = le.fit_transform(y)
label_mapping = {0: 'AD', 1: 'CN', 2: 'EMCI', 3: 'LMCI', 4: 'SMC'}
#此处为了后续输出混淆矩阵时，用原始数据输出
</code></pre>
<h3>3.3 将数据分为训练数据和测试数据</h3>
<p>本文将原始样本数据通过随机洗牌，并将70%的样本数据作为训练数据，30%的样本数据作为测试数据。这是一个较为常见的拆分方法，读者可通过不同的拆分测试最佳准确率和F1-score。</p>
<pre><code class="python">from sklearn.model_selection import train_test_split
# 将数据分为训练数据和测试数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, train_size=0.7, random_state=42)
</code></pre>
<h3>3.4训练XGBOOST模型</h3>
<p>基于70%的样本数据进行训练建模，python有XGBOOST算法的库，所以很方便就可以调用。</p>
<pre><code class="python">import xgboost as xgb
# 训练XGBoost分类器
model = xgb.XGBClassifier()
model.fit(X_train, y_train)
#xgb.plot_tree(model)
</code></pre>
<h3>3.5 测试模型</h3>
<p>利用另外的30%样本数据进行测试模型准确率、精确率、召回率和F1度量值。</p>
<pre><code class="python"># 使用测试数据预测类别
y_pred = model.predict(X_test)
</code></pre>
<h3>3.6 输出模型的预测混淆矩阵（结果矩阵）</h3>
<p>此处输出混淆矩阵的方法和之前的随机森林、KNN算法都有点不同，因为随机森拉算法不需要将定类数据进行分类编码就可以直接调用随机森林算法模型。</p>
<pre><code class="python">from sklearn.metrics import confusion_matrix, classification_report, accuracy_score
cm = confusion_matrix(y_test, y_pred)
# 输出混淆矩阵
for i, true_label in enumerate(label_mapping.values()):
    row = ''
    for j, pred_label in enumerate(label_mapping.values()):
        row += f'{cm[i, j]} ({pred_label})\t'
    print(f'{row} | {true_label}')

# 输出混淆矩阵
print(classification_report(y_test, y_pred,target_names=['AD', 'CN', 'EMCI', 'LMCI', 'SMC']))  # 输出混淆矩阵
</code></pre>
<h3>3.7 输出模型准确率</h3>
<pre><code class="python">#此处的导库在上一个代码段中已引入
print("Accuracy:")
print(accuracy_score(y_test, y_pred))
</code></pre>
<h3>3.8 绘制混淆矩阵图</h3>
<p>将混淆矩阵结果图绘制并输出，可以将这一结果图放在论文中，提升论文美感和信服度。</p>
<pre><code class="python">import matplotlib.pyplot as plt
import numpy as np
label_names = ['AD', 'CN', 'EMCI', 'LMCI', 'SMC']
cm = confusion_matrix(y_test, y_pred)

# 绘制混淆矩阵图
fig, ax = plt.subplots()
im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
ax.figure.colorbar(im, ax=ax)
ax.set(xticks=np.arange(cm.shape[1]),
       yticks=np.arange(cm.shape[0]),
       xticklabels=label_names, yticklabels=label_names,
       title='Confusion matrix',
       ylabel='True label',
       xlabel='Predicted label')

# 在矩阵图中显示数字标签
thresh = cm.max() / 2.
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(j, i, format(cm[i, j], 'd'),
                ha="center", va="center",
                color="white" if cm[i, j] &gt; thresh else "black")

fig.tight_layout()
#plt.show()
plt.savefig('XGBoost_Conclusion.png', dpi=300)
</code></pre>
<p>上面的代码首先计算混淆矩阵，然后使用 matplotlib 库中的 imshow 函数将混淆矩阵可视化，最后通过 text 函数在混淆矩阵上添加数字，并使用 show/savefig 函数显示图像，结果输出如图3.1所示。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7b092f4dea293064e2c100c16c162e4c.png&amp;size=m" alt=""></p>
<p>图3.1 混淆矩阵结果图</p>
<h3>3.9 完整实现代码</h3>
<pre><code class="python"># 导入需要的库
from sklearn.preprocessing import LabelEncoder
import matplotlib.pyplot as plt
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report, accuracy_score
import numpy as np

le = LabelEncoder()
label_mapping = {0: 'AD', 1: 'CN', 2: 'EMCI', 3: 'LMCI', 4: 'SMC'}
X = pd.DataFrame(pd.read_excel('DataX.xlsx')).values  # 输入特征
y = pd.DataFrame(pd.read_excel('DataY.xlsx')).values  # 目标变量
y = le.fit_transform(y)
# 将数据分为训练数据和测试数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, train_size=0.7, random_state=42)
# 训练XGBoost分类器
model = xgb.XGBClassifier()
model.fit(X_train, y_train)
#xgb.plot_tree(model)
# 使用测试数据预测类别
y_pred = model.predict(X_test)
cm = confusion_matrix(y_test, y_pred)
# 输出混淆矩阵
for i, true_label in enumerate(label_mapping.values()):
    row = ''
    for j, pred_label in enumerate(label_mapping.values()):
        row += f'{cm[i, j]} ({pred_label})\t'
    print(f'{row} | {true_label}')

# 输出混淆矩阵
print(classification_report(y_test, y_pred,target_names=['AD', 'CN', 'EMCI', 'LMCI', 'SMC']))  # 输出混淆矩阵
print("Accuracy:")
print(accuracy_score(y_test, y_pred))


# label_names 是分类变量的取值名称列表
label_names = ['AD', 'CN', 'EMCI', 'LMCI', 'SMC']
cm = confusion_matrix(y_test, y_pred)

# 绘制混淆矩阵图
fig, ax = plt.subplots()
im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
ax.figure.colorbar(im, ax=ax)
ax.set(xticks=np.arange(cm.shape[1]),
       yticks=np.arange(cm.shape[0]),
       xticklabels=label_names, yticklabels=label_names,
       title='Confusion matrix',
       ylabel='True label',
       xlabel='Predicted label')

# 在矩阵图中显示数字标签
thresh = cm.max() / 2.
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(j, i, format(cm[i, j], 'd'),
                ha="center", va="center",
                color="white" if cm[i, j] &gt; thresh else "black")

fig.tight_layout()
#plt.show()
plt.savefig('XGBoost_Conclusion.png', dpi=300)
# 上面的代码首先计算混淆矩阵，然后使用 matplotlib 库中的 imshow 函数将混淆矩阵可视化，最后通过 text 函数在混淆矩阵上添加数字，并使用 show/savefig 函数显示图像。
</code></pre>
<h3>3.10 结果输出示例</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3f28791d50b74e95595c69ddcc95b182.png&amp;size=m" alt=""></p>
<p>图3.2 结果输出示例</p>
<h2>四、 XGBOOST算法的敏感性分析和实际应用</h2>
<h3>4.1 敏感性分析</h3>
<p>敏感性分析也叫做稳定性分析，可以基于统计学思想，通过百次测试，记录其准确率、精确率、召回率和F1-Score的数据，统计其中位数、平均值、最大值和最小值等数据，从而进行对应的敏感性分析。结果表明符合原模型成立，则通过了敏感性分析。前面的随机森林算法和KNN算法也是如此。</p>
<h3>4.2 算法应用</h3>
<p>XGBOOST算法可应用于大数据分析、预测等方面，尤其是大数据竞赛（Kaggle、阿里天池等竞赛中）特别常用，也是本人目前认为最好用的一个算法。</p>
<h2>五、结论</h2>
<p>本文基于XGBOOST算法，从数据预处理、算法原理、算法实现、敏感性分析和算法应用都做了具体的分析，可适用于大部分机器学习算法初学者。</p>
<h2>六、备注</h2>
<p>本文为原创文章，禁止转载，违者必究。如需原始数据，可点赞+收藏，然后私聊作者或在评论区中留下你的邮箱，即可获得训练数据一份。</p>
<p>原文链接: https://blog.csdn.net/m0_61399808/article/details/129718219</p>]]></description><guid isPermaLink="false">/archives/87e51b4d-931b-4296-9b1a-7a6317e58fb6</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Sun, 29 Sep 2024 08:44:20 GMT</pubDate></item><item><title><![CDATA[SM3杂凑算法与实现]]></title><link>https://wiki.dgfygk.com/archives/aa0fe2a7-8e52-4d6e-86b7-92a5069d3a3f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SM3%E6%9D%82%E5%87%91%E7%AE%97%E6%B3%95%E4%B8%8E%E5%AE%9E%E7%8E%B0&amp;url=/archives/aa0fe2a7-8e52-4d6e-86b7-92a5069d3a3f" width="1" height="1" alt="" style="opacity:0;">
<h2>一、实验目的</h2>
<p>SM3 杂凑算法与实现</p>
<p>（1）理解 Hash 函数的计算原理和特点</p>
<p>（2）掌握 SM3 杂凑算法原理</p>
<p>（3）编程实现 SM3 杂凑算法</p>
<p>
 <br>
</p>
<p>硬件：运行 Windows 操作系统的计算机</p>
<p>软件：Python</p>
<h2>二、方案设计</h2>
<h3><strong></strong>背景<strong></strong></h3>
<p>SM3 是我国采用的一种密码散列函数标准，由国家密码管理局于2010 年12 月17日发布。《SM3 密码杂凑算法》于2012 年发布为密码行业标准（GM/T 0004-2012），2016年发布为国家标准（GB/T 32905-2016）。2018 年10 月，含有我国《SM3 密码杂凑算法》的ISO/IEC 10118-3：2018《信息安全技术杂凑函数第3 部分：专用杂凑函数》第4 版由ISO 发布，《SM3 密码杂凑算法》正式称为国际标准。在商用密码体系中，SM3 主要用于数字签名及验证、消息认证码生成及验证、随机数生成等，其算法公开。据国家密码管理局表示，其安全性及效率与SHA-256 相当。在实现上，SM3 密码杂凑算法运算速率高、灵活灵用、支持跨平台的高效实现，具有较好的实现效能。</p>
<h3><strong></strong>原理<strong></strong></h3>
<h4><strong></strong>1、术语与定义:<strong></strong></h4>
<p>（1）比特串 bit string</p>
<p>由 0 和 1 组成的二进制数字序列。</p>
<p>（2）大端 big-endian</p>
<p>数据在内存中的一种表示格式，规定左边为高有效位，右边为低有效位。数的高阶字节放在存储器的低地址，数的低阶字节放在存储器的高地址。</p>
<p>（3）消息 message</p>
<p>任意有限长度的比特串。本文本中消息作为杂凑算法的输入数据。</p>
<p>（4）杂凑值 hash value</p>
<p>杂凑算法作用于消息后输出的特定长度的比特串。本文本中的杂凑值长度为 256 比 特。</p>
<p>（5）字 word</p>
<p>长度为 32 位的比特串</p>
<h4><strong></strong>2、符号<strong></strong></h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2fb30a5aa4064fc0b65e8bb9269d3806.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fac1094465b304d988a541db43665f17c.png&amp;size=m" alt=""></p>
<h4><strong></strong>3.常量与函数<strong></strong></h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa288cf5913584dacb72f2fb278b112f7.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F68daa250bc07447bbf5747ef5a9d9977.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3b827348ac6241f4a0eaf1f7558e4da1.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9b4cabd71d844b01aeaf54cfe79d98c6.png&amp;size=m" alt=""></p>
<h4><strong></strong>4、算法描述<strong></strong></h4>
<p>
 <br>
</p>
<p>SM3 密码杂凑算法基于 Merkle-Damgard 结构，杂凑函数 H 可将一个任意有限比特长度的消息 m 压缩到某一固定长度为 n 比特的杂凑值 h，即</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Flatex.csdn.net%2Feq%3FH%2528m%2529%253Dh&amp;size=m" alt=""></p>
<p>SM3 密码杂凑算法是对长度为<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Flatex.csdn.net%2Feq%3F1bit%25281%253C2%255E%257B64%257D%2529&amp;size=m" alt="">的消息 m 进行填充和迭代压缩生成杂凑值，杂凑值的长度为 256 bit。 整个算法的执行过程可以概括为四个步骤：消息填充、消息扩展、迭代压缩、输出结果。</p>
<h5><strong></strong>消息填充：<strong></strong></h5>
<p>假设消息 m 的长度为1比特。首先将比特” 1"添加到消息的末尾，再添加 k 个” 0”，k 是满足
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Flatex.csdn.net%2Feq%3F1%2Bl%2Bk%255Cequiv%2520448%2520%2528mod%2520512%2529&amp;size=m" alt=""> 的最小的正整数。然后再添加一个 64 位的比特串，该比特串是长度
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Flatex.csdn.net%2Feq%3Fl&amp;size=m" alt="">的二进制表示。填充后的消息 m′ 的比特长度为 512 的倍数。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9303dbd5d97e48dabcc778bb998d1ef5.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h5><strong></strong>消息扩展：<strong></strong></h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F804ce7903b614e9eacbfadd0414fff34.png&amp;size=m" alt=""></p>
<h5>迭代压缩：</h5>
<h6>迭代过程</h6>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F338b5af44dc34bb0971bf090cde79ae7.png&amp;size=m" alt=""></p>
<h6><strong></strong>压缩函数<strong></strong></h6>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F16544b42abcc4413a07500544ac21d26.png&amp;size=m" alt=""></p>
<h2>三、方案实现</h2>
<h3><strong></strong>1.流程图<strong></strong></h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F13e869952a574e0598d5a46e03d216b0.png&amp;size=m" alt=""></p>
<p><strong></strong>图1 SM3算法实现流程图<strong></strong></p>
<p>
 <br>
</p>
<h3><strong></strong>2.主要函数<strong></strong></h3>
<p>def pad_message(message):——实现消息填充。由于先添加1，对应于16进制即为\x80，再填0补全至448mod 512，最后在加入消息长度的2进制比特串。</p>
<p>def SM3_CF(V, B):——实现压缩迭代。初始V为IV默认值，每次返回与前一次V的级联结果。按照SM3的算法流程实现压缩和迭代，其中，消息扩展也在本环节中进行，</p>
<p>def or_16(A, B):——实现两个字符串按位与或，返回整数C。</p>
<p>def LS(X, n):——实现循环左移。将左右个移动，再连接起来，实现循环。</p>
<h3>3.代码实现</h3>
<pre><code class="python">import binascii
import re
# SM3的初始向量
IV = [
    0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600,
    0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E
]

# 布尔函数FFj


def FF(X, Y, Z, j):
    if j &lt; 16:
        return X ^ Y ^ Z
    else:
        return (X &amp; Y) | (X &amp; Z) | (Y &amp; Z)

# 置换函数


def P0(X):
    return X ^ LS(X, 9) ^ LS(X, 17)


def P1(X):
    return X ^ LS(X, 15) ^ LS(X, 23)

# 循环左移


def LS(X, n):
    return ((X &lt;&lt; n) &amp; 0xFFFFFFFF) | (X &gt;&gt; (32 - n))

# 布尔函数GGj


def GG(X, Y, Z, j):
    if j &lt; 16:
        return X ^ Y ^ Z
    else:
        return (X &amp; Y) | (~X &amp; Z)

# 消息填充函数


def pad_message(message):
    mlen1 = len(message)
    mlen = mlen1
    message += b'\x80'
    mlen += 1
    while mlen % 64 != 56:
        message += b'\x00'
        mlen += 1
    message += (mlen1 * 8).to_bytes(8, 'big')
    return message

# SM3压缩函数


def SM3_CF(V, B, k):
    W = [0] * 68
    W_ = [0] * 64
    for i in range(16):
        W[i] = int.from_bytes(B[i*4:i*4+4], 'big')
    for i in range(16, 68):
        W[i] = P1(W[i-16] ^ W[i-9] ^ LS(W[i-3], 15)) ^ LS(W[i-13], 7) ^ W[i-6]
    for i in range(64):
        W_[i] = W[i] ^ W[i+4]
    for i in range(0, len(W), 4):
        print(' '.join(hex(value) for value in W[i:i+4]))

    A, B, C, D, E, F, G, H = V
    for i in range(64):
        SS1 = LS((LS(A, 12) + E + LS(T(i), i % 32)) &amp; 0xFFFFFFFF, 7)
        SS2 = SS1 ^ LS(A, 12)
        TT1 = (FF(A, B, C, i) + D + SS2 + W_[i]) &amp; 0xFFFFFFFF
        TT2 = (GG(E, F, G, i) + H + SS1 + W[i]) &amp; 0xFFFFFFFF
        D = C
        C = LS(B, 9)
        B = A
        A = TT1
        H = G
        G = LS(F, 19)
        F = E
        E = P0(TT2)
        print(
            f"Step {i+1} - A: {A:08X}, B: {B:08X}, C: {C:08X}, D: {D:08X}, E: {E:08X}, F: {F:08X}, G: {G:08X}, H: {H:08X}")
    return A, B, C, D, E, F, G, H

# T函数


def T(j):
    if j &lt; 16:
        return 0x79CC4519
    else:
        return 0x7A879D8A

# SM3哈希函数


def string_to_ascii(s):
    return [ord(c) for c in s]


def or_16(A, B):
    A = int(A, 16)
    B = int(B, 16)
    C = A ^ B
    C = '{:08x}'.format(C)
    return C
# SM3哈希函数


def SM3(message):
    ascii_values = string_to_ascii(message)
    message_bytes = bytes(ascii_values)
    padded_message = pad_message(message_bytes)
    result = ''
    for i in IV:
        result += '{:08x}'.format(i)
    V = IV.copy()
    for i in range(len(padded_message) // 64):
        B = padded_message[i*64:(i+1)*64]
        V = SM3_CF(V, B, i)
        hex_string1 = binascii.hexlify(B).decode()

        formatted_hex_string1 = re.sub(r'(.{8})', r'\1 ', hex_string1)
        print(
            f"Extended Message at Step {i+1}: {formatted_hex_string1}")
        all = ''
        for iii in V:
            all += '{:08x}'.format(iii)
        result = or_16(all, result)

    return result


# 示例
message = 'abc'
hash_value = SM3(message)
formatted_hex_string2 = re.sub(r'(.{8})', r'\1 ', hash_value)
print("Hash Value:", formatted_hex_string2)
</code></pre>
<h2>四、数据分析</h2>
<h3><strong></strong>1. SM3执行结果<strong></strong></h3>
<p><strong>测试数据</strong>：abc</p>
<p><strong>中间数据</strong>：</p>
<p>填充完的消息：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff2f4057e1a0640ec9797023a0de5474b.png&amp;size=m" alt=""></p>
<p><strong></strong>图<strong></strong> <strong></strong>2<strong></strong> <strong></strong>SM3算法<strong></strong> <strong></strong>填充<strong></strong> <strong></strong>后的消息<strong></strong> <strong></strong>结果<strong></strong> <strong></strong>图<strong></strong></p>
<p>扩展后的消息：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb308dec843874f778e8d2f96a4ce9ace.png&amp;size=m" alt=""></p>
<p><strong></strong>图<strong></strong> <strong></strong>3<strong></strong> <strong></strong>SM3算法扩展后的消息<strong></strong> <strong></strong>结果<strong></strong> <strong></strong>图<strong></strong></p>
<p>每一轮的V：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F647b325e78cb461bbbdd9cd6af053abd.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Faab190dbfa634a389f59a1c3b3c8eb97.png&amp;size=m" alt=""></p>
<p><strong></strong>图<strong></strong> <strong></strong>5 SM3算法中间数据V的数据结果图<strong></strong></p>
<p><strong>杂凑值：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3933aead8ae04220850c99799e11b0e9.png&amp;size=m" alt=""></p>
<p><strong></strong>图<strong></strong> <strong></strong>4<strong></strong> <strong></strong>SM3算法<strong></strong> <strong></strong>杂凑结果<strong></strong> <strong></strong>图<strong></strong></p>
<p>符合测试样例，算法执行成功。</p>
<h3><strong></strong>2.SM3的安全性分析<strong></strong></h3>
<h4><strong></strong>2.1 优势<strong></strong></h4>
<p>
 <br>
</p>
<p><strong>高抗碰撞性：</strong>SM3算法经过严格的安全性分析和测试，具有较高的抗碰撞性和抗预处理性，能够有效地保护数据的完整性和真实性。这意味着已知一个哈希结果，构建出具有相同结果的输入数据的难度非常大。</p>
<p><strong>与国际标准相当：</strong>SM3算法在结构上与SHA-256相似，但通过增加多种新的设计技术，使其在安全性和效率上具有优势。尽管SM3算法的字典库远小于SHA-256，但其破解难度更大，因此在实际应用中具有更高的安全性。</p>
<p><strong>抵抗常见攻击：</strong>SM3算法被设计为能够抵抗各种常见的密码分析攻击，包括碰撞攻击和预像攻击。虽然某些版本的SM3算法可能存在漏洞，如原根攻击和伪碰撞攻击，但完整的SM3算法仍然具有非常高的安全性。</p>
<p><strong>高于其他算法：</strong>SM3算法的安全性要高于MD5算法和SHA-1算法。这是因为这些算法已经被证明存在严重的安全缺陷，而SM3算法则在设计和实现上更加精细和安全。</p>
<p><strong>广泛应用和认可：</strong>SM3算法已经被46类重要的经济行业规范所采纳，并且在数字认证、金融密码系统、国家电网、社会保障信息系统等领域得到了广泛应用。这进一步证明了其在实际应用中的高安全性和可靠性。</p>
<p>SM3算法在安全性方面表现优异，能够有效抵抗各种常见的密码分析攻击，并且在国际标准上具有相当的水平。其高抗碰撞性、高效性以及广泛的应用领域，使其成为一个值得信赖的密码学哈希函数。</p>
<h4><strong></strong>2.2风险<strong></strong></h4>
<p>SM3算法作为一种密码杂凑函数，自2012年发布为行业标准，并在2016年成为国家标准。然而，近年来对其安全性进行了深入研究，发现了一些潜在的漏洞。</p>
<p>2021年2月，蜚语安全在对客户产品进行安全代码审计时，发现其依赖的微信小程序sm-crypto 开源NodeJS国密算法库中的SM3算法实现存在问题。进一步的研究表明，对于带消息填充的29步SM3算法，可以通过中间相遇攻击技术进行原根攻击和伪碰撞攻击。具体来说，从第1步开始的带消息填充的29步SM3算法的原根攻击时间复杂度为2254，而伪碰撞攻击时间复杂度为2125。这说明该算法不能有效抵抗这些攻击。</p>
<p>长度扩展攻击是指针对某些允许包含额外信息的加密散列函数的攻击手段。对于满足以下条件的散列函数，都可以作为攻击对象：① 加密前将待加密的明文按一定规则填充到固定长度（例如512或1024比特）的倍数；② 按照该规则进行加密。虽然证据中没有直接提到SM3算法是否受到长度扩展攻击，但考虑到其设计特性，这种攻击可能也会影响SM3的安全性。</p>
<h2>五、思考与总结</h2>
<p>
 <br>
</p>
<h3>1.请简述SM3与MD5、SHA-1的异同。</h3>
<p>答：</p>
<p>SM3、MD5和SHA-1均属于散列函数，它们的主要功能是接收任意长度的数据输入，并输出固定长度的散列值，这个散列值通常被称为"消息摘要”。这些算法广泛应用于数据完整性验证和密码学领域。</p>
<h4><strong></strong>相同点:<strong></strong></h4>
<p><strong>数据完整性:</strong>三者都用于确保数据在传输或存储过程中未被篡改。</p>
<p><strong>固定长度输出:</strong>不论输入数据的大小，输出的散列值长度是固定的。</p>
<p><strong>雪崩效应:</strong>输入数据的任何微小变化都会导致输出散列值的巨大变化。</p>
<p><strong>不可逆:</strong> 从散列值不能推导出原始输入数据。</p>
<h4><strong></strong>不同点:<strong></strong></h4>
<p><strong>设计标准:</strong> SM3是中国国家密码管理局制定的密码散列算法标准，而MD5和SHA-1是由国际组织或标准制定。</p>
<p><strong>安全性:</strong> MD5和SHA-1由于设计时间较早，已存在已知的安全问题和攻击方法，如碰撞攻击。SM3设计时考虑了这些安全问题，采用了更复杂的结构来提高抗攻击能力。</p>
<p><strong>输出长度:</strong> MD5产生128位的散列值，SHA-1产生160位的散列值，而SM3产生256位的散列值，提供了更高的安全边际。</p>
<p><strong>算法结构:</strong>SM3使用了更复杂的数学结构和算法流程，包括用于置乱消息的置换函数和用于压缩消息的布尔函数，这使得其对抵抗密码分析攻击更为强固。</p>
<h3>2.实验过程中还遇到了什么问题，如何解决的？通过该实验有何收获？</h3>
<p>答：</p>
<p><strong></strong>问题1：<strong></strong>在消息填充模块，算法实现时将mlen用于直接累加计数，导致原数据长度丢失。</p>
<p><strong></strong>方案：<strong></strong>设置一个变量，记录初始长度，在结尾将原mlen替换。</p>
<p><strong></strong>问题2：<strong></strong>在得到V后，如何输出杂凑函数值，如何级联?</p>
<p><strong></strong>方案：<strong></strong>属于概念未明晰，不知如何进行级联。第一轮的输出应该与初始向量IV级联，而后以此类推。而且，需要注意的是，在级联后，需要按位进行与或操作。</p>
<h3><strong></strong>收获：<strong></strong></h3>
<p>通过本次实验，我深入理解了SM3算法的原理和特点，尤其掌握了SM3算法的结构和实现方法，编程实践增强了我对该算法的理解并提升了我的技术能力，让我对密码学领域有了更深刻的认识。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/m0_74985950/article/details/140408432</p>]]></description><guid isPermaLink="false">/archives/aa0fe2a7-8e52-4d6e-86b7-92a5069d3a3f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:44:00 GMT</pubDate></item><item><title><![CDATA[国密SM2算法进行数据的加密、签名和验签、解密]]></title><link>https://wiki.dgfygk.com/archives/48d1bb66-f4ef-4f9c-8dee-0f336b5cc0d7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9B%BD%E5%AF%86SM2%E7%AE%97%E6%B3%95%E8%BF%9B%E8%A1%8C%E6%95%B0%E6%8D%AE%E7%9A%84%E5%8A%A0%E5%AF%86%E3%80%81%E7%AD%BE%E5%90%8D%E5%92%8C%E9%AA%8C%E7%AD%BE%E3%80%81%E8%A7%A3%E5%AF%86&amp;url=/archives/48d1bb66-f4ef-4f9c-8dee-0f336b5cc0d7" width="1" height="1" alt="" style="opacity:0;">
<p>写在前面：下面的依赖是基于JDK17的，如果自己的JDK版本过低，适当降低依赖的版本，具体适配去Maven官方仓库查询</p>
<h4>一、算法介绍</h4>
<p>SM2算法是中国密码学研究所（中国国家加密管理局）发布的一种非对称加密算法，适用于数字签名、密钥交换和公钥加密等场景。该算法基于椭圆曲线密码学，使用椭圆曲线上的点进行运算。</p>
<p>SM2算法的安全性主要基于椭圆曲线离散对数问题的难解性。它采用的是一个特定的椭圆曲线参数集，该参数集已经在国际上得到了广泛的认可。SM2算法的安全性与RSA算法和Diffie-Hellman算法相当。</p>
<p>SM2算法的主要特点如下：</p>
<ol>
 <li>安全性高：SM2算法基于椭圆曲线离散对数问题，具有较高的安全性。</li>
 <li>算法效率高：SM2算法的计算量相对较小，适合在资源受限的环境中使用。</li>
 <li>适用性广泛：SM2算法可用于数字签名、密钥交换和公钥加密等多种密码应用场景。</li>
 <li>算法标准化：SM2算法已被国际电信联盟（ITU-T）和国际标准化组织（ISO）认可为国际标准。</li>
</ol>
<p>总的来说，SM2算法是一种安全性高、效率高且广泛应用的非对称加密算法，被广泛应用于各种密码场景中。</p>
<h4>二、引入pom依赖</h4>
<pre><code class="java">&lt;dependency&gt;
    &lt;groupId&gt;org.bouncycastle&lt;/groupId&gt;
    &lt;artifactId&gt;bcprov-jdk15on&lt;/artifactId&gt;
    &lt;version&gt;1.70&lt;/version&gt;
&lt;/dependency&gt;
&lt;!--注意检查是否已经引入lombok依赖，已经引入则不需要此依赖--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
    &lt;artifactId&gt;lombok&lt;/artifactId&gt;
    &lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;
</code></pre>
<p>主要是引入了工具包和lombok依赖</p>
<h4>三、密钥对工具类</h4>
<pre><code class="java">package com.hl.sm2demo.util;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;

/**
 * @ Description 国密公私钥对工具类
 */
@Slf4j
public class KeyUtils {
    public static final String PUBLIC_KEY = "publicKey";

    public static final String PRIVATE_KEY = "privateKey";

    /**
     * 生成国密公私钥对
     */
    public static Map&lt;String, String&gt; generateSmKey() throws Exception {
        KeyPairGenerator keyPairGenerator = null;
        SecureRandom secureRandom = new SecureRandom();
        ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        keyPairGenerator = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
        keyPairGenerator.initialize(sm2Spec);
        keyPairGenerator.initialize(sm2Spec, secureRandom);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        String publicKeyStr = new String(Base64.getEncoder().encode(publicKey.getEncoded()));
        String privateKeyStr = new String(Base64.getEncoder().encode(privateKey.getEncoded()));
        return Map.of(PUBLIC_KEY, publicKeyStr, PRIVATE_KEY, privateKeyStr);
    }

    /**
     * 将Base64转码的公钥串，转化为公钥对象
     */
    public static PublicKey createPublicKey(String publicKey) {
        PublicKey publickey = null;
        try {
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
            KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
            publickey = keyFactory.generatePublic(publicKeySpec);
        } catch (Exception e) {
            log.error("将Base64转码的公钥串，转化为公钥对象异常：{}", e.getMessage(), e);
        }
        return publickey;
    }

    /**
     * 将Base64转码的私钥串，转化为私钥对象
     */
    public static PrivateKey createPrivateKey(String privateKey) {
        PrivateKey publickey = null;
        try {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
            KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
            publickey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception e) {
            log.error("将Base64转码的私钥串，转化为私钥对象异常：{}", e.getMessage(), e);
        }
        return publickey;
    }

}
</code></pre>
<p><strong>1.生成国密公私钥对</strong> ：
 <br>
 方法generateSmKey()使用KeyPairGenerator生成一个基于椭圆曲线"sm2p256v1"的KeyPair，这是SM2算法所使用的曲线。
 <br>
 公钥和私钥分别编码为Base64字符串，然后以键值对的形式存储在Map中，键分别为PUBLIC_KEY和PRIVATE_KEY。</p>
<p><strong>注意：实际开发中，我们可能会要求生成的公私钥对是稳定不变的，我们可以先提前使用上面的KeyUtils 工具类方法generateSmKey()生成好公私钥字符串，加密解密需要用到时，再使用其中的转公私钥对象方法createPublicKey()、createPrivateKey()。</strong></p>
<p><strong>2.公钥对象的转换</strong> ：
 <br>
 方法createPublicKey(String publicKey)接收一个Base64编码的公钥字符串，通过X509EncodedKeySpec解析成公钥对象。这里使用了KeyFactory实例化EC类型的公钥。</p>
<p><strong>3.私钥对象的转换</strong> ：
 <br>
 方法createPrivateKey(String privateKey)类似地，接收一个Base64编码的私钥字符串，通过PKCS8EncodedKeySpec解析成私钥对象。同样使用KeyFactory实例化EC类型的私钥。</p>
<h4>四、 SM2工具类</h4>
<pre><code class="java">package com.hl.sm2demo.util;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;

import java.security.*;

/**
 * @ Description SM2实现工具类
 */
@Slf4j
public class Sm2Util {
/*    这行代码是在Java中用于向安全系统添加Bouncy Castle安全提供器的。
    Bouncy Castle是一个流行的开源加密库，它提供了许多密码学算法和安全协议的实现。

    通过调用Security.addProvider并传入BouncyCastleProvider对象，你可以注册Bouncy Castle提供的安全服务和算法到Java的安全框架中。
    这样一来，你就可以在你的应用程序中使用Bouncy Castle所提供的加密算法、密钥生成和管理等功能。*/
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 根据publicKey对原始数据data，使用SM2加密
     */
    public static byte[] encrypt(byte[] data, PublicKey publicKey) {
        ECPublicKeyParameters localECPublicKeyParameters = getEcPublicKeyParameters(publicKey);
        SM2Engine localSM2Engine = new SM2Engine();
        localSM2Engine.init(true, new ParametersWithRandom(localECPublicKeyParameters, new SecureRandom()));
        byte[] arrayOfByte2;
        try {
            arrayOfByte2 = localSM2Engine.processBlock(data, 0, data.length);
            return arrayOfByte2;
        } catch (InvalidCipherTextException e) {
            log.error("SM2加密失败:{}", e.getMessage(), e);
            return null;
        }
    }

    private static ECPublicKeyParameters getEcPublicKeyParameters(PublicKey publicKey) {
        ECPublicKeyParameters localECPublicKeyParameters = null;
        if (publicKey instanceof BCECPublicKey localECPublicKey) {
            ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
            ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(),
                    localECParameterSpec.getG(), localECParameterSpec.getN());
            localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), localECDomainParameters);
        }
        return localECPublicKeyParameters;
    }

    /**
     * 根据privateKey对加密数据encode data，使用SM2解密
     */
    public static byte[] decrypt(byte[] encodeData, PrivateKey privateKey) {
        SM2Engine localSM2Engine = new SM2Engine();
        BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
        ECParameterSpec localECParameterSpec = sm2PriK.getParameters();
        ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(),
                localECParameterSpec.getG(), localECParameterSpec.getN());
        ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(sm2PriK.getD(),
                localECDomainParameters);
        localSM2Engine.init(false, localECPrivateKeyParameters);
        try {
            return localSM2Engine.processBlock(encodeData, 0, encodeData.length);
        } catch (InvalidCipherTextException e) {
            log.error("SM2解密失败:{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 私钥签名
     */
    public static byte[] signByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        sig.initSign(privateKey);
        sig.update(data);
        return sig.sign();
    }

    /**
     * 公钥验签
     */
    public static boolean verifyByPublicKey(byte[] data, PublicKey publicKey, byte[] signature) throws Exception {
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        sig.initVerify(publicKey);
        sig.update(data);
        return sig.verify(signature);
    }

}
</code></pre>
<p><strong>1.加密：</strong>
 <br>
 encrypt方法使用SM2算法对原始数据进行加密。它首先获取ECPublicKeyParameters对象，然后初始化SM2Engine并进行加密处理。如果加密过程中出现InvalidCipherTextException，则记录错误日志并返回null。</p>
<p><strong>2.解密：</strong>
 <br>
 decrypt方法根据私钥解密加密数据。同样，初始化SM2Engine，进行解密处理。如果解密过程中出现InvalidCipherTextException，则记录错误日志并返回null。</p>
<p><strong>3.签名：</strong>
 <br>
 signByPrivateKey方法使用私钥对数据进行签名。首先，创建一个Signature实例，指定SM2签名算法，初始化签名器，更新数据，然后生成签名。</p>
<p><strong>4.验证：</strong>
 <br>
 verifyByPublicKey方法使用公钥验证签名。创建Signature实例，指定SM2签名算法，初始化验证器，更新数据，然后执行验证。返回验证结果，即布尔值表示签名是否有效</p>
<h4>五、案例测试Junit</h4>
<pre><code class="java">package com.hl.sm2demo;

import com.hl.sm2demo.util.KeyUtils;
import com.hl.sm2demo.util.Sm2Util;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.Map;

@SpringBootTest
class Sm2DemoApplicationTests {

    PublicKey publicKey = null;
    PrivateKey privateKey = null;


    @Test
    public void test() throws Exception {
        //生成公私钥对
        Map&lt;String,String&gt; keys = KeyUtils.generateSmKey();

        String testStr = "hello JAVA";
        System.out.println("原始字符串：" + testStr);
        System.out.println("公钥：" + keys.get(KeyUtils.PUBLIC_KEY));
        publicKey = KeyUtils.createPublicKey(keys.get(KeyUtils.PUBLIC_KEY));

        System.out.println("私钥：" + keys.get(KeyUtils.PRIVATE_KEY));
        privateKey = KeyUtils.createPrivateKey(keys.get(KeyUtils.PRIVATE_KEY));

        System.out.println();

        //公钥加密
        byte[] encrypt = Sm2Util.encrypt(testStr.getBytes(), publicKey);
        //加密转base64
        String encryptBase64Str = Base64.getEncoder().encodeToString(encrypt);
        System.out.println("加密数据：" + encryptBase64Str);
        //私钥签名,方便对方收到数据后用公钥验签
        byte[] sign = Sm2Util.signByPrivateKey(testStr.getBytes(), privateKey);
        System.out.println("数据签名：" + Base64.getEncoder().encodeToString(sign));

        //公钥验签，验证通过后再进行数据解密
        boolean b = Sm2Util.verifyByPublicKey(testStr.getBytes(), publicKey, sign);
        System.out.println("数据验签：" + b);
        //私钥解密
        byte[] decode = Base64.getDecoder().decode(encryptBase64Str);
        byte[] decrypt = Sm2Util.decrypt(decode, privateKey);
        assert decrypt != null;
        System.out.println("解密数据：" + new String(decrypt));
    }
}
</code></pre>
<p>测试代码主要逻辑：</p>
<p>密钥准备：首先生成一个公私钥对字符串，再使用工具分别转换为公私钥的java对象；</p>
<p>加密过程：把数据使用公钥加密，把密文转换成base64编码格式，再用私钥签名；</p>
<p>解密过程：拿到加密数据后用公钥验签，以确保密文数据没有被第三方拦截篡改，验证通过后，base64解码，最后使用私钥进行解密，还原数据；</p>
<p>测试结果如下图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F275aaeef1a094a521ff6c352b13ff2ed.png&amp;size=m" alt=""></p>
<h4>六、总结</h4>
<p>SM2是一款优秀的国产加密算法，目前已经应用到多种领域内，保障了国家信息安全 ，我们日常开发中也可以尽量多使用SM系列的加密算法，如SM3、SM4等，支持国产从点点滴滴做起！</p>
<p>原文链接: https://blog.csdn.net/weixin_40141628/article/details/136057891</p>]]></description><guid isPermaLink="false">/archives/48d1bb66-f4ef-4f9c-8dee-0f336b5cc0d7</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:43:43 GMT</pubDate></item><item><title><![CDATA[非对称加密算法SM2]]></title><link>https://wiki.dgfygk.com/archives/14de0b31-f769-45b6-bc0e-2097e7799217</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95SM2&amp;url=/archives/14de0b31-f769-45b6-bc0e-2097e7799217" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>SM2是<a href="https://baike.baidu.com/item/%E5%9B%BD%E5%AE%B6%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%B1%80/2712999?fromModule=lemma_inlink" title="国家密码管理局">国家密码管理局</a>于2010年12月17日发布的椭圆曲线公钥密码算法。</p>
 <p>SM2算法和<a href="https://baike.baidu.com/item/RSA%E7%AE%97%E6%B3%95/263310?fromModule=lemma_inlink" title="RSA算法">RSA算法</a>都是公钥密码算法，SM2算法是一种更先进安全的算法，在我们国家<a href="https://baike.baidu.com/item/%E5%95%86%E7%94%A8%E5%AF%86%E7%A0%81/10636778?fromModule=lemma_inlink" title="商用密码">商用密码</a>体系中被用来替换<a href="https://baike.baidu.com/item/RSA%E7%AE%97%E6%B3%95/263310?fromModule=lemma_inlink" title="RSA算法">RSA算法</a>。</p>
 <p>随着密码技术和计算机技术的发展，目前常用的1024位RSA算法面临严重的安全威胁，我们国家密码管理部门经过研究，决定采用SM2<a href="https://baike.baidu.com/item/%E6%A4%AD%E5%9C%86%E6%9B%B2%E7%BA%BF%E7%AE%97%E6%B3%95/9533976?fromModule=lemma_inlink" title="椭圆曲线算法">椭圆曲线算法</a>替换RSA算法。</p>
</blockquote>
<h3>SM2算法和RSA算法比较</h3>
<p>SM2性能更优更安全：密码复杂度高、处理速度快、机器性能消耗更小</p>
<p>|——–|————-|————–|
 <br>
 | | SM2 | RSA |
 <br>
 | 算法结构 | 基本椭圆曲线（ECC） | 基于特殊的可逆模幂运算 |
 <br>
 | 计算复杂度 | 完全指数级 | 亚指数级 |
 <br>
 | 存储空间 | 192-256bit | 2048-4096bit |
 <br>
 | 秘钥生成速度 | 较RSA算法快百倍以上 | 慢 |
 <br>
 | 解密加密速度 | 较快 | 一般 |</p>
<h3>工具类如下：</h3>
<p>依赖pom：</p>
<pre><code class="hljs">        &lt;!-- 国密算法 --&gt;
        &lt;!-- 加密工具--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;cn.hutool&lt;/groupId&gt;
            &lt;artifactId&gt;hutool-all&lt;/artifactId&gt;
            &lt;version&gt;5.8.14&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- SM支持 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.bouncycastle&lt;/groupId&gt;
            &lt;artifactId&gt;bcprov-jdk16&lt;/artifactId&gt;
            &lt;version&gt;1.46&lt;/version&gt;
        &lt;/dependency&gt;
</code></pre>
<p>Cipher.java</p>
<pre><code class="java">import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;

public class Cipher {
    private int ct;
    private ECPoint p2;
    private SM3Digest sm3keybase;
    private SM3Digest sm3c3;
    private byte key[];
    private byte keyOff;

    public Cipher()
    {
        this.ct = 1;
        this.key = new byte[32];
        this.keyOff = 0;
    }

    private void Reset()
    {
        this.sm3keybase = new SM3Digest();
        this.sm3c3 = new SM3Digest();

        byte p[] = Util.byteConvert32Bytes(p2.getX().toBigInteger());
        this.sm3keybase.update(p, 0, p.length);
        this.sm3c3.update(p, 0, p.length);

        p = Util.byteConvert32Bytes(p2.getY().toBigInteger());
        this.sm3keybase.update(p, 0, p.length);
        this.ct = 1;
        NextKey();
    }

    private void NextKey()
    {
        SM3Digest sm3keycur = new SM3Digest(this.sm3keybase);
        sm3keycur.update((byte) (ct &gt;&gt; 24 &amp; 0xff));
        sm3keycur.update((byte) (ct &gt;&gt; 16 &amp; 0xff));
        sm3keycur.update((byte) (ct &gt;&gt; 8 &amp; 0xff));
        sm3keycur.update((byte) (ct &amp; 0xff));
        sm3keycur.doFinal(key, 0);
        this.keyOff = 0;
        this.ct++;
    }

    public ECPoint Init_enc(SM2 sm2, ECPoint userKey)
    {
        AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger k = ecpriv.getD();
        ECPoint c1 = ecpub.getQ();
        this.p2 = userKey.multiply(k);
        Reset();
        return c1;
    }

    public void Encrypt(byte data[])
    {
        this.sm3c3.update(data, 0, data.length);
        for (int i = 0; i &lt; data.length; i++)
        {
            if (keyOff == key.length)
            {
                NextKey();
            }
            data[i] ^= key[keyOff++];
        }
    }

    public void Init_dec(BigInteger userD, ECPoint c1)
    {
        this.p2 = c1.multiply(userD);
        Reset();
    }

    public void Decrypt(byte data[])
    {
        for (int i = 0; i &lt; data.length; i++)
        {
            if (keyOff == key.length)
            {
                NextKey();
            }
            data[i] ^= key[keyOff++];
        }

        this.sm3c3.update(data, 0, data.length);
    }

    public void Dofinal(byte c3[])
    {
        byte p[] = Util.byteConvert32Bytes(p2.getY().toBigInteger());
        this.sm3c3.update(p, 0, p.length);
        this.sm3c3.doFinal(c3, 0);
        Reset();
    }
}
</code></pre>
<p>SM2.java</p>
<pre><code class="java">import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECFieldElement.Fp;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.security.SecureRandom;

public class SM2 {

    //正式参数
    public static String[] ecc_param = {
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
            "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
    };

    public static SM2 Instance()
    {
        return new SM2();
    }

    public final BigInteger ecc_p;
    public final BigInteger ecc_a;
    public final BigInteger ecc_b;
    public final BigInteger ecc_n;
    public final BigInteger ecc_gx;
    public final BigInteger ecc_gy;
    public final ECCurve ecc_curve;
    public final ECPoint ecc_point_g;
    public final ECDomainParameters ecc_bc_spec;
    public final ECKeyPairGenerator ecc_key_pair_generator;
    public final ECFieldElement ecc_gx_fieldelement;
    public final ECFieldElement ecc_gy_fieldelement;

    public SM2()
    {
        this.ecc_p = new BigInteger(ecc_param[0], 16);
        this.ecc_a = new BigInteger(ecc_param[1], 16);
        this.ecc_b = new BigInteger(ecc_param[2], 16);
        this.ecc_n = new BigInteger(ecc_param[3], 16);
        this.ecc_gx = new BigInteger(ecc_param[4], 16);
        this.ecc_gy = new BigInteger(ecc_param[5], 16);

        this.ecc_gx_fieldelement = new Fp(this.ecc_p, this.ecc_gx);
        this.ecc_gy_fieldelement = new Fp(this.ecc_p, this.ecc_gy);

        this.ecc_curve = new ECCurve.Fp(this.ecc_p, this.ecc_a, this.ecc_b);
        this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement, this.ecc_gy_fieldelement);

        this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.ecc_n);

        ECKeyGenerationParameters ecc_ecgenparam;
        ecc_ecgenparam = new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom());

        this.ecc_key_pair_generator = new ECKeyPairGenerator();
        this.ecc_key_pair_generator.init(ecc_ecgenparam);
    }
}
</code></pre>
<p>SM2Utils.java</p>
<pre><code class="java">import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;

public class SM2Utils {

    //生成随机秘钥对
    public static void generateKeyPair(){
        SM2 sm2 = SM2.Instance();
        AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();

        System.out.println("公钥: " + Util.byteToHex(publicKey.getEncoded()));
        System.out.println("私钥: " + Util.byteToHex(privateKey.toByteArray()));
    }
    // 国密规范正式私钥
    private static String prik = "00C27C803367F9D6CFEC4B346E029DF63EEBF470036543D459A62E36C5C1A86925";
    // 国密规范正式公钥
    private static String pubk = "04BDF955A37EFAC70111B533F0DBFBB368C50B9015213C31359BA3FB3B600D0BD8A1BD2F152180AAB7448EB281C8CE3938CF1BE005D664950E6A9CCF3B10C61E52";
    //数据加密
    public static String encrypt(byte[] data) throws IOException
    {
        byte[]  publicKey =Util.hexToByte(pubk);
        if (data == null || data.length == 0)
        {
            return null;
        }

        byte[] source = new byte[data.length];
        System.arraycopy(data, 0, source, 0, data.length);

        Cipher cipher = new Cipher();
        SM2 sm2 = SM2.Instance();
        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);

        ECPoint c1 = cipher.Init_enc(sm2, userKey);
        cipher.Encrypt(source);
        byte[] c3 = new byte[32];
        cipher.Dofinal(c3);

        //C1 C2 C3拼装成加密字串
        return Util.byteToHex(c1.getEncoded()) + Util.byteToHex(source) + Util.byteToHex(c3);

    }

    //数据解密
    public static byte[] decrypt( byte[] encryptedData) throws IOException
    {
        byte[] privateKey=Util.hexToByte(prik);
        if (encryptedData == null || encryptedData.length == 0)
        {
            return null;
        }
        //加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2
        String data = Util.byteToHex(encryptedData);
        /***分解加密字串
         * （C1 = C1标志位2位 + C1实体部分128位 = 130）
         * （C3 = C3实体部分64位  = 64）
         * （C2 = encryptedData.length * 2 - C1长度  - C2长度）
         */
        byte[] c1Bytes = Util.hexToByte(data.substring(0,130));
        int c2Len = encryptedData.length - 97;
        byte[] c2 = Util.hexToByte(data.substring(130,130 + 2 * c2Len));
        byte[] c3 = Util.hexToByte(data.substring(130 + 2 * c2Len,194 + 2 * c2Len));

        SM2 sm2 = SM2.Instance();
        BigInteger userD = new BigInteger(1, privateKey);

        //通过C1实体字节来生成ECPoint
        ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);
        Cipher cipher = new Cipher();
        cipher.Init_dec(userD, c1);
        cipher.Decrypt(c2);
        cipher.Dofinal(c3);

        //返回解密结果
        return c2;
    }

    public static void main(String[] args) throws Exception
    {
        //生成公钥私钥
//        generateKeyPair();
        //加解密测试
        String a = "saassa";
        String j = SM2Utils.encrypt( a.getBytes());
        System.out.println("加密 = " + j);
        System.out.println("解密 = " + new String(SM2Utils.decrypt( Util.hexToByte(j))));
    }
}
</code></pre>
<p>SM3.java</p>
<pre><code class="java">public class SM3 {
    public static final byte[] iv = { 0x73, (byte) 0x80, 0x16, 0x6f, 0x49,
            0x14, (byte) 0xb2, (byte) 0xb9, 0x17, 0x24, 0x42, (byte) 0xd7,
            (byte) 0xda, (byte) 0x8a, 0x06, 0x00, (byte) 0xa9, 0x6f, 0x30,
            (byte) 0xbc, (byte) 0x16, 0x31, 0x38, (byte) 0xaa, (byte) 0xe3,
            (byte) 0x8d, (byte) 0xee, 0x4d, (byte) 0xb0, (byte) 0xfb, 0x0e,
            0x4e };

    public static int[] Tj = new int[64];

    static
    {
        for (int i = 0; i &lt; 16; i++)
        {
            Tj[i] = 0x79cc4519;
        }

        for (int i = 16; i &lt; 64; i++)
        {
            Tj[i] = 0x7a879d8a;
        }
    }

    public static byte[] CF(byte[] V, byte[] B)
    {
        int[] v, b;
        v = convert(V);
        b = convert(B);
        return convert(CF(v, b));
    }

    private static int[] convert(byte[] arr)
    {
        int[] out = new int[arr.length / 4];
        byte[] tmp = new byte[4];
        for (int i = 0; i &lt; arr.length; i += 4)
        {
            System.arraycopy(arr, i, tmp, 0, 4);
            out[i / 4] = bigEndianByteToInt(tmp);
        }
        return out;
    }

    private static byte[] convert(int[] arr)
    {
        byte[] out = new byte[arr.length * 4];
        byte[] tmp = null;
        for (int i = 0; i &lt; arr.length; i++)
        {
            tmp = bigEndianIntToByte(arr[i]);
            System.arraycopy(tmp, 0, out, i * 4, 4);
        }
        return out;
    }

    public static int[] CF(int[] V, int[] B)
    {
        int a, b, c, d, e, f, g, h;
        int ss1, ss2, tt1, tt2;
        a = V[0];
        b = V[1];
        c = V[2];
        d = V[3];
        e = V[4];
        f = V[5];
        g = V[6];
        h = V[7];

        int[][] arr = expand(B);
        int[] w = arr[0];
        int[] w1 = arr[1];

        for (int j = 0; j &lt; 64; j++)
        {
            ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(Tj[j], j));
            ss1 = bitCycleLeft(ss1, 7);
            ss2 = ss1 ^ bitCycleLeft(a, 12);
            tt1 = FFj(a, b, c, j) + d + ss2 + w1[j];
            tt2 = GGj(e, f, g, j) + h + ss1 + w[j];
            d = c;
            c = bitCycleLeft(b, 9);
            b = a;
            a = tt1;
            h = g;
            g = bitCycleLeft(f, 19);
            f = e;
            e = P0(tt2);

        }

        int[] out = new int[8];
        out[0] = a ^ V[0];
        out[1] = b ^ V[1];
        out[2] = c ^ V[2];
        out[3] = d ^ V[3];
        out[4] = e ^ V[4];
        out[5] = f ^ V[5];
        out[6] = g ^ V[6];
        out[7] = h ^ V[7];

        return out;
    }

    private static int[][] expand(int[] B)
    {
        int W[] = new int[68];
        int W1[] = new int[64];
        for (int i = 0; i &lt; B.length; i++)
        {
            W[i] = B[i];
        }

        for (int i = 16; i &lt; 68; i++)
        {
            W[i] = P1(W[i - 16] ^ W[i - 9] ^ bitCycleLeft(W[i - 3], 15))
                    ^ bitCycleLeft(W[i - 13], 7) ^ W[i - 6];
        }

        for (int i = 0; i &lt; 64; i++)
        {
            W1[i] = W[i] ^ W[i + 4];
        }

        int arr[][] = new int[][] { W, W1 };
        return arr;
    }

    private static byte[] bigEndianIntToByte(int num)
    {
        return back(Util.intToBytes(num));
    }

    private static int bigEndianByteToInt(byte[] bytes)
    {
        return Util.byteToInt(back(bytes));
    }

    private static int FFj(int X, int Y, int Z, int j)
    {
        if (j &gt;= 0 &amp;&amp; j &lt;= 15)
        {
            return FF1j(X, Y, Z);
        }
        else
        {
            return FF2j(X, Y, Z);
        }
    }

    private static int GGj(int X, int Y, int Z, int j)
    {
        if (j &gt;= 0 &amp;&amp; j &lt;= 15)
        {
            return GG1j(X, Y, Z);
        }
        else
        {
            return GG2j(X, Y, Z);
        }
    }

    // 逻辑位运算函数
    private static int FF1j(int X, int Y, int Z)
    {
        int tmp = X ^ Y ^ Z;
        return tmp;
    }

    private static int FF2j(int X, int Y, int Z)
    {
        int tmp = ((X &amp; Y) | (X &amp; Z) | (Y &amp; Z));
        return tmp;
    }

    private static int GG1j(int X, int Y, int Z)
    {
        int tmp = X ^ Y ^ Z;
        return tmp;
    }

    private static int GG2j(int X, int Y, int Z)
    {
        int tmp = (X &amp; Y) | (~X &amp; Z);
        return tmp;
    }

    private static int P0(int X)
    {
        int y = rotateLeft(X, 9);
        y = bitCycleLeft(X, 9);
        int z = rotateLeft(X, 17);
        z = bitCycleLeft(X, 17);
        int t = X ^ y ^ z;
        return t;
    }

    private static int P1(int X)
    {
        int t = X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23);
        return t;
    }

    /**
     * 对最后一个分组字节数据padding
     *
     * @param in
     * @param bLen
     *            分组个数
     * @return
     */
    public static byte[] padding(byte[] in, int bLen)
    {
        int k = 448 - (8 * in.length + 1) % 512;
        if (k &lt; 0)
        {
            k = 960 - (8 * in.length + 1) % 512;
        }
        k += 1;
        byte[] padd = new byte[k / 8];
        padd[0] = (byte) 0x80;
        long n = in.length * 8 + bLen * 512;
        byte[] out = new byte[in.length + k / 8 + 64 / 8];
        int pos = 0;
        System.arraycopy(in, 0, out, 0, in.length);
        pos += in.length;
        System.arraycopy(padd, 0, out, pos, padd.length);
        pos += padd.length;
        byte[] tmp = back(Util.longToBytes(n));
        System.arraycopy(tmp, 0, out, pos, tmp.length);
        return out;
    }

    /**
     * 字节数组逆序
     *
     * @param in
     * @return
     */
    private static byte[] back(byte[] in)
    {
        byte[] out = new byte[in.length];
        for (int i = 0; i &lt; out.length; i++)
        {
            out[i] = in[out.length - i - 1];
        }

        return out;
    }

    public static int rotateLeft(int x, int n)
    {
        return (x &lt;&lt; n) | (x &gt;&gt; (32 - n));
    }

    private static int bitCycleLeft(int n, int bitLen)
    {
        bitLen %= 32;
        byte[] tmp = bigEndianIntToByte(n);
        int byteLen = bitLen / 8;
        int len = bitLen % 8;
        if (byteLen &gt; 0)
        {
            tmp = byteCycleLeft(tmp, byteLen);
        }

        if (len &gt; 0)
        {
            tmp = bitSmall8CycleLeft(tmp, len);
        }

        return bigEndianByteToInt(tmp);
    }

    private static byte[] bitSmall8CycleLeft(byte[] in, int len)
    {
        byte[] tmp = new byte[in.length];
        int t1, t2, t3;
        for (int i = 0; i &lt; tmp.length; i++)
        {
            t1 = (byte) ((in[i] &amp; 0x000000ff) &lt;&lt; len);
            t2 = (byte) ((in[(i + 1) % tmp.length] &amp; 0x000000ff) &gt;&gt; (8 - len));
            t3 = (byte) (t1 | t2);
            tmp[i] = (byte) t3;
        }

        return tmp;
    }

    private static byte[] byteCycleLeft(byte[] in, int byteLen)
    {
        byte[] tmp = new byte[in.length];
        System.arraycopy(in, byteLen, tmp, 0, in.length - byteLen);
        System.arraycopy(in, 0, tmp, in.length - byteLen, byteLen);
        return tmp;
    }
}
</code></pre>
<p>SM3Digest.java</p>
<pre><code class="java">import org.bouncycastle.util.encoders.Hex;

public class SM3Digest {
    /** SM3值的长度 */
    private static final int BYTE_LENGTH = 32;

    /** SM3分组长度 */
    private static final int BLOCK_LENGTH = 64;

    /** 缓冲区长度 */
    private static final int BUFFER_LENGTH = BLOCK_LENGTH * 1;

    /** 缓冲区 */
    private byte[] xBuf = new byte[BUFFER_LENGTH];

    /** 缓冲区偏移量 */
    private int xBufOff;

    /** 初始向量 */
    private byte[] V = SM3.iv.clone();

    private int cntBlock = 0;

    public SM3Digest() {
    }

    public SM3Digest(SM3Digest t)
    {
        System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length);
        this.xBufOff = t.xBufOff;
        System.arraycopy(t.V, 0, this.V, 0, t.V.length);
    }

    /**
     * SM3结果输出
     *
     * @param out 保存SM3结构的缓冲区
     * @param outOff 缓冲区偏移量
     * @return
     */
    public int doFinal(byte[] out, int outOff)
    {
        byte[] tmp = doFinal();
        System.arraycopy(tmp, 0, out, 0, tmp.length);
        return BYTE_LENGTH;
    }

    public void reset()
    {
        xBufOff = 0;
        cntBlock = 0;
        V = SM3.iv.clone();
    }

    /**
     * 明文输入
     *
     * @param in
     *            明文输入缓冲区
     * @param inOff
     *            缓冲区偏移量
     * @param len
     *            明文长度
     */
    public void update(byte[] in, int inOff, int len)
    {
        int partLen = BUFFER_LENGTH - xBufOff;
        int inputLen = len;
        int dPos = inOff;
        if (partLen &lt; inputLen)
        {
            System.arraycopy(in, dPos, xBuf, xBufOff, partLen);
            inputLen -= partLen;
            dPos += partLen;
            doUpdate();
            while (inputLen &gt; BUFFER_LENGTH)
            {
                System.arraycopy(in, dPos, xBuf, 0, BUFFER_LENGTH);
                inputLen -= BUFFER_LENGTH;
                dPos += BUFFER_LENGTH;
                doUpdate();
            }
        }

        System.arraycopy(in, dPos, xBuf, xBufOff, inputLen);
        xBufOff += inputLen;
    }

    private void doUpdate()
    {
        byte[] B = new byte[BLOCK_LENGTH];
        for (int i = 0; i &lt; BUFFER_LENGTH; i += BLOCK_LENGTH)
        {
            System.arraycopy(xBuf, i, B, 0, B.length);
            doHash(B);
        }
        xBufOff = 0;
    }

    private void doHash(byte[] B)
    {
        byte[] tmp = SM3.CF(V, B);
        System.arraycopy(tmp, 0, V, 0, V.length);
        cntBlock++;
    }

    private byte[] doFinal()
    {
        byte[] B = new byte[BLOCK_LENGTH];
        byte[] buffer = new byte[xBufOff];
        System.arraycopy(xBuf, 0, buffer, 0, buffer.length);
        byte[] tmp = SM3.padding(buffer, cntBlock);
        for (int i = 0; i &lt; tmp.length; i += BLOCK_LENGTH)
        {
            System.arraycopy(tmp, i, B, 0, B.length);
            doHash(B);
        }
        return V;
    }

    public void update(byte in)
    {
        byte[] buffer = new byte[] { in };
        update(buffer, 0, 1);
    }

    public int getDigestSize()
    {
        return BYTE_LENGTH;
    }

    public static void main(String[] args)
    {
        byte[] md = new byte[32];
        byte[] msg1 = "ererfeiisgod".getBytes();
        SM3Digest sm3 = new SM3Digest();
        sm3.update(msg1, 0, msg1.length);
        sm3.doFinal(md, 0);
        String s = new String(Hex.encode(md));
        System.out.println(s.toUpperCase());
    }
}
</code></pre>
<p>Util.java</p>
<pre><code class="java">import java.math.BigInteger;

public class Util {
    /**
     * 整形转换成网络传输的字节流（字节数组）型数据
     *
     * @param num 一个整型数据
     * @return 4个字节的自己数组
     */
    public static byte[] intToBytes(int num)
    {
        byte[] bytes = new byte[4];
        bytes[0] = (byte) (0xff &amp; (num &gt;&gt; 0));
        bytes[1] = (byte) (0xff &amp; (num &gt;&gt; 8));
        bytes[2] = (byte) (0xff &amp; (num &gt;&gt; 16));
        bytes[3] = (byte) (0xff &amp; (num &gt;&gt; 24));
        return bytes;
    }

    /**
     * 四个字节的字节数据转换成一个整形数据
     *
     * @param bytes 4个字节的字节数组
     * @return 一个整型数据
     */
    public static int byteToInt(byte[] bytes)
    {
        int num = 0;
        int temp;
        temp = (0x000000ff &amp; (bytes[0])) &lt;&lt; 0;
        num = num | temp;
        temp = (0x000000ff &amp; (bytes[1])) &lt;&lt; 8;
        num = num | temp;
        temp = (0x000000ff &amp; (bytes[2])) &lt;&lt; 16;
        num = num | temp;
        temp = (0x000000ff &amp; (bytes[3])) &lt;&lt; 24;
        num = num | temp;
        return num;
    }

    /**
     * 长整形转换成网络传输的字节流（字节数组）型数据
     *
     * @param num 一个长整型数据
     * @return 4个字节的自己数组
     */
    public static byte[] longToBytes(long num)
    {
        byte[] bytes = new byte[8];
        for (int i = 0; i &lt; 8; i++)
        {
            bytes[i] = (byte) (0xff &amp; (num &gt;&gt; (i * 8)));
        }

        return bytes;
    }

    /**
     * 大数字转换字节流（字节数组）型数据
     *
     * @param n
     * @return
     */
    public static byte[] byteConvert32Bytes(BigInteger n)
    {
        byte tmpd[] = (byte[])null;
        if(n == null)
        {
            return null;
        }

        if(n.toByteArray().length == 33)
        {
            tmpd = new byte[32];
            System.arraycopy(n.toByteArray(), 1, tmpd, 0, 32);
        }
        else if(n.toByteArray().length == 32)
        {
            tmpd = n.toByteArray();
        }
        else
        {
            tmpd = new byte[32];
            for(int i = 0; i &lt; 32 - n.toByteArray().length; i++)
            {
                tmpd[i] = 0;
            }
            System.arraycopy(n.toByteArray(), 0, tmpd, 32 - n.toByteArray().length, n.toByteArray().length);
        }
        return tmpd;
    }

    /**
     * 换字节流（字节数组）型数据转大数字
     *
     * @param b
     * @return
     */
    public static BigInteger byteConvertInteger(byte[] b)
    {
        if (b[0] &lt; 0)
        {
            byte[] temp = new byte[b.length + 1];
            temp[0] = 0;
            System.arraycopy(b, 0, temp, 1, b.length);
            return new BigInteger(temp);
        }
        return new BigInteger(b);
    }

    /**
     * 根据字节数组获得值(十六进制数字)
     *
     * @param bytes
     * @return
     */
    public static String getHexString(byte[] bytes)
    {
        return getHexString(bytes, true);
    }

    /**
     * 根据字节数组获得值(十六进制数字)
     *
     * @param bytes
     * @param upperCase
     * @return
     */
    public static String getHexString(byte[] bytes, boolean upperCase)
    {
        String ret = "";
        for (int i = 0; i &lt; bytes.length; i++)
        {
            ret += Integer.toString((bytes[i] &amp; 0xff) + 0x100, 16).substring(1);
        }
        return upperCase ? ret.toUpperCase() : ret;
    }

    /**
     * 打印十六进制字符串
     *
     * @param bytes
     */
    public static void printHexString(byte[] bytes)
    {
        for (int i = 0; i &lt; bytes.length; i++)
        {
            String hex = Integer.toHexString(bytes[i] &amp; 0xFF);
            if (hex.length() == 1)
            {
                hex = '0' + hex;
            }
            System.out.print("0x" + hex.toUpperCase() + ",");
        }
        System.out.println("");
    }

    /**
     * Convert hex string to byte[]
     *
     * @param hexString
     *            the hex string
     * @return byte[]
     */
    public static byte[] hexStringToBytes(String hexString)
    {
        if (hexString == null || hexString.equals(""))
        {
            return null;
        }

        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i &lt; length; i++)
        {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) &lt;&lt; 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    /**
     * Convert char to byte
     *
     * @param c
     *            char
     * @return byte
     */
    public static byte charToByte(char c)
    {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    /**
     * 用于建立十六进制字符的输出的小写字符数组
     */
    private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * 用于建立十六进制字符的输出的大写字符数组
     */
    private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data byte[]
     * @return 十六进制char[]
     */
    public static char[] encodeHex(byte[] data) {
        return encodeHex(data, true);
    }

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data        byte[]
     * @param toLowerCase &lt;code&gt;true&lt;/code&gt; 传换成小写格式 ， &lt;code&gt;false&lt;/code&gt; 传换成大写格式
     * @return 十六进制char[]
     */
    public static char[] encodeHex(byte[] data, boolean toLowerCase) {
        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data     byte[]
     * @param toDigits 用于控制输出的char[]
     * @return 十六进制char[]
     */
    protected static char[] encodeHex(byte[] data, char[] toDigits) {
        int l = data.length;
        char[] out = new char[l &lt;&lt; 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; i &lt; l; i++) {
            out[j++] = toDigits[(0xF0 &amp; data[i]) &gt;&gt;&gt; 4];
            out[j++] = toDigits[0x0F &amp; data[i]];
        }
        return out;
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data byte[]
     * @return 十六进制String
     */
    public static String encodeHexString(byte[] data) {
        return encodeHexString(data, true);
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data        byte[]
     * @param toLowerCase &lt;code&gt;true&lt;/code&gt; 传换成小写格式 ， &lt;code&gt;false&lt;/code&gt; 传换成大写格式
     * @return 十六进制String
     */
    public static String encodeHexString(byte[] data, boolean toLowerCase) {
        return encodeHexString(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data     byte[]
     * @param toDigits 用于控制输出的char[]
     * @return 十六进制String
     */
    protected static String encodeHexString(byte[] data, char[] toDigits) {
        return new String(encodeHex(data, toDigits));
    }

    /**
     * 将十六进制字符数组转换为字节数组
     *
     * @param data 十六进制char[]
     * @return byte[]
     * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度，将抛出运行时异常
     */
    public static byte[] decodeHex(char[] data) {
        int len = data.length;

        if ((len &amp; 0x01) != 0) {
            throw new RuntimeException("Odd number of characters.");
        }

        byte[] out = new byte[len &gt;&gt; 1];

        // two characters form the hex value.
        for (int i = 0, j = 0; j &lt; len; i++) {
            int f = toDigit(data[j], j) &lt;&lt; 4;
            j++;
            f = f | toDigit(data[j], j);
            j++;
            out[i] = (byte) (f &amp; 0xFF);
        }

        return out;
    }

    /**
     * 将十六进制字符转换成一个整数
     *
     * @param ch    十六进制char
     * @param index 十六进制字符在字符数组中的位置
     * @return 一个整数
     * @throws RuntimeException 当ch不是一个合法的十六进制字符时，抛出运行时异常
     */
    protected static int toDigit(char ch, int index) {
        int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new RuntimeException("Illegal hexadecimal character " + ch
                    + " at index " + index);
        }
        return digit;
    }

    /**
     * 数字字符串转ASCII码字符串
     *
     * @param String
     *            字符串
     * @return ASCII字符串
     */
    public static String StringToAsciiString(String content) {
        String result = "";
        int max = content.length();
        for (int i = 0; i &lt; max; i++) {
            char c = content.charAt(i);
            String b = Integer.toHexString(c);
            result = result + b;
        }
        return result;
    }

    /**
     * 十六进制转字符串
     *
     * @param hexString
     *            十六进制字符串
     * @param encodeType
     *            编码类型4：Unicode，2：普通编码
     * @return 字符串
     */
    public static String hexStringToString(String hexString, int encodeType) {
        String result = "";
        int max = hexString.length() / encodeType;
        for (int i = 0; i &lt; max; i++) {
            char c = (char) hexStringToAlgorism(hexString
                    .substring(i * encodeType, (i + 1) * encodeType));
            result += c;
        }
        return result;
    }

    /**
     * 十六进制字符串装十进制
     *
     * @param hex
     *            十六进制字符串
     * @return 十进制数值
     */
    public static int hexStringToAlgorism(String hex) {
        hex = hex.toUpperCase();
        int max = hex.length();
        int result = 0;
        for (int i = max; i &gt; 0; i--) {
            char c = hex.charAt(i - 1);
            int algorism = 0;
            if (c &gt;= '0' &amp;&amp; c &lt;= '9') {
                algorism = c - '0';
            } else {
                algorism = c - 55;
            }
            result += Math.pow(16, max - i) * algorism;
        }
        return result;
    }

    /**
     * 十六转二进制
     *
     * @param hex
     *            十六进制字符串
     * @return 二进制字符串
     */
    public static String hexStringToBinary(String hex) {
        hex = hex.toUpperCase();
        String result = "";
        int max = hex.length();
        for (int i = 0; i &lt; max; i++) {
            char c = hex.charAt(i);
            switch (c) {
                case '0':
                    result += "0000";
                    break;
                case '1':
                    result += "0001";
                    break;
                case '2':
                    result += "0010";
                    break;
                case '3':
                    result += "0011";
                    break;
                case '4':
                    result += "0100";
                    break;
                case '5':
                    result += "0101";
                    break;
                case '6':
                    result += "0110";
                    break;
                case '7':
                    result += "0111";
                    break;
                case '8':
                    result += "1000";
                    break;
                case '9':
                    result += "1001";
                    break;
                case 'A':
                    result += "1010";
                    break;
                case 'B':
                    result += "1011";
                    break;
                case 'C':
                    result += "1100";
                    break;
                case 'D':
                    result += "1101";
                    break;
                case 'E':
                    result += "1110";
                    break;
                case 'F':
                    result += "1111";
                    break;
            }
        }
        return result;
    }

    /**
     * ASCII码字符串转数字字符串
     *
     * @param String
     *            ASCII字符串
     * @return 字符串
     */
    public static String AsciiStringToString(String content) {
        String result = "";
        int length = content.length() / 2;
        for (int i = 0; i &lt; length; i++) {
            String c = content.substring(i * 2, i * 2 + 2);
            int a = hexStringToAlgorism(c);
            char b = (char) a;
            String d = String.valueOf(b);
            result += d;
        }
        return result;
    }

    /**
     * 将十进制转换为指定长度的十六进制字符串
     *
     * @param algorism
     *            int 十进制数字
     * @param maxLength
     *            int 转换后的十六进制字符串长度
     * @return String 转换后的十六进制字符串
     */
    public static String algorismToHexString(int algorism, int maxLength) {
        String result = "";
        result = Integer.toHexString(algorism);

        if (result.length() % 2 == 1) {
            result = "0" + result;
        }
        return patchHexString(result.toUpperCase(), maxLength);
    }

    /**
     * 字节数组转为普通字符串（ASCII对应的字符）
     *
     * @param bytearray
     *            byte[]
     * @return String
     */
    public static String byteToString(byte[] bytearray) {
        String result = "";
        char temp;

        int length = bytearray.length;
        for (int i = 0; i &lt; length; i++) {
            temp = (char) bytearray[i];
            result += temp;
        }
        return result;
    }

    /**
     * 二进制字符串转十进制
     *
     * @param binary
     *            二进制字符串
     * @return 十进制数值
     */
    public static int binaryToAlgorism(String binary) {
        int max = binary.length();
        int result = 0;
        for (int i = max; i &gt; 0; i--) {
            char c = binary.charAt(i - 1);
            int algorism = c - '0';
            result += Math.pow(2, max - i) * algorism;
        }
        return result;
    }

    /**
     * 十进制转换为十六进制字符串
     *
     * @param algorism
     *            int 十进制的数字
     * @return String 对应的十六进制字符串
     */
    public static String algorismToHEXString(int algorism) {
        String result = "";
        result = Integer.toHexString(algorism);

        if (result.length() % 2 == 1) {
            result = "0" + result;

        }
        result = result.toUpperCase();

        return result;
    }

    /**
     * HEX字符串前补0，主要用于长度位数不足。
     *
     * @param str
     *            String 需要补充长度的十六进制字符串
     * @param maxLength
     *            int 补充后十六进制字符串的长度
     * @return 补充结果
     */
    static public String patchHexString(String str, int maxLength) {
        String temp = "";
        for (int i = 0; i &lt; maxLength - str.length(); i++) {
            temp = "0" + temp;
        }
        str = (temp + str).substring(0, maxLength);
        return str;
    }

    /**
     * 将一个字符串转换为int
     *
     * @param s
     *            String 要转换的字符串
     * @param defaultInt
     *            int 如果出现异常,默认返回的数字
     * @param radix
     *            int 要转换的字符串是什么进制的,如16 8 10.
     * @return int 转换后的数字
     */
    public static int parseToInt(String s, int defaultInt, int radix) {
        int i = 0;
        try {
            i = Integer.parseInt(s, radix);
        } catch (NumberFormatException ex) {
            i = defaultInt;
        }
        return i;
    }

    /**
     * 将一个十进制形式的数字字符串转换为int
     *
     * @param s
     *            String 要转换的字符串
     * @param defaultInt
     *            int 如果出现异常,默认返回的数字
     * @return int 转换后的数字
     */
    public static int parseToInt(String s, int defaultInt) {
        int i = 0;
        try {
            i = Integer.parseInt(s);
        } catch (NumberFormatException ex) {
            i = defaultInt;
        }
        return i;
    }

    /**
     * 十六进制串转化为byte数组
     *
     * @return the array of byte
     */
    public static byte[] hexToByte(String hex)
            throws IllegalArgumentException {
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException();
        }
        char[] arr = hex.toCharArray();
        byte[] b = new byte[hex.length() / 2];
        for (int i = 0, j = 0, l = hex.length(); i &lt; l; i++, j++) {
            String swap = "" + arr[i++] + arr[i];
            int byteint = Integer.parseInt(swap, 16) &amp; 0xFF;
            b[j] = new Integer(byteint).byteValue();
        }
        return b;
    }

    /**
     * 字节数组转换为十六进制字符串
     *
     * @param b
     *            byte[] 需要转换的字节数组
     * @return String 十六进制字符串
     */
    public static String byteToHex(byte b[]) {
        if (b == null) {
            throw new IllegalArgumentException(
                    "Argument b ( byte array ) is null! ");
        }
        String hs = "";
        String stmp = "";
        for (int n = 0; n &lt; b.length; n++) {
            stmp = Integer.toHexString(b[n] &amp; 0xff);
            if (stmp.length() == 1) {
                hs = hs + "0" + stmp;
            } else {
                hs = hs + stmp;
            }
        }
        return hs.toUpperCase();
    }

    public static byte[] subByte(byte[] input, int startIndex, int length) {
        byte[] bt = new byte[length];
        for (int i = 0; i &lt; length; i++) {
            bt[i] = input[i + startIndex];
        }
        return bt;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/turboanho/article/details/126647410</p>]]></description><guid isPermaLink="false">/archives/14de0b31-f769-45b6-bc0e-2097e7799217</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:43:26 GMT</pubDate></item><item><title><![CDATA[【数据结构初阶】单链表接口实现超详解]]></title><link>https://wiki.dgfygk.com/archives/ee5e77a5-87ab-46d2-a342-cf7650af6750</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%88%9D%E9%98%B6%E3%80%91%E5%8D%95%E9%93%BE%E8%A1%A8%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0%E8%B6%85%E8%AF%A6%E8%A7%A3&amp;url=/archives/ee5e77a5-87ab-46d2-a342-cf7650af6750" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1__4">1. 顺序表问题与思考</a></li>
 <li><a href="https://wiki.dgfygk.com#2_14">2.单链表</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#2_1__15">2. 1 概念与结构</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#211__22">2.1.1 结点</a></li>
   <li><a href="https://wiki.dgfygk.com#212__28">2.1.2 链表的性质</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#3__47">3. 单链表实现</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#3_1__52">3. 1 单链表初始化</a></li>
 <li><a href="https://wiki.dgfygk.com#3_2__59">3. 2 单链表的打印</a></li>
 <li><a href="https://wiki.dgfygk.com#3_3__82">3. 3 头插</a></li>
 <li><a href="https://wiki.dgfygk.com#3_4__113">3. 4 创建新节点</a></li>
 <li><a href="https://wiki.dgfygk.com#3_5__152">3. 5 尾插</a></li>
 <li><a href="https://wiki.dgfygk.com#3_6__192">3. 6 头删和尾删</a></li>
 <li><a href="https://wiki.dgfygk.com#3_7__258">3. 7 查找</a></li>
 <li><a href="https://wiki.dgfygk.com#3_8__281">3. 8 在指定位置之后插入或删除</a></li>
 <li><a href="https://wiki.dgfygk.com#3_9__325">3. 9 在指定位置前面插入或删除指定位置</a></li>
 <li><a href="https://wiki.dgfygk.com#3_10__389">3. 10 销毁</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<ol>
 <li>
  <h2>顺序表问题与思考</h2>
 </li>
</ol>
<p><a href="https://blog.csdn.net/fhvyxyci/article/details/141285463">上一篇博客</a>中我们介绍了顺序表，那么顺序表有什么缺点呢？</p>
<ol>
 <li>中间 / 头部的插入删除，时间复杂度为<strong>O(N)</strong></li>
 <li><strong>增容需要申请新空间，拷贝数据，释放旧空间。会有不小的消耗。</strong></li>
 <li>增容一般是呈2倍的增长，势必会有一定的<strong>空间浪费</strong>。例如当前容量为100，满了以后增容到200，我们再继续插入了5个数据，后面没有数据插入了，那么就浪费了95个数据空间。</li>
</ol>
<p>如何解决以上问题呢?
 <br>
 我们可以使用<strong>单链表</strong>。</p>
<h2>2.单链表</h2>
<h3>2. 1 概念与结构</h3>
<p>概念：<strong>链表是一种物理存储结构上非连续、非顺序的存储结构，数据元素的逻辑顺序是通过链表中的指针链接次序实现的</strong>。</p>
<p>链表就像一个火车：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb9bcdd3160d543eda71609a8bbec6b52.png&amp;size=m" alt="">淡季时车次的车厢会相应减少，旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉加上，不会影响其他车厢，每节车厢都是<strong>独立存在</strong> 的。
 <br>
 在链表里，每节"车厢"是什么样的呢?
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa309f305771f4c279779aa756ebac97e.png&amp;size=m" alt=""></p>
<h4>2.1.1 结点</h4>
<p>与顺序表不同的是，<strong>链表里的每节"车厢"都是独立申请下来的空间，我们称之为"结点”</strong> 。
 <br>
 结点的组成主要有两个部分：<strong>当前结点要保存的数据</strong> 和<strong>保存下一个结点的地址</strong> (指针变量)。
 <br>
 图中指针变量 <code>plist</code>保存的是第一个结点的地址，我们称<code>plist</code>此时指向第一个结点，如果我们希望<code>plist</code>指向第二个结点时，只需要修改<code>plist</code>保存的内容为<code>0x0012FFA0</code>。
 <br>
 链表中每个结点都是独立申请的(即<strong>需要插入数据时才去申请一块结点的空间</strong> )，我们需要<strong>通过指针变量来保存下一个结点位置才能从当前结点找到下一个结点</strong>。</p>
<h4>2.1.2 链表的性质</h4>
<ol>
 <li>链式机构在<strong>逻辑上是连续的，在物理结构上不一定连续</strong></li>
 <li>结点一般是从<strong>堆</strong>上申请的</li>
 <li>从堆上申请来的空间，是按照一定策略分配出来的，每次申请的空间可<strong>能连续，可能不连续</strong></li>
</ol>
<p>结合前面学到的结构体和顺序表知识，我们可以给出每个结点对应的结构体代码：</p>
<pre><code class="prism language-c">typedef int SLTDataType;

typedef struct SListNode
{

    SLTDataType data;       //用于存储数据
    struct SListNode* next; //指向下一个节点
}SLTNode;
</code></pre>
<p>当我们想要保存一个整型数据时，实际是向操作系统申请了一块内存**，这块内存不仅要保存整型数据，也需要保存下一个结点的地址**(当下一个结点为空时保存的地址为空)
 <br>
 <strong>当我们想要从第一个结点走到最后一个结点时，只需要在当前结点拿上下一个结点的地址就可以了。</strong></p>
<ol>
 <li>
  <h2>单链表实现</h2>
 </li>
</ol>
<p>不带头单向不循环链表
 <br>
 记得进行良好的<strong>文件管理</strong> 。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcf9efb01d6e54786967fe685b93b4293.png&amp;size=m" alt=""></p>
<h3>3. 1 单链表初始化</h3>
<p>实际上，单链表并不需要一个用于初始化的函数，这是由单链表的性质决定的。
 <br>
 在使用单链表时，需要在 <code>main</code> 函数中创建一个 <code>SLTNode*</code> 的变量，再将它的地址传递给其他函数就可以了。</p>
<p>为什么在最开始要创建一个<strong>指针变量</strong> ？
 <br>
 这个问题在后面<strong>头插</strong>函数中解释。</p>
<h3>3. 2 单链表的打印</h3>
<p>单链表的底层结构不是数组了，那我们应该怎样进行打印呢？或者说，我们应该怎么遍历单链表？</p>
<pre><code class="prism language-c">void SListPrint(SListNode* plist);  //phead是在main函数中创建的变量，是单链表的头
</code></pre>
<p>我们可以用一个<code>SLTNode*</code>变量 pcur 来遍历数组，在打印了<code>pcur-&gt;data</code>之后，让<code>pcur</code>变为<code>pcur-&gt;next</code>，直到<code>cur==NULL</code>结束，就可以了。</p>
<pre><code class="prism language-c">void SListPrint(SListNode* plist)
{

    SListNode* cur = plist;
    while (cur)
    {

        printf("%d-&gt;", cur-&gt;data);
        cur = cur-&gt;next;
    }
    printf("NULL\n");
}
</code></pre>
<p>如图所示：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0f4a536e59854551b6e48f4dd0946dfb.png&amp;size=m" alt=""></p>
<h3>3. 3 头插</h3>
<pre><code class="prism language-c">void SListPushFront(SListNode** pplist, SLTDateType x); //注意第一个参数是二级指针
</code></pre>
<p>要完成头插，我们需要完成几个步骤：</p>
<ol>
 <li><strong>创建新节点</strong></li>
 <li><strong>将头结点的<code>next</code>指针指向原来的头结点</strong></li>
 <li><strong>将头结点换成这个新插入的节点</strong></li>
</ol>
<p>首先第一步创建新节点，这个步骤在其他的插入函数中也会用到，所以我们先跳过，稍后单独封装一个函数<code>BuyListNode</code>方便使用。
 <br>
 假设我们得到了新节点<code>newnode</code>，完成第二步就很简单了，只需要<code>newnode-&gt;next=*pphead</code> 就可以了。
 <br>
 第三步，<code>pphead</code>是指向头结点的指针，因此要修改头结点，只需要修改<code>*pphead</code>就可以了（这也就是为什么要传递<strong>二级指针</strong>）</p>
<p>参考代码：</p>
<pre><code class="prism language-c">void SListPushFront(SListNode** pplist, SLTDateType x)
{

    assert(pplist);     //在函数解引用之前断言它不是空指针
    SListNode* newnode = BuySListNode(x);
    if (*pplist == NULL)
        *pplist = newnode;
    else
    {

        newnode-&gt;next = *pplist;
        *pplist = newnode;
    }
}
</code></pre>
<p>值得注意的是，<strong>最后两步的顺序绝对不可以颠倒</strong>！</p>
<h3>3. 4 创建新节点</h3>
<p>创建新节点一般是在<strong>插入</strong> 时调用的，因此将<strong>存储数据</strong>这一步直接放在函数内部会更方便使用。</p>
<pre><code class="prism language-c">SLTNode* BuyListNode(SLTDataType x);
</code></pre>
<p>这个函数用来<strong>动态开辟</strong>一个新节点，注意一定要是动态申请的，不然创建的节点会在函数调用结束后随着函数栈帧一起销毁。</p>
<pre><code class="prism language-c">SLTNode* BuyListNode(SLTDataType x)//创建新节点
{

    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (NewNode == NULL)
    {

        printf("malloc failed");
        exit(-1);           //检查malloc是否成功，若失败则报错退出
    }
    newnode-&gt;data = x;
    newnode-&gt;next = NULL;   //新的节点的next置为NULL，方便使用
    return newnode;
}
</code></pre>
<p>除了上面这样使用返回值传递新节点，你当然也可以使用二级指针来传递新节点，就像这样：</p>
<pre><code class="prism language-c">void BuyListNode(SLTNode** ppnewnode,SLTDataType x);
{

    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (newnode == NULL)
    {

        printf("malloc failed");
        exit(-1);
    }
    newnode-&gt;data = x;
    newnode-&gt;next = NULL;
    *ppnewnode = newnode;
}
</code></pre>
<p>但是这样的话就要在函数调用之前创建<code>SLTNode*</code>类型的变量，不方便使用，所以<strong>不推荐</strong>这样写。</p>
<h3>3. 5 尾插</h3>
<pre><code class="prism language-c">void SListPushBack(SListNode** pplist, SLTDateType x);
</code></pre>
<p>相较于头插，尾插多了一个步骤：<strong>找到单链表现在的尾节点</strong> 。
 <br>
 要完成头插，我们需要完成几个步骤：</p>
<ol>
 <li><strong>创建新节点</strong></li>
 <li><strong>找到尾节点</strong></li>
 <li><strong>将尾结点的<code>next</code>指针指向新节点</strong></li>
</ol>
<p>我们重点来看第二步，在单链表中，并没有存储尾节点，但是我们可以通过遍历的方式找到尾节点，就像：</p>
<pre><code class="prism language-c">SListNode* cur = *pplist;
while (cur-&gt;next)
    cur = cur-&gt;next;
</code></pre>
<p>最终退出循环时，<code>cur</code>就是尾节点了。</p>
<p>当然，值得注意的是，如果此时单链表为空也就是<code>*plist==NULL</code>的时候就不能按第三步进行了，否则会发生<strong>空指针的解引用</strong>，要单独处理这一情况，将这个新节点变成头结点。</p>
<p>参考代码：</p>
<pre><code class="prism language-c">void SListPushBack(SListNode** pplist, SLTDateType x)
{

    assert(pplist);
    SListNode* newnode = BuySListNode(x);
    if (*pplist == NULL)
        *pplist = newnode;      //如果链表为空，将新节点作为头结点
    else
    {

        SListNode* cur = *pplist;
        while (cur-&gt;next)
            cur = cur-&gt;next;    //找到尾节点
        cur-&gt;next = newnode;
    }
}
</code></pre>
<h3>3. 6 头删和尾删</h3>
<p>头删相对简单，分为两个步骤：</p>
<ol>
 <li><strong>将原来的头结点存储起来，并将下一个节点作为头结点。</strong></li>
 <li><strong>释放原来的头结点，并将指向原来的头结点的指针置空。</strong></li>
</ol>
<p>我们来分析一下，要不要对单链表中<strong>只有一个节点</strong> 的情况特殊处理：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa6229a6d24cc462fa235b5ed18ce7c23.png&amp;size=m" alt="">
 <br>
 很显然，不需要，因为头结点的下一个节点就是<code>NULL</code>，那么<strong>头结点也就变成了空结点</strong> 。
 <br>
 参考代码：</p>
<pre><code class="prism language-c">void SListPopFront(SListNode** pplist)
{

    assert(pplist &amp;&amp; *pplist);
    SListNode* tmp = *pplist;
    *pplist = (*pplist)-&gt;next;
    free(tmp);
    tmp = NULL;
}
</code></pre>
<p>再来看尾删，尾删麻烦一点，分为几个步骤：</p>
<ol>
 <li><strong>找到尾节点和它的上一个节点。</strong></li>
 <li><strong>释放尾节点。</strong></li>
 <li><strong>将现在的尾节点的<code>next</code>指针置为空。</strong></li>
</ol>
<p>除此之外，我们也来分析一下如果<strong>链表中只有一个节点</strong> 的情况：
 <br>
 不需要过多分析我们就能发现：<strong>在第一步的时候，就无法找到头结点的上一个节点</strong>，因此这种情况显然要单独处理：</p>
<pre><code class="prism language-c">if ((*pplist)-&gt;next == NULL)
{

    SListNode* tmp = *pplist;
    free(tmp);              //不要忘记释放节点，避免内存泄漏
    tmp = NULL;
    *pplist = NULL;
}
</code></pre>
<p>那么尾删的代码也就很容易写出来了：</p>
<pre><code class="prism language-c">void SListPopBack(SListNode** pplist)
{

    assert(pplist &amp;&amp; *pplist);
    if ((*pplist)-&gt;next == NULL)
    {

        SListNode* tmp = *pplist;
        free(tmp);
        tmp = NULL;
        *pplist = NULL;
    }
    else
    {

        SListNode* cur = *pplist;
        SListNode* pre = NULL;
        while (cur-&gt;next)   //循环会在cur为尾节点时停止
        {

            pre = cur;
            cur = cur-&gt;next;
        }
        free(cur);
        cur = NULL;
        pre-&gt;next = NULL;   //如果不进行这一步，尾节点就会指向野指针
    }
}
</code></pre>
<h3>3. 7 查找</h3>
<pre><code class="prism language-c">SListNode* SListFind(SListNode* plist, SLTDateType x);
//找到存储了数据x的节点，并返回节点没找到就返回NULL
</code></pre>
<p>这个是为了后面的<strong>删除</strong> 和<strong>插入</strong> 做前置工作。
 <br>
 这个函数只需要<strong>遍历一遍链表并一一比较</strong>就可以了：</p>
<pre><code class="prism language-c">SListNode* SListFind(SListNode* plist, SLTDateType x)
{

    assert(plist);
    SListNode* cur = plist;
    while (cur)
    {

        if (cur-&gt;data == x)
            return cur;
        cur = cur-&gt;next;
    }
    return NULL;
}
</code></pre>
<h3>3. 8 在指定位置之后插入或删除</h3>
<p>先来看插入：</p>
<pre><code class="prism language-c">void SListInsertAfter(SListNode* pos, SLTDateType x);
</code></pre>
<p>插入有几个步骤：</p>
<ol>
 <li><strong>申请新节点</strong></li>
 <li><strong>将新节点的<code>next</code>指针指向<code>pos-&gt;next</code></strong></li>
 <li><strong>将<code>pos-&gt;next</code>指向新节点</strong></li>
</ol>
<p>这个<code>pos</code>来自上面的查找函数，也就是说，<strong>如果<code>pos</code>不为空，那么<code>pos</code>是一定在链表中的</strong> ，不需要额外检查，但需要<strong>检查它是否为空</strong> 。
 <br>
 特别要注意的是：<strong>第二步和第三步的顺序绝对不能颠倒，不然就会找不到原来的下一个节点</strong>。</p>
<pre><code class="prism language-c">void SListInsertAfter(SListNode* pos, SLTDateType x)
{

    assert(pos);
    SListNode* newnode = BuySListNode(x);
    newnode-&gt;next = pos-&gt;next;
    pos-&gt;next = newnode;
}
</code></pre>
<p>再来看删除：</p>
<pre><code class="prism language-c">void SListEraseAfter(SListNode* pos)
</code></pre>
<p>删除分为几个步骤：</p>
<ol>
 <li><strong>将pos-&gt;next指向pos-&gt;next-&gt;next</strong></li>
 <li><strong>释放pos-&gt;next</strong></li>
</ol>
<p>当然，<code>pos</code>也需要进行判空操作，<code>pos-&gt;next</code>也需要，要调用这个函数，这两者应该都不能为空。</p>
<pre><code class="prism language-c">void SListEraseAfter(SListNode* pos)
{

    assert(pos &amp;&amp; pos-&gt;next);
    SListNode* tmp = pos-&gt;next;
    pos-&gt;next = tmp-&gt;next;
    free(tmp);
    tmp = NULL;
}
</code></pre>
<h3>3. 9 在指定位置前面插入或删除指定位置</h3>
<p>需要注意的是，在C++的STL库（数据结构库）中，并没有这样的两个函数，至于原因会在最后讲。</p>
<p>先来看插入：</p>
<pre><code class="prism language-c">void SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x);
</code></pre>
<p>分为几个步骤：</p>
<ol>
 <li><strong>遍历单链表，找到<code>pos</code>的上一个节点</strong></li>
 <li><strong>申请新节点</strong></li>
 <li><strong>将新节点按在指定位置之后插入的方式插入到<code>pos</code>的上一个节点后面</strong></li>
</ol>
<pre><code class="prism language-c">void SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x)
{

    assert(pphead &amp;&amp; pos);
    SListNode* newnode = BuySListNode(x);
    SListNode* pre = *pphead;
    while (pre-&gt;next != pos)
    {

        pre = pre-&gt;next;    //遍历找到pos的上一个节点
    }
    pre-&gt;next = newnode;    //剩下的便是在指定位置之后插入了
    newnode-&gt;next = pos;
}
</code></pre>
<p>再来看删除：</p>
<pre><code class="prism language-c">void SListErase(SListNode** pphead, SListNode* pos)
</code></pre>
<p>分为几个步骤：</p>
<ol>
 <li><strong>遍历单链表，找到<code>pos</code>的上一个节点</strong></li>
 <li><strong>按在指定位置之后删除的方式删除<code>pos</code></strong></li>
</ol>
<p>不过值得注意的是：如果<code>pos</code>是头结点的话，还需要将头结点改变一下，这也是为什么这个函数中头结点要传递二级指针。</p>
<pre><code class="prism language-c">void SListErase(SListNode** pphead, SListNode* pos)
{

    assert(pphead &amp;&amp; pos);
    if (*pphead == pos)         //处理pphead和pos重合的情况
    {

        *pphead = pos-&gt;next;
        free(pos);
        pos = NULL;
    }
    else
    {

        SListNode* pre = *pphead;
        while (pre-&gt;next != pos)
        {

            pre = pre-&gt;next;    //找到pos的上一个节点
        }
        pre-&gt;next = pos-&gt;next;  //这后面的就是在指定位置后面删除节点
        free(pos);
        pos = NULL;
    }
}
</code></pre>
<p>为什么C++的STL库不提供这两个函数?
 <br>
 我们可以计算一下这两个函数以及上面的向后插入删除的函数的时间复杂度，可以发现，向后插入删除的函数的时间复杂度都是<strong>O(1)</strong> ，而下面这两个函数的时间复杂度都是<strong>O(N)</strong> ，因为<strong>单链表不能直接找到节点的上一个节点</strong> ，所以单链表并不适合向前插入和原地删除的函数，<strong>有其他的链表可以更好地完成这样的任务</strong>。</p>
<h3>3. 10 销毁</h3>
<pre><code class="prism language-c">void SListDestroy(SListNode** pphead)
</code></pre>
<p>在单链表使用结束后，一定要记得销毁单链表，不然会发生内存泄漏，可能导致程序崩溃。
 <br>
 单链表的销毁比顺序表麻烦一些，因为单链表的空间是一块一块地申请的，所以也要一块一块地释放。
 <br>
 只需要遍历单链表， 逐个释放就可以了。</p>
<pre><code class="prism language-c">void SListDestroy(SListNode** pphead)
{

    assert(pphead);
    SListNode* cur = *pphead;
    while (cur)
    {

        SListNode* tmp = cur-&gt;next;
        free(cur);
        cur = tmp;      //遍历着往下走
    }
    *pphead = NULL;     //还要记得将头结点置空，不然就是一个野指针
}
</code></pre>
<p><strong>谢谢你的阅读，喜欢的话来个点赞收藏评论关注吧！ 
  <br>我会持续更新更多优质文章</strong></p>
<p>原文链接: https://blog.csdn.net/fhvyxyci/article/details/141400142</p>]]></description><guid isPermaLink="false">/archives/ee5e77a5-87ab-46d2-a342-cf7650af6750</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 08:43:15 GMT</pubDate></item><item><title><![CDATA[信息安全技术知识点]]></title><link>https://wiki.dgfygk.com/archives/e245e2e6-f5bf-4bc0-95c6-a4a323d1b8ee</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E7%9F%A5%E8%AF%86%E7%82%B9&amp;url=/archives/e245e2e6-f5bf-4bc0-95c6-a4a323d1b8ee" width="1" height="1" alt="" style="opacity:0;">
<p>第1章 概览</p>
<ol>
 <li>
  <p>信息安全的定义（六点）</p>
 </li>
 <li>
  <p>保密性：对信息访问和公开进行授权限制。e.g. A要保证只有B能收到A的转账单，即使第三者收到，也不知道转账单的细节。</p>
 </li>
 <li>
  <p>完整性：防止对信息的不恰当修改或破坏。e.g. A和B要保证别人不会篡改转账单内容。</p>
 </li>
 <li>
  <p>可用性：确保对信息的及时和可靠的访问和使用。e.g.<strong>¡</strong> 由于某个非授权用户C盗窃和修改了A的个人银行账号，使无法给B开出合法的转账单。</p>
 </li>
 <li>
  <p>真实性：信息和信息的来源是正确的。e.g. B要保证转账单是来自A，而不是别人假装A （否则是假转账单）。</p>
 </li>
 <li>
  <p>不可抵赖：防止发送方和接收方拒绝承认发送和接受过某条信息。e.g.¡如果B把转账单的100元转入B的账号中，钱从A账号转到B账号之后，A 否认签发了转账单呢？法院要用A的签名否认A的抵账，解决争端。</p>
 </li>
 <li>
  <p>访问控制：限制和控制通过通信连接对主机和应用进行访问。e.g. 指定允许B浏览A开出的转账单，但不能更改转账单的信息。而A则可以更改转账单的信息。而A则可以更改转账单的信息。</p>
 </li>
 <li>
  <p>OSI安全框架（三点）：</p>
 </li>
</ol>
<p>（1）安全攻击：任何危及信息系统安全的行为。</p>
<ul>
 <li>被动攻击：对传输进行窃听和监测。攻击者的目标是获得传输的信息。e.g.信息内容的泄漏、流量分析。</li>
 <li>主动攻击：对数据流进行修改或伪造数据流，e.g.伪装、重播、消息修改、拒绝服务。</li>
</ul>
<p>（2）安全机制：用来检测、阻止攻击或者从攻击状态恢复到正常状态的过程。</p>
<ul>
 <li>加密、数字签名、访问控制、数据完整性、认证交换、流量填充、路由控制、公证</li>
</ul>
<p>（3）安全服务：加强数据处理系统和信息传输的安全性的一种处理过程或通信服务。</p>
<ul>
 <li>认证、访问控制、数据保密性、数据完整性、不可否认性、可用性服务</li>
</ul>
<p>第2章 传统加密技术</p>
<ol>
 <li>
  <p>对称密码的基本成分（五点）：</p>
 </li>
 <li>
  <p>明文、加密算法、密钥、密文、解密算法</p>
 </li>
 <li>
  <p>密码算法中的两个基本函数：</p>
 </li>
 <li>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7b20aaaad9ca184d9039380afed94a39.gif&amp;size=m" alt=""></p>
 </li>
 <li>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7a01683febbddee1fce27719e6254c6f.gif&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>密码编码学系统的三个特征：</p>
 </li>
</ol>
<p>（1）转换明文为密文的运算类型（加密算法的两个基本原理）</p>
<ul>
 <li>代替：将明文中的每个元素映射成另一个元素；</li>
 <li>置换：将明文的元素重新排列。</li>
</ul>
<p>（2）所用的密钥数（对称密码与非对称密码的区别）</p>
<ul>
 <li>对称密码（传统密码）：发送方和接收方使用相同的密钥；</li>
 <li>非对称密码（公钥密码）：发收双方使用不同的密钥。</li>
</ul>
<p>（3）处理明文的方法（分组密码和流密码的区别）</p>
<ul>
 <li>
  <p>分组密码：每次处理输入的一组元素，相应地输出一组元素。分组密码的优势之一是可以重复使用密钥。</p>
 </li>
 <li>
  <p>流密码：连续地处理输入元素，每次输出一个元素。流密码的优势在于不使用块密码作为构建块，比分组密码会更快，代码更短。</p>
 </li>
 <li>
  <p>攻击密码的两种一般方法：</p>
 </li>
 <li>
  <p>密码分析学：利用算法的性质来推导出特定的明文或使用的密钥。</p>
 </li>
 <li>
  <p>穷举攻击：尝试所有可能的密钥。</p>
 </li>
 <li>
  <p>基于攻击者所知道信息的密码分析攻击类型（五种）：</p>
 </li>
 <li>
  <p>唯密文攻击、已知明文攻击、选择明文攻击、选择密文攻击、选择文本攻击</p>
 </li>
 <li>
  <p>四种代替密码：</p>
 </li>
</ul>
<p>（1）Caesar密码（一种单表代替密码）：对字母表中的每个字母，用它之后的第3个字母来代替。</p>
<p>（2）Vigenere密码（一种多表代替密码）：代替规则由26个Caesar密码的代替表组成，其中每一个代替表是对明文字母表移位0\~25次后得到的代替单表。</p>
<ul>
 <li><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77d5f825e36f4f0a2a1aa5a271513bf8.png&amp;size=m" alt=""></li>
</ul>
<p>（3）Playfair密码（一种多字母代替密码）：基于一个由密钥词构成的5*5字母矩阵，把明文中的双字母音节作为一个单元并将其转换成密文的"双字母音节”。</p>
<ul>
 <li><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa9674f7728da6226c4cfc89bfaf800b2.png&amp;size=m" alt=""></li>
 <li><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5d2dc331680cf0e715e75e81fe915b05.png&amp;size=m" alt=""></li>
</ul>
<p>（4）Hill密码：用m个线性等式将m个连续的明文字母替换成m个密文字母。</p>
<ul>
 <li>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F62b56fe73a8f5688dcb9ebec4cd650f4.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>一次一密的两个问题（难点）：</p>
 </li>
 <li>
  <p>产生大规模随机密钥有实际困难。</p>
 </li>
 <li>
  <p>更令人担忧的是密钥的分配和保护。</p>
 </li>
 <li>
  <p>隐藏明文信息的两种方法：</p>
 </li>
 <li>
  <p>隐写术：隐藏信息的存在。</p>
 </li>
 <li>
  <p>密码学：通过对明文信息的不同转换而实现信息的对外不可读。</p>
 </li>
</ul>
<p>第3章 分组密码和数据加密标准</p>
<ol>
 <li>
  <p>乘积密码：依次使用两个或两个以上基本密码，所得结果的密码强度将强于所有单个密码的强度。</p>
 </li>
 <li>
  <p>扩散与混淆的区别</p>
 </li>
 <li>
  <p>扩散：尽可能使明文和密文间的统计关系变得复杂，以挫败推导出密钥的企图；</p>
 </li>
 <li>
  <p>混淆：尽可能使密文和加密密钥间的统计关系变得复杂，以阻止攻击者发现密钥。</p>
 </li>
 <li>
  <p>Feistel密码具体实现所依赖的参数和特征</p>
 </li>
 <li>
  <p>分组长度：分组越长意味着安全性越高，但是会降低加、解密的速度。</p>
 </li>
 <li>
  <p>密钥长度：密钥较长意味着安全性较高，但是会降低加、解密的速度。</p>
 </li>
 <li>
  <p>迭代轮数：Feistel密码的本质在于单轮不能提供足够的安全性而多轮加密可取得很高的安全性。</p>
 </li>
 <li>
  <p>子密钥产生算法：子密钥产生越复杂，密码分析就越困难。</p>
 </li>
 <li>
  <p>轮函数F：轮函数越复杂，抗攻击的能力就越强。</p>
 </li>
 <li>
  <p>Feistel密码结构的互逆性</p>
 </li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff7be44eae02b9841197300b3a58ac454.png&amp;size=m" alt=""></p>
<p>对于加密过程有<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3e78efceb0f7edfd30582f8ebd7eedd3.gif&amp;size=m" alt="">；</p>
<p>对于解密算法有<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FLD_%257B1%257D%253DRD_%257B0%257D%253DLE_%257B16%257D%253DRE_%257B15%257D&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fee0a53077c4262a920ec3dcc230ca696.gif&amp;size=m" alt=""></p>
<ol>
 <li>雪崩效应：明文或密钥的某一位发生变化会导致密文的很多位发生变化。</li>
</ol>
<p>第5章 高级加密标准</p>
<ol>
 <li>
  <p>AES加密算法的四个阶段</p>
 </li>
 <li>
  <p>字节代替：用一个 S 盒完成分组的字节到字节的替代。 其中，高四位作为检索的行号，低四位作为检索的列号。</p>
 </li>
 <li>
  <p>行移位：将状态数组的第<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc41d653023a050de9bcff8a9260a5ecc.gif&amp;size=m" alt="">行左移<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc41d653023a050de9bcff8a9260a5ecc.gif&amp;size=m" alt="">个字节（<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffbf6c20612ec3c0d9bb7be1a2576da7d.gif&amp;size=m" alt="">）。</p>
 </li>
 <li>
  <p>列混淆：利用域<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd477d3adf354db1578d4ddc54dcb3043.gif&amp;size=m" alt="">上的加法和乘法的一个代替。</p>
 </li>
 <li>
  <p>轮密钥加：将状态数组的每一列与当前轮的扩展密钥的每个字做异或运算。</p>
 </li>
 <li>
  <p>密钥扩展算法</p>
 </li>
</ol>
<p>密钥的第一个字为上一轮的第一个字和上一轮的最后一个字的g函数值做异或运算得结果；其余字由前一个字和上一轮对应的字做异或运算得到。g函数的三个步骤如下：</p>
<ul>
 <li>字循环：一个字（4字节）循环左移一个字节。</li>
 <li>字代替：使用S盒对输入字的每个字节进行字节代替。</li>
 <li>轮常量异或：将上面的结果与轮常量相异或。</li>
</ul>
<p>第6章 分组密码的工作模式</p>
<ol>
 <li>分组密码的工作模式</li>
</ol>
<p>（1）电码本（ECB）：用相同的密钥分别对明文分组独立加密。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff43b5645384dfcfee9e2d1de0af25a98.png&amp;size=m" alt=""></p>
<p>（2）密文分组链接（CBC）：加密算法的输入是上一个密文组和下一个明文组的异或。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F29dd1d33aadab917e695a306004ed4b2.png&amp;size=m" alt=""></p>
<p>（3）密文反馈（CFB）：一次处理s位，上一块密文作为加密算法的输入，产生的伪随机数输出与明文异或作为下一单元的密文。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffab4d49c5aa2acb3159ff0d07785cb23.png&amp;size=m" alt=""></p>
<p>（4）输出反馈（OFB）：与CFB类似，只是加密算法的输入是上一次加密的输出，且使用整个分组。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fac893f579c6b9a9abb61965207a4cc5d.png&amp;size=m" alt=""></p>
<p>（5）计数器（CTR）：每个明文分组都与一个经过加密的计数器相异或。对每个后续分组计数器递增。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F806655216dcdc52a4745fec5ec11d949.png&amp;size=m" alt=""></p>
<p>第9章 公钥密码学与RSA</p>
<ol>
 <li>
  <p>公钥密码体制的主要成分</p>
 </li>
 <li>
  <p>明文、加密算法、公钥、私钥、密文、解密算法</p>
 </li>
 <li>
  <p>公钥密码体制的三种应用：</p>
 </li>
 <li>
  <p>加密/解密、数字签名、密钥交换</p>
 </li>
 <li>
  <p>公钥密码体制索要满足的要求</p>
 </li>
 <li>
  <p>密钥的产生在计算上是容易的；</p>
 </li>
 <li>
  <p>密文的产生在计算上是容易的；</p>
 </li>
 <li>
  <p>明文的恢复在计算上是容易的；</p>
 </li>
 <li>
  <p>已知公钥，攻击者要确定私钥在计算上是不可行的；</p>
 </li>
 <li>
  <p>已知公钥和密文，攻击者要恢复明文在计算上是不可行的。</p>
 </li>
 <li>
  <p>RSA的加密和解密过程</p>
 </li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F217aaae6297b3a9cf51c68065d1a891c.gif&amp;size=m" alt=""></p>
<p>其中<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9ff0fbc52d0def94f6fbfd99fff4c0d6.gif&amp;size=m" alt="">为明文，<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FC&amp;size=m" alt="">为密文，<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4eefae06a0ee5a0ba619715a6883c29b.gif&amp;size=m" alt="">(<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdef4b12ce262f11975f7f7a799563d9f.gif&amp;size=m" alt="">为大素数），<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe55552a443a68e1abd99bd617adc66a0.gif&amp;size=m" alt="">满足<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8dace33951c9ac1f8d3954893ec4741a.gif&amp;size=m" alt="">.</p>
<ol>
 <li>
  <p>挑选素数的有效过程（素性检验）</p>
 </li>
 <li>
  <p>随机选择一个奇素数<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77c7fbcec5201ab39f606d5ae8721cf6.gif&amp;size=m" alt="">；</p>
 </li>
 <li>
  <p>随机选择一个整数<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0a7548cc462c6bac3333b7d5c7184f91.gif&amp;size=m" alt="">；</p>
 </li>
 <li>
  <p>执行注入Miller-Rabin之类的概率素数测试。若<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77c7fbcec5201ab39f606d5ae8721cf6.gif&amp;size=m" alt="">未通过测试，则拒绝<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77c7fbcec5201ab39f606d5ae8721cf6.gif&amp;size=m" alt="">，并转到步骤（1）；</p>
 </li>
 <li>
  <p>若<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77c7fbcec5201ab39f606d5ae8721cf6.gif&amp;size=m" alt="">通过测试足够多次，则接受<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77c7fbcec5201ab39f606d5ae8721cf6.gif&amp;size=m" alt="">；否则转到步骤（2）。</p>
 </li>
</ol>
<p>第10章 密钥管理和其他公钥密码体制</p>
<ol>
 <li>
  <p>Diffie-Hellman密钥交换协议的步骤</p>
 </li>
 <li>
  <p>选取大素数<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0eebc49f0557e247d89d76277b34497f.gif&amp;size=m" alt="">和它的一个本原根<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdebaf45b2a13185bcada5601844a59bc.gif&amp;size=m" alt="">，这些参数公开；</p>
 </li>
 <li>
  <p>A选择随机数<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fab516690379fb7f044d76091eac220e3.gif&amp;size=m" alt="">，B选择随机数<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3852adb2b82b386bf24d139ce741d81e.gif&amp;size=m" alt="">；</p>
 </li>
 <li>
  <p>A计算<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6b92b253624c7b8a629b99eb7f61feed.gif&amp;size=m" alt="">，B计算<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FY_b%253D%255Calpha%255E%257BX_b%257D%2528mod%255C%253Bq%2529&amp;size=m" alt="">；</p>
 </li>
 <li>
  <p>交换<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FY_a%252CY_b&amp;size=m" alt="">；</p>
 </li>
 <li>
  <p>A计算<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FK%253DY_b%255E%257BX_a%257D%2528mod%255C%253Bq%2529&amp;size=m" alt="">，B计算<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F26ff7d8c7bb48114b1092bd86b8d7f04.gif&amp;size=m" alt="">。</p>
 </li>
 <li>
  <p>中间人攻击</p>
 </li>
</ol>
<p>交换Y的过程中，Y有可能被替换假冒，而且不能发现。形式上，可以理解为一个中间人在跟双方同时通信，通信内容在中间人那里是可见的。</p>
<p>密钥交换协议不能抵抗中间人攻击，因为它没有对通信的参与方进行认证。这些缺陷可以通过使用数字签名和公钥证书来克服。</p>
<ol>
 <li>椭圆曲线上的加法</li>
</ol>
<p>（1）椭圆曲线是一个具有两个变元及系数的方程，其三次方程形如<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fce6048a427287ff31e68e95cf1f2d049.gif&amp;size=m" alt="">。椭圆曲线的定义中还包含一个称为无穷远点或零点的元素，记为<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faf68ced42b45909b4a06d2b62b4f95ad.gif&amp;size=m" alt="">.</p>
<p>（2）<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faf68ced42b45909b4a06d2b62b4f95ad.gif&amp;size=m" alt="">是加法的单位元，有<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faf68ced42b45909b4a06d2b62b4f95ad.gif%253D-O&amp;size=m" alt="">，<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd81be7a85b8f0c6b44a0315fceafd0c2.gif&amp;size=m" alt="">.</p>
<p>（3）点<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FP%2528x%252Cy%2529&amp;size=m" alt="">的负元为<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faf1b191b8b467be3f2a34072818791e6.gif&amp;size=m" alt="">，<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FP%2B%2528-P%2529%253DP-P%253DO&amp;size=m" alt="">.</p>
<p>（4）同一直线上的三个点之和为<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faf68ced42b45909b4a06d2b62b4f95ad.gif&amp;size=m" alt="">.</p>
<p>（5）更一般地，对于互不为负元的两个点：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb7702d309e4310a5927a2986a7425b03.png&amp;size=m" alt=""></p>
<p>（相当于切线斜率<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3F%255CDelta%253D%255Cfrac%257B3x_P%255E2%2Ba%257D%257B2y_P%257D&amp;size=m" alt="">)</p>
<p>e.g.<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9e62beb545a74c951c7948efbf1af8e8.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <p>ECC与RSA的对比：</p>
 </li>
 <li>
  <p>RSA基于因子分解难题，而为了提高难度需要加大位数，从而导致计算速度变慢；</p>
 </li>
 <li>
  <p>ECC基于离散对数难题，可以用较小的密钥长度达到较高的计算难度。</p>
 </li>
</ol>
<p>第11章 密码学Hash函数</p>
<ol>
 <li>
  <p>Hash函数的两个重要应用：</p>
 </li>
 <li>
  <p>消息认证：验证消息完整性的一种机制，确保收到的数据确实和发送时的一样，以及确保发送方声称的身份是真实有效的。消息认证是通过使用消息认证码（MAC）来实现的。MAC函数将通信双方共享的同一密钥和数据块作为输入，产生Hash值作为输出。</p>
 </li>
 <li>
  <p>数字签名：与MAC相似，在进行数字签名过程中使用用户的私钥加密消息的Hash值，其他任何知道该用户公钥的人都能够通过数字签名来验证消息的完整性。</p>
 </li>
 <li>
  <p>密码学Hash函数的安全性需求：</p>
 </li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbe731df17450859fdb5937857520a10c.png&amp;size=m" alt=""></p>
<ol>
 <li>SHA-512步骤</li>
</ol>
<p>（1）<strong>附加填充位</strong>。填充消息使其长度模1024与896同余[即长度= 896(mod 1024)]，即使消息已经满足上述长度要求，仍然需要进行填充，因此填充位数在1\~1024之间。填充由一个1和后续的0组成。</p>
<p>（2）<strong>附加长度</strong>。在消息后附加一个128位的块，将其视为128位的无符号整数（最高有效字节在前），它包含填充前消息的长度。</p>
<p>（3）<strong>初始化Hash缓冲区</strong>。Hash函数的中间结果和最终结果保存于512位的缓冲区中，缓冲区用8个64位的寄存器(a, b, c, d, e, f, g, h)表示。</p>
<p>（4）<strong>以1024位的分组（128个字节）为单位处理消息</strong>。算法的核心是具有80轮运算的模块。</p>
<p>（5）<strong>输出</strong>。所有的N个1024位分组都处理完以后，从第N阶段输出的是512位的消息摘要。</p>
<ol>
 <li>
  <p>MD5与SHA1的比较</p>
 </li>
 <li>
  <p>散列值长度：MD5为128bits；SHA-1为160bits。</p>
 </li>
 <li>
  <p>安全性：SHA-1好一点，但是SHA-1的设计原则没有公开。</p>
 </li>
 <li>
  <p>速度：SHA-1比MD5慢一些。</p>
 </li>
</ol>
<p>第12章 消息认证码</p>
<ol>
 <li>
  <p>产生认证符的函数类型：</p>
 </li>
 <li>
  <p>Hash函数</p>
 </li>
 <li>
  <p>消息加密（可逆）</p>
 </li>
 <li>
  <p>消息认证码（不要求可逆，多对一）：基于Hash的MAC（HMAC）；基于分组密码的MAC（DAA、CMAC）</p>
 </li>
 <li>
  <p>HMAC将Hash函数视为"黑盒"的两个好处：</p>
 </li>
 <li>
  <p>可对HMAC代码进行封装，有需要时直接使用；</p>
 </li>
 <li>
  <p>若希望替代HMAC中的Hash函数，则只需删去现有的Hash函数模块并加入新模块。</p>
 </li>
</ol>
<p>第13章 数字签名</p>
<ol>
 <li>
  <p>消息认证的两种争议：</p>
 </li>
 <li>
  <p>A可以伪造消息并称该消息发自B。</p>
 </li>
 <li>
  <p>B可以否认曾发送过某条消息。</p>
 </li>
 <li>
  <p>消息认证和数字签名的区别：</p>
 </li>
 <li>
  <p>消息认证可以保护信息交换双方不受第三方的攻击；</p>
 </li>
 <li>
  <p>数字签名能够处理通信双方自身发生的攻击。</p>
 </li>
 <li>
  <p>数字签名的特征（应该具有的性质）：</p>
 </li>
 <li>
  <p>必须能验证签名者、签名日期和时间。</p>
 </li>
 <li>
  <p>必须能认证被签的消息内容。</p>
 </li>
 <li>
  <p>应能由第三方仲裁以解决纷争。</p>
 </li>
 <li>
  <p>数字签名应满足的要求：</p>
 </li>
 <li>
  <p>签名必须是与消息相关的二进制位串。</p>
 </li>
 <li>
  <p>签名必须使用发送方某些独有的信息，以防伪造和否认。</p>
 </li>
 <li>
  <p>产生数字签名比较容易。</p>
 </li>
 <li>
  <p>识别和验证签名比较容易。</p>
 </li>
 <li>
  <p>伪造数字签名在计算上是不可行的。</p>
 </li>
 <li>
  <p>保存数字签名的副本是可行的。</p>
 </li>
 <li>
  <p>公钥密码加密方案和数字签名方案的对比：</p>
 </li>
 <li>
  <p>加密方案能够使用用户的公钥进行加密，使用用户的私钥进行解密。</p>
 </li>
 <li>
  <p>数字签名方案则是使用用户的私钥进行加密，公钥进行解密。</p>
 </li>
 <li>
  <p>RSA产生数字签名和DSA产生数字签名的区别：</p>
 </li>
 <li>
  <p>在RSA方法中，Hash函数的输入是要签名的消息，输出是定长的Hash码，用发送方的私钥将该Hash码加密形成签名，然后发送消息及其签名。接收方收到消息，计算Hash码。接收方用发送方的公钥对签名解密，如果计算出的Hash码与解密出的结果相同，则认为签名是有效的。因为只有发送方拥有私钥，所以只有发送方能够产生有效的签名。
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5db1f8eb190bba3ff4bd446fb140f90f.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>DSA方法也使用Hash函数，它产生的Hash码和为此次签名而产生的随机数<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3Fk&amp;size=m" alt="">作为签名函数的输入，签名函数依赖于发送方的私钥(<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FPR_a&amp;size=m" alt="">)和一组参数，这些参数为一组通信伙伴所共有，我们可以认为这组参数构成全局公钥(<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FPU_G&amp;size=m" alt="">)。签名由两部分组成，标记为<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3Fs&amp;size=m" alt="">和<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3Fr&amp;size=m" alt="">。接收方对接收到的消息产生Hash码，这个Hash码和签名一起作为验证函数的输入，验证函数依赖于全局公钥和发送方公钥(<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fddc656eb136d2d2691735b88ac2bb7ed.gif&amp;size=m" alt="">)，该发送方公钥同发送方私钥组成密钥对。若验证函数的输出等于签名中的<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3Fr&amp;size=m" alt="">成分，则签名是有效的。签名函数保证只有拥有私钥的发送方才能产生有效签名。
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F13ef49c067eaf1f854d8ad349211d9b2.png&amp;size=m" alt=""></p>
 </li>
</ol>
<p>第14章 密钥管理和分发</p>
<ol>
 <li>密钥分发方案</li>
</ol>
<p>（1）基于对称加密的对称密钥分发</p>
<p>假设每个用户与密钥分发中心（KDC）共享唯一的主密钥（A与KDC共享<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F66734bda7d5f6dd4ea9d37e332e60c26.gif&amp;size=m" alt="">，B与KDC共享<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7c2bb583d0bb8e616e64f0febea98104.gif&amp;size=m" alt="">）。</p>
<ul>
 <li>A向KDC请求一个会话密钥，包括A、B的身份以及临时交互号<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F19569aaeefa60ee171bbac62a4904146.gif&amp;size=m" alt="">（该次传输的唯一标志）。</li>
 <li>KDC返回用<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F66734bda7d5f6dd4ea9d37e332e60c26.gif&amp;size=m" alt="">加密的信息（包括会话密钥<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6f3c27a3f96df7939129dacbcf10e47.gif&amp;size=m" alt="">、临时交互号<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F19569aaeefa60ee171bbac62a4904146.gif&amp;size=m" alt="">等，临时交互号可以判断先前的请求是否被重放）和用<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7c2bb583d0bb8e616e64f0febea98104.gif&amp;size=m" alt="">加密的信息（包括会话密钥<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6f3c27a3f96df7939129dacbcf10e47.gif&amp;size=m" alt="">和A的标志<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FID_A&amp;size=m" alt="">。</li>
 <li>A存储将要使用的会话密钥，把来源于KDC的信息发送给B。</li>
 <li>B使用<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6f3c27a3f96df7939129dacbcf10e47.gif&amp;size=m" alt="">加密临时交互号<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FN_2&amp;size=m" alt="">并将结果发送给A。</li>
 <li>A使用<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6f3c27a3f96df7939129dacbcf10e47.gif&amp;size=m" alt="">加密<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3Ff%2528N_2%2529&amp;size=m" alt="">后发给B，其中<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3Ff&amp;size=m" alt="">是一个执行<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FN_2&amp;size=m" alt="">转换的函数。这一步保证B在步骤（3）中收到的信息没有受到重放攻击。</li>
</ul>
<p>（2）基于非对称加密的对称密钥分发</p>
<p>简单密钥分发方案容易受到中间人攻击，需采取确保保密性和身份认证的密钥分发方案。</p>
<ul>
 <li>
  <p>A用B的公钥<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FPU_b&amp;size=m" alt="">去加密包含A的标志符<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FID_A&amp;size=m" alt="">和一个临时交互号<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F19569aaeefa60ee171bbac62a4904146.gif&amp;size=m" alt="">的消息并发给B。</p>
 </li>
 <li>
  <p>B用A的公钥<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fddc656eb136d2d2691735b88ac2bb7ed.gif&amp;size=m" alt="">加密包含A的临时交互号<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F19569aaeefa60ee171bbac62a4904146.gif&amp;size=m" alt="">及B产生的新的临时交互号<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FN_2&amp;size=m" alt="">的消息发送给A。因为只有B可以解密消息(1)，故<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3F%257B%255Ccolor%257BRed%257D%2520N_1%257D&amp;size=m" alt="">在消息(2)中出现可以使A确定该消息来自于B。</p>
 </li>
 <li>
  <p>A使用的B的公钥加密后返回<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate.codecogs.com%2Fgif.latex%3FN_2&amp;size=m" alt="">，使B确定消息来自于A。</p>
 </li>
 <li>
  <p>A选择会话密钥<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6f3c27a3f96df7939129dacbcf10e47.gif&amp;size=m" alt="">，后发送<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9ff0fbc52d0def94f6fbfd99fff4c0d6.gif%253D%2520E%2528PU_b%252C%2520E%2528PR_a%252C%2520K_s%2529%2529&amp;size=m" alt="">给B。用B的公钥加密确保只有B可以读取该消息，用A的私钥加密保证只有A可以发送该消息。</p>
 </li>
 <li>
  <p>B计算<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2671d72a35e65f4f8b9d6ab5d2faccfe.gif&amp;size=m" alt="">从而恢复密钥<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6f3c27a3f96df7939129dacbcf10e47.gif&amp;size=m" alt="">。</p>
 </li>
 <li>
  <p>公钥分发方案</p>
 </li>
</ul>
<p>（1）公开发布</p>
<p>（2）公开可访问目录</p>
<p>（3）公钥授权</p>
<p>（4）公钥证书</p>
<ul>
 <li>任何通信方可以读取证书并确定证书拥有者的姓名和公钥。</li>
 <li>任何通信方可以验证该证书出自证书管理员，而不是伪造的。</li>
 <li>只有证书管理员才能产生并更新证书。</li>
 <li>任何通信方可以验证证书的时效性。</li>
</ul>
<p>第15章 用户认证</p>
<ol>
 <li>重放攻击的定义</li>
</ol>
<p>重放攻击是指攻击者发送一个目的主机已接收过的包，来达到欺骗系统的目的，主要用于身份认证过程，破坏认证的正确性。</p>
<ol>
 <li>
  <p>重放攻击的例子</p>
 </li>
 <li>
  <p>简单重放：对手只是复制消息然后重放给消息接收者。</p>
 </li>
 <li>
  <p>记录重复：对手在有效的时间窗口中重放一个时间戳消息给消息接收者。</p>
 </li>
 <li>
  <p>截获重放：原始消息被截获不能到达，只有重放消息可以到达消息接收者。</p>
 </li>
 <li>
  <p>无更改的反向重放：这个重放是给消息发送者的，这种攻击可能出现是因为对称加密的使用，或者发送者很难识别消息的发送及接收在内容上的不同。</p>
 </li>
 <li>
  <p>防止重放攻击的三个方法：</p>
 </li>
 <li>
  <p>序列号。为每一个用于认证交互的消息附上一个序列号，新的消息只有其序列号满足适当的顺序时才会被接收。</p>
 </li>
 <li>
  <p>时间戳：只有当消息中包含一个时间戳时，A才接收该消息。该时间截由A来判断，要接近于A所知的当前时间。该方法要求不同参与者之间的时钟是同步的。</p>
 </li>
 <li>
  <p>挑战/应答：A想要一个来自B的新消息，首先发给B一个临时交互号（询问），并要求后面从B收到的消息（回复）包含正确的临时交互号值。</p>
 </li>
</ol>
<p>第17章 传输层安全</p>
<ol>
 <li>
  <p>SSL体系结构所包含的协议：</p>
 </li>
 <li>
  <p><strong>SSL记录协议</strong>：为SSL连接提供了保密性和信息完整性的服务。记录协议包括四个步骤：（1）分段；（2）对压缩数据计算其消息认证码MAC；（3）将压缩信息和MAC用对称加密方法加密；（4）加上一个SSL头。</p>
 </li>
 <li>
  <p><strong>SSL修改密码规范协议</strong>：用于更新此连接使用的密码组。</p>
 </li>
 <li>
  <p><strong>SSL警报协议</strong>：用于向对等实体传递SSL相关的警报。</p>
 </li>
 <li>
  <p><strong>SSL握手协议</strong>：此协议允许客户端和服务器端相互认证、协商加密和MAC算法，保护数据使用的密钥通过SSL记录传送。握手协议在传递应用数据之前使用。握手协议包括四个阶段：（1）建立安全功能；（2）服务器认证和密钥交换；（3）客户端认证和密钥交换；（4）完成。</p>
 </li>
 <li>
  <p>SSL连接与SSL会话的区别：</p>
 </li>
 <li>
  <p>SSL连接：连接是提供合适服务类型的一种传输。对SSL来说,，连接表示的是对等网络关系，且连接是短暂的，每个连接与一个会话相关。</p>
 </li>
 <li>
  <p>SSL会话：会话是一个客户端和服务器间的关联，是通过握手协议创建的，定义了一组多个连接共享的密码安全参数。会话可用于减少为每次连接建立安全参数的昂贵协商费用。</p>
 </li>
 <li>
  <p>安全服务的四个应用</p>
 </li>
 <li>
  <p>SSL（安全套接层）：为TCP提供可靠的端到端安全服务。</p>
 </li>
 <li>
  <p>TLS（传输层安全）：编写SSL的互联网标准。</p>
 </li>
 <li>
  <p>HTTPS（SSL上的HTTP）：结合HTTP和SSL来实现Web浏览器和Web服务器之间的安全通信。</p>
 </li>
 <li>
  <p>SSH（安全外壳）：一种保障网络通信安全的协议，并且简单易实现，可以作为远程登录或X隧道技术的备选方法。</p>
 </li>
</ol>
<p>第19章 电子邮件安全</p>
<ol>
 <li>
  <p>PGP提供的4类服务：</p>
 </li>
 <li>
  <p>认证（数字签名）</p>
 </li>
 <li>
  <p>保密（消息加密）</p>
 </li>
 <li>
  <p>压缩</p>
 </li>
 <li>
  <p>电子邮件兼容性</p>
 </li>
 <li>
  <p>分离签名的用途：</p>
 </li>
 <li>
  <p>用户可能希望为所有发送或接收到的消息的各分离式签名建立并保持一个日志。</p>
 </li>
 <li>
  <p>一个可执行程序的分离式签名可以用来甄别该程序是否被病毒感染。</p>
 </li>
 <li>
  <p>分离式签名还可以用于多个成员对一份诸如合法契约等文件进行签名。</p>
 </li>
 <li>
  <p>PGP在对消息签名之后和加密之前压缩的原因：</p>
 </li>
 <li>
  <p>对未压缩的消息进行签名更有利于仅存储未压缩消息和签名以便未来的签名验证。</p>
 </li>
 <li>
  <p>PGP的压缩算法不适合为验证签名动态地生成消息的压缩版。</p>
 </li>
 <li>
  <p>在压缩之后对消息进行加密是为了增强密码运算的安全性。</p>
 </li>
 <li>
  <p>Radix-64编码：每3个字节组成的二进制数据映射成4个ASCII字符。由于大多数电子邮件系统都只允许使用由ASCII文本组成的数据块，所以PGP使用Radix-64将原字节流转换成可打印的ASCII字符流。</p>
 </li>
 <li>
  <p>S/MIME的功能：</p>
 </li>
 <li>
  <p>封装数据</p>
 </li>
 <li>
  <p>签名数据</p>
 </li>
 <li>
  <p>透明签名数据</p>
 </li>
 <li>
  <p>签名并封装数据</p>
 </li>
 <li>
  <p>S/MIME与PGP的区别：</p>
 </li>
 <li>
  <p>S/MIME倾向于推广为商业或组织用户的工业标准；</p>
 </li>
 <li>
  <p>PGP则用来为个人电子邮件安全提供服务选择。</p>
 </li>
</ol>
<p>第20章 IP安全性</p>
<ol>
 <li>
  <p>IPSec的应用</p>
 </li>
 <li>
  <p>分支机构通过Internet安全互连；</p>
 </li>
 <li>
  <p>远程安全访问Internet；</p>
 </li>
 <li>
  <p>与合作者建立外联网和内联网联系；</p>
 </li>
 <li>
  <p>加强电子商务的安全性。</p>
 </li>
 <li>
  <p>传输模式与隧道模式的区别：</p>
 </li>
 <li>
  <p>传输模式只加密有效载荷部分，不加密地址等头部分，用于两个机器之间直接连接的情况。</p>
 </li>
 <li>
  <p>隧道模式加密整个IP分组，并放在一个新的IP分组的有效载荷部分，用于两个LAN通过VPN网关连接的情况。</p>
 </li>
</ol>
<p>第23章 防火墙</p>
<ol>
 <li>防火墙的定义</li>
</ol>
<p>防火墙是指设置在不同网络或网络安全域（公共网和企业内部网）之间的一系列部件的组合。它是不同网络（安全域）之间的唯一出入口，能根据企业的安全政策控制（允许、拒绝、监测）出入网络的信息流，且本身具有很高的抗攻击能力，它是提供信息安全服务，实现网络和信息安全的基础设施。</p>
<ol>
 <li>
  <p>防火墙的基本原理</p>
 </li>
 <li>
  <p>建立起网络控制系统；</p>
 </li>
 <li>
  <p>隔离内部和外部网络；</p>
 </li>
 <li>
  <p>执行访问控制策略；</p>
 </li>
 <li>
  <p>防止外部的未授权节点访问内部网络和非法向外传递内部信息。</p>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/Gosick_Geass_Gate/article/details/86482170</p>]]></description><guid isPermaLink="false">/archives/e245e2e6-f5bf-4bc0-95c6-a4a323d1b8ee</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 08:43:01 GMT</pubDate></item><item><title><![CDATA[什么是加密算法]]></title><link>https://wiki.dgfygk.com/archives/abab3294-a234-438c-a027-ccb8725ac91e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AF%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95&amp;url=/archives/abab3294-a234-438c-a027-ccb8725ac91e" width="1" height="1" alt="" style="opacity:0;">
<p>转载自<a href="https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&amp;mid=2653195006&amp;idx=1&amp;sn=5046ccd0ceee3ccfcde3a63aabaa0c69&amp;chksm=8c99f824bbee71323d9c580a6b4b74312b45fc6565f4345ca37d70629f60d5011bb9fe14a907&amp;mpshare=1&amp;scene=1&amp;srcid=0926Ojz5Uny6YMFeF6MkI5lv#rd">什么是加密算法</a></p>
<p>Java的加密知识也是Java常见的领域之一，加密技术的底层确实很复杂，运用了大量的数学知识，要弄明白非常复杂。但是Java语言中运用密码加密工具却是非常简单。我们在Java里面运用这些加密技术，只需要把原理和使用场景等搞明白就可以了，具体底层实现不用研究。</p>
<p>常用的加密算法有对称加密算法，非对称加密算法，哈希算法，数字签名等几类。</p>
<p>对称加密顾名思义就是加密和解密是对称的，加密时用一个秘钥去加密，解密时用同一个秘钥去解密，由信息发送方和接收方共同约定一个秘钥。缺点是风险都在这个秘钥上面，一旦被窃取，信息会暴露。所以安全级别不够高。常用对称加密算法有DES，3DES，AES等。在jdk中也都有封装。</p>
<p>非对称加密，顾名思义就是加密与解密的过程不是对称的，不是用的同一个秘钥。非对称加密有个公私钥对的概念，也就是有两把秘钥，一把是公钥，一把是私钥，一对公私钥有固定的生成方法，在加密的时候，用公钥去加密，接收方再用对应的私钥去解密。使用时可以由接收方生成公私钥对，然后将公钥传给加密方，这样私钥不会在网络中传输，没有被窃取的风险。比如github底层的ssh协议就是公私钥非对称加密。并且公钥是可以由私钥推导出来的，反过来却不行，由通过公钥无法推导出私钥。常用算法有RSA，ECC等。ECC也是比特币底层用的比较多的算法。通过和对称加密的对比，可以看到，非对称加密解决了秘钥传输中的安全问题。</p>
<p>哈希算法，简单说就是将任意数据都转换成一个固定长度的字符串。通过哈希后的值几乎无法推导出原文。而且两个不同的原文哈希后结果一定不同。常用算法有MD5，SHA256等等。常用场景，md5常用场景是数据库的密码存储。sha256在挖矿中可以用到。</p>
<p>非对称加密也有一个问题，就是内容在发送前可能被篡改，因为公钥是有可能被窃取的，所以窃取者完全可以改为发送别的内容。</p>
<p>解决的办法就是数字签名。数字签名和非对称加密是反过来的，也是有公私钥对，但是是用私钥签名，用公钥去验证签名。比如发送方除了发送用公钥加密后的密文，还要发送签名，签名内容通常是密文哈希后的字符串，接收方首先验证签名是否正确，如果正确那么密文解密后就是真正需要并且没有被篡改过的内容。注意，签名和非对称用的是两对不同的公私钥。</p>
<p>上面是对几个加密算法的一个简单讲解，除了上面的还有base58等，比特币底层安全也是依赖于加密。后面会一个一个介绍要用到的加密算法的介绍和使用，但是仅仅是使用，底层不会讲。</p>
<h2>MD5</h2>
<p><strong>MD5</strong>即Message-Digest Algorithm 5（信息-摘要算法5），用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一（又译摘要算法、哈希算法），主流编程语言普遍已有MD5实现。将数据（如汉字）运算为另一固定长度值，是杂凑算法的基础原理，MD5的前身有MD2、MD3和MD4。</p>
<p>MD5算法具有以下特点：</p>
<p>1、压缩性：任意长度的数据，算出的MD5值长度都是固定的。</p>
<p>2、容易计算：从原数据计算出MD5值很容易。</p>
<p>3、抗修改性：对原数据进行任何改动，哪怕只修改1个字节，所得到的MD5值都有很大区别。</p>
<p>4、强抗碰撞：已知原数据和其MD5值，想找到一个具有相同MD5值的数据（即伪造数据）是非常困难的。</p>
<p>MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式（就是把一个任意长度的字节串变换成一定长的十六进制数字串）。除了MD5以外，其中比较有名的还有sha-1、RIPEMD以及Haval等。</p>
<p>JDK就自带了md5加密算法，直接调用很方便。需要引入一个类：</p>
<blockquote>
 <p>import java.security.MessageDigest;</p>
</blockquote>
<p>首先创建一个springboot项目，加上一个web以来，内容如下：</p>
<h2>SHA256</h2>
<p>学Java的对哈希算法都不陌生，毕竟每个类都有hashCode方法。</p>
<p>散列算法（Hash Algorithm），又称哈希算法，杂凑算法，是一种从任意文件中创造小的数字「指纹」的方法。与指纹一样，散列算法就是一种以较短的信息来保证文件唯一性的标志，这种标志与文件的每一个字节都相关，而且难以找到逆向规律。因此，当原有文件发生改变时，其标志值也会发生改变，从而告诉文件使用者当前的文件已经不是你所需求的文件。</p>
<p>一个优秀的 hash 算法，将能实现：</p>
<p>正向快速：给定明文和 hash 算法，在有限时间和有限资源内能计算出 hash 值。</p>
<p>逆向困难：给定（若干） hash 值，在有限时间内很难（基本不可能）逆推出明文。</p>
<p>输入敏感：原始输入信息修改一点信息，产生的 hash 值看起来应该都有很大不同。</p>
<p>冲突避免：很难找到两段内容不同的明文，使得它们的 hash 值一致（发生冲突）。即对于任意两个不同的数据块，其hash值相同的可能性极小；对于一个给定的数据块，找到和它hash值相同的数据块极为困难。</p>
<p>但在不同的使用场景中，如数据结构和安全领域里，其中对某一些特点会有所侧重。</p>
<p>安全散列算法（英语：Secure Hash Algorithm，缩写为SHA）是一个密码散列函数家族，是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的，长度固定的字符串（又称消息摘要）的算法。且若输入的消息不同，它们对应到不同字符串的机率很高。</p>
<p>SHA家族的五个算法，分别是SHA-1、SHA-224、SHA-256、SHA-384，和SHA-512。主要适用于数字签名标准（DigitalSignature Standard DSS）里面定义的数字签名算法（Digital Signature Algorithm DSA）。比特币里面的就是SHA-256算法。</p>
<p>说简单一些，就是对一个对象的多个关键不重复信息组合起来，通过算法生成一个加密字符串。</p>
<p>继续上一个项目，在对应的包下建一个工具类：</p>
<blockquote>
 <p>package btcdemo.btcdemo.security;</p>
 <p>public class Sha256Utils</p>
</blockquote>
<p>引入的加密类和md5一样：</p>
<blockquote>
 <p>import java.security.MessageDigest;</p>
</blockquote>
<p>下面是算法的具体内容：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc2508269739e6026204d36a569886b49.jpeg&amp;size=m" alt=""></p>
<p>下面是算法的测试类内容：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F44509980a35be6b50072884d18881b24.jpeg&amp;size=m" alt=""></p>
<p>运行可以看到如下结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2568498834384b65b7572ab015223c15.jpeg&amp;size=m" alt=""></p>
<p>目前目录结构如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc531204b91b31b8a344209712cd60633.jpeg&amp;size=m" alt=""></p>
<p>项目代码：https://github.com/guoyb1990/btc-demo.git</p>
<h2>BASE64\&amp;BASE58</h2>
<p>Base64是网络上最常见的用于传输8Bit字节码的编码方式之一，Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看RFC2045～RFC2049，上面有MIME的详细规范。</p>
<p>Base64编码是从二进制到字符的过程，可用于在HTTP环境下传递较长的标识信息。例如，在Java Persistence系统Hibernate中，就采用了Base64来将一个较长的唯一标识符（一般为128-bit的UUID）编码为一个字符串，用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中，也常常需要把二进制数据编码为适合放在URL（包括隐藏表单域）中的形式。此时，采用Base64编码具有不可读性，需要解码后才能阅读。</p>
<p>Base64编码要求把3个8位字节（3*8=24）转化为4个6位的字节（4*6=24），之后在6位的前面补两个0，形成8位一个字节的形式。 如果剩下的字符不足3个字节，则用0填充，输出字符使用'='，因此编码后输出的文本末尾可能会出现1或2个'='。</p>
<p>jdk的工具包中就自带base64的工具类，使用base64的方法也非常简单，先新建一个工具类：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0ccd814848ba3ead93771707376307a0.jpeg&amp;size=m" alt=""></p>
<p>base64是可逆的，加密解密内容如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8b052b12b27ef1afa736f014a861b9b9.jpeg&amp;size=m" alt=""></p>
<p>可以看到使用非常简单，下面是测试类内容：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Feae81768120be73ae6901bddb9a1ea4e.jpeg&amp;size=m" alt=""></p>
<p>执行测试方法，结果如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5995674946c2d5f2bc2283254f9e85bc.jpeg&amp;size=m" alt=""></p>
<p>可以看到base64非常简单。</p>
<p>base58和base64一样是一种二进制转可视字符串的算法，主要用来转换大整数值。区别是，转换出来的字符串，去除了几个看起来会产生歧义的字符，如 0 (零), O (大写字母O), I (大写的字母i) and l (小写的字母L) ，和几个影响双击选择的字符，如/, +。结果字符集正好58个字符(包括9个数字，24个大写字母，25个小写字母)。不同的应用实现中，base58 最后查询的字母表可能不同，所以没有具体的标准。</p>
<p>可以看出，base58是base64的一种人性化的版本，是站在使用者的角度考虑的。</p>
<p>新建一个Base58的工具类，目录结构如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F39ccb7e641e50e7f68710d708d607341.jpeg&amp;size=m" alt=""></p>
<p>工具类的大概内容结构如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa6320f0f9ad7222502b1b866c0787be4.jpeg&amp;size=m" alt=""></p>
<p>因为内容过多，可以通过下载源码查看。</p>
<p>下面是测试代码：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F37a5a2a0201cdd692e9b822f3e7b3af8.jpeg&amp;size=m" alt=""></p>
<p>运行结果如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa1c16aee955677afa3e233349bb5ee15.jpeg&amp;size=m" alt=""></p>
<p>可以看到，base58的使用方法是一样的。</p>
<p>项目代码：https://github.com/guoyb1990/btc-demo.git</p>
<p>原文链接: https://blog.csdn.net/moakun/article/details/82888925</p>]]></description><guid isPermaLink="false">/archives/abab3294-a234-438c-a027-ccb8725ac91e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:42:49 GMT</pubDate></item><item><title><![CDATA[常见的加密算法介绍]]></title><link>https://wiki.dgfygk.com/archives/2c5be969-035e-4b91-ad64-562560b0c38f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B8%B8%E8%A7%81%E7%9A%84%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95%E4%BB%8B%E7%BB%8D&amp;url=/archives/2c5be969-035e-4b91-ad64-562560b0c38f" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>数据加密目前仍是计算机系统对信息进行保护的一种最可靠的办法。它利用密码技术对信息进行加密，实现信息隐蔽，从而起到保护信息的安全的作用。</p>
</blockquote>
<h4>常见的加密算法</h4>
<p>常见的加密算法有SHA、MD5、DES、3DES、RC4、RSA、AES等。</p>
<h4>分类</h4>
<p><strong>加密的算法：</strong> SHA MD5
 <br>
 <strong>既能加密又能解密的算法：</strong> DES、3DES、RC4、AES、RSA</p>
<h4>对称加密和非对称加密算法</h4>
<p><strong>对称加密</strong> ：即加密和解密的密钥是一致的，上面加密算法中DES、RC4、AES属于对称加密
 <br>
 <strong>非对称加密</strong>：加密和解密的密钥不相同，一般分为公钥和私钥，公钥大家都知道所以用于加密，私钥自己才知道用来对其解密。上面RSA属于非对称加密算法</p>
<h4>加密算法概念</h4>
<h5><strong>SHA(Secure Hash Algorithm安全散列算法)安全的哈希算法</strong></h5>
<p>安全哈希算法（Secure Hash Algorithm）主要适用于数字签名标准（Digital Signature Standard DSS）里面定义的数字签名算法（Digital Signature Algorithm DSA）。对于长度小于2^64位的消息，SHA1会产生一个160位的消息摘要。该算法经过加密专家多年来的发展和改进已日益完善，并被广泛使用。该算法的思想是接收一段明文，然后以一种不可逆的方式将它转换成一段（通常更小）密文，也可以简单的理解为取一串输入码（称为预映射或信息），并把它们转化为长度较短、位数固定的输出序列即散列值（也称为信息摘要或信息认证代码）的过程。散列函数值可以说是对明文的一种"指纹"或是"摘要"所以对散列值的数字签名就可以视为对此明文的数字签名。</p>
<h5><strong>MD5(Message Digest algorithm 5，信息摘要算法)</strong></h5>
<p>MD5即Message-Digest Algorithm 5（信息-摘要算法5），用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一（又译摘要算法、哈希算法），主流编程语言普遍已有MD5实现。将数据（如汉字）运算为另一固定长度值，是杂凑算法的基础原理，MD5的前身有MD2、MD3和MD4。
 <br>
 MD5算法具有以下特点：
 <br>
 1、压缩性：任意长度的数据，算出的MD5值长度都是固定的。
 <br>
 2、容易计算：从原数据计算出MD5值很容易。
 <br>
 3、抗修改性：对原数据进行任何改动，哪怕只修改1个字节，所得到的MD5值都有很大区别。
 <br>
 4、强抗碰撞：已知原数据和其MD5值，想找到一个具有相同MD5值的数据（即伪造数据）是非常困难的。</p>
<h5><strong>DES</strong></h5>
<p>DES全称为Data Encryption Standard，即数据加密标准，是一种使用密钥加密的块算法，1977年被美国联邦政府的国家标准局确定为联邦资料处理标准（FIPS），并授权在非密级政府通信中使用，随后该算法在国际上广泛流传开来。需要注意的是，在某些文献中，作为算法的DES称为数据加密算法（Data Encryption Algorithm,DEA），已与作为标准的DES区分开来。
 <br>
 DES算法入口参数编辑
 <br>
 DES算法的入口参数有三个：Key、Data、Mode。其中Key为7个字节共56位，是DES算法的工作密钥；Data为8个字节64位，是要被加密或被解密的数据；Mode为DES的工作方式,有两种:加密或解密。</p>
<h5><strong>3DES-DES向AES的过度</strong></h5>
<p>　　3DES是DES加密算法的一种模式，它使用3条64位的密钥对数据进行三次加密。数据加密标准（DES）是美国的一种由来已久的加密标准，它使用对称密钥加密法。</p>
<p>　　3DES（即Triple DES）是DES向AES过渡的加密算法（1999年，NIST将3-DES指定为过渡的加密标准），是DES的一个更安全的变形。它以DES为基本模块，通过组合分组方法设计出分组加密算法。
 <br>
 　　
 <br>
 　　设Ek()和Dk()代表DES算法的加密和解密过程，K代表DES算法使用的密钥，P代表明文，C代表密表，这样，</p>
<p>　　3DES加密过程为：C=Ek3(Dk2(Ek1(P)))</p>
<p>　　3DES解密过程为：P=Dk1((EK2(Dk3©))</p>
<p>　　K1、K2、K3决定了算法的安全性，若三个密钥互不相同，本质上就相当于用一个长为168位的密钥进行加密。多年来，它在对付强力攻击时是比较安全的。若数据对安全性要求不那么高，K1可以等于K3。在这种情况下，密钥的有效长度为112位。</p>
<h5><strong>RC4</strong></h5>
<p>RC4加密算法是大名鼎鼎的RSA三人组中的头号人物Ronald Rivest在1987年设计的密钥长度可变的流加密算法簇。之所以称其为簇，是由于其核心部分的S-box长度可为任意，但一般为256字节。该算法的速度可以达到DES加密的10倍左右，且具有很高级别的非线性。RC4起初是用于保护商业机密的。但是在1994年9月，它的算法被发布在互联网上，也就不再有什么商业机密了。RC4也被叫做ARC4（Alleged RC4——所谓的RC4），因为RSA从来就没有正式发布过这个算法。</p>
<h5><strong>AES</strong></h5>
<p>　　AES算法基于排列和置换运算。排列是对数据重新进行安排，置换是将一个数据单元替换为另一个。
 <br>
 　　AES使用几种不同的方法来执行排列和置换运算。AES是一个迭代的、对称密钥分组的密码，它可以使用128、192和256位密钥，并且用128位（16字节）分组加密和解密数据。
 <br>
 　　与公共密钥加密使用密钥对不同，对称密钥密码使用相同的密钥加密和解密数据。通过分组密码返回的加密数据的位数与输入数据相同。迭代加密使用一个循环结构，在该循环中重复置换和替换输入数据。</p>
<h5><strong>RSA(三位数学家Rivest、Shamir、Adleman)</strong></h5>
<p>RSA是目前最有影响力和最常用的公钥加密算法，它能够抵抗到目前为止已知的绝大多数密码攻击，已被ISO推荐为公钥数据加密标准。
 <br>
 今天只有短的RSA钥匙才可能被强力方式解破。到2008年为止，世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长，用RSA加密的信息实际上是不能被解破的。但在分布式计算和量子计算机理论日趋成熟的今天，RSA加密安全性受到了挑战和质疑。
 <br>
 RSA算法基于一个十分简单的数论事实：将两个大质数相乘十分容易，但是想要对其乘积进行因式分解却极其困难，因此可以将乘积公开作为加密密钥。RSA公开密钥密码体制。所谓的公开密钥密码体制就是使用不同的加密密钥与解密密钥，是一种"由已知加密密钥推导出解密密钥在计算上是不可行的"密码体制。</p>
<p>后面将对这几种加密算法一一介绍　　</p>
<p>原文链接: https://blog.csdn.net/qianyiyiding/article/details/77887330</p>]]></description><guid isPermaLink="false">/archives/2c5be969-035e-4b91-ad64-562560b0c38f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:42:34 GMT</pubDate></item><item><title><![CDATA[【JVM】垃圾释放方式：标记-清除、复制算法、标记-整理、分代回收]]></title><link>https://wiki.dgfygk.com/archives/8ac38139-7035-4da3-9223-7e3f5056d396</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90JVM%E3%80%91%E5%9E%83%E5%9C%BE%E9%87%8A%E6%94%BE%E6%96%B9%E5%BC%8F%EF%BC%9A%E6%A0%87%E8%AE%B0-%E6%B8%85%E9%99%A4%E3%80%81%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95%E3%80%81%E6%A0%87%E8%AE%B0-%E6%95%B4%E7%90%86%E3%80%81%E5%88%86%E4%BB%A3%E5%9B%9E%E6%94%B6&amp;url=/archives/8ac38139-7035-4da3-9223-7e3f5056d396" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1__5">1. 标记-清除</a></li>
 <li><a href="https://wiki.dgfygk.com#2__23">2. 复制算法</a></li>
 <li><a href="https://wiki.dgfygk.com#4__39">4. 标记-整理</a></li>
 <li><a href="https://wiki.dgfygk.com#4__51">4. 分代回收</a></li>
</ul>
<p>把标记为垃圾的对象的内存空间进行释放。主要有三种释放方式</p>
<p>
 <br>
</p>
<ol>
 <li>
  <h2>标记-清除</h2>
 </li>
</ol>
<p>把标记为垃圾的对象，直接释放掉（最朴素的做法）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F23cbc460a46b3c64559a8d54230786b0.png&amp;size=m" alt="">
 <br>
 此时就是把标记为垃圾的对象所对应的内存空间直接释放。但这样的释放会产生”<strong>内存碎片</strong>“问题</p>
<ul>
 <li>上述释放方式，就可能会产生很多小的，但是离散的空闲内存空间</li>
 <li>这样就可能会导致后续申请内存失败</li>
 <li>因为内存申请都是一次申请一个连续的空间</li>
 <li>申请 <code>1M</code> 内存空间，此时 <code>1M</code> 都是连续的</li>
 <li>如果存在很多内存碎片，就可能导致总的空闲空间远远超过 <code>1MB</code>，但是并不存在比 <code>1M</code> 大的连续空间。此时，去申请内存就会失败</li>
</ul>
<blockquote>
 <p>类似于，你去吗房子，需要一次性付 <code>30W</code> 首付。你的总存款超过了 <code>30W</code>，但是可能分散在不同的卡上，所以就没法完成上述的支付操作</p>
</blockquote>
<p><strong>一般不会使用这个方案，内存碎片问题，比较致命</strong></p>
<ol>
 <li>
  <h2>复制算法</h2>
 </li>
</ol>
<p>复制算法的核心就是：不直接释放内存，而是把不是垃圾的对象，复制到内存的另一半里面。</p>
<ul>
 <li>然后就把左侧空间整体释放掉<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F8902181aa10bc5393ce8d181ddd25dbe.png&amp;size=m" alt=""></li>
</ul>
<hr>
<p>确实能规避内存碎片问题，但是也有缺点：</p>
<ol>
 <li>
  <p>总的可用内存变少了（买两碗豆浆，喝一碗倒一碗）</p>
 </li>
 <li>
  <p>如果每次要复制的对象比较多，此时复制的开销也就很大了。</p>
 </li>
</ol>
<p><strong>需要是当这一轮 GC 的过程中，大部分对象都释放，少数对象存活，这个时候适合用复制</strong></p>
<ol>
 <li>
  <h2>标记-整理</h2>
 </li>
</ol>
<p>类似于顺序表删除中间元素，中间有个"搬运"的过程</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F006fd54394239ed4c836dab9ad4396c8.png&amp;size=m" alt=""></p>
<ul>
 <li>若要删除 1，就把 2 往前搬，覆盖掉 1，3 覆盖掉 2… 最后把后面的内存释放</li>
</ul>
<p>通过这个过程，也可以解决内存碎片问题，并且这个过程也不像复制算法一样，需要浪费过多的内存空间。但是，这里的搬运内存的开销很大</p>
<p>因此，JVM 没有直接使用上述的方案，而是结合上述思想，高出了一个"综合性"方案，取长补短</p>
<ol>
 <li>
  <h2>分代回收</h2>
 </li>
</ol>
<p>依据不同种类的对象，采取不同的方式</p>
<p>引入概念：<strong>对象的年龄</strong></p>
<ul>
 <li>JVM 中，有专门的线程负责周期性扫描/释放</li>
 <li>一个对象如果被线程扫描了一次，可达了（不是垃圾），年龄就+1（初始年龄相当于是 0）</li>
</ul>
<p>JVM 中就会跟对象年龄的差异，把整个堆内存分为两个大的部分：新生代（年龄小的对象）/老年代（年龄大的对象）</p>
<blockquote>
 <p>年龄也是会占内存的，每个对象有一个"对象头”，在对象头里有一个属性来存储年龄</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F172d6bc39e42f2c3038d7f8de4385f96.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <p>当我们代码中 <code>new</code> 出一个新的对象，这个对象就是被创建在伊甸区，在伊甸区会有很多对象</p>
 </li>
 <li>
  <p><strong>一个经验规律</strong>：伊甸区中的对象，大部分是活不过第一轮 GC 的（朝生夕死，生命周期非常短）</p>
 </li>
 <li>
  <p>第一轮 GC 扫描完成之后，少数在伊甸区中幸存的对象，就会通过复制算法，复制到生存区</p>
 </li>
 <li>
  <p>后续的 GC 扫描线程还会持续的扫描，不仅要扫描伊甸区，还要扫描生存区的对象</p>
 </li>
 <li>
  <p>生存区中的大部分对象也会在扫描中被标记为垃圾</p>
 </li>
 <li>
  <p>少数存活的，就会继续使用复制算法，复制到另一个生存区中</p>
 </li>
 <li>
  <p>只要这个对象能够在生存区中继续存活，就会被复制算法继续拷贝到另一半的生存区中</p>
 </li>
 <li>
  <p>每经历一轮 GC，对象的年龄就会<code>+1</code></p>
 </li>
</ol>
<blockquote>
 <p>每一次拷贝不仅仅只有生存区的对象进行互相拷贝，还有来自伊甸区的对象</p>
</blockquote>
<ol>
 <li>
  <p>如果这个对象在生存区中, 经过了若干轮 GC 仍然健在，JVM 就会认为，这个对象生命周期大概很长，就会把这个对象从生存区拷贝到老年代</p>
 </li>
 <li>
  <p>老年代的对象，当然也要被 GC 扫描，但是扫描频次就会大大降低了</p>
 </li>
</ol>
<blockquote>
 <p>老年代的对象，要寄早寄了。既然没有寄，说明其生命周期应该是很长的，频繁 GC 扫描意义也不大，白白浪费时间。不入放到老年代，降低扫描频率</p>
</blockquote>
<ol>
 <li>对象在老年代寿终正寝，此时 JVM 就会按照标记整理的方式，释放内存</li>
</ol>
<hr>
<blockquote>
 <p>上述过程，也是非常好理解的。这个过程和我们找工作是一模一样的</p>
 <ul>
  <li>伊甸区：一个公司收到很多的简历，然后安排笔试。笔试的过程就会使绝大部分的人被刷掉，少数的人能进入到面试环节</li>
  <li>生存区：进入面试环节，面试有很多轮，每一轮也会刷掉一批人</li>
  <li>老年代：通过了上述层层筛选，拿到 offer，进入公司入职了。</li>
  <li>进入公司后也会有绩效考评、末位淘汰，但周期比较长，远远超过笔试和面试</li>
 </ul>
</blockquote>
<p>上述分代回收是 JVM GC 中的核心思想。但是 JVM 实际的垃圾回收的实现细节上，还会存在一些变数和优化</p>
<p>垃圾收集器就是具体实际的情况
 <br>
 课件列出了 7 个垃圾收集器，主要掌握 CMS，G1（ZGC）</p>
<p>原文链接: https://blog.csdn.net/Yeeear/article/details/142519214</p>]]></description><guid isPermaLink="false">/archives/8ac38139-7035-4da3-9223-7e3f5056d396</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 08:42:23 GMT</pubDate></item><item><title><![CDATA[哈希 详解]]></title><link>https://wiki.dgfygk.com/archives/b42ddcf4-b4c8-4245-b98b-9d07d3835dac</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%93%88%E5%B8%8C%20%E8%AF%A6%E8%A7%A3&amp;url=/archives/b42ddcf4-b4c8-4245-b98b-9d07d3835dac" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p>
 <br>
</p>
<p><a href="https://wiki.dgfygk.com#1.%20%E2%80%9C%E5%93%88%E5%B8%8C%E2%80%9D%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F">1. “哈希"是什么？</a></p>
<p><a href="https://wiki.dgfygk.com#2.%20%E5%93%88%E5%B8%8C%E5%86%B2%E7%AA%81">2. 哈希冲突</a></p>
<p><a href="https://wiki.dgfygk.com#3.%20%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0%C2%A0">3. 哈希函数</a></p>
<p><a href="https://wiki.dgfygk.com#3.1%20%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99">3.1 设计原则</a></p>
<p><a href="https://wiki.dgfygk.com#3.2%20%E5%B8%B8%E8%A7%81%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0">3.2 常见哈希函数</a></p>
<p><a href="https://wiki.dgfygk.com#4.%20%E8%A7%A3%E5%86%B3%E5%93%88%E5%B8%8C%E5%86%B2%E7%AA%81%E7%9A%84%E4%B8%A4%E7%A7%8D%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95">4. 解决哈希冲突的两种常见方法</a></p>
<p><a href="https://wiki.dgfygk.com#4.1%20%E9%97%AD%E6%95%A3%E5%88%97">4.1 闭散列</a></p>
<p><a href="https://wiki.dgfygk.com#4.2%20%E5%BC%80%E6%95%A3%E5%88%97">4.2 开散列</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A04.3%20%E6%95%A3%E5%88%97%E8%A1%A8%E7%9A%84%E6%89%A9%E5%AE%B9%E9%97%AE%E9%A2%98">4.3 散列表的扩容问题</a></p>
<p><a href="https://wiki.dgfygk.com#5.%20%E5%93%88%E5%B8%8C%E8%A1%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%20%E5%B9%B6%20%E5%B0%81%E8%A3%85%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0unordered%E7%B3%BB%E5%88%97%E5%AE%B9%E5%99%A8">5. 哈希表的实现 并 封装模拟实现unordered系列容器</a></p>
<p><a href="https://wiki.dgfygk.com#6.%20%E5%93%88%E5%B8%8C%E7%9A%84%E5%BA%94%E7%94%A8">6. 哈希的应用</a></p>
<p><a href="https://wiki.dgfygk.com#6.1%20%E4%BD%8D%E5%9B%BE%20--%20bitset">6.1 位图 – bitset</a></p>
<p><a href="https://wiki.dgfygk.com#6.2%20%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%20--%C2%A0Bloom%20Filter%C2%A0">6.2 布隆过滤器 – Bloom Filter</a></p>
<hr>
<p>
 <br>
</p>
<ol>
 <li>
  <h2>“哈希"是什么？</h2>
 </li>
</ol>
<p>在进入正题之前，先带大家看一下什么是序列式/关联式容器：</p>
<p>比如：vector、list、deque、 forward_list(C++11)等，这些容器统称为<strong>序列式容器</strong> ，因为其底层为<strong>线性</strong>序列的数据结构，里面存储的是元素本身。</p>
<p><strong>关联式容器</strong> 也是用来存储数据的，与序列式容器不同的是，其底层为<strong>非线性</strong> 结构，且里面通常存储的是<key, value>结构的<strong>键值对</strong>。</key,></p>
<p>在C++98中，STL提供了底层为红黑树结构的一系列关联式容器（set/map/multiset/multimap），<strong>在查询时效率可达到log_2 N</strong>，即使最差情况下也只需要比较红黑树的高度次，相较 序列式容器的线性查找 效率更高；但当树中的节点非常多时（海量数据），查询效率也不理想。</p>
<p><strong>由此可以得到结论：搜索的效率取决于搜索过程中关键码的比较次数。</strong></p>
<p><strong>那么最理想的搜索方法就是：可以不经过任何比较，直接就能将元素找到；或者退而求其次，将整体的比较次数降到最低。</strong></p>
<p><strong>至于如何做到呢，就是本文的重点 ——</strong><strong>哈希：构造一种存储结构，让其可以通过某种方法，使关键码（key）和 其存储位置建立一 一对应的映射关系。</strong></p>
<p><strong>当向该结构中：</strong></p>
<p><strong>插入元素时： 根据待插入元素的关键码，以这种方法得到该元素的存储位置并按此位置进行存放。</strong></p>
<p><strong>搜索元素时： 对元素的关键码进行同样的计算，得到存储位置，在结构中按此位置取元素决定是否搜索成功。</strong></p>
<p><strong>这样的方法就称为：哈希函数（hashfunc） 
  <br>构造出来的结构称为：哈希表或散列表</strong></p>
<p>如下示例一：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F96fc2d36f9864415af9101ce36392905.png&amp;size=m" alt=""></p>
<p><strong>至此，就可以回答最初的问题：“哈希"是什么？—— “哈希” 是一种数据结构和算法的思想，通俗点说就是一种解决问题的思考方式和处理手段；不是某种具体的数据结构或容器。</strong></p>
<p>而在C++11中，STL提供的4个 unordered 系列的关联式容器，其底层结构就是用 哈希思想 实现的。</p>
<p>（关于unordered系列容器的接口使用与红黑树结构的关联式容器使用方式基本类似，此处不赘述，可点击查看在线文档说明：<a href="https://legacy.cplusplus.com/reference/unordered_map/unordered_map/" title="unordered_map">unordered_map</a> ，<a href="https://legacy.cplusplus.com/reference/unordered_set/unordered_set/" title="unordered_set">unordered_set</a> ，<a href="https://legacy.cplusplus.com/reference/unordered_map/unordered_multimap/" title="unordered_multimap">unordered_multimap</a> ，<a href="https://legacy.cplusplus.com/reference/unordered_set/unordered_multiset/" title="unordered_multiset">unordered_multiset</a> )</p>
<p>接下来，本文将围绕以下内容展开： 哈希函数的设计和选择；会产生的问题(哈希冲突)及如何解决；改造模拟实现unordered系列容器；哈希的应用等。</p>
<ol>
 <li>
  <h2>哈希冲突</h2>
 </li>
</ol>
<p><strong>也叫哈希碰撞，即不同关键码通过 同一哈希函数 映射 到同一位置。</strong></p>
<p>把具有不同关键码而具有相同哈希地址的数据元素称为"同义词”。</p>
<p>比如，现在要在上述点1的示例一中插入元素16，即key为16，Hashi(16) = 16 % 10 = 6，但是此时6地址处已有元素，于是发生冲突。</p>
<p><strong>那么发生哈希冲突该如何处理呢？</strong></p>
<p><strong>引起哈希冲突的一个原因可能是：哈希函数设计不够合理。</strong></p>
<ol>
 <li>
  <h2>哈希函数</h2>
 </li>
</ol>
<h3>3.1 设计原则</h3>
<p>哈希函数的定义域必须包括需要存储的全部关键码；而如果散列表允许有m个地址时，其值域(h哈希地址)必须在0到m-1之间</p>
<p>哈希函数计算出来的地址能均匀分布在整个空间中——&gt;尽量减少冲突</p>
<p>哈希函数应该比较简单</p>
<h3>3.2 常见哈希函数</h3>
<p><strong>1. 直接定址法（常用）</strong></p>
<p>直接用关键码做哈希地址</p>
<p>优点：简单</p>
<p>缺点：需要事先知道关键码的大小分布情况；可能造成较大空间浪费</p>
<p><strong>只适用于：元素集合的关键码较小且连续的情况。比如：找出字符串中只出现一次的英文字母，就可以建立数组，直接用字母的ASCII值做哈希地址 进行次数统计。</strong></p>
<p><strong>2. 除留余数法（常用）</strong></p>
<p>设散列表中允许的地址数为m，取一个不大于m，但最接近或者等于m的质数p作为除数， 按照哈希函数：Hash(key) = key% p(p&lt;=m),将关键码转换成哈希地址。如上述点1的示例一。</p>
<p><strong>3. 平方取中法</strong></p>
<p>假设关键码为1234，对它平方就是1522756，抽取中间的3位227作为哈希地址； 再比如关键码为4321，对它平方就是18671041，抽取中间的3位671(或710)作为哈希地址。</p>
<p><strong>平方取中法比较适合：不知道关键码的分布，而位数又不是很大的情况</strong></p>
<p><strong>4. 折叠法</strong></p>
<p>关键码从左到右分割成位数相等的几部分(最后一部分位数可以短些)，然后将这 几部分叠加求和，并按散列表表长，取后几位作为散列地址。</p>
<p><strong>折叠法适合事先不需要知道关键码的分布且位数比较多的情况</strong></p>
<p><strong>5. 随机数法</strong></p>
<p>选择一个随机函数，取关键字的随机函数值为它的哈希地址，即H(key) = random(key),其中 random为随机数函数。</p>
<p><strong>通常应用于关键字长度不等时采用此法</strong></p>
<p><strong>6. 数学分析法</strong></p>
<p>设有n个d位数，每一位可能有r种不同的符号，这r种不同的符号在各位上出现的频率不一定相同，可能在某些位上分布比较均匀，每种符号出现的机会均等，在某些位上分布不均匀，只有某几种符号经常出现。此时可根据散列表的大小，选择其中各种符号分布均匀的若干位作为散列地址。</p>
<p>比如：假设要存储某家公司员工登记表，如果用手机号作为关键字，那么极有可能前7位都是 相同的，那么我们可以选择后面的4位作为散列地址，如果这样的抽取工作还容易出现冲突，还可以对抽取出来的数字进行反转(如1234改成4321)、右环移位(如1234改成4123)、左环移位、前两数与后两数叠加(如1234改成12+34=46)等方法。</p>
<p><strong>数字分析法通常适合处理关键字位数比较大的情况，如果事先知道关键字的分布且关键字的 若干位分布较均匀的情况</strong></p>
<p><strong>……</strong></p>
<p>上述所有方法的共同点是：关键码都是整型才能得到整形哈希地址，但是实际上的关键码key可以是任何类型，以常见的 string 来说，如何在使用上述方法求哈希地址前将其转换成整形，可减小冲突概率呢？<a href="https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html" title="点击查看各种字符串Hash函数对比">点击查看各种字符串Hash函数对比</a></p>
<p>总的来说，在哈希函数的设计上，留给我们的发挥空间更大了，但于此同时，我们要考虑的东西也更多了，对能力的要求也提高了不少，而这恰恰也是我们对自己的要求和学习的动力！</p>
<p><strong>回到正题：</strong><strong>哈希函数设计的越精妙，产生哈希冲突的可能性就越低，但是无法避免哈希冲突。</strong></p>
<p>至于如何解决这个问题，我们接着往下看。</p>
<ol>
 <li>
  <h2>解决哈希冲突的两种常见方法</h2>
 </li>
</ol>
<h3>4.1 闭散列</h3>
<p>也叫<strong>开放定址法</strong> ，<strong>一种将所有数据存储在散列表本身的方法</strong>。</p>
<p>插入元素时，如果发生哈希冲突，并且哈希表未被装满，说明在哈希表中必然还有空位置，那么可以把key存放到冲突位置中的 “下一个” 空位置中去。 （这里暂且假设表的空位置充足）</p>
<p>还是以点1的示例一为例，继续依次插入元素16，9，19；结果如下：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff56fe878b06740fe967e89bc2342fae2.png&amp;size=m" alt=""></p>
<p>这种方法就叫做<strong>线性探测</strong>，下一个探测位置的<strong>Hashi = (current_position + i) % table_size</strong>。【i 是探测的步数，上述示例取为1（常用）】</p>
<p>查询也是如此，但是如果表中没有你要查找的元素时，终止此操作的条件又是什么呢？—— 遇到空位置，表明这个位置从始至终没有元素。</p>
<p>关键是现在还有一个操作：删除。表中某位置进行此操作后，此时这个位置的数据状态应该认为就是空 呢，还是需要一个新的 删除状态呢？如果遵循前者设为空，假设要查询的元素均在表中，第一次查询刚才被删除的元素，发现对应位置为空，终止查询，没有找到，返回结果为失败，这个过程是正确的；但是再一次查询另一个元素时，因为存在哈希冲突，导致此次查询元素的存储位置在已删除元素的位置"后面”(逻辑意义上的，因为线性探测是从冲突位置往后的，如上图示例，可能又从表头开始)，而查询遇到空就终止，因此就会发生漏查的情况。</p>
<p><strong>所以，我们实现时，散列表的每个位置的数据状态至少有三种情况：Empty，Exist，Delete。插入时的"空"包括Empty 和 Delete；查询终止时的"空"则只指Empty；并且有了Empty后，只需修改状态就行，无需改动数据，属于"伪删除”。</strong></p>
<p>线性探测的优点是：实现非常简单。</p>
<p><strong>但缺点也很明显：一旦发生哈希冲突，所有的冲突连在一起，容易产生数据"堆积”，即：不同关键码占据了可利用的空位置，使得寻找某关键码的位置需要许多次比较，导致搜索效率降低。</strong></p>
<p>这与其找下一个探测位置有关系，所以出现 新的探测方式 <strong>“二次探测”，也叫平方探测</strong>，来缓解这个问题；<strong>其下一个探测位置的Hashi = (current_position + c1 * i + c2 * i^2) % table_size</strong><strong>。它的特点是：步长不再是固定的，而是根据冲突的次数而变化；这样，即使两个关键字产生相同的初始散列地址，它们在探测过程中也可能分散到不同的位置。</strong></p>
<p><strong>【</strong>c1和c2是常数参数，它们可以任意选择，但通常会根据实际情况进行调整。常见的选择方式是c1 = c2 = 1，或者c1 = 1，c2 = -1。这些选择可以在一定程度上减少哈希冲突的发生，提高哈希表的性能。然而，具体选择哪个常数值是根据实际问题和性能需求来确定的，不是任意选择的。</p>
<p>i 通常的初始值为0，然后逐步增加至1、2、3，以此类推，直到找到可以插入元素的位置或者遍历完整个哈希表。
 <br>
 <strong>】</strong></p>
<p>除此之外，再介绍一种方法： <strong>双重散列法。其特点为：通过引入第二个哈希函数来降低冲突概率</strong>。</p>
<p><strong>其下一个探测位置的Hashi = (h1(key) + i * h2(key)) % table_size</strong>。【h1和h2分别表示两个不同的哈希函数，i 是探查次数】</p>
<p><strong>具体做法为：</strong>首先使用第一个哈希函数h1将关键码映射到一个位置；如果该位置已经被占用，则使用第二个哈希函数 h2(key) * i 做增量值（通常确保 <code>h2(key)</code> 不等于零，以避免陷入死循环）找下一个探查位置。</p>
<p>举个例子：</p>
<p>假设有一个哈希表 <code>table</code>，大小为 7。我们要插入键 <code>key1 = 5</code>，<code>key2 = 12</code> 和 <code>key3 = 19</code>。</p>
<ol>
 <li>
  <p><strong>哈希函数定义</strong>：</p>
 </li>
 <li>
  <p><code>h1(key) = key % 7</code></p>
 </li>
 <li>
  <p><code>h2(key) = 1 + (key % 6)</code> （注意：确保 <code>h2(key)</code> 不为零）</p>
 </li>
 <li>
  <p><strong>插入 <code>key1 = 5</code></strong>：</p>
 </li>
 <li>
  <p>计算 <code>h1(5) = 5 % 7 = 5</code></p>
 </li>
 <li>
  <p>位置5为空，将 <code>key1</code> 插入位置 5。</p>
 </li>
 <li>
  <p><strong>插入 <code>key2 = 12</code></strong>：</p>
 </li>
 <li>
  <p>计算 <code>h1(12) = 12 % 7 = 5</code></p>
 </li>
 <li>
  <p>计算 <code>h2(12) = 1 + (12 % 6) = 1</code></p>
 </li>
 <li>
  <p>位置 5 已经被占用，因此用步长 1 探查下一个位置 <code>(5 + 1 * 1) % 7 = 6</code>，将 <code>key2</code> 插入位置 6。</p>
 </li>
 <li>
  <p><strong>插入 <code>key3 = 19</code></strong>：</p>
 </li>
 <li>
  <p>计算 <code>h1(19) = 19 % 7 = 5</code></p>
 </li>
 <li>
  <p>计算 <code>h2(19) = 1 + (19 % 6) = 2</code></p>
 </li>
 <li>
  <p>位置 5 被占用，因此用步长 2 探查下一个位置 <code>(5 + 1 * 2) % 7 = 7 % 7 = 0</code>，将 <code>key3</code> 插入位置 0。</p>
 </li>
</ol>
<p>其它的方法，如：再哈希法（Rehashing），伪随机探测法（Pseudo-random Probing）, Hopscotch Hashing法，<strong>Cuckoo Hashing</strong>（布谷鸟哈希），Robin Hood Hashing等等，各有优缺点，适用于不同的场景，留给大家自行探索吧！</p>
<p>至于如何使用闭散列自行实现一张哈希表，先不急，因为有些问题还没解决，小编在后面会给出参考代码，请继续往下看。</p>
<h3>4.2 开散列</h3>
<p>STL中的unordered系列容器就采用这种方法。</p>
<p><strong>不同于闭散列的是：其数据是 使用指针链接到外部存储的，哈希表中存的是指针。所以，也叫链地址法（拉链法），或者哈希桶。</strong></p>
<p><strong>具体做法为：首先对关键码集合用散列函数计算散列地址，具有相同地址的关键码归于同 一 子集合，每一个子集合称为一个桶，各个桶中的元素通过一个单链表链接起来，各链表的头结点存储在哈希表中。</strong></p>
<p>如下图例：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F24c56345f4784eb49c11aa73b0dd55fd.png&amp;size=m" alt=""></p>
<p>这种方法就不会产生4.1中的数据"堆积"问题，每个桶中放的都是发生哈希冲突的元素。</p>
<p>至于增删查等操作，只要找到对应的桶去操作链表就可以，此处不再赘述。</p>
<h3>4.3 散列表的扩容问题</h3>
<p><strong>这个问题的核心是：决定散列表是否要扩容的衡量标准是什么？</strong></p>
<p><strong>—— 负载因子（load_factor） = 实际存的数据量 / 整个表的地址量</strong></p>
<p>一般来说，负载因子越小，冲突的概率就越低，但是空间浪费就会多一些，这就类似于降低密度，减少竞争以提高产量的做法。</p>
<p><strong>对闭散列来说</strong>，<strong>有研究表明：</strong> 当表的长度为质数且表负载因子不超过0.5时，新的表项一定能够插入，而且任何一个位置都不会被探查两次；因此只要表中有一半的空位置，就不会存在表满的问题。此时在搜索时就可以不考虑表装满的情况，但在插入时必须确保表的负载因子a不超过0.5，如果超出必须考虑增容。<strong>此时空间利用率比较低，这是闭散列最大的缺陷，也是哈希的缺陷</strong>。</p>
<p>因此，在有些版本的STL源码中，在扩容时的容量选择上有类似如下代码：</p>
<pre><code class="cpp">size_t GetNextPrime(size_t prime)
{
    const int PRIMECOUNT = 28;
    static const size_t primeList[PRIMECOUNT] =
    {
    //类似两倍关系的素数
    53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
    49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
    1572869ul, 3145739ul, 6291469ul, 12582917ul,
   25165843ul,50331653ul, 100663319ul, 201326611ul, 402653189ul,
   805306457ul, 1610612741ul, 3221225473ul, 4294967291ul
    };
    size_t i = 0;
    for (; i &lt; PRIMECOUNT; ++i)
    {
        if (primeList[i] &gt; prime)
            return primeList[i];
    }
    return primeList[i];
}
</code></pre>
<p><strong>但实际一般情况下，开放定址法的负载因子通常设置在0.7到0.75之间</strong>。这个范围是为了在保证较高的存取效率（如查找、插入和删除操作的时间复杂度）与内存利用率之间取得平衡。</p>
<p>
 <br>
</p>
<p><strong>而对开散列来说</strong> ，桶的个数是一定的，随着元素的不断插入，每个桶中元素的个数不断增多，<strong>极端情况下</strong> ，可能会导致一个桶中链表节点非常多，影响散列表的性能，因此在一定条件下需要对哈希表进行增容，那该条件怎么确认呢？<strong>开散列最好的情况是：每个哈希桶中刚好挂一个节点， 再继续插入元素时，每一次都会发生哈希冲突，因此，在元素个数刚好等于桶的个数时，可以给哈希表增容</strong> 。所以，开散列的负载因子一般在<strong>1及其以上</strong>。</p>
<ol>
 <li>
  <h2>哈希表的实现 并 封装模拟实现unordered系列容器</h2>
 </li>
</ol>
<p>点击 <a href="https://gitee.com/a-clear-meaning/cpp/tree/master/%E9%87%8D%E7%82%B9%E7%9F%A5%E8%AF%86%E6%80%BB%E7%BB%93/STL%E7%9A%84%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0/unordered_set&amp;map" title="my_unordered_set/map">my_unordered_set/map</a> 前往我的Gitee仓库获取参考代码。</p>
<ol>
 <li>
  <h2>哈希的应用</h2>
 </li>
</ol>
<p>本大点主要介绍如何运用哈希思想解决海量数据（几亿个，几十亿个，几百亿个……）的处理处理问题，并由此设计出的一些 结构 或 模板。</p>
<h3>6.1 位图 – bitset</h3>
<p><strong>概念：就是用每一位（二进制bit位）来存放某种状态。</strong></p>
<p>通常是用来判断某个数据存不存在的，比如 1表示存在，0表示不存在。</p>
<p><strong>其底层结构设计采用哈希思想，属于直接映射，即哈希地址就是关键码key，所以决定位图容量大小的是数据本身的大小范围，而不是数据个数，所以没有哈希冲突。</strong></p>
<p>如下图例： <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa6ef6afe3eb34e7fbbd667175df5657e.png&amp;size=m" alt=""></p>
<p>那么如何运用到具体场景中呢？下面是小编总结好的一些示例：</p>
<p><strong>示例1：</strong>给40亿个不重复的无符号整数，没排过序。给一个无符号整数，如何快速判断一个数是否在这40亿个数中。【腾讯面试题】</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6fcbbf9665054a4e92c593ef86d95cdf.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>变形1：</strong> 给定100亿个整数，设计算法找到只出现一次的整数?</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Feff8afb40e6b408e915341e80b08b082.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>变形2：</strong>给两个文件，分别有100亿个整数，我们只有1G内存，如何找到两个文件交集?</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcc6d22200eef4c2fa2d4bcf8ad87c7cb.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><strong>示例2：</strong> 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址？</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcaf4ef16dcbe45d790abe245cb4517e2.png&amp;size=m" alt=""></p>
<p><strong>这种分割方式就叫做 哈希切割，其不同于 平均切割 的地方是：它在切割的同时还有"分类"的作用，即将共性更高（发生哈希冲突）的数据集中到一起，可以说，这种分割方式是有着很强且明确的 目的性 的，更有利于解决复杂问题。</strong></p>
<p>同样的方法，还有下面的变形1:</p>
<p><strong>变形1：</strong>给两个文件，分别有100亿个query(查询，即字符串)，我们只有1G内存，如何找到两个文件交集？</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7d06a3aa550f4829ae68d1c504fbd31b.png&amp;size=m" alt=""></p>
<p>相信这几个例子会加深你对哈希的自我理解！</p>
<p>接下来，就是简单模拟实现STL中的类模板bitset：</p>
<p>参考代码：</p>
<pre><code class="cpp">template&lt;size_t N&gt;
    class bitset
    {
    public:
        bitset()
        {
            _bits.resize(N / 32 + 1, 0);
        }

        //三个核心操作

        // 把pos位置映射的位标记成1
        bitset&amp; set(size_t pos)
        {
            assert(pos &lt;= N);

            size_t i = pos / 32;
            size_t j = pos % 32;

            _bits[i] |= (1 &lt;&lt; j);
            return *this;
        }

        // 把pos位置映射的位标记成0
        bitset&amp; reset(size_t pos)
        {
            assert(pos &lt;= N);

            size_t i = pos / 32;
            size_t j = pos % 32;

            _bits[i] &amp;= ~(1 &lt;&lt; j);
            return *this;
        }

        bool test(size_t pos) const
        {
            assert(pos &lt;= N);

            size_t i = pos / 32;
            size_t j = pos % 32;

            return _bits[i] &amp; (1 &lt;&lt; j);
        }

    private:
        vector&lt;int&gt; _bits;
    };
</code></pre>
<p>至此，小结一下位图的几种常用场景：</p>
<p><strong>1.</strong> 快速查找某个数据是否在一个集合中</p>
<p><strong>2.</strong> 排序 + 去重</p>
<p><strong>3.</strong>求两个集合的交集、并集等</p>
<p><strong>4.</strong> 操作系统中磁盘块标记</p>
<h3>6.2 布隆过滤器 – Bloom Filter</h3>
<p>设想现在有这么个场景： 字符串 “baidu” 在 位图 中 set()，通过<strong>某哈希函数</strong>转换后的映射位置是 pos1；此时查询字符串 “jingdong”（从未set() 过） 是否存在，发现映射位置也是pos1，如果 “tengxun” 也映射到pos1，…… ，就会返回错误的结果，进而可能影响后序操作。</p>
<p>前面我们说过，这种情况叫 哈希冲突，而引起 此 的一个重要原因是：哈希函数设计不够合理。那么是不是说只要重新设计哈希函数就能解决这个问题呢？理论上确实如此，<strong>但是实际情况是：你的 数学分析能力 真的能找到把 海量数据中的每一个不确定性 都变成 相对唯一性 的一种方法吗？</strong></p>
<p>想必你已经在摇头了，这着实也不是个 “经济划算” 的事。</p>
<p><strong>【</strong>那么，不如转换一下思路，虽然我们在哈希函数 的 <strong>“质”</strong> 上能力有限，但在<strong>“量”</strong> 上，可以使用多个哈希函数，即每个元素映射多个位置。查询某个元素时，只要其中任意一个映射位置状态为空，就可以确定这个元素<strong>一定不存在</strong> ；如果每个映射位置状态都为非空，那<strong>大概率这个元素存在</strong> ，<strong>较小概率不存在</strong> ，因为映射位置可能产生的<strong>冲突是无法避免</strong> 的，如下图例。<strong>】</strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F77872de11e2142e68042755e8a7dc55a.png&amp;size=m" alt=""></p>
<p>此时查询 “baidu” , 就只能说其可能存在。</p>
<p><strong>但相较于 位图 的单个映射，误判的概率大大降低了！</strong></p>
<p>这便是由布隆（Burton Howard Bloom）在1970年提出的 一种紧凑型的、比较巧妙的<strong>概率型数据结构</strong>，<strong>可以用来告诉你 “某样东西一定不存在或者可能存在”。</strong></p>
<p>比如，下面的这个模型：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F06f6b8cd062440b1900c548accbe9a1e.png&amp;size=m" alt=""></p>
<p>资讯，广告，视频推荐也是相似的道理。</p>
<p>
 <br>
</p>
<p><strong>那么，如何选择合适的 哈希函数的个数和布隆过滤器的长度，将误报率尽可能的降低呢？</strong></p>
<p>如下统计：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fffcce7656da34fbfa211bc400d1f8086.png&amp;size=m" alt=""></p>
<p>计算m和K的公式：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe9d7751e6e8646fca08428b9a292adef.png&amp;size=m" alt=""></p>
<p><strong>下一个问题：可以删除吗？</strong></p>
<p>传统的布隆过滤器并不支持删除操作，因为存在哈希冲突导致相互影响，如上示例图（“baidu”, “tengxun”），但如果把每个位置改成多位的 引用计数 就可以支持 ，但也可能产生新的问题，如"计数回绕”；可参考文章 <a href="https://cloud.tencent.com/developer/article/1136056" title="Counting Bloom Filter 的原理和实现">Counting Bloom Filter 的原理和实现</a></p>
<p>下面给一段传统布隆过滤器的简单实现和测试 参考代码：</p>
<pre><code class="cpp">template&lt;size_t N,
    class K = string,//常用的数据类型：字符串
    class Hash1 = HashFuncBKDR,//前面给过的：字符串哈希函数
    class Hash2 = HashFuncAP,
    class Hash3 = HashFuncDJB&gt;
class BloomFilter
{
public:
    BloomFilter&amp; set(const K&amp; key)
    {
        size_t hash1 = Hash1()(key) % M;
        size_t hash2 = Hash2()(key) % M;
        size_t hash3 = Hash3()(key) % M;

        _bs-&gt;set(hash1);
        _bs-&gt;set(hash2);
        _bs-&gt;set(hash3);
        return *this;
    }


    bool test(const K&amp; key)
    {
        size_t hash1 = Hash1()(key) % M;
        if (_bs-&gt;test(hash1) == false)
            return false;

        size_t hash2 = Hash2()(key) % M;
        if (_bs-&gt;test(hash2) == false)
            return false;

        size_t hash3 = Hash3()(key) % M;
        if (_bs-&gt;test(hash3) == false)
            return false;

        return true; // 存在误判(有可能3个位都冲突)
    }

private:
    static const size_t M = 10 * N;
    std::bitset&lt;M&gt;* _bs = new std::bitset&lt;M&gt;;//避免数据量过大时，有些编译器实现的std::bitset&lt;N&gt; 开的是静态数组，有栈溢出的风险
};

//测试：一般来说，同一个K值，如果空间加大，映射位置变多，理论上会降低误判的概率
#include&lt;time.h&gt;
void TestBloomFilter()
{
    srand(time(0));
    const size_t N = 1000000;
    BloomFilter&lt;N&gt; bf;

    std::vector&lt;std::string&gt; v1;
    std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
    std::string url1 = "你好";

    for (size_t i = 0; i &lt; N; ++i)
    {
        v1.push_back(url + std::to_string(i));
    }

    for (auto&amp; str : v1)
    {
        bf.set(str);
    }

    // v2跟v1是相似字符串集（前缀一样），但是后缀不一样
    std::vector&lt;std::string&gt; v2;
    for (size_t i = 0; i &lt; N; ++i)
    {
        std::string urlstr = url;
        urlstr += std::to_string(9999999 + i);
        v2.push_back(urlstr);
    }

    size_t n2 = 0;
    for (auto&amp; str : v2)
    {
        if (bf.test(str)) // 误判
        {
            ++n2;
        }
    }
    cout &lt;&lt; "相似字符串误判率:" &lt;&lt; (double)n2 / (double)N &lt;&lt; endl;

    // 不相似字符串集  前缀后缀都不一样
    std::vector&lt;std::string&gt; v3;
    for (size_t i = 0; i &lt; N; ++i)
    {
        string urlstr = std::to_string(i + rand()) + url1;
        urlstr += std::to_string(i + rand());
        v3.push_back(urlstr);
    }

    size_t n3 = 0;
    for (auto&amp; str : v3)
    {
        if (bf.test(str))
        {
            ++n3;
        }
    }
    cout &lt;&lt; "不相似字符串误判率:" &lt;&lt; (double)n3 / (double)N &lt;&lt; endl;
}
</code></pre>
<p>本文至此就结束了，如果对你有所帮助，就是对小编最大的鼓励，可以的话 <strong>三连并留下你的评论</strong>也是小编坚持创作的不懈动力，持续更新中！</p>
<p>原文链接: https://blog.csdn.net/m0_74171054/article/details/141782642</p>]]></description><guid isPermaLink="false">/archives/b42ddcf4-b4c8-4245-b98b-9d07d3835dac</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:42:05 GMT</pubDate></item><item><title><![CDATA[AES加密解密算法]]></title><link>https://wiki.dgfygk.com/archives/34e2160d-c3cd-4ef8-9f6c-983e0b77248f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AES%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86%E7%AE%97%E6%B3%95&amp;url=/archives/34e2160d-c3cd-4ef8-9f6c-983e0b77248f" width="1" height="1" alt="" style="opacity:0;">
<h2>一，AES算法概述</h2>
<p>AES属于分组加密，算法明文长度<strong>固定为128位</strong>（单位是比特bit，1bit就是1位，128位等于16字节）</p>
<p>而密钥长度可以是128、192、256位</p>
<p>当密钥为128位时，需要循环10轮完成加密，在此基础上，密钥每增加64位，就要多循环2轮（即192位密钥循环12轮，256位密钥循环14轮）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd348a7c84ae08bc0591b905d2654217b.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<hr>
<h2>二，加密过程</h2>
<h3>（零）加密过程总览</h3>
<p>以128位密钥为例子，AES算法会对明文先进行【<strong>初始变换</strong> 】（这里的初始变换不属于10轮加密的范畴内），然后是9轮【<strong>循环运算</strong> 】，加1轮【<strong>最终轮</strong>】，最终得到密文</p>
<p>这里的最终轮其实唯一的区别就是最终轮少了9轮主循环中第3步的列混合</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb0113f179ec8da92005f88241ea65d1b.png&amp;size=m" alt=""></p>
<h3>（一）初始变换</h3>
<p>首先要理解，无论是<strong>明文</strong> 还是<strong>密钥</strong>，16个字节（即128位）的字符，在进行加密的过程中都是以4*4的矩阵形式存在的，这个矩阵也有要求——输入的字符需要按从上到下从左往右的顺序排列成矩阵</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcc85090c9a9b527e6e29af736add76cc.png&amp;size=m" alt=""></p>
<p>接受了这个概念后，我们来看AES的初始变换操作，其实十分简单，就是对明文矩阵和密钥矩阵对应的元素异或得到新矩阵，如下图</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8a1b6b98185d3e0bf86d299e8937c787.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h3>（二）9轮循环运算</h3>
<h4>（1）字节代换</h4>
<p>对于完成第一步初始变换的矩阵进行字节代换</p>
<p>我们要将矩阵中的每一个元素都替换掉，但替换是有规则的替换，即S-BOX，按照矩阵元素的值去映射S-BOX中对应的值</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F97dff33e264c731f7743bfd22f2f38a6.png&amp;size=m" alt=""></p>
<h4>（2）行移位</h4>
<p>对于完成字节替换的矩阵进行行移位</p>
<p>规则是矩阵的第一行不变，第二行向左移动1个字节，第三行向左移动2个字节，第四行向左移动3个字节</p>
<p>注意，这里认为矩阵的最左边连通最右边，即坐标（2，1）的p2左移后出现在坐标（2，4）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe94f4a1c4b85e124d3668a713ce8b206.png&amp;size=m" alt=""></p>
<p>下图是步骤（1）字节替换后的矩阵进行行移位的结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5005777e58260f0fb8d368729db5bc65.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>（3）列混合（该操作在最终轮不执行）</h4>
<p>对于完成行移位的矩阵进行列混合</p>
<p>规则是左乘一个<strong>给定</strong>的4*4矩阵，注意这个矩阵是程序设计时给定的，可能每个程序使用的AES算法中的这个矩阵不一定相同</p>
<p>（注意，这里的矩阵乘法并不是简单的线性代数中的元素相乘相加，本篇不展开讲，详见本篇-&gt;<a href="https://blog.csdn.net/weixin_46395886/article/details/112793345" title="关于AES的列混合计算和解密流程问题_aes列混合运算-CSDN博客">关于AES的列混合计算和解密流程问题_aes列混合运算-CSDN博客</a>）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F35fdb5098ed9814f92e7688e6b71ad89.png&amp;size=m" alt=""></p>
<p>下图是步骤（2）行移位后的矩阵进行列混合的结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F118f0de47c0e72a280f864e07220c04d.png&amp;size=m" alt=""></p>
<h4>（4）轮密钥加</h4>
<p>对于完成列混合的矩阵进行轮密钥加</p>
<p>规则是对完成列混合的矩阵和<strong>轮密钥矩阵</strong>对应的元素进行异或，这里我们先知道要这样操作即可，至于轮密钥矩阵在后面马上就介绍了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3776cf9ea1c307c1f21b5fba8207c9d0.png&amp;size=m" alt=""></p>
<h4>（*）轮密钥矩阵</h4>
<p>最开始只有一个密钥矩阵（假设就是下图这个矩阵），我们对这个矩阵的列进行如下标号</p>
<p>9轮循环运算中，每进行一轮，需要对密钥拓展出如下图的ai列，并使（a1, a2, a3, ai）成为当前轮的轮密钥矩阵</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff05aff7d74eebc21c7e16ad86f8bfcac.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3753bce13f774c12b1a0ad4bf2921642.png&amp;size=m" alt=""></p>
<p>拓展时，使用如下规则——</p>
<ul>
 <li>如果i不是4的倍数，那么第i列由如下等式确定：<strong>a[i]=a[i-4] ^ a[i-1]</strong>，例如i等于5时：a[i]=a[1] ^ a[4]，即第2列和第5列异或（第1轮没有第5列，第2轮才有第5列）</li>
 <li>如果i是4的倍数，那么第i列由如下等式确定：<strong>a[i]=a[i-4]^T(a[i-1])</strong> ，例如i等于4时，a[i]=a[0] ^ T(a[3])，即第1列和<strong>经过T函数计算的第4列</strong>异或</li>
</ul>
<p>函数T由3部分组成：字循环、字节代换和轮常量异或（坚持一下，就要结束了┭┮﹏┭┮）</p>
<p>①字循环</p>
<p>下图是对i等于4的情况下进行字循环操作</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4f3c88cdbf463c0fb93aac44807ac654.png&amp;size=m" alt=""></p>
<p>②字节代换</p>
<p>和9轮循环运算中的第（1）步操作是一样的，将字节映射到进行的S-BOX中进行字节替换</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc78da3685d1adb2e1bf64c19cdca8860.png&amp;size=m" alt=""></p>
<p>③轮常量异或</p>
<p>轮常量是给定的，这里的Rcon就是轮常量矩阵，可能每个程序使用的AES算法中的这个矩阵不一定相同</p>
<p>（注意，下图左，橙色列和黄色列是轮常量异或，最终再异或蓝色列是按照公式<strong>a[i]=a[i-4]^T(a[i-1])</strong>拓展轮密钥矩阵，蓝色列就是公式中的a[i-4]）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F63fe816953223aa2e255c335abe89c55.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<hr>
<h2>（补充），AES加密的不同模式</h2>
<blockquote>
 <p>AES加密算法的模式主要有五种，它们是ECB模式、CBC模式、CFB模式、OFB模式和CTR模式。它们之间的主要区别在于加密过程中明文和密文的处理方式以及安全性特点：</p>
 <ol>
  <li>
   <p><strong>ECB模式</strong>：</p>
  </li>
  <li>
   <p>每个明文块被独立加密，相同的明文块得到相同的密文块。</p>
  </li>
  <li>
   <p>不适合加密大量数据或具有重复模式的数据，容易受到攻击。</p>
  </li>
  <li>
   <p><strong>CBC模式</strong>：</p>
  </li>
  <li>
   <p>使用前一个密文块对当前明文块进行加密，增加了随机性，相同的明文块在不同位置得到不同密文块。</p>
  </li>
  <li>
   <p>需要使用初始化向量（IV）来增加安全性。</p>
  </li>
  <li>
   <p><strong>CFB模式</strong>：</p>
  </li>
  <li>
   <p>使用前一个密文块作为输入生成伪随机密钥流与明文进行异或运算得到密文。</p>
  </li>
  <li>
   <p>可以支持实时加密和流加密。</p>
  </li>
  <li>
   <p><strong>OFB模式</strong>：</p>
  </li>
  <li>
   <p>类似于CFB，使用前一个密文块生成伪随机密钥流，然后与明文进行异或运算。</p>
  </li>
  <li>
   <p>对加密和解密中的错误更加鲁棒，但不支持部分更新。</p>
  </li>
  <li>
   <p><strong>CTR模式</strong>：</p>
  </li>
  <li>
   <p>使用一个计数器与密钥输入加密算法生成伪随机密钥流，与明文进行异或运算。</p>
  </li>
  <li>
   <p>可以并行加密和解密，适用于需要高效加密的场景。</p>
  </li>
 </ol>
 <p>选择哪种模式取决于具体应用的需求和安全性要求，例如需要实时加密或流加密的场景可选用CFB模式，而需要高效加密的场景可选用CTR模式。</p>
</blockquote>
<p>
 <br>
</p>
<hr>
<h2>三，实际运用</h2>
<p>博主最近在钻研逆向技术，在逆向分析过程中会遇到AES加密，因此需要略微了解AES算法的原理（深入了解就是密码学者干的事情了，逆向分析但求能一眼看出是AES加密即可）</p>
<p>如下图，在第31行运用到了AES加密（当然这题属于十分基础的，函数名直接表明是AES加密了）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7def9e203476fe68c6214c07f4404494.png&amp;size=m" alt=""></p>
<p>在函数中此函数中，能够很清晰地看出AES的循环加密过程</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcf9921715038e6e678f8e064f1202dfa.png&amp;size=m" alt=""></p>
<p>知道它是AES加密后，逆向的工作就很清楚了，我们只需要从程序中分析获取密文和密钥，解密即可获得flag，至于解密的具体代码不必关心，可以直接借助python的第三方库解决，会写脚本即可</p>
<p>（这里只做举例，具体问题请具体分析）</p>
<pre><code class="python">from Crypto.Cipher import AES

#注意解密过程中密钥的格式需要为byte类型，字符串转byte直接在开头加个b标志
key = b'1234567890123456' 
#注意解密过程中密文的格式需要为byte类型，本题获取到的是十六进制文本，处理成字符串后使用bytes.fromhex()函数转为对应的字节数组
en_text = bytes.fromhex('F3498AED82CE44E2357C23F5DCF897A43B6A7BFEE0467C591E301CBC38F99913')

aes = AES.new(key, AES.MODE_ECB) #创建一个AES解密对象，需要传入密钥和加密模式（这里是ECB模式）
print(aes.decrypt(en_text)) #调用decrypt方法实现解密
</code></pre>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/liKeQing1027520/article/details/137024097</p>]]></description><guid isPermaLink="false">/archives/34e2160d-c3cd-4ef8-9f6c-983e0b77248f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:41:43 GMT</pubDate></item><item><title><![CDATA[滑动窗口详解]]></title><link>https://wiki.dgfygk.com/archives/f7c055a6-cc13-4013-b7df-764589358393</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E8%AF%A6%E8%A7%A3&amp;url=/archives/f7c055a6-cc13-4013-b7df-764589358393" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F61162011f9964a0bb25033ebf5bb27b7.gif&amp;size=m" alt=""></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2886255420c4429bb4c6fb9fdcc510a4.gif&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2F7f06907c3c4b4c2a8800bace1e511bb8.gif%23pic_center&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>滑动窗口其实也就是之前介绍的同向双指针</p>
<blockquote>
 <p>步骤：</p>
 <ol>
  <li>
   <p>定义 left = 0, right = 0</p>
  </li>
  <li>
   <p>进窗口</p>
  </li>
  <li>
   <p>判断 出窗口 更新结果（更新结果的步骤根据具体题目中的要求进行）</p>
  </li>
 </ol>
</blockquote>
<ol>
 <li>
  <h2>长度最小的子数组</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/minimum-size-subarray-sum/" title="209. 长度最小的子数组">209. 长度最小的子数组</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F4bb8cd2889fc99c110ea00ff65f74f23.png&amp;size=m" alt=""></p>
<p><strong>暴力解法：</strong>枚举出所有的情况，然后判断长度和区间和，这种方法的时间复杂度最优为O(n^2)</p>
<p>优化：利用单调性，使用同向指针优化</p>
<p><strong>思路：定义left = 0,right = 0，right先往右移动，直到区间内的和符合条件，然后left往右移动并判断是否符合条件</strong></p>
<p>当right移动到刚好符合条件的位置时，后面的就没必要进行枚举了，并且right也没有必要再从头再来枚举所有的情况了</p>
<pre><code class="java">class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0, right = 0, sum = 0;
        int min = 100001;
        while (right &lt; nums.length) {
            sum += nums[right];
            while (target &lt;= sum) {
                min = Math.min(min, right - left + 1);
                sum -= nums[left];
                left++;
            }
            right++;
        }
        return (min == 100001) ? 0 : min;
    }
}
</code></pre>
<ol>
 <li>
  <h2>LCR 016. 无重复字符的最长子串</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/wtcaE1/" title="LCR 016. 无重复字符的最长子串">LCR 016. 无重复字符的最长子串</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F8cf634a91fd6575d741292ae341da8c2.png&amp;size=m" alt=""></p>
<p><strong>暴力解法：</strong> 从第一个字符开始，固定每一个起始位置，一直往后枚举到出现重复字符，计算子串长度
 <br>
 再从下一个位置继续往后枚举，最终从枚举到的所有子串中找到最长的
 <br>
 判断重复元素时可以利用哈希表来判断（后面的题经常要用到这个技巧）</p>
<p><strong>思路：</strong> <strong>在暴力解法中可以发现，当遇到第一个重复字符时，只需要把left往后移动，跳过这个字符就可以继续往下枚举了，并且期间跳过的子串肯定是没有第一次枚举的子串长的，</strong></p>
<p><strong>还发现，当left跳过重复字符之后，right可以在原来的位置继续往后移动，不用再回过头来重新枚举字符串，这些子串的长度肯定也还是没有原来的长的，这样就又可以使用同向指针构成滑动窗口来解决这道题了</strong></p>
<pre><code class="java">class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] hash = new int[128];
        char[] chars = s.toCharArray();
        int max = 0;
        int left = 0, right = 0;
        while (right &lt; s.length()) {
            //进窗口
            hash[chars[right]]++;
            //判断出窗口
            while (hash[chars[right]] &gt; 1) {
                hash[chars[left]]--;
                left++;
            }
            //更新结果
            max = Math.max(max, right - left + 1);
            right++;
        }
        return max;
    }
}
</code></pre>
<ol>
 <li>
  <h2>最大连续1的个数 III</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/max-consecutive-ones-iii/" title="1004. 最大连续1的个数 III">1004. 最大连续1的个数 III</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fef3f1f058bf619a85240707206d6a258.png&amp;size=m" alt=""></p>
<p><strong>翻转0的操作可以转化为查找一个区间最多有k个0，如果这个区间的0的个数在k个以内，那肯定就都可以翻转</strong></p>
<p>暴力解法：还是通过两层for循环，依次枚举所有的可能，比较每种可能的长度</p>
<p>优化：通过双指针left,right，依次先确定left的位置，right向右移动，区间内有k个0之后，再把left右移，确定下一个区间，这时可以发现，如果left的位置不是0的话，再枚举k个0的区间肯定没有第一次枚举的区间长</p>
<blockquote>
 <p><strong>第一个优化：left右移确定下一个起始位置时，right不用再回来</strong></p>
 <p><strong>第二个优化：可以直接把left移动到下一个0的位置，此时right就可以往后移动</strong></p>
</blockquote>
<pre><code class="java">class Solution {
    public int longestOnes(int[] nums, int k) {
        int flag = 0, ans = 0;//flag为0的个数
        for (int left = 0, right = 0; right &lt; nums.length; right++) {
            //进窗口
            if (nums[right] == 0) {
                flag++;
            }
            //判断出窗口
            while (flag &gt; k) {
                if (nums[left++] == 0) {
                    flag--;
                }
            }
            ans = Math.max(right - left + 1, ans);
        }
        return ans;
    }
}
</code></pre>
<ol>
 <li>
  <h2>将 x 减到 0 的最小操作数</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/" title="1658. 将 x 减到 0 的最小操作数">1658. 将 x 减到 0 的最小操作数</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa9e3f49eeb1ed2c747d06baccff7398b.png&amp;size=m" alt=""></p>
<p>如果按照题目中的思路直接去考虑两端的数据就不太好想，所以换一种方式，考虑剩余的数据，左右两端的数据加起来等于x，那么中间的数据就等于nums数组和 - x ，也就是找出一段子串和等于 sum - x，再去比较出长度最长的子串，所以就转化为了使用滑动窗口求满足一定条件长度最长的子串的问题了</p>
<pre><code class="java">class Solution {
    public int minOperations(int[] nums, int x) {
        int sum = 0;
        int ans = -1;
        for (int a : nums)
            sum += a;
        if (sum &lt; x)
            return -1;
        sum -= x;
        for (int left = 0, right = 0, tmp = 0; right &lt; nums.length; right++) {
            tmp += nums[right];
            while (tmp &gt; sum) {
                tmp -= nums[left++];
            }
            if (tmp == sum)
                ans = Math.max(right - left + 1, ans);
        }
        return (ans == -1) ? -1 : nums.length - ans;
    }
}
</code></pre>
<ol>
 <li>
  <h2>水果成篮</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/fruit-into-baskets/" title="904. 水果成篮">904. 水果成篮</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fcb0bd07f26d2ad659a6acb6480d14b4b.png&amp;size=m" alt=""></p>
<p>这道题就是求满足一定条件的最长的子数组的问题</p>
<p>思路：使用哈希表来存储元素出现的次数，以此判断存储元素的种类，然后利用滑动窗口更新答案值</p>
<p>优化：如果使用HashMap的话，虽然说结果好存储，但是运行效率不高，所以采用数组模拟哈希表的方法，定义一个kind变量，来统计当前篮子中的水果数量</p>
<pre><code class="java">class Solution {
    public int totalFruit(int[] fruits) {
        int ans = 0;
        int[] hash = new int[fruits.length + 5];
        for (int left = 0, right = 0, kind = 0; right &lt; fruits.length; right++) {
            if (hash[fruits[right]] == 0) {
                kind++;
            }
            hash[fruits[right]]++;
            while (kind &gt; 2) {
                hash[fruits[left]]--;
                if (hash[fruits[left]] == 0) {
                    kind--;
                }
                left++;
            }
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}
</code></pre>
<ol>
 <li>
  <h2>找到字符串中所有字母异位词</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/find-all-anagrams-in-a-string/" title="438. 找到字符串中所有字母异位词">438. 找到字符串中所有字母异位词</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fd6755075ed37d7149f7183004cdbbf6c.png&amp;size=m" alt=""></p>
<p>暴力解法：创建两个哈希表，然后把字符串p里面的每个字符存里面，接着遍历枚举所有s里面的和字符串p长度相等的子串，再把子串也存到哈希表中，对比两个哈希表中的值是否相等</p>
<p>滑动窗口优化：在枚举所有组合的过程中发现，因为需要维护子串的长度，所以如果right向右移动，left也要向右</p>
<p>移动，并且，存放到哈希表中的数据只是受到了left和right两个位置的影响，所以就可以使用滑动窗口进行枚举</p>
<p>判断条件优化：由于这道题只有26个字符，所以每次都遍历哈希表进行判断也可以，但是还可以进行优化</p>
<p><strong>可以定义一个计数器cnt来统计枚举出来的组合的有效元素，在遍历的过程中，如果hashs中的数据大于hashp里的就代表出现了重复的元素，此时cnt就不用增加，在出窗口的过程中，如果hashs中的数据小于hashp里的，就表示此时滑出窗口的元素是一个有效值，cnt就需要减一，最后判断有效数据的个数是否等于字符串p的长度来更新结果</strong></p>
<pre><code class="java">class Solution {
    public List&lt;Integer&gt; findAnagrams(String s, String p) {
        //存储结果
        List&lt;Integer&gt; ans = new ArrayList&lt;&gt;();
        int[] hashP = new int[26];
        char[] pp = p.toCharArray();
        //把字符串p存放到哈希表中
        for (char c : pp) {
            hashP[c - 'a']++;
        }
        int[] hashS = new int[26];
        char[] ss = s.toCharArray();
        for (int left = 0, right = 0, cnt = 0; right &lt; ss.length; right++) {
            //进窗口 + 判断有效字符个数cnt
            if (++hashS[ss[right] - 'a'] &lt;= hashP[ss[right] - 'a']) {
                cnt++;
            }
            //判断+出窗口
            if (right - left + 1 &gt; pp.length) {
                if (hashS[ss[left] - 'a']-- &lt;= hashP[ss[left] - 'a']) {
                    cnt--;
                }
                left++;
            }
            //有效字符相等，更新结果
            if (cnt == p.length()) {
                ans.add(left);
            }
        }
        return ans;
    }
}
</code></pre>
<ol>
 <li>
  <h2>串联所有单词的子串</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/substring-with-concatenation-of-all-words/" title="30. 串联所有单词的子串">30. 串联所有单词的子串</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F7b1cd19200c2058efed74ac18ca6bd60.png&amp;size=m" alt=""></p>
<p>这道题其实和找出所有字母异位词特别像，只不过这道题把字母换成了字符串而已，那么就不能再使用普通的数组模拟哈希表来存储了，需要使用到容器来存储每一个字符串和出现的次数，然后就是一些细节问题的处理</p>
<blockquote>
 <ol>
  <li>
   <p><strong>由于是字符串，所以需要进行多次的滑动窗口，具体的次数根据给出的字符串数组中的字符串长度来看</strong></p>
  </li>
  <li>
   <p><strong>注意，每一次使用滑动窗口都要重新创建一个哈希表</strong></p>
  </li>
  <li>
   <p><strong>在更新cnt的时候，要注意此时最开始的字符串可能不在hash1中，直接调get方法会空指针异常</strong></p>
  </li>
 </ol>
</blockquote>
<pre><code class="java">class Solution {
    public List&lt;Integer&gt; findSubstring(String s, String[] words) {
        List&lt;Integer&gt; ans = new ArrayList&lt;&gt;();
        //把words放进哈希表中
        Map&lt;String, Integer&gt; hash1 = new HashMap&lt;&gt;();
        for (String word : words) {
            hash1.put(word, hash1.getOrDefault(word, 0) + 1);
        }
        int len = words[0].length();
        //外层表示执行滑动窗口的次数
        for (int i = 0; i &lt; len; i++) {
            //每次都要创建新的哈希表
            Map&lt;String, Integer&gt; hash2 = new HashMap&lt;&gt;();
            for (int left = i, right = i, cnt = 0; right + len &lt;= s.length(); right += len) {
                //截取字符串，添加到哈希表中
                String str = s.substring(right, right + len);
                hash2.put(str, hash2.getOrDefault(str, 0) + 1);
                //hash1中也使用getOrDefault判断，可以避免空指针
                if (hash2.get(str) &lt;= hash1.getOrDefault(str, 0)) {
                    cnt++;
                }
                if (right - left + 1 &gt; words.length * len) {
                    String strl = s.substring(left, left + len);
                    if (hash2.get(strl) &lt;= hash1.getOrDefault(strl, 0)) {
                        cnt--;
                    }
                    hash2.put(strl, hash2.get(strl) - 1);
                    left += len;
                }
                if (cnt == words.length) {
                    ans.add(left);
                }
            }
        }
        return ans;
    }
}
</code></pre>
<ol>
 <li>
  <h2>最小覆盖子串</h2>
 </li>
</ol>
<p><a href="https://leetcode.cn/problems/minimum-window-substring/" title="76. 最小覆盖子串">76. 最小覆盖子串</a></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa7cea3ba5f140039b22a42313c408d83.png&amp;size=m" alt=""></p>
<p>暴力解法和上面的几题都很相似，直接来看使用滑动窗口优化之后的版本</p>
<p>思路：使用同向双指针维护一个窗口，使窗口内的子串涵盖字符串 t 所有字符，然后让left指针右移，此时会出现两种情况，一种是第一个和字符串t中字符相等的有很多，就算一个字符出窗口之后剩余的字符还会符合条件，另一种就是不符合条件的情况，需要right指针右移</p>
<p><strong>判断出窗口的情况是窗口内的字符刚好符合题目要求，才开始出窗口，因为需要找到最小子串，那么更新结果也是在判断刚好符合条件之后就要更新结果了</strong></p>
<p><strong>优化：使用变量cnt来标记有效字符的种类，无论是进窗口还是出窗口，更新cnt时都需要保证两个哈希表中该该字符出现的种类数是相等的，否则cnt就会重复计数，最终判断有效字符个数cnt是否和hash1的元素个数相等就可以更新结果了</strong></p>
<pre><code class="java">class Solution {
    public String minWindow(String s, String t) {
        int min = 100000, begin = -1;
        char[] chars = s.toCharArray();
        char[] chart = t.toCharArray();
        int[] hash1 = new int[128];
        int count = 0;
        for (char value : chart) {
            if (hash1[value] == 0) {
                count++;
            }
            hash1[value]++;
        }
        int[] hash2 = new int[128];
        //cnt : 有效字符的种类
        for (int left = 0, right = 0, cnt = 0; right &lt; chars.length; right++) {
            //进窗口
            hash2[chars[right]]++;
            if (hash1[chars[right]] == hash2[chars[right]]) {
                cnt++;
            }
            //判断
            while (cnt == count) {
                //更新结果
                if (right - left + 1 &lt; min) {
                    begin = left;
                    min = right - left + 1;
                }
                //出窗口
                if (hash2[chars[left]] == hash1[chars[left]]) {
                    cnt--;
                }
                hash2[chars[left]]--;
                left++;
            }
        }
        if (begin == -1)
            return new String();
        else
            return s.substring(begin, begin + min);
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2F7f06907c3c4b4c2a8800bace1e511bb8.gif%23pic_center&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/2202_76097976/article/details/141811969</p>]]></description><guid isPermaLink="false">/archives/f7c055a6-cc13-4013-b7df-764589358393</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:41:30 GMT</pubDate></item><item><title><![CDATA[数据结构与算法——DFS(深度优先搜索)]]></title><link>https://wiki.dgfygk.com/archives/f4c3842b-6d8b-4588-b6b2-4d59cae12203</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E2%80%94%E2%80%94DFS%28%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2%29&amp;url=/archives/f4c3842b-6d8b-4588-b6b2-4d59cae12203" width="1" height="1" alt="" style="opacity:0;">
<h3>算法介绍：</h3>
<p>深度优先搜索（Depth-First Search，简称DFS）是一种用于<strong>遍历或搜索树或图</strong>的算法。这种算法会尽可能深地搜索图的分支，直到找到目标节点或达到叶节点（没有子节点的节点），然后回溯到上一个分支继续搜索。DFS可以用于许多问题，比如路径寻找、连通性验证、拓扑排序等。</p>
<p>在ACM、蓝桥杯等著名竞赛中DFS算法是比较重要的，特别是在蓝桥杯中每一年几乎都要考<strong>DFS/BFS算法</strong> 。DFS算法在OI赛中用处非常大，可以通过DFS/BFS暴力的方式可以拿到部分分数，蓝桥杯一般可以拿到20%的分数，有的甚至高达50%，是<strong>暴力得分</strong>的不二之选。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5296c4391f6643d8a787b51ecd4543d3.gif&amp;size=m" alt=""></p>
<h3>基本步骤：</h3>
<p>DFS通常使用递归或栈来实现。以下是DFS的基本步骤：</p>
<ol>
 <li><strong>选择起始点</strong>：选择图中的一个点作为起始点。</li>
 <li><strong>访问节点</strong>：标记起始节点为已访问，并将该节点加入递归或栈中。</li>
 <li><strong>探索邻接节点</strong>：从该点周围取出一个点，检查它的所有未访问的邻接节点。</li>
 <li><strong>递归或迭代</strong>：对每个未访问的邻接节点，将其标记为已访问，然后将其推入递归或栈中。</li>
 <li><strong>回溯</strong>：当当前节点的所有邻接节点都被访问后，递归中回溯/从栈中弹出该节点，继续搜索上一个点的其他分支。</li>
 <li><strong>结束条件</strong>：当栈为空或找到目标节点时，搜索结束。</li>
</ol>
<hr>
<h3>图解算法：</h3>
<p>下面放一张我们学校ACM在大一培训时使用的一张动态BFS/DFS步骤图。注：红色遍历为BFS、黄色遍历为DFS。（绿色为起点，紫色为终点，黑色为障碍物）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F55925d33124642a0ba804342f400c497.gif&amp;size=m" alt=""></p>
<p>由上图中我们可以看出，DFS的遍历为一条路，用我们学长的话说就是一条路走到头，找不到就再回头换另一条路，继续搜索，直到找到终点。那么BFS就是在一个点的周围每一个点都走一遍试一下，属于扩散型的。找不到的话在这个点的基础上再去扩散四周寻找。<strong>BFS一般通过栈来实现，DFS一般通过递归来实现。</strong></p>
<p>下面我们将以3*3的网格，只考虑上下左右四个方向，上左、下左等四个方向不考虑，递归顺序为{上、下、左、右}，顺序可按照自己的想法，这里以上下左右顺序为例，给大家模拟实现一下过程。</p>
<h5>第一步：</h5>
<p>本身就在起点，先把此时起点标记为已经走过了，即vis[起点]=true，告诉后面这个点不能再搜索了，不标记的话可能会陷入死循环。在寻找起点的上下左右格子，我们发现，{上}、{左}方向都已经超出了格子范围，在题中就是超出了下标范围，那么我们能考虑的就只有{下}、{右}方向，由于我们递归的顺序为{上、下、左、右}，{上}方向超出了格子范围，那么级别最高且合法的就是{下}方向了，从起点向下走。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0b8c027b40ee46aeac0a9f3a866f94e1.png&amp;size=m" alt=""></p>
<h5>第二步：</h5>
<p>先标记，vis[第一步]=true。此时我们发现{左}方向已经不在网格里面了，下标已经越界了，所以不考虑{左}方向，此时递归顺序为{上、下、右}方向，向上走，在第一步一开始我们就把vis[起点]标记为true，说明已经走过了，下面不允许走了，{上}方向也不能走了。此时优先级最高的为下方向了，下方向没有被标记过，所以可以走。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4f553f4791384e5bb79926cbd80947e4.png&amp;size=m" alt=""></p>
<h5>第三步：</h5>
<p>还是先标记，把当前位置所走的标记为已经走过，即vis[第二步]=true。此时我们发现{左、下}两个方向已经越界了，所以不考虑，此时递归优先级为{上、右}，由于第三步刚开始就把vis[第二步]=true，已经标记了，不能走了，所以这一步只能向右走。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2d2630457efa469bb758b8d7d235965f.png&amp;size=m" alt=""></p>
<h5>第四步：</h5>
<p>还还还是先标记，vis[第三步]=true。此时我们发现{下}方向已经越界了，所以不考虑{下}方向了，在这一步刚开始我们把{左}方向这个点已经给标记过了，所以不能走，那么剩余的合法递归顺序为{上、右}，此时我们便可以向上走，找到终点，完成此次DFS(深度优先搜索)。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F10e3cbcdd4ab403fb586c12ee69575dc.png&amp;size=m" alt=""></p>
<hr>
<h3>算法模板：</h3>
<p>从上面的图解算法中步骤我们把<strong>算法步骤</strong>归结为一下：</p>
<p>首先我们需要一个数组即输入的数据数组，可能为int、也肯为char，在绝大部分题目中都需要一个标记数组，即bool类型的vis数组。然后在题目中找到搜索的起点跟终点，设置dfs函数的参数，根据题目的不同参数个数不同。在主函数dfs函数之前<strong>先把vis[起点]标记为true</strong> ，一定不要忘记，否则就是死循环。确定了起点，把参数传给dfs函数参数，进入dfs递归函数，确定函数的终止条件，即当前状态==目标状态或者递归值==某个数等，<strong>终止条件一定在函数的最前面</strong> ，否则会影响答案。再次，在当前点，寻找当前点的周围（根据题目，可能四个点，也可能八个点等），用for循环遍历每一个点，如果这个点已经被标记过了或者数组下标越界都是不合法的，直接continue掉。那么剩下的就是合法的，可以访问下一个点，先把下一个点给标记完再去递归下一个点，再进入dfs函数递归，注意大部分dfs都需要回溯的,即把vis[当前点]=false,<strong>为什么需要回溯</strong>，这就是我们所说的一条路走不通，回头换一条路走，那么我要回头，前面走过的已经被标记的，还咋走，那就要vis[当前点]=false解除标记。下面贴一个dfs函数的一般模板。</p>
<pre><code class="cpp">void dfs(int dep){//dep为当前状态
    if(dep==x){//当前状态==目标状态，终止条件
        更新结果
        return;
    }
    for(int i=0;i&lt;n;i++){//遍历每一种可能
        if(不符合条件){
            continue;//跳过
        }
        //符合条件
        vis[访问的点]=true;//先标记
        dfs(下一个状态);
        vis[访问的点]=false;//恢复现场,大部分需要回溯,有的不需要这一步
    }
}
</code></pre>
<hr>
<h3>算法例题：</h3>
<p>现在各大算法刷题网站上dfs题目非常的多，dfs题目的变化也比较多，现在各种各样的题目层出不穷，博主所做过的题印象中大部分在终止条件哪里变化的比较多，像是还有在条件判断上增加附加条件等等，下面博主选取几个比较具有代表性的给大家讲解一下，加深理解一下dfs算法。</p>
<hr>
<h4>一、马走日</h4>
<blockquote>
 <h6>题目描述：</h6>
 <p>马在中国象棋以日字形规则移动。请编写一段程序，给定n×m大小的棋盘，以及马的初始位置(x，y)，要求不能重复经过棋盘上的同一个点，计算马可以有多少途径遍历棋盘上的所有点。
  <br>
  <strong>输入</strong>
  <br>
  第一行为整数T(T &lt; 10)，表示测试数据组数。
  <br>
  每一组测试数据包含一行，为四个整数，分别为棋盘的大小以及初始位置坐标n,m,x,y。(0≤x≤n-1,0≤y≤m-1, m &lt; 10, n &lt; 10)。
  <br>
  <strong>输出</strong>
  <br>
  每组测试数据包含一行，为一个整数，表示马能遍历棋盘的途径总数，0为无法遍历一次。
  <br>
  <strong>输入示例</strong>
  <br>
  1
  <br>
  5 4 0 0
  <br>
  <strong>输出示例</strong>
  <br>
  32</p>
</blockquote>
<h5>解题思路：</h5>
<p>这个题不同在了马走的方向跟之前走四个方向的不同他是斜着跳，与象棋上的马一样，例如（1，2）、（-2，1）等，初始状态就是起点（x,y），终点的话，除了起点每个点都可能是终点，这样不好确定终点，但是我们有一个条件，遍历完所有点，有两种方法可以确定遍历完所有点，第一种是n*m的棋盘，那么如果走完了n*m步就完成了一次，第二种设置一个标记数组，标记棋盘上每一个点都被标记完了就完成一次，很明显第一种方法比第二种好。由于统计的是途径总数，每完成一次计数就++,直到他无路可走，自动退出函数。</p>
<h5>AC代码：</h5>
<pre><code class="cpp">#include&lt;iostream&gt;
using namespace std;
int a[15][15],vis[15][15];
int n,m,sx,sy;
int ans;//途径数
int dx[]={1,1,-1,-1,2,2,-2,-2};//马在棋盘上可以跳八个方向
int dy[]={2,-2,2,-2,1,-1,1,-1};
void dfs(int x,int y,int dep){//（x,y）当前坐标，dep当前步数
    if(dep==n*m){//终止条件
        ans++;
        return;
    }
    for(int i=0;i&lt;8;i++){//八个方向遍历
        int bx=x+dx[i];
        int by=y+dy[i];
        if(vis[bx][by]==1){//已经被标记过了
            continue;
        }
        if(bx&lt;0||bx&gt;=n||by&lt;0||by&gt;=m){//下标越界
            continue;
        }
        vis[bx][by]=1;//先标记该点
        dfs(bx,by,dep+1);//再去递归下一次
        vis[bx][by]=0;//回溯--解标记
    }
}
int main(){
    int t;
    cin&gt;&gt;t;
    while(t--){
        ans=0;
        scanf("%d%d%d%d",&amp;n,&amp;m,&amp;sx,&amp;sy);
        vis[sx][sy]=1;//起点标记
        dfs(sx,sy,1);
        printf("%d",ans);
    }

    return 0;
}
</code></pre>
<hr>
<h4><a href="https://www.acwing.com/problem/content/3431/" title="二、AcWing&nbsp;3428. 放苹果">二、AcWing 3428. 放苹果</a></h4>
<blockquote>
 <p>把 M个同样的苹果放在 N个同样的盘子里，允许有的盘子空着不放，问共有多少种不同的分法？</p>
 <p>盘子相对顺序不同，例如 5，1，1和 1，5，1算作同一种分法。</p>
 <p><strong>输入格式</strong></p>
 <p>输入包含多组测试数据。</p>
 <p>每组数据占一行，包含两个整数 M 和 N。</p>
 <p><strong>输出格式</strong></p>
 <p>每组数据，输出一行一个结果表示分法数量。</p>
 <p><strong>数据范围</strong></p>
 <p>1≤M,N≤10;</p>
 <p><strong>输入样例：</strong></p>
 <pre><code>7 3
</code></pre>
 <p><strong>输出样例：</strong></p>
 <pre><code>8
</code></pre>
</blockquote>
<h6>解题思路：</h6>
<p>N与M最大只有10，dfs可以适用，M个苹果放到N个盘子里，允许盘子空着不放，可以直接dfs递归实现，我们设置两个参数，一个遍历的盘子数dep,一个遍历的苹果数sum,每一次向盘子里面放t个苹果，那么苹果数sum-t，盘子数sum+1，当超出了盘子数不符合条件或者苹果数&lt;0则返回上一步执行。</p>
<h6>AC代码：</h6>
<pre><code class="cpp">#include&lt;iostream&gt;
using namespace std;

int a[101]={1};//初始化为1放苹果的至少放一个
int m,n,num=0;//m:苹果，n:盘子

void dfs(int sum,int dep){
    if(sum&lt;0){//没有苹果就没法放了，回溯
        return;
    }
    if(sum==0&amp;&amp;dep&lt;=n+1){//终止条件
        num++;
        return;
    }
    for(int i=a[dep-1];i&lt;=m;i++){//防止重复放比如{1，5，1}跟{5，1，1}是一样的放法
        a[dep]=i;//放的个数
        dfs(sum-i,dep+1);//继续搜索下一个盘子
        //没有回溯，因为这是组合问题，分的苹果数重复了属于同一种
    }
}
int main(){
    while(scanf("%d%d",&amp;m,&amp;n)!=EOF){
        num=0;
        dfs(m,1);
        printf("%d\n",num);
    }
    return 0;
}
</code></pre>
<hr>
<h4>三、八皇后问题</h4>
<blockquote>
 <p>会下国际象棋的人都很清楚：皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。</p>
 <p>如何将 8 个皇后放在棋盘上（有 8×8 个方格），使它们谁也不能被吃掉！</p>
 <p>这就是著名的八皇后问题。</p>
 <p>已经知道 8 皇后问题一共有 92 组解。</p>
 <p>要求打印每一种解。</p>
</blockquote>
<h5>解题思路：</h5>
<p>对于八皇后问题以后博主感觉不会出类似的题，但是我还是想把八皇后加入到例题来讲解，因为它实在是太经典了。八皇后问题是最经典的递归问题，<strong>你可以说没学过八皇后，但是不能说学了dfs但是不会八皇后。</strong>为什么说八皇后问题最经典，八皇后问题的条件一般是不会改变的，它在标记这一块动了手脚，本来由标记一个点变为标记整行、整列、点所在的主对角线、点所在的副对角线。主要麻烦在了标记这一块，标记处理好了，回溯的时候也比较好处理，现在对于标记网上有很多方法，我看了很多方法找出了一种我认为比较好的方法，是acking老师讲解的方法，跟大家分享一下。</p>
<p>首先，它需要标记四条线，分别是行、列、上左到右下、上右到左下，把它简化一下，我每次遍历从第一行开始，每次向下一个，这样保证了每次行不一样，可以少标记一个数组。列的话，一个一维vis数组就够了，上左到右下这条线，把它顺时针旋转45°，会发现是竖直的线。每一条线满足i-j+n不一样（i，j）是坐标。上右到左下这条线逆时针旋转45°，也会发现是一条竖线，每一条线满足i+j是不一样的。这样可以把它们的值当作下标值处理。</p>
<h6>视频讲解：acking老师讲解—&gt;<a href="https://www.bilibili.com/video/BV1Pa411T7p2/?p=5&amp;vd_source=fb68acc8676366210b38465a4a61ff28" title="点击这里">点击这里</a>&lt;—</h6>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb81afd30eab342d3bb7735b8b6552cc4.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbf33ddaafdaf4f61b57ed5a07493d0ef.png&amp;size=m" alt=""></p>
<h5>代码实现：</h5>
<pre><code class="cpp">#include&lt;stdio.h&gt;
int a[10][10];
int vis1[8]/*标记列*/, vis2[16],/*标记左上右下*/ vis3[16]/*标记右上左下*/;
int n = 8, ans = 0;
void vis(int i, int j, int flag) {
    a[i][j] = flag;
    vis1[j] = flag;
    vis2[i - j + n] = flag;
    vis3[i + j] = flag;
}
void dfs(int i) {
    if (i == n) {
        printf("No.%d\n", ++ans);
        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; n; j++) {
                printf("%d ", a[i][j]);
            }
            printf("\n");
        }
        return;
    }
    for (int j = 0; j &lt; n; j++) {
        if (vis1[j] == 0 &amp;&amp; vis2[i - j + n] == 0 &amp;&amp; vis3[i + j] == 0) {
            vis(i, j, 1);
            dfs(i + 1);
            vis(i, j, 0);
        }
    }
}
int main() {
    dfs(0);
    return 0;
}
</code></pre>
<hr>
<h4><a href="https://www.lanqiao.cn/problems/3513/learning/?subject_code=1&amp;group_code=4&amp;match_num=14&amp;match_flow=1&amp;origin=cup" title="四、第十四届蓝桥杯省赛大学B组 岛屿个数">四、第十四届蓝桥杯省赛大学B组 岛屿个数</a></h4>
<blockquote>
 <h6>题目描述：</h6>
 <p>小蓝得到了一副大小为 M×N 的格子地图，可以将其视作一个只包含字符 0（代表海水）和 1（代表陆地）的二维数组，地图之外可以视作全部是海水，每个岛屿由在上/下/左/右四个方向上相邻的 1 相连接而形成。</p>
 <p>在岛屿 A 所占据的格子中，如果可以从中选出 k 个不同的格子，使得他们的坐标能够组成一个这样的排列：(x0,y0),(x1,y1),…,(xk−1,yk−1)，其中 (x(i+1)%k,y(i+1)%k) 是由 (xi,yi) 通过上/下/左/右移动一次得来的 (0≤i≤k−1)，此时这 k 个格子就构成了一个 “环”。</p>
 <p>如果另一个岛屿 B 所占据的格子全部位于这个 “环” 内部，此时我们将岛屿 B 视作是岛屿 A 的子岛屿。若 B 是 A 的子岛屿，C 又是 B 的子岛屿，那 C 也是 A 的子岛屿。</p>
 <p>请问这个地图上共有多少个岛屿？在进行统计时不需要统计子岛屿的数目。</p>
 <p><strong>输入格式:</strong></p>
 <p>第一行一个整数 T，表示有 T 组测试数据。</p>
 <p>接下来输入 T 组数据。</p>
 <p>对于每组数据，第一行包含两个用空格分隔的整数 M、N 表示地图大小；接下来输入 M行，每行包含 N 个字符，字符只可能是 0 或 1。</p>
 <p><strong>输出格式:</strong></p>
 <p>对于每组数据，输出一行，包含一个整数表示答案。</p>
 <p><strong>数据范围:</strong></p>
 <p>对于 30% 的评测用例，1≤M,N≤10。
  <br>
  对于 100% 的评测用例，1≤T≤10，1≤M,N≤50。</p>
</blockquote>
<h5>解题思路：</h5>
<p>这个题博主感觉是<strong>蓝桥杯出的比较成功</strong>的题，它考查的非常具有思维性，打破了传统的dfs思路。加入了新的判断条件，很具有挑战性，博主感觉以后的像蓝桥杯这种比赛，dfs的考察一定会向着这个方向发展，传统的dfs将会退出题海战术。具体的讲解跟AC代码，请看我这一篇文章，文章单独讲解了这一道题，由于文章长度限制这里不再详解，请移步下面链接。</p>
<p><a href="https://blog.csdn.net/m0_73633807/article/details/137248445?spm=1001.2014.3001.5501" title="第十四届省赛大学B组（C/C++）岛屿个数-CSDN博客">第十四届省赛大学B组（C/C++）岛屿个数-CSDN博客文章浏览阅读1.1k次，点赞30次，收藏14次。这不是普通的DFS/BFS搜索题，看着很像最少连通块，但是题目中又有了新的定义就是在陆地环里面（被陆地包围）也算属于此外围岛屿，那么我们就也要判定这种环岛屿，博主的思路是先BFS也可DFS找出连通块的个数（四个方向），建一个vector把连通块的起点存进去，方便去找环岛屿，只要有一个起点（或者此连通块任意一个点），此连通块的点便可通过移动一网打尽，再BFS（或者DFS）判定该岛屿是否属于这种环岛屿，不属于就结果加一，属于就不用加。对于 100% 的评测用例，1≤T≤10，1≤M,N≤50。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbe19846480ab44ce477585fc567aeaa0.png&amp;size=m" alt="">https://blog.csdn.net/m0_73633807/article/details/137248445?spm=1001.2014.3001.5501</a></p>
<hr>
<p>感谢大家支持，下篇更新BFS(广度优先搜索)</p>
<p>原文链接: https://blog.csdn.net/m0_73633807/article/details/141037673</p>]]></description><guid isPermaLink="false">/archives/f4c3842b-6d8b-4588-b6b2-4d59cae12203</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:41:11 GMT</pubDate></item><item><title><![CDATA[【数据结构】栈和队列]]></title><link>https://wiki.dgfygk.com/archives/f15d75fe-1438-43b1-a7ca-1b3e05febff4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E3%80%91%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97&amp;url=/archives/f15d75fe-1438-43b1-a7ca-1b3e05febff4" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p><strong><em>Hi\~！这里是奋斗的小羊，很荣幸您能阅读我的文章，诚请评论指点，欢迎欢迎 \~\~</em></strong>
  <br>
  <strong>💥💥个人主页：<a href="https://blog.csdn.net/2301_78843337?type=blog">奋斗的小羊</a> 
   <br>💥💥所属专栏：<a href="https://blog.csdn.net/2301_78843337/category_12704050.html">数据结构</a></strong>
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbe0eba2cdb902865512268e5b80c9530.gif&amp;size=m" alt="">
  <br>
  <strong><em>🚀本系列文章为个人学习笔记，在这里撰写成文一为巩固知识，二为记录我的学习过程及理解。文笔、排版拙劣，望见谅。</em></strong></p>
</blockquote>
<hr>
<p>
 <br>
</p>
<h4>目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_12">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#_15">一、栈</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_16">1、栈的结构和概念</a></li>
 <li><a href="https://wiki.dgfygk.com#2_26">2、实现栈的方法选择</a></li>
 <li><a href="https://wiki.dgfygk.com#3_46">3、栈的实现</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#31_47">3.1完整代码</a></li>
   <li><a href="https://wiki.dgfygk.com#32_187">3.2特殊的接口</a></li>
   <li><a href="https://wiki.dgfygk.com#33_230">3.3访问栈的所有元素</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#4_284">4、用栈解决括号匹配的问题</a></li>
 <li><a href="https://wiki.dgfygk.com#_348">二、队列</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_349">1、队列的概念和结构</a></li>
 <li><a href="https://wiki.dgfygk.com#2_364">2、队列的实现</a></li>
 <li><a href="https://wiki.dgfygk.com#3_502">3、队列完整代码</a></li>
 <li><a href="https://wiki.dgfygk.com#_690">总结</a></li>
</ul>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faebbbfef9f9c77a280e79d33d508ee8b.gif&amp;size=m" alt=""></p>
<h2>前言</h2>
<p>栈作为一种比较特殊的存储结构，可以使用数组、单链表、双向链表等多种方法来实现，其在函数调用和递归、表达式求值、浏览器的历史记录、撤销操作、系统调用等多个场景中被广泛使用。</p>
<hr>
<h2>一、栈</h2>
<h3>1、栈的结构和概念</h3>
<p>栈是一种特殊的线性表，其只允许在固定的一端进行插入和删除元素的操作。
 <br>
 <strong>进行元素的插入和删除的一端称为栈顶，另一端称为栈底</strong> ，栈中的数据遵循<strong>后进先出</strong> 的原则。
 <br>
 <strong>压栈</strong> ：<strong>栈的插入</strong> 操作叫做进栈、压栈、入栈
 <br>
 <strong>出栈</strong> ：<strong>栈的删除</strong>操作叫做出栈</p>
<p>栈的结构类似于桶，只能从上面进，只能从上面出，因此我们一般只关心栈顶。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F385aa1eba2ae491fba4b91f4fdc743b2.png&amp;size=m" alt=""></p>
<hr>
<h3>2、实现栈的方法选择</h3>
<p>了解的栈的结构，接下来就要考虑如何实现栈。
 <br>
 在这之前我们学习了顺序表（底层就是数组）和链表，链表又分单链表和双向链表，用哪个方法实现效果最好呢？</p>
<p>上篇文章中我们简单地列举的顺序表和链表的比较，如下：</p>
<p>| | 顺序表 | 链表（双向链表） |
 <br>
 |————-|——————————–|—————-|
 <br>
 | 存储空间上 | 逻辑、物理上都连续 | 逻辑上连续、物理上不一定连续 |
 <br>
 | 随机访问 | 复杂度O(1) | 复杂度O(N) |
 <br>
 | 任意位置插入或删除数据 | 需要挪动数据，复杂度O(N) | 只需要改变指针指向 |
 <br>
 | 插入 | 动态顺序表，空间不够时扩容，扩容本身就有消耗，还容易空间浪费 | 没有容量的概念 |
 <br>
 | 应用场景 | 数据高效存储+频繁访问 | 任意位置频繁插入、删除数据 |
 <br>
 | 缓存利用率 | 高 | 低 |</p>
<p>其中我们关键看<strong>缓存利用率</strong> ，简单地说缓存利用率是指计算机系统中缓存（如CPU缓存、内存缓存等）被有效利用的程度。
 <br>
 CPU执行指令运算要访问数据，会先去缓存中找有没有这个数据，如果有，说明缓存命中了；如果没有，说明缓存未命中，就<strong>从主存中读取一段连续内存空间的数据到缓存</strong> ，继续找。
 <br>
 而我们知道在物理结构上顺序表是连续的，链表不连续。那从主存中读取一段连续内存空间的数据到缓存时链表读取到的很可能不是我们想要的数据，从而造成<strong>缓存污染</strong> 。
 <br>
 虽然顺序表在扩容的时候时间和空间都有消耗，而且还容易存在空间浪费，但是动态扩容也不是说很频繁，只有在空间不够时才扩容。
 <br>
 其实在实现栈上顺序表和链表只是五十步和百步的区别，只是相对而言顺序表更好一点。</p>
<hr>
<h3>3、栈的实现</h3>
<h4>3.1完整代码</h4>
<p>用顺序表的方法实现栈，和我们之前实现的顺序表没多大区别，只是栈中的元素只能从栈顶进从栈顶出，遵循后进先出的原则。
 <br>
 这里我们就先展示用顺序表的方法实现的栈的完整代码，其中的细节再一一介绍。
 <br>
 <code>stack.h:</code></p>
<pre><code class="prism language-c">#pragma once

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;assert.h&gt;
#include &lt;stdbool.h&gt;

typedef int st_data_type;

typedef struct stack
{

    st_data_type* arr;
    int top;
    int capacity;
}stack;

//初始化和销毁
void stack_init(stack* pst);
void stack_destroy(stack* pst);

//入栈和出栈
void stack_push(stack* pst, st_data_type x);
void stack_pop(stack* pst);

//取出栈顶元素
st_data_type stack_top(stack* pst);

//判空
bool stack_empty(stack* pst);

//获取数据个数
int stack_size(stack* pst);
</code></pre>
<p><code>stack.c:</code></p>
<pre><code class="prism language-c">#include "stack.h"

//初始化
void stack_init(stack* pst)
{

    assert(pst);
    pst-&gt;arr = NULL;
    pst-&gt;top = pst-&gt;capacity = 0;
}

//入栈
void stack_push(stack* pst, st_data_type x)
{

    assert(pst);
    if (pst-&gt;capacity == pst-&gt;top)
    {

        int newcapacity = pst-&gt;capacity == 0 ? 4 : 2 * pst-&gt;capacity;
        st_data_type* tmp = (st_data_type*)realloc(pst-&gt;arr, newcapacity * sizeof(st_data_type));
        if (tmp == NULL)
        {

            perror("realloc fail!");
            return 1;
        }
        pst-&gt;arr = tmp;
        tmp = NULL;
        pst-&gt;capacity = newcapacity;
    }
    pst-&gt;arr[pst-&gt;top] = x;
    pst-&gt;top++;
}

//出栈
void stack_pop(stack* pst)
{

    assert(pst);
    assert(pst-&gt;top &gt; 0);
    pst-&gt;top--;
}

//取出栈顶元素
st_data_type stack_top(stack* pst)
{

    assert(pst);
    assert(pst-&gt;top &gt; 0);
    return pst-&gt;arr[pst-&gt;top-1];
}

//销毁
void stack_destroy(stack* pst)
{

    assert(pst);
    free(pst-&gt;arr);
    pst-&gt;arr = NULL;
    pst-&gt;capacity = pst-&gt;top = 0;
}

//判空
bool stack_empty(stack* pst)
{

    assert(pst);
    return pst-&gt;top == 0;
}

//获取元素个数
int stack_size(stack* pst)
{

    assert(pst);
    return pst-&gt;top;
}
</code></pre>
<p><code>test.c:</code></p>
<pre><code class="prism language-c">#define  _CRT_SECURE_NO_WARNINGS

#include "stack.h"

void test()
{

    stack st;
    stack_init(&amp;st);

    //...

    stack_push(&amp;st, 1);
    stack_push(&amp;st, 2);
    stack_push(&amp;st, 3);
    stack_push(&amp;st, 4);

    stack_destroy(&amp;st);
}

int main()
{

    test();
    return 0;
}
</code></pre>
<p>可以看到因为栈结构的特殊，栈的实现比之前的顺序表简单多了，少了很多接口（函数）。
 <br>
 其中初始化、入栈、销毁函数跟之前实现的顺序表是一样的，这里就不再赘述。</p>
<hr>
<h4>3.2特殊的接口</h4>
<p><strong><em>出栈：</em></strong></p>
<pre><code class="prism language-c">//出栈
void stack_pop(stack* pst)
{

    assert(pst);
    assert(pst-&gt;top &gt; 0);
    pst-&gt;top--;
}
</code></pre>
<p>因为出栈就是<strong>删除栈顶</strong> 的一个元素数据，所以出栈的实现只需要将<code>top--</code>就行了。</p>
<p><strong><em>取出栈顶元素数据：</em></strong></p>
<pre><code class="prism language-c">//取出栈顶元素
st_data_type stack_top(stack* pst)
{

    assert(pst);
    assert(pst-&gt;top &gt; 0);
    return pst-&gt;arr[pst-&gt;top-1];
}
</code></pre>
<p>在取栈顶元素的时候，极其容易写出：<code>return pst-&gt;arr[pst-&gt;top];</code> 这条代码，因为我们下意识思维会认为最后一个元素数据的下标为top，其实不是的，这里跟顺序表中的<code>size</code>一样表示的是栈中元素的个数，所以栈中最后一个元素的下标是<code>top-1</code>。</p>
<p><strong><em>判空、获取数据个数：</em></strong></p>
<pre><code class="prism language-c">//判空
bool stack_empty(stack* pst)
{

    assert(pst);
    return pst-&gt;top == 0;
}

//获取元素个数
int stack_size(stack* pst)
{

    assert(pst);
    return pst-&gt;top;
}
</code></pre>
<p>如果栈中没有数据，则<code>pst-&gt;top == 0;</code>为真，返回<code>ture</code>，反之则返回<code>false</code>。</p>
<hr>
<h4>3.3访问栈的所有元素</h4>
<p>由于栈后进先出的特殊结构，访问栈的所有元素我们不能像顺序表那样利用下标循环打印，应该使用下面这种方式：</p>
<pre><code class="prism language-c">while (!stack_empty(&amp;pst))
{

    printf("%d ", stack_top(&amp;pst));
    stack_pop(&amp;pst);
}
</code></pre>
<p>拿到栈顶的元素后再拿下一个元素前需要<strong>先将栈顶的元素弹出（也就是删除），才能访问到下一个元素。</strong>
 <br>
 但是这也导致了一个问题，就是我们访问完栈中的所有元素后栈也就空了，不过不用担心这是正常现象。</p>
<p><strong><code>这里有个问题</code></strong> : <strong>栈的原则是后进先出</strong> ，如果我们入栈的顺序是1、2、3、4，那出栈的顺序一定是4、3、2、1吗？
 <br>
 其实不是的，因为我们可以边进边出。</p>
<pre><code class="prism language-c">#define  _CRT_SECURE_NO_WARNINGS

#include "stack.h"

void test()
{

    stack st;
    stack_init(&amp;st);

    stack_push(&amp;st, 1);
    printf("%d ", stack_top(&amp;st));
    stack_pop(&amp;st);

    stack_push(&amp;st, 2);
    printf("%d ", stack_top(&amp;st));
    stack_pop(&amp;st);

    stack_push(&amp;st, 3);
    stack_push(&amp;st, 4);

    while (!stack_empty(&amp;st))
    {

        printf("%d ", stack_top(&amp;st));
        stack_pop(&amp;st);
    }

    stack_destroy(&amp;st);
}

int main()
{

    test();
    return 0;
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3a4ef226db344465a83aed818bf41716.png&amp;size=m" alt=""></p>
<blockquote>
 <p>也就是说，<strong><em>出栈的顺序可以有多种</em></strong>。</p>
</blockquote>
<hr>
<h3>4、用栈解决括号匹配的问题</h3>
<p><strong>题目：</strong> <a href="https://leetcode.cn/problems/valid-parentheses/">有效的括号—leetcode</a></p>
<p>题目描述：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fed9d0bcb21e7439297e912a4c08a9c82.png&amp;size=m" alt=""></p>
<p>我们怎么使用栈的特点来解决这个问题呢？</p>
<p><strong><code>解题思路</code></strong> ：
 <br>
 返回<code>false</code>的情况有两种，一种是数量不匹配，一种是顺序不匹配。
 <br>
 栈有后进先出的特点，而括号匹配的问题首先需要找到<strong>若干个左括号</strong> ，然后再找<strong>右括号</strong> 进行一一配对，如果全部配对成功则返回<code>true</code>，否则返回<code>false</code>。
 <br>
 也就是说当我们拿到字符串中的第一个字符，如果是左括号则压栈，如果是右括号则出栈与右括号配对，只要有一次配对不上就返回<code>false</code>。</p>
<p><strong>有几个需要注意的点或特殊情况：</strong></p>
<blockquote>
 <ul>
  <li><strong>如果拿到的第一个字符就是右括号（也就是栈为空时），这时则直接返回<code>false</code></strong></li>
  <li><strong>当在字符串中取到<code>'\0'</code>退出循环后，还要再判断一下栈是否为空，因为有可能栈内还剩有压进去的左括号</strong></li>
  <li><strong><code>在每次返回前都要先销毁栈，避免内存泄漏</code></strong></li>
 </ul>
</blockquote>
<p>解题过程如下：</p>
<pre><code class="prism language-c">bool isValid(char* s) {

    //创建栈并初始化
    stack st;
    stack_init(&amp;st);

    while (*s != '\0')
    {

        //左括号则压栈
        if (*s == '(' || *s == '[' || *s == '{')
        {

            stack_push(&amp;st, *s);
        }
        //右括号则出栈，配对
        else
        {

            if (stack_empty(&amp;st))//拿到的第一个字符就是右括号
            {

                stack_destroy(&amp;st);
                return false;
            }
            //取
            char top = stack_top(&amp;st);
            //删
            stack_pop(&amp;st);
            //如果不配对则直接返回false
            if (top == '(' &amp;&amp; *s != ')'
            || top == '[' &amp;&amp; *s != ']'
            || top == '{' &amp;&amp; *s != '}')
            {

                stack_destroy(&amp;st);//避免内存泄漏
                return false;
            } 
        }
        s++;
    }
    //判断栈内是否还有未配对的左括号
    bool ret = stack_empty(&amp;st);
    stack_destroy(&amp;st);
    return ret;
}
</code></pre>
<p><strong>不管是栈后进先出的原则，还是栈元素访问后就消失的特点，在这个题中都能较好的体现出来。</strong></p>
<hr>
<h2>二、队列</h2>
<h3>1、队列的概念和结构</h3>
<p><strong>队列：</strong> 遵循<strong>先进先出</strong> 的原则，即最先被加入队列的元素最先被取出。
 <br>
 入队操作将新元素加入到队列的末尾（队尾），出队操作则<strong>移除并返回队列的第一个元素（队头）</strong>。</p>
<blockquote>
 <p><strong><em>与栈不同的是，队列结构不管怎样，入队和出队的顺序总是一样的。</em></strong></p>
</blockquote>
<p>队列通常应用于解耦和异步通信、任务调度、请求排队、资源分配、数据处理流水线、HTTP请求等多种场景。</p>
<p><strong>栈和队列比较：</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F60baba2d8fe84974b327d7153bfb1652.png&amp;size=m" alt="">
 <br>
 队列既可以用链表实现也可以用数组实现，但<strong>单链表</strong> 结构总体更优一些。因为如果使用数组实现，则出队列时剩余所有数据都要挪动位置，复杂度O(N)；
 <br>
 而使用链表实现的队列在插入和删除操作上更高效，不涉及元素搬移的操作。</p>
<hr>
<h3>2、队列的实现</h3>
<p>将队列看作一个特殊的单链表，则此单链表只有头删和尾插等操作，如果按照之前单链表的实现来完成队列的尾插，只有一个头节点的地址让尾插效率不是很高，因为还要遍历链表来找尾节点。
 <br>
 所以这里不再使用创建一个结构体指针变量记录第一个节点的方法，我们直接使用两个指针分别记录第一个节点和最后一个节点，这样入队（尾插）和出队（头删）效率更高。</p>
<pre><code class="prism language-c">typedef int QDataType;
//队列节点结构
typedef struct QueueNode
{

    QDataType data;
    struct QueueNode* next;
}QNode; 

//队尾插入
void QNodePush(QNode** pphead, QNode** pptail, QDataType x);
//队头删除
void QNodePop(QNode** pphead, QNode** pptail);
</code></pre>
<p>但是这样既要传两个指针还要传二级指针，为了简化代码我们用结构体变量分装起来，并且再添加一个整型值来记录队列元素个数。</p>
<pre><code class="prism language-c">//队列结构
typedef struct Queue
{

    QNode* phead;
    QNode* ptail;
    int size;
}Que;

//初始化队列结构
void QueueInit(Que* pq);
//队尾插入
void QNodePush(Que* pq, QDataType x);
//队头删除
void QNodePop(Que* pq);
//队列元素个数
int QNodeSize(Que* pq);
//取队头
QDataType QNodeFront(Que* pq);
//取队尾
QDataType QNodeBack(Que* pq);
//判空
bool QNodeEmpty(Que* pq);
//销毁队列
void QNodeDestroy(Que* pq);
</code></pre>
<p>这样一来我们只需要传递结构体指针，就能改变头指针和尾指针的指向，但是这个结构体开始需要初始化一下：</p>
<pre><code class="prism language-c">//初始化队列结构
void QueueInit(Que* pq)
{

    assert(pq);
    pq-&gt;phead = pq-&gt;ptail = NULL;
    pq-&gt;size = 0;
}
</code></pre>
<p><strong><em>队尾插入：</em></strong></p>
<pre><code class="prism language-c">//队尾插入
void QNodePush(Que* pq, QDataType x)
{

    assert(pq);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {

        perror("malloc fail");
        return;
    }
    newnode-&gt;data = x;
    newnode-&gt;next = NULL;

    //没有节点
    if (pq-&gt;phead == NULL)
    {

        pq-&gt;phead = pq-&gt;ptail = newnode;
    }
    else//有节点
    {

        pq-&gt;ptail-&gt;next = newnode;
        pq-&gt;ptail = newnode;
    }
    pq-&gt;size++;
}
</code></pre>
<p>入队（尾插）需要注意的是，如果当前队列没有节点，则头指针和尾指针都要指向新节点。</p>
<p><strong><em>队头删除：</em></strong></p>
<pre><code class="prism language-c">//队头删除
void QNodePop(Que* pq)
{

    assert(pq);
    assert(pq-&gt;phead);//没有节点

    //一个节点
    if (pq-&gt;phead == pq-&gt;ptail)
    {

        free(pq-&gt;phead);
        pq-&gt;phead = pq-&gt;ptail = NULL;
    }
    //多个节点
    else
    {

        QNode* next = pq-&gt;phead-&gt;next;
        free(pq-&gt;phead);
        pq-&gt;phead = next;
    }
    pq-&gt;size--;
}
</code></pre>
<p>出队（头删）分为三种情况，没有节点、一个节点和多个节点。
 <br>
 如果只有一个节点（头指针和尾指针指向同一节点），则在释放掉这个节点后头指针和尾指针都要置<code>NULL</code>。</p>
<p><strong><em>取队头、取队尾：</em></strong></p>
<pre><code class="prism language-c">//取队头
QDataType QNodeFront(Que* pq)
{

    assert(pq);
    assert(pq-&gt;phead);
    return pq-&gt;phead-&gt;data;
}

//取队尾
QDataType QNodeBack(Que* pq)
{

    assert(pq);
    assert(pq-&gt;ptail);
    return pq-&gt;ptail-&gt;data;
}

while (!QNodeEmpty(&amp;q))
{

    printf("%d ", QNodeFront(&amp;q));
    //元素取完要删除
    QNodePop(&amp;q);
}
</code></pre>
<p>取出队列中的元素后需要弹出此元素才能取到下一个元素，这点与栈相同。</p>
<hr>
<h3>3、队列完整代码</h3>
<p><code>queue.h:</code></p>
<pre><code class="prism language-c">#pragma once

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;assert.h&gt;
#include &lt;stdbool.h&gt;

typedef int QDataType;

//队列节点结构
typedef struct QueueNode
{

    QDataType data;
    struct QueueNode* next;
}QNode; 

//队列结构
typedef struct Queue
{

    QNode* phead;
    QNode* ptail;
    int size;
}Que;

//初始化队列结构
void QueueInit(Que* pq);
//队尾插入
void QNodePush(Que* pq, QDataType x);
//队头删除
void QNodePop(Que* pq);
//队列元素个数
int QNodeSize(Que* pq);
//取队头
QDataType QNodeFront(Que* pq);
//取队尾
QDataType QNodeBack(Que* pq);
//判空
bool QNodeEmpty(Que* pq);
//销毁队列
void QNodeDestroy(Que* pq);
</code></pre>
<p><code>queue.c:</code></p>
<pre><code class="prism language-c">#define  _CRT_SECURE_NO_WARNINGS

#include "queue.h"

//初始化队列结构
void QueueInit(Que* pq)
{

    assert(pq);
    pq-&gt;phead = pq-&gt;ptail = NULL;
    pq-&gt;size = 0;
}

//队尾插入
void QNodePush(Que* pq, QDataType x)
{

    assert(pq);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {

        perror("malloc fail");
        return;
    }
    newnode-&gt;data = x;
    newnode-&gt;next = NULL;

    //没有节点
    if (pq-&gt;phead == NULL)
    {

        pq-&gt;phead = pq-&gt;ptail = newnode;
    }
    else//多个节点
    {

        pq-&gt;ptail-&gt;next = newnode;
        pq-&gt;ptail = newnode;
    }
    pq-&gt;size++;
}

//队头删除
void QNodePop(Que* pq)
{

    assert(pq);
    assert(pq-&gt;phead);//没有节点

    //一个节点
    if (pq-&gt;phead == pq-&gt;ptail)
    {

        free(pq-&gt;phead);
        pq-&gt;phead = pq-&gt;ptail = NULL;
    }
    //多个节点
    else
    {

        QNode* next = pq-&gt;phead-&gt;next;
        free(pq-&gt;phead);
        pq-&gt;phead = next;
    }
    pq-&gt;size--;
}

//队列元素个数
int QNodeSize(Que* pq)
{

    assert(pq);
    return pq-&gt;size;
}

//取队头
QDataType QNodeFront(Que* pq)
{

    assert(pq);
    assert(pq-&gt;phead);
    return pq-&gt;phead-&gt;data;
}

//取队尾
QDataType QNodeBack(Que* pq)
{

    assert(pq);
    assert(pq-&gt;ptail);
    return pq-&gt;ptail-&gt;data;
}

//判空
bool QNodeEmpty(Que* pq)
{

    assert(pq);
    return pq-&gt;phead == NULL;
}

//销毁队列
void QNodeDestroy(Que* pq)
{

    assert(pq);
    while (pq-&gt;phead)
    {

        QNode* next = pq-&gt;phead-&gt;next;
        free(pq-&gt;phead);
        pq-&gt;phead = next;
    }
    pq-&gt;ptail = NULL;
    pq-&gt;size = 0;
}
</code></pre>
<p><code>test.c:</code></p>
<pre><code class="prism language-c">#define  _CRT_SECURE_NO_WARNINGS

#include "queue.h"

void test()
{

    Que q;
    QueueInit(&amp;q);

    QNodePush(&amp;q, 1);
    QNodePush(&amp;q, 2);
    QNodePush(&amp;q, 3);
    QNodePush(&amp;q, 4);

    QNodePop(&amp;q);

    while (!QNodeEmpty(&amp;q))
    {

        printf("%d ", QNodeFront(&amp;q));
        //元素取完要删除
        QNodePop(&amp;q);
    }
    printf("\n");

    QNodeDestroy(&amp;q);
}

int main()
{

    test();
    return 0;
}
</code></pre>
<hr>
<h2>总结</h2>
<blockquote>
 <ul>
  <li>栈适合在需要临时存储、后进先出的场景下使用，特别在处理递归、嵌套、层次结构等问题时非常有用</li>
  <li>栈的操作受限于其后进先出的特性，在处理大量数据、需要随机操作等情况下，不太适合使用栈</li>
  <li>队列通常有一个固定的容量，一旦队列达到最大容量，后续元素无法入队，可能导致数据丢失或系统阻塞</li>
 </ul>
</blockquote>
<p>原文链接: https://blog.csdn.net/2301_78843337/article/details/140359453</p>]]></description><guid isPermaLink="false">/archives/f15d75fe-1438-43b1-a7ca-1b3e05febff4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:40:58 GMT</pubDate></item><item><title><![CDATA[【数据结构】有关环形链表题目的总结]]></title><link>https://wiki.dgfygk.com/archives/f107cb42-0ac5-4cd5-b952-c8283e2685cf</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E3%80%91%E6%9C%89%E5%85%B3%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%E9%A2%98%E7%9B%AE%E7%9A%84%E6%80%BB%E7%BB%93&amp;url=/archives/f107cb42-0ac5-4cd5-b952-c8283e2685cf" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#___4">引入 - 快慢指针</a></li>
 <li><a href="https://wiki.dgfygk.com#___38">思考 - 快慢指针行走步数</a></li>
 <li><a href="https://wiki.dgfygk.com#___63">进阶 - 寻找环形链表的头</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<h2>引入 - 快慢指针</h2>
<p><a href="https://leetcode.cn/problems/linked-list-cycle/description/">141-环形链表 - Leetcode</a>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1e6a36fc2542a022bee2a4fb84afeebb.png&amp;size=m" alt="">
 <br>
 关于这道题，大家可以利用快慢指针，一个每次走两步，一个每次走一步，只要他们有一次相撞了就代表说这是一个链表
 <br>
 为了更好的理解我们可以代入下图
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fddf791930723e08f14c25729f12f69f5.png&amp;size=m" alt="">
 <br>
 我们假设<strong>slow</strong> 进环的时侯，<strong>fast</strong> 已经走了<strong>C（环形区域的总长度）-N</strong> 步了，那么它就距离<strong>slow</strong> 刚刚好<strong>N</strong> 步于是乎没进行一次移动，即<strong>fast</strong> 往前走2步，<strong>slow</strong> 往前走一步，那么它们之间的距离就是<strong>N-1</strong> 步以此类推直到<strong>0</strong> 步
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc31f20e73a5ad75fa4f6407e876c0365.png&amp;size=m" alt="">
 <br>
 所以这一题的解答代码就是</p>
<pre><code class="prism language-c">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {

    if(head == NULL)
        return false;
    ListNode *fast = head, *slow = head;
    while(fast &amp;&amp; fast-&gt;next)
    {

        fast = fast-&gt;next-&gt;next;
        slow = slow-&gt;next;
        if(slow == fast)
            return true;
    }
    return false;
}
</code></pre>
<h2>思考 - 快慢指针行走步数</h2>
<p>根据上面快指针一次走两步，慢指针一次走一步，我们可以得到结果并知晓原理</p>
<p>但如果快指针一次走的是三步呢，结果还会一样吗。
 <br>
 为此，我们不妨罗列一下三步的情况</p>
<p>假设一次走三步，我们在不分 <strong>C（环形区域的总长度）</strong> 的情况下，它们之间的距离就会是N -&gt; N-2 -&gt; N-4 -&gt; …
 <br>
 因为不分奇偶就无法得出最后结果，所以我们可以得到下面这个分类讨论</p>
<p>即：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5ef1420a5dca541259935ee297f3f0a4.png&amp;size=m" alt="">
 <br>
 我们发现一旦 <strong>C（环形区域的总长度）</strong> 为奇数的话，就会导致最后快指针比慢指针多走了一步，这就会导致，追不上吗，
 <br>
 但真的会这样吗</p>
<p>大家肯定会反应过来， <strong>C（环形区域的总长度）</strong> 的总长度是不会变的，变得只是快慢指针之间的距离，如果在进行一次循环我们可以将 <strong>N</strong> 的初始值看成 <strong>C-1</strong> 了，而<strong>N</strong> 每次移动都需要 <strong>-2</strong> ，并且 <strong>C</strong> 为奇数，因此这一次，就可以看成<strong>偶数</strong> 的这种情况了，这样就能抓到了
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F257f8ba8e3a918be764b1a7baa8744b6.png&amp;size=m" alt="">
 <br>
 同样的我们如果将这种情况以数学公式表示
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F752085a2530f93d6be38a5bcbd7aca5c.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F65a1028800b6b3480de5d3ac75653392.png&amp;size=m" alt=""></p>
<p>这是快指针步数为3的情况，为4，为5的讨论情况也差不多这样，无非就是再讨论一下，这里就不赘述了</p>
<hr>
<h2>进阶 - 寻找环形链表的头</h2>
<p><a href="https://leetcode.cn/problems/linked-list-cycle-ii/description/">142 - 环形链表Ⅱ - Leetcode</a>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb010505918a693758a72655fe31baea4.png&amp;size=m" alt="">
 <br>
 乍一看，这道题与上面一道题基本一样，但是细看我们会发现这里返回开始入环的第一个节点，所以还是需要思考</p>
<p>如果有环的话，创建两个指针，一个指针从head节点开始，另一个指针从相遇点meet开始，两个指针每次都走一步，两个指针相遇的点就是链表入环的第一个节点。</p>
<p>因此在上面引入那道题的基础上我们需要增进一步
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc28bc1dde59e390183ff6ce16dd1777f.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbe81986e9088183384a6c4eb84c400fa.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F775be42e419a8d96c0d784872a7b6ff9.png&amp;size=m" alt="">
 <br>
 即为L距离就等于meet在环中转x-1圈，再走C-N，所以说meet和head的相遇点一定是链表入环的第一个节点。</p>
<p>即</p>
<pre><code class="prism language-c">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {


    if(head == NULL)
        return NULL;
    ListNode *fast = head, *slow = head;
    while(fast &amp;&amp; fast-&gt;next)
    {

        fast = fast-&gt;next-&gt;next;
        slow = slow-&gt;next;
        if(slow == fast)
        {

            ListNode* phead = head;
            ListNode* meet = slow;
            while(phead != meet)
            {

                phead = phead-&gt;next;
                meet = meet-&gt;next;
            }
            return meet;
        }
    }
    return NULL;
}
</code></pre>
<p>原文链接: https://blog.csdn.net/2301_77954967/article/details/138506571</p>]]></description><guid isPermaLink="false">/archives/f107cb42-0ac5-4cd5-b952-c8283e2685cf</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:40:16 GMT</pubDate></item><item><title><![CDATA[【数据结构】排序算法---桶排序]]></title><link>https://wiki.dgfygk.com/archives/e7464195-8540-4a96-935c-0ac793c4accf</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E3%80%91%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95---%E6%A1%B6%E6%8E%92%E5%BA%8F&amp;url=/archives/e7464195-8540-4a96-935c-0ac793c4accf" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F09858dabb3024d64a024f10a2a3fb022.gif%23pic_center&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1__4">1. 定义</a></li>
 <li><a href="https://wiki.dgfygk.com#2__15">2. 算法步骤</a></li>
 <li><a href="https://wiki.dgfygk.com#3__27">3. 演示</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#31_1_29">3.1 动态演示1</a></li>
 <li><a href="https://wiki.dgfygk.com#32_2_34">3.2 动态演示2</a></li>
 <li><a href="https://wiki.dgfygk.com#33_1_39">3.3 图片演示1</a></li>
 <li><a href="https://wiki.dgfygk.com#34_2_51">3.4 图片演示2</a></li>
 <li><a href="https://wiki.dgfygk.com#4__56">4. 性质</a></li>
 <li><a href="https://wiki.dgfygk.com#5__82">5. 算法分析</a></li>
 <li><a href="https://wiki.dgfygk.com#6__86">6. 代码实现</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#C_88">C语言</a></li>
 <li><a href="https://wiki.dgfygk.com#Python_175">Python</a></li>
 <li><a href="https://wiki.dgfygk.com#Java_208">Java</a></li>
 <li><a href="https://wiki.dgfygk.com#C_285">C++</a></li>
 <li><a href="https://wiki.dgfygk.com#Go_354">Go</a></li>
 <li><a href="https://wiki.dgfygk.com#_398">结语</a></li>
</ul>
<p>
 <br>
</p>
<ol>
 <li>
  <h2>定义</h2>
 </li>
</ol>
<p>桶排序（英文：Bucket sort）是计数排序的升级版，适用于待排序数据值域较大但分布比较均匀的情况。它利用了函数的映射关系，高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理：假设输入数据服从均匀分布，将数据分到有限数量的桶里，每个桶再分别排序（有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排）。</p>
<p>为了使桶排序更加高效，我们需要做到这两点：</p>
<ol>
 <li>在额外空间充足的情况下，尽量增大桶的数量</li>
 <li>使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中</li>
</ol>
<p>同时，对于桶中元素的排序，选择何种比较排序算法对于性能的影响至关重要。</p>
<ol>
 <li>
  <h2>算法步骤</h2>
 </li>
</ol>
<p>桶排序按下列步骤进行：</p>
<ol>
 <li>
  <p>设置一个定量的数组当作空桶；</p>
 </li>
 <li>
  <p>遍历输入数据，并且把数据一个一个放到对应的桶里去；</p>
 </li>
 <li>
  <p>对每个不是空的桶进行排序；</p>
 </li>
 <li>
  <p>从不是空的桶里把排好序的数据拼接起来。</p>
 </li>
 <li>
  <h2>演示</h2>
 </li>
</ol>
<h3>3.1 动态演示1</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0a3a8ae00d954ce8b7959da1c6cc86f9.gif%23pic_center&amp;size=m" alt=""></p>
<h3>3.2 动态演示2</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4b220269ac8847edb184141659c0a477.gif%23pic_center&amp;size=m" alt=""></p>
<h3>3.3 图片演示1</h3>
<p>元素分布在桶中：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F40c9b5fc30914fe8805642a39c155a6d.png%23pic_center&amp;size=m" alt=""></p>
<p>然后，元素在每个桶中排序：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa5db68986ff74dc6b051611c8dfe184d.png%23pic_center&amp;size=m" alt=""></p>
<h3>3.4 图片演示2</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F934c02d23d74456bb448c10ee01a6f10.png%23pic_center&amp;size=m" alt=""></p>
<ol>
 <li>
  <h2>性质</h2>
 </li>
</ol>
<p><strong>稳定性</strong>：</p>
<p>如果使用稳定的内层排序，并且将元素插入桶中时不改变元素间的相对顺序，那么桶排序就是一种稳定的排序算法。</p>
<p>由于每块元素不多，一般使用<strong>插入排序</strong>。此时桶排序是一种稳定的排序算法。</p>
<p><strong>空间复杂度</strong>：</p>
<p>桶排序算法排序过程中新建了一个桶和一个输出数组，所以算法的空间复杂度是 O ( N + M ) O(N+M) O(N+M)。</p>
<p><strong>时间复杂度</strong>：</p>
<p>桶排序的平均时间复杂度为 O ( n + n 2 k + k ) O(n+{n^2 \over k}+k) O(n+kn2+k)（将值域平均分成n块 + 排序 + 重新合并元素)，当k≈n时为 O ( n ) O(n) O(n)。</p>
<p>桶排序的最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)。</p>
<blockquote>
 <p><strong>什么时候最快</strong>：</p>
 <p>当输入的数据可以均匀的分配到每一个桶中。</p>
 <p><strong>什么时候最慢</strong>：</p>
 <p>当输入的数据被分配到了同一个桶中。</p>
</blockquote>
<ol>
 <li>
  <h2>算法分析</h2>
 </li>
</ol>
<p>桶排序最好情况下使用线性时间 O ( n ) O(n) O(n)，桶排序的时间复杂度，取决与对各个桶之间数据进行排序的时间复杂度，因为其它部分的时间复杂度都为 O ( n ) O(n) O(n)。很显然，桶划分的越小，各个桶之间的数据越少，排序所用的时间也会越少。但相应的空间消耗就会增大。</p>
<ol>
 <li>
  <h2>代码实现</h2>
 </li>
</ol>
<h3>C语言</h3>
<pre><code class="prism language-c">void RadixSort(int* arr, int n)
{

    //max为数组中最大最小值
    int max = arr[0];
    int min = arr[0];
    int base = 1;

    //找出数组中的最大值
    for (int i = 0; i &lt; n; i++)
    {

        if (arr[i] &gt; max)
        {

            max = arr[i];
        }
        if (arr[i] &lt; min)
        {

            min = arr[i];
        }
    }
    //循环结束max就是数组最大最小值

    //循环将数组的元素全部变为正数
    //所有元素加上最小值的绝对值
    for (int i = 0; i &lt; n; i++)
    {

        arr[i] += abs(min);
    }

    //临时存放数组元素的空间
    int* tmp = (int*)malloc(sizeof(int)*n);

    //循环次数为最大数的位数
    while (max / base &gt; 0)
    {

        //定义十个桶，桶里面装的不是数据本身，而是每一轮排序对应（十、白、千...）位的个数
        //统计每个桶里面装几个数
        int bucket[10] = {
    0 };
        for (int i = 0; i &lt; n; i++)
        {

            //arr[i] / base % 10可以取到个位、十位、百位对应的数字
            bucket[arr[i] / base % 10]++;
        }
        //循环结束就已经统计好了本轮每个桶里面应该装几个数


        //将桶里面的元素依次累加起来，就可以知道元素存放在临时数组中的位置
        for (int i = 1; i &lt; 10; i++)
        {

            bucket[i] += bucket[i - 1];
        }
        //循环结束现在桶中就存放的是每个元素应该存放到临时数组的位置


        //开始放数到临时数组tmp
        for (int i = n - 1; i &gt;= 0; i--)
        {

            tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
            bucket[arr[i] / base % 10]--;
        }
        //不能从前往后放，因为这样会导致十位排好了个位又乱了，百位排好了十位又乱了
        /*for (int i = 0; i &lt; n; i++)
        {
            tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
            bucket[arr[i] / base % 10]--;
        }*/

        //把临时数组里面的数拷贝回去
        for (int i = 0; i &lt; n; i++)
        {

            arr[i] = tmp[i];
        }
        base *= 10;
    }
    free(tmp);

    //还原原数组
    for (int i = 0; i &lt; n; i++)
    {

        arr[i] -= abs(min);
    }
}
</code></pre>
<h3>Python</h3>
<pre><code class="prism language-python">N = 100010
w = n = 0
a = [0] * N
bucket = [[] for i in range(N)]


def insertion_sort(A):
    for i in range(1, len(A)):
        key = A[i]
        j = i - 1
        while j &gt;= 0 and A[j] &gt; key:
            A[j + 1] = A[j]
            j -= 1
        A[j + 1] = key


def bucket_sort():
    bucket_size = int(w / n + 1)
    for i in range(0, n):
        bucket[i].clear()
    for i in range(1, n + 1):
        bucket[int(a[i] / bucket_size)].append(a[i])
    p = 0
    for i in range(0, n):
        insertion_sort(bucket[i])
        for j in range(0, len(bucket[i])):
            a[p] = bucket[i][j]
            p += 1
</code></pre>
<h3>Java</h3>
<pre><code class="prism language-java">public class BucketSort extends BaseSort {


    public static void main(String[] args) {

        BucketSort sort = new BucketSort();
        sort.printNums();
    }

    @Override
    protected void sort(int[] nums) {

        if (nums == null || nums.length &lt; 2) {

            return;
        }
        bucketSort(nums);
    }

    public void bucketSort(int[] nums) {

        if (nums == null || nums.length &lt; 2) {

            return;
        }
        //找出最大值，最小值
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int num : nums) {

            min = Math.min(min, num);
            max = Math.max(max, num);
        }
        int length = nums.length;
        //桶的数量
        int bucketCount = (max - min) / length + 1;
        int[][] bucketArrays = new int[bucketCount][];
        //遍历数组，放入桶内
        for (int i = 0; i &lt; length; i++) {

            //找到桶的下标
            int index = (nums[i] - min) / length;
            //添加到指定下标的桶里，并且使用插入排序排序
            bucketArrays[index] = insertSortArrays(bucketArrays[index], nums[i]);
        }
        int k = 0;
        //合并全部桶的
        for (int[] bucketArray : bucketArrays) {

            if (bucketArray == null || bucketArray.length == 0) {

                continue;
            }
            for (int i : bucketArray) {

                //把值放回到nums数组中
                nums[k++] = i;
            }
        }
    }

    //每个桶使用插入排序进行排序
    private int[] insertSortArrays(int[] arr, int num) {

        if (arr == null || arr.length == 0) {

            return new int[]{
   num};
        }
        //创建一个temp数组，长度是arr数组的长度+1
        int[] temp = new int[arr.length + 1];
        //把传进来的arr数组，复制到temp数组
        for (int i = 0; i &lt; arr.length; i++) {

            temp[i] = arr[i];
        }
        //找到一个位置，插入，形成新的有序的数组
        int i;
        for (i = temp.length - 2; i &gt;= 0 &amp;&amp; temp[i] &gt; num; i--) {

            temp[i + 1] = temp[i];
        }
        //插入需要添加的值
        temp[i + 1] = num;
        //返回
        return temp;
    }
}
</code></pre>
<h3>C++</h3>
<pre><code class="prism language-c">#include&lt;iterator&gt;
#include&lt;iostream&gt;
#include&lt;vector&gt;
using namespace std;
const int BUCKET_NUM = 10;

struct ListNode{

        explicit ListNode(int i=0):mData(i),mNext(NULL){
   }
        ListNode* mNext;
        int mData;
};

ListNode* insert(ListNode* head,int val){

        ListNode dummyNode;
        ListNode *newNode = new ListNode(val);
        ListNode *pre,*curr;
        dummyNode.mNext = head;
        pre = &amp;dummyNode;
        curr = head;
        while(NULL!=curr &amp;&amp; curr-&gt;mData&lt;=val){

                pre = curr;
                curr = curr-&gt;mNext;
        }
        newNode-&gt;mNext = curr;
        pre-&gt;mNext = newNode;
        return dummyNode.mNext;
}


ListNode* Merge(ListNode *head1,ListNode *head2){

        ListNode dummyNode;
        ListNode *dummy = &amp;dummyNode;
        while(NULL!=head1 &amp;&amp; NULL!=head2){

                if(head1-&gt;mData &lt;= head2-&gt;mData){

                        dummy-&gt;mNext = head1;
                        head1 = head1-&gt;mNext;
                }else{

                        dummy-&gt;mNext = head2;
                        head2 = head2-&gt;mNext;
                }
                dummy = dummy-&gt;mNext;
        }
        if(NULL!=head1) dummy-&gt;mNext = head1;
        if(NULL!=head2) dummy-&gt;mNext = head2;

        return dummyNode.mNext;
}

void BucketSort(int n,int arr[]){

        vector&lt;ListNode*&gt; buckets(BUCKET_NUM,(ListNode*)(0));
        for(int i=0;i&lt;n;++i){

                int index = arr[i]/BUCKET_NUM;
                ListNode *head = buckets.at(index);
                buckets.at(index) = insert(head,arr[i]);
        }
        ListNode *head = buckets.at(0);
        for(int i=1;i&lt;BUCKET_NUM;++i){

                head = Merge(head,buckets.at(i));
        }
        for(int i=0;i&lt;n;++i){

                arr[i] = head-&gt;mData;
                head = head-&gt;mNext;
        }
}
</code></pre>
<h3>Go</h3>
<pre><code class="prism language-go">package main

import (
    "fmt"
    "container/list"
)

func bucketSort(theArray []int,num int){

    var theSort [99]int
    for i:=0;i&lt; len(theArray);i++{

        theSort[10]=1
        if theSort[theArray[i]] !=0{

            theSort[theArray[i]] = theSort[theArray[i]]+1
        }else{

            theSort[theArray[i]] = 1
        }
    }
    l:=list.New()
    for j:=0;j&lt;len(theSort);j++{

        if theSort[j]==0{

            //panic("error test.....")
        }else{

            for k:=0;k&lt;theSort[j];k++{

                l.PushBack(j)
            }
        }
    }
    for e := l.Front(); e != nil; e = e.Next() {

        fmt.Print(e.Value, " ")
    }

}

func main() {

    var theArray = []int{
   10, 1, 18, 30, 23, 12, 7, 5, 18, 17}
    fmt.Print("排序前")
    fmt.Println(theArray)
    fmt.Print("排序后")
    bucketSort(theArray,11)
}
</code></pre>
<h2>结语</h2>
<p>今天的分享到这里就结束啦！如果觉得文章还不错的话，可以三连支持一下。</p>
<p>也可以点点关注，避免以后找不到我哦！</p>
<p><a href="https://blog.csdn.net/2301_80191662?type=lately">Crossoads主页</a>还有很多有趣的文章，欢迎小伙伴们前去点评，您的支持就是作者前进的动力！</p>
<blockquote>
 <p>带你初步了解排序算法：<a href="https://blog.csdn.net/2301_80191662/article/details/142211265">https://blog.csdn.net/2301_80191662/article/details/142211265</a>
  <br>
  直接插入排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142300973">https://blog.csdn.net/2301_80191662/article/details/142300973</a>
  <br>
  希尔排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142302553">https://blog.csdn.net/2301_80191662/article/details/142302553</a>
  <br>
  直接选择排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142312028">https://blog.csdn.net/2301_80191662/article/details/142312028</a>
  <br>
  堆排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142312338">https://blog.csdn.net/2301_80191662/article/details/142312338</a>
  <br>
  冒泡排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142324131">https://blog.csdn.net/2301_80191662/article/details/142324131</a>
  <br>
  快速排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142324307">https://blog.csdn.net/2301_80191662/article/details/142324307</a>
  <br>
  归并排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142350640">https://blog.csdn.net/2301_80191662/article/details/142350640</a>
  <br>
  计数排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142350741">https://blog.csdn.net/2301_80191662/article/details/142350741</a>
  <br>
  桶排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142375338">https://blog.csdn.net/2301_80191662/article/details/142375338</a>
  <br>
  基数排序：<a href="https://blog.csdn.net/2301_80191662/article/details/142375592">https://blog.csdn.net/2301_80191662/article/details/142375592</a>
  <br>
  十大经典排序算法总结与分析：<a href="https://blog.csdn.net/2301_80191662/article/details/142211564">https://blog.csdn.net/2301_80191662/article/details/142211564</a></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F753936e54d6d4a36aa89a4f934f23667.gif%23pic_center&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/2301_80191662/article/details/142375338</p>]]></description><guid isPermaLink="false">/archives/e7464195-8540-4a96-935c-0ac793c4accf</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:39:58 GMT</pubDate></item><item><title><![CDATA[算法： 双指针]]></title><link>https://wiki.dgfygk.com/archives/05f0a6a2-6fdb-4bdb-a94f-6f8dc6f4afc4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%AE%97%E6%B3%95%EF%BC%9A%20%E5%8F%8C%E6%8C%87%E9%92%88&amp;url=/archives/05f0a6a2-6fdb-4bdb-a94f-6f8dc6f4afc4" width="1" height="1" alt="" style="opacity:0;">
<h2>题目：<a href="https://leetcode.cn/problems/linked-list-cycle/description/" title="环形链表">环形链表</a></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8dcb553da3b445c5b80275e5655bb99c.png&amp;size=m" alt=""></p>
<h2>题目讲解：</h2>
<h4>判断环</h4>
<p>要判断链表是否有环，可以使用<strong>快慢指针</strong>的方法。快指针每次走两步，慢指针每次走一步。如果链表有环，快慢指针最终会相遇；如果没有环，快指针会先到达链表末尾。</p>
<p>为什么快指针走两步，慢指针走一步？因为这样快指针会更快进入环，并在环中追上慢指针。具体来说，当快指针进入环后，它将在环中不断接近慢指针，最终相遇。即使快指针在环中比慢指针快，它们也不会永远错过，而是必然会在某一时刻相遇。</p>
<p>这个相遇的原因是因为在每一次循环中，快指针都比慢指针多走一步，这样慢指针总会缩小它与快指针之间的距离，直至两者相遇。</p>
<p>如果操场是一个环形的（即链表有环），那么即使快跑者一开始远远超过慢跑者，由于他们在同一条跑道上不断绕圈，快跑者最终一定会追上慢跑者。这是因为在每一圈中，快跑者都会比慢跑者多跑一圈的距离，最终两人会在某个点相遇。</p>
<h2>代码实现：</h2>
<pre><code>class Solution 
{
public:
    bool hasCycle(ListNode *head) 
    {
        //判断空链表及单节点
       if(head == nullptr || head-&gt;next == nullptr)
       {
        return false;
       }
        ListNode* fast = head-&gt;next;
        ListNode* slow = head;
        while(fast != nullptr &amp;&amp; fast-&gt;next != nullptr)
        {
        fast = fast-&gt;next-&gt;next;
        slow = slow-&gt;next;
         if(fast == slow)
           {
            return true;
           } 
        }
        return false;
    }
};
</code></pre>
<h2>注意事项：</h2>
<p>在处理链表时，必须仔细考虑 <code>while</code> 循环的判断条件。如果链表不是环形的，快指针每次移动两步可能会最终指向空引用，导致野指针问题。为了避免这种情况，你需要提前检查快指针的下一个节点是否为空。使用 <code>&amp;&amp;</code> 运算符可以确保两个条件都为真，这样可以防止当链表只有两个节点时发生错误——在这种情况下执行 <code>fast = fast-&gt;next-&gt;next;</code> 会导致野指针。</p>
<p>关于快指针最初指向第一个节点而慢指针指向空的做法，在这里并不适用。如果 <code>slow</code> 为空，执行 <code>slow = slow-&gt;next;</code> 会导致空指针引用错误。因此，必须根据具体问题定制你的方法，而不是盲目套用模式。</p>
<p>
 <br>
</p>
<h2>题目：<a href="https://leetcode.cn/problems/linked-list-cycle-ii/description/" title="环形链表||">环形链表\|\|</a></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd50e35d3134b417ea18b14b6cb78e743.png&amp;size=m" alt=""></p>
<h2>题目讲解：</h2>
<h3>找环入口</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F75efd5392ed74df7b11c8e38ede8f7fe.png&amp;size=m" alt=""></p>
<h4>数学解释</h4>
<p>假设链表中从头到环入口点的距离是 <code>a</code></p>
<p>环的长度是 <code>b + c（</code>其中 <code>b</code> 是从环入口点到 <code>slow</code> 与 <code>fast</code> 相遇点的距离<code>）</code></p>
<p><code>c</code> 是从相遇点再回到入口点的距离。</p>
<p>相遇时slow走过的路程是 a + b</p>
<p>相遇时fast走过的路程是 a + (b+ c) + b</p>
<p>又因为fast走的路程是slow的两倍所以 2(a + b) == a + (b+ c) + b -&gt; a = c</p>
<p>所以头到入口的距离就等于相遇点到入口的距离，这样的话让它们从头和快慢指针相遇的地点一起出发，等什么时候它们相遇那就什么时候遇到入口。</p>
<h4>快慢指针相遇的理解</h4>
<p><code>fast</code> 指针比 <code>slow</code> 指针更早进入环，并且在环内移动得更快。当 <code>slow</code> 进入环时，<code>fast</code> 就开始追赶它。由于 <code>fast</code> 每次走两步，而 <code>slow</code> 只走一步，这就好像 <code>fast</code> 在不断地逼近一个"静止"的目标。你可以把这情况类比为 <code>slow</code> 不动，而 <code>fast</code> 每次向前移动一步。在这种情况下，<code>fast</code> 会很快追上 <code>slow</code>。</p>
<p>因此，<code>slow</code> 并不需要走完整个环就会被 <code>fast</code> 追上。相遇是必然的，而 <code>slow</code> 没走完一整圈就和 <code>fast</code> 相遇的情况，正是因为 <code>fast</code> 在追击 <code>slow</code> 的过程中缩短了它们之间的距离。</p>
<h2>代码实现：</h2>
<pre><code>class Solution 
{
public:
    ListNode *detectCycle(ListNode *head) 
    {
        if(head == nullptr || head-&gt;next == nullptr)
        {
            return nullptr;
        }
        ListNode* fast = head;
        ListNode* slow = head;
        ListNode* Ptr = head;
        while(fast != nullptr &amp;&amp; fast-&gt;next != nullptr)
        {
           fast = fast-&gt;next-&gt;next;
           slow = slow-&gt;next;
           if(fast == slow)
           {
             while(Ptr != fast)
             {
                Ptr = Ptr-&gt;next;
                fast = fast-&gt;next;
             }
             return Ptr;
           }
        }
        return nullptr;
    }
};
</code></pre>
<p>
 <br>
</p>
<h2>题目：<a href="https://leetcode.cn/problems/find-the-duplicate-number/description/" title="寻找重复数">寻找重复数</a></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbb478387816f460999350593f3f072f2.png&amp;size=m" alt=""></p>
<h2>题目讲解：</h2>
<h4>数组该怎样链表化？</h4>
<p>你是否想过，数组中的每个元素其实可以看作链表中的一个节点？那么，这些节点该如何指向彼此呢？链表中的指向并不是随意的，它靠什么来决定呢？</p>
<p>答案就在数组的下标和元素之间的关系。下标永远是固定的，从 0 开始不变，而真正"变动"的是数组中的元素。每个元素的值决定了它指向的下一个位置。比如，数组的第一个元素是 1，它就会指向下标为 1 的位置。如果下标 1 的元素是 3，那么它再指向下标 3 的位置。如此循环下去，直到某个节点被指向两次，这就意味着找到了重复的数字。这样，你就把数组成功"链表化"了，利用这个方法轻松找出数组中的重复元素。</p>
<p>代码实现：</p>
<pre><code>class Solution 
{
public:
    int findDuplicate(vector&lt;int&gt;&amp; nums) 
    {
      int fast = 0;
      int slow = 0;

      do
      {
        slow = nums[slow];
        fast = nums[nums[fast]]; // 走两步
      }while(slow != fast);

      int tmp = 0;
      while(tmp != fast)
      {
       tmp = nums[tmp];
       fast = nums[fast];
      }
      return tmp;
    }
};
</code></pre>
<h2>题目：<a href="https://leetcode.cn/problems/happy-number/description/" title="快乐水">快乐水</a></h2>
<h2><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fdca9f5fe5fca42ff8341c21f5d7cac62.png&amp;size=m" alt=""></h2>
<h2>题目讲解：</h2>
<p>这个题目其实和之前的三道题思路类似。题目给出了两种情况：第一种是计算结果为 1，然后进入无限循环，循环的数字全是 1；第二种是永远算不出 1，进入一个与 1 无关的数字循环。这个循环可以被看作一个"环”，要么全是 1，要么是其他数。我们需要找到这个"环"的入口，也就是第一个 1 或者与 1 无关的数。</p>
<p>其实，这就是在找环的入口，完全可以用快慢指针来解决。快慢指针的核心思想是，一个走得快，一个走得慢，只要能满足这个条件，就可以称为快慢指针。在这里，我们让慢指针每次变化一次，快指针每次变化两次。如果它们相遇且值为 1，就返回 <code>true</code>，否则返回 <code>false</code>。</p>
<h2>代码实现：</h2>
<pre><code>class Solution 
{
public:
    int qwe(int number)
    {
        int sum = 0;
        int tmp = 0;
        while(number)
        {
          tmp = number % 10;
          sum += tmp * tmp;
          number = number / 10;
        }
        return sum;
    }
    bool isHappy(int n) 
    {
       int slow = n;
       int fast = n;
       do
       {
        slow = qwe(slow);
        fast = qwe(fast);
        fast = qwe(fast);
       }
       while(slow != fast);
      if(slow == 1)
      {
        return true;
      }
      return false;
    }
};
</code></pre>
<h2>题目：<a href="https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/" title="删除链表的倒数第N个元素">删除链表的倒数第N个元素</a></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6a3b69a38806462daa8c7ef9e3fe1e9b.png&amp;size=m" alt=""></p>
<h2>题目讲解：</h2>
<p>这道题可以先让快指针走n步然后再让慢指针和快指针一同走那这样快指针走到结尾那慢指针也就刚好移动到目标位置的前一个。</p>
<h2>代码实现：</h2>
<pre><code>class Solution 
{
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) 
    {
     ListNode* dummyHead = new ListNode(0 , head);
     dummyHead-&gt;next = head;
     ListNode* fast = dummyHead;
     ListNode* slow = dummyHead;
      for (int i = 0; i &lt;= n; ++i) 
    {
       fast = fast-&gt;next;
    }
     while(fast)
     {
        fast = fast-&gt;next;
        slow = slow-&gt;next;
     }
     slow-&gt;next = slow-&gt;next-&gt;next;
     return dummyHead-&gt;next;
    }
};
</code></pre>
<h2>这里定义哨兵位（虚拟头节点）的作用：</h2>
<h4><strong>为什么要用虚拟头节点（哨兵位）？</strong></h4>
<ul>
 <li>当我们需要删除链表中的某个节点时，通常需要访问待删除节点的前一个节点，因为删除操作涉及到将前一个节点的 <code>next</code> 指针重新指向待删除节点的下一个节点。</li>
</ul>
<h4>2. <strong>删除头节点的特殊情况</strong></h4>
<ul>
 <li>如果没有虚拟头节点，要删除头节点会比较麻烦，因为头节点前面没有其他节点，无法轻易访问它的前一个节点。因此，删除头节点通常需要特别处理，直接返回第二个节点来实现删除。</li>
</ul>
<h4>3. <strong>虚拟头节点解决了什么问题？</strong></h4>
<ul>
 <li>引入虚拟头节点后，即使是删除头节点的操作，我们也可以按照统一的方式处理。虚拟头节点的 <code>next</code> 指向实际的头节点，这样我们就能在删除任何节点时，不需要再为删除头节点编写额外的特殊逻辑了。</li>
</ul>
<h4>4. <strong>总结</strong></h4>
<ul>
 <li><strong>统一的删除逻辑</strong>：有了虚拟头节点后，删除头节点就变得和删除其他节点一样，不需要再单独处理特殊情况。</li>
 <li><strong>代码简洁性</strong>：使用虚拟头节点让代码更简洁，因为所有节点的删除操作都可以通过相同的逻辑来处理，无需特别关注链表的第一个节点。</li>
</ul>
<h2>题目：<a href="https://leetcode.cn/problems/middle-of-the-linked-list/description/" title="链表的中间节点">链表的中间节点</a></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4029a14d880c4f02820a4c8f2ffe1cc3.png&amp;size=m" alt=""></p>
<h2>题目讲解：</h2>
<p>这题比较easy，所以我就贴代码算了。</p>
<h2>代码实现：</h2>
<pre><code>class Solution 
{
public:
    ListNode* middleNode(ListNode* head) 
    {
     ListNode* fast = head;
     ListNode* slow = head;
     while(fast &amp;&amp; fast-&gt;next)
     {
        fast = fast-&gt;next-&gt;next;
        slow = slow-&gt;next;
     }
     return slow;
    }
};
</code></pre>
<h2>题目：<a href="https://leetcode.cn/problems/aMhZSa/description/" title="回文链表">回文链表</a></h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffc142ea681dc4a8b905b12650c9be543.png&amp;size=m" alt=""></p>
<h2>题目讲解：</h2>
<p>判断回文链表我们可以先利用快慢指针找到前部分的尾部然后然后再去反转后部分链表判断，然后再去判断它们的val是否一摸一样，我们唯一要注意的就是反转链表，</p>
<h2>代码实现：</h2>
<pre><code class="hljs">class Solution 
{
public:
//反转中间节点后的数据
ListNode* reverse(ListNode* Newhead)
{
ListNode* PPfast = Newhead;
ListNode* PPslow = nullptr;
 while(PPfast)
 {
    ListNode* next = PPfast-&gt;next;
    PPfast-&gt;next = PPslow;
    PPslow = PPfast;
    PPfast = next;
 }
 Newhead = PPslow;
 return Newhead;
}
    bool isPalindrome(ListNode* head) 
    {
      ListNode* dummyHead = new ListNode (0 , head);
      dummyHead-&gt;next = head;
      ListNode* fast = head;
      ListNode* slow = head;
      // 
      while(fast &amp;&amp; fast-&gt;next)
      {
        slow = slow-&gt;next;
        fast = fast-&gt;next-&gt;next;
      }
      fast = reverse(slow);
      slow = head;
      while(fast &amp;&amp; slow)
      {
        if(fast-&gt;val != slow-&gt;val)
        {
            return false;
        }
        fast = fast-&gt;next;
        slow = slow-&gt;next;
      }
      return true;
    }
};
</code></pre>
<p>原文链接: https://blog.csdn.net/m0_72625526/article/details/141569657</p>]]></description><guid isPermaLink="false">/archives/05f0a6a2-6fdb-4bdb-a94f-6f8dc6f4afc4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:39:39 GMT</pubDate></item><item><title><![CDATA[【排序算法】—— 归并排序]]></title><link>https://wiki.dgfygk.com/archives/8ad3241f-42cb-42e3-9afc-f7e1cf0edf1a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E3%80%91%E2%80%94%E2%80%94%20%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F&amp;url=/archives/8ad3241f-42cb-42e3-9afc-f7e1cf0edf1a" width="1" height="1" alt="" style="opacity:0;">
<p>归并排序时间复杂度O(NlongN)，空间复杂度O(N)，是一种稳定的排序，其次可以用来做<strong>外排序算法</strong>，即对磁盘(文件)上的数据进行排序。</p>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F">一、有序数组排序</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E6%8E%92%E5%BA%8F%E6%80%9D%E8%B7%AF">二、排序思路</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0">三、递归实现</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E9%9D%9E%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0">四、非递归实现</a></p>
<hr>
<p>
 <br>
</p>
<h2>一、有序数组排序</h2>
<p>要理解归并排序的核心逻辑我们得先看懂下面一个题：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4bd34b2c9d9242f3a50d018fbe4f9b72.png&amp;size=m" alt=""></p>
<p>刚接触这个题的时候，大家可能会想把他俩写到一个数组里面然后再写一个++排序算法++，这是一个比较容易想到也是比较++蛮力++的一种方法，但是这里有一个特点这两个数组是有序的。所以有一个很巧妙的方法。</p>
<p>使用两个变量分别记录他们的下标，并从零下标开始，比较他们下标对应下的值把小的那个先放进去新数组里面，然后把他的下标移到下一位。然后重复进行该操作，直到把其中的一个遍历完。
 <br>
 ++当然此时还没有完全排完序，当其中一组遍历完后，还有另一组剩余的的元素没有放在新数组内，因为无法确定那一组会先遍历完所以我们俩组都需要检查一遍，检查他们的下标并把剩余元素放在新数组内++。</p>
<pre><code class="cpp">#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;
#include&lt;assert.h&gt;
int main()
{
    int ar1[] = { 1,2,3,4 };
    int ar2[] = { 3,4,5,6,7 };
    int sz1 = sizeof(ar1)/sizeof(int), sz2 = sizeof(ar2) / sizeof(int);
    int* arr = (int*)malloc(sizeof(int) * (sz1 + sz2));
    assert(arr);
    int i = 0, j = 0, t = 0;
    while (i &lt; sz1 &amp;&amp; j &lt; sz2)
    {
        if (ar1[i] &lt; ar2[j])
            arr[t++] = ar1[i++];
        else
            arr[t++] = ar2[j++];
    }
    while (i &lt; sz1)
        arr[t++] = ar1[i++];
    while (j &lt; sz2)
        arr[t++] = ar2[j++];
    for (int i = 0; i &lt; sz1 + sz2; i++)
        printf("%d ", arr[i]);
    return 0;
}
</code></pre>
<h2>二、排序思路</h2>
<p>通过理解上面那个题，那么对于一个乱序的数组，我们可以把一分为二，先让两个小数组有序然后再用上面的方法合并。那么如何让这两个小数组有序呢，同样的可以把他们分别再拆开，变成四个小组，然后继续一直往下拆，直到拆到只有一个元素，那么它必然是有序的，最后进行进行一一合并，这整个的思路有点像++<strong>二叉树的后续遍历</strong>++。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6bd2897cc2db4a268625f90679ae3d6c.png&amp;size=m" alt=""></p>
<p>动图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa748343cd73b43229fe2c874ac8ef79f.gif&amp;size=m" alt=""></p>
<h2>三、递归实现</h2>
<p>在分析的过程中，我们就可以感受到使用递归可以很好的解决这个问题。++在做分割的时候，最好是选择从中间位置开始++ ，这样避免的递归深度太深。在处理递归问题的时候还要注意一个点，就是递归的结束条件，这里递归什么时候结束呢？通过上面的分析当数组只分割成单个元素的时候它必然是有序的，那么递归结束条件就是当分割的小数组只有一个元素的时候返回。</p>
<p>代码示例:</p>
<pre><code class="cpp">void _MergeSort(int* arr, int* tmp, int left, int right)
{
    if (left &gt;= right)
        return;
    int key = (left + right) / 2;
    int begin1 = left, end1 = key;
    int begin2 = key + 1, end2 = right;
    _MergeSort(arr, tmp, begin1, end1);
    _MergeSort(arr, tmp, begin2, end2);
    int i = left;
    while (begin1 &lt;= end1 &amp;&amp; begin2 &lt;= end2)
    {
        if (arr[begin1] &lt; arr[begin2])
            tmp[i++] = arr[begin1++];
        else
            tmp[i++] = arr[begin2++];
    }
    while (begin1 &lt;= end1)
        tmp[i++] = arr[begin1++];
    while (begin2 &lt;= end2)
        tmp[i++] = arr[begin2++];
    memcpy(arr + left, tmp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* arr, int size)//归并排序------递归
{
    int* tmp = (int*)malloc(sizeof(int) * size);
    assert(tmp);
    _MergeSort(arr, tmp, 0, size - 1);
    free(tmp);
}
</code></pre>
<h2>四、非递归实现</h2>
<p>把递归转为非递归可以防止函数栈针开的太多导致栈溢出。在把递归转为非递归时第一想到的应该是使用栈结构来辅助完成。但是对于这个排序使用栈来实现非递归还是比较复杂，也根本用不着。</p>
<p>思路：</p>
<p>gap：表示分割的每个小数组中储存的元素个数。</p>
<p>size：表示整个大数的长度</p>
<p>首先我们仿照广度优先的思想去合并，因为++<em>刚开始只有单个元素自己作为一个数组(即gap=1)的时候才有序，所以从最后一层开始两两合并成一个</em>++ ，那么接下来gap=2的小数组就变得有序，合并完gap=2的的数组后，同理gap=4的小数组变得有序，对它们进行两两合并，直到<strong>gap&gt;=size</strong>。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F46109f2080c74dd6ae0496010183ae85.png&amp;size=m" alt=""></p>
<p>代码示例:</p>
<pre><code class="cpp">void MergeSortNoF(int* arr, int sz)
{
    int* tmp = (int*)malloc(sizeof(int) * sz);
    assert(tmp);
    int gap = 1;
    while (gap &lt; sz)
    {
        for (int i = 0; i &lt; sz; i += gap * 2)
        {
            int j = i;
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + 2 * gap - 1;
            if (begin2 &gt;= sz)
                break;
            if (end2 &gt;= sz)
                end2 = sz - 1;
            while (begin1 &lt;= end1 &amp;&amp; begin2 &lt;= end2)
            {
                if (arr[begin1] &lt;= arr[begin2])
                    tmp[j++] = arr[begin1++];
                else
                    tmp[j++] = arr[begin2++];
            }
            while (begin1 &lt;= end1)
                tmp[j++] = arr[begin1++];
            while (begin2 &lt;= end2)
                tmp[j++] = arr[begin2++];
            memmove(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
        }
        gap*=2;
    }
}
</code></pre>
<p>细节：</p>
<p>无论是递归还是非递归都需要开辟一块空间tmp来储存合并后的元素，最后把tmp上的数据拷贝给原数组，使用memmove函数比较便捷。</p>
<p>区间越界问题：</p>
<p>(1)、[begin1, end1] [begin2, end2]</p>
<p>(2)、[begin1, end1] [begin2, end2]</p>
<p>(3)、[begin1, end1] [begin2, end2]</p>
<p>以上红色表示越界，这是越界可能出现的所有情况，观察发现出现(2)，(3)种情况的时候并不需要做合并，直接break;怎么写条件呢？因为end1越界begin2一定越界，所有用一个if(begin2&gt;=sz)就可以表示(2)，(3)种情况。</p>
<p>至于第一种情况，我们还是要合并的但需要调整end2为sz-1，所以有一下代码：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffb23c16c1244464eb09c9b9990b909ce.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/2302_80105876/article/details/140413102</p>]]></description><guid isPermaLink="false">/archives/8ad3241f-42cb-42e3-9afc-f7e1cf0edf1a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:39:26 GMT</pubDate></item><item><title><![CDATA[【数据结构】利用顺序表实现通讯录]]></title><link>https://wiki.dgfygk.com/archives/f96b087d-4cf6-40aa-b903-7d88fabd6984</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E3%80%91%E5%88%A9%E7%94%A8%E9%A1%BA%E5%BA%8F%E8%A1%A8%E5%AE%9E%E7%8E%B0%E9%80%9A%E8%AE%AF%E5%BD%95&amp;url=/archives/f96b087d-4cf6-40aa-b903-7d88fabd6984" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_4">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#_7">通讯录要求</a></li>
 <li><a href="https://wiki.dgfygk.com#_20">利用顺序表的现有功能</a></li>
 <li><a href="https://wiki.dgfygk.com#_94">代码呈现</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<h2>前言</h2>
<p>这篇文章实现的通讯录利用了笔者上一篇写的有关顺序表的应用 <a href="https://blog.csdn.net/2301_77954967/article/details/137360029?spm=1001.2014.3001.5502">https://blog.csdn.net/2301_77954967/article/details/137360029?spm=1001.2014.3001.5502</a>，需要用的朋友自行复制</p>
<h2>通讯录要求</h2>
<p>1)至少能够存储100个人的通讯信息
 <br>
 2)能够保存用户信息:名字、性别、年龄、电话、地址等
 <br>
 3)增加联系人信息
 <br>
 4)删除指定联系人
 <br>
 5)查找制定联系人
 <br>
 6)修改指定联系人
 <br>
 7)显示联系人信息
 <br>
 8)能够保存信息用于下次读取</p>
<hr>
<h2>利用顺序表的现有功能</h2>
<p>我们现在contact.h的头文件里建立如下结构体，</p>
<pre><code class="prism language-csharp">#define NAME_MAX 100
#define GENDER_MAX 10  //male female
#define TEL_MAX 20
#define ADDR_MAX 100

//定义联系人数据结构
//姓名 性别 年龄 电话 地址
typedef struct personInfo
{

    char name[NAME_MAX];
    char gender[GENDER_MAX];
    int age;
    char tel[TEL_MAX];
    char addr[ADDR_MAX];
}peoInfo;
</code></pre>
<p>我们可以把这个通讯录中的每个人的信息看成一个顺序表中每一个数组的数据，这样一来就只需要将这里的类型名 <strong></strong>peoInfo<strong></strong> 给重命名成 <strong></strong>SLDataType<strong></strong> 就行了，需要注意的是，这里是两个头文件，不仅需要放在<strong>同一目录</strong> 下，还需要在 <strong></strong>SeqList.h<strong></strong> 文件里附加上 <strong></strong>Contact.h<strong></strong> 的头文件名</p>
<pre><code class="prism language-csharp">#include"Contact.h"

typedef peoInfo SLDataType;//将proInfo改成SLDataType

typedef struct SeqList
{

    SLDataType* arr;
    int size;//有效数据个数
    int capacity;//空间大小
}SL;
</code></pre>
<p>再然后，如果后续的功能名仍用 <strong></strong>SL<strong></strong> 来命名会有些模糊，但是<strong>两个头文件之间不能互相引用</strong> ，那么这里我们可以回到 <strong></strong>Contact.h<strong></strong> 里，进行前置声明，</p>
<pre><code class="prism language-csharp">typedef struct SeqList Contact;//前置声明，用顺序表里第一个创立的结构体名SeqList，而不是SL
</code></pre>
<p>这样一来就是要进行方案功能的实现就行了，开始工作做好了，后面就会简单很多</p>
<pre><code class="prism language-csharp">//初始化通讯录
void InitContact(Contact* con);

//添加通讯录数据
void AddContact(Contact* con);

//删除通讯录数据
void DelContact(Contact* con);

//展示通讯录数据
void ShowContact(Contact* con);

//查找通讯录数据
void FindContact(Contact* con);

//修改通讯录数据
void ModifyContact(Contact* con);

//销毁通讯录数据
void DestroyContact(Contact* con);
</code></pre>
<p>假设我们上面几个方法都已经完善并测验好了，我们需要思考的如何使这些数据进行保存，方便下一次读取，这里就需要用到文件了</p>
<p>这里我们选择用二进制文件的方式读取和写入，原因是，这里我们利用了 <strong></strong>peoInfo<strong></strong> 这个现有的结构体，和 <strong></strong>SLPushBack<strong></strong> 这个现有的方法，不需要以文本文件格式化的形式读入文件，再花费功夫去调整文本文件的格式</p>
<hr>
<h2>代码呈现</h2>
<p><strong></strong>Contact.h<strong></strong></p>
<pre><code class="prism language-csharp">#pragma once//同一个文件不会被包含多次。这里所说的"同一个文件"是指物理上的一个文件，而不是指内容相同的两个文件

#define _CRT_SECURE_NO_WARNINGS  1
#pragma warning(disable:6031)
#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;
#include&lt;assert.h&gt;




#define NAME_MAX 100
#define GENDER_MAX 10  //male female
#define TEL_MAX 20
#define ADDR_MAX 100



//定义联系人数据结构
//姓名 性别 年龄 电话 地址
typedef struct personInfo
{

    char name[NAME_MAX];
    char gender[GENDER_MAX];
    int age;
    char tel[TEL_MAX];
    char addr[ADDR_MAX];
}peoInfo;


typedef struct SeqList Contact;//前置声明，用到顺序表里第一个创立的结构体名
//这里的通讯录实际上就是顺序表

//要用到顺序表相关的方法，对通讯录的操作实际就是对顺序表进行操作
// 
//通讯录相关的方法

//初始化通讯录
void InitContact(Contact* con);

//添加通讯录数据
void AddContact(Contact* con);

//删除通讯录数据
void DelContact(Contact* con);

//展示通讯录数据
void ShowContact(Contact* con);

//查找通讯录数据
void FindContact(Contact* con);

//修改通讯录数据
void ModifyContact(Contact* con);

//销毁通讯录数据
void DestroyContact(Contact* con);

//利用文件保存数据
//读取
void LoadContact(Contact* con);

//写入
void SaveContact(Contact* con);
</code></pre>
<p><strong></strong>SeqList.h<strong></strong></p>
<pre><code class="prism language-csharp">#pragma once
#define _CRT_SECURE_NO_WARNINGS  1
#pragma warning(disable:6031)
#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;
#include&lt;assert.h&gt;


#include"Contact.h"//头文件之间不能互相包含



typedef peoInfo SLDataType;//将proInfo改成SLDataType

typedef struct SeqList
{

    SLDataType* arr;
    int size;//有效数据个数
    int capacity;//空间大小
}SL;


//typedef struct SeqList SL;//将这个顺序表的类型名简化成SL


//顺序表初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestory(SL* ps);

//尾部插入
void SLPushBack(SL* ps, SLDataType x);
//头部插入
void SLPushFront(SL* ps, SLDataType x);

//尾部删除
void SLPopBack(SL* ps);
//头部删除
void SLPopFront(SL* ps);


//扩容
void SLCheckCapacity(SL* ps);
//打印
void SLPrint(SL s);

//在指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除指定位置的数据
void SLErase(SL* ps, int pos);

//查找
int SLFind(SL* ps, SLDataType x);
</code></pre>
<p><strong></strong>Contact.c<strong></strong></p>
<pre><code class="prism language-csharp">#include"Contact.h"
#include"SeqList.h"


//初始化通讯录
void InitContact(Contact* con)
{

    //实际上要进行的是顺序表的初始化
    //con-&gt;arr = NULL;
    //con-&gt;capacity = con-&gt;size = 0;
    SLInit(con);
    LoadContact(con);
}


//销毁通讯录数据
void DestroyContact(Contact* con)
{

    SaveContact(con);
    SLDestory(con);
}


//添加通讯录数据
void AddContact(Contact* con)
{

    //获取用户输入的内容一个结构体
    peoInfo info;
    printf("请输入要添加的联系人姓名\n");
    scanf("%s", info.name);

    printf("请输入要添加的联系人性别\n");
    scanf("%s", info.gender);

    printf("请输入要添加的联系人年龄\n");
    scanf("%d", &amp;info.age);//数组名即首元素的地址，所以不用取地址，创建时字符是以数组的形式创建的

    printf("请输入要添加的联系人电话\n");
    scanf("%s", info.tel);

    printf("请输入要添加的联系人地址\n");
    scanf("%s", info.addr);

    //往通讯录中添加联系人数据
    SLPushBack(con, info);//顺序表中已有功能的复用
}


//查找通讯录数据（依靠名字）
FindByName(Contact* con, char name[])
{

    for (int i = 0; i &lt; con-&gt;size; i++)
    {

        if (0 == strcmp(con-&gt;arr[i].name, name))
        {

            //找到了
            return i;
        }
    }
    //没有找到
    return -1;
}


//通讯录删除数据
void DelContact(Contact* con)
{

    //要删除的数据必须要存在，才能执行删除操作
    //查找
    char name[NAME_MAX];
    printf("请输入要删除的联系人名字:\n");
    scanf("%s", name);

    int find = FindByName(con, name);
    if (find &lt; 0)
    {

        printf("要删除的联系人数据不存在!\n");
        return;
    }
    //要删除的联系人数据存在从 -&gt; 知道了要删除联系人的下标
    SLErase(con, find);
    printf("删除成功！\n");
}


//展示通讯录数据
void ShowContact(Contact* con)
{

    //打印表头
    printf("%5s %5s %5s %5s %5s\n", "姓名", "性别", "年龄", "电话", "地址");
    //遍历通讯录，按照格式打印每个联系人数据
    for (int i = 0; i &lt; con-&gt;size; i++)
    {

        printf("%5s %5s %5d %5s %5s\n",
            con-&gt;arr[i].name,
            con-&gt;arr[i].gender,
            con-&gt;arr[i].age,
            con-&gt;arr[i].tel,
            con-&gt;arr[i].addr
            );
    }
}

//修改通讯录数据
void ModifyContact(Contact* con)
{

    char name[NAME_MAX];
    printf("请输入要修改的数据\n");
    scanf("%s", name);
    int find = FindByName(con,name);
    if (find &lt; 0)
    {

        printf("要修改的联系人数据不存在\n");
        return;
    }
    //直接修改
    printf("请输入新的姓名：\n");
    scanf("%s",con-&gt;arr[find].name);
    printf("请输入新的性别：\n");
    scanf("%s",con-&gt;arr[find].gender);
    printf("请输入新的年龄：\n");
    scanf("%d",&amp;con-&gt;arr[find].age);
    printf("请输入新的电话：\n");
    scanf("%s",con-&gt;arr[find].tel);
    printf("请输入新的地址：\n");
    scanf("%s",con-&gt;arr[find].addr);

    printf("修改成功！\n");
}

//查找通讯录数据
void FindContact(Contact* con)
{

    char name[NAME_MAX];
    printf("请输入要查找的联系人：\n");
    scanf("%s", name);
    int find = FindByName(con,name);
    if (find &lt; 0)
    {

        printf("要查找的联系人数据不存在\n");
        return;
    }
    printf("%5s %5s %5s %5s %5s\n", "姓名", "性别", "年龄", "电话", "地址");
    printf("%5s %5s %5d %5s %5s\n",
        con-&gt;arr[find].name,
        con-&gt;arr[find].gender,
        con-&gt;arr[find].age,
        con-&gt;arr[find].tel,
        con-&gt;arr[find].addr
    );

}


//利用文件保存数据
//读取
void LoadContact(Contact* con)
{

    FILE* pf = fopen("contact.txt", "rb");
    if (pf == NULL)
    {

        perror("fopen error!\n");
        return;
    }

    //循环读取文件数据
    peoInfo info;
    while (fread(&amp;info, sizeof(peoInfo), 1, pf))
    {

        SLPushBack(con, info);
    }

    fclose(pf);
    pf = NULL;

    printf("历史数据写入成功!\n");
}


//写入
void SaveContact(Contact* con)
{

    FILE* pf = fopen("contact.txt", "wb");
    if (pf == NULL)
    {

        perror("fopen error!\n");
        return;
    }
    for (int i = 0; i &lt; con-&gt;size; i++)
    {

        fwrite(con-&gt;arr + i, sizeof(peoInfo), 1, pf);
    }

    fclose(pf);
    pf = NULL;


    printf("通讯录数据保存成功!\n");
}
</code></pre>
<p><strong></strong>SeqList.c<strong></strong></p>
<pre><code class="prism language-csharp">#include"SeqList.h"

//初始化顺序表
void SLInit(SL* ps)
{

    ps-&gt;arr = NULL;
    ps-&gt;size = ps-&gt;capacity = 0;

}

//顺序表的销毁
void SLDestory(SL* ps)
{

    if (ps-&gt;arr)
    {

        free(ps-&gt;arr);
    }
    ps-&gt;arr = NULL;
    ps-&gt;size = ps-&gt;capacity = 0;
}

//扩容
void SLCheckCapacity(SL* ps)
{

    //插入数据前看看空间够不够
    if (ps-&gt;size == ps-&gt;capacity)
    {

        //扩容，申请空间
        int newCapacity = ps-&gt;capacity == 0 ? 4 : 2 * ps-&gt;capacity;//三目表达式
        SLDataType* tmp = (SLDataType*)realloc(ps-&gt;arr, newCapacity * 2 * sizeof(SLDataType));
        if (tmp == NULL)
        {

            perror("realloc fail!");
            exit(1);
        }
        //空间申请成功
        ps-&gt;arr = tmp;
        ps-&gt;capacity = newCapacity;
    }
}

//打印
void SLPrint(SL s)
{

    for (int i = 0; i &lt; s.size; i++)
    {

        printf("%d ", s.arr[i]);
    }
    printf("\n");
}


//在尾部插入
void SLPushBack(SL* ps, SLDataType x)
{

    //温柔的解决方式
    //if(ps == NULL)
    //{

    //  return 0;
    //}
    assert(ps);//

    //扩容
    SLCheckCapacity(ps);

    //ps-&gt;arr[ps-&gt;size] = x;
    //++ps-&gt;size;
    ps-&gt;arr[ps-&gt;size++] = x;
}

//头插
void SLPushFront(SL* ps, SLDataType x)
{

    //扩容
    SLCheckCapacity(ps);

    //先将顺序表中已有的数据整体往后移动一位
    for (int i = ps-&gt;size; i &gt; 0; i--)
    {

        ps-&gt;arr[i] = ps-&gt;arr[i - 1];
    }
    ps-&gt;arr[0] = x;
    ps-&gt;size++;
}

//尾部删除
void SLPopBack(SL* ps)
{

    assert(ps);
    assert(ps-&gt;size);
    //顺序表不为空
    --ps-&gt;size;
}

//头部删除
void SLPopFront(SL* ps)
{

    assert(ps);
    assert(ps-&gt;size);
    //顺序表不为空
    //数据整体王前移动一位
    for (int i = 0; i &lt; ps-&gt;size - 1; i++)
    {

        ps-&gt;arr[i] = ps-&gt;arr[i + 1];
    }
    ps-&gt;size--;
}

//在指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{

    assert(ps);
    assert(pos &gt;= 0 &amp;&amp; pos &lt;= ps-&gt;size);

    //扩容，判断空间够不够
    SLCheckCapacity(ps);
    //让pos之后及其自身往后移动一位
    for (int i = ps-&gt;size; i &gt; pos; i--)
    {

        ps-&gt;arr[i] = ps-&gt;arr[i - 1];
    }
    ps-&gt;size++;
    ps-&gt;arr[pos] = x;
}

//删除指定位置的数据
void SLErase(SL* ps, int pos)
{

    assert(ps);
    assert(pos &gt;= 0 &amp;&amp; pos &lt; ps-&gt;size);
    for (int i = pos; i &lt; ps-&gt;size - 1; i++)
    {

        ps-&gt;arr[i] = ps-&gt;arr[i + 1];
    }
    ps-&gt;size--;
}

//查找
//int SLFind(SL* ps, SLDataType x)
//{

//  assert(ps);
//  for (int i = 0; i &lt; ps-&gt;size; i++)
//  {

//      if (ps-&gt;arr[i] == x)
//      {

//          return i;
//      }
//  }
//  return -1;
//}
</code></pre>
<p><strong></strong>test.c<strong></strong></p>
<pre><code class="prism language-csharp">#include"Contact.h"
#include"SeqList.h"


通讯录测试
//void ContactTest01()
//{

//  Contact con;//创建通讯录对象
//  InitContact(&amp;con);
//
//  //测试添加数据功能
//  AddContact(&amp;con);
//  AddContact(&amp;con);
//
//  //测试删除数据功能
//  DelContact(&amp;con);
//
//  //测试打印功能
//  ShowContact(&amp;con);
//  
//  //销毁通讯录数据
//  DestroyContact(&amp;con);
//
//}
//
//int main1()
//{

//  ContactTest01();
//  return 0;
//}

void menu()
{

    printf("*******************通讯录******************\n");
    printf("*****1.增加联系人  ******  2.删除联系人****\n");
    printf("*****3.修改联系人  ******  4.查找联系人****\n");
    printf("*****5.展示联系人  ******  0.  退  出  ****\n");
    printf("*******************************************\n");
}
int main()
{

    int op = -1;
    Contact con;
    InitContact(&amp;con);

    do
    {

        menu();
        printf("请选择您的操作：\n");
        scanf("%d", &amp;op);

        //要根据对应的op执行不同的操作
        switch (op)
        {

        case 1:
            AddContact(&amp;con);
            break;
        case 2:
            DelContact(&amp;con);
            break;
        case 3:
            ModifyContact(&amp;con);
            break;
        case 4:
            FindContact(&amp;con);
            break;
        case 5:
            ShowContact(&amp;con);
            break;
        case 0:
            op = 0;
            printf("退出通讯录中......\n");
            break;
        default:
            printf("选择错误!请重新选择\n");
            break;

        }
    }while(op != 0);
    printf("退出成功\n");


    DestroyContact(&amp;con);

    return 0;
}
</code></pre>
<p>原文链接: https://blog.csdn.net/2301_77954967/article/details/137407676</p>]]></description><guid isPermaLink="false">/archives/f96b087d-4cf6-40aa-b903-7d88fabd6984</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:39:12 GMT</pubDate></item><item><title><![CDATA[数据结构（复杂度）]]></title><link>https://wiki.dgfygk.com/archives/c42cddbd-3a79-4d3a-9467-b6a5ca7dc683</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%EF%BC%88%E5%A4%8D%E6%9D%82%E5%BA%A6%EF%BC%89&amp;url=/archives/c42cddbd-3a79-4d3a-9467-b6a5ca7dc683" width="1" height="1" alt="" style="opacity:0;">
<h2>复杂度</h2>
<p>算法在编写成可执行程序后，运⾏时需要耗费时间资源和空间(内存)资源。因此衡量⼀个算法的好 坏，⼀般是从时间和空间两个维度来衡量的，即<strong>时间复杂度</strong> 和<strong>空间复杂度</strong>。</p>
<p>时间复杂度主要衡量⼀个算法的运⾏快慢，⽽空间复杂度主要衡量⼀个算法运⾏所需要的额外空间。</p>
<p>在计算机发展的早期，计算机的存储容量很⼩。所以对空间复杂度很是在乎。但是经过计算机⾏业的迅速发展，计算机的存储容量已经达到了很⾼的程度。所以我们如今已经不需要再特别关注⼀个算法的空间复杂度。</p>
<h2>时间复杂度</h2>
<p><strong>定义</strong>：在计算机科学中，算法的时间复杂度是⼀个函数式T(N)，它定量描述了该算法的运⾏时间。时间复杂度是衡量程序的时间效率，那么为什么不去计算程序的运⾏时间呢？</p>
<ol>
 <li>
  <p>因为程序运⾏时间和编译环境和运⾏机器的配置都有关系，⽐如同⼀个算法程序，⽤⼀个⽼编译 器进⾏编译和新编译器编译，在同样机器下运⾏时间不同。</p>
 </li>
 <li>
  <p>同⼀个算法程序，⽤⼀个⽼低配置机器和新⾼配置机器，运⾏时间也不同。</p>
 </li>
 <li>
  <p>并且时间只能程序写好后测试，不能写程序前通过理论思想计算评估。</p>
 </li>
</ol>
<p>下面我们来看个案例：</p>
<pre><code class="cs">// 请计算⼀下Func1中++count语句总共执⾏了多少
次？ 
void Func1(int N) 
{ 
 int count = 0; 
 for (int i = 0; i &lt; N ; ++ i) 
 { 
 for (int j = 0; j &lt; N ; ++ j) 
 { 
 ++count; 
 } 
 } 
 for (int k = 0; k &lt; 2 * N ; ++ k) 
 { 
 ++count; 
 } 
 int M = 10; 
 while (M--) 
 { 
 ++count; 
 } 
}
</code></pre>
<p>++<strong>Func1执⾏的基本操作次数：</strong>++</p>
<p><strong>T (N) = N^2+2∗N+10</strong></p>
<p><strong>• N=10 T(N)=130</strong></p>
<p><strong>• N=100 T(N)=10210</strong></p>
<p><strong>• N=1000 T(N)=1002010</strong></p>
<p>通过对N取值分析，对结果影响最⼤的⼀项是N^2。</p>
<p>实际中我们计算时间复杂度时，计算的也不是程序的精确的执⾏次数，精确执⾏次数计算起来还是很⿇烦的(不同的⼀句程序代码，编译出的指令条数都是不⼀样的)，计算出精确的执⾏次数意义也不⼤， 因为我么计算时间复杂度只是想⽐较算法程序的增⻓量级，也就是++当N不断变⼤时T(N)的差别++ ，上⾯我们已经看到了当N不断变⼤时常数和低阶项对结果的影响很⼩，所以我们只需要计算<strong>程序能代表增⻓量级的⼤概执⾏次数</strong> ，复杂度的表⽰通常使⽤<strong>⼤O的渐进表⽰法</strong>。</p>
<h3>大O渐进表示法</h3>
<p><strong>⼤O符号（Big O notation）：是⽤于描述函数渐进⾏为的数学符号。</strong></p>
<p>推导大O阶的规则：</p>
<ol>
 <li>
  <p>时间复杂度函数式T(N)中，只保留<strong>最⾼阶项</strong> ，去掉那些低阶项，因为当N不断变⼤时，低阶项对结果影响<strong>越来越⼩</strong> ，当N<em>⽆穷⼤</em>时，就可以忽略不计了。</p>
 </li>
 <li>
  <p>如果最⾼阶项存在且不是1，则去除这个项⽬的<strong>常数系数</strong> ，因为当N不断变⼤，这个系数对结果影响<strong>越来越⼩</strong>，当N⽆穷⼤时，就可以忽略不计了。</p>
 </li>
 <li>
  <p>T(N)中如果没有N相关的项⽬，<strong>只有常数项</strong>，⽤常数1取代所有加法常数。</p>
 </li>
</ol>
<p>通过以上⽅法，可以得到<strong>Func1</strong> 的时间复杂度为：<strong>O(N^2)</strong>。</p>
<h3>时间复杂度案例分析</h3>
<h4>案例一：</h4>
<pre><code class="cs">// 计算Func2的时间复杂度？ 
void Func2(int N) 
{ 
 int count = 0; 
 for (int k = 0; k &lt; 2 * N ; ++ k) 
 { 
 ++count; 
 } 
 int M = 10; 
 while (M--) 
 { 
 ++count; 
 } 
 printf("%d\n", count); 
} 
</code></pre>
<p><strong>Func2执⾏的基本操作次数</strong>：</p>
<p><em>F(N) = 2N + 10</em></p>
<p>根据推导规则第3条得出</p>
<p><strong>Func2</strong> 的时间复杂度为：<strong>O(N)。</strong></p>
<h4>案例二：</h4>
<pre><code class="cs">// 计算Func3的时间复杂度？ 
void Func3(int N, int M) 
{ 
 int count = 0; 
 for (int k = 0; k &lt; M; ++ k) 
 { 
 ++count; 
 } 
 for (int k = 0; k &lt; N ; ++ 
k) 
 { 
 ++count; 
 } 
 printf("%d\n", count); 
}
</code></pre>
<p><strong>Func3执⾏的基本操作次数</strong>：</p>
<p><em>F (N) = M + N</em></p>
<p>因此：<strong>Func3</strong> 的时间复杂度为：<strong>O(M+N)</strong></p>
<h4>案例三：</h4>
<pre><code class="cs">// 计算Func4的时间复杂度？ 
void Func4(int N) 
{ 
 int count = 0; 
 for (int k = 0; k &lt; 100; ++ k) 
 { 
 ++count; 
 } 
 printf("%d\n", count); 
}
</code></pre>
<p><strong>Func4执⾏的基本操作次数</strong>：</p>
<p><em>F (N) = 100</em></p>
<p>根据推导规则第1条得出</p>
<p><strong>Func4的时间复杂度为</strong> ：<strong>O(1)</strong></p>
<h4><strong>案例四：</strong></h4>
<pre><code class="cs">// 计算strchr的时间复杂度？ 
const char * strchr ( const char 
* str, int character)
{
 const char* p_begin = s;
 while (*p_begin != character)
 {
 if (*p_begin == '\0')
 return NULL;
 p_begin++;
}
 return p_begin;
}
</code></pre>
<p><strong>strchr执⾏的基本操作次数</strong>：</p>
<p>1）若要查找的字符在字符串第⼀个位置，则：F (N) = 1</p>
<p>2）若要查找的字符在字符串最后的⼀个位置， 则：F (N) = N</p>
<p>3）若要查找的字符在字符串中间位置，则：F (N) = N/2</p>
<p>因此：strchr的时间复杂度分为：</p>
<p><strong>最好情况：O(1)</strong></p>
<p><strong>最坏情况：O(N)</strong></p>
<p><strong>平均情况：O(N)</strong></p>
<p><strong>小结：</strong></p>
<p>通过上⾯我们会发现，有些算法的时间复杂度存在最好、平均和最坏情况。</p>
<p>最坏情况：任意输⼊规模的最⼤运⾏次数(上界)</p>
<p>平均情况：任意输⼊规模的期望运⾏次数</p>
<p>最好情况：任意输⼊规模的最⼩运⾏次数(下界)</p>
<p>⼤O的渐进表⽰法在实际中⼀般情况关注的是算法的<strong>上界</strong> ，也就是<strong>最坏</strong> 运⾏情况。</p>
<hr>
<h4>案例五：</h4>
<pre><code class="cs">// 计算BubbleSort的时间复杂度？ 
void BubbleSort(int* a, int n) 
{ 
 assert(a); 
 for (size_t end = n; end &gt; 0; --end) 
 { 
 int exchange = 0; 
 for (size_t i = 1; i &lt; end; ++i) 
 { 
 if (a[i-1] &gt; a[i]) 
 { 
 Swap(&amp;a[i-1], &amp;a[i]); 
 exchange = 1; 
 } 
 } 
 if (exchange == 0) 
 break; 
 } 
}
</code></pre>
<p><strong>BubbleSort执⾏的基本操作次数</strong>：</p>
<p>1）若数组有序，则：F (N) = N</p>
<p>2）若数组有序且为降序，则：F (N) = （N∗(N + 1)）/2</p>
<p>3）若要查找的字符在字符串中间位置，则为2）中情况的1/2.</p>
<p>因此：BubbleSort的时间复杂度取最差情况为：O(N^2).</p>
<h4>案例六：</h4>
<pre><code class="cs">void func5(int n)
{
 int cnt = 1;
 while (cnt &lt; n)
 {
 cnt *= 2;
 }
}
</code></pre>
<p>当n=2时，执⾏次数为1</p>
<p>当n=4时，执⾏次数为2</p>
<p>当n=16时，执⾏次数为4</p>
<p>假设执⾏次数为x ，则2^x = n</p>
<p>因此执⾏次数：x = log n</p>
<p>因此：<strong>func5的时间复杂度取最差情况为： O(log2 n)</strong></p>
<p>*特别的，当n接近⽆穷⼤时，<strong>底数的⼤⼩</strong> 对结果影响不⼤。因此，⼀般情况下不管底数是多少都可以省略不写，即可以表⽰为<strong>log n 。</strong></p>
<h4><strong>案例七：</strong></h4>
<pre><code class="cs">// 计算阶乘递归Fac的时间复杂度？ 
long long Fac(size_t N) 
{ 
 if(0 == N) 
 return 1; 
 return Fac(N-1)*N; 
}
</code></pre>
<p>调⽤⼀次Fac函数的时间复杂度为<strong>O(1)</strong></p>
<p>⽽在Fac函数中，存在n次递归调⽤Fac函数</p>
<p>因此：</p>
<p>阶乘递归的时间复杂度为：<strong>O(n)。</strong></p>
<hr>
<h2>空间复杂度</h2>
<p>空间复杂度也是⼀个<strong>数学表达式</strong> ，是对⼀个算法在运⾏过程中因为算法的需要<strong>额外临时开辟</strong>的空间。空间复杂度不是程序占⽤了多少bytes的空间，因为常规情况每个对象⼤⼩差异不会很⼤，所以空间复杂度算的是变量的个数。</p>
<p>空间复杂度计算规则基本跟实践复杂度类似，也使⽤⼤O渐进表⽰法。</p>
<p>注意：函数运⾏时所需要的栈空间(++存储参数、局部变量、⼀些寄存器信息等++ )在<strong>编译期间</strong> 已经确定好了，因此<strong>空间复杂度主要通过函数在运⾏时候显式申请的额外空间来确定。</strong></p>
<h3><strong>空间复杂度的计算示例</strong></h3>
<h4><strong>示例一：</strong></h4>
<pre><code class="cs">// 计算BubbleSort的时间复杂度？ 
void BubbleSort(int* a, int n) 
{ 
 assert(a); 
 for (size_t end = n; end &gt; 0; --end) 
 { 
 int exchange = 0; 
 for (size_t i = 1; i &lt; end; ++i) 
 { 
 if (a[i-1] &gt; a[i]) 
 { 
 Swap(&amp;a[i-1], &amp;a[i]); 
 exchange = 1; 
 } 
 } 
 if (exchange == 0) 
 break; 
 } 
}
</code></pre>
<p>函数栈帧在编译期间已经确定好了， 只需要关注函数在运⾏时额外申请的空间。</p>
<p>BubbleSort额外申请的空间有exchange等有限个局部变量，使⽤了++常数++个额外空间</p>
<p>因此空间复杂度为：<strong>O(1)</strong></p>
<h4><strong>示例二：</strong></h4>
<pre><code class="cs">// 计算阶乘递归Fac的空间复杂度？ 
long long Fac(size_t N) 
{ 
 if(N == 0) 
 return 1; 

 return Fac(N-1)*N; 
}
</code></pre>
<p>Fac递归调⽤了N次，额外开辟了N个函数栈帧， 每个栈帧使⽤了常数个空间</p>
<p>因此空间复杂度为：<strong>O(N)</strong></p>
<hr>
<h2>常见复杂度对比</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F06ad825737b245f68b8b38aabdddecfc.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6fd11150dbd24a1787ed262f935ed611.png&amp;size=m" alt=""></p>
<h2>小结</h2>
<p>以上便是我对复杂度的分享，不是高手，但是有颗成为高手的心态，欢迎各位在评论区提出问题，纠正不足。感谢观看！</p>
<p>原文链接: https://blog.csdn.net/2406_83947720/article/details/140345592</p>]]></description><guid isPermaLink="false">/archives/c42cddbd-3a79-4d3a-9467-b6a5ca7dc683</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 08:39:01 GMT</pubDate></item><item><title><![CDATA[MySQl索引事务（B树）]]></title><link>https://wiki.dgfygk.com/archives/3a9f32c0-5914-4aad-b1be-5d2fb63c0827</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MySQl%E7%B4%A2%E5%BC%95%E4%BA%8B%E5%8A%A1%EF%BC%88B%E6%A0%91%EF%BC%89&amp;url=/archives/3a9f32c0-5914-4aad-b1be-5d2fb63c0827" width="1" height="1" alt="" style="opacity:0;">
<p>目标：</p>
<p><strong>索引</strong></p>
<p><strong>事务</strong></p>
<h2>1.索引</h2>
<h3>1.1概念</h3>
<p>索引是一种特殊的文件，包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引，并指定索引的类型，各类索引有各自的数据结构实现。</p>
<h3>1.2作用</h3>
<blockquote>
 <ul>
  <li><strong>数据库中的表、数据、索引之间的关系，类似于书架上的图书、书籍内容和书籍目录的关系。</strong></li>
  <li><strong>索引所起的作用类似书籍目录，可用于快速定位、检索数据。</strong></li>
  <li><strong>索引对于提高数据库的性能有很大的帮助。</strong></li>
 </ul>
</blockquote>
<h3>1.3使用场景</h3>
<p>要考虑数据库表的某一列或者几列创建索引，时要考虑几点：</p>
<blockquote>
 <ul>
  <li>数据量较大，且经常对这一些列进行条件查询。</li>
  <li>该数据库的插入操作，及对这些列的修改操作频率较低。</li>
  <li>索引会占用额外的磁盘空间。</li>
 </ul>
</blockquote>
<p>满足以上条件时，考虑对表中的这些字段创建索引，以提高查询效率。
 <br>
 反之，如果非条件查询列，或经常做插入、修改操作，或磁盘空间不足时，不考虑创建索引。</p>
<h3><strong>1.4</strong><strong>使用</strong></h3>
<p><strong>创建主键约束（PRIMARY KEY）、唯一约束（UNIQUE）、外键约束（FOREIGN KEY）时，会自动创建对应列的索引。</strong></p>
<p>
 <br>
</p>
<p>相关操作：</p>
<h4>查看索引</h4>
<pre><code>show index from 表名;
</code></pre>
<p>案例：查看学生表已有的索引</p>
<pre><code>show index from student;
</code></pre>
<h4>创建索引</h4>
<p>对于非主键、非唯一约束、非外键的字段，可以创建普通索引</p>
<pre><code class="java">create index 索引名 on 表名(字段名);
</code></pre>
<p>案例：创建班级表中， name 字段的索引</p>
<pre><code class="java">create index idx_classes_name on classes(name);
</code></pre>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6a8a7c9ad6e540128b8969d18fe1b5d5.png&amp;size=m" alt=""></h4>
<h4>删除索引</h4>
<pre><code class="java">drop index 索引名 on 表名;
</code></pre>
<p>案例：删除班级表中 name 字段的索引</p>
<pre><code class="java">drop index idx_classes_name on classes;
</code></pre>
<h4>重要</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F91a32b833e324b14bd6324282ec54449.png&amp;size=m" alt=""></p>
<h3>1.5 演示</h3>
<p>接下来我们演示一遍主键，外键，unique 自动产生索引。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9ad0eef0717141ffa6675cbdb529250f.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0546c70ff6f7464b81518142fcddd518.png&amp;size=m" alt=""></p>
<p>unique唯一</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa349294a29454401bb44444f55ae4f40.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd69cd3adbd9d479f817fe06dfd08c63b.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <h2>B树和B+树</h2>
 </li>
</ol>
<h3>2.1 B树</h3>
<p>为什么不用哈希表和二叉树，他们都是查找效率较高的？</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F367bb3c93d9b421788947003c2eb8149.png&amp;size=m" alt=""></p>
<p>B树的结构图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F16c08a8caf864904b4c906a369b9c603.png&amp;size=m" alt=""></p>
<p>但是：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcceedc7b788b414fb4e8420903d2cfba.png&amp;size=m" alt=""></p>
<h3>2.2B+树</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7d54d462917f4c28a6290d319564c86f.png&amp;size=m" alt=""></p>
<p>两树不同的地方</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F51ca3a0d15424aba82a212588fa1fa86.png&amp;size=m" alt=""></p>
<h3>总结：</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Feb5661bdfca344b181f4bcbb65474bbc.png&amp;size=m" alt=""></p>
<h2>3<strong>.</strong><strong>事务</strong></h2>
<h3>3.1为什么使用事务</h3>
<p>假如在执行以上第一句 SQL 时，出现网络错误，或是数据库挂掉了，阿里巴巴的账户会减少 2000 ，但是用户账户上就没有了增加的金额。
 <br>
 解决方案：使用事务来控制，保证以上两句 SQL 要么全部执行成功，要么全部执行失败。
 <br>
 <strong>也称为"回滚”</strong>
 <br>
 <strong>回滚==》执行到一半而失败，就会重新执行事务</strong></p>
<h3>3.2事务的概念</h3>
<p>事务指逻辑上的一组操作，组成这组操作的各个单元，要么全部成功，要么全部失败。
 <br>
 在不同的环境中，都可以有事务。对应在数据库中，就是数据库事务。</p>
<h3><strong>3.3</strong><strong>使用</strong></h3>
<p>格式：</p>
<blockquote>
 <p>（ 1 ）开启事务： start transaction;
  <br>
  （ 2 ）执行多条 SQL 语句
  <br>
  （ 3 ）回滚或提交： rollback/commit;</p>
</blockquote>
<p><strong>说明：rollback即是全部失败，commit即是全部成功。</strong></p>
<p>事务的四大特性</p>
<h3>原子性</h3>
<p>解释：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc8a501a15f124a748e6a41ee8bd7c36b.png&amp;size=m" alt=""></p>
<h3>一致性</h3>
<p>解释：</p>
<h3><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3c64efcdbd0548c8827c1bdc54211eda.png&amp;size=m" alt=""></h3>
<h3><strong>持久性</strong></h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcd6b3d897e724937897e14c6d7806755.png&amp;size=m" alt=""></p>
<h3>隔离性 （重点）</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe71e923554be42e9a02e96c1caed3380.png&amp;size=m" alt=""></p>
<p><strong>但是并发也会产生新的问题</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Faecff98ee55343adb2b26783ab1cfc97.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd117ac67d9b748a6a6cb6a969d6ee2c5.png&amp;size=m" alt=""></p>
<h4>四个隔离级别</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F19e67f7ddd3949508bb9b6149bb670f2.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F794d66edc0df432a96f14ea14b35bfcb.png&amp;size=m" alt=""></p>
<p>好了，今天就到这里了，感谢观看。</p>
<p>原文链接: https://blog.csdn.net/chendemingxxx/article/details/142359664</p>]]></description><guid isPermaLink="false">/archives/3a9f32c0-5914-4aad-b1be-5d2fb63c0827</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Sun, 29 Sep 2024 08:38:36 GMT</pubDate></item><item><title><![CDATA[深入解析HDFS：定义、架构、原理、应用场景及常用命令]]></title><link>https://wiki.dgfygk.com/archives/2f854ebf-43ec-49e7-a3bc-d41cdaa9da2a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90HDFS%EF%BC%9A%E5%AE%9A%E4%B9%89%E3%80%81%E6%9E%B6%E6%9E%84%E3%80%81%E5%8E%9F%E7%90%86%E3%80%81%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%E5%8F%8A%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4&amp;url=/archives/2f854ebf-43ec-49e7-a3bc-d41cdaa9da2a" width="1" height="1" alt="" style="opacity:0;">
<h2>引言</h2>
<p>Hadoop分布式文件系统（HDFS，Hadoop Distributed File System）是Hadoop框架的核心组件之一，它提供了高可靠性、高可用性和高吞吐量的大规模数据存储和管理能力。本文将从HDFS的定义、架构、工作原理、应用场景以及常用命令等多个方面进行详细探讨，帮助读者全面深入地了解HDFS。</p>
<h2><strong>1. HDFS</strong> <strong>的定义</strong></h2>
<h3><strong>1.1</strong> <strong>什么是HDFS</strong></h3>
<p>HDFS是Hadoop生态系统中的一个分布式文件系统，旨在在集群的廉价硬件上可靠地存储大数据集。HDFS设计为高容错，并为高吞吐量数据访问而优化，适用于在商用硬件上运行的大数据应用。</p>
<h3><strong>1.2 HDFS</strong> <strong>的历史背景</strong></h3>
<p>HDFS的灵感来自于Google文件系统（GFS），由Apache软件基金会的Hadoop项目团队开发。2006年，Doug Cutting和Mike Cafarella基于Google的GFS论文，开始开发HDFS，成为Hadoop框架的基础组件之一。</p>
<h3><strong>1.3 HDFS</strong> <strong>的优点</strong></h3>
<ul>
 <li><strong>高容错性</strong>：数据通过副本机制存储在多个节点上，确保在硬件故障时数据的高可用性。</li>
 <li><strong>高吞吐量</strong>：通过批量处理大数据，HDFS优化了数据的读写速度。</li>
 <li><strong>可扩展性</strong>：通过添加节点，可以轻松扩展HDFS的存储容量和计算能力。</li>
 <li><strong>可靠性</strong>：通过分布式架构和数据冗余，确保数据在系统故障情况下的完整性和可用性。</li>
</ul>
<h2><strong>2. HDFS</strong> <strong>的架构</strong></h2>
<p>HDFS采用主从架构，主要由NameNode和DataNode两类节点组成。</p>
<h3><strong>2.1 NameNode</strong></h3>
<p>NameNode是HDFS的主节点，负责管理文件系统的命名空间和文件块的映射关系。它存储所有文件和目录的元数据（如文件名、权限、块位置等），并协调客户端对数据的访问请求。</p>
<h3><strong>2.1.1 NameNode</strong> <strong>的职责</strong></h3>
<ul>
 <li><strong>文件系统命名空间管理</strong>：管理文件和目录的结构，维护元数据。</li>
 <li><strong>块管理</strong>：管理文件与块的映射关系，以及块在DataNode上的存储位置。</li>
 <li><strong>集群管理</strong>：监控DataNode的健康状态，处理节点故障。</li>
</ul>
<h3><strong>2.2 DataNode</strong></h3>
<p>DataNode是HDFS的工作节点，负责存储实际的数据块。每个DataNode定期向NameNode发送心跳信号，报告其健康状态和存储情况。</p>
<h4><strong>2.2.1 DataNode</strong> <strong>的职责</strong></h4>
<ul>
 <li><strong>数据存储</strong>：存储HDFS文件的数据块。</li>
 <li><strong>数据块报告</strong>：定期向NameNode发送数据块列表，报告其存储情况。</li>
 <li><strong>数据块操作</strong>：执行客户端请求的读写操作，负责数据块的创建、删除和复制。</li>
</ul>
<h3><strong>2.3 Secondary NameNode</strong></h3>
<p>Secondary NameNode并不是NameNode的热备份，而是辅助NameNode进行元数据管理的节点。它定期获取NameNode的元数据快照并合并编辑日志，以减轻NameNode的负载。</p>
<h4><strong>2.3.1 Secondary NameNode</strong> <strong>的职责</strong></h4>
<ul>
 <li><strong>元数据快照</strong>：定期从NameNode获取元数据快照。</li>
 <li><strong>合并编辑日志</strong>：将元数据快照与编辑日志合并，生成新的元数据文件，减轻NameNode的内存压力。</li>
</ul>
<h3><strong>2.4 HDFS</strong> <strong>的基本架构图</strong></h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F361eeec10afc0a91736b4ecd5eebb511.png&amp;size=m" alt=""></p>
<h2><strong>3. HDFS</strong> <strong>的工作原理</strong></h2>
<p>HDFS通过分布式存储和冗余机制，实现高可靠性和高可用性。以下是HDFS的几个关键工作原理。</p>
<h3><strong>3.1</strong> <strong>文件存储</strong></h3>
<p>HDFS将文件分割成固定大小的块（默认64MB或128MB），并将这些块存储在不同的DataNode上。每个块会被复制到多个DataNode（默认3个副本），以确保数据的可靠性。</p>
<h3><strong>3.2</strong> <strong>数据写入</strong></h3>
<p>当客户端向HDFS写入数据时，数据首先被分割成块，并通过Pipeline机制写入到多个DataNode。具体步骤如下：</p>
<ol>
 <li><strong>客户端请求NameNode</strong>：客户端向NameNode请求写入文件。</li>
 <li><strong>NameNode</strong> <strong>分配块和DataNode</strong>：NameNode为文件分配数据块并选择存储这些块的DataNode。</li>
 <li><strong>客户端写入数据块</strong>：客户端将数据块写入第一个DataNode，第一个DataNode再将数据块复制到第二个DataNode，依此类推。</li>
 <li><strong>数据块确认</strong>：当所有副本写入成功后，客户端接收到确认消息，表示数据写入完成。</li>
</ol>
<h3>3.3 数据读取</h3>
<p>当客户端从HDFS读取数据时，NameNode提供数据块的位置信息，客户端直接从相应的DataNode读取数据。具体步骤如下：</p>
<ol>
 <li><strong>客户端请求NameNode</strong>：客户端向NameNode请求读取文件。</li>
 <li><strong>NameNode</strong> <strong>返回块位置</strong>：NameNode返回文件块所在的DataNode列表。</li>
 <li><strong>客户端读取数据块</strong>：客户端直接从DataNode读取数据块，并在本地合并这些数据块，恢复成完整的文件。</li>
</ol>
<h3><strong>3.4</strong> <strong>容错机制</strong></h3>
<p>HDFS通过数据块副本机制实现容错。当DataNode发生故障时，NameNode会检测到该DataNode的心跳信号丢失，并在其他健康的DataNode上重新复制丢失的数据块。</p>
<h3><strong>3.5</strong> <strong>元数据管理</strong></h3>
<p>NameNode负责管理文件系统的元数据，包括文件名、目录结构、权限和数据块位置等。为了保证元数据的一致性和持久性，NameNode将元数据存储在内存中，并定期写入到本地磁盘。</p>
<h2><strong>4. HDFS</strong> <strong>的应用场景</strong></h2>
<p>HDFS广泛应用于需要大规模数据存储和高吞吐量数据处理的场景。以下是一些典型的应用场景：</p>
<h2><strong>4.1</strong> <strong>数据仓库</strong></h2>
<p>HDFS可以用作数据仓库，存储和管理大规模的结构化和非结构化数据。企业可以利用HDFS构建数据湖，统一存储各种来源的数据，方便后续的数据分析和挖掘。</p>
<h3><strong>4.2</strong> <strong>大数据分析</strong></h3>
<p>HDFS为大数据分析提供了高效的数据存储和访问机制。结合MapReduce、Spark等分布式计算框架，HDFS能够快速处理和分析海量数据，帮助企业从数据中获取有价值的洞察。</p>
<h3><strong>4.3</strong> <strong>日志存储与处理</strong></h3>
<p>HDFS适用于存储和处理大规模的日志数据，如Web服务器日志、应用日志和系统日志等。企业可以利用HDFS集中存储日志数据，并结合数据分析工具，进行实时监控和异常检测。</p>
<h3><strong>4.4</strong> <strong>机器学习</strong></h3>
<p>HDFS为机器学习提供了可靠的数据存储和高效的数据访问能力。数据科学家可以将训练数据集存储在HDFS上，并利用分布式计算框架训练和优化机器学习模型。</p>
<h3><strong>4.5</strong> <strong>多媒体存储</strong></h3>
<p>HDFS可以用于存储和管理大规模的多媒体数据，如图像、音频和视频文件等。通过分布式存储和并行处理，HDFS能够高效地存储和传输多媒体数据。</p>
<h3><strong>5.</strong> <strong>常见的HDFS命令</strong></h3>
<p>掌握HDFS的常见命令，可以帮助用户更加高效地管理和操作HDFS。以下是一些常用的HDFS命令：</p>
<h3><strong>5.1</strong> <strong>文件操作命令</strong></h3>
<pre><code class="hljs">ls：列出指定目录下的文件和目录。
hdfs dfs -ls /路径

mkdir：创建一个新的目录。
hdfs dfs -mkdir /路径

put：将本地文件上传到HDFS。
hdfs dfs -put 本地文件 /路径

get：从HDFS下载文件到本地。
hdfs dfs -get /路径 本地文件

rm：删除指定路径下的文件或目录。
hdfs dfs -rm /路径

mv：移动或重命名HDFS文件或目录。
hdfs dfs -mv /源路径 /目标路径

cat：显示HDFS文件的内容
</code></pre>
<h3><strong>5.2</strong> <strong>数据块操作命令</strong></h3>
<pre><code class="hljs">fsck：检查HDFS的文件系统状态，报告文件的健康状况。
hdfs fsck /路径

du：显示指定目录或文件的磁盘使用情况。
hdfs dfs -du /路径

df：显示HDFS文件系统的总空间和可用空间。
hdfs dfs -df

checksum：获取HDFS文件的校验和。
hdfs dfs -checksum /路径
</code></pre>
<h3><strong>5.3</strong> <strong>数据备份和恢复命令</strong></h3>
<pre><code class="hljs">distcp：用于在HDFS集群之间或在HDFS和其他文件系统之间复制大量数据。
hadoop distcp hdfs://源路径 hdfs://目标路径

snapot：创建HDFS目录的快照。
hdfs dfs -createSnapot /路径 快照名称

deleteSnapot：删除HDFS目录的快照。
hdfs dfs -deleteSnapot /路径 快照名称
</code></pre>
<h3><strong>5.4</strong> <strong>权限管理命令</strong></h3>
<pre><code class="hljs">chown：更改文件或目录的所有者。
hdfs dfs -chown 用户:组 /路径

chmod：更改文件或目录的权限。
hdfs dfs -chmod 权限 /路径

chgrp：更改文件或目录的组。
hdfs dfs -chgrp 组 /路径
</code></pre>
<h3><strong>5.5 HDFS</strong> <strong>管理员命令</strong></h3>
<pre><code class="hljs">balancer：启动HDFS数据块平衡器，重新分布数据块以优化存储利用。
hdfs balancer

dfsadmin：执行HDFS的管理任务，如查看集群状态、刷新节点等。
hdfs dfsadmin -report

namenode：启动或停止NameNode。
hdfs namenode -format

datanode：启动或停止DataNode。
hdfs datanode
</code></pre>
<h2><strong>6. HDFS</strong> <strong>的性能优化</strong></h2>
<p>为了充分利用HDFS的优势，可以从以下几个方面进行性能优化：</p>
<h3><strong>6.1</strong> <strong>数据分块优化</strong></h3>
<p>合理设置数据块大小，可以提高HDFS的性能。默认情况下，HDFS的数据块大小为128MB，根据实际情况调整块大小，可以优化数据的读写性能。</p>
<h3><strong>6.2</strong> <strong>副本数量优化</strong></h3>
<p>根据数据的重要性和系统的容错要求，适当调整数据块的副本数量。默认情况下，HDFS的副本数量为3个，可以根据具体情况进行调整，以达到最佳的性能和可靠性平衡。</p>
<h3><strong>6.3</strong> <strong>网络带宽优化</strong></h3>
<p>优化HDFS集群的网络带宽，可以提高数据的传输速度。采用高带宽网络和优化网络拓扑结构，可以显著提升HDFS的性能。</p>
<h3><strong>6.4</strong> <strong>硬件配置优化</strong></h3>
<p>合理配置HDFS集群的硬件资源，包括CPU、内存和磁盘等，可以提高系统的整体性能。采用高性能的磁盘和增加内存容量，可以显著提升HDFS的读写速度。</p>
<h3><strong>6.5</strong> <strong>数据压缩优化</strong></h3>
<p>对大规模数据进行压缩存储，可以减少磁盘空间的使用，提升数据传输效率。HDFS支持多种数据压缩格式，如Gzip、Snappy和LZO等，可以根据实际需求选择合适的压缩算法。</p>
<h3><strong>6.6</strong> <strong>元数据管理优化</strong></h3>
<p>定期对NameNode的元数据进行快照和编辑日志合并，可以减轻NameNode的内存压力，提高系统的响应速度。采用Secondary NameNode或Backup Node，可以进一步增强元数据的管理和容错能力。</p>
<h2><strong>7. HDFS</strong> <strong>的安全性</strong></h2>
<p>HDFS提供了多种安全机制，保护数据的完整性和机密性。</p>
<h3><strong>7.1</strong> <strong>认证机制</strong></h3>
<p>HDFS支持Kerberos认证，确保只有合法用户才能访问文件系统。通过配置Kerberos票据，可以实现安全的用户认证和访问控制。</p>
<h3><strong>7.2</strong> <strong>授权机制</strong></h3>
<p>HDFS提供了基于POSIX的权限模型，可以对文件和目录进行细粒度的访问控制。通过设置文件和目录的所有者、组和权限，可以控制用户对数据的读写和执行权限。</p>
<h3><strong>7.3</strong> <strong>数据加密</strong></h3>
<p>HDFS支持数据在传输和存储过程中的加密，确保数据的机密性。通过配置传输层安全（TLS）和加密文件系统（EFS），可以实现数据的端到端加密。</p>
<h3><strong>7.4</strong> <strong>审计日志</strong></h3>
<p>HDFS提供了审计日志功能，可以记录用户的访问和操作日志。通过分析审计日志，可以监控系统的使用情况，检测和防范潜在的安全威胁。</p>
<h2><strong>8. HDFS</strong> <strong>的未来发展</strong></h2>
<p>HDFS作为一个关键的大数据存储系统，未来将继续发展和演进，以应对不断变化的数据处理需求。</p>
<h3><strong>8.1</strong> <strong>更高的可扩展性</strong></h3>
<p>HDFS将进一步提高其可扩展性，支持更大规模的数据存储和计算。通过优化数据块管理和副本机制，HDFS将能够处理更多的节点和数据。</p>
<h3><strong>8.2</strong> <strong>更强的兼容性</strong></h3>
<p>HDFS将与更多的大数据处理工具和平台集成，提供更加灵活和强大的数据处理能力。通过兼容更多的数据格式和存储协议，HDFS将能够满足不同数据处理需求。</p>
<h3><strong>8.3</strong> <strong>更好的用户体验</strong></h3>
<p>HDFS将继续改进其易用性，包括更加直观的管理界面、更简便的配置流程和更强大的开发工具。通过提供更多的自动化和智能化功能，HDFS将能够简化用户的操作，提高系统的易用性。</p>
<h3><strong>8.4</strong> <strong>更加智能的资源调度</strong></h3>
<p>随着机器学习和人工智能技术的发展，HDFS的资源调度将更加智能化，能够根据任务的特点和资源的使用情况自动调整和优化。通过引入智能化的资源管理算法，HDFS将能够提高系统的整体效率和性能。</p>
<h3><strong>8.5</strong> <strong>增强的安全性</strong></h3>
<p>HDFS将进一步加强其安全机制，提供更加全面和灵活的认证、授权和加密功能。通过引入更多的安全技术和工具，HDFS将能够更好地保护数据的安全和隐私，确保数据在传输和存储过程中的机密性和完整性。</p>
<h2><strong>9. HDFS</strong> <strong>的社区和支持</strong></h2>
<p>HDFS作为一个开源项目，有着庞大的社区支持和丰富的资源。用户可以通过以下渠道获取帮助和支持：</p>
<h3><strong>9.1</strong> <strong>官方文档</strong></h3>
<p>Apache Hadoop的官方网站提供了详细的官方文档，包括安装指南、配置说明和API参考。用户可以通过阅读官方文档快速上手和解决常见问题。</p>
<h3><strong>9.2</strong> <strong>社区论坛</strong></h3>
<p>HDFS社区论坛是用户交流和讨论的主要平台，用户可以在论坛上提出问题、分享经验和获取帮助。Apache Hadoop的官方邮件列表也是一个重要的交流渠道。</p>
<h3><strong>9.3</strong> <strong>开源贡献</strong></h3>
<p>作为一个开源项目，HDFS欢迎用户参与代码贡献和项目维护。用户可以通过GitHub提交代码、报告Bug和参与讨论，帮助改进和发展HDFS。</p>
<h3><strong>9.4</strong> <strong>商业支持</strong></h3>
<p>许多公司提供HDFS的商业支持和服务，包括安装、配置、优化和培训等。用户可以选择合适的商业支持服务，确保HDFS在生产环境中的稳定运行。</p>
<h2><strong>10.</strong> <strong>总结</strong></h2>
<p>HDFS作为Hadoop生态系统的核心组件，提供了高效的大规模数据存储和管理能力。本文从HDFS的定义、架构、工作原理、应用场景、常见命令、性能优化、安全性、未来发展和社区支持等多个方面进行了详细的介绍。</p>
<p>HDFS凭借其高容错性、高吞吐量和可扩展性的特点，已经在数据仓库、大数据分析、日志存储与处理、机器学习和多媒体存储等领域得到了广泛应用。通过掌握HDFS的基础知识和操作技能，用户可以在实际工作中充分利用这一强大的工具，解决大规模数据处理和分析的挑战。</p>
<p>未来，随着大数据技术的不断发展，HDFS将继续演进和优化，提供更加灵活、高效和安全的数据存储和管理解决方案。无论是作为数据工程师、数据科学家还是大数据架构师，深入掌握和应用HDFS都将成为你职业发展的重要技能。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/weixin_42175752/article/details/140097992</p>]]></description><guid isPermaLink="false">/archives/2f854ebf-43ec-49e7-a3bc-d41cdaa9da2a</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 08:37:52 GMT</pubDate></item><item><title><![CDATA[【算法专题】贪心算法]]></title><link>https://wiki.dgfygk.com/archives/4412b32e-dad3-45c9-a2a6-cb349ec5162e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%AE%97%E6%B3%95%E4%B8%93%E9%A2%98%E3%80%91%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95&amp;url=/archives/4412b32e-dad3-45c9-a2a6-cb349ec5162e" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>贪心算法</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_2">贪心算法介绍</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1__23">1. 柠檬水找零</a></li>
 <li><a href="https://wiki.dgfygk.com#2__125">2. 将数组和减半的最少操作次数</a></li>
 <li><a href="https://wiki.dgfygk.com#3__212">3. 最大数</a></li>
 <li><a href="https://wiki.dgfygk.com#4__288">4. 摆动序列(贪心思路)</a></li>
 <li><a href="https://wiki.dgfygk.com#5__364">5. 最长递增子序列(贪心算法)</a></li>
 <li><a href="https://wiki.dgfygk.com#6__441">6. 递增的三元子序列</a></li>
 <li><a href="https://wiki.dgfygk.com#7__498">7. 最长连续递增序列</a></li>
 <li><a href="https://wiki.dgfygk.com#8__563">8. 买卖股票的最佳时机</a></li>
 <li><a href="https://wiki.dgfygk.com#9__616">9. 买卖股票的最佳时机Ⅱ(贪心算法)</a></li>
 <li><a href="https://wiki.dgfygk.com#10_K__686">10. K 次取反后最大化的数组和</a></li>
 <li><a href="https://wiki.dgfygk.com#11__764">11. 按身高排序</a></li>
 <li><a href="https://wiki.dgfygk.com#12__834">12. 优势洗牌</a></li>
 <li><a href="https://wiki.dgfygk.com#13__904">13. 最长回文串</a></li>
 <li><a href="https://wiki.dgfygk.com#14__982">14. 增减字符串匹配</a></li>
 <li><a href="https://wiki.dgfygk.com#15__1046">15. 分发饼干</a></li>
 <li><a href="https://wiki.dgfygk.com#16__1122">16. 最优除法</a></li>
 <li><a href="https://wiki.dgfygk.com#17__1202">17. 跳跃游戏Ⅱ</a></li>
 <li><a href="https://wiki.dgfygk.com#18__1270">18. 跳跃游戏</a></li>
 <li><a href="https://wiki.dgfygk.com#19__1327">19. 加油站</a></li>
 <li><a href="https://wiki.dgfygk.com#20__1405">20. 单调递增的数字</a></li>
</ul>
<p>
 <br>
</p>
<h2>贪心算法介绍</h2>
<p>什么是贪心算法呢？</p>
<p>首先，我们需要知道贪心策略，即解决问题的策略，将局部最优转变为全局最优；</p>
<ul>
 <li>把解决问题的过程分为若干步；</li>
 <li>解决每一步的时候，都选择当前看起来"最优的"解法；</li>
 <li>“希望"得到全局最优解</li>
</ul>
<p>贪心算法的特点：</p>
<ol>
 <li>提出贪心策略，但是贪心策略的提出是没有标准和模板的，可能每一道题的贪心策略都是不同的；</li>
 <li>贪心策略的正确性没有保障，因为我们提出的"贪心策略"有可能是错误的，正确的贪心策略是需要"证明的”；常用的证明方法是我们学过的数学中见过的证明方法。</li>
</ol>
<p>下面我们结合题目进行分析。</p>
<h3>1. 柠檬水找零</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/lemonade-change/">Leetcode -860.柠檬水找零</a></p>
<p>Leetcode -860.柠檬水找零</p>
<p>题目：在柠檬水摊上，每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品，（按账单 bills 支付的顺序）一次购买一杯。</p>
<p>每位顾客只买一杯柠檬水，然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零，也就是说净交易是每位顾客向你支付 5 美元。</p>
<p>注意，一开始你手头没有任何零钱。</p>
<p>给你一个整数数组 bills ，其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零，返回 true ，否则返回 false 。</p>
<p>示例 1：
 <br>
 输入：bills = [5, 5, 5, 10, 20]
 <br>
 输出：true
 <br>
 解释：
 <br>
 前 3 位顾客那里，我们按顺序收取 3 张 5 美元的钞票。
 <br>
 第 4 位顾客那里，我们收取一张 10 美元的钞票，并返还 5 美元。
 <br>
 第 5 位顾客那里，我们找还一张 10 美元的钞票和一张 5 美元的钞票。
 <br>
 由于所有客户都得到了正确的找零，所以我们输出 true。</p>
<p>示例 2：
 <br>
 输入：bills = [5, 5, 10, 10, 20]
 <br>
 输出：false
 <br>
 解释：
 <br>
 前 2 位顾客那里，我们按顺序收取 2 张 5 美元的钞票。
 <br>
 对于接下来的 2 位顾客，我们收取一张 10 美元的钞票，然后返还 5 美元。
 <br>
 对于最后一位顾客，我们无法退回 15 美元，因为我们现在只有两张 10 美元的钞票。
 <br>
 由于不是每位顾客都得到了正确的找零，所以答案是 false。</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= bills.length &lt;= 10^5</li>
 <li>bills[i] 不是 5 就是 10 或是 20</li>
</ul>
<p><strong>贪⼼策略：分情况讨论：</strong></p>
<ul>
 <li><strong>遇到 5 元钱，直接收下；</strong></li>
 <li><strong>遇到 10 元钱，找零 5 元钱之后，收下；</strong></li>
 <li><strong>遇到 20 元钱：</strong>
  <br>
  *</li>
 <li><strong>先尝试凑 10 + 5 的组合；</strong>
  <br>
  *</li>
 <li><strong>如果凑不出来，拼凑 5 + 5 + 5 的组合；</strong></li>
</ul>
<p>代码如下：</p>
<pre><code>        class Solution {
        public:
            bool lemonadeChange(vector&lt;int&gt;&amp; bills)
            {
                int five = 0, ten = 0;
                for(int i = 0; i &lt; bills.size(); i++)
                {
                    // 5 元
                    if(bills[i] == 5)
                    {
                        five++;
                    }

                    // 10元
                    else if(bills[i] == 10)
                    {
                        ten++;

                        if(five == 0) return false;

                        five--;
                    }

                    // 20 元
                    else
                    {
                        // 5 块和 10 块都有的时候优先找5+10
                        if(five &amp;&amp; ten) 
                        {
                            ten--;
                            five--;
                        }

                        else if(five &gt;= 3)
                        {
                            five -= 3;
                        }

                        else
                        {
                            return false;
                        }
                    }
                }
                return true;
            }
        };
</code></pre>
<h3>2. 将数组和减半的最少操作次数</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/minimum-operations-to-halve-array-sum/">Leetcode -2208.将数组和减半的最少操作次数</a></p>
<p>Leetcode -2208.将数组和减半的最少操作次数</p>
<p>题目：给你一个正整数数组 nums 。每一次操作中，你可以从 nums 中选择 任意 一个数并将它减小到 恰好 一半。（注意，在后续操作中你可以对减半过的数继续执行操作）</p>
<p>请你返回将 nums 数组和 至少 减少一半的 最少 操作数。</p>
<p>示例 1：
 <br>
 输入：nums = [5, 19, 8, 1]
 <br>
 输出：3
 <br>
 解释：初始 nums 的和为 5 + 19 + 8 + 1 = 33 。
 <br>
 以下是将数组和减少至少一半的一种方法：
 <br>
 选择数字 19 并减小为 9.5 。
 <br>
 选择数字 9.5 并减小为 4.75 。
 <br>
 选择数字 8 并减小为 4 。
 <br>
 最终数组为[5, 4.75, 4, 1] ，和为 5 + 4.75 + 4 + 1 = 14.75 。
 <br>
 nums 的和减小了 33 - 14.75 = 18.25 ，减小的部分超过了初始数组和的一半，18.25 &gt;= 33 / 2 = 16.5 。
 <br>
 我们需要 3 个操作实现题目要求，所以返回 3 。
 <br>
 可以证明，无法通过少于 3 个操作使数组和减少至少一半。</p>
<p>示例 2：
 <br>
 输入：nums = [3, 8, 20]
 <br>
 输出：3
 <br>
 解释：初始 nums 的和为 3 + 8 + 20 = 31 。
 <br>
 以下是将数组和减少至少一半的一种方法：
 <br>
 选择数字 20 并减小为 10 。
 <br>
 选择数字 10 并减小为 5 。
 <br>
 选择数字 3 并减小为 1.5 。
 <br>
 最终数组为[1.5, 8, 5] ，和为 1.5 + 8 + 5 = 14.5 。
 <br>
 nums 的和减小了 31 - 14.5 = 16.5 ，减小的部分超过了初始数组和的一半， 16.5 &gt;= 31 / 2 = 15.5 。
 <br>
 我们需要 3 个操作实现题目要求，所以返回 3 。
 <br>
 可以证明，无法通过少于 3 个操作使数组和减少至少一半。</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 10^5</li>
 <li>1 &lt;= nums[i] &lt;= 10^7</li>
</ul>
<p><strong>贪心策略：</strong></p>
<ul>
 <li><strong>每次挑选出「当前」数组中「最大」的数，然后「减半」；</strong></li>
 <li><strong>直到数组和减少到至少一半为止。</strong></li>
</ul>
<p><strong>为了「快速」挑选出数组中最大的数，我们可以利用「堆」这个数据结构。</strong></p>
<p>代码如下：</p>
<pre><code>        class Solution {
        public:
            int halveArray(vector&lt;int&gt;&amp; nums) 
            {
                int ret = 0;
                double sum = 0;
                priority_queue&lt;double&gt; pq;

                // 统计数组总和，并将元素放入大堆中
                for(auto&amp; num : nums) 
                {
                    sum += num;
                    pq.push(num);
                }

                // sum 取一半，每次取当前数组中最大的元素减少一半，sum 并减去这个数，然后更新大堆，统计操作次数
                sum /= 2.0;
                while(sum &gt; 0)
                {
                    double num = pq.top() / 2.0;
                    pq.pop();
                    sum -= num;
                    pq.push(num);
                    ret++;
                }

                return ret;      
            }
        };
</code></pre>
<h3>3. 最大数</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/largest-number/description/">Leetcode -179.最大数</a></p>
<p>Leetcode -179.最大数</p>
<p>题目：给定一组非负整数 nums，重新排列每个数的顺序（每个数不可拆分）使之组成一个最大的整数。</p>
<p>注意：输出结果可能非常大，所以你需要返回一个字符串而不是整数。</p>
<p>示例 1：
 <br>
 输入：nums = [10, 2]
 <br>
 输出：“210”</p>
<p>示例 2：
 <br>
 输入：nums = [3, 30, 34, 5, 9]
 <br>
 输出：“9534330”</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 100</li>
 <li>0 &lt;= nums[i] &lt;= 10^9</li>
</ul>
<p><strong>思路：贪心，可以先优化，将所有的数字当成字符串处理，那么两个数字之间的拼接操作以及比较操作就会很方便。</strong></p>
<p><strong>贪心策略：按照题目的要求，重新定义一个新的排序规则，然后排序即可。</strong></p>
<p><strong>我们定义的排序规则：</strong></p>
<ul>
 <li><strong>「A 拼接 B」 大于 「B 拼接 A」，那么 A 在前，B 在后；</strong></li>
 <li><strong>「A 拼接 B」 等于 「B 拼接 A」，那么 A B 的顺序无所谓；</strong></li>
 <li><strong>「A 拼接 B」 小于 「B 拼接 A」，那么 B 在前，A 在后；</strong></li>
</ul>
<p><strong>例如 [“10”, “2”] 两个数，“10” 和 “2” 拼接为 “102”；“2” 和 “10” 拼接为 “210”；因为按字典序比较 “210” 大于 “102”，所以 “210” 在前面。</strong></p>
<p>代码如下：</p>
<pre><code>        class mysort
        {
        public:
            bool operator()(const string&amp; x, const string&amp; y)
            {
                return x + y &gt; y + x;
            }
        };

        class Solution {
        public:
            string largestNumber(vector&lt;int&gt;&amp; nums) 
            {
                // 先将数字转为字符串放入 strs 中
                vector&lt;string&gt; strs;
                for(auto&amp; num : nums) strs.push_back(to_string(num));

                // 按仿函数的方法排序
                sort(strs.begin(), strs.end(), mysort());

                // 将排好序的字符串拼接起来放入 ret 中
                string ret;
                for(auto&amp; str : strs) ret += str;

                // 如果第一位为 0 就返回 0
                if(ret[0] == '0') return "0";

                return ret;
            }
        };
</code></pre>
<h3>4. 摆动序列(贪心思路)</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/wiggle-subsequence/description/">Leetcode -376.摆动序列</a></p>
<p>Leetcode -376.摆动序列(贪心思路)</p>
<p>题目：如果连续数字之间的差严格地在正数和负数之间交替，则数字序列称为 摆动序列 。第一个差（如果存在的话）可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。</p>
<p>例如，[1, 7, 4, 9, 2, 5] 是一个 摆动序列 ，因为差值(6, -3, 5, -7, 3) 是正负交替出现的。</p>
<p>相反，[1, 4, 7, 2, 5] 和[1, 7, 4, 5, 5] 不是摆动序列，第一个序列是因为它的前两个差值都是正数，第二个序列是因为它的最后一个差值为零。
 <br>
 子序列 可以通过从原始序列中删除一些（也可以不删除）元素来获得，剩下的元素保持其原始顺序。</p>
<p>给你一个整数数组 nums ，返回 nums 中作为 摆动序列 的 最长子序列的长度 。</p>
<p>示例 1：
 <br>
 输入：nums = [1, 7, 4, 9, 2, 5]
 <br>
 输出：6
 <br>
 解释：整个序列均为摆动序列，各元素之间的差值为(6, -3, 5, -7, 3) 。</p>
<p>示例 2：
 <br>
 输入：nums = [1, 17, 5, 10, 13, 15, 10, 5, 16, 8]
 <br>
 输出：7
 <br>
 解释：这个序列包含几个长度为 7 摆动序列。
 <br>
 其中一个是[1, 17, 10, 13, 10, 16, 8] ，各元素之间的差值为(16, -7, 3, -3, 6, -8) 。</p>
<p>示例 3：
 <br>
 输入：nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
 <br>
 输出：2</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 1000</li>
 <li>0 &lt;= nums[i] &lt;= 1000</li>
</ul>
<p><strong>思路：贪心算法，其中这道题我们在动态规划专题也做过。这道题的贪心策略：对于某一个位置来说：</strong></p>
<ul>
 <li><strong>如果接下来呈现上升趋势的话，我们让其上升到波峰的位置；</strong></li>
 <li><strong>如果接下来呈现下降趋势的话，我们让其下降到波谷的位置。</strong></li>
</ul>
<p><strong>因此，如果把整个数组放在「折线图」中，我们统计出所有的波峰以及波谷的个数即可。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe3a1b15a94141ef35de0b13c00afef09.png&amp;size=m" alt=""></p>
<p>代码如下：</p>
<pre><code>        class Solution {
        public:
            int wiggleMaxLength(vector&lt;int&gt;&amp; nums) 
            {
                if(nums.size() &lt; 2) return nums.size();

                // right 代表后一个数减去当前这个数的差，正数代表上升趋势，负数代表下降趋势，即当前这个数右边数的趋势
                // left 代表当前这个数左边数的趋势，0代表不确定，因为首尾位置必选，所以初始化的时候给 left 初始化为0
                int left = 0, right = 0, ret = 0;
                for(int i = 0; i &lt; nums.size() - 1; i++)
                {
                    right = nums[i + 1] - nums[i];
                    if(right == 0) continue; // 忽略掉相同的数

                    if(right * left &lt;= 0) ret++;
                    left = right;
                }
                // 加上最后一个位置
                return ++ret;
            }
        };
</code></pre>
<h3>5. 最长递增子序列(贪心算法)</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/longest-increasing-subsequence/">Leetcode -300.最长递增子序列</a></p>
<p>Leetcode -300.最长递增子序列</p>
<p>题目：给你一个整数数组 nums ，找到其中最长严格递增子序列的长度。</p>
<p>子序列 是由数组派生而来的序列，删除（或不删除）数组中的元素而不改变其余元素的顺序。
 <br>
 例如，[3, 6, 2, 7] 是数组[0, 3, 1, 6, 2, 2, 7] 的子序列。</p>
<p>示例 1：
 <br>
 输入：nums = [10, 9, 2, 5, 3, 7, 101, 18]
 <br>
 输出：4
 <br>
 解释：最长递增子序列是[2, 3, 7, 101]，因此长度为 4 。</p>
<p>示例 2：
 <br>
 输入：nums = [0, 1, 0, 3, 2, 3]
 <br>
 输出：4</p>
<p>示例 3：
 <br>
 输入：nums = [7, 7, 7, 7, 7, 7, 7]
 <br>
 输出：1</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 2500</li>
 <li>-10^4 &lt;= nums[i] &lt;= 10^4</li>
</ul>
<p><strong>贪心策略：我们在考虑最长递增子序列的长度的时候，其实并不关心这个序列长什么样子，我们只是关心最后一个元素是谁。这样新来一个元素之后，我们就可以判断是否可以拼接到它的后面。</strong></p>
<p><strong>因此，我们可以创建一个数组，统计长度为 x 的递增子序列中，最后一个元素是谁。为了尽可能的让这个序列更长，我们仅需统计长度为 x 的所有递增序列中最后一个元素的「最小值」。</strong></p>
<p><strong>统计的过程中发现，数组中的数呈现「递增」趋势，因此可以使用「二分」来查找插入位置。</strong></p>
<p>代码如下：</p>
<pre><code>        class Solution 
        {
        public:
            int lengthOfLIS(vector&lt;int&gt;&amp; nums)
            {
                int n = nums.size();
                vector&lt;int&gt; ret;
                ret.push_back(nums[0]);
                for(int i = 1; i &lt; n; i++)
                {
                    // 因为我们的思路是会让数组有序的，所以当 nums[i ]大于 ret 数组最后一个元素就直接尾插
                    if(nums[i] &gt; ret.back())
                    {
                        ret.push_back(nums[i]);
                    }

                    else
                    {
                        // 利用二分插入（ret数组已经有序）
                        // 找出第一次大于等于 nums[i] 的元素，使 nums[i] 替代它的位置
                        int left = 0, right = ret.size() - 1;
                        while(left &lt; right)
                        {
                            int mid = left + (right - left) / 2;
                            if(ret[mid] &gt;= nums[i]) right = mid;
                            else left = mid + 1;
                        }
                        ret[left] = nums[i];
                    }
                }
                return ret.size();
            }
        };
</code></pre>
<h3>6. 递增的三元子序列</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/increasing-triplet-subsequence/description/">Leetcode -334.递增的三元子序列</a></p>
<p>Leetcode -334.递增的三元子序列</p>
<p>题目：给你一个整数数组 nums ，判断这个数组中是否存在长度为 3 的递增子序列。</p>
<p>如果存在这样的三元组下标(i, j, k) 且满足 i &lt; j &lt; k ，使得 nums[i] &lt; nums[j] &lt; nums[k] ，返回 true ；否则，返回 false 。</p>
<p>示例 1：
 <br>
 输入：nums = [1, 2, 3, 4, 5]
 <br>
 输出：true
 <br>
 解释：任何 i &lt; j &lt; k 的三元组都满足题意</p>
<p>示例 2：
 <br>
 输入：nums = [5, 4, 3, 2, 1]
 <br>
 输出：false
 <br>
 解释：不存在满足题意的三元组</p>
<p>示例 3：
 <br>
 输入：nums = [2, 1, 5, 0, 4, 6]
 <br>
 输出：true
 <br>
 解释：三元组(3, 4, 5) 满足题意，因为 nums[3] == 0 &lt; nums[4] == 4 &lt; nums[5] == 6</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 5 * 10^5</li>
 <li>-2^31 &lt;= nums[i] &lt;= 2^31 - 1</li>
</ul>
<p><strong>贪心策略：最长递增子序列的简化版。不用一个数组存数据，仅需两个变量即可。也不用二分插入位置，仅需两次比较就可以找到插入位置。</strong></p>
<p>代码如下：</p>
<pre><code>        class Solution {
        public:
            bool increasingTriplet(vector&lt;int&gt;&amp; nums) 
            {
                int n = nums.size();
                int x = nums[0], y = INT_MAX;
                for(int i = 1; i &lt; n; i++)
                {
                    if(nums[i] &gt; y) return true; // 如果 nums[i] 大于 y，说明它可以放在 y 后面，这时候长度为3，直接返回 true
                    else if(nums[i] &gt; x) y = nums[i]; // 如果nums[i]大于x，说明它的大小位于 x 和 y 之间，所以可以替代 y
                    else x = nums[i]; // 否则，就是比 x 小，可以替代x
                }
                return false;
            }
        };
</code></pre>
<h3>7. 最长连续递增序列</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/longest-continuous-increasing-subsequence/description/">Leetcode -674.最长连续递增序列</a></p>
<p>Leetcode -674.最长连续递增序列</p>
<p>题目：给定一个未经排序的整数数组，找到最长且 连续递增的子序列，并返回该序列的长度。</p>
<p>连续递增的子序列 可以由两个下标 l 和 r（l &lt; r）确定，如果对于每个 l &lt;= i &lt; r，都有 nums[i] &lt; nums[i + 1] ，那么子序列[nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。</p>
<p>示例 1：
 <br>
 输入：nums = [1, 3, 5, 4, 7]
 <br>
 输出：3
 <br>
 解释：最长连续递增序列是[1, 3, 5], 长度为3。
 <br>
 尽管[1, 3, 5, 7] 也是升序的子序列, 但它不是连续的，因为 5 和 7 在原数组里被 4 隔开。</p>
<p>示例 2：
 <br>
 输入：nums = [2, 2, 2, 2, 2]
 <br>
 输出：1
 <br>
 解释：最长连续递增序列是[2], 长度为1。</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 10^4</li>
 <li>-10^9 &lt;= nums[i] &lt;= 10^9</li>
</ul>
<p><strong>思路：思路与 第五题 类似，区别在于本题是要找连续的区间，所以当我们找到开始不连续的元素时，将 ret 数组清空，重新插入；每次判断结束判断找到最长递增序列。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                int findLengthOfLCIS(vector&lt;int&gt;&amp; nums) 
                {
                    int ans = 1;
                    vector&lt;int&gt; ret; 
                    ret.push_back(nums[0]);
                    for(int i = 1; i &lt; nums.size(); i++)
                    {
                        if(nums[i] &gt; ret.back()) 
                        {
                            ret.push_back(nums[i]);
                        }

                        else
                        {
                            ret.clear();
                            ret.push_back(nums[i]);
                        }
                        ans = fmax(ans, ret.size());
                    }
                    return ans;
                }
            }; 
</code></pre>
<h3>8. 买卖股票的最佳时机</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/">Leetcode -121.买卖股票的最佳时机</a></p>
<p>Leetcode -121.买卖股票的最佳时机</p>
<p>题目：给定一个数组 prices ，它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。</p>
<p>你只能选择 某一天 买入这只股票，并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。</p>
<p>返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润，返回 0 。</p>
<p>示例 1：
 <br>
 输入：[7, 1, 5, 3, 6, 4]
 <br>
 输出：5
 <br>
 解释：在第 2 天（股票价格 = 1）的时候买入，在第 5 天（股票价格 = 6）的时候卖出，最大利润 = 6 - 1 = 5 。
 <br>
 注意利润不能是 7 - 1 = 6, 因为卖出价格需要大于买入价格；同时，你不能在买入前卖出股票。</p>
<p>示例 2：
 <br>
 输入：prices = [7, 6, 4, 3, 1]
 <br>
 输出：0
 <br>
 解释：在这种情况下, 没有交易完成, 所以最大利润为 0。</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= prices.length &lt;= 10^5</li>
 <li>0 &lt;= prices[i] &lt;= 10^4</li>
</ul>
<p><strong>思路：由于只能交易一次，所以对于某一个位置 i ，要想获得最大利润，仅需知道前⾯所有元素的最小值。然后在最小值的位置「买入」股票，在当前位置「卖出」股票即可。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                int maxProfit(vector&lt;int&gt;&amp; prices)
                {
                    int prevMin = INT_MAX, ret = 0;
                    for (auto&amp; p : prices)
                    {
                        ret = max(ret, p - prevMin); // 记录最大利润
                        prevMin = min(prevMin, p);  // 记录最小买入价格
                    }
                    return ret;
                }
            };
</code></pre>
<h3>9. 买卖股票的最佳时机Ⅱ(贪心算法)</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/">Leetcode -122.买卖股票的最佳时机Ⅱ</a></p>
<p>Leetcode -122.买卖股票的最佳时机Ⅱ</p>
<p>题目：给你一个整数数组 prices ，其中 prices[i] 表示某支股票第 i 天的价格。</p>
<p>在每一天，你可以决定是否购买和 / 或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买，然后在 同一天 出售。</p>
<p>返回 你能获得的 最大 利润 。</p>
<p>示例 1：
 <br>
 输入：prices = [7, 1, 5, 3, 6, 4]
 <br>
 输出：7
 <br>
 解释：在第 2 天（股票价格 = 1）的时候买入，在第 3 天（股票价格 = 5）的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
 <br>
 随后，在第 4 天（股票价格 = 3）的时候买入，在第 5 天（股票价格 = 6）的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
 <br>
 总利润为 4 + 3 = 7 。</p>
<p>示例 2：
 <br>
 输入：prices = [1, 2, 3, 4, 5]
 <br>
 输出：4
 <br>
 解释：在第 1 天（股票价格 = 1）的时候买入，在第 5 天 （股票价格 = 5）的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
 <br>
 总利润为 4 。</p>
<p>示例 3：
 <br>
 输入：prices = [7, 6, 4, 3, 1]
 <br>
 输出：0
 <br>
 解释：在这种情况下, 交易无法获得正利润，所以不参与交易可以获得最大利润，最大利润为 0 。</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= prices.length &lt;= 3 * 10^4</li>
 <li>0 &lt;= prices[i] &lt;= 10^4</li>
</ul>
<p><strong>贪心策略：由于可以进行无限次交易，所以只要是一个「上升区域」，我们就把利润拿到手就好了，即需要找到一个区间上升的末端，所以我们可以是使用双指针来寻找。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution 
            {
            public:
                int maxProfit(vector&lt;int&gt;&amp; prices) 
                {
                    // 贪心 + 双指针
                    int ret = 0;
                    for(int start = 0; start &lt; prices.size();)
                    {
                        int end = start + 1;

                        // 找到上升的末端
                        while(end &lt; prices.size() &amp;&amp; prices[end] &gt; prices[end - 1]) end++;

                        // 利润等于上升的末端 - 上升区间的起始，如果不是上升的区间，即是自己减去自己，不影响
                        ret += prices[end - 1] - prices[start];
                        start = end;
                    }
                    return ret;
                }
            };
</code></pre>
<h3>10. K 次取反后最大化的数组和</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/">Leetcode -1005.K 次取反后最大化的数组和</a></p>
<p>Leetcode -1005.K 次取反后最大化的数组和</p>
<p>题目：给你一个整数数组 nums 和一个整数 k ，按以下方法修改该数组：</p>
<p>选择某个下标 i 并将 nums[i] 替换为 - nums[i] 。
 <br>
 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。</p>
<p>以这种方式修改数组后，返回数组 可能的最大和 。</p>
<p>示例 1：
 <br>
 输入：nums = [4, 2, 3], k = 1
 <br>
 输出：5
 <br>
 解释：选择下标 1 ，nums 变为[4, -2, 3] 。</p>
<p>示例 2：
 <br>
 输入：nums = [3, -1, 0, 2], k = 3
 <br>
 输出：6
 <br>
 解释：选择下标(1, 2, 2) ，nums 变为[3, 1, 0, 2] 。</p>
<p>示例 3：
 <br>
 输入：nums = [2, -3, -1, 5, -4], k = 2
 <br>
 输出：13
 <br>
 解释：选择下标(1, 4) ，nums 变为[2, 3, -1, 5, 4] 。</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 10^4</li>
 <li>-100 &lt;= nums[i] &lt;= 100</li>
 <li>1 &lt;= k &lt;= 10^4</li>
</ul>
<p><strong>贪心策略：每次只需把最小的改变符号即可，所以我们可以借助优先级队列完成。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:

                void GetTopToOpNum(priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt;&amp; pq)
                {
                    int num = pq.top();
                    pq.pop();
                    num = -num;
                    pq.push(num);
                }

                int largestSumAfterKNegations(vector&lt;int&gt;&amp; nums, int k) 
                {
                    int ret = 0;
                    priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; pq;
                    for(auto&amp; num : nums)
                        pq.push(num);

                    // 每次取最小的变正数
                    while(k--) GetTopToOpNum(pq);

                    while(!pq.empty())
                    {
                        ret += pq.top();
                        pq.pop();
                    }

                    return ret;
                }
            };
</code></pre>
<h3>11. 按身高排序</h3>
<p>注意：本题没有用到贪心的思想，是为了下一题田忌赛马做准备。</p>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/sort-the-people/">Leetcode -2418.按身高排序</a></p>
<p>Leetcode -2418.按身高排序</p>
<p>题目：给你一个字符串数组 names ，和一个由 互不相同 的正整数组成的数组 heights 。两个数组的长度均为 n 。</p>
<p>对于每个下标 i，names[i] 和 heights[i] 表示第 i 个人的名字和身高。</p>
<p>请按身高 降序 顺序返回对应的名字数组 names 。</p>
<p>示例 1：
 <br>
 输入：names = [“Mary”, “John”, “Emma”], heights = [180, 165, 170]
 <br>
 输出：[“Mary”, “Emma”, “John”]
 <br>
 解释：Mary 最高，接着是 Emma 和 John 。</p>
<p>示例 2：
 <br>
 输入：names = [“Alice”, “Bob”, “Bob”], heights = [155, 185, 150]
 <br>
 输出：[“Bob”, “Alice”, “Bob”]
 <br>
 解释：第一个 Bob 最高，然后是 Alice 和第二个 Bob 。</p>
<p>提示：</p>
<ul>
 <li>n == names.length == heights.length</li>
 <li>1 &lt;= n &lt;= 10^3</li>
 <li>1 &lt;= names[i].length &lt;= 20</li>
 <li>1 &lt;= heights[i] &lt;= 10^5</li>
 <li>names[i] 由大小写英文字母组成</li>
 <li>heights 中的所有值互不相同</li>
</ul>
<p><strong>思路：我们不能直接按照 i 位置对应的 heights 来排序，因为排序过程是会移动元素的，但是 names 内的元素是不会移动的。由题意可知，names 数组和 heights 数组的下标是一一对应的，因此我们可以重新创建出来一个下标数组，将这个下标数组按照 heights[i] 的大小排序。那么，当下标数组排完序之后，里面的顺序就相当于 heights 这个数组排完序之后的下标。之后通过排序后的下标，依次找到原来的 name ，完成对名字的排序。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                vector&lt;string&gt; sortPeople(vector&lt;string&gt;&amp; names, vector&lt;int&gt;&amp; heights) 
                {
                    int n = heights.size();

                    // 创建一个下标数组，用这个数组重写排序方法，按照身高排序，再利用这个下标数组找到对应的身高和名字
                    vector&lt;int&gt; index;
                    for(int i = 0; i &lt; n; i++) index.push_back(i);

                    // 对下标进行排序
                    sort(index.begin(), index.end(),  [&amp;](int i, int j)
                    {
                        return heights[i] &gt; heights[j];
                    });

                    vector&lt;string&gt; ret;
                    for(int i = 0; i &lt; n; i++)
                        ret.push_back(names[index[i]]);

                    return ret;
                }
            };
</code></pre>
<h3>12. 优势洗牌</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/advantage-shuffle/description/">Leetcode -870.优势洗牌</a></p>
<p>Leetcode -870.优势洗牌</p>
<p>题目：给定两个长度相等的数组 nums1 和 nums2，nums1 相对于 nums2 的优势可以用满足 nums1[i] &gt; nums2[i] 的索引 i 的数目来描述。</p>
<p>返回 nums1 的任意排列，使其相对于 nums2 的优势最大化。</p>
<p>示例 1：
 <br>
 输入：nums1 = [2, 7, 11, 15], nums2 = [1, 10, 4, 11]
 <br>
 输出：[2, 11, 7, 15]</p>
<p>示例 2：
 <br>
 输入：nums1 = [12, 24, 8, 32], nums2 = [13, 25, 32, 11]
 <br>
 输出：[24, 32, 8, 12]</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums1.length &lt;= 10^5</li>
 <li>nums2.length == nums1.length</li>
 <li>0 &lt;= nums1[i], nums2[i] &lt;= 10^9</li>
</ul>
<p><strong>思路：在做此题之前我们需要了解一个故事：田忌赛马。由田忌赛马的故事我们可以得出一个最优的决策方式：</strong></p>
<ul>
 <li><strong>当己方此时最差的比不过对面最差的时候，让我方最差的去换取掉对面最好的（反正都要输，不如去拖掉对面一个最强的）；</strong></li>
 <li><strong>当己方此时最差的能比得上对面最差的时候，就让两者比对下去（最差的都能获胜，为什么还要输呢？）。</strong></li>
</ul>
<p><strong>由此我们每次的决策，都会使我方处于优势。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                vector&lt;int&gt; advantageCount(vector&lt;int&gt;&amp; nums1, vector&lt;int&gt;&amp; nums2) 
                {
                    int n = nums2.size();
                    vector&lt;int&gt; index(n);
                    for(int i = 0; i &lt; n; i++) index[i] = i;

                    sort(nums1.begin(), nums1.end());  // 对nums1排序
                    sort(index.begin(), index.end(), [&amp;](int x, int y) 
                    {
                        return nums2[x] &lt; nums2[y];
                    });   //对nums2的下标数组重写排序方法

                    vector&lt;int&gt; ret(n);
                    int left = 0, right = n - 1; // left 和 right 指向index数组的下标

                    // 田忌赛马（贪心）
                    // 遍历 nums1 数组，用nums1的下等马(最小数)跟nums2的下等马(最小数)比较，如果比nums2中的马大，就直接放入ret数组中以 index[left] 为下标的位置，然后 left 迭代往后走；否则用 nums1 的下等马(最小数) 换取 nums2 的上等马(最大数)，然后 right 迭代往前走
                    for(auto&amp; num : nums1)
                    {
                        if(num &gt; nums2[index[left]]) ret[index[left++]] = num;
                        else ret[index[right--]] = num;
                    }
                    return ret;
                }
            };
</code></pre>
<h3>13. 最长回文串</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/longest-palindrome/">Leetcode -409.最长回文串</a></p>
<p>Leetcode -409.最长回文串</p>
<p>题目：给定一个包含大写字母和小写字母的字符串 s ，返回 通过这些字母构造成的 最长的回文串 。</p>
<p>在构造过程中，请注意 区分大小写 。比如 “Aa” 不能当做一个回文字符串。</p>
<p>示例 1:
 <br>
 输入:s = “abccccdd”
 <br>
 输出 : 7
 <br>
 解释 :
 <br>
 我们可以构造的最长的回文串是"dccaccd”, 它的长度是 7。</p>
<p>示例 2 :
 <br>
 输入 : s = “a”
 <br>
 输出 : 1</p>
<p>示例 3：
 <br>
 输入 : s = “aaaaaccc”
 <br>
 输出 : 7</p>
<p>提示 :</p>
<ul>
 <li>1 &lt;= s.length &lt;= 2000</li>
 <li>s 只由小写 和 / 或 大写英文字母组成</li>
</ul>
<p><strong>贪心策略：用尽可能多的字符去构造回文串：</strong></p>
<ul>
 <li><strong>如果字符出现偶数个，那么全部都可以用来构造回文串；</strong></li>
 <li><strong>如果字符出现奇数个，减去一个之后，剩下的字符能够全部用来构造回文串；</strong></li>
 <li><strong>最后再判断一下，如果有字符出现奇数个，就把它单独拿出来放在中间；</strong></li>
</ul>
<p>代码如下：</p>
<pre><code>            class Solution 
            {
            public:
                int longestPalindrome(string s) 
                {
                    vector&lt;int&gt; arr(128);
                    for(auto&amp; ch : s) arr[ch - 'A']++;

                    bool flag = true;
                    int ret = 0;
                    for(int i = 0; i &lt; 128; i++)
                    {
                        // 偶数个
                        if(arr[i] % 2 == 0) 
                        {
                            ret += arr[i];
                        }
                        // 奇数个
                        else
                        {
                            if(flag)
                            {
                                ret += arr[i];
                                flag = false;
                            }
                            else
                            {
                                ret += arr[i] - 1;
                            }
                        }
                    }
                    return ret;
                }
            };
</code></pre>
<h3>14. 增减字符串匹配</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/di-string-match/">Leetcode -942.增减字符串匹配</a></p>
<p>Leetcode -942.增减字符串匹配</p>
<p>题目：由范围[0, n] 内所有整数组成的 n + 1 个整数的排列序列可以表示为长度为 n 的字符串 s ，其中 :</p>
<p>如果 perm[i] &lt; perm[i + 1] ，那么 s[i] == 'I'
 <br>
 如果 perm[i] &gt; perm[i + 1] ，那么 s[i] == 'D'
 <br>
 给定一个字符串 s ，重构排列 perm 并返回它。如果有多个有效排列perm，则返回其中 任何一个 。</p>
<p>示例 1：
 <br>
 输入：s = “IDID”
 <br>
 输出：[0, 4, 1, 3, 2]</p>
<p>示例 2：
 <br>
 输入：s = “III”
 <br>
 输出：[0, 1, 2, 3]</p>
<p>示例 3：
 <br>
 输入：s = “DDI”
 <br>
 输出：[3, 2, 0, 1]</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= s.length &lt;= 10^5</li>
 <li>s 只包含字符 “I” 或 “D”</li>
</ul>
<p><strong>贪心策略：</strong></p>
<ul>
 <li>
  <p><strong>当遇到 'I' 的时候，为了让下一个上升的数可选择的「范围更多」，当前选择「最小」的那个数；</strong></p>
 </li>
 <li>
  <p><strong>当遇到 'D' 的时候，为了让下一个下降的数可选择的「范围更多」，选择当前「最大」的那个数。</strong></p>
  <pre><code>        class Solution {
        public:
            vector&lt;int&gt; diStringMatch(string s) 
            {
                vector&lt;int&gt; ret;

                // left 和 right 分别标记最大值和最小值
                int left = 0, right = s.size();
                for(auto&amp; ch : s)
                {
                    if(ch == 'I') ret.push_back(left++);
                    else ret.push_back(right--);
                }

                // 把最后一个数放进去
                ret.push_back(left);

                return ret;
            }
        };
</code></pre>
 </li>
</ul>
<h3>15. 分发饼干</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/assign-cookies/">Leetcode -455.分发饼干</a></p>
<p>Leetcode -455.分发饼干</p>
<p>题目：假设你是一位很棒的家长，想要给你的孩子们一些小饼干。但是，每个孩子最多只能给一块饼干。</p>
<p>对每个孩子 i，都有一个胃口值 g[i]，这是能让孩子们满足胃口的饼干的最小尺寸；并且每块饼干 j，都有一个尺寸 s[j] 。
 <br>
 如果 s[j] &gt;= g[i]，我们可以将这个饼干 j 分配给孩子 i ，这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子，并输出这个最大数值。</p>
<p>示例 1:
 <br>
 输入: g = [1, 2, 3], s = [1, 1]
 <br>
 输出 : 1
 <br>
 解释 :
 <br>
 你有三个孩子和两块小饼干，3个孩子的胃口值分别是：1, 2, 3。
 <br>
 虽然你有两块小饼干，由于他们的尺寸都是1，你只能让胃口值是1的孩子满足。
 <br>
 所以你应该输出1。</p>
<p>示例 2 :
 <br>
 输入 : g = [1, 2], s = [1, 2, 3]
 <br>
 输出 : 2
 <br>
 解释 :
 <br>
 你有两个孩子和三块小饼干，2个孩子的胃口值分别是1, 2。
 <br>
 你拥有的饼干数量和尺寸都足以让所有孩子满足。
 <br>
 所以你应该输出2.</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= g.length &lt;= 3 * 10^4</li>
 <li>0 &lt;= s.length &lt;= 3 * 10^4</li>
 <li>1 &lt;= g[i], s[j] &lt;= 2^31 - 1</li>
</ul>
<p><strong>贪心策略：先将两个数组排序。针对胃口较小的孩子，从小到大挑选饼干：</strong></p>
<ul>
 <li><strong>如果当前饼干能满足，直接喂给这个孩子（最小的饼干都能满足，不要浪费大饼干）；</strong></li>
 <li><strong>如果当前饼干不能满足，放弃这个饼干，去检测下一个饼干（这个饼干连最小胃口的孩子都无法满足，其它的孩子也不能满足）。</strong></li>
</ul>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                int findContentChildren(vector&lt;int&gt;&amp; g, vector&lt;int&gt;&amp; s) 
                {   
                    // 排序
                    sort(g.begin(), g.end());
                    sort(s.begin(), s.end());

                    int kids = 0, mine = 0, ret = 0;
                    while(kids &lt; g.size() &amp;&amp; mine &lt; s.size())
                    {
                        // 如果能够满足当前孩子，就把饼干分给这个孩子
                        if(g[kids] &lt;= s[mine])
                        {
                            kids++;
                            ret++;
                        }
                        // 无论满不满足当前孩子，都要往下遍历饼干
                        mine++;
                    }
                    return ret;
                }
            };
</code></pre>
<h3>16. 最优除法</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/optimal-division/description/">Leetcode -553.最优除法</a></p>
<p>Leetcode -553.最优除法</p>
<p>题目：给定一正整数数组 nums，nums 中的相邻整数将进行浮点除法。例如，[2, 3, 4] -&gt; 2 / 3 / 4 。</p>
<p>例如，nums = [2, 3, 4]，我们将求表达式的值 “2/3/4”。
 <br>
 但是，你可以在任意位置添加任意数目的括号，来改变算数的优先级。你需要找出怎么添加括号，以便计算后的表达式的值为最大值。</p>
<p>以字符串格式返回具有最大值的对应表达式。</p>
<p>注意：你的表达式不应该包含多余的括号。</p>
<p>示例 1：
 <br>
 输入 : [1000, 100, 10, 2]
 <br>
 输出 : “1000/(100/10/2)”
 <br>
 解释 : 1000 / (100 / 10 / 2) = 1000 / ((100 / 10) / 2) = 200
 <br>
 但是，以下加粗的括号 “1000/((100/10)/2)” 是冗余的，
 <br>
 因为他们并不影响操作的优先级，所以你需要返回 “1000/(100/10/2)“。
 <br>
 其他用例 :
 <br>
 1000 / (100 / 10) / 2 = 50
 <br>
 1000 / (100 / (10 / 2)) = 50
 <br>
 1000 / 100 / 10 / 2 = 0.5
 <br>
 1000 / 100 / (10 / 2) = 2</p>
<p>示例 2:
 <br>
 输入: nums = [2, 3, 4]
 <br>
 输出 : “2/(3/4)”
 <br>
 解释 : (2 / (3 / 4)) = 8 / 3 = 2.667
 <br>
 可以看出，在尝试了所有的可能性之后，我们无法得到一个结果大于 2.667 的表达式。</p>
<p>说明 :</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 10</li>
 <li>2 &lt;= nums[i] &lt;= 1000</li>
</ul>
<p>对于给定的输入只有一种最优除法。</p>
<p><strong>贪心策略：在最终的结果中，前两个数的位置是无法改变的。 
  <br>因为每一个数的都是大于等于 2 的，为了让结果更大，我们应该尽可能的把剩下的数全都放在「分子」上。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                string optimalDivision(vector&lt;int&gt;&amp; nums) 
                {
                    //在最终的结果中，前两个数的位置是无法改变的。
                    //因为每⼀个数都是大于等于 2 的，为了让结果更大，我们应该尽可能的把剩下的数全都放在分⼦上
                    if(nums.size() == 1) return to_string(nums[0]);
                    if(nums.size() == 2) return to_string(nums[0]) + "/" + to_string(nums[1]);

                    string ret = to_string(nums[0]) + "/(" + to_string(nums[1]);

                    for(int i = 2; i &lt; nums.size(); i++)
                    {
                        ret += "/" + to_string(nums[i]);
                    }

                    ret += ")";
                    return ret;
                }
            };
</code></pre>
<h3>17. 跳跃游戏Ⅱ</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/jump-game-ii/description/">Leetcode -45.跳跃游戏Ⅱ</a></p>
<p>Leetcode -45.跳跃游戏Ⅱ</p>
<p>题目：给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。</p>
<p>每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说，如果你在 nums[i] 处，你可以跳转到任意 nums[i + j] 处:</p>
<p>0 &lt;= j &lt;= nums[i]
 <br>
 i + j &lt; n
 <br>
 返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。</p>
<p>示例 1:
 <br>
 输入: nums = [2, 3, 1, 1, 4]
 <br>
 输出 : 2
 <br>
 解释 : 跳到最后一个位置的最小跳跃数是 2。
 <br>
 从下标为 0 跳到下标为 1 的位置，跳 1 步，然后跳 3 步到达数组的最后一个位置。</p>
<p>示例 2 :
 <br>
 输入 : nums = [2, 3, 0, 1, 4]
 <br>
 输出 : 2</p>
<p>提示 :</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 10^4</li>
 <li>0 &lt;= nums[i] &lt;= 1000</li>
</ul>
<p>题目保证可以到达 nums[n - 1]</p>
<p><strong>思路：类似层序遍历的过程：用类似层序遍历的过程，将第 i 次跳跃的「起始位置」和「结束位置」找出来，用这次跳跃的情况，更新出下一次跳跃的「起始位置」和「终止位置」。这样「循环往复」，就能更新出到达 n - 1 位置的最小跳跃步数。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                int jump(vector&lt;int&gt;&amp; nums) 
                {
                    //⽤类似层序遍历的过程，将第 i 次跳跃的「起始位置」和「结束位置」找出来，⽤这次跳跃的情况，更新出下⼀次跳跃的「起始位置」和「终⽌位置」
                    int left = 0, right = 0, maxPos = 0, ret = 0;
                    while(left &lt;= right)  // 以防跳不到 n - 1位置
                    {
                        // 先判断⼀下是否已经能跳到最后⼀个位置
                        if(maxPos &gt;= nums.size() - 1) return ret;

                        for(int i = left; i &lt;= right; i++)
                        {
                            maxPos = max(i + nums[i], maxPos);
                        }
                        left = right + 1;
                        right = maxPos;
                        ret++;
                    }
                    return -1; // 跳不到的情况
                }
            };
</code></pre>
<h3>18. 跳跃游戏</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/jump-game/">Leetcode -55.跳跃游戏</a></p>
<p>Leetcode -55.跳跃游戏</p>
<p>题目：给你一个非负整数数组 nums ，你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。</p>
<p>判断你是否能够到达最后一个下标，如果可以，返回 true ；否则，返回 false 。</p>
<p>示例 1：
 <br>
 输入：nums = [2, 3, 1, 1, 4]
 <br>
 输出：true
 <br>
 解释：可以先跳 1 步，从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。</p>
<p>示例 2：
 <br>
 输入：nums = [3, 2, 1, 0, 4]
 <br>
 输出：false
 <br>
 解释：无论怎样，总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 ， 所以永远不可能到达最后一个下标。</p>
<p>提示：</p>
<ul>
 <li>1 &lt;= nums.length &lt;= 10^4</li>
 <li>0 &lt;= nums[i] &lt;= 10^5</li>
</ul>
<p><strong>思路：和 跳跃游戏II 一样，仅需修改一下返回值即可。</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                bool canJump(vector&lt;int&gt;&amp; nums) 
                {
                    // 和跳跃游戏Ⅱ的思路一样
                    int left = 0, right = 0, maxPos = 0, n = nums.size();
                    while(left &lt;= right)
                    {
                        if(maxPos &gt;= n - 1) return true;

                        for(int i = left; i &lt;= right; i++)
                        {
                            maxPos = max(maxPos, nums[i] + i);
                        }
                        left = right + 1;
                        right = maxPos;
                    }
                    return false;
                }
            };
</code></pre>
<h3>19. 加油站</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/gas-station/">Leetcode -134.加油站</a></p>
<p>Leetcode -134.加油站</p>
<p>题目：在一条环路上有 n 个加油站，其中第 i 个加油站有汽油 gas[i] 升。</p>
<p>你有一辆油箱容量无限的的汽车，从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发，开始时油箱为空。</p>
<p>给定两个整数数组 gas 和 cost ，如果你可以按顺序绕环路行驶一周，则返回出发时加油站的编号，否则返回 -1 。如果存在解，则 保证 它是 唯一 的。</p>
<p>示例 1:
 <br>
 输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
 <br>
 输出: 3
 <br>
 解释:
 <br>
 从 3 号加油站(索引为 3 处)出发，可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
 <br>
 开往 4 号加油站，此时油箱有 4 - 1 + 5 = 8 升汽油
 <br>
 开往 0 号加油站，此时油箱有 8 - 2 + 1 = 7 升汽油
 <br>
 开往 1 号加油站，此时油箱有 7 - 3 + 2 = 6 升汽油
 <br>
 开往 2 号加油站，此时油箱有 6 - 4 + 3 = 5 升汽油
 <br>
 开往 3 号加油站，你需要消耗 5 升汽油，正好足够你返回到 3 号加油站。
 <br>
 因此，3 可为起始索引。</p>
<p>示例 2:
 <br>
 输入: gas = [2,3,4], cost = [3,4,3]
 <br>
 输出: -1
 <br>
 解释:
 <br>
 你不能从 0 号或 1 号加油站出发，因为没有足够的汽油可以让你行驶到下一个加油站。
 <br>
 我们从 2 号加油站出发，可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
 <br>
 开往 0 号加油站，此时油箱有 4 - 3 + 2 = 3 升汽油
 <br>
 开往 1 号加油站，此时油箱有 3 - 3 + 3 = 3 升汽油
 <br>
 你无法返回 2 号加油站，因为返程需要消耗 4 升汽油，但是你的油箱只有 3 升汽油。
 <br>
 因此，无论怎样，你都不可能绕环路行驶一周。</p>
<p>提示:</p>
<ul>
 <li>gas.length == n</li>
 <li>cost.length == n</li>
 <li>1 &lt;= n &lt;= 10^5</li>
 <li>0 &lt;= gas[i], cost[i] &lt;= 10^4</li>
</ul>
<p><strong>思路：依次枚举所有的起点；从起点开始，模拟⼀遍加油的流程；使用贪⼼优化：我们发现，当从 i 位置出发，⾛了 step 步之后，如果失败了。那么 [i, i + step] 这个区间内任意⼀个位置作为起点，都不可能环绕⼀圈。因此我们枚举的下⼀个起点，应该是 i + step + 1.</strong></p>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                int canCompleteCircuit(vector&lt;int&gt;&amp; gas, vector&lt;int&gt;&amp; cost) 
                {
                    int n = gas.size();

                    // 暴力枚举每一个位置，模拟从每个位置开始出发，能不能回到原位，使用step 变量标记走了多少步
                    for(int i = 0; i &lt; n; i++)
                    {
                        int rest = 0, step = 0; // step 记录走的步数，判断是否已经走了一圈
                        for(; step &lt; n; step++)
                        {
                            int index = (i + step) % n;  // 使下标呈环状，当越界时继续返回数组的起始位置
                            rest = rest + gas[index] - cost[index];
                            if(rest &lt; 0) break;
                        }
                        if(rest &gt;= 0) return i;

                        // 优化，不加这句会超时；这句话的原理就是假设我们从a位置开始枚举到d位置，发现不行，那么从b、c位置开始枚举到d也是不行的，所以我们直接让i跳过step步，从它的下一步开始枚举（循环内会++）
                        i += step;  
                    }
                    return -1;
                }
            };
</code></pre>
<h3>20. 单调递增的数字</h3>
<p><strong>题目链接</strong> -&gt; <a href="https://leetcode.cn/problems/monotone-increasing-digits/description/">Leetcode -738.单调递增的数字</a></p>
<p>Leetcode -738.单调递增的数字</p>
<p>题目：当且仅当每个相邻位数上的数字 x 和 y 满足 x &lt;= y 时，我们称这个整数是单调递增的。</p>
<p>给定一个整数 n ，返回 小于或等于 n 的最大数字，且数字呈 单调递增 。</p>
<p>示例 1:
 <br>
 输入: n = 10
 <br>
 输出: 9</p>
<p>示例 2:
 <br>
 输入: n = 1234
 <br>
 输出: 1234</p>
<p>示例 3:
 <br>
 输入: n = 332
 <br>
 输出: 299</p>
<p>提示:</p>
<ul>
 <li>0 &lt;= n &lt;= 10^9</li>
</ul>
<p><strong>思路：</strong></p>
<ol>
 <li><strong>将整数 n 转换为字符串形式，以便于对其进⾏修改操作，并将其存储在字符串变量 str 中。</strong></li>
 <li><strong>初始化⼀个变量 pos，⽤于记录从⾼位到低位第⼀个不满⾜单调递增的数字的位置。初始值为 -1，表示在第⼀位之前。</strong></li>
 <li><strong>从⾼位到低位遍历字符串 str，寻找第⼀个不满⾜单调递增的数字的位置。当遇到⼀个数字⼩于前⼀个数字时，记录这个位置为 pos，并退出循环。</strong></li>
 <li><strong>如果 pos 被更新，说明存在需要修改的数字，执⾏以下操作：</strong></li>
</ol>
<p><strong>a. 将 pos 位置后的所有数字修改为 9，这样可以保证修改后的数字是最⼤的。</strong></p>
<p><strong>b. 将 pos 位置的数字减⼀，因为这是最⼩的减少量，同时也能够保证修改后的数字仍然⼩于原数字 n.</strong></p>
<p><strong>c. 检查 pos 前⼀位数字是否⼩于减⼀后的 pos 位置数字，如果⼩于，则说明在 pos 位置之前还有相同的数字，需要将 pos 前⼀位数字减⼀，并将 pos 位置修改为 9。</strong></p>
<p><strong>d.重复这个操作，直到 pos 前⼀位数字⼤于等于减⼀后的 pos 位置数字或 pos 已经移动到了第⼀位。</strong></p>
<ol>
 <li><strong>将修改后的字符串 str 转换为整型数字并返回。</strong></li>
</ol>
<p>代码如下：</p>
<pre><code>            class Solution {
            public:
                int monotoneIncreasingDigits(int n) 
                {
                    string str = to_string(n);
                    int index = 0;

                    // 找到第一个递减的位置
                    while(index + 1 &lt; str.size() &amp;&amp; str[index] &lt;= str[index + 1]) index++;

                    if(index + 1 == str.size()) return n; // 全是递增的情况

                    // 回推，如果第一个递减的位置的数字相同，找到最高位的那个
                    while(index - 1 &gt;= 0 &amp;&amp; str[index] == str[index - 1]) index--;
                    str[index]--; // 找到后减一

                    // 后面的全变9
                    for(int i = index + 1; i &lt; str.size(); i++)
                        str[i] = '9';

                    return stoi(str);
                }
            };
</code></pre>
<p>原文链接: https://blog.csdn.net/YoungMLet/article/details/134962185</p>]]></description><guid isPermaLink="false">/archives/4412b32e-dad3-45c9-a2a6-cb349ec5162e</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 08:36:56 GMT</pubDate></item><item><title><![CDATA[Java中的经典排序算法：快速排序、归并排序和计数排序详解（如果想知道Java中有关快速排序、归并排序和计数排序的知识点，那么只看这一篇就足够了！）]]></title><link>https://wiki.dgfygk.com/archives/77b87cf5-b988-4f29-950d-6f13654b975d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E7%9A%84%E7%BB%8F%E5%85%B8%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%EF%BC%9A%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E3%80%81%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E5%92%8C%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E8%AF%A6%E8%A7%A3%EF%BC%88%E5%A6%82%E6%9E%9C%E6%83%B3%E7%9F%A5%E9%81%93Java%E4%B8%AD%E6%9C%89%E5%85%B3%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E3%80%81%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E5%92%8C%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E7%9A%84%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%8C%E9%82%A3%E4%B9%88%E5%8F%AA%E7%9C%8B%E8%BF%99%E4%B8%80%E7%AF%87%E5%B0%B1%E8%B6%B3%E5%A4%9F%E4%BA%86%EF%BC%81%EF%BC%89&amp;url=/archives/77b87cf5-b988-4f29-950d-6f13654b975d" width="1" height="1" alt="" style="opacity:0;">
<p><strong>前言：排序算法在计算机科学中占有重要地位，不同的算法适用于不同的场景。本文将深入探讨快速排序、归并排序和计数排序。</strong></p>
<hr>
<blockquote>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7264bb4539094cb1a436d7a0d5d12d42.jpeg&amp;size=m" alt=""></p>
 <p><strong>✨✨✨这里是秋刀鱼不做梦的BLOG</strong></p>
 <p><strong>✨✨✨想要了解更多内容可以访问我的主页</strong><a href="https://blog.csdn.net/2302_80198073?type=blog" title="秋刀鱼不做梦-CSDN博客">秋刀鱼不做梦-CSDN博客</a></p>
</blockquote>
<p><strong>先让我们看一下本文大致的讲解内容：</strong></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3e99d7e25cce4977b3c72d1d83fcb22a.png&amp;size=m" alt=""></strong></p>
<p><strong>对于每个排序算法，我们都会从算法简介、其原理与步骤、代码实现、时间复杂度分析、空间复杂度分析与该排序算法的应用场景这几个方面来进行讲解。</strong></p>
<p><strong>目录</strong></p>
<p><strong><a href="https://wiki.dgfygk.com#1.%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%20%28Quicksort%29">1.快速排序 (Quicksort)</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%881%EF%BC%89%E7%AE%97%E6%B3%95%E7%AE%80%E4%BB%8B">（1）算法简介</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%882%EF%BC%89%E7%AE%97%E6%B3%95%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%8E%E6%AD%A5%E9%AA%A4">（2）算法的原理与步骤</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%883%EF%BC%89Java%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0">（3）Java代码实现</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%E3%80%901%E3%80%91Hoare%E6%B3%95">【1】Hoare法</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%E3%80%902%E3%80%91%E6%8C%96%E5%9D%91%E6%B3%95">【2】挖坑法</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%E3%80%903%E3%80%91%E5%89%8D%E5%90%8E%E6%8C%87%E9%92%88%E6%B3%95">【3】前后指针法</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%EF%BC%884%EF%BC%89%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%92%8C%E7%A9%BA%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6">（4）时间复杂度和空间复杂度</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%885%EF%BC%89%E7%AE%97%E6%B3%95%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF">（5）算法的应用场景</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#2.%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%20%28Merge%20Sort%29">2.归并排序 (Merge Sort)</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%881%EF%BC%89%E7%AE%97%E6%B3%95%E7%AE%80%E4%BB%8B">（1）算法简介</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%EF%BC%882%EF%BC%89%E7%AE%97%E6%B3%95%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%8E%E6%AD%A5%E9%AA%A4">（2）算法的原理与步骤</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%883%EF%BC%89Java%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0">（3）Java代码实现</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%884%EF%BC%89%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%92%8C%E7%A9%BA%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6">（4）时间复杂度和空间复杂度</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%885%EF%BC%89%E7%AE%97%E6%B3%95%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF">（5）算法的应用场景</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#3.%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%20%28Counting%20Sort%29">3.计数排序 (Counting Sort)</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%881%EF%BC%89%E7%AE%97%E6%B3%95%E7%AE%80%E4%BB%8B">（1）算法简介</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%EF%BC%882%EF%BC%89%E7%AE%97%E6%B3%95%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%8E%E6%AD%A5%E9%AA%A4">（2）算法的原理与步骤</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%883%EF%BC%89Java%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0">（3）Java代码实现</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%884%EF%BC%89%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%92%8C%E7%A9%BA%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6">（4）时间复杂度和空间复杂度</a></strong></p>
<p><strong><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%885%EF%BC%89%E7%AE%97%E6%B3%95%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF">（5）算法的应用场景</a></strong></p>
<hr>
<p>
 <br>
</p>
<h2>1.快速排序 (Quicksort)</h2>
<h3>（1）算法简介</h3>
<blockquote>
 <p><strong>快速排序是一种高效的排序算法，由C.A.R. Hoare在1960年提出。它采用分治法（Divide and Conquer），通过递归地将未排序的部分分割为较小的子数组进行排序，再将其合并。快速排序的平均时间复杂度为 O(nlog⁡n)，在大多数情况下比其他 O(nlog⁡n) 的算法，如归并排序，具有更好的性能。</strong></p>
</blockquote>
<p>当然，我相信读者读完快速排序的简介之后，对于快速排序算法的认识还是没什么认识的，接下来让我们直接带着你边学如何实现排序算法边理解该算法的内核。</p>
<p>
 <br>
</p>
<h3>（2）算法的原理与步骤</h3>
<p><strong>快速排序的核心思想是选择一个"基准”（pivot），并通过一系列的比较和交换操作将数组分成两部分：一部分所有元素小于基准，另一部分所有元素大于基准。然后，对这两部分分别递归地应用快速排序。具体步骤如下：</strong></p>
<ol>
 <li>
  <p><strong>选择基准</strong>: 从数组中选择一个元素作为基准。选择基准的方法有多种，如选择第一个元素、最后一个元素、随机选择或三数取中法。</p>
 </li>
 <li>
  <p><strong>分区 (Partitioning)</strong>: 通过遍历数组，将所有小于基准的元素放在基准的左边，所有大于基准的元素放在右边。</p>
 </li>
 <li>
  <p><strong>递归排序</strong>: 对基准左边和右边的子数组递归地进行快速排序。</p>
 </li>
 <li>
  <p><strong>组合</strong>: 由于分区已经确保了基准左边的元素小于基准，右边的元素大于基准，因此当递归完成后，数组已经是有序的。</p>
 </li>
</ol>
<p>
 <br>
</p>
<p><strong>——看完了上述大体如何实现快速排序算法之后，让我们从代码的层面来实现一下快速排序算法。</strong></p>
<p>
 <br>
</p>
<h3>（3）Java代码实现</h3>
<p><strong>以下为Java中实现快速排序的大致代码：</strong></p>
<pre><code class="java">public void quickSort(int[] array) {
    int end = array.length - 1;
    quickSort(array, 0, end);
}

private void quickSortHo(int[] array, int left, int right) {
    // 如果子数组长度为1或更小，则返回（即递归终止条件）
    if (left &gt;= right) {
        return;
    }

    // 查找基准元素的位置，并将数组分成两部分
    int target = findTarget(array, left, right);

    // 对基准元素左边的子数组进行递归排序
    quickSort(array, left, target - 1);

    // 对基准元素右边的子数组进行递归排序
    quickSort(array, target + 1, right);
}
</code></pre>
<p><strong>从上述代码中我们可以看到我们在寻找基准元素位置的时候，使用了findTarget（）方法，那么这个方法该如何实现呢？</strong></p>
<p>
 <br>
</p>
<p><strong>实现该方法的方式大致有三种，分别是：Hoare法、挖坑法和前后指针法。接下来我们一一进行讲解。</strong></p>
<h4><strong>【1】Hoare法</strong></h4>
<pre><code class="java">public int findTargetH(int[] array, int left, int right) {
    // 获取中间索引并将中间元素交换到数组的最左端
    int mid = middleIndex(array, left, right);
    swap(array, left, mid);

    // 将最左端的元素作为基准元素
    int temp = array[left];
    int keyi = left; // 保存基准元素的初始位置

    // 开始从数组的两端向中间扫描
    while (left &lt; right) {
        // 从右向左扫描，找到第一个小于基准的元素
        while (left &lt; right &amp;&amp; array[right] &gt;= temp) {
            right--;
        }
        // 从左向右扫描，找到第一个大于基准的元素
        while (left &lt; right &amp;&amp; array[left] &lt;= temp) {
            left++;
        }
        // 交换左侧找到的大于基准的元素和右侧找到的小于基准的元素
        swap(array, left, right);
    }

    // 将基准元素放置到最终的位置，即左、右指针相遇的位置
    swap(array, keyi, left);

    // 返回基准元素的位置索引
    return left;
}
</code></pre>
<p><strong>解释说明：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong><code>middleIndex(array, left, right)</code></strong>：假设这是一个用于计算数组中间索引的方法，以获取中间元素的位置。</p>
  </li>
  <li>
   <p><strong><code>swap(array, left, mid)</code></strong>：将中间元素和最左端元素交换，使得基准元素（最左端元素）在处理前放到适当的位置。</p>
  </li>
  <li>
   <p><strong><code>temp</code></strong>：基准元素，用于比较其他元素。</p>
  </li>
  <li>
   <p><strong><code>keyi</code></strong>：保存基准元素的初始索引位置，以便最终将基准元素放到正确的位置。</p>
  </li>
  <li>
   <p><strong><code>while (left &lt; right)</code></strong>：循环扫描数组，直到左右指针相遇。</p>
  </li>
  <li>
   <p><strong><code>swap(array, left, right)</code></strong>：当找到左侧大于基准的元素和右侧小于基准的元素时，交换它们的位置。</p>
  </li>
  <li>
   <p><strong>最后一次<code>swap</code></strong>：将基准元素放回到排序后应在的位置上，并返回该位置的索引。</p>
  </li>
 </ul>
</blockquote>
<p><strong>——这就是Hoare实现查找基准元素位置。</strong></p>
<p>
 <br>
</p>
<h4>【2】挖坑法</h4>
<pre><code class="java">public int findTargetW(int[] array, int left, int right) {
    // 将最左端的元素作为基准元素
    int temp = array[left];

    // 开始从数组的两端向中间扫描
    while (left &lt; right) {
        // 从右向左扫描，找到第一个小于基准的元素
        while (left &lt; right &amp;&amp; array[right] &gt;= temp) {
            right--;
        }
        // 将小于基准的元素移到左侧
        array[left] = array[right];

        // 从左向右扫描，找到第一个大于基准的元素
        while (left &lt; right &amp;&amp; array[left] &lt;= temp) {
            left++;
        }
        // 将大于基准的元素移到右侧
        array[right] = array[left];
    }

    // 将基准元素放置到最终的位置，即左、右指针相遇的位置
    array[left] = temp;

    // 返回基准元素的位置索引
    return left;
}
</code></pre>
<p><strong>解释说明：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong><code>temp</code></strong>：基准元素，取自数组的最左端，用于比较和调整其他元素的位置。</p>
  </li>
  <li>
   <p><strong><code>while (left &lt; right)</code></strong>：主循环，通过左右指针的移动将数组分为小于基准和大于基准的两部分，直到左右指针相遇。</p>
  </li>
  <li>
   <p><strong><code>array[left] = array[right]</code></strong>：将右侧扫描中找到的小于基准的元素移动到左侧。</p>
  </li>
  <li>
   <p><strong><code>array[right] = array[left]</code></strong>：将左侧扫描中找到的大于基准的元素移动到右侧。</p>
  </li>
  <li>
   <p><strong>最后一步</strong>：将基准元素放置在最终的正确位置，并返回该位置的索引，以供进一步的递归使用。</p>
  </li>
 </ul>
</blockquote>
<p><strong>——这就是挖坑法实现查找基准元素位置。</strong></p>
<p>
 <br>
</p>
<h4>【3】前后指针法</h4>
<pre><code class="java">public int findTargetQ(int[] array, int left, int right) {
    // 当前基准元素的位置（初始为左边界）
    int cur = left;

    // 从基准元素的下一个位置开始遍历
    int prev = left + 1;

    // 保存基准元素的值
    int temp = array[left];

    // 遍历数组，直到右边界
    while (prev &lt;= right) {
        // 如果当前元素小于基准元素且 cur 与 prev 不相等，则交换这两个元素
        if (array[prev] &lt; temp &amp;&amp; ++cur != prev) {
            swap(array, cur, prev);
        }
        // 移动到下一个元素
        prev++;
    }

    // 将基准元素放置到最终的位置，即 cur 的位置
    swap(array, left, cur);

    // 返回基准元素的位置索引
    return cur;
}
</code></pre>
<p><strong>解释说明：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong><code>cur</code></strong>：表示当前已经确定位置的元素的索引，初始值为左边界，负责标记小于基准元素的部分的末尾。</p>
  </li>
  <li>
   <p><strong><code>prev</code></strong>：用于遍历数组的索引，从基准元素的下一个位置开始。</p>
  </li>
  <li>
   <p><strong><code>temp</code></strong>：基准元素的值，用于与其他元素进行比较。</p>
  </li>
  <li>
   <p><strong><code>while (prev &lt;= right)</code></strong>：遍历数组中的元素，寻找小于基准元素的值，并将其与当前元素进行交换。</p>
  </li>
  <li>
   <p><strong><code>if (array[prev] &lt; temp &amp;&amp; ++cur != prev)</code></strong> ：如果当前元素小于基准元素，并且 <code>cur</code> 和 <code>prev</code> 不相等（确保不进行自我交换），则交换这两个元素。</p>
  </li>
  <li>
   <p><strong><code>swap(array, cur, left)</code></strong>：将基准元素放置到最终的位置，即所有小于基准元素的元素的末尾。</p>
  </li>
  <li>
   <p><strong><code>return cur</code></strong>：返回基准元素的位置索引，用于进一步的排序。</p>
  </li>
 </ul>
</blockquote>
<p><strong>——这就是前后指针法实现查找基准元素位置。</strong></p>
<p><strong>通过上述的学习，我们就了解了如何使用Java代码来实现快速排序算法。</strong></p>
<p>
 <br>
</p>
<h3>（4）时间复杂度和空间复杂度</h3>
<ul>
 <li>
  <p><strong>时间复杂度</strong></p>
 </li>
 <li>
  <p><strong>平均时间复杂度</strong>: O(nlog⁡n)。快速排序在分区步骤中，每次都将数组分成两个大致相等的部分，因此递归的深度为 O(log⁡n)，每层递归处理的元素数为 O(n)。</p>
 </li>
 <li>
  <p><strong>最坏时间复杂度</strong>: O(n2)。当每次分区产生的子数组大小极度不均匀（如数组已经有序或逆序）时，递归深度变为 O(n)。</p>
 </li>
 <li>
  <p><strong>空间复杂度</strong>: O(log⁡n)（主要为递归调用栈的空间）。由于每次递归都会消耗栈空间，因此快速排序的空间复杂度与递归的深度相关。</p>
 </li>
</ul>
<p>
 <br>
</p>
<h3>（5）算法的应用场景</h3>
<p><strong>快速排序适用于大部分需要排序的场景，尤其是：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong>大数据集</strong>: 快速排序的平均性能优于其他排序算法，适合处理大规模数据集。</p>
  </li>
  <li>
   <p><strong>内存空间有限的情况</strong>: 相较于归并排序，快速排序的空间复杂度更低，更适合内存资源有限的场景。</p>
  </li>
  <li>
   <p><strong>不需要稳定排序的场景</strong>: 快速排序是一个不稳定的排序算法，不适用于需要保持相同元素相对顺序的场景。</p>
  </li>
 </ul>
</blockquote>
<p><strong>这样我们就学习完了快速排序算法了！！！</strong></p>
<p>
 <br>
</p>
<h2><strong>2.</strong>归并排序 (Merge Sort)</h2>
<h3>（1）算法简介</h3>
<blockquote>
 <p><strong>归并排序是一种稳定的排序算法，采用分治策略，将待排序的数组分成若干子数组，分别对每个子数组进行排序，再将这些子数组合并成一个有序数组。归并排序的时间复杂度为 O(nlog⁡n)，在数据量较大且对排序稳定性要求较高的场景中有较好的表现。</strong></p>
</blockquote>
<p><strong>同样，我们接下来带着你边学如何实现排序算法边理解该算法的内核。</strong></p>
<p>
 <br>
</p>
<h3>（2）算法的原理与步骤</h3>
<p><strong>归并排序的基本思想是将数组分成尽可能小的子数组（每个子数组只有一个元素），然后逐步合并这些子数组，使得合并后的数组有序。具体步骤如下：</strong></p>
<ol>
 <li>
  <p><strong>分割</strong>: 将数组分成两部分，递归地对每一部分进行归并排序。</p>
 </li>
 <li>
  <p><strong>排序并合并</strong>: 当每个子数组的长度为1时，开始合并相邻的子数组。在合并时，使用双指针技术，比较两个子数组的元素，将较小的元素放入临时数组中。</p>
 </li>
 <li>
  <p><strong>递归处理</strong>: 对左右子数组分别递归应用归并排序，直至最终将所有元素合并为一个有序数组。</p>
 </li>
</ol>
<p><strong>——看完了上述大体如何实现归并排序算法之后，让我们从代码的层面来实现一下快速排序算法。</strong></p>
<p>
 <br>
</p>
<h3><strong>（3）</strong> Java代码实现</h3>
<p><strong>以下为Java中实现归并排序的大致代码：</strong></p>
<pre><code class="java">public void mergeSort(int[] array) {
    // 调用归并排序的递归方法，排序整个数组
    mergeInsertSort(array, 0, array.length - 1);
}

public void mergeInsertSort(int[] array, int left, int right) {
    // 如果子数组只有一个元素或为空，则返回（递归终止条件）
    if (left &gt;= right) {
        return;
    }

    // 计算中间位置，将数组分成两部分
    int mid = (left + right) / 2;

    // 对左半部分进行递归排序
    mergeInsertSort(array, left, mid);

    // 对右半部分进行递归排序
    mergeInsertSort(array, mid + 1, right);

    // 合并两个已排序的子数组
    merge(array, left, mid, right);
}

public void merge(int[] array, int left, int mid, int right) {
    // 右半部分的起始位置
    int rightBegin = mid + 1;

    // 左半部分的起始位置
    int leftBegin = left;

    // 用于存放合并结果的临时数组
    int i = 0;
    int[] ret = new int[right - left + 1];

    // 合并两个已排序的子数组
    while (left &lt;= mid &amp;&amp; rightBegin &lt;= right) {
        // 比较左右两部分的元素，将较小的元素放入临时数组
        if (array[left] &lt; array[rightBegin]) {
            ret[i++] = array[left++];
        } else {
            ret[i++] = array[rightBegin++];
        }
    }

    // 将左半部分剩余的元素添加到临时数组中
    while (left &lt;= mid) {
        ret[i++] = array[left++];
    }

    // 将右半部分剩余的元素添加到临时数组中
    while (rightBegin &lt;= right) {
        ret[i++] = array[rightBegin++];
    }

    // 将临时数组中的元素复制回原数组的相应位置
    for (int j = 0; j &lt; ret.length; j++) {
        array[j + leftBegin] = ret[j];
    }
}
</code></pre>
<p><strong>解释：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong><code>mergeSort(int[] array)</code></strong> ：这是归并排序的入口方法，它调用 <code>mergeInsertSort</code> 方法对整个数组进行排序。</p>
  </li>
  <li>
   <p><strong><code>mergeInsertSort(int[] array, int left, int right)</code></strong> ：这是归并排序的递归方法，递归地将数组分成更小的部分并排序，然后调用 <code>merge</code> 方法合并已排序的子数组。</p>
  </li>
  <li>
   <p><strong><code>merge(int[] array, int left, int mid, int right)</code></strong> ：这是合并两个已排序的子数组的方法。它创建一个临时数组 <code>ret</code>，将左右两部分按顺序合并到 <code>ret</code> 中，然后将合并后的结果复制回原数组。</p>
  </li>
 </ul>
</blockquote>
<p><strong>——这样我们就了解了如何使用Java代码来实现归并排序算法。</strong></p>
<p>
 <br>
</p>
<h3>（4）时间复杂度和空间复杂度</h3>
<ul>
 <li>
  <p><strong>时间复杂度</strong>: O(nlog⁡n)。每次分割将数组对半分，深度为 O(log⁡n)，合并过程需要遍历整个数组，因此时间复杂度为 O(nlog⁡n)。</p>
 </li>
 <li>
  <p><strong>空间复杂度</strong>: O(n)。归并排序需要额外的空间来存储临时数组，用于合并过程中临时存放子数组的元素。</p>
 </li>
</ul>
<h3>（5）算法的应用场景</h3>
<p><strong>归并排序的稳定性和时间复杂度使其适用于以下场景：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong>稳定性要求高的排序</strong>: 归并排序是一种稳定排序算法，适用于对相同值的元素相对顺序有要求的场景。</p>
  </li>
  <li>
   <p><strong>外部排序</strong>: 归并排序适用于处理超大数据集的外部排序，由于其稳定性和性能，尤其适用于磁盘文件的排序操作。</p>
  </li>
  <li>
   <p><strong>数据集较大且未全部加载到内存</strong>: 在处理大规模数据时，归并排序由于其分割和合并的特性，可以有效地处理不能一次性加载到内存的数据。</p>
  </li>
 </ul>
</blockquote>
<p><strong>——这样我们学习完了如何在Java中实现归并算法。</strong></p>
<p>
 <br>
</p>
<h2><strong>3.</strong>计数排序 (Counting Sort)</h2>
<h3>（1）算法简介</h3>
<blockquote>
 <p><strong>计数排序是一种非比较排序算法，主要用于对整数进行排序。它通过计算每个元素在数组中出现的次数来确定其在排序后数组中的位置。这种排序算法适用于元素范围较小且数据量较大的场景。</strong></p>
</blockquote>
<p><strong>同样，我们接下来带着你边学如何实现排序算法边理解该算法的内核。</strong></p>
<p>
 <br>
</p>
<h3>（2）算法的原理与步骤</h3>
<p><strong>计数排序的基本思想是创建一个计数数组，通过统计原始数组中每个元素的出现次数来确定它们在排序后数组中的正确位置。具体步骤如下：</strong></p>
<ol>
 <li>
  <p><strong>计算最大值和最小值</strong>: 确定数组中的最大值和最小值，进而决定计数数组的大小。</p>
 </li>
 <li>
  <p><strong>计数</strong>: 创建一个计数数组，将每个元素出现的次数记录在计数数组中。</p>
 </li>
 <li>
  <p><strong>累加计数</strong>: 对计数数组中的值进行累加，以便确定每个元素的最终位置。</p>
 </li>
 <li>
  <p><strong>构建排序数组</strong>: 遍历原始数组，通过计数数组确定每个元素在排序后数组中的位置，构建最终的有序数组。</p>
 </li>
</ol>
<p><strong>——接下来让我们使用代码来实现一下计数排序。</strong></p>
<p>
 <br>
</p>
<h3><strong>（3）</strong>Java代码实现</h3>
<pre><code class="java">public void countSort(int[] array) {
    // 初始化最小值和最大值为数组的第一个元素
    int min = array[0];
    int max = array[0];

    // 遍历数组，找到最小值和最大值
    for (int i = 0; i &lt; array.length; i++) {
        if (array[i] &lt; min) {
            min = array[i];
        }
        if (array[i] &gt; max) {
            max = array[i];
        }
    }

    // 创建计数数组，用于统计每个元素出现的次数
    int[] count = new int[max - min + 1];

    // 统计数组中每个元素出现的次数
    for (int i = 0; i &lt; array.length; i++) {
        count[array[i] - min]++;
    }

    // 将排序后的元素放回原数组
    int arrayIndex = 0;
    for (int i = 0; i &lt; count.length; i++) {
        // 对计数数组进行处理，将每个元素根据其计数放入原数组中
        while (count[i] != 0) {
            array[arrayIndex++] = i + min;
            count[i]--;
        }
    }
}
</code></pre>
<p><strong>解释：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong><code>int min = array[0]; int max = array[0];</code></strong>：初始化最小值和最大值为数组的第一个元素。</p>
  </li>
  <li>
   <p><strong><code>for (int i = 0; i &lt; array.length; i++)</code></strong>：遍历数组，找到最小值和最大值。</p>
  </li>
  <li>
   <p><strong><code>int[] count = new int[max - min + 1];</code></strong> ：创建计数数组 <code>count</code>，其长度为 <code>max - min + 1</code>，用来存储每个值的出现次数。</p>
  </li>
  <li>
   <p><strong><code>for (int i = 0; i &lt; array.length; i++)</code></strong>：遍历数组并填充计数数组。</p>
  </li>
  <li>
   <p><strong><code>count[array[i] - min]++</code></strong> ：更新计数数组中的值，表示 <code>array[i]</code> 出现的次数。</p>
  </li>
  <li>
   <p><strong><code>for (int i = 0; i &lt; count.length; i++)</code></strong>：遍历计数数组，并将排序后的元素放回原数组中。</p>
  </li>
  <li>
   <p><strong><code>while (count[i] != 0)</code></strong>：根据计数数组的值将元素放入原数组中，每个元素按照计数的次数放入对应的位置。</p>
  </li>
 </ul>
</blockquote>
<p><strong>——这样我们就实现了计数排序算法了！！！</strong></p>
<p>
 <br>
</p>
<h3>（4）时间复杂度和空间复杂度</h3>
<ul>
 <li>
  <p><strong>时间复杂度</strong>: O(n+k)O(n + k)O(n+k)，其中 nnn 是数组的长度，kkk 是数组中元素的范围（最大值与最小值之间的差值）。</p>
 </li>
 <li>
  <p><strong>空间复杂度</strong>: O(k)O(k)O(k)。计数排序需要额外的计数数组，空间复杂度与元素的范围成正比。</p>
 </li>
</ul>
<p>
 <br>
</p>
<h3>（5）算法的应用场景</h3>
<p><strong>计数排序的特点使其适用于以下场景：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong>数据范围较小的整数排序</strong>: 计数排序适合整数范围较小的排序任务，如考试成绩、年龄等。</p>
  </li>
  <li>
   <p><strong>需要稳定排序的场景</strong>: 计数排序是一种稳定的排序算法，适用于需要保持相同元素相对顺序的场景。</p>
  </li>
  <li>
   <p><strong>数据分布相对均匀</strong>: 如果数据分布极度不均匀，计数排序的效率会大大降低，因此适用于数据分布较为均匀的情况。</p>
  </li>
 </ul>
</blockquote>
<p><strong>——这样我们学习完了如何在Java中实现计数排序算法。</strong></p>
<p>
 <br>
</p>
<hr>
<p><strong>以上就是本篇文章的全部内容了\~\~\~</strong></p>
<p>原文链接: https://blog.csdn.net/2302_80198073/article/details/141786223</p>]]></description><guid isPermaLink="false">/archives/77b87cf5-b988-4f29-950d-6f13654b975d</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 03:40:15 GMT</pubDate></item><item><title><![CDATA[【数据结构与算法】十大经典排序算法深度解析：冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序、计数排序、桶排序、基数排序]]></title><link>https://wiki.dgfygk.com/archives/a243cd7c-f72b-413d-ae60-ac5ccf8e0067</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E3%80%91%E5%8D%81%E5%A4%A7%E7%BB%8F%E5%85%B8%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%EF%BC%9A%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E3%80%81%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E3%80%81%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E3%80%81%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E3%80%81%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E3%80%81%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F%E3%80%81%E5%A0%86%E6%8E%92%E5%BA%8F%E3%80%81%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E3%80%81%E6%A1%B6%E6%8E%92%E5%BA%8F%E3%80%81%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F&amp;url=/archives/a243cd7c-f72b-413d-ae60-ac5ccf8e0067" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p><strong>💓 博客主页：</strong><strong></strong><strong></strong><strong></strong><a href="https://blog.csdn.net/2302_78391795?type=blog" title="倔强的石头的CSDN主页">倔强的石头的CSDN主页</a><strong></strong><strong></strong><strong></strong><strong></strong></p>
 <p><strong>📝Gitee主页：</strong><strong></strong><strong></strong><strong></strong><a href="https://gitee.com/xing-shichuang" title="倔强的石头的gitee主页">倔强的石头的gitee主页</a><strong></strong><strong></strong><strong></strong><strong></strong></p>
 <p><strong>⏩ 文章专栏：</strong><strong></strong><strong></strong><strong></strong><a href="https://blog.csdn.net/2302_78391795/category_12527598.html?spm=1001.2014.3001.5482" title="《数据结构与算法》">《数据结构与算法》</a><strong></strong><strong></strong><strong></strong><strong></strong></p>
 <p><strong>期待您的关注</strong></p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F47f09392526c71b5885ec838a3ea7ffe.gif&amp;size=m" alt="">​</p>
 <p><strong>目录</strong></p>
</blockquote>
<p><a href="https://wiki.dgfygk.com#%E5%BC%95%E8%A8%80">引言</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%A6%82%E8%BF%B0">一、排序算法概述</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E7%AE%80%E4%BB%8B">排序算法简介</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E7%9A%84%E5%88%86%E7%B1%BB">排序算法的分类</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%80%A7%E8%83%BD%E6%8C%87%E6%A0%87">性能指标</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E5%8D%81%E5%A4%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E8%AF%A6%E8%A7%A3">二、十大排序算法详解</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃冒泡排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃直接选择排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃直接插入排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃希尔排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃快速排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃堆排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃归并排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃计数排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83hoare%E7%89%88%E6%9C%AC">🍃桶排序</a></p>
<p><a href="https://wiki.dgfygk.com#%F0%9F%8D%83%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F">🍃基数排序</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E7%9A%84%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83%E4%B8%8E%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF%E5%88%86%E6%9E%90">三、排序算法的性能比较与适用场景分析</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83">性能比较</a></p>
<p><a href="https://wiki.dgfygk.com#%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF%E5%88%86%E6%9E%90">适用场景分析</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E6%80%BB%E7%BB%93">四、总结</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%80%A7%E8%83%BD%E6%80%BB%E7%BB%93">性能总结</a></p>
<p><a href="https://wiki.dgfygk.com#%E7%A8%B3%E5%AE%9A%E6%80%A7%E4%B8%8E%E9%80%82%E7%94%A8%E6%80%A7">稳定性与适用性</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%86%99%E5%9C%A8%E7%BB%93%E5%B0%BE">写在结尾</a></p>
<hr>
<h3>引言</h3>
<blockquote>
 <p>在数据处理的广阔领域中，排序算法作为基石般的存在，扮演着至关重要的角色。<strong>无论是从日常生活中的简单列表排序，到复杂系统中海量数据的组织与管理，排序算法都是不可或缺的工具。它们不仅决定了数据处理的效率，还直接影响到用户体验和系统性能。</strong></p>
 <p><strong>排序算法的核心任务是将一组数据元素（记录）按照某个关键字（或关键字序列）的递增或递减顺序重新排列，使得原本无序的数据变得有序。这一过程看似简单，实则蕴含着丰富的算法思想和优化策略。</strong></p>
 <p>随着计算机科学的发展，众多排序算法应运而生，每种算法都有其独特的适用场景和性能特点。为了帮助读者系统地掌握这些经典算法，本文精心挑选了十大经典排序算法进行深度解析。这十大算法不仅涵盖了比较排序和非比较排序两大类，还包含了从简单直观到高效复杂的多种实现方式，能够满足不同场景下的排序需求。</p>
 <p><strong><em>在本文中，我们将逐一介绍每种排序算法的基本原理、实现步骤、性能特点以及适用场景。通过具体的代码示例和性能比较，读者将能够深入理解每种算法的优势与局限，从而在实际应用中做出更加合理的选择。</em></strong></p>
</blockquote>
<h3>一、排序算法概述</h3>
<h4>排序算法简介</h4>
<blockquote>
 <p>排序算法是计算机科学中用于将一组数据元素（或记录）按照某个特定的顺序重新排列的算法。这些算法广泛应用于各种数据处理场景中，如数据库管理、数据分析、软件开发等。排序算法的核心目标是将无序的数据序列转变为有序的数据序列，以便进行进一步的查找、分析或呈现。</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F35b69883b4384bbea2615000c0c633be.png&amp;size=m" alt=""></p>
<h4>排序算法的分类</h4>
<blockquote>
 <p>排序算法可以根据多种标准进行分类，但最常见的分类方式是基于它们的基本工作原理。主要分为两大类：比较排序和非比较排序。（更多的分类标准可以参照上图）</p>
 <ol>
  <li>
   <p><strong>比较排序</strong>：这类排序算法通过比较数据元素之间的大小关系来确定它们的顺序。比较排序算法包括冒泡排序、选择排序、插入排序、归并排序、快速排序等。这些算法的性能通常依赖于数据元素的初始排列和比较操作的效率。</p>
  </li>
  <li>
   <p><strong>非比较排序</strong>：与比较排序不同，非比较排序算法不直接比较数据元素之间的大小。它们通常依赖于数据元素的某些特定属性或额外的数据结构来实现排序。非比较排序算法包括计数排序、桶排序和基数排序等。这些算法在特定条件下（如数据范围有限或数据分布均匀）能够提供比比较排序更优的性能。</p>
  </li>
 </ol>
 <h4>性能指标</h4>
 <p>评价排序算法优劣的关键指标主要包括以下几个方面：</p>
 <ol>
  <li>
   <p><strong>时间复杂度</strong>：衡量算法执行所需时间的度量标准。通常用大O表示法（如O(n^2)、O(n log n)）来描述算法在最坏情况、平均情况和最好情况下的时间复杂度。</p>
  </li>
  <li>
   <p><strong>空间复杂度</strong>：衡量算法执行过程中所需额外空间的度量标准。空间复杂度较低的算法更适合处理大数据集或内存受限的环境。</p>
  </li>
  <li>
   <p><strong>稳定性</strong>：稳定性是指排序算法在保持相等元素之间原有顺序的能力。稳定的排序算法在处理具有相等键值的元素时能够保持它们的相对顺序不变。</p>
  </li>
  <li>
   <p><strong>适应性</strong>：指算法对不同类型数据的适应能力。一些算法可能特别适用于整数排序，而另一些则可能更擅长处理浮点数或字符串等复杂数据类型。</p>
  </li>
 </ol>
 <p>通过综合考虑以上性能指标，我们可以选择最适合特定应用场景的排序算法。在本文的后续部分，我们将逐一深入探讨十大经典排序算法的原理、实现和性能特点，以帮助读者更好地理解并应用这些算法。</p>
</blockquote>
<h3>二、十大排序算法详解</h3>
<h4>🍃冒泡排序</h4>
<p><strong>基本思想</strong>：</p>
<blockquote>
 <p>通过重复地遍历待排序的序列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。遍历序列的工作是重复地进行直到没有再需要交换，也就是说该序列已经排序完成。
  <br>
  <strong>详情请阅读专题文章 ：</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/139692652?spm=1001.2014.3001.5502" title="​【数据结构与算法】冒泡排序：简单易懂的排序算法解析-CSDN博客">​【数据结构与算法】冒泡排序：简单易懂的排序算法解析-CSDN博客</a></strong></p>
</blockquote>
<p><strong>算法过程</strong>：</p>
<ul>
 <li><strong>比较相邻元素</strong>：重复地走访需要排序的元素列表，依次比较两个相邻的元素。</li>
 <li><strong>交换元素</strong>：如果顺序（如从大到小或从小到大）错误，就交换这两个元素的位置。</li>
 <li><strong>重复进行</strong>：重复以上步骤，直到没有相邻的元素需要交换，则元素列表排序完成。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbc0a43629298796eb26dc672913d57f5.png&amp;size=m" alt=""></p>
<p><strong>C语言实现代码：</strong></p>
<pre><code class="cpp">//冒泡排序
void BubbleSort1(DataType* a, int size)//升序排序
{
    for (int i = 0; i &lt; size - 1; i++)//控制排序趟数
    {
        for (int j = 0; j &lt; size - 1 - i; j++)//控制每次比较次数
        {
            if (a[j] &gt; a[j + 1])//不满足升序就交换位置
            {
                DataType tmp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = tmp;
            }
        }
    }
}
void BubbleSort2(DataType* a, int size)//降序排序
{
    for (int i = 0; i &lt; size - 1; i++)//控制排序趟数
    {
        for (int j = 0; j &lt; size - 1 - i; j++)//控制每次比较次数
        {
            if (a[j] &lt; a[j + 1])//不满足降序就交换位置
            {
                DataType tmp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = tmp;
            }
        }
    }
}
</code></pre>
<h4>🍃直接选择排序</h4>
<p><strong>基本思想</strong>：</p>
<blockquote>
 <p>选择排序（Selection Sort）是一种简单直观的排序算法。</p>
 <p>它的<strong>工作原理</strong>是：</p>
 <p><em>首先在未排序序列中找到最小（大）元素，存放到排序序列的起始位置，然后，再从剩余未排序元素中继续寻找最小（大）元素，然后放到已排序序列的末尾。以此类推，直到所有元素均排序完毕。</em>
  <br>
  <strong>详情请阅读专题文章：</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/140250168?spm=1001.2014.3001.5502" title="【数据结构与算法】选择排序：原理、实现、优化与分析-CSDN博客">【数据结构与算法】选择排序：原理、实现、优化与分析-CSDN博客</a></strong></p>
</blockquote>
<p><strong>算法过程：</strong></p>
<ul>
 <li><strong>初始化</strong>：未排序区间为数组的全部元素，即整个数组；已排序区间为空。</li>
 <li><strong>遍历</strong>：从未排序区间中遍历找到最小（或最大）的元素。</li>
 <li><strong>交换</strong>：将找到的最小（或最大）的元素与未排序区间的第一个元素进行交换，该元素即为未排序区间的最小值（或最大值），因此交换后，它就到了已排序区间的末尾。</li>
 <li><strong>缩小未排序区间</strong>：将未排序区间的第一个元素排除（因为它已经是排序好的了），继续在剩余的未排序区间中重复上述步骤，直到未排序区间为空。</li>
</ul>
<p><strong>下图以升序排序为例进行演示</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7eaceeb231984cda89cc94f1b84efe88.png&amp;size=m" alt=""></p>
<p><strong>C语言实现代码：</strong></p>
<pre><code class="cpp">void swap(int* a, int* b)//交换函数
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
void SelectSort1(int* a, int n)//选择排序降序
{
    for (int i = 0; i &lt; n - 1; i++)//控制选择次数
    {
        int mini = i;
        for (int j = i + 1; j &lt; n; j++)//控制查找范围
        {
            if (a[j] &lt; a[mini])
                mini = j;
        }
        swap(&amp;a[i], &amp;a[mini]);
    }
}
void SelectSort2(int* a, int n)//选择排序降序
{
    for (int i = 0; i &lt; n - 1; i++)//控制选择次数
    {
        int maxi = i;
        for (int j = i + 1; j &lt; n; j++)//控制查找范围
        {
            if (a[j] &gt; a[maxi])
                maxi = j;
        }
        swap(&amp;a[i], &amp;a[maxi]);
    }
}
</code></pre>
<h4>🍃直接插入排序</h4>
<p><strong>基本思想</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbd1c822db403477c97c1ff44a1f664ac.png&amp;size=m" alt=""></p>
<blockquote>
 <p><strong>插入排序的基本思想是将一个数据插入到已经排好序的有序数据中，从而得到一个新的、个数加一的有序数据。</strong></p>
 <p><strong>详情请阅读专题文章：</strong></p>
 <p><a href="https://blog.csdn.net/2302_78391795/article/details/140191599?spm=1001.2014.3001.5502" title="【数据结构与算法】插入排序：原理、实现与分析-CSDN博客">【数据结构与算法】插入排序：原理、实现与分析-CSDN博客</a></p>
 <p><strong>算法过程</strong></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb67604e1a5e849ed9b79519bca2a582a.gif&amp;size=m" alt="">​</p>
<blockquote>
 <p><strong>具体步骤是这样的：</strong>
  <br>
  （以排升序为例）</p>
 <ol>
  <li><em>从第一个元素开始，该元素可以认为已经被排序。</em></li>
  <li><em>取出下一个元素，在已经排序的元素序列中从后向前扫描。</em></li>
  <li><em>如果该元素（已排序）大于新元素，将该元素向后移动一位。</em></li>
  <li><em>重复步骤3，直到找到已排序的元素小于或者等于新元素的位置。</em></li>
  <li><em>将新元素插入到该位置后。</em></li>
  <li><em>重复步骤2\~5，直到所有元素都排序完成。</em></li>
 </ol>
</blockquote>
<p><strong>C语言实现代码</strong></p>
<pre><code class="cpp">void InsertSort1(int* a, int n)//升序
{
    for (int i = 1; i &lt;= n-1 ; i++)//控制要插入的元素个数
    {
        int key = a[i];
        int end = i - 1;
        while (end &gt;= 0 &amp;&amp; a[end] &gt; key)//移动元素
        {
            a[end + 1] = a[end];
            end--;
        }
        a[end+1] = key;//满足大小关系后插入到指定位置
    }
}
void InsertSort2(int* a, int n)//降序
{
    for (int i = 1; i &lt;= n - 1; i++)//控制要插入的元素个数
    {
        int key = a[i];
        int end = i - 1;
        while (end &gt;= 0 &amp;&amp; a[end] &lt; key)//移动元素
        {
            a[end + 1] = a[end];
            end--;
        }
        a[end + 1] = key;//满足大小关系后插入到指定位置
    }
}
</code></pre>
<h4>🍃希尔排序</h4>
<p><strong>基本思想：</strong></p>
<blockquote>
 <p><strong>希尔排序的基本思想是</strong> ：<em>先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序，待整个序列中的记录"基本有序"时，再对全体记录进行一次标准的直接插入排序。</em></p>
 <p><strong>这里的"基本有序"是指</strong> ：<em>待排序的数组元素值满足某个增量序列的"局部有序”</em>，即对于某个变量gap，序列中所有距离为gap的元素之间是有序的。随着变量gap的逐渐减小，当gap减小到1时，整个序列恰好被"基本有序”，此时再对全体元素进行一次直接插入排序即可
  <br>
  <strong>详情请阅读专题文章：</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/140214752?spm=1001.2014.3001.5502" title="【数据结构与算法】希尔排序：基于插入排序的高效排序算法-CSDN博客">【数据结构与算法】希尔排序：基于插入排序的高效排序算法-CSDN博客</a></strong></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F40d13289c0024edda6a18cc2be99dc51.png&amp;size=m" alt="">​</p>
<p><strong>实现步骤：</strong></p>
<p>
 <br>
</p>
<p><strong>1.外循环进行多轮预排序</strong>
 <br>
 选择一个变量序列：</p>
<p>这个序列是逐渐减小的，gap的值较大时，数据可以更快的前后变动，但不容易"基本有序”；gap较小时数据前后变动较慢，但更接近"基本有序”。 通常可以选取gap = n/3, gap = gap/3, …，直到gap= 1。</p>
<p>但是要注意，如果直接每次都/3，可能面临的情况就是最后一组gap的值跳过了1，比如n=8时，gap第一次等于2，第二次等于0，解决方法也很简单，gap每次不是/3，而是gap=gap/3+1，就可以让gap最后一次一定会减小到1</p>
<p><strong>2.第二层循环，每一轮预排序中进行分组</strong>
 <br>
 按gap进行分组：根据当前的变量gap，将待排序的数组元素下标按gap分组，总共可以分成gap组。比如gap为3时，每一组元素的首元素分别是0，1，2</p>
<p><strong>3.第三层循环，分组之后，控制组里数据执行插入排序</strong>
 <br>
 每一组的数据有n/gap个，下标为0，gap, 2gap, 3gap,…的元素分为一组；下标为1，gap+1，2gap+1，3gap+1……的元素分为一组……</p>
<p>
 <br>
</p>
<p>这一层循环一个需要注意的细节就是预防数组的越界:每一组分组数据的最后一个数据一般不会是数组的最后一个数据。每次选取的要插入的数据下标是end+gap，那么这个下标不能超过n-gap。比如数组有10个元素，gap为3，第一组数据最后一个数据的下标是9，要保证这一组数据访问到下标9之后，不再向后访问，因为下一次访问end为9，要插入的数据，9+gap的位置已经没有数据了。</p>
<p>
 <br>
</p>
<p><strong>4.第四层循环，实现插入排序的过程</strong>
 <br>
 每个数据向前扫描和移动，找到合适的位置后插入，直接在插入排序代码的基础上稍加修改即可</p>
<p><strong>5.递减变量gap并重复上述分组排序过程：</strong></p>
<p>每完成一轮按变量gap的分组排序后，将变量gap减小，然后重复分组排序过程，直到变量gap为1，此时整个数组恰好被分成一组，进行最后一次直接插入排序。</p>
<p><strong>C语言实现代码</strong></p>
<pre><code class="cpp">void ShellSort1(int* a, int n)//希尔排序升序
{
    int gap = n;
    while (gap &gt; 1)//多组预排序，最后一组gap==1为直接插入排序
    {
        gap = gap / 3 + 1;
        for (int i = 0; i &lt;gap; i++)//控制分组的组数：gap组
        {
            for (int j = i; j &lt; n - gap; j += gap)//控制每组的插入元素个数：n/gap个
            {
                int key = a[j+gap];
                int end = j;
                while (end &gt;= 0 &amp;&amp; a[end] &gt; key)//比较和移动元素
                {
                    a[end + gap] = a[end];
                    end -= gap;
                }
                a[end + gap] = key;//满足大小关系后插入到指定位置
            }
        }
    }
}

void ShellSort2(int* a, int n)//希尔排序降序
{
    int gap = n;
    while (gap &gt; 1)//多组预排序，最后一组gap==1为直接插入排序
    {
        gap = gap / 3 + 1;
        for (int i = 0; i &lt; gap; i++)//控制分组的组数：gap组
        {
            for (int j = i; j &lt; n - gap; j += gap)//控制每组的插入元素个数：n/gap个
            {
                int key = a[j + gap];
                int end = j;
                while (end &gt;= 0 &amp;&amp; a[end] &lt; key)//比较和移动元素
                {
                    a[end + gap] = a[end];
                    end -= gap;
                }
                a[end + gap] = key;//满足大小关系后插入到指定位置
            }
        }
    }
}
</code></pre>
<h4>🍃快速排序</h4>
<blockquote>
 <p><strong>快速排序由于分为多个版本和优化，同时这也是比较重要和应用较多的排序算法，所以篇幅较长，就放在单独的文章，不在本文展开讲解了</strong></p>
 <p><a href="https://blog.csdn.net/2302_78391795/article/details/140363715?spm=1001.2014.3001.5502" title="【数据结构与算法】快速排序万字全攻略：hoare版本、挖坑法、前后指针法、优化版、非递归实现-CSDN博客">【数据结构与算法】快速排序万字全攻略：hoare版本、挖坑法、前后指针法、优化版、非递归实现-CSDN博客</a></p>
</blockquote>
<h4>🍃堆排序</h4>
<blockquote>
 <p><strong>堆排序需要较多的前置知识，可参考同系列其他文章，每篇文章都有单独的讲解</strong></p>
 <p><strong>可参考下列步骤阅读：</strong></p>
 <p><strong>先了解二叉树的基本概念</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/139972433?spm=1001.2014.3001.5502" title="【数据结构与算法】详解二叉树 上：理论篇——二叉树的基本概念与性质-CSDN博客">【数据结构与算法】详解二叉树 上：理论篇——二叉树的基本概念与性质-CSDN博客</a></strong></p>
 <p><strong>再了解二叉树的应用——堆</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/139664921?spm=1001.2014.3001.5502" title="【数据结构与算法】探索数组在堆数据结构中的妙用：从原理到实现-CSDN博客">【数据结构与算法】探索数组在堆数据结构中的妙用：从原理到实现-CSDN博客</a></strong></p>
 <p><strong>最后再了解堆的应用——堆排序</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/139667219?spm=1001.2014.3001.5502" title="【数据结构与算法】堆排序算法原理与实现：基于堆实现的高效排序算法-CSDN博客">【数据结构与算法】堆排序算法原理与实现：基于堆实现的高效排序算法-CSDN博客</a></strong></p>
</blockquote>
<h4>🍃归并排序</h4>
<p><strong>基本思想</strong></p>
<blockquote>
 <p>归并排序（Merge Sort）是一种分而治之的排序算法。它将数组分成两半，对每半部分递归地应用归并排序，然后将结果合并成一个有序数组。归并排序的关键在于合并两个已排序的数组段</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2b3c79d1f05c4b4ba750409c96548f3d.gif&amp;size=m" alt=""></p>
<blockquote>
 <p><strong>更多细节及优化可参考文章：</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/140277909" title="【数据结构与算法】归并排序：从理论到实践的深度剖析-CSDN博客">【数据结构与算法】归并排序：从理论到实践的深度剖析-CSDN博客</a></strong></p>
</blockquote>
<p><strong>算法过程：</strong></p>
<blockquote>
 <p><strong>注意：</strong></p>
 <p>因为归并排序在合并过程中需要一个与待排序数组大小相同的临时数组来存储合并的结果。</p>
 <p>所以直接在进入函数之后一次申请一个同样大小的空间，免去多次动态申请空间和释放的消耗</p>
 <p><strong><em>主调函数只完成空间申请和释放，以及中间的调用子函数</em></strong></p>
 <p><strong><em>将归并排序的核心代码放在子函数完成</em></strong></p>
</blockquote>
<p><strong>子函数的任务（归并的核心）：</strong></p>
<p><strong>🍃1. 分解</strong></p>
<ul>
 <li>将当前区间一分为二，即找到中间位置mid = (left + right) / 2</li>
 <li>递归地对左半部分left…mid进行归并排序。</li>
 <li>递归地对右半部分mid+1…right进行归并排序。</li>
</ul>
<p><strong>🍃2. 解决</strong></p>
<ul>
 <li>在归并排序中，“解决"步骤实际上是在递归调用中隐式完成的，即通过递归调用自身来实现对左右子数组的排序。</li>
 <li>当递归调用达到基本情况（即子数组只有一个元素或为空时），由于一个元素的数组自然是有序的，因此不需要进行任何操作，递归开始返回。</li>
</ul>
<p><strong>🍃3. 合并（以升序为例）：核心代码</strong></p>
<ul>
 <li>使用两个变量i和j，分别指向左半部分和右半部分的起始位置。</li>
 <li>比较左半部分和右半部分当前指针所指的元素，将较小的元素先存入temp数组，并移动对应的变量。</li>
 <li>重复上述步骤，直到左半部分或右半部分的所有元素都被复制到temp数组中。</li>
 <li>将左半部分或右半部分中剩余的元素（一定是某部分的元素先复制完成）直接复制到temp数组的末尾。</li>
 <li>将temp数组中的元素复制回原数组，以完成合并过程。</li>
</ul>
<p><strong>C语言实现代码</strong></p>
<pre><code class="cpp">// 归并排序递归实现升序
void Merge1(int left,int right,int* a,int* tmp)//归并排序子函数
{
    if (left &gt;= right)//区间只有一个元素或不存在时不需要归并
        return;
    int mid = (left + right) / 2;//取得下标中间值
    Merge1(left, mid, a, tmp);//递归左区间
    Merge1(mid + 1, right, a, tmp);//递归右区间

    int begin1 = left,end1 = mid;//左区间范围
    int begin2 = mid + 1,end2 = right;//右区间范围
    int begin = left;
    while (begin1 &lt;= end1 &amp;&amp; begin2 &lt;= end2)//二路归并
    {
        if (a[begin1] &lt; a[begin2])
            tmp[begin] = a[begin1++];
        else
            tmp[begin] = a[begin2++];
        begin++;
    }
    //判断哪个区间元素没有遍历完，直接搬下来
    while (begin1 &lt;= end1)
        tmp[begin++] = a[begin1++];
    while (begin2 &lt;= end2)
        tmp[begin++] = a[begin2++];

    memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
    //将归并和排序之后的数据拷贝至原数组
}
void MergeSort1(int* a, int n)//归并排序主调函数
{
    int* tmp = (int*)malloc(sizeof(int) * n);//申请临时数组
    if (tmp == NULL)
    {
        perror("Merge malloc\n");
        return;
    }
    Merge1(0, n - 1, a, tmp);//调用归并函数
    free(tmp);//排序完成释放空间
    tmp = NULL;
}
</code></pre>
<h4>🍃计数排序</h4>
<p><strong>基本原理</strong></p>
<blockquote>
 <p><strong>计数排序（Counting Sort）是一种非比较型排序算法，它的基本原理是通过统计每个元素的出现次数，然后利用这些信息来重建排序后的数组。</strong></p>
 <p>其<strong>核心思想在于：</strong> <em>通过统计每个元素在数组中出现的次数，来确定该元素在排序后数组中的位置。这种方法在处理具有明显范围限制且分布相对均匀的整数数据时，尤为高效。</em></p>
 <p>作为一种线性时间复杂度的排序，它要求输入的数据必须是有确定范围的整数。
  <br>
  <strong>更多详情可阅读文章：</strong></p>
 <p><strong><a href="https://blog.csdn.net/2302_78391795/article/details/140279707" title="【数据结构与算法】详解计数排序：小范围整数排序的最佳选择-CSDN博客">【数据结构与算法】详解计数排序：小范围整数排序的最佳选择-CSDN博客</a></strong></p>
</blockquote>
<p><strong>请看下图动图演示</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F027b5605bab4416094da68800bedf0d6.gif&amp;size=m" alt=""></p>
<p><strong>实现步骤</strong></p>
<p><strong>1. 确定数据范围</strong></p>
<p>首先，代码通过遍历待排序数组 <code>a</code>，找出其中的最大值 <code>max</code> 和最小值 <code>min</code>。这两个值用于确定计数数组 <code>count</code> 的大小，因为计数数组需要覆盖待排序数组中所有可能出现的值（在最小值和最大值之间）。</p>
<pre><code class="cpp">int min = INT_MAX;
int max = INT_MIN;
for (int i = 0; i &lt; n; i++)//求得最大值和最小值
{
    if (a[i] &gt; max)
        max = a[i];
    if (a[i] &lt; min)
        min = a[i];
}
</code></pre>
<p><strong>2. 初始化计数数组</strong></p>
<p><strong>根据最大值和最小值计算出</strong> 的范围（<code>max - min + 1</code>），代码使用 <code>calloc</code> 分配了一个足够大的整数数组 <code>count</code>，并将所有元素初始化为 0。这个数组用于统计待排序数组中每个值出现的次数。使用 <code>calloc</code> 而不是 <code>malloc</code> 加初始化是为了确保所有元素都初始化为 0，因为计数排序需要这些初始值来正确统计。</p>
<pre><code class="cpp">int size = max - min + 1;
int* count = (int*)calloc(size, sizeof(int));//根据数据范围申请空间
if (count == NULL)
{
    perror("calloc fail\n");
    return;
}
</code></pre>
<p><strong>3. 统计元素频率</strong></p>
<p>接下来，代码再次遍历待排序数组 <code>a</code>，这次是为了统计每个元素出现的次数。对于数组 <code>a</code> 中的每个元素 <code>a[i]</code>，代码通过 <code>count[a[i] - min]++</code> 将 <code>a[i]</code> 的出现次数记录在 <code>count</code> 数组的相应位置上。这里减去 <code>min</code> 是为了将 <code>a</code> 中的值映射到 <code>count</code> 数组的有效索引范围内。</p>
<pre><code class="cpp">for (int i = 0; i &lt; n; i++)//遍历原数组，将数据出现的次数写入count数组对应的位置
{
    count[a[i] - min]++;//核心代码1
}
</code></pre>
<p><strong>4. 排序</strong></p>
<p>代码遍历 <code>count</code> 数组，并根据每个值出现的次数，将对应的值依次放回原数组 <code>a</code> 中。这里使用了一个双层循环，外层循环遍历 <code>count</code> 数组的每个索引（即待排序数组中的每个可能值），内层循环（通过 <code>while</code> 循环实现）则根据 <code>count[j]</code> 的值（即该值出现的次数）将 <code>j + min</code>（即原始值）放回原数组 <code>a</code> 中。每次放回一个值后，<code>count[j]</code> 递减，直到该值的所有出现都被放回原数组。</p>
<pre><code class="cpp">int i = 0;
for (int j = 0; j &lt; size; j++)//遍历count数组，根据数据出现次数，将数据写入原数组对应的位置
{
    while (count[j]--)//每写入一次，次数--
    {
        a[i++] = j + min;//核心代码2
    }
}
</code></pre>
<p><strong>5. 清理资源</strong></p>
<p>最后，代码释放了 <code>count</code> 数组占用的内存，并将其指针设置为 <code>NULL</code>，以避免野指针问题。</p>
<pre><code class="cpp">free(count);
count = NULL;
</code></pre>
<p><strong>C语言实现代码</strong></p>
<pre><code class="cpp">void CountSort1(int* a, int n)//计数排序升序
{
    int min = INT_MAX;
    int max = INT_MIN;
    for (int i = 0; i &lt; n; i++)//求得最大值和最小值
    {
        if (a[i] &gt; max)
            max = a[i];
        if (a[i] &lt; min)
            min = a[i];
    }
    int size = max - min + 1;
    int* count = (int*)calloc(size, sizeof(int));//根据数据范围申请空间
    if (count == NULL)
    {
        perror("calloc fail\n");
        return;
    }

    for (int i = 0; i &lt; n; i++)//遍历原数组，将数据出现的次数写入count数组对应的位置
    {
        count[a[i] - min]++;//核心代码1
    }

    int i = 0;
    for (int j = 0; j &lt; size; j++)//遍历count数组，根据数据出现次数，将数据写入原数组对应的位置
    {
        while (count[j]--)//每写入一次，次数--
        {
            a[i++] = j + min;//核心代码2
        }
    }

    free(count);
    count = NULL;
}
</code></pre>
<h4>🍃桶排序</h4>
<p><strong>基本思想：</strong></p>
<blockquote>
 <p>桶排序（Bucket Sort）是一种分布式排序算法，它将数组分到有限数量的桶里。每个桶再个别排序（有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序）。最后，将各个桶中的数据有序地合并起来。</p>
 <p>桶排序最适合用在数据分布均匀的情况下，这样每个桶中的数据量大致相同，排序效率最高。当数据分布极不均匀时，桶排序的效率会大打折扣。</p>
</blockquote>
<p><strong>算法过程</strong></p>
<ol>
 <li><strong>确定桶的数量</strong>：根据待排序数据的范围来确定桶的数量。</li>
 <li><strong>分配元素到各个桶</strong>：遍历待排序数组，将每个元素分配到对应的桶中。</li>
 <li><strong>对每个桶进行排序</strong>：可以使用不同的排序算法对每个桶中的元素进行排序，也可以使用递归的桶排序。</li>
 <li><strong>合并桶中的数据</strong>：将各个桶中的数据有序地合并成一个有序数组。</li>
</ol>
<p><strong>C语言实现代码</strong></p>
<pre><code class="cpp">#define MAX_NUM 100 // 假设数据最大为100  
#define BUCKET_SIZE (MAX_NUM / 5) // 假设桶的数量是数据范围的1/5  

void insertionSort(int arr[], int n) {  
    int i, key, j;  
    for (i = 1; i &lt; n; i++) {  
        key = arr[i];  
        j = i - 1;  

        /* 将arr[i]插入到已排序序列arr[0..i-1]的适当位置 */  
        while (j &gt;= 0 &amp;&amp; arr[j] &gt; key) {  
            arr[j + 1] = arr[j];  
            j = j - 1;  
        }  
        arr[j + 1] = key;  
    }  
}  

void bucketSort(int arr[], int n) {  
    int i, j;  
    int bucket[BUCKET_SIZE][n]; // 假设最坏情况下每个桶都有n个元素  
    int count[BUCKET_SIZE] = {0}; // 每个桶中的元素数量  

    // 1. 分配元素到各个桶  
    for (i = 0; i &lt; n; i++) {  
        int bi = arr[i] / (MAX_NUM / BUCKET_SIZE); // 桶的索引  
        bucket[bi][count[bi]++] = arr[i];  
    }  

    // 2. 对每个桶进行排序  
    for (i = 0; i &lt; BUCKET_SIZE; i++) {  
        insertionSort(bucket[i], count[i]);  
    }  

    // 3. 合并桶中的数据  
    int index = 0;  
    for (i = 0; i &lt; BUCKET_SIZE; i++) {  
        for (j = 0; j &lt; count[i]; j++) {  
            arr[index++] = bucket[i][j];  
        }  
    }  
}  
</code></pre>
<pre><code>```
&nbsp;
</code></pre>
<h4><strong>🍃基数排序</strong></h4>
<p><strong>基本思想：</strong></p>
<blockquote>
 <p>基数排序（Radix Sort）是一种非比较型整数排序算法，其原理是将整数按位数切割成不同的数字，然后按每个位数分别比较。具体来说，就是将整数按位数切割成不同的数字，然后按每个位数分别比较。由于整数也可以表示字符串（如日期、时间等）的排序，因此基数排序也不是仅适用于整数。
  <br>
  <strong>基数排序有两种方法：</strong></p>
 <ol>
  <li><strong>LSD（Least significant digital）</strong>：从最低位开始进行排序，然后向最高位进行。</li>
  <li><strong>MSD（Most significant digital）</strong>：从最高位开始进行排序，然后向最低位进行。通常使用递归方法实现。</li>
 </ol>
 <p>这里主要介绍LSD方法，因为它更直观且易于实现。</p>
</blockquote>
<p><strong>算法过程</strong></p>
<ol>
 <li><strong>找出待排序数组中的最大数</strong>，以确定最大位数。</li>
 <li><strong>从最低位开始，依次进行一次排序</strong> 。</li>
 <li>分配：根据当前位数，将元素分配到不同的桶中。</li>
 <li>收集：将桶中的元素按顺序收集起来，形成新的数组。</li>
 <li><strong>重复上述过程</strong>，直到最高位。</li>
</ol>
<p><strong>C语言实现代码</strong></p>
<p>以下是一个基于LSD的基数排序的C语言实现，假设我们排序的是非负整数，并且每个数字的最大位数是已知的（或者可以动态计算）。为了简化，这里假设所有数字都是单个字符（即0-9的数字），并存储在字符数组中。</p>
<pre><code class="cpp">#define MAXD 10 // 假设最大位数是10（实际情况可能需要根据数据范围确定）  
#define RADIX 10 // 基数为10，因为处理的是十进制数  

// 用于基数排序的桶  
int count[RADIX];  
char output[MAXD][1000]; // 假设最多有1000个数字  

// 获取字符串num的最低位（这里简化为取最后一个字符代表的数字）  
int getDigit(char num[], int d) {  
    return num[strlen(num) - 1 - d] - '0';  
}  

// 基数排序函数  
void radixSort(char arr[][10], int n) {  
    // 找到最大数的位数  
    int m = 0;  
    for (int i = 0; i &lt; n; i++)  
        if (strlen(arr[i]) &gt; m)  
            m = strlen(arr[i]);  

    // 从最低位到最高位进行排序  
    for (int d = 0; d &lt; m; d++) {  
        // 初始化桶  
        memset(count, 0, sizeof(count));  

        // 将元素分配到桶中  
        for (int i = 0; i &lt; n; i++)  
            count[getDigit(arr[i], d)]++;  

        // 更改count[i]，使其包含实际位置信息  
        for (int r = 1; r &lt; RADIX; r++)  
            count[r] += count[r - 1];  

        // 将元素收集到输出数组中  
        for (int i = n - 1; i &gt;= 0; i--) {  
            output[count[getDigit(arr[i], d)] - 1] = arr[i];  
            count[getDigit(arr[i], d)]--;  
        }  

        // 将输出数组的内容复制回原数组  
        for (int i = 0; i &lt; n; i++)  
            strcpy(arr[i], output[i]);  
    }  
}  
</code></pre>
<h3>三、排序算法的性能比较与适用场景分析</h3>
<h4>性能比较</h4>
<p>| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
 <br>
 |——|————-|—————-|————-|———|—————-|
 <br>
 | 冒泡排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
 <br>
 | 选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
 <br>
 | 插入排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
 <br>
 | 希尔排序 | O(nlogn) | O(n^s) (s为常数) | O(n) | O(1) | 不稳定 |
 <br>
 | 快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(logn) | 不稳定 |
 <br>
 | 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
 <br>
 | 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
 <br>
 | 计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 |
 <br>
 | 桶排序 | O(n+k) | O(n^2) | O(n) | O(n+k) | 稳定(平均)/不稳定(最坏) |
 <br>
 | 基数排序 | O(d*(n+k)) | O(d*(n+k)) | O(d*(n+k)) | O(n+k) | 稳定 |</p>
<h4>适用场景分析</h4>
<ol>
 <li><strong>冒泡排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于数据量较小的情况，如简单的数据排序练习或小规模数据的排序。</li>
 <li><strong>缺点</strong>：在数据量较大时效率极低。</li>
 <li><strong>选择排序</strong> ：</li>
 <li><strong>适用场景</strong>：与冒泡排序类似，适用于数据量较小的情况。</li>
 <li><strong>缺点</strong>：由于需要多次遍历未排序部分以找到最小（或最大）元素，因此效率较低。</li>
 <li><strong>插入排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于部分有序或数据量较小的数据排序。对于几乎已经排序的数据，插入排序的效率很高。</li>
 <li><strong>优点</strong>：在数据规模较小时，效率较高；对于部分有序的数据集，效率也较高。</li>
 <li><strong>希尔排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于中等规模的数据排序，特别是在数据基本有序时，效率较高。</li>
 <li><strong>优点</strong>：通过引入增量序列，减少了数据比较和移动的次数，提高了排序效率。</li>
 <li><strong>快速排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于大规模数据的排序，是实际应用中最常用的排序算法之一。</li>
 <li><strong>优点</strong>：平均情况下效率高，且可以通过随机选择基准元素来避免最坏情况的发生。</li>
 <li><strong>归并排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于大规模数据的排序，特别是当需要稳定排序时。</li>
 <li><strong>优点</strong>：稳定排序，且时间复杂度为O(nlogn)，效率较高。但空间复杂度较高，不适合内存受限的环境。</li>
 <li><strong>堆排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于需要快速选择最大（或最小）元素的场景，如堆数据结构的应用。</li>
 <li><strong>优点</strong>：时间复杂度为O(nlogn)，效率较高；且不需要额外的存储空间（除了递归所需的栈空间）。</li>
 <li><strong>计数排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于整数数据范围较小的情况，如考试成绩、年龄等数据的排序。</li>
 <li><strong>优点</strong>：在数据范围较小时，效率极高；且稳定排序。</li>
 <li><strong>桶排序</strong> ：</li>
 <li><strong>适用场景</strong>：适用于数据分布均匀的情况，如正态分布的数据。</li>
 <li><strong>优点</strong>：在数据分布均匀时，效率较高；且稳定排序（平均情况下）。</li>
 <li><strong>基数排序</strong>
  <ul>
   <li><strong>适用场景</strong>：数据范围大但位数较少，关键字可以分割成独立的数字，数据分布较均匀</li>
   <li><strong>优点</strong>：在数据分布均匀时，效率较高；且稳定排序（平均情况下）。</li>
  </ul></li>
</ol>
<h3>四、总结</h3>
<p><strong>在探讨完十大经典排序算法后，我们可以从多个维度对这些算法进行总结，以便更好地理解它们的特点、适用场景以及性能差异。</strong></p>
<h4>性能总结</h4>
<ol>
 <li><strong>时间复杂度</strong> ：</li>
 <li><strong>最优情况</strong>：冒泡排序、插入排序、选择排序在最优情况下时间复杂度为O(n)，但这通常发生在数据已经接近有序的情况下。快速排序、归并排序、堆排序在最优情况下可以达到O(n log n)。桶排序和计数排序在特定条件下（如数据分布均匀）可以达到O(n)。</li>
 <li><strong>平均情况</strong>：大多数比较排序算法（如冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序）的平均时间复杂度为O(n^2)或O(n log n)。非比较排序（如桶排序、计数排序、基数排序）在平均情况下也能达到O(n)。</li>
 <li><strong>最差情况</strong>：冒泡排序、插入排序、选择排序的最差时间复杂度为O(n2)。快速排序在最差情况下（如每次分区都选择到最大或最小元素）也会退化到O(n2)，但通过随机化选择基准元素可以显著降低这种情况的发生概率。归并排序和堆排序的最差时间复杂度始终为O(n log n)。</li>
 <li><strong>空间复杂度</strong> ：</li>
 <li>冒泡排序、插入排序、选择排序等原地排序算法的空间复杂度为O(1)，即它们只使用常量额外空间。</li>
 <li>归并排序、快速排序等在某些实现中可能需要额外的空间来存储递归调用栈或临时数组，其空间复杂度为O(log n)或O(n)。</li>
 <li>桶排序、计数排序、基数排序等非比较排序算法通常需要额外的空间来存储桶、计数数组或基数队列，其空间复杂度取决于数据的分布和范围。</li>
</ol>
<h4>稳定性与适用性</h4>
<ul>
 <li><strong>稳定性</strong>：排序算法的稳定性指的是在排序过程中，如果两个元素相等，它们的相对位置在排序前后保持不变。归并排序、插入排序、冒泡排序是稳定的排序算法，而快速排序、选择排序、堆排序等则是不稳定的。</li>
 <li><strong>适用性</strong> ：</li>
 <li>对于小数据集或几乎有序的数据集，插入排序和冒泡排序通常表现良好。</li>
 <li>当数据量较大时，快速排序、归并排序和堆排序等更高效的算法更为适用。</li>
 <li>如果数据分布具有特定模式（如大量重复元素或有限取值范围），则可以考虑使用计数排序、桶排序或基数排序等非比较排序算法。</li>
</ul>
<blockquote>
 <h4>写在结尾</h4>
 <p>十大排序算法各有其特点和适用场景。在选择排序算法时，需要根据数据的具体特点（如规模、分布、有序程度等）以及算法的性能指标（如时间复杂度、空间复杂度、稳定性等）进行综合考虑。在实际应用中，可能还需要结合具体问题的需求和约束条件来选择合适的排序算法。通过深入了解这些算法的原理和特性，我们可以更加灵活地应对各种排序问题。</p>
</blockquote>
<p><strong>本文完</strong></p>
<p>原文链接: https://blog.csdn.net/2302_78391795/article/details/139706656</p>]]></description><guid isPermaLink="false">/archives/a243cd7c-f72b-413d-ae60-ac5ccf8e0067</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 03:40:00 GMT</pubDate></item><item><title><![CDATA[【Prometheus】jmx_prometheus_javaagent监控java应用]]></title><link>https://wiki.dgfygk.com/archives/486742f6-1bf2-4950-a21c-c6218bd6fd5d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90Prometheus%E3%80%91jmx_prometheus_javaagent%E7%9B%91%E6%8E%A7java%E5%BA%94%E7%94%A8&amp;url=/archives/486742f6-1bf2-4950-a21c-c6218bd6fd5d" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fdba6cea6bfb4936fd21ee683a66df121.png&amp;size=m" alt=""></p>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#skrjX">一、概述</a></p>
<p><a href="https://wiki.dgfygk.com#AoPVW">1.1 promethues简介</a></p>
<p><a href="https://wiki.dgfygk.com#jcd3u">1.2 JMX Exporter简介</a></p>
<p><a href="https://wiki.dgfygk.com#JmO6c">二、监控SparkHistoryServer实现</a></p>
<hr>
<h2>一、概述</h2>
<hr>
<h3>1.1 promethues简介</h3>
<hr>
<p>promethues采集数据的方法很多，常用的是通过各种exporter去主机采集，然后有些程序是没有相关的exporter,所以有些时候会通过脚本采集服务的数据，还有一些程序的metrics自身也没有暴露，通过 脚本采集的方法也不常用，工作中java程序又比较常见，所以有人开发了jmx_prometheus_javaagent.jar专门用来采集java程序metrics的。</p>
<p>
 <br>
</p>
<h3>1.2 JMX Exporter简介</h3>
<hr>
<blockquote>
 <p>Java Management Extensions，JMX 是管理 Java 的一种扩展框架，JMX Exporter 基于此框架读取 JVM 的运行时状态。JMX Exporter 利用 Java 的 JMX 机制来读取 JVM 运行时的监控数据，然后将其转换为 Prometheus 可辨识的 metrics 格式，以便让 Prometheus 对其进行监控采集。</p>
 <p>JMX Exporter 提供<strong>启动独立进程</strong> 及 <strong>JVM 进程内启动（in-process）</strong>两种方式暴露 JVM 监控指标：</p>
</blockquote>
<ol>
 <li>
  <p><strong>启动独立进程 JVM 启动时指定参数，暴露 JMX 的 RMI 接口。JMX Exporter 调用 RMI 获取 JVM 运行时状态数据，转换为 Prometheus metrics 格式，并暴露端口让 Prometheus 采集。</strong></p>
 </li>
 <li>
  <p><strong>JVM 进程内启动（in-process）</strong> JVM 启动时指定参数，通过 javaagent 的形式运行 JMX Exporter 的 jar 包，进程内读取 JVM 运行时状态数据，转换为 Prometheus metrics 格式，并暴露端口让 Prometheus 采集。</p>
 </li>
</ol>
<hr>
<h2>二、监控SparkHistoryServer实现</h2>
<hr>
<p>prometheus.yml</p>
<pre><code># my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
       - localhost:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"
  - "alert_rules/*.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.
    static_configs:
    - targets: ['localhost:9090']
  - job_name: 'grafana'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.
    static_configs:
    - targets: ['localhost:3000']
  - job_name: 'alertmanager'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.
    static_configs:
    - targets: ['localhost:9093']

  - job_name: 'sparkhistoryserver' # historyserver
    file_sd_configs:
     - files:
       - configs/sparkhistoryserver.json
</code></pre>
<p>configs/sparkhistoryserver.json 监控目标</p>
<pre><code>[root@ddp01 prometheus]# cat configs/sparkhistoryserver.json
[
 {
  "targets":["ddp01:18082"]
 }
]
</code></pre>
<p>alert_rules/spark3.yml 定义告警规则</p>
<pre><code>[root@ddp01 prometheus]# cat alert_rules/spark3.yml 
groups:
- name: SPARK3
  # rules：定义规则
  rules:
  # alert：告警规则的名称
  - alert: SparkHistoryServer进程存活
    expr: up{job="sparkhistoryserver"} != 1
    for: 15s
    labels:
      # severity: 指定告警级别。有三种等级，分别为warning、critical和emergency。严重等级依次递增。
      severity: exception
      clusterId: 1
      serviceRoleName: SparkHistoryServer
    annotations:
      # summary描述告警的概要信息
      # description用于描述告警的详细信息。
      summary: 重新启动
      description: "{
  { $labels.job }}的{
  { $labels.instance }}实例产生告警"
</code></pre>
<p>增加 SPARK_HISTORY_OPTS 环境变量， 也就是将spark程序与监控插件绑定</p>
<pre><code>[root@ddp01 datasophon]# cat spark3/conf/spark-env.sh
export SPARK_DIST_CLASSPATH=$(/opt/datasophon/hadoop-3.3.3/bin/hadoop classpath)
export HADOOP_CONF_DIR=/opt/datasophon/hadoop-3.3.3/etc/hadoop
export YARN_CONF_DIR=/opt/datasophon/hadoop-3.3.3/etc/hadoop
export SPARK_CLASSPATH=/opt/datasophon/spark-3.1.3/carbonlib/*
export SPARK_HISTORY_OPTS="$SPARK_HISTORY_OPTS -javaagent:/opt/datasophon/spark-3.1.3/jmx/jmx_prometheus_javaagent-0.16.1.jar=18082:/opt/datasophon/spark-3.1.3/jmx/prometheus_config.yml"
</code></pre>
<p>jmx_prometheus_javaagent-0.16.1.jar 包位置</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Feae8bda077784ed4a10ccbc113e59efb.png&amp;size=m" alt=""></p>
<p>查看进程状态</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0a4cffe832db482eaf180f8388332403.png&amp;size=m" alt=""></p>
<p>rules</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F331207858b6c4f17852589ff1d179b96.png&amp;size=m" alt="">targats</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb3925a99e21a4645aa524689bccc8617.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接：<a href="https://blog.csdn.net/weixin_38965592/article/details/134812312" title="prometheus通过jmx_prometheus_javaagent-.jar采集java程序的metrics_jmx promethues-CSDN博客">prometheus通过jmx_prometheus_javaagent-.jar采集java程序的metrics_jmx promethues-CSDN博客</a></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/qq_35995514/article/details/142340731</p>]]></description><guid isPermaLink="false">/archives/486742f6-1bf2-4950-a21c-c6218bd6fd5d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:39:41 GMT</pubDate></item><item><title><![CDATA[如何使用浏览器发post请求]]></title><link>https://wiki.dgfygk.com/archives/29f3acb5-5eae-443a-bf3f-4b5059e71ee7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E5%8F%91post%E8%AF%B7%E6%B1%82&amp;url=/archives/29f3acb5-5eae-443a-bf3f-4b5059e71ee7" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>如何使用浏览器发送post请求</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#_3">第一种：无请求体</a></li>
 <li><a href="https://wiki.dgfygk.com#post_12">第二种：要设置请求体的post请求</a></li>
</ul>
<p>
 <br>
</p>
<p>通过浏览器发送post请求有两种简单的方式，只需要根据实际情况在console执行以下代码即可。</p>
<h3>第一种：无请求体</h3>
<p>没有请求体，可以直接使用以下方式，url需要换成你自己的地址，简单方便。</p>
<pre><code class="prism language-powershell">fetch(new Request('http://test.com',{
   method:'POST'})).then((resp)=&gt;{
   console.log(resp)})
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4e1c4643976d5a0f25c695e0d36f0290.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbc8bd358c886afc4dff4310f1b67cb81.png&amp;size=m" alt=""></p>
<h3>第二种：要设置请求体的post请求</h3>
<p>但是需要设置请求体时，且需要设置请求头可以使用以下方法,url和param的内容换成你自己的参数即可。</p>
<pre><code class="prism language-powershell">var url = "http://localhost:82/marriage-admin/marriage/act/exportEvaluationUser";
var params = {

  "page": 1,
  "pageSize": 10,
  "bsnType": "marriage",
  "serverType": "",
  "bsnIds": ["3685056891847020"]

};
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = function (e) {

  if (xhr.readyState === 4) {

    if (xhr.status === 200) {

      console.log(xhr.responseText);
    } else {

      console.error(xhr.statusText);
    }
  }
};
xhr.onerror = function (e) {

  console.error(xhr.statusText);
};
xhr.send(JSON.stringify(params));
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F71c64ce91d63dd01fa2ce00b846affd0.png&amp;size=m" alt="">
 <br>
 可变参数：</p>
<pre><code class="prism language-java">public class VarArgsTest1
{

    private static void test(String s, String...ss)
    {

        for(int i = 0; i &lt; ss.length; i++)
        {

            System.out.println(ss[i]);
        }
    }
    public static void main(String[] args)
    {

        test(null);
        test("");
        test("aaa");
        test("aaa", "bbb");
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/m0_50736744/article/details/138311444</p>]]></description><guid isPermaLink="false">/archives/29f3acb5-5eae-443a-bf3f-4b5059e71ee7</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 03:39:25 GMT</pubDate></item><item><title><![CDATA[AcWing算法基础课-785快速排序-Java题解]]></title><link>https://wiki.dgfygk.com/archives/eae037d1-5a29-47ef-9f73-d5e9f9014606</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AcWing%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80%E8%AF%BE-785%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F-Java%E9%A2%98%E8%A7%A3&amp;url=/archives/eae037d1-5a29-47ef-9f73-d5e9f9014606" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F294f496c53d8cd620581b14d1235932a.png%23pic_center&amp;size=m" alt=""></p>
<blockquote>
 <p>大家好，我是何未来，本篇文章给大家讲解《AcWing算法基础课》785 题——快速排序。这篇文章介绍了使用快速排序算法对整数数列进行排序的方法，包括选择基准元素、分区操作和递归排序子数组。通过详细的步骤和示例，解释了快速排序的过程及其非稳定性特征，并提供了相应的 Java 代码实现。</p>
</blockquote>
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_5">❓题目描述</a></li>
 <li><a href="https://wiki.dgfygk.com#_44">💡算法思路</a></li>
 <li><a href="https://wiki.dgfygk.com#Java_120">✅Java代码</a></li>
 <li><a href="https://wiki.dgfygk.com#_180">🔗参考</a></li>
</ul>
<p>
 <br>
</p>
<h2>❓题目描述</h2>
<p>给定你一个长度为 n 的整数数列。</p>
<p>请你使用快速排序对这个数列按照从小到大进行排序。</p>
<p>并将排好序的数列按顺序输出。</p>
<p><strong>输入格式</strong></p>
<p>输入共两行，第一行包含整数 n。</p>
<p>第二行包含 n 个整数（所有整数均在 1∼10^9 范围内），表示整个数列。</p>
<p><strong>输出格式</strong></p>
<p>输出共一行，包含 n 个整数，表示排好序的数列。</p>
<p><strong>数据范围</strong></p>
<pre><code>1≤n≤100000
</code></pre>
<p><strong>输入样例：</strong></p>
<pre><code>5
3 1 2 4 5
</code></pre>
<p><strong>输出样例：</strong></p>
<pre><code>1 2 3 4 5
</code></pre>
<h2>💡算法思路</h2>
<ol>
 <li>选择基准元素：从数组中选择一个元素作为基准。这里我们选择的是中间位置的元素。</li>
 <li>分区操作：重新排列数组，将所有比基准值小的元素摆放在基准前面，所有比基准值大的元素摆在基准的后面（相同的数可以到任一边）。在这个分区退出之后，该基准就处于数列的中间位置。这个称为分区操作。</li>
 <li>递归排序子数组：递归地将小于基准值元素的子数列和大于基准值元素的子数列排序。</li>
</ol>
<p>具体实现步骤如下：</p>
<ul>
 <li>首先检查数组的起始索引<code>l</code>是否大于或等于结束索引<code>r</code>，如果是，则直接返回，因为这意味着数组已经是有序的或者是空数组。</li>
 <li>初始化两个指针<code>i</code>和<code>j</code>，分别指向数组的起始位置的前一个位置和结束位置的后一个位置。选择数组的中间元素作为基准元素<code>x</code>。</li>
 <li>使用两个指针<code>i</code>和<code>j</code>从数组的两端向中间移动，直到<code>i</code>找到一个大于或等于基准的元素，<code>j</code>找到一个小于或等于基准的元素。如果<code>i</code>仍然小于<code>j</code>，则交换这两个元素。</li>
 <li>重复上述过程，直到<code>i</code>不再小于<code>j</code>。</li>
 <li>递归地对基准元素左侧和右侧的子数组进行快速排序。</li>
</ul>
<p>时间复杂度：<code>O(n log n)</code>
 <br>
 空间复杂度：<code>O(n)</code>
 <br>
 快速排序是<strong>非稳定排序</strong>：快速排序的分区过程中，当遇到与基准元素相等的元素时，通常会停止移动指针（在某些实现中，指针会继续移动），这可能导致相同元素的相对顺序被改变。例如，如果数组中有多个相同的元素，分区操作可能会将这些元素分散到基准的两侧，从而改变它们的相对顺序。</p>
<blockquote>
 <p>稳定排序的定义是：在排序过程中，具有相同键值的元素在排序前后的相对位置保持不变。然而，快速排序的分区操作可能会改变相同元素的相对顺序。</p>
</blockquote>
<p>接下来，我给大家举个具体的例子来说明为什么快速排序是非稳定排序。</p>
<p>假设我们有一个包含学生信息的数组，每个学生由他们的姓名和成绩组成。我们希望按照成绩对学生进行排序。初始数组如下：</p>
<pre><code>[ ("何未来", 85), ("乔布斯", 75), ("牛顿", 85), ("图灵", 75) ]
</code></pre>
<p>选择基准元素 <code>("牛顿", 85)</code>，进行第一次分区操作：</p>
<ol>
 <li>
  <p>初始指针位置：<code>i = -1</code>，<code>j = 4</code></p>
 </li>
 <li>
  <p>移动 <code>i</code> 直到找到大于或等于基准的元素：<code>i</code> 停在 <code>("何未来", 85)</code></p>
 </li>
 <li>
  <p>移动 <code>j</code> 直到找到小于或等于基准的元素：<code>j</code> 停在 <code>("图灵", 75)</code></p>
 </li>
 <li>
  <p>交换 <code>i</code> 和 <code>j</code> 指向的元素：</p>
  <p>[ (“图灵”, 75), (“乔布斯”, 75), (“牛顿”, 85), (“何未来”, 85) ]</p>
 </li>
</ol>
<p>第一次分区结束，基准元素 <code>("牛顿", 85)</code> 的位置已经确定。现在我们有两个子数组：</p>
<ul>
 <li>左子数组：<code>[ ("图灵", 75), ("乔布斯", 75) ]</code></li>
 <li>右子数组：<code>[ ("何未来", 85) ]</code></li>
</ul>
<p>我们先对左子数组进行递归排序：</p>
<p>选择基准元素 <code>("乔布斯", 75)</code>，进行第二次分区操作：</p>
<ol>
 <li>
  <p>初始指针位置：<code>i = -1</code>，<code>j = 2</code></p>
 </li>
 <li>
  <p>移动 <code>i</code> 直到找到大于或等于基准的元素：<code>i</code> 停在 <code>("图灵", 75)</code></p>
 </li>
 <li>
  <p>移动 <code>j</code> 直到找到小于或等于基准的元素：<code>j</code> 停在 <code>("乔布斯", 75)</code></p>
 </li>
 <li>
  <p>交换 <code>i</code> 和 <code>j</code> 指向的元素：</p>
  <p>[ (“乔布斯”, 75), (“图灵”, 75) ]</p>
 </li>
</ol>
<p>第二次分区结束，基准元素 <code>("乔布斯", 75)</code> 的位置已经确定。现在我们有两个子数组：</p>
<ul>
 <li>左子数组：<code>[ ]</code>（空数组）</li>
 <li>右子数组：<code>[ ("图灵", 75) ]</code></li>
</ul>
<p>由于左子数组为空，右子数组只有一个元素，这两个子数组都已经是有序的。</p>
<p>接下来对右子数组进行递归排序：</p>
<p>右子数组 <code>[ ("何未来", 85) ]</code> 只有一个元素，已经是有序的。</p>
<p>最终排序结果：</p>
<pre><code>[ ("乔布斯", 75), ("图灵", 75), ("牛顿", 85), ("何未来", 85) ]
</code></pre>
<p>通过这个过程，我们可以看到，初始数组中相同成绩的学生 <code>("何未来", 85)</code> 和 <code>("牛顿", 85)</code> 的相对顺序在排序后发生了变化，从 <code>("何未来", 85)</code> 在前变成了 <code>("牛顿", 85)</code> 在前。这就是快速排序不稳定性的体现。</p>
<h2>✅Java代码</h2>
<pre><code class="prism language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class Aw785 {


    // 创建一个StreamTokenizer用于读取输入
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    // 读取下一个整数的方法
    static int nextInt() throws IOException {

        in.nextToken();
        return (int) in.nval;
    }

    static int n; // 存储数组的大小
    static int[] nums = new int[100000 + 10]; // 存储输入的数组，预留一些额外的空间

    public static void main(String[] args) throws IOException {

        n = nextInt(); // 读取数组的大小
        for (int i = 0; i &lt; n; i++) {

            nums[i] = nextInt(); // 读取数组的每个元素
        }
        quickSort(0, n - 1, nums); // 调用快速排序方法对数组进行排序
        for (int i = 0; i &lt; n; i++) {

            System.out.print(nums[i] + " "); // 输出排序后的数组
        }
    }

    // 快速排序方法，参数为数组的起始和结束索引，以及数组本身
    static void quickSort(int l, int r, int[] a) {

        if (l &gt;= r) {
    // 如果起始索引大于或等于结束索引，则直接返回
            return;
        }
        int i = l - 1, j = r + 1, x = a[l + r &gt;&gt; 1]; // 初始化指针i, j和基准元素x，以中间元素作为基准元素
        while (i &lt; j) {
    // 当i小于j时，执行循环
            do {

                i++; // i指针向右移动，直到找到一个大于或等于x的元素
            } while (a[i] &lt; x);
            do {

                j--; // j指针向左移动，直到找到一个小于或等于x的元素
            } while (a[j] &gt; x);
            if (i &lt; j) {
    // 如果i仍然小于j，交换这两个元素
                int tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
        }
        quickSort(l, j, a); // 递归排序左半部分
        quickSort(j + 1, r, a); // 递归排序右半部分
    }

}
</code></pre>
<h2>🔗参考</h2>
<ul>
 <li><a href="https://www.acwing.com/problem/content/787/">https://www.acwing.com/problem/content/787/</a></li>
 <li><a href="https://www.hello-algo.com/chapter_sorting/quick_sort/">https://www.hello-algo.com/chapter_sorting/quick_sort/</a></li>
</ul>
<p>作者：<a href="http://heweilai.com">程序员何未来-heweilai.com</a></p>
<hr>
<p>🔍<strong>推荐阅读</strong></p>
<ol>
 <li><a href="https://blog.csdn.net/coder_heweilai/article/details/141062183">++【七夕节实践】把爱心代码放在自己的网站上是什么体验？++</a></li>
 <li><a href="https://blog.csdn.net/coder_heweilai/article/details/140248098">++塑造你的技术名片：写给程序员的个人品牌建设指南++</a></li>
 <li><a href="https://blog.csdn.net/coder_heweilai/article/details/139566996">++博客赚钱全攻略：从新手到专家的变现之路++</a></li>
</ol>
<blockquote>
 <p>欢迎<code>关注</code>我的博客：<a href="https://blog.csdn.net/coder_heweilai">@程序员何未来</a>，持续为你输出<strong>有价值</strong> 的技术文章\~
  <br>
  你们的点赞👍 收藏⭐ 留言🗨️ 关注✅
  <br>
  是我持续创作，输出<code>优质内容</code>的最大动力！谢谢！</p>
</blockquote>
<p>文章关键词：算法，计算机算法，算法题解，算法竞赛，Java，数据结构，AcWing算法基础课</p>
<p>原文链接: https://blog.csdn.net/coder_heweilai/article/details/141720984</p>]]></description><guid isPermaLink="false">/archives/eae037d1-5a29-47ef-9f73-d5e9f9014606</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 03:39:05 GMT</pubDate></item><item><title><![CDATA[【C++】二叉搜索树的底层以及实现]]></title><link>https://wiki.dgfygk.com/archives/df4ed6ea-7e7a-460f-b668-abec0c733a1a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90C%2B%2B%E3%80%91%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%BA%95%E5%B1%82%E4%BB%A5%E5%8F%8A%E5%AE%9E%E7%8E%B0&amp;url=/archives/df4ed6ea-7e7a-460f-b668-abec0c733a1a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2F1b7335aca73b41609b7f05d1d366f476.gif%23pic_center%23pic_center&amp;size=m" alt="">
 <br>
 <a href="https://blog.csdn.net/2301_81044829?type=blog">个人主页</a>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F564f8c1ca83e4916b47a5be3f8dd2017.jpeg%23pic_center&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_5">⭐一、二叉搜索树的概念</a></li>
 <li><a href="https://wiki.dgfygk.com#_13">🚀二、二叉搜索树性能分析</a></li>
 <li><a href="https://wiki.dgfygk.com#_28">🏝️三、二叉搜索树的操作</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1__29">1. 插入</a></li>
 <li><a href="https://wiki.dgfygk.com#2__81">2. 查找</a></li>
 <li><a href="https://wiki.dgfygk.com#3__113">3. 删除</a></li>
 <li><a href="https://wiki.dgfygk.com#4__218">4. 遍历节点</a></li>
 <li><a href="https://wiki.dgfygk.com#K_232">🎄四、二叉搜索树的实现（K模型）</a></li>
 <li><a href="https://wiki.dgfygk.com#_419">🎉五、二叉搜索树的应用</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_K_420">1. K模型</a></li>
 <li><a href="https://wiki.dgfygk.com#2_KV_425">2. KV模型</a></li>
</ul>
<p>
 <br>
</p>
<h2>⭐一、二叉搜索树的概念</h2>
<p>二叉搜索树（Binary Search Tree，简称BST），又称为二叉查找树或二叉排序树，是一种特殊的二叉树结构。其具有以下几个性质：
 <br>
 • <strong>左子树：</strong> 若它的左子树不为空，则左子树上所有结点的值都小于等于根结点的值。
 <br>
 • <strong>右子树：</strong> 若它的右子树不为空，则右子树上所有结点的值都大于等于根结点的值。
 <br>
 • <strong>递归：</strong> 它的左右子树也分别为二叉搜索树。</p>
<p><strong>注： 二叉搜索树可以支持插入相等的值，也可以不支持插入相等的值，具体看使用的场景来定义。</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe56398ee2862485f876c1eaae770cef0.png&amp;size=m" alt=""></p>
<h2>🚀二、二叉搜索树性能分析</h2>
<p>最优情况下(即树是平衡的)，二叉搜索树为完全二叉树(或者接近完全二叉树)，其高度为： O(log2 N)
 <br>
 最差情况下(即树退化成为一个链表)，二叉搜索树退化为单支树(或者类似单支)，其高度为： O( N / 2)</p>
<p>综合而言<strong>二叉搜索树增删查改时间复杂度为： O(N)</strong>
 <br>
 但这样的效率显然是不满足我们需求的，我们后面学到的AVL树和红黑树，才能适用于我们内存中存储和搜索数据。</p>
<p>注：虽然<strong>二分查找</strong> 也能实现O(logN) 级别的查找效率，但它有<strong>两大缺陷</strong> ：
 <br>
 1.需要存储在<strong>支持下标随机访问</strong> 的结构中，并且<strong>有序</strong> 。
 <br>
 2.插入和删除数据效率很<strong>低</strong> ，因为存储在下标随机访问的结构中，<strong>插入和删除数据一般需要挪动数据</strong></p>
<p>这也就体现出了平衡二叉搜索树的价值。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F60e676b2881d4b36a10b5678d127eae2.png&amp;size=m" alt=""></p>
<h2>🏝️三、二叉搜索树的操作</h2>
<h3>1. 插入</h3>
<p>插入节点操作的具体步骤如下：
 <br>
 1.树为空，则直接新增结点，赋值给root指针
 <br>
 2.树不空，按二叉搜索树性质，插入的值比当前结点大时往右走，插入值比当前结点小时往左左，直到找到空位置时，插入新结点。
 <br>
 3.如果支持插入相等的值，插入值跟当前结点相等的值可以往右走，也可以往左走，直到找到空位置，插人新结点。（要注意的是<strong>要保持逻辑一致性，插入相等的值不要一会往右走，一会往左走</strong> ）
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb7e8c197a35842249051037fb950b777.png&amp;size=m" alt=""></p>
<p>下面是插入节点操作的代码实现：</p>
<pre><code class="prism language-cpp">template&lt;class K&gt;
class BSTree
{

    typedef BSTNode&lt;K&gt; Node;
public:
    bool Insert(const K&amp; key)
    {

        if (_root == nullptr)
        {

            _root = new Node(key);  
            return true;
        }
        Node* cur = _root;
        Node* parent = nullptr;
        while (cur)
        {

            if (cur-&gt;_key &lt; key)
            {

                parent = cur;
                cur = cur-&gt;_right;
            }
            else if (cur-&gt;_key &gt; key)
            {

                parent = cur;
                cur = cur-&gt;_left;
            }
            else
            {

                return false;
            }
        }
        cur = new Node(key);
        if (parent-&gt;_key &lt; key)
        {

            parent-&gt;_right = cur;
        }
        else
        {

            parent-&gt;_left = cur;
        }
        return true;
    }
</code></pre>
<h3>2. 查找</h3>
<p>查找节点操作的具体步骤如下：
 <br>
 1.从根开始进行比较，查找x，x比根的值大则往右边进行查找，x比根的值小则往左边进行查找。
 <br>
 2.最多查找高度次，如果走到空时还没找到，则说明这个值不存在。
 <br>
 3.如果不支持插入相等的值，则找到x即可返回
 <br>
 4.如果支持插入相等的值，意味着有多个x存在，一般要求查找中序的第⼀个x。如下图，查找3，要找到1的右孩子的那个3进行返回
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4e2187f47af94e4d9ba1f7bb51ad2a63.png&amp;size=m" alt=""></p>
<p>查找节点操作具体代码如下：</p>
<pre><code class="prism language-cpp">bool Find(const K&amp; key)
{

    Node* cur = _root;
    while (cur)
    {

        if (cur-&gt;_key &lt; key)
        {

            cur = cur-&gt;_right;
        }
        else if (cur-&gt;_key &gt; key)
        {

            cur = cur-&gt;_left;
        }
        else
        {

            return true;
        }
    }
    return false;
}
</code></pre>
<h3>3. 删除</h3>
<p>删除节点的操作就是在树中移除一个指定的节点。但删除操作相对复杂一点，因为我们要考虑删除过后如何保持树的有序性。</p>
<p>删除节点操作具有以下四种情况：
 <br>
 1.要删除结点N左右孩子均为空
 <br>
 2.要删除的结点N左孩子位空，右孩子结点不为空
 <br>
 3.要删除的结点N右孩子位空，左孩子结点不为空
 <br>
 4.要删除的结点N左右孩子结点均不为空</p>
<p>那么如何解决它们所对应的情况呢？其对应的方案：
 <br>
 1.把N结点的父亲对应的孩子指针指向空，直接删除N结点
 <br>
 2.把N结点的父亲对应的孩子指针指向N的右孩子，直接删除N结点</p>
<ol>
 <li>把N结点的父亲对应的孩子指针指向N的左孩子，直接删除N结点</li>
 <li>因为我们无法直接删除N结点，所以只能用替换法进行删除。找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)来替代N，因为这两个结点中任意⼀个放到N的位置，都满足二叉搜索树的规则。
  <br>
  （注：替代N的意思就是将N和R的两个结点的值交换，转变成删除R的结点）</li>
</ol>
<p>删除节点操作具体代码如下：</p>
<pre><code class="prism language-cpp">bool erase(const K&amp; key)
{

    Node* parent = nullptr;
    Node* cur = _root;
    while (cur)
    {

        if (cur-&gt;_key &lt; key)
        {

            parent = cur;
            cur = cur-&gt;_right;
        }
        else if (cur-&gt;_key &gt; key)
        {

            parent = cur;
            cur = cur-&gt;_left;
        }
        else
        {

            //左为空
            if (cur-&gt;_left == nullptr)
            {

                if (cur == _root)
                {

                    _root = cur-&gt;_right;
                }
                else
                {

                    if (parent-&gt;_left == cur)
                    {

                        parent-&gt;_left = cur-&gt;_right;
                    }
                    else
                    {

                        parent-&gt;_right = cur-&gt;_right;
                    }
                }
                delete cur;
            }
            else if (cur-&gt;_right == nullptr)
            {

                //右为空
                if (cur == _root)
                {

                    _root = cur-&gt;_left;
                }
                else
                {

                    if (parent-&gt;_right == cur)
                    {

                        parent-&gt;_right = cur-&gt;_left;    
                    }
                    else
                    {

                        parent-&gt;_left = cur-&gt;_left;
                    }
                }
                delete cur;
            }
            else
            {

                //左右都不为空
                //右子树最左节点
                Node* replaceparent = cur;
                Node* replace = cur-&gt;_right;
                while (replace-&gt;_left)
                {

                    replaceparent = replace;
                    replace = replace-&gt;_left;
                }
                cur-&gt;_key = replace-&gt;_key;
                if (replaceparent-&gt;_left == replace)
                {

                    replaceparent-&gt;_left = replace-&gt;_right;
                }
                else
                {

                    replaceparent-&gt;_right = replace-&gt;_right;
                }
                delete replace;
            }
            return true;
        }
    }
    return false;
}
</code></pre>
<h3>4. 遍历节点</h3>
<p>遍历节点是按照一定的顺序去访问树中的所有节点，常用的遍历方式有：前序遍历、中序遍历和后序遍历。在这里我们采用中序遍历的方法，因为它可以按小到大的顺序输出树中所有的值。</p>
<pre><code class="prism language-cpp">void _Inorder(Node* root)
{

    if (root == nullptr)
    {

        return;
    }
    _Inorder(root-&gt;_left);
    cout &lt;&lt; root-&gt;_key &lt;&lt; " ";
    _Inorder(root-&gt;_right);
}
</code></pre>
<h2>🎄四、二叉搜索树的实现（K模型）</h2>
<p>下面是二叉树实现的源代码：</p>
<pre><code class="prism language-cpp">template&lt;class K&gt;
struct BSTNode
{

    K _key;
    BSTNode&lt;K&gt;* _left;
    BSTNode&lt;K&gt;* _right;

    BSTNode(const K&amp; key)
        :_key(key)
        , _left(nullptr)
        , _right(nullptr)
    {
   }
};

template&lt;class K&gt;
class BSTree
{

    typedef BSTNode&lt;K&gt; Node;
public:
    bool Insert(const K&amp; key)
    {

        if (_root == nullptr)
        {

            _root = new Node(key);
            return true;
        }
        Node* cur = _root;
        Node* parent = nullptr;
        while (cur)
        {

            if (cur-&gt;_key &lt; key)
            {

                parent = cur;
                cur = cur-&gt;_right;
            }
            else if (cur-&gt;_key &gt; key)
            {

                parent = cur;
                cur = cur-&gt;_left;
            }
            else
            {

                return false;
            }
        }
        cur = new Node(key);
        if (parent-&gt;_key &lt; key)
        {

            parent-&gt;_right = cur;
        }
        else
        {

            parent-&gt;_left = cur;
        }
        return true;
    }

    bool Find(const K&amp; key)
    {

        Node* cur = _root;
        while (cur)
        {

            if (cur-&gt;_key &lt; key)
            {

                cur = cur-&gt;_right;
            }
            else if (cur-&gt;_key &gt; key)
            {

                cur = cur-&gt;_left;
            }
            else
            {

                return true;
            }
        }
        return false;
    }

    bool erase(const K&amp; key)
    {

        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {

            if (cur-&gt;_key &lt; key)
            {

                parent = cur;
                cur = cur-&gt;_right;
            }
            else if (cur-&gt;_key &gt; key)
            {

                parent = cur;
                cur = cur-&gt;_left;
            }
            else
            {

                //左为空
                if (cur-&gt;_left == nullptr)
                {

                    if (cur == _root)
                    {

                        _root = cur-&gt;_right;
                    }
                    else
                    {

                        if (parent-&gt;_left == cur)
                        {

                            parent-&gt;_left = cur-&gt;_right;
                        }
                        else
                        {

                            parent-&gt;_right = cur-&gt;_right;
                        }
                    }
                    delete cur;
                }
                else if (cur-&gt;_right == nullptr)
                {

                    //右为空
                    if (cur == _root)
                    {

                        _root = cur-&gt;_left;
                    }
                    else
                    {

                        if (parent-&gt;_right == cur)
                        {

                            parent-&gt;_right = cur-&gt;_left;
                        }
                        else
                        {

                            parent-&gt;_left = cur-&gt;_left;
                        }
                    }
                    delete cur;
                }
                else
                {

                    //左右都不为空
                    //右子树最左节点
                    Node* replaceparent = cur;
                    Node* replace = cur-&gt;_right;
                    while (replace-&gt;_left)
                    {

                        replaceparent = replace;
                        replace = replace-&gt;_left;
                    }
                    cur-&gt;_key = replace-&gt;_key;
                    if (replaceparent-&gt;_left == replace)
                    {

                        replaceparent-&gt;_left = replace-&gt;_right;
                    }
                    else
                    {

                        replaceparent-&gt;_right = replace-&gt;_right;
                    }
                    delete replace;
                }
                return true;
            }
        }
        return false;
    }

    void Inorder()
    {

        _Inorder(_root);
        cout &lt;&lt; endl;
    }
private:
    void _Inorder(Node* root)
    {

        if (root == nullptr)
        {

            return;
        }
        _Inorder(root-&gt;_left);
        cout &lt;&lt; root-&gt;_key &lt;&lt; " ";
        _Inorder(root-&gt;_right);
    }
private:
    Node* _root = nullptr;
};
</code></pre>
<h2>🎉五、二叉搜索树的应用</h2>
<h3>1. K模型</h3>
<p>在K模型中，搜索二叉树仅存储关键码（key）,不存储与关键码相关联的值（Value）,关键码即为需要搜索或存储的值。
 <br>
 K模型代码可以参考搜索二叉树的实现。</p>
<p>特点：<strong>存储简单</strong> (每个节点只存储了一个键值，减少了空间)；<strong>查找效率高。</strong></p>
<h3>2. KV模型</h3>
<p>二叉搜索树的KV模型是一种特殊的数据结构。在KV模型中，每个节点不仅存储一个键（Key），还存储一个与该键相关联的值（Value）。这种模型在处理需要快速通过键来检索值的场景时非常有用，如简单的中英互译、统计一篇文章中单词出现的次数等。</p>
<p><strong>KV模型的搜索场景实现的二叉树搜索树支持修改，但是不支持修改key，因为修改key破坏搜索树结构了，可以修改value。</strong></p>
<p>下面是KV模型的二叉搜索树的代码实现：</p>
<pre><code class="prism language-cpp">template&lt;class K,class V&gt;
struct BSTNode
{

    K _key;
    V _value;
    BSTNode&lt;K,V&gt;* _left;
    BSTNode&lt;K,V&gt;* _right;

    BSTNode(const K&amp; key,const V&amp; value)
        :_key(key)
        ,_value(value)
        , _left(nullptr)
        , _right(nullptr)
    {
   }
};

template&lt;class K,class V&gt;
class BSTree
{

    using Node = BSTNode&lt;K,V&gt;;
public:
    bool Insert(const K&amp; key,const V&amp; value)
    {

        if (_root == nullptr)
        {

            _root = new Node(key,value);
            return true;
        }
        Node* cur = _root;
        Node* parent = nullptr;
        while (cur)
        {

            if (cur-&gt;_key &lt; key)
            {

                parent = cur;
                cur = cur-&gt;_right;
            }
            else if (cur-&gt;_key &gt; key)
            {

                parent = cur;
                cur = cur-&gt;_left;
            }
            else
            {

                return false;
            }
        }
        cur = new Node(key,value);
        if (parent-&gt;_key &lt; key)
        {

            parent-&gt;_right = cur;
        }
        else
        {

            parent-&gt;_left = cur;
        }
        return true;
    }

    Node* Find(const K&amp; key)
    {

        Node* cur = _root;
        while (cur)
        {

            if (cur-&gt;_key &lt; key)
            {

                cur = cur-&gt;_right;
            }
            else if (cur-&gt;_key &gt; key)
            {

                cur = cur-&gt;_left;
            }
            else
            {

                return cur;
            }
        }
        return nullptr;
    }

    bool erase(const K&amp; key)
    {

        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {

            if (cur-&gt;_key &lt; key)
            {

                parent = cur;
                cur = cur-&gt;_right;
            }
            else if (cur-&gt;_key &gt; key)
            {

                parent = cur;
                cur = cur-&gt;_left;
            }
            else
            {

                //左为空
                if (cur-&gt;_left == nullptr)
                {

                    if (cur == _root)
                    {

                        _root = cur-&gt;_right;
                    }
                    else
                    {

                        if (parent-&gt;_left == cur)
                        {

                            parent-&gt;_left = cur-&gt;_right;
                        }
                        else
                        {

                            parent-&gt;_right = cur-&gt;_right;
                        }
                    }
                    delete cur;
                }
                else if (cur-&gt;_right == nullptr)
                {

                    //右为空
                    if (cur == _root)
                    {

                        _root = cur-&gt;_left;
                    }
                    else
                    {

                        if (parent-&gt;_left == cur)
                        {

                            parent-&gt;_left = cur-&gt;_left;
                        }
                        else
                        {

                            parent-&gt;_right = cur-&gt;_left;
                        }
                    }
                    delete cur;
                }
                else
                {

                    //左右都不为空
                    //右子树最左节点
                    Node* replaceparent = cur;
                    Node* replace = cur-&gt;_right;
                    while (replace-&gt;_left)
                    {

                        replaceparent = replace;
                        replace = replace-&gt;_left;
                    }
                    cur-&gt;_key = replace-&gt;_key;
                    if (replaceparent-&gt;_left == replace)
                    {

                        replaceparent-&gt;_left = replace-&gt;_right;
                    }
                    else
                    {

                        replaceparent-&gt;_right = replace-&gt;_right;
                    }
                    delete replace;
                }
                return true;
            }
        }
        return false;
    }

    void Inorder()
    {

        _Inorder(_root);
        cout &lt;&lt; endl;
    }
private:
    void _Inorder(Node* root)
    {

        if (root == nullptr)
        {

            return;
        }
        _Inorder(root-&gt;_left);
        cout &lt;&lt; root-&gt;_key &lt;&lt; ":" &lt;&lt; root-&gt;_value &lt;&lt; endl;
        _Inorder(root-&gt;_right);
    }
private:
    Node* _root = nullptr;
};
</code></pre>
<p>原文链接: https://blog.csdn.net/2301_81044829/article/details/142372104</p>]]></description><guid isPermaLink="false">/archives/df4ed6ea-7e7a-460f-b668-abec0c733a1a</guid><dc:creator>黄健</dc:creator><category>C++</category><pubDate>Sun, 29 Sep 2024 03:38:42 GMT</pubDate></item><item><title><![CDATA[Java中的位图和布隆过滤器（如果想知道Java中有关位图和布隆过滤器的知识点，那么只看这一篇就足够了！）]]></title><link>https://wiki.dgfygk.com/archives/3fc35d12-6af3-4cf7-8e91-d530ead9de22</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E7%9A%84%E4%BD%8D%E5%9B%BE%E5%92%8C%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%EF%BC%88%E5%A6%82%E6%9E%9C%E6%83%B3%E7%9F%A5%E9%81%93Java%E4%B8%AD%E6%9C%89%E5%85%B3%E4%BD%8D%E5%9B%BE%E5%92%8C%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%8C%E9%82%A3%E4%B9%88%E5%8F%AA%E7%9C%8B%E8%BF%99%E4%B8%80%E7%AF%87%E5%B0%B1%E8%B6%B3%E5%A4%9F%E4%BA%86%EF%BC%81%EF%BC%89&amp;url=/archives/3fc35d12-6af3-4cf7-8e91-d530ead9de22" width="1" height="1" alt="" style="opacity:0;">
<p><strong>前言：在学习之前的数据结构的时候，我们使用的数据量都不是很大，但是在生活中，我们常常面临着要处理大量数据结果并得出最终结果，那么有没有什么数据结构可以帮助我们实现这样的功能呢？</strong></p>
<hr>
<blockquote>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa27075d63ed64169864a5c4601ef25a8.jpeg&amp;size=m" alt=""></p>
 <p><strong>✨✨✨这里是秋刀鱼不做梦的BLOG</strong></p>
 <p><strong>✨✨✨想要了解更多内容可以访问我的主页</strong><a href="https://blog.csdn.net/2302_80198073?type=blog" title="秋刀鱼不做梦-CSDN博客">秋刀鱼不做梦-CSDN博客</a></p>
</blockquote>
<p><strong>那么在开始学习处理大量数据的数据结构之前，先让我们看一下本文大致的讲解内容：</strong></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F46c3f3df70de4681a5ce3bd987e361c1.png&amp;size=m" alt=""></strong></p>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#1.%E4%BD%8D%E5%9B%BE">1.位图</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%881%EF%BC%89%E4%BD%8D%E5%9B%BE%E7%9A%84%E6%A6%82%E5%BF%B5">（1）位图的概念</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%EF%BC%882%EF%BC%89%E4%BD%8D%E5%9B%BE%E7%9A%84%E5%AE%9E%E7%8E%B0">（2）位图的实现</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%883%EF%BC%89%E4%BD%8D%E5%9B%BE%E7%9A%84%E5%BA%94%E7%94%A8">（3）位图的应用</a></p>
<p><a href="https://wiki.dgfygk.com#2.%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8">2.布隆过滤器</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%EF%BC%881%EF%BC%89%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E6%A6%82%E5%BF%B5">（1）布隆过滤器的概念</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%882%EF%BC%89%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0">（2）布隆过滤器的实现</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%883%EF%BC%89%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9">（3）布隆过滤器的优缺点</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%884%EF%BC%89%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E5%BA%94%E7%94%A8">（4）布隆过滤器的应用</a></p>
<p><a href="https://wiki.dgfygk.com#3.%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF">3.海量数据处理中的应用场景</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%881%EF%BC%89%E4%BD%8D%E5%9B%BE%E5%9C%A8%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8">（1）位图在海量数据处理中的应用</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%882%EF%BC%89%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E5%9C%A8%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8">（2）布隆过滤器在海量数据处理中的应用</a></p>
<hr>
<p>
 <br>
</p>
<h2>1.位图</h2>
<h3>（1）位图的概念</h3>
<p>首先我们要学习的数据结构就是位图，那么读者会发问了？什么是位图呢？以下是位图的概念：</p>
<blockquote>
 <p><strong>位图是一种空间优化的数据结构，适合用于表示大量数据的存在性。其基本思想是将每个数据用二进制的"0"或"1"表示，“1"表示数据存在，“0"表示数据不存在。位图特别适合用于对海量整数数据进行存在性检查或排序操作。</strong></p>
</blockquote>
<p><strong>如图：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe3465a5f0a8b4ba8b7dbddfd3caa3bba.png&amp;size=m" alt=""></p>
<p>通过上述的描述，相信读者对位图有了一定的了解，在了解了位图的定义之后，让我们用一个实例再来说明一下位图的作用：</p>
<p>假设有40亿个不重复的无符号整数，我们需要判断某个数是否存在于这40亿个数中。通常的解决方法可能是将数据存储在数组或列表中，然后进行遍历或使用二分查找。然而，这两种方式的时间复杂度较高，而位图通过将每个整数映射到相应的比特位，能以较低的空间消耗实现高效查询。如上例，假设数据集中有40亿个整数，使用位图只需要约512MB的空间即可完成数据存储和查询。</p>
<p>至此，我们就对位图有了初步的了解了！</p>
<p>
 <br>
</p>
<h3>（2）位图的实现</h3>
<p>当我们了解了位图是什么之后，现在让我们自我实现一下位图。</p>
<p><strong>——位图的核心是将整数映射到一个数组的特定位置并通过位操作来设置或获取该位置的值。下面是位图实现的代码：</strong></p>
<pre><code class="java">public class MyBitSet {

    private byte[] elem;
    private int usedSize;

    // 构造函数，初始化位图大小
    public MyBitSet(int n) {
        elem = new byte[n / 8 + 1]; // 位图大小：n个比特位
    }

    // 设置某一位为1，表示对应的值存在
    public void set(int val) {
        int arrayIndex = val / 8;
        int bitIndex = val % 8;
        elem[arrayIndex] |= (1 &lt;&lt; bitIndex);
        usedSize++;
    }

    // 检查某个值是否存在
    public boolean get(int val) {
        int arrayIndex = val / 8;
        int bitIndex = val % 8;
        return (elem[arrayIndex] &amp; (1 &lt;&lt; bitIndex)) != 0;
    }

    // 重置某一位为0，表示对应的值不存在
    public void reSet(int val) {
        int arrayIndex = val / 8;
        int bitIndex = val % 8;
        elem[arrayIndex] &amp;= ~(1 &lt;&lt; bitIndex);
        usedSize--;
    }

    // 获取已使用的比特位数量
    public int getUsedSize() {
        return usedSize;
    }
}
</code></pre>
<p><strong>根据上边的注释，我们可以很好的理解如何去实现一个位图，不过在此我们还是对上述的代码进行解释：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong><code>elem</code> 数组用于存储比特位信息，每个字节（8位）可存储8个整数的存在状态。</strong></p>
  </li>
  <li>
   <p><strong><code>set()</code> 方法通过位运算将某一位设置为1。</strong></p>
  </li>
  <li>
   <p><strong><code>get()</code> 方法通过按位与运算检查某位是否为1，从而判断对应的整数是否存在。</strong></p>
  </li>
  <li>
   <p><strong><code>reSet()</code> 方法则将某一位重置为0，表示数据不存在。</strong></p>
  </li>
 </ul>
 <p>
  <br>
 </p>
 <p><strong>这样我们就了解了如何去自我实现位图了！</strong></p>
</blockquote>
<p>
 <br>
</p>
<h3><strong>（3）位图的应用</strong></h3>
<p><strong>了解完了什么是位图和如何去实现一个位图之后，读者可以会发问到：位图除了可以处理大量数据之外，位图在生活中还有什么用处呢？</strong></p>
<p><strong>以下为位图的一些应用场景：</strong></p>
<ol>
 <li><strong>快速判断数据是否存在：例如，在大数据集群中判断某个用户ID是否存在。</strong></li>
 <li><strong>数据去重：通过位图可以快速判断某数据是否已经存在，从而避免重复操作。</strong></li>
 <li><strong>集合运算：位图能够高效地实现集合的并集、交集等操作，尤其适合用于处理大规模的集合数据。</strong></li>
 <li><strong>排序和去重：位图可以用于对大量整数进行排序和去重操作，例如，系统中磁盘块的标记操作。</strong></li>
</ol>
<p><strong>这样我们就了解了位图的应用了！</strong></p>
<p>
 <br>
</p>
<h2><strong>2.布隆过滤器</strong></h2>
<p><strong>同位图一样，在正式开始学习布隆过滤器之前，先让我们了解一下什么是布隆过滤器</strong></p>
<h3>（1）布隆过滤器的概念</h3>
<blockquote>
 <p><strong>布隆过滤器是一种概率型的数据结构，主要用于集合查询。与位图不同，布隆过滤器能够高效地判断某个元素是否"可能存在"或"肯定不存在”。它的特点是通过多个哈希函数将元素映射到位数组的不同位置，并设置对应比特位为1。当查询一个元素时，如果所有哈希函数映射的比特位都为1，则认为该元素"可能存在”；若有一个比特位为0，则该元素"肯定不存在”。</strong></p>
</blockquote>
<p><strong>如图：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb56a0348d74f464e829dc6391b671ced.png&amp;size=m" alt=""></p>
<p><strong>光看上述的文字解释可能有点晦涩难懂，那么这里我们使用一个生活中的案例来对其进行解释：</strong></p>
<p>想象你在家里开派对，邀请了50位朋友，但你不记得是否某位朋友已经到达。你可以通过查看他们是否签了名（签名簿）来判断。每个朋友到达后，你会要求他们签名在一个特定的位置上，这类似于哈希函数的映射。如果你检查到所有他们应签的位都已经被签了，那就可能意味着他们已经来过了，但也可能是误判。</p>
<p>相信通过上述的解释之后，读者可以对布隆过滤器有了更好的理解！</p>
<p>
 <br>
</p>
<h3>（2）布隆过滤器的实现</h3>
<p>了解完了布隆过滤器的定义之后，现在让我们自我实现一个布隆过滤器。</p>
<p>布隆过滤器的基本实现包含一个位数组和多个不同的哈希函数。每个哈希函数将输入的元素映射到位数组的一个位置，并将该位置的比特位设置为1。</p>
<p><strong>下面是实现布隆过滤器的代码：</strong></p>
<pre><code class="java">import java.util.BitSet;

// 简单哈希类
class SimpleHash {
    private int cap; // 哈希表的大小
    private int seed; // 哈希种子

    // 构造函数，初始化哈希表大小和种子
    public SimpleHash(int cap, int seed) {
        this.cap = cap;
        this.seed = seed;
    }

    // 哈希函数
    public int hash(String value) {
        int result = 0;
        int len = value.length();
        // 遍历字符串的每一个字符
        for (int i = 0; i &lt; len; i++) {
            result = seed * result + value.charAt(i); // 计算哈希值
        }
        // 返回哈希值与 cap-1 的位与操作结果
        return (cap - 1) &amp; result;
    }
}

// 布隆过滤器类
public class BloomFilter {
    private static final int DEFAULT_SIZE = 1 &lt;&lt; 24; // 位数组大小
    private static final int[] seeds = {5, 7, 11, 13, 31, 37, 61}; // 哈希种子

    private BitSet bits = new BitSet(DEFAULT_SIZE); // 位数组
    private SimpleHash[] func = new SimpleHash[seeds.length]; // 哈希函数数组

    // 构造函数，初始化哈希函数
    public BloomFilter() {
        for (int i = 0; i &lt; seeds.length; i++) {
            func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]); // 根据种子初始化 SimpleHash
        }
    }

    // 插入数据
    public void set(String value) {
        for (SimpleHash f : func) {
            bits.set(f.hash(value)); // 使用每个哈希函数设置位
        }
    }

    // 查询数据是否存在
    public boolean contains(String value) {
        for (SimpleHash f : func) {
            if (!bits.get(f.hash(value))) {
                return false; // 只要有一个哈希函数返回 false，就说明不在集合中
            }
        }
        return true; // 有可能误判
    }

    // 主函数，用于测试
    public static void main(String[] args) {
        BloomFilter filter = new BloomFilter();
        filter.set("test"); // 插入字符串 "test"
        System.out.println(filter.contains("test")); // 输出: true，表示 "test" 存在
        System.out.println(filter.contains("hello")); // 输出: false，表示 "hello" 不存在
    }
}
</code></pre>
<p><strong>代码解释：</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong><code>SimpleHash</code> 类实现了哈希函数，通过不同的种子（seed）保证多个哈希函数的多样性。</strong></p>
  </li>
  <li>
   <p><strong><code>BloomFilter</code> 类使用位数组和多个哈希函数实现数据的插入与查询操作。<code>set()</code> 方法将数据插入布隆过滤器，而 <code>contains()</code> 方法则检查数据是否可能存在。</strong></p>
  </li>
  <li>
   <p><strong>布隆过滤器的查询可能存在误判，但能保证元素"肯定不存在"的准确性。</strong></p>
  </li>
 </ul>
</blockquote>
<p><strong>这样我们就大致的了解了如何去自我实现一个布隆过滤器了！</strong></p>
<p>
 <br>
</p>
<h3><strong>（3）布隆过滤器的优缺点</strong></h3>
<p><strong>从上述的布隆过滤器的自我实现中，我们就可以发现布隆过滤器的一些优点和缺点，那么布隆过滤器有哪些优缺点呢？</strong></p>
<p><strong>优点</strong>：</p>
<blockquote>
 <ol>
  <li>
   <p><strong>高效的空间利用</strong>：布隆过滤器能够通过少量的空间判断数据是否存在，大大减少了内存占用。</p>
  </li>
  <li>
   <p><strong>快速查询</strong>：插入和查询的时间复杂度为O(K)，其中K为哈希函数的个数，与数据量大小无关。</p>
  </li>
  <li>
   <p><strong>并行化处理</strong>：多个哈希函数的操作可以并行执行，提高了性能。</p>
  </li>
 </ol>
</blockquote>
<p><strong>缺点</strong>：</p>
<blockquote>
 <ol>
  <li>
   <p><strong>误判</strong>：布隆过滤器可能会误判某些不存在的元素为存在，但它能保证不存在的元素一定不会被误判为存在。</p>
  </li>
  <li>
   <p><strong>不支持删除</strong>：一旦元素被插入布隆过滤器，无法删除，因为删除操作可能会误删除其他哈希值相同的元素。</p>
  </li>
 </ol>
</blockquote>
<p>
 <br>
</p>
<h3>（4）布隆过滤器的应用</h3>
<p><strong>通过上述对布隆过滤器的解释，我相信读者可以对布隆过滤器有一定的了解了，那么布隆过滤器有哪些应用呢？</strong></p>
<p><strong>布隆过滤器在以下场景中被广泛使用：</strong></p>
<ol>
 <li>
  <p><strong>网络爬虫的URL去重</strong>：在大规模的网络爬虫中，布隆过滤器用于判断某个URL是否已经被访问过，防止重复爬取。</p>
 </li>
 <li>
  <p><strong>垃圾邮件过滤</strong>：布隆过滤器用于记录垃圾邮件发送者的地址，并在邮件服务器上快速过滤垃圾邮件。</p>
 </li>
 <li>
  <p><strong>数据库缓存穿透</strong>：在大规模分布式系统中，布隆过滤器可以用于防止频繁的数据库查询，减少缓存穿透的风险。</p>
 </li>
</ol>
<p><strong>这样我们就了解了布隆过滤器的应用场景了！</strong></p>
<p>
 <br>
</p>
<h2><strong>3.</strong>海量数据处理中的应用场景</h2>
<p><strong>从上面的文章内容中我们可以知道，位图和布隆过滤器都是对海量数据进行处理的数据结构，那么其在海量数据处理中的应用场景有哪些呢？</strong></p>
<h3>（1）位图在海量数据处理中的应用</h3>
<p>位图可以处理非常庞大的数据集，特别是在内存有限的情况下，位图通过其空间效率优势可以快速解决诸如数据去重、集合运算等问题。例如，假设有两个包含100亿个整数的文件，我们只有1GB的内存，可以通过位图来实现这两个集合的交集、并集等操作。</p>
<p><strong>案例：找到只出现一次的整数</strong></p>
<pre><code class="java">// 假设我们有一个包含100亿个整数的文件，下面的代码实现了位图的简单应用
MyBitSet bitSet = new MyBitSet(10000000000);  // 10 billion bits
for (int num : nums) {
    if (!bitSet.get(num)) {
        bitSet.set(num);  // 如果没有出现过，设置为1
    } else {
        bitSet.reSet(num);  // 如果再次出现，重置为0
    }
}
// 遍历 bitSet，输出所有只出现一次的整数
</code></pre>
<p>
 <br>
</p>
<h3>（2）布隆过滤器在海量数据处理中的应用</h3>
<p>布隆过滤器由于其空间优势和高效的查询能力，在海量数据处理中被广泛应用于去重、快速查询等场景。例如，在网络爬虫中，布隆过滤器用于检测某个URL是否已经访问过，防止重复抓取。</p>
<p><strong>案例：布隆过滤器在URL去重中的应用</strong></p>
<pre><code class="java">BloomFilter filter = new BloomFilter();
for (String url : urls) {
    if (!filter.contains(url)) {
        // 处理未访问过的 URL
        filter.set(url);
    }
}
</code></pre>
<p><strong>——通过上述的简单案例，希望读者可以对其在海量数据处理中的应用场景有一定的了解。</strong></p>
<p>
 <br>
</p>
<hr>
<p><strong>以上就是本篇文章的全部内容了\~\~\~</strong></p>
<p>原文链接: https://blog.csdn.net/2302_80198073/article/details/142455572</p>]]></description><guid isPermaLink="false">/archives/3fc35d12-6af3-4cf7-8e91-d530ead9de22</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:38:31 GMT</pubDate></item><item><title><![CDATA[Python 神器：wxauto 库——解锁微信自动化的无限可能]]></title><link>https://wiki.dgfygk.com/archives/8f870ae2-a51a-435d-847f-95efb520ed0f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Python%20%E7%A5%9E%E5%99%A8%EF%BC%9Awxauto%20%E5%BA%93%E2%80%94%E2%80%94%E8%A7%A3%E9%94%81%E5%BE%AE%E4%BF%A1%E8%87%AA%E5%8A%A8%E5%8C%96%E7%9A%84%E6%97%A0%E9%99%90%E5%8F%AF%E8%83%BD&amp;url=/archives/8f870ae2-a51a-435d-847f-95efb520ed0f" width="1" height="1" alt="" style="opacity:0;">
<p>📝个人主页🌹：<a href="https://blog.csdn.net/Xxy_1008?spm=1010.2135.3001.5343" title="誓则盟约">誓则盟约</a>
 <br>
 ⏩收录专栏⏪：<a href="https://blog.csdn.net/xxy_1008/category_12700697.html?spm=1001.2014.3001.5482" title="机器学习">机器学习</a>
 <br>
 🤡往期回顾🤡：<a href="https://blog.csdn.net/Xxy_1008/article/details/139672696" title="“探索机器学习的多面世界：从理论到应用与未来展望”">“探索机器学习的多面世界：从理论到应用与未来展望”</a>
 <br>
 🌹🌹期待您的关注 🌹🌹</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0974e31108ad464b99dad4d7ee16e751.gif&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc5a01d861522e3834f82a2c3c6b6048e.gif%23pic_center&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5003fed9ac3c8de3f55c92d52e59fd7a.gif%23pic_center&amp;size=m" alt=""></p>
<h2>Python 神器：wxauto 库——解锁微信自动化的无限可能</h2>
<p><strong>在当今数字化的时代，自动化工具为我们的生活和工作带来了极大的便利。Python 的 <code>wxauto</code> 库正是这样一个强大的工具，它为我们打开了微信自动化操作的大门。</strong></p>
<hr>
<p>
 <br>
</p>
<h2><strong>目录：</strong></h2>
<p><a href="https://wiki.dgfygk.com#%C2%A0Python%20%E7%A5%9E%E5%99%A8%EF%BC%9Awxauto%20%E5%BA%93%E2%80%94%E2%80%94%E8%A7%A3%E9%94%81%E5%BE%AE%E4%BF%A1%E8%87%AA%E5%8A%A8%E5%8C%96%E7%9A%84%E6%97%A0%E9%99%90%E5%8F%AF%E8%83%BD">Python 神器：wxauto 库——解锁微信自动化的无限可能</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D%EF%BC%9A">功能介绍：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1%E5%A5%BD%E5%8F%8B%E5%92%8C%E7%BE%A4%E7%BB%84%E4%BF%A1%E6%81%AF">一、获取微信好友和群组信息</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81%EF%BC%9A">样例代码：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E8%AF%BB%E5%8F%96%E5%92%8C%E5%A4%84%E7%90%86%E8%81%8A%E5%A4%A9%E8%AE%B0%E5%BD%95">二、读取和处理聊天记录</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81%EF%BC%9A">样例代码：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E5%8A%9F%E8%83%BD">三、消息发送功能</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81%EF%BC%9A">样例代码：</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E8%87%AA%E5%8A%A8%E5%9B%9E%E5%A4%8D">四、自动回复</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81%EF%BC%9A">样例代码：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%E6%96%87%E4%BB%B6%E5%A4%84%E7%90%86">五、文件处理</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81%EF%BC%9A">样例代码：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95%EF%BC%9A">使用方法：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E5%AE%89%E8%A3%85">一、安装</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E5%AF%BC%E5%85%A5%E5%BA%93">二、导入库</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1%E5%AE%9E%E4%BE%8B">三、获取微信实例</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E5%85%B7%E4%BD%93%E6%93%8D%E4%BD%9C">四、具体操作</a></p>
<p><a href="https://wiki.dgfygk.com#1.%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%92%8C%E7%BE%A4%E7%BB%84%E4%BF%A1%E6%81%AF">1.获取好友和群组信息</a></p>
<p><a href="https://wiki.dgfygk.com#2.%E8%AF%BB%E5%8F%96%E8%81%8A%E5%A4%A9%E8%AE%B0%E5%BD%95">2.读取聊天记录</a></p>
<p><a href="https://wiki.dgfygk.com#3.%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF">3.发送消息</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%E5%8F%91%E9%80%81%E6%96%87%E6%9C%AC%E6%B6%88%E6%81%AF%EF%BC%9A">发送文本消息：</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87%E6%B6%88%E6%81%AF%EF%BC%9A">发送图片消息：</a></p>
<p><a href="https://wiki.dgfygk.com#4.%E8%87%AA%E5%8A%A8%E5%9B%9E%E5%A4%8D">4.自动回复</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%C2%A0">五、注意事项</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9A">应用场景：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%AE%A2%E6%9C%8D">一、自动化客服</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E4%BF%A1%E6%81%AF%E9%80%9A%E7%9F%A5%E4%B8%8E%E6%8F%90%E9%86%92">二、信息通知与提醒</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E7%A4%BE%E4%BA%A4%E5%AA%92%E4%BD%93%E7%AE%A1%E7%90%86">三、社交媒体管理</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E6%95%B0%E6%8D%AE%E9%87%87%E9%9B%86%E4%B8%8E%E5%88%86%E6%9E%90">四、数据采集与分析</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%E4%B8%AA%E6%80%A7%E5%8C%96%E8%90%A5%E9%94%80">五、个性化营销</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%85%AD%E3%80%81%E6%99%BA%E8%83%BD%E8%81%8A%E5%A4%A9%E6%9C%BA%E5%99%A8%E4%BA%BA">六、智能聊天机器人</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BC%98%E5%8A%BF%E4%B8%8E%E5%B1%80%E9%99%90%E6%80%A7%EF%BC%9A">优势与局限性：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E4%BC%98%E5%8A%BF">一、优势</a></p>
<p><a href="https://wiki.dgfygk.com#1.%20%E5%BC%BA%E5%A4%A7%E7%9A%84%E5%8A%9F%E8%83%BD">1. 强大的功能</a></p>
<p><a href="https://wiki.dgfygk.com#2.%20%E6%8F%90%E9%AB%98%E6%95%88%E7%8E%87">2. 提高效率</a></p>
<p><a href="https://wiki.dgfygk.com#3.%20%E6%98%93%E4%BA%8E%E4%BD%BF%E7%94%A8">3. 易于使用</a></p>
<p><a href="https://wiki.dgfygk.com#4.%20%E7%81%B5%E6%B4%BB%E6%80%A7">4. 灵活性</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E5%B1%80%E9%99%90%E6%80%A7">二、局限性</a></p>
<p><a href="https://wiki.dgfygk.com#1.%20%E4%BE%9D%E8%B5%96%E5%BE%AE%E4%BF%A1%E5%AE%A2%E6%88%B7%E7%AB%AF">1. 依赖微信客户端</a></p>
<p><a href="https://wiki.dgfygk.com#2.%20%E5%8F%AF%E8%83%BD%E7%9A%84%E8%BF%9D%E8%A7%84%E9%A3%8E%E9%99%A9">2. 可能的违规风险</a></p>
<p><a href="https://wiki.dgfygk.com#3.%20%E7%A8%B3%E5%AE%9A%E6%80%A7%E9%97%AE%E9%A2%98">3. 稳定性问题</a></p>
<p><a href="https://wiki.dgfygk.com#4.%20%E5%AE%89%E5%85%A8%E5%92%8C%E9%9A%90%E7%A7%81%E8%80%83%E9%87%8F">4. 安全和隐私考量</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%9C%AA%E6%9D%A5%E5%B1%95%E6%9C%9B%EF%BC%9A">未来展望：</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E6%9B%B4%E5%BC%BA%E5%A4%A7%E7%9A%84%E5%8A%9F%E8%83%BD%E9%9B%86%E6%88%90">一、更强大的功能集成</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81%E5%A2%9E%E5%BC%BA%E7%9A%84%E7%A8%B3%E5%AE%9A%E6%80%A7%E5%92%8C%E5%85%BC%E5%AE%B9%E6%80%A7">二、增强的稳定性和兼容性</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81%E6%99%BA%E8%83%BD%E5%8C%96%E4%B8%8E%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%9A%84%E8%9E%8D%E5%90%88">三、智能化与机器学习的融合</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9B%9B%E3%80%81%E5%AE%89%E5%85%A8%E4%B8%8E%E5%90%88%E8%A7%84%E7%9A%84%E5%BC%BA%E5%8C%96">四、安全与合规的强化</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%94%E3%80%81%E7%A4%BE%E5%8C%BA%E4%B8%8E%E7%94%9F%E6%80%81%E7%9A%84%E5%8F%91%E5%B1%95">五、社区与生态的发展</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%85%AD%E3%80%81%E4%B8%8E%E5%85%B6%E4%BB%96%E6%8A%80%E6%9C%AF%E7%9A%84%E9%9B%86%E6%88%90">六、与其他技术的集成</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%80%9C%E8%87%AA%E4%BF%A1%E4%BA%BA%E7%94%9F%E4%BA%8C%E7%99%BE%E5%B9%B4%EF%BC%8C%E4%BC%9A%E5%BD%93%E6%B0%B4%E5%87%BB%E4%B8%89%E5%8D%83%E9%87%8C%E3%80%82%E2%80%9D%E2%80%94%E2%80%94%E3%80%8A%E6%AE%8B%E5%8F%A5%E3%80%8B">“自信人生二百年，会当水击三千里。“——《残句》</a></p>
<hr>
<h3>功能介绍：</h3>
<h4>一、获取微信好友和群组信息</h4>
<p><strong>wxauto</strong><strong>库使我们能够轻松获取微信中的好友和群组列表。</strong> 通过简单的代码调用，我们可以获取到好友的昵称、备注、微信号等详细信息，以及群组的名称、成员数量等关键数据。<strong>这一功能在需要对微信联系人进行管理和分析的场景中非常有用。</strong></p>
<p>例如，如果您正在开发一个微信营销工具，您可以使用此功能快速筛选出特定条件的好友或群组，如活跃度高的群组或潜在客户的好友，从而有针对性地进行推广活动。</p>
<p>样例代码：</p>
<pre><code class="python">from wxauto import *

# 获取当前微信客户端
wx = WeChat()

# 获取好友信息
friends = wx.GetFriends()
for friend in friends:
    print(f"好友昵称: {friend['NickName']}, 备注: {friend['RemarkName']}")

# 获取群组信息
groups = wx.GetGroups()
for group in groups:
    print(f"群组名称: {group['NickName']}, 成员数量: {group['MemberCount']}")
</code></pre>
<h4>二、读取和处理聊天记录</h4>
<p>能够<strong>读取微信的聊天记录是 <code>wxauto</code> 库的另一个重要功能</strong>。这不仅包括文本消息，还可能涵盖图片、语音、文件等多种类型的消息。</p>
<p><strong>通过对聊天记录的分析，我们可以挖掘出有价值的信息</strong>。比如，对于客服工作，我们可以利用这个功能自动监测客户的常见问题和反馈，以便及时优化服务策略。又或者在社交分析中，通过对大量聊天记录的情感分析，了解用户的情绪倾向和需求。</p>
<p>样例代码：</p>
<pre><code class="python">from wxauto import *

# 获取微信实例
wx = WeChat()

# 选择要读取聊天记录的好友或群组
who = '好友昵称'  # 或者 '群组名称'

# 获取聊天记录
msgs = wx.GetChatMessage(who)

# 打印聊天记录
for msg in msgs:
    print(f"发送者: {msg['Sender']}, 内容: {msg['Content']}")

# 可以根据具体需求对聊天记录进行处理，比如统计特定关键词的出现次数
keyword = '特定关键词'
count = 0
for msg in msgs:
    if keyword in msg['Content']:
        count += 1
print(f"{keyword} 在聊天记录中出现的次数: {count}")
</code></pre>
<h4>三、消息发送功能</h4>
<p><strong>wxauto</strong><strong>库支持向单个好友、多个好友或者群组发送各种类型的消息</strong> 。无论是简单的文本消息，还是复杂的多媒体内容，如图片、文件、语音等，都能轻松<strong>实现自动化发送</strong>。</p>
<p><strong>这在需要批量通知、定时提醒或者自动回复的场景中发挥了巨大作用。</strong>例如，企业可以使用此功能在节假日向员工发送祝福消息，或者电商卖家可以根据订单状态自动向客户发送物流更新信息。</p>
<p><strong>样例代码：</strong></p>
<pre><code class="python">from wxauto import *

# 获取微信实例
wx = WeChat()

# 要发送消息的对象，可以是好友昵称或群组名称
who = '好友昵称'  # 或者 '群组名称'

# 要发送的消息内容
message = '这是一条测试消息！'

# 发送文本消息
wx.SendMsg(message, who)

# 发送图片消息（需要提供图片的路径）
image_path = 'image.jpg'  # 替换为实际的图片路径
wx.SendImage(image_path, who)

# 发送文件（需要提供文件的路径）
file_path = 'document.pdf'  # 替换为实际的文件路径
wx.SendFiles(file_path, who)
</code></pre>
<h4>四、自动回复</h4>
<p><strong>自动回复功能是 <code>wxauto</code> 库的一大亮点。我们可以根据预设的规则和条件，实现对微信消息的自动回复。</strong></p>
<p>比如，当收到特定关键词的消息时，自动回复相应的内容；或者在非工作时间，自动回复告知对方将在工作时间处理。<strong>这不仅提高了沟通效率，还能及时响应重要消息，提升用户体验</strong>。</p>
<p>样例代码：</p>
<pre><code class="python">from wxauto import *
import time

def auto_reply():
    wx = WeChat()
    while True:
        msgs = wx.GetAllMessage
        for msg in msgs:
            if '特定关键词' in msg.Content:
                wx.SendMsg('这是自动回复的消息', msg.Sender)
        time.sleep(5)  # 每隔 5 秒检查一次新消息

if __name__ == "__main__":
    auto_reply()
</code></pre>
<h4>五、文件处理</h4>
<p>除了上述主要功能外，<strong><code>wxauto</code> 库还具备处理微信文件的能力。</strong>可以自动下载、上传文件，方便文件在微信中的管理和共享。</p>
<p>无论是工作中的文档共享，还是朋友间的照片分享，<strong>这个功能都使得文件的交互变得更加便捷和高效。</strong></p>
<p>样例代码：</p>
<pre><code class="python">from wxauto import *

# 获取微信实例
wx = WeChat()

# 定义要发送的文件路径
file_path = 'your_file_path'  # 替换为实际的文件路径

# 定义接收文件的对象，可以是好友昵称或群组名称
receiver = 'friend_or_group_name'  # 替换为实际的接收对象

# 发送文件
wx.SendFiles(file_path, receiver)
</code></pre>
<hr>
<h3>使用方法：</h3>
<h4>一、安装</h4>
<p><strong>使用以下命令通过 <code>pip</code> 安装 <code>wxauto</code> 库：</strong></p>
<pre><code class="python">pip install wxauto
</code></pre>
<h4>二、导入库</h4>
<p><strong>在 Python 脚本中导入所需的类和函数：</strong></p>
<pre><code class="python">from wxauto import WeChat
</code></pre>
<h4>三、获取微信实例</h4>
<p><strong>通过以下代码获取当前登录的微信实例：</strong></p>
<pre><code class="python">wx = WeChat()
</code></pre>
<h4>四、具体操作</h4>
<p><strong>1.获取好友和群组信息</strong></p>
<pre><code class="python">    friends = wx.GetFriends()
    groups = wx.GetGroups()
</code></pre>
<p><strong>2.读取聊天记录</strong></p>
<pre><code class="python">    who = '好友昵称或群组名称'
    msgs = wx.GetChatMessage(who)
</code></pre>
<p><strong>3.发送消息</strong></p>
<ul>
 <li>发送文本消息：</li>
</ul>
<pre><code class="python">   message = '消息内容'
   wx.SendMsg(message, '接收对象')
</code></pre>
<ul>
 <li>发送图片消息：</li>
</ul>
<pre><code class="python">  image_path = '图片路径'
  wx.SendImage(image_path, '接收对象')
</code></pre>
<p><strong>4.自动回复</strong></p>
<pre><code class="python">    def auto_reply():
        while True:
            msgs = wx.GetAllMessage
            for msg in msgs:
                if '特定关键词' in msg.Content:
                    wx.SendMsg('回复内容', msg.Sender)
</code></pre>
<hr>
<h4>五、注意事项</h4>
<p><strong>1.运行代码时，微信客户端需处于登录状态且保持可见，不能最小化或隐藏。</strong></p>
<p><strong>2.部分微信版本可能存在兼容性问题，建议使用较新的稳定版本。</strong> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe1b77088530a489cb330155f0c17684a.png&amp;size=m" alt=""></p>
<h3>应用场景：</h3>
<p><strong>在当今数字化和智能化的时代，Python 的 <code>wxauto</code> 库为我们打开了许多有趣且实用的应用场景之门。</strong></p>
<h4>一、自动化客服</h4>
<p>在电商和在线服务领域，客户咨询量通常很大。<strong>利用 <code>wxauto</code> 库，可以构建一个自动客服系统。</strong> 当客户发送特定的问题关键词时，系统能够自动回复常见问题的答案，如订单状态查询、售后服务指引等。这<strong>不仅提高了客户服务的响应速度，还减轻了人工客服的工作负担，让人工客服能够专注于处理更复杂和个性化的问题。</strong></p>
<p>
 <br>
</p>
<h4>二、信息通知与提醒</h4>
<p><strong>企业或团队可以通过 <code>wxauto</code> 库实现内部信息的自动化通知</strong> 。例如，每天定时向员工发送工作安排、重要会议提醒，或者在项目关键节点自动推送进度更新。<strong>对于学校和教育机构，也可以向学生和家长发送课程变动通知、考试成绩发布等信息。</strong></p>
<p>
 <br>
</p>
<h4>三、社交媒体管理</h4>
<p>对于社交媒体运营者，<strong><code>wxauto</code> 库能帮助实现多账号的管理和内容发布</strong> 。可以自动向多个微信好友或群组发送推广信息、活动邀请，还能根据用户的互动情况进行自动回复和互动，<strong>提高社交媒体的运营效率和用户参与度。</strong></p>
<p>
 <br>
</p>
<h4>四、数据采集与分析</h4>
<p>通过读取微信聊天记录，我们<strong>可以采集大量有价值的数据</strong> 。例如，对某个产品的用户反馈、市场调研中的意见收集等。<strong>利用这些数据进行分析，能够帮助企业更好地了解用户需求，优化产品和服务。</strong></p>
<p>
 <br>
</p>
<h4>五、个性化营销</h4>
<p>基于用户的微信聊天记录和行为数据，<strong>使用 <code>wxauto</code> 库可以实现个性化的营销推送</strong>。例如，根据用户的购买历史和兴趣偏好，向其发送相关的产品推荐和优惠信息，提高营销的精准度和转化率。</p>
<p>
 <br>
</p>
<h4>六、智能聊天机器人</h4>
<p><strong>结合自然语言处理技术和 <code>wxauto</code> 库，开发一个智能聊天机器人</strong>。它可以与微信用户进行自然流畅的对话，提供娱乐、知识解答、情感陪伴等服务。</p>
<p>
 <br>
</p>
<h3>优势与局限性：</h3>
<h4>一、优势</h4>
<ol>
 <li>强大的功能</li>
</ol>
<p><strong>wxauto</strong><strong>库提供了丰富的功能，涵盖了微信操作的多个方面</strong>，如获取好友和群组信息、读取和处理聊天记录、发送各种类型的消息、实现自动回复等。这使得开发者能够在微信平台上实现高度自动化和定制化的任务。</p>
<p>
 <br>
</p>
<ol>
 <li>提高效率</li>
</ol>
<p>通过自动化微信相关的操作，<strong>可以节省大量的人工时间和精力</strong>。例如，自动发送通知、批量处理消息等，能够快速完成原本繁琐的工作。</p>
<p>
 <br>
</p>
<ol>
 <li>易于使用</li>
</ol>
<p>对于熟悉 Python 编程的开发者来说，<strong><code>wxauto</code> 库的接口相对简洁明了，易于理解和使用。</strong>借助 Python 的语法和逻辑，能够轻松构建复杂的微信交互逻辑。</p>
<p>
 <br>
</p>
<p><strong>4. 灵活性</strong></p>
<p>可以根据具体的需求灵活定制各种功能，满足不同场景下的特殊要求。无论是用于个人项目还是企业级应用，<strong>都能提供一定的可扩展性</strong>。</p>
<p>
 <br>
</p>
<h4>二、局限性</h4>
<ol>
 <li>依赖微信客户端</li>
</ol>
<p><code>wxauto</code> 的功能实现严重依赖于微信客户端的状态和版本。<strong>微信客户端的更新可能导致库的部分功能失效或不稳定，需要及时进行适配和修复。</strong></p>
<p>
 <br>
</p>
<ol>
 <li>可能的违规风险</li>
</ol>
<p>使用自动化工具操作微信需要谨慎遵循微信的使用规则和相关法律法规。<strong>不当的使用可能导致微信账号被封禁或引发其他法律问题。</strong></p>
<p>
 <br>
</p>
<ol>
 <li>稳定性问题</li>
</ol>
<p><strong>由于微信客户端的复杂性和不确定性，<code>wxauto</code> 在某些情况下可能会出现不稳定的情况</strong>，例如消息发送失败、获取信息不准确等。</p>
<p>
 <br>
</p>
<ol>
 <li>安全和隐私考量</li>
</ol>
<p><strong>处理微信中的数据涉及到用户的隐私和安全问题</strong>。需要开发者采取适当的措施来保护数据的安全性和合规性。</p>
<hr>
<h3>未来展望：</h3>
<p><strong>随着技术的不断发展和人们对自动化需求的持续增长，Python 的 <code>wxauto</code> 库有望在以下几个方面取得进一步的发展和突破：</strong></p>
<p>
 <br>
</p>
<h4>一、更强大的功能集成</h4>
<p><strong>未来，<code>wxauto</code> 库可能会整合更多微信的高级功能，如微信小程序的交互、微信支付的自动化处理、视频通话的控制等。</strong>这将使开发者能够在更广泛的领域利用微信平台实现自动化操作，拓展其应用场景。</p>
<p>
 <br>
</p>
<h4>二、增强的稳定性和兼容性</h4>
<p>为了应对微信客户端频繁的更新，<strong><code>wxauto</code> 库将不断改进和优化，以提高其稳定性和兼容性。</strong>这意味着更少的功能失效和错误，能够更可靠地在各种微信版本和操作系统上运行，为用户提供更流畅的体验。</p>
<p>
 <br>
</p>
<h4>三、智能化与机器学习的融合</h4>
<p><strong>结合人工智能和机器学习技术，<code>wxauto</code> 库可能会实现更智能的消息处理和交互。</strong>例如，通过自然语言处理和情感分析，自动回复能够更加精准和贴合上下文，更好地满足用户的需求，提供更个性化的服务。</p>
<p>
 <br>
</p>
<h4>四、安全与合规的强化</h4>
<p><strong>随着数据隐私和安全法规的日益严格，<code>wxauto</code> 库将更加注重安全机制的完善。</strong>开发者将能够更方便地遵循相关法规，确保在使用库进行微信操作时，用户的数据得到充分保护，避免潜在的法律风险。</p>
<p>
 <br>
</p>
<h4>五、社区与生态的发展</h4>
<p><strong>预计会有越来越多的开发者参与到 <code>wxauto</code> 库的开发和改进中，形成一个活跃的社区。</strong>这将促进更多的插件和扩展的出现，丰富库的功能，同时也为用户提供更多的交流和学习资源。</p>
<p>
 <br>
</p>
<h4>六、与其他技术的集成</h4>
<p><strong>wxauto</strong><strong>库有望与其他热门的技术框架和工具进行集成</strong>，如与自动化测试框架、数据分析库等的结合。这将使开发者能够在一个综合的技术栈中，更高效地利用微信数据和功能，实现更复杂的业务逻辑。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6651cbec76da4304ba3c9cafcf247e45.jpeg&amp;size=m" alt=""></p>
<h2>“自信人生二百年，会当水击三千里。“——《残句》</h2>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/Xxy_1008/article/details/140354558</p>]]></description><guid isPermaLink="false">/archives/8f870ae2-a51a-435d-847f-95efb520ed0f</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Sun, 29 Sep 2024 03:37:43 GMT</pubDate></item><item><title><![CDATA[【python】python代码打包工具cx_Freeze的介绍、原理、用法及实战案例分析]]></title><link>https://wiki.dgfygk.com/archives/ee1b8035-ea2b-4592-bdf9-abb69329d7f9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90python%E3%80%91python%E4%BB%A3%E7%A0%81%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7cx_Freeze%E7%9A%84%E4%BB%8B%E7%BB%8D%E3%80%81%E5%8E%9F%E7%90%86%E3%80%81%E7%94%A8%E6%B3%95%E5%8F%8A%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90&amp;url=/archives/ee1b8035-ea2b-4592-bdf9-abb69329d7f9" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa83ac3be949d33a951d8df335d277275.jpeg%23pic_center&amp;size=m" alt=""></p>
<p><strong>✨✨ 欢迎大家来到景天科技苑✨✨</strong></p>
<p><strong>🎈🎈 养成好习惯，先赞后看哦\~🎈🎈</strong></p>
<blockquote>
 <p>🏆 作者简介：景天科技苑
  <br>
  🏆《头衔》：大厂架构师，华为云开发者社区专家博主，阿里云开发者社区专家博主，CSDN全栈领域优质创作者，掘金优秀博主，51CTO博客专家等。
  <br>
  🏆《博客》：Python全栈，PyQt5和Tkinter桌面开发，小程序开发，人工智能，js逆向，App逆向，网络系统安全，数据分析，Django，fastapi，flask等框架，云原生K8S，linux，shell脚本等实操经验，网站搭建，数据库等分享。</p>
 <p><strong>所属的专栏：</strong> <a href="https://blog.csdn.net/littlefun591/category_12543578.html?spm=1001.2014.3001.5482">python综合应用，基础语法到高阶实战教学</a>
  <br>
  <strong>景天的主页：</strong> <a href="https://jingtian.blog.csdn.net/">景天科技苑</a></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F74c8480e94cf486f82a8f892271aa91e.gif%23pic_center&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#Pythoncx_Freeze_20">Python中cx_Freeze的详细用法教程</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#cx_Freeze_22">cx_Freeze概述</a></li>
 <li><a href="https://wiki.dgfygk.com#cx_FreezePyInstaller_30">cx_Freeze相对于PyInstaller的优点</a></li>
 <li><a href="https://wiki.dgfygk.com#cx_Freeze_58">安装cx_Freeze</a></li>
 <li><a href="https://wiki.dgfygk.com#_72">基本用法</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#setuppy_74">编写setup.py</a></li>
   <li><a href="https://wiki.dgfygk.com#_111">打包可执行文件</a></li>
   <li><a href="https://wiki.dgfygk.com#_121">注意事项</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_126">进阶用法</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_128">图标设置</a></li>
   <li><a href="https://wiki.dgfygk.com#_141">添加数据文件</a></li>
   <li><a href="https://wiki.dgfygk.com#_156">隐藏控制台窗口</a></li>
   <li><a href="https://wiki.dgfygk.com#_160">打包成单个文件</a></li>
   <li><a href="https://wiki.dgfygk.com#PyQt5_164">打包实例：使用PyQt5</a></li>
   <li><a href="https://wiki.dgfygk.com#_200">打包前的准备</a></li>
   <li><a href="https://wiki.dgfygk.com#_204">打包过程</a></li>
   <li><a href="https://wiki.dgfygk.com#_210">遇到的问题及解决方案</a></li>
   <li><a href="https://wiki.dgfygk.com#_216">后续步骤</a></li>
   <li><a href="https://wiki.dgfygk.com#_225">调试与故障排除</a></li>
   <li><a href="https://wiki.dgfygk.com#_253">打包后的测试</a></li>
   <li><a href="https://wiki.dgfygk.com#_262">分发</a></li>
   <li><a href="https://wiki.dgfygk.com#_270">维护和更新</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_278">总结</a></li>
</ul>
<p>
 <br>
</p>
<h2>Python中cx_Freeze的详细用法教程</h2>
<h3>cx_Freeze概述</h3>
<p>在Python开发中，将脚本打包成独立的可执行文件是一项常见的需求，特别是在需要将程序分发给没有安装Python环境的用户时。<code>cx_Freeze</code>是一个流行的Python库，它可以将Python脚本及其依赖项打包成独立的可执行文件，支持多平台（如Windows、Linux和macOS）。本文将结合实际案例，详细介绍<code>cx_Freeze</code>的安装、配置和使用方法。</p>
<p>官网：https://pypi.org/project/cx-Freeze/
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcd0a1b1104c64461873d0e3eb5852b5e.png&amp;size=m" alt=""></p>
<h3>cx_Freeze相对于PyInstaller的优点</h3>
<p>在Python中，<code>cx_Freeze</code>和<code>PyInstaller</code>都是流行的打包工具，它们各自具有一些优点和特性。关于<code>cx_Freeze</code>相对于<code>PyInstaller</code>的优点，可以从以下几个方面进行归纳：</p>
<ol>
 <li>
  <p><strong>配置灵活性</strong>：</p>
 </li>
 <li>
  <p><code>cx_Freeze</code>通过<code>setup.py</code>文件提供了较高的配置灵活性。开发者可以详细指定打包过程中的各种选项和参数，如程序名称、版本号、图标、依赖项等。这种灵活性使得<code>cx_Freeze</code>能够更精确地满足特定项目的需求。</p>
 </li>
 <li>
  <p><strong>依赖项管理</strong>：</p>
 </li>
 <li>
  <p><code>cx_Freeze</code>能够自动检测和打包Python脚本所依赖的模块和库，包括标准库和第三方库。这种自动化的依赖项管理减少了手动配置的工作量，并降低了因遗漏依赖项而导致的错误。</p>
 </li>
 <li>
  <p><strong>输出格式多样性</strong>：</p>
 </li>
 <li>
  <p><code>cx_Freeze</code>支持生成多种输出格式，如可执行文件、目录、ZIP归档文件等，以适应不同的部署需求。这种多样性为开发者提供了更多的选择和灵活性。</p>
 </li>
 <li>
  <p><strong>与Python库的兼容性</strong>：</p>
 </li>
 <li>
  <p><code>cx_Freeze</code>与许多常见的Python库和框架兼容，包括Tkinter、PyQt、Django等。这意味着开发者可以更容易地将这些库和框架集成到他们的项目中，并使用<code>cx_Freeze</code>进行打包。</p>
 </li>
 <li>
  <p><strong>社区和文档支持</strong>：</p>
 </li>
 <li>
  <p>尽管<code>PyInstaller</code>在流行度和社区支持方面可能略胜一筹，但<code>cx_Freeze</code>也拥有一个活跃的社区和相对完善的文档。这些资源可以帮助开发者解决在打包过程中遇到的问题。</p>
 </li>
 <li>
  <p><strong>透明度和可定制性</strong>：</p>
 </li>
 <li>
  <p><code>cx_Freeze</code>的打包过程相对透明，开发者可以更容易地理解其工作原理并进行必要的定制。这对于需要高度定制打包过程的项目来说是一个重要的优点。</p>
 </li>
</ol>
<p>需要注意的是，以上优点并非绝对，因为<code>PyInstaller</code>也具有许多独特的优点和特性。在选择使用哪个打包工具时，开发者应根据自己的项目需求、个人偏好以及工具的特性和限制来做出决策。</p>
<p>此外，随着时间的推移和工具的不断发展，上述优点可能会发生变化。因此，在做出决策之前，建议查阅最新的文档和社区讨论以获取最准确的信息。</p>
<h3>安装cx_Freeze</h3>
<p>首先，确保你的Python环境已经安装。然后，使用pip命令安装<code>cx_Freeze</code>。在命令行（终端）中运行以下命令：</p>
<pre><code class="prism language-bash">pip install cx_Freeze
</code></pre>
<p>如果你使用的是特定版本的Python，并且需要安装与之兼容的<code>cx_Freeze</code>版本，可以通过指定版本号来安装，例如：</p>
<pre><code class="prism language-bash">pip install cx_Freeze==6.1
</code></pre>
<h3>基本用法</h3>
<h4>编写setup.py</h4>
<p>要使用<code>cx_Freeze</code>打包Python脚本，你需要编写一个<code>setup.py</code>文件，该文件告诉<code>cx_Freeze</code>如何打包你的程序。以下是一个基本的<code>setup.py</code>示例：</p>
<pre><code class="prism language-python">from cx_Freeze import setup, Executable

# 要打包的Python脚本路径
script = "main.py"

# 创建可执行文件的配置
exe = Executable(
    script=script,
    base=None,  # 对于GUI应用，可以设置为"Win32GUI"来隐藏控制台窗口
    targetName="MyProgram"  # 生成的可执行文件名称
)

# 打包的参数配置
options = {

    "build_exe": {

        "packages": [],  # 需要打包的额外Python包列表
        "excludes": [],  # 不需要打包的Python包列表
        "include_files": [],  # 需要包含的文件或文件夹列表
        "include_msvcr": True  # 是否包含Microsoft Visual C++运行时库
    }
}

# 打包配置
setup(
    name="MyProgram",
    version="1.0",
    description="My Program Description",
    options=options,
    executables=[exe]
)
</code></pre>
<h4>打包可执行文件</h4>
<p>在命令行（终端）中，运行以下命令来打包你的Python脚本：</p>
<pre><code class="prism language-bash">python setup.py build
</code></pre>
<p>执行上述命令后，<code>cx_Freeze</code>会创建一个<code>build</code>文件夹，其中包含打包好的可执行文件和所有必要的库。</p>
<h4>注意事项</h4>
<ul>
 <li>打包时可能会因为缺少依赖库而失败，此时可以在<code>options</code>键下添加依赖库来修复。</li>
 <li>如果你的应用是GUI应用，并且不希望显示控制台窗口，可以将<code>base</code>参数设置为<code>"Win32GUI"</code>。</li>
</ul>
<h3>进阶用法</h3>
<h4>图标设置</h4>
<p>为了让生成的可执行文件看起来更专业，你可以在<code>Executable</code>配置中指定一个图标文件。修改<code>setup.py</code>中的<code>exe</code>配置如下：</p>
<pre><code class="prism language-python">exe = Executable(
    script=script,
    base=None,
    targetName="MyProgram",
    icon="my_icon.ico"  # 指定图标文件
)
</code></pre>
<h4>添加数据文件</h4>
<p>如果你的应用依赖于一些额外的数据文件（如配置文件、图像等），可以在<code>setup.py</code>中添加这些文件。修改<code>options</code>配置如下：</p>
<pre><code class="prism language-python">options = {

    "build_exe": {

        "packages": [],
        "excludes": [],
        "include_files": ["data/config.json", "images/"],  # 需要包含的文件或文件夹
        "include_msvcr": True
    }
}
</code></pre>
<h4>隐藏控制台窗口</h4>
<p>对于GUI应用，你可能不希望在运行时显示控制台窗口。除了将<code>base</code>参数设置为<code>"Win32GUI"</code>外，你还需要确保你的Python脚本中没有打印到控制台的代码，或者这些代码被适当地重定向或隐藏。</p>
<h4>打包成单个文件</h4>
<p><code>cx_Freeze</code>本身不支持生成单个文件，但你可以使用外部工具（如Inno Setup、NSIS等）来创建单个安装文件。这需要在生成的<code>build</code>文件夹的基础上，创建一个安装程序。</p>
<h4>打包实例：使用PyQt5</h4>
<p>假设你有一个使用PyQt5编写的GUI应用，并希望将其打包成可执行文件。首先，确保你的<code>setup.py</code>文件中包含了PyQt5的依赖。以下是一个示例配置：</p>
<pre><code class="prism language-python">from cx_Freeze import setup, Executable

# 打包配置
build_exe_options = {

    "packages": ["PyQt5.QtWidgets", "PyQt5.QtGui", "PyQt5.QtCore"],
    "excludes": [],
    "include_files": [],
    "include_msvcr": True,
    "optimize": 2  # 启用Python优化（可选）
}

# 可执行文件配置
exe = Executable(
    script="main.py",
    base="Win32GUI" if sys.platform == "win32" else None,  # 根据平台决定是否需要隐藏控制台
    icon="app_icon.ico",  # 应用程序图标
    targetName="MyApp"  # 生成的可执行文件名
)

# 设置
setup(
    name="MyApp",
    version="1.0",
    description="My PyQt5 Application",
    options={
   "build_exe": build_exe_options},
    executables=[exe]
)
</code></pre>
<p>在这个配置中，我们明确指定了PyQt5的一些主要子模块（<code>QtWidgets</code>, <code>QtGui</code>, <code>QtCore</code>）作为需要打包的包。这确保了<code>cx_Freeze</code>能够找到并包含这些必要的依赖项。</p>
<h4>打包前的准备</h4>
<p>在打包之前，请确保你的Python环境干净且只安装了必要的库。过多的库可能会增加打包后的文件大小，并可能引入不必要的依赖冲突。</p>
<h4>打包过程</h4>
<ol>
 <li><strong>编写你的Python脚本</strong> （例如<code>main.py</code>），并确保它可以在你的Python环境中正常运行。</li>
 <li><strong>创建<code>setup.py</code></strong> ：根据上面的示例，创建一个包含打包配置的<code>setup.py</code>文件。</li>
 <li><strong>运行打包命令</strong> ：在命令行（终端）中，运行<code>python setup.py build</code>来执行打包过程。</li>
</ol>
<h4>遇到的问题及解决方案</h4>
<ul>
 <li><strong>缺少依赖</strong> ：如果打包过程中提示缺少某些依赖，请检查你的<code>packages</code>列表是否包含了所有必要的库。对于复杂的项目，可能还需要手动指定额外的依赖项。</li>
 <li><strong>运行时错误</strong>：如果生成的可执行文件在运行时出现错误，请检查Python脚本本身是否有错误，或者依赖项是否未正确包含。</li>
 <li><strong>图标不显示</strong> ：如果应用程序图标未正确显示，请确保图标文件（<code>.ico</code>）是有效的，并且路径在<code>setup.py</code>中正确指定。</li>
</ul>
<h4>后续步骤</h4>
<ul>
 <li><strong>测试可执行文件</strong>：在目标平台上运行生成的可执行文件，确保它按预期工作。</li>
 <li><strong>创建分发包</strong>：如果你需要将应用程序分发给其他用户，可以使用额外的工具（如Inno Setup、NSIS等）来创建一个安装程序，该安装程序可以自动处理依赖项和文件放置。</li>
 <li><strong>优化</strong> ：考虑优化你的Python脚本和<code>cx_Freeze</code>配置，以减小生成的可执行文件的大小并提高性能。</li>
</ul>
<h4>调试与故障排除</h4>
<p>在打包过程中，可能会遇到各种问题，以下是一些常见的调试和故障排除技巧：</p>
<ol>
 <li>
  <p><strong>查看构建日志</strong> ：
   <br>
   <code>cx_Freeze</code> 在构建过程中会生成详细的日志，这通常位于构建输出目录中（默认为 <code>build</code>）。检查这些日志文件可以帮助你了解哪些文件被复制了，哪些模块被包含了，以及是否有任何错误或警告。</p>
 </li>
 <li>
  <p><strong>使用命令行选项</strong> ：
   <br>
   你可以使用 <code>cx_Freeze</code> 的命令行选项来获取更多的输出信息。例如，<code>python setup.py build --verbose</code> 会以更详细的方式输出构建过程，有助于诊断问题。</p>
 </li>
 <li>
  <p><strong>依赖项管理</strong> ：
   <br>
   确保你的 <code>setup.py</code> 文件中正确列出了所有必需的依赖项。如果遗漏了某些依赖项，你的应用程序可能无法正常运行。使用像 <code>pipreqs</code> 这样的工具可以帮助你自动生成 <code>requirements.txt</code> 文件，但请注意，它可能不会捕获所有动态加载的依赖项。</p>
 </li>
 <li>
  <p><strong>虚拟环境</strong> ：
   <br>
   在虚拟环境中打包你的应用程序是一个好习惯。这有助于确保你的构建环境干净且没有不必要的包干扰。你可以使用 <code>venv</code>（Python 3.3+ 内置）或 <code>virtualenv</code>（对于较旧的Python版本）来创建一个虚拟环境。</p>
 </li>
 <li>
  <p><strong>测试不同版本的Python和cx_Freeze</strong> ：
   <br>
   如果你遇到了问题，尝试在不同的Python版本或 <code>cx_Freeze</code> 版本上打包你的应用程序。有时，问题可能是由特定版本的库或Python解释器的bug引起的。</p>
 </li>
 <li>
  <p><strong>检查Python脚本</strong> ：
   <br>
   确保你的Python脚本在打包之前可以在你的本地环境中正常运行。任何在脚本中引入的错误都可能在打包后的应用程序中复现。</p>
 </li>
 <li>
  <p><strong>使用钩子（Hooks）</strong> ：
   <br>
   <code>cx_Freeze</code> 允许你编写自定义的钩子（Hooks）来处理特殊的打包需求。如果你的应用程序依赖于一些非标准的Python模块或扩展，你可能需要编写一个钩子来告诉 <code>cx_Freeze</code> 如何处理它们。</p>
 </li>
 <li>
  <p><strong>社区和资源</strong> ：
   <br>
   如果你遇到了无法解决的问题，不要害怕寻求帮助。<code>cx_Freeze</code> 的官方文档、GitHub仓库、Stack Overflow 以及相关的Python社区都是宝贵的资源。</p>
 </li>
</ol>
<h4>打包后的测试</h4>
<p>在将打包后的应用程序分发给用户之前，你应该进行彻底的测试以确保它按预期工作。以下是一些测试要点：</p>
<ul>
 <li><strong>功能测试</strong>：验证应用程序的所有功能是否都按预期工作。</li>
 <li><strong>兼容性测试</strong>：在不同的操作系统和Python版本上测试你的应用程序，以确保它的兼容性。</li>
 <li><strong>性能测试</strong>：测量应用程序的启动时间、内存使用和CPU使用率等性能指标。</li>
 <li><strong>用户测试</strong>：如果可能的话，让一些潜在的用户测试你的应用程序，并收集他们的反馈。</li>
</ul>
<h4>分发</h4>
<p>一旦你的应用程序通过了测试，你就可以准备将其分发给用户了。以下是一些分发选项：</p>
<ul>
 <li><strong>直接分发可执行文件</strong>：对于小型应用程序或内部项目，你可以直接将可执行文件分发给用户。</li>
 <li><strong>创建安装程序</strong> ：使用像 Inno Setup、NSIS 或 PyInstaller 的 <code>--onefile</code> 选项（虽然这不是 <code>cx_Freeze</code> 的选项，但提到了作为参考）来创建一个安装程序，该安装程序可以自动处理依赖项和文件放置。</li>
 <li><strong>打包为容器</strong>：对于需要更复杂分发和部署的应用程序，你可以考虑将其打包为Docker容器或其他类型的虚拟化容器。</li>
</ul>
<h4>维护和更新</h4>
<p>最后，不要忘记维护和更新你的应用程序。随着时间的推移，新的依赖项和库版本可能会出现，你的应用程序可能需要更新以利用这些改进或修复安全漏洞。此外，根据用户的反馈，你可能还需要添加新功能或改进现有功能。</p>
<p>通过使用 <code>cx_Freeze</code>，你可以将Python脚本及其依赖项打包成易于分发的可执行文件，从而使你的应用程序更容易被用户接受和使用。尽管打包过程可能会遇到一些挑战，但通过上述技巧和资源，你应该能够成功地构建并分发你的应用程序。</p>
<h3>总结</h3>
<p><code>cx_Freeze</code>是一个强大的工具，可以将Python脚本及其依赖项打包成独立的可执行文件。通过编写一个简单的<code>setup.py</code>文件，你可以轻松地配置打包过程，并生成可在不同平台上运行的应用程序。尽管它可能不如其他商业解决方案（如PyInstaller或Py2exe）那样功能丰富，但它仍然是一个值得考虑的选项，特别是对于那些需要跨平台支持且对性能要求不高的项目。</p>
<p>原文链接: https://blog.csdn.net/littlefun591/article/details/141310306</p>]]></description><guid isPermaLink="false">/archives/ee1b8035-ea2b-4592-bdf9-abb69329d7f9</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Sun, 29 Sep 2024 03:37:19 GMT</pubDate></item><item><title><![CDATA[Java中的红黑树（如果想知道Java中有关红黑树的知识点，那么只看这一篇就足够了！）]]></title><link>https://wiki.dgfygk.com/archives/f6b52229-fa06-4116-a5ab-0d3a40289095</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E7%9A%84%E7%BA%A2%E9%BB%91%E6%A0%91%EF%BC%88%E5%A6%82%E6%9E%9C%E6%83%B3%E7%9F%A5%E9%81%93Java%E4%B8%AD%E6%9C%89%E5%85%B3%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%8C%E9%82%A3%E4%B9%88%E5%8F%AA%E7%9C%8B%E8%BF%99%E4%B8%80%E7%AF%87%E5%B0%B1%E8%B6%B3%E5%A4%9F%E4%BA%86%EF%BC%81%EF%BC%89&amp;url=/archives/f6b52229-fa06-4116-a5ab-0d3a40289095" width="1" height="1" alt="" style="opacity:0;">
<p><strong>前言：红黑树作为一种自平衡的二叉搜索树，在计算机科学领域具有极其重要的地位。它通过颜色约束和旋转操作保持树的高度平衡，从而保证了查找、插入、删除等操作的高效性。红黑树广泛应用于操作系统的调度算法、数据库索引、Java集合框架等领域。</strong></p>
<hr>
<blockquote>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8a80107afd6945c5a45c45e1311c580e.jpeg&amp;size=m" alt=""></p>
 <p><strong>✨✨✨这里是秋刀鱼不做梦的BLOG</strong></p>
 <p><strong>✨✨✨想要了解更多内容可以访问我的主页</strong><a href="https://blog.csdn.net/2302_80198073?type=blog" title="秋刀鱼不做梦-CSDN博客">秋刀鱼不做梦-CSDN博客</a></p>
</blockquote>
<h2>写在前面</h2>
<hr>
<p><strong>Java中红黑树是一种高级的数据结构，相较于前边的线性表，二叉树，哈希表难上许多，如何读者没有二叉搜索树、AVL树的基础是很难理解红黑树的，这里我们推荐读者先将二叉搜索树与AVL树理解之后，在来学习红黑树！！！</strong></p>
<p><strong>二叉搜索树知识：</strong><a href="https://blog.csdn.net/2302_80198073/article/details/140767638?spm=1001.2014.3001.5501" title="Java中的二叉搜索树（如果想知道Java中有关二叉搜索树的知识点，那么只看这一篇就足够了！）-CSDN博客">Java中的二叉搜索树（如果想知道Java中有关二叉搜索树的知识点，那么只看这一篇就足够了！）-CSDN博客</a></p>
<p><strong>AVL树知识：</strong></p>
<p><a href="https://blog.csdn.net/2302_80198073/article/details/142139104?spm=1001.2014.3001.5501" title="Java中的AVL树（如果想知道Java中有关AVL树的知识点，那么只看这一篇就足够了！）-CSDN博客">Java中的AVL树（如果想知道Java中有关AVL树的知识点，那么只看这一篇就足够了！）-CSDN博客</a></p>
<p>
 <br>
</p>
<p><strong>那么在开始学习之前，先让我们看一下本文大致的讲解内容：</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F80a406f88ca64bfaa53d5f008b63a4b5.png&amp;size=m" alt=""></p>
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%E5%86%99%E5%9C%A8%E5%89%8D%E9%9D%A2">写在前面</a></p>
<p><a href="https://wiki.dgfygk.com#1.%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E6%A6%82%E5%BF%B5%E4%B8%8E%E6%80%A7%E8%B4%A8">1.红黑树的概念与性质</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%EF%BC%881%EF%BC%89%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E6%A6%82%E5%BF%B5">（1）红黑树的概念</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%882%EF%BC%89%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E6%80%A7%E8%B4%A8">（2）红黑树的性质</a></p>
<p><a href="https://wiki.dgfygk.com#2.%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E8%8A%82%E7%82%B9%E7%9A%84%E5%AE%9A%E4%B9%89">2.红黑树的节点的定义</a></p>
<p><a href="https://wiki.dgfygk.com#3.%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E6%8F%92%E5%85%A5">3.红黑树的插入</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%881%EF%BC%89%E6%83%85%E5%86%B5%E4%B8%80%3A%20cur%E4%B8%BA%E7%BA%A2%EF%BC%8Cp%E4%B8%BA%E7%BA%A2%EF%BC%8Cg%E4%B8%BA%E9%BB%91%EF%BC%8Cu%E5%AD%98%E5%9C%A8%E4%B8%94%E4%B8%BA%E7%BA%A2">（1）情况一: cur为红，p为红，g为黑，u存在且为红</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%EF%BC%882%EF%BC%89%E6%83%85%E5%86%B5%E4%BA%8C%3A%20cur%E4%B8%BA%E7%BA%A2%EF%BC%8Cp%E4%B8%BA%E7%BA%A2%EF%BC%8Cg%E4%B8%BA%E9%BB%91%EF%BC%8Cu%E4%B8%8D%E5%AD%98%E5%9C%A8%2Fu%E4%B8%BA%E9%BB%91">（2）情况二: cur为红，p为红，g为黑，u不存在/u为黑</a></p>
<p><a href="https://wiki.dgfygk.com#%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%EF%BC%883%EF%BC%89%E6%83%85%E5%86%B5%E4%B8%89%3A%20cur%E4%B8%BA%E7%BA%A2%EF%BC%8Cp%E4%B8%BA%E7%BA%A2%EF%BC%8Cg%E4%B8%BA%E9%BB%91%EF%BC%8Cu%E4%B8%8D%E5%AD%98%E5%9C%A8%2Fu%E4%B8%BA%E9%BB%91">（3）情况三: cur为红，p为红，g为黑，u不存在/u为黑</a></p>
<p><a href="https://wiki.dgfygk.com#4.%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E9%AA%8C%E8%AF%81">4.红黑树的验证</a></p>
<p><a href="https://wiki.dgfygk.com#1.%20%E6%A3%80%E6%B5%8B%E5%85%B6%E6%98%AF%E5%90%A6%E6%BB%A1%E8%B6%B3%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%28%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E6%98%AF%E5%90%A6%E4%B8%BA%E6%9C%89%E5%BA%8F%E5%BA%8F%E5%88%97%29">1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)</a></p>
<p><a href="https://wiki.dgfygk.com#2.%E6%A3%80%E6%B5%8B%E5%85%B6%E6%98%AF%E5%90%A6%E6%BB%A1%E8%B6%B3%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E6%80%A7%E8%B4%A8">2.检测其是否满足红黑树的性质</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%A7%A3%E9%87%8A%EF%BC%9A">解释：</a></p>
<p><a href="https://wiki.dgfygk.com#5.%E7%BA%A2%E9%BB%91%E6%A0%91%E4%B8%8EAVL%E6%A0%91%E7%9A%84%E6%AF%94%E8%BE%83">5.红黑树与AVL树的比较</a></p>
<p><a href="https://wiki.dgfygk.com#6.%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E5%BA%94%E7%94%A8">6.红黑树的应用</a></p>
<p><a href="https://wiki.dgfygk.com#1.%20Java%E4%B8%AD%E7%9A%84TreeMap%E5%92%8CTreeSet">1. Java中的TreeMap和TreeSet</a></p>
<p><a href="https://wiki.dgfygk.com#2.%20%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B4%A2%E5%BC%95">2. 数据库索引</a></p>
<p><a href="https://wiki.dgfygk.com#3.%20%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%B0%83%E5%BA%A6%E5%99%A8">3. 操作系统调度器</a></p>
<hr>
<p>
 <br>
</p>
<h2>1.红黑树的概念与性质</h2>
<h3><strong>（1）红黑树的概念</strong></h3>
<p><strong>在开始正式的学习Java中的红黑树之前，先让我们了解一下红黑树的概念：</strong></p>
<blockquote>
 <p><strong>红黑树是一种自平衡的二叉搜索树。二叉搜索树的性质决定了左子树的所有节点都小于根节点，右子树的所有节点都大于根节点。红黑树在此基础上，通过对节点着色及特定的规则来维持树的平衡。其核心特点在于红黑树通过颜色限制和旋转操作来避免二叉搜索树退化成线性链表，从而使得插入、删除和查找操作的时间复杂度始终保持在 O(log n)。</strong></p>
</blockquote>
<p>当然根据上述枯燥乏味的文字读者可能立即不了什么是红黑树，这里我们附上一张红黑树的图片：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9b3b2d2bb4b94765b6817f2eb9fd1c64.png&amp;size=m" alt=""></p>
<p><strong>当然，我们从这张图片中，我们就可以发现红黑树的一些性质</strong></p>
<p>
 <br>
</p>
<h3>（2）红黑树的性质</h3>
<p><strong>从上图中，我们可以发现：</strong></p>
<blockquote>
 <ol>
  <li>
   <p><strong>最长路径做多是最短路径的2倍</strong></p>
  </li>
  <li>
   <p><strong>每个结点不是红色就是黑色</strong></p>
  </li>
  <li>
   <p><strong>根节点是黑色的</strong></p>
  </li>
  <li>
   <p><strong>如果一个节点是红色的，则它的两个孩子结点是黑色的【没有2个连续的红色节点】</strong></p>
  </li>
  <li>
   <p><strong>对于每个结点，从该结点到其所有后代叶结点的简单路径上，均 包含相同数目的黑色结点</strong></p>
  </li>
  <li>
   <p><strong>每个叶子结点都是黑色的(此处的叶子结点指的是空结点)</strong></p>
  </li>
 </ol>
</blockquote>
<p>这里读者可以简单的理解一下，下面我们在自行实现红黑树的时候，能更好的理解这些性质。</p>
<p>
 <br>
</p>
<h2>2.红黑树的节点的定义</h2>
<p>在了解完了红黑树的基本概念之后，我们发现，红黑树就是一棵带有颜色的近似平衡的二叉搜索树，那么现在让我们看一下如何去定义红黑树的节点。</p>
<p><strong>红黑树的节点定义与普通二叉树节点相似，但多了一个颜色属性，用于记录节点的颜色（红或黑），节点的基本定义如下：</strong></p>
<pre><code class="java">public static class TreeNode {
    public TreeNode left;    // 左子节点
    public TreeNode right;   // 右子节点
    public TreeNode parent;  // 父节点
    public int val;          // 节点值
    public Color color;      // 节点颜色

    // 枚举类型定义红色和黑色
    public enum Color {
        RED, BLACK
    }

    // 构造函数，默认节点颜色为红色
    public TreeNode(int val) {
        this.val = val;
        this.color = Color.RED; // 新插入节点初始为红色
    }
}
</code></pre>
<p><strong>其中的属性为：</strong></p>
<blockquote>
 <ul>
  <li><code>left</code>：指向左子节点。</li>
  <li><code>right</code>：指向右子节点。</li>
  <li><code>parent</code>：指向父节点。</li>
  <li><code>val</code>：节点的值（整数类型）。</li>
  <li><code>color</code>：节点的颜色，表示为<code>Color</code>类型（红色或黑色）。</li>
 </ul>
</blockquote>
<p><strong>通过上述的讲解，现在我们就了解了红黑树的节点的定义了！！！</strong></p>
<p>
 <br>
</p>
<h2><strong>3.</strong>红黑树的插入</h2>
<p><strong>当我们了解完了什么是红黑树以及红黑树的节点如何定义之后，现在让我们自行实现一个红黑树的代码，创建一棵红黑树的大致流程为：</strong></p>
<blockquote>
 <p>**1. 按照二叉搜索的树规则插入新节点；</p>
 <ol>
  <li>检测新节点插入后，红黑树的性质是否造到破坏；**</li>
 </ol>
</blockquote>
<p><strong>而对于红黑树中节点的插入（插入节点之后），有以下几种情况：</strong> <strong>（</strong><strong>约定:cur为当前节点，p为父节点，g为祖父节点，u为叔叔节点）</strong></p>
<h3>（1）情况一: cur为红，p为红，g为黑，u存在且为红</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Feee772d29d144b1785d582b14869bc4f.png&amp;size=m" alt=""></p>
<p><strong>——解决方式：将p,u改为黑，g改为红，然后把g当成cur，继续向上调整。</strong></p>
<p>
 <br>
</p>
<h3>（2）情况二: cur为红，p为红，g为黑，u不存在/u为黑</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6e758fd826f4459383b86fe85b3f9f83.png&amp;size=m" alt=""></p>
<p><strong>——解决方式：p为g的左孩子，cur为p的左孩子，则进行右单旋转；相反，p为g的右孩子，cur为p的右孩子，则进行左单旋转，p、g变色–p变黑，g变红</strong></p>
<p>
 <br>
</p>
<h3>（3）情况三: cur为红，p为红，g为黑，u不存在/u为黑</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F343e40867ce842318305cd2b62a9f149.png&amp;size=m" alt=""></p>
<p><strong>——解决方式：p为g的左孩子，cur为p的右孩子，则针对p做左单旋转；相反，p为g的右孩子，cur为p的左孩子，则针对p做右单旋转，则转换成了情况2</strong></p>
<p>
 <br>
</p>
<p><strong>这样我们只需要对每种情况进行处理即可！！！以下为整体实现的代码：</strong></p>
<pre><code class="java">public void insert(int val) {
    // 如果当前树为空，直接插入根节点，并将根节点设为黑色
    if (root == null) {
        root = new TreeNode(val);
        root.color = Color.BLACK;
        return;
    }

    TreeNode prev = null;
    TreeNode cur = root;

    // 寻找插入位置
    while (cur != null) {
        if (cur.val &lt; val) {
            prev = cur;
            cur = cur.right;  // 继续在右子树查找
        } else if (cur.val &gt; val) {
            prev = cur;
            cur = cur.left;   // 继续在左子树查找
        } else {
            return;  // 如果已存在相同值的节点，直接返回，不进行插入
        }
    }

    // 创建新节点，并根据比较结果插入到合适位置
    TreeNode node = new TreeNode(val);
    if (prev.val &lt; val) {
        prev.right = node;  // 插入到右子节点
    } else {
        prev.left = node;   // 插入到左子节点
    }

    cur = node;
    TreeNode parent = cur.parent;

    // 修正红黑树的性质
    while (parent != null &amp;&amp; parent.color == Color.RED) {
        TreeNode grandParent = parent.parent;

        // 情况1：父节点是祖父节点的左子节点
        if (grandParent.left == parent) {
            TreeNode uncle = grandParent.right;

            // 情况1.1：叔叔节点为红色
            if (uncle != null &amp;&amp; uncle.color == Color.RED) {
                parent.color = Color.BLACK;  // 将父节点设为黑色
                uncle.color = Color.BLACK;   // 将叔叔节点设为黑色
                grandParent.color = Color.RED;  // 将祖父节点设为红色
                cur = grandParent;  // 将当前节点指向祖父节点，继续修正
                parent = cur.parent;  // 更新父节点
            } else {
                // 情况1.2：叔叔节点为黑色，当前节点是右子节点
                if (parent.right == cur) {
                    rotateLeft(parent);  // 左旋父节点
                    TreeNode temp = parent;
                    parent = cur;
                    cur = temp;  // 更新当前节点和父节点
                }
                // 情况1.3：叔叔节点为黑色，当前节点是左子节点
                rotateRight(grandParent);  // 右旋祖父节点
                parent.color = Color.BLACK;  // 将父节点设为黑色
                grandParent.color = Color.RED;  // 将祖父节点设为红色
            }
        } else {
            // 情况2：父节点是祖父节点的右子节点
            TreeNode uncle = grandParent.left;

            // 情况2.1：叔叔节点为红色
            if (uncle != null &amp;&amp; uncle.color == Color.RED) {
                parent.color = Color.BLACK;  // 将父节点设为黑色
                uncle.color = Color.BLACK;   // 将叔叔节点设为黑色
                grandParent.color = Color.RED;  // 将祖父节点设为红色
                cur = grandParent;  // 将当前节点指向祖父节点，继续修正
                parent = cur.parent;  // 更新父节点
            } else {
                // 情况2.2：叔叔节点为黑色，当前节点是左子节点
                if (parent.left == cur) {
                    rotateRight(parent);  // 右旋父节点
                    TreeNode temp = parent;
                    parent = cur;
                    cur = temp;  // 更新当前节点和父节点
                }
                // 情况2.3：叔叔节点为黑色，当前节点是右子节点
                rotateLeft(grandParent);  // 左旋祖父节点
                grandParent.color = Color.RED;  // 将祖父节点设为红色
                parent.color = Color.BLACK;  // 将父节点设为黑色
            }
        }
    }

    // 保证根节点始终是黑色
    root.color = Color.BLACK;
}
</code></pre>
<p><strong>其中的左旋操作：</strong></p>
<pre><code class="java">public void rotateLeft(TreeNode prev) {
    // 取得当前节点的右子节点
    TreeNode subR = prev.right;
    // 取得右子节点的左子节点
    TreeNode subRL = subR.left;

    // 将右子节点的左子节点连接到当前节点的右子节点上
    subR.left = prev;
    // 将当前节点的右子节点连接到右子节点的左子节点上
    prev.right = subRL;

    // 取得当前节点的父节点
    TreeNode parParent = prev.parent;
    // 更新当前节点的父节点为右子节点
    prev.parent = subR;
    // 如果右子节点的左子节点不为空，更新它的父节点为当前节点
    if (subRL != null) {
        subRL.parent = prev;
    }

    // 如果当前节点是根节点，将右子节点设为新的根节点
    if (prev == root) {
        root = subR;
        subR.parent = null; // 更新根节点的父节点为 null
    } else {
        // 否则，根据当前节点是父节点的左子节点还是右子节点来更新父节点的子节点
        if (parParent.left == prev) {
            parParent.left = subR;
            subR.parent = parParent;
        } else {
            parParent.right = subR;
            subR.parent = parParent;
        }
    }
}
</code></pre>
<p><strong>其中右旋操作：</strong></p>
<pre><code class="java">public void rotateRight(TreeNode prev) {
    // 取得当前节点的左子节点
    TreeNode subL = prev.left;
    // 取得左子节点的右子节点
    TreeNode subLR = subL.right;

    // 将左子节点的右子节点连接到当前节点的左子节点上
    prev.left = subLR;
    // 将当前节点连接到左子节点的右子节点上
    subL.right = prev;

    // 如果左子节点的右子节点不为空，更新它的父节点为当前节点
    if (subLR != null) {
        subLR.parent = prev;
    }

    // 取得当前节点的父节点
    TreeNode parParent = prev.parent;
    // 更新当前节点的父节点为左子节点
    prev.parent = subL;

    // 如果当前节点是根节点，将左子节点设为新的根节点
    if (prev == root) {
        root = subL;
        subL.parent = null; // 更新根节点的父节点为 null
    } else {
        // 否则，根据当前节点是父节点的左子节点还是右子节点来更新父节点的子节点
        if (parParent.left == prev) {
            parParent.left = subL;
            subL.parent = parParent;
        } else {
            parParent.right = subL;
            subL.parent = parParent;
        }
    }
}
</code></pre>
<p><strong>——这里的左旋与右旋和AVL树中的左旋与右旋是相同的！！！</strong></p>
<p>
 <br>
</p>
<p><strong>当读者耐心的读完并理解完上述的代码之后，现在我们在总体的梳理一下上述代码的整理逻辑：</strong></p>
<blockquote>
 <p><strong>1. 查找插入位置：</strong></p>
 <p>从根节点开始，比较插入的值 <code>val</code> 和当前节点的值，确定插入位置。如果插入值小于当前节点，则继续在左子树查找；否则在右子树查找。这样确保了插入的新节点满足二叉搜索树的性质。</p>
 <ol>
  <li>插入节点：</li>
 </ol>
 <p>一旦找到合适的位置，就将新节点插入到树中。如果父节点的值小于插入值，新节点作为父节点的右子节点；否则作为左子节点。</p>
 <ol>
  <li>调整红黑树的性质：</li>
 </ol>
 <p>由于插入的新节点默认是红色，可能会违反红黑树的性质。红黑树的主要性质包括：</p>
 <ul>
  <li>
   <p>根节点必须是黑色。</p>
  </li>
  <li>
   <p>红色节点的子节点必须是黑色（即红色节点不能有红色子节点）。</p>
  </li>
  <li>
   <p>从任意节点到其叶子节点的每条路径上黑色节点的数量必须相同。</p>
  </li>
 </ul>
 <p>为了恢复红黑树的平衡性，可能需要进行以下调整：</p>
 <ul>
  <li>
   <p><strong>重新着色</strong>：如果插入节点的父节点和叔叔节点都是红色，那么父节点和叔叔节点重新染成黑色，祖父节点染成红色，并继续检查祖父节点。</p>
  </li>
  <li>
   <p><strong>旋转操作</strong>：当叔叔节点是黑色或不存在时，通过旋转来调整树的结构。左旋或右旋操作可以修正父节点和祖父节点之间的关系，使树重新达到平衡。</p>
  </li>
  <li>
   <p>保持根节点为黑色：</p>
  </li>
 </ul>
 <p>红黑树的一个重要性质是根节点必须始终是黑色，因此在所有调整完成后，确保根节点被设置为黑色。</p>
</blockquote>
<p><strong>这样我们就完成了红黑树节点的插入操作了！！！</strong></p>
<p>
 <br>
</p>
<h3><strong>4.</strong>红黑树的验证</h3>
<p>当我们创建完了一棵红黑树之后，我们如何去验证其为一棵红黑树呢？验证一棵树为红黑树，我们需要从其定义性质入手。</p>
<p><strong>红黑树的检测分为两步：</strong></p>
<h3>1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)</h3>
<pre><code class="java">public void inorder(TreeNode root) {
    // 如果当前节点为空，则返回（递归终止条件）
    if (root == null) {
        return;
    }

    // 递归访问左子树
    inorder(root.left);

    // 访问当前节点
    System.out.println(root.val);

    // 递归访问右子树
    inorder(root.right);
}
</code></pre>
<h3>2.检测其是否满足红黑树的性质</h3>
<pre><code class="java">public boolean isRBTree(TreeNode root) {
    // 如果根节点为空，空树被认为是红黑树
    if (root == null) {
        return true;
    }

    // 根节点必须是黑色
    if (root.color == Color.RED) {
        return false;
    }

    // 计算从根节点到最左边叶子节点路径上的黑色节点数量
    int prevBlackNodeNum = calBlackNumber(root);

    // 检查红色节点是否有连续的红色节点，并检查所有路径上的黑色节点数量是否一致
    return examRedNode(root) &amp;&amp; examBlackNode(root, 0, prevBlackNodeNum);
}

// 计算从根节点到最左边叶子节点路径上的黑色节点数量
private int calBlackNumber(TreeNode root) {
    int prevBlackNodeNum = 0;
    TreeNode cur = root;

    // 沿着左子树遍历，计算黑色节点数量
    while (cur.left != null) {
        if (cur.color == Color.BLACK) {
            prevBlackNodeNum++;
        }
        cur = cur.left;
    }
    return prevBlackNodeNum;
}

// 检查红色节点的父节点是否也为红色，红色节点不能连续
private boolean examRedNode(TreeNode root) {
    if (root == null) {
        return true;
    }

    // 如果当前节点是红色，检查其父节点是否也为红色
    if (root.color == Color.RED) {
        TreeNode parent = root.parent;
        if (parent.color == Color.RED) {
            return false;
        }
    }

    // 递归检查左右子树
    return examRedNode(root.left) &amp;&amp; examRedNode(root.right);
}

// 检查从根节点到每个叶子节点路径上的黑色节点数量是否一致
private boolean examBlackNode(TreeNode root, int blackNodeNum, int prevBlackNodeNum) {
    if (root == null) {
        return true;
    }

    // 如果当前节点是黑色，黑色节点计数加一
    if (root.color == Color.BLACK) {
        blackNodeNum++;
    }

    // 如果到达叶子节点，检查路径上的黑色节点数量是否与最左边路径一致
    if (root.left == null &amp;&amp; root.right == null) {
        if (blackNodeNum != prevBlackNodeNum) {
            return false;
        }
    }

    // 递归检查左右子树
    return examBlackNode(root.left, blackNodeNum, prevBlackNodeNum)
            &amp;&amp; examBlackNode(root.right, blackNodeNum, prevBlackNodeNum);
}
</code></pre>
<h4>解释：</h4>
<ul>
 <li>
  <p><strong><code>isRBTree</code></strong> 方法：</p>
 </li>
 <li>
  <p>如果根节点为空，认为是红黑树。</p>
 </li>
 <li>
  <p>根节点必须是黑色。</p>
 </li>
 <li>
  <p>计算最左边路径的黑色节点数量。</p>
 </li>
 <li>
  <p>检查红色节点是否有连续红色节点，并确保所有路径上的黑色节点数量一致。</p>
 </li>
 <li>
  <p><strong><code>calBlackNumber</code></strong> 方法：</p>
 </li>
 <li>
  <p>计算从根节点到最左边叶子节点路径上的黑色节点数量。</p>
 </li>
 <li>
  <p><strong><code>examRedNode</code></strong> 方法：</p>
 </li>
 <li>
  <p>确保红色节点不能有红色的父节点。</p>
 </li>
 <li>
  <p><strong><code>examBlackNode</code></strong> 方法：</p>
 </li>
 <li>
  <p>确保从根节点到每个叶子节点的路径上黑色节点数量一致。</p>
 </li>
</ul>
<p><strong>这样我们就知道了如何判断一棵树是否为红黑树了！</strong></p>
<p>
 <br>
</p>
<h2><strong>5.</strong>红黑树与AVL树的比较</h2>
<p><strong>学习完了红黑树之后，读者可能会有疑问了，红黑树相较于前面所学的AVL树有什么优势吗？这里我们举出其中的差异：</strong></p>
<ol>
 <li>
  <p><strong>平衡性</strong>：AVL树更加"严格平衡”，插入和删除操作后会尽可能保持树的高度平衡，因此查找操作效率较高。红黑树则更"松散”，允许树稍微倾斜，插入和删除操作效率更高。</p>
 </li>
 <li>
  <p><strong>旋转次数</strong>：由于红黑树的平衡要求较低，相比AVL树，红黑树在插入和删除操作中所需的旋转次数更少。AVL树为了保持高度平衡，可能会执行更多次旋转操作，因此在插入和删除时的性能稍逊于红黑树。</p>
 </li>
 <li>
  <p><strong>查找效率</strong> ：由于AVL树保持更严格的平衡，它的查找操作在最坏情况下的性能比红黑树稍好。红黑树在最坏情况下的高度接近 <code>2log(n)</code>，而AVL树的高度接近 <code>log(n)</code>，这使得AVL树在查找操作中略有优势。</p>
 </li>
 <li>
  <p><strong>应用场景</strong> ：红黑树的插入和删除操作比AVL树更快，因此在需要频繁插入和删除的场景中，红黑树更为合适。比如Java中的<code>TreeMap</code>和<code>TreeSet</code>等数据结构都采用了红黑树。而AVL树则更适合用于查找频率较高且对平衡性要求较高的场景。</p>
 </li>
</ol>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><strong>AVL树与红黑树的总结对比：</strong></p>
<p>| 特性 | 红黑树 | AVL树 |
 <br>
 |——–|———|——|
 <br>
 | 平衡策略 | 较为宽松 | 严格平衡 |
 <br>
 | 插入删除效率 | 高 | 较低 |
 <br>
 | 查找效率 | 较低 | 高 |
 <br>
 | 旋转次数 | 较少 | 较多 |
 <br>
 | 应用场景 | 插入/删除频繁 | 查找频繁 |</p>
<p><strong>这样我们就了解了红黑树与AVL树差异了</strong></p>
<p>
 <br>
</p>
<h2><strong>6.</strong>红黑树的应用</h2>
<p>红黑树因其高效的性能和自平衡性质，广泛应用于各种数据结构中。Java标准库中的一些重要类也基于红黑树来实现。</p>
<h5>1. Java中的TreeMap和TreeSet</h5>
<blockquote>
 <p><code>TreeMap</code>和<code>TreeSet</code>是Java集合框架中的重要实现，底层数据结构是红黑树。<code>TreeMap</code>提供了有序的键值对存储，而<code>TreeSet</code>则提供了有序的集合元素存储。它们都通过红黑树保证插入、删除和查找操作的时间复杂度为 O(log n)。</p>
</blockquote>
<h5>2. 数据库索引</h5>
<blockquote>
 <p>在某些数据库系统中，红黑树也可以用于索引结构的实现。通过红黑树的数据平衡特性，数据库可以高效地执行增删改查操作，尤其是在处理大量数据时，红黑树能保证数据的有序性和访问效率。</p>
</blockquote>
<h5>3. 操作系统调度器</h5>
<blockquote>
 <p>红黑树也被广泛用于操作系统调度器中。例如，在Linux的CFS（完全公平调度器）中，红黑树被用来高效地管理进程的优先级和时间片，确保进程调度的公平性和效率。</p>
</blockquote>
<p><strong>这样我们就学完了有关Java中红黑树的全部知识点了！</strong></p>
<p>
 <br>
</p>
<hr>
<p><strong>以上就是本篇文章的全部内容了\~\~\~</strong></p>
<p>原文链接: https://blog.csdn.net/2302_80198073/article/details/142334946</p>]]></description><guid isPermaLink="false">/archives/f6b52229-fa06-4116-a5ab-0d3a40289095</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:37:07 GMT</pubDate></item><item><title><![CDATA[Java 中的监听器（Listener）]]></title><link>https://wiki.dgfygk.com/archives/b0b22f58-0db8-4a21-a2d5-d1f54943ceef</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20%E4%B8%AD%E7%9A%84%E7%9B%91%E5%90%AC%E5%99%A8%EF%BC%88Listener%EF%BC%89&amp;url=/archives/b0b22f58-0db8-4a21-a2d5-d1f54943ceef" width="1" height="1" alt="" style="opacity:0;">
<h3>1,监听器模式的基本原理</h3>
<p>监听器模式由以下几个部分组成：</p>
<ol>
 <li><strong>事件源（Event Source）</strong>：产生事件的对象。</li>
 <li><strong>事件对象（Event Object）</strong>：封装了事件的详细信息。</li>
 <li><strong>监听器接口（Listener Interface）</strong>：定义了响应特定事件的方法。</li>
 <li><strong>监听器实现（Listener Implementation）</strong>：实现监听器接口，包含具体的事件处理逻辑。</li>
</ol>
<h4>监听器模式的步骤</h4>
<ol>
 <li><strong>定义事件类</strong>：创建一个事件类，用于封装事件信息。</li>
 <li><strong>定义监听器接口</strong>：创建一个接口，定义响应事件的方法。</li>
 <li><strong>实现监听器接口</strong>：创建一个类，实现监听器接口，并在方法中编写事件处理逻辑。</li>
 <li><strong>注册监听器</strong>：将监听器对象注册到事件源中，当事件发生时，事件源会通知监听器并调用相应的方法。</li>
</ol>
<h3>2,实际应用场景</h3>
<h4>2.1 图形用户界面（GUI）事件处理</h4>
<p>监听器模式广泛应用于图形用户界面（GUI）编程中，用于处理用户交互事件。</p>
<ul>
 <li><strong>按钮点击事件</strong>：当用户点击按钮时，触发相应的事件处理程序。</li>
 <li><strong>鼠标事件</strong>：处理鼠标点击、移动、进入、退出等事件。</li>
 <li><strong>键盘事件</strong>：处理键盘按下、释放等事件。</li>
 <li><strong>窗口事件</strong>：处理窗口打开、关闭、最小化、最大化等事件。</li>
</ul>
<pre><code class="java">JButton button = new JButton("Click Me");
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked!");
    }
});
</code></pre>
<h4>2.2 数据模型更新通知</h4>
<p>监听器模式用于通知数据模型的变化，通常用于实现观察者模式。当数据发生变化时，所有注册的监听器都会被通知更新。</p>
<ul>
 <li><strong>MVC 模式</strong>：在模型-视图-控制器（MVC）架构中，当模型数据发生变化时，视图需要更新以反映变化。</li>
 <li><strong>数据绑定</strong>：在某些框架中，监听器用于实现数据绑定，当数据源变化时自动更新界面。</li>
</ul>
<pre><code class="java">public class DataModel {
    private List&lt;DataChangeListener&gt; listeners = new ArrayList&lt;&gt;();

    public void addDataChangeListener(DataChangeListener listener) {
        listeners.add(listener);
    }

    public void removeDataChangeListener(DataChangeListener listener) {
        listeners.remove(listener);
    }

    public void notifyDataChange() {
        for (DataChangeListener listener : listeners) {
            listener.dataChanged();
        }
    }
}

public interface DataChangeListener {
    void dataChanged();
}
</code></pre>
<h4>2.3 日志系统</h4>
<p>日志系统可以使用监听器模式将日志信息发送到多个不同的目的地（例如控制台、文件、远程服务器等）。</p>
<ul>
 <li><strong>多重日志处理</strong>：一个日志事件可以被多个日志处理程序监听和处理，确保日志信息被保存到多个地方。</li>
</ul>
<pre><code class="java">public class Logger {
    private List&lt;LogListener&gt; listeners = new ArrayList&lt;&gt;();

    public void addLogListener(LogListener listener) {
        listeners.add(listener);
    }

    public void removeLogListener(LogListener listener) {
        listeners.remove(listener);
    }

    public void log(String message) {
        for (LogListener listener : listeners) {
            listener.onLog(message);
        }
    }
}

public interface LogListener {
    void onLog(String message);
}
</code></pre>
<h4>2.4 文件系统监控</h4>
<p>监听器模式用于监控文件系统的变化，例如文件的创建、修改、删除等事件。</p>
<ul>
 <li><strong>文件监控工具</strong>：某些工具可以监听特定目录的变化，当有文件变化时触发相应的处理。</li>
</ul>
<pre><code class="java">Path path = Paths.get("/path/to/directory");
WatchService watchService = FileSystems.getDefault().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);

while (true) {
    WatchKey key = watchService.take();
    for (WatchEvent&lt;?&gt; event : key.pollEvents()) {
        WatchEvent.Kind&lt;?&gt; kind = event.kind();
        Path fileName = (Path) event.context();
        System.out.println(kind + ": " + fileName);
    }
    key.reset();
}
</code></pre>
<h4>2.5 网络和分布式系统</h4>
<p>监听器模式用于处理网络和分布式系统中的异步事件，例如消息的到达、连接的建立或断开等。</p>
<ul>
 <li><strong>消息队列</strong>：监听器模式用于处理消息队列中的消息，当消息到达时触发相应的处理程序。</li>
 <li><strong>网络连接</strong>：在网络编程中，监听器用于处理连接的建立、断开和数据接收等事件。</li>
</ul>
<pre><code class="java">public interface MessageListener {
    void onMessage(String message);
}

public class MessageQueue {
    private List&lt;MessageListener&gt; listeners = new ArrayList&lt;&gt;();

    public void addMessageListener(MessageListener listener) {
        listeners.add(listener);
    }

    public void removeMessageListener(MessageListener listener) {
        listeners.remove(listener);
    }

    public void receiveMessage(String message) {
        for (MessageListener listener : listeners) {
            listener.onMessage(message);
        }
    }
}
</code></pre>
<h4>2.6 任务调度和异步处理</h4>
<p>监听器模式用于任务调度和异步处理，允许任务在完成后通知监听器进行后续处理。</p>
<ul>
 <li><strong>任务完成通知</strong>：当任务完成时，触发监听器执行相应的后续操作。</li>
 <li><strong>异步事件处理</strong>：处理异步事件，当事件发生时通知监听器。</li>
</ul>
<pre><code class="java">public interface TaskCompleteListener {
    void onTaskComplete();
}

public class Task {
    private List&lt;TaskCompleteListener&gt; listeners = new ArrayList&lt;&gt;();

    public void addTaskCompleteListener(TaskCompleteListener listener) {
        listeners.add(listener);
    }

    public void removeTaskCompleteListener(TaskCompleteListener listener) {
        listeners.remove(listener);
    }

    public void complete() {
        for (TaskCompleteListener listener : listeners) {
            listener.onTaskComplete();
        }
    }
}
</code></pre>
<h3>3 自定义监听器</h3>
<h4>3.1 Java 中常见的监听器接口</h4>
<ul>
 <li><strong>ActionListener</strong>：用于处理动作事件，例如按钮点击。</li>
 <li><strong>MouseListener</strong>：用于处理鼠标事件，例如鼠标点击、进入、退出等。</li>
 <li><strong>KeyListener</strong>：用于处理键盘事件，例如按键按下、释放等。</li>
 <li><strong>WindowListener</strong>：用于处理窗口事件，例如窗口打开、关闭等。</li>
 <li><strong>FocusListener</strong>：用于处理组件焦点事件。</li>
</ul>
<h4>3,2 使用自定义监听器</h4>
<p>有时你可能需要定义自己的事件和监听器。以下是一个示例，展示如何创建和使用自定义监听器。</p>
<h5>3,2,1 定义事件类</h5>
<pre><code class="java">import java.util.EventObject;

public class CustomEvent extends EventObject {
    public CustomEvent(Object source) {
        super(source);
    }
}
</code></pre>
<h5>3.2.2 定义监听器接口</h5>
<pre><code class="java">import java.util.EventListener;

public interface CustomEventListener extends EventListener {
    void handleEvent(CustomEvent event);
}
</code></pre>
<h5>3.2.3 实现事件源类</h5>
<pre><code class="java">import java.util.ArrayList;
import java.util.List;

public class EventSource {
    private final List&lt;CustomEventListener&gt; listeners = new ArrayList&lt;&gt;();

    public void addCustomEventListener(CustomEventListener listener) {
        listeners.add(listener);
    }

    public void removeCustomEventListener(CustomEventListener listener) {
        listeners.remove(listener);
    }

    public void triggerEvent() {
        CustomEvent event = new CustomEvent(this);
        for (CustomEventListener listener : listeners) {
            listener.handleEvent(event);
        }
    }
}
</code></pre>
<h5>3.2.4 使用自定义监听器</h5>
<pre><code class="java">public class CustomEventListenerExample {
    public static void main(String[] args) {
        EventSource eventSource = new EventSource();

        // 添加自定义事件监听器
        eventSource.addCustomEventListener(new CustomEventListener() {
            @Override
            public void handleEvent(CustomEvent event) {
                System.out.println("Custom event triggered!");
            }
        });

        // 触发事件
        eventSource.triggerEvent();
    }
}
</code></pre>
<h3>4 总结</h3>
<p>监听器模式是一种强大的设计模式，适用于需要处理异步事件和通知的场景。通过使用监听器模式，可以实现松耦合的事件处理逻辑，提高代码的可维护性和扩展性。无论是在 GUI 编程、数据模型更新、日志系统、文件系统监控、网络编程还是任务调度中，监听器模式都提供了一种高效的事件处理机制。</p>
<p>原文链接: https://blog.csdn.net/m0_74870921/article/details/139802383</p>]]></description><guid isPermaLink="false">/archives/b0b22f58-0db8-4a21-a2d5-d1f54943ceef</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:36:57 GMT</pubDate></item><item><title><![CDATA[Java 打包 ZIP 文件详解]]></title><link>https://wiki.dgfygk.com/archives/eff61c91-5a4e-448b-af6e-0d2d0d7c45ed</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20%E6%89%93%E5%8C%85%20ZIP%20%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3&amp;url=/archives/eff61c91-5a4e-448b-af6e-0d2d0d7c45ed" width="1" height="1" alt="" style="opacity:0;">
<p>在软件开发中，经常需要对文件或文件夹进行压缩打包，以便于存储、传输或备份。ZIP 是一种常见的压缩格式，它具有高效的压缩比和广泛的兼容性。本文将详细介绍如何使用 Java 语言进行 ZIP 文件的创建、读取和操作，并涵盖一些高级技巧和最佳实践。</p>
<h3>一、ZIP 格式简介</h3>
<p>ZIP 文件格式是一种用于数据压缩和存档的文件格式，由 PKWARE 公司在 1989 年首次推出。它通过压缩算法减少文件体积，同时支持多个文件和文件夹的打包。ZIP 文件具有以下特点：</p>
<ol>
 <li><strong>多文件打包</strong>：支持将多个文件和文件夹打包成一个 ZIP 文件。</li>
 <li><strong>压缩算法</strong>：常用的压缩算法有 Deflate、Bzip2 和 LZMA 等。</li>
 <li><strong>平台无关性</strong>：ZIP 文件可以在不同操作系统之间传输而不影响数据的完整性。</li>
 <li><strong>高效解压缩</strong>：解压缩速度较快，支持随机访问文件内容。</li>
</ol>
<h3>二、Java 中处理 ZIP 文件的基本库</h3>
<p>Java 提供了丰富的标准库用于处理 ZIP 文件，其中最主要的是 <code>java.util.zip</code> 包。该包包含了一系列类和接口，用于创建、读取和操作 ZIP 文件。常用的类包括：</p>
<ul>
 <li><code>ZipInputStream</code> 和 <code>ZipOutputStream</code>：用于顺序读取和写入 ZIP 文件。</li>
 <li><code>ZipFile</code> 和 <code>ZipEntry</code>：用于随机访问 ZIP 文件中的条目。</li>
</ul>
<p>下面我们将通过具体实例，详细讲解如何使用这些类进行 ZIP 文件的操作。</p>
<h3>三、创建 ZIP 文件</h3>
<h4>3.1 使用 ZipOutputStream 创建 ZIP 文件</h4>
<p><code>ZipOutputStream</code> 是一个用于将文件压缩到 ZIP 格式的输出流。我们可以通过它将多个文件和文件夹压缩到一个 ZIP 文件中。以下是一个简单的示例，展示如何使用 <code>ZipOutputStream</code> 创建一个 ZIP 文件：</p>
<pre><code class="prism language-java">import java.io.*;
import java.util.zip.*;

public class ZipExample {

    public static void main(String[] args) {

        String sourceFile = "src";
        String zipFile = "archive.zip";

        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {


            File fileToZip = new File(sourceFile);

            zipFile(fileToZip, fileToZip.getName(), zos);

        } catch (IOException e) {

            e.printStackTrace();
        }
    }

    private static void zipFile(File fileToZip, String fileName, ZipOutputStream zos) throws IOException {

        if (fileToZip.isHidden()) {

            return;
        }
        if (fileToZip.isDirectory()) {

            if (fileName.endsWith("/")) {

                zos.putNextEntry(new ZipEntry(fileName));
                zos.closeEntry();
            } else {

                zos.putNextEntry(new ZipEntry(fileName + "/"));
                zos.closeEntry();
            }
            File[] children = fileToZip.listFiles();
            for (File childFile : children) {

                zipFile(childFile, fileName + "/" + childFile.getName(), zos);
            }
            return;
        }
        try (FileInputStream fis = new FileInputStream(fileToZip)) {

            ZipEntry zipEntry = new ZipEntry(fileName);
            zos.putNextEntry(zipEntry);
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) &gt;= 0) {

                zos.write(bytes, 0, length);
            }
        }
    }
}
</code></pre>
<p>上述代码实现了一个递归压缩目录及其内容的功能。<code>zipFile</code> 方法会遍历目录中的所有文件和子目录，并将它们逐一压缩到 ZIP 文件中。</p>
<h4>3.2 压缩单个文件</h4>
<p>有时候我们只需要压缩单个文件，而不是整个目录。这种情况下，可以简化上述代码，只需要处理文件本身：</p>
<pre><code class="prism language-java">import java.io.*;
import java.util.zip.*;

public class ZipSingleFileExample {

    public static void main(String[] args) {

        String sourceFile = "document.txt";
        String zipFile = "document.zip";

        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos);
             FileInputStream fis = new FileInputStream(sourceFile)) {


            ZipEntry zipEntry = new ZipEntry(sourceFile);
            zos.putNextEntry(zipEntry);

            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) &gt;= 0) {

                zos.write(buffer, 0, length);
            }

            zos.closeEntry();

        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>该示例展示了如何将一个文件 <code>document.txt</code> 压缩成 <code>document.zip</code>。通过 <code>FileInputStream</code> 读取源文件的数据，并将其写入 <code>ZipOutputStream</code> 中。</p>
<h3>四、读取 ZIP 文件</h3>
<h4>4.1 使用 ZipInputStream 读取 ZIP 文件</h4>
<p><code>ZipInputStream</code> 是一个用于解压缩 ZIP 文件的输入流。它提供了逐个读取 ZIP 条目的功能，可以遍历并读取 ZIP 文件中的每一个条目。以下是一个示例，展示如何使用 <code>ZipInputStream</code> 读取 ZIP 文件中的内容：</p>
<pre><code class="prism language-java">import java.io.*;
import java.util.zip.*;

public class UnzipExample {

    public static void main(String[] args) {

        String zipFile = "archive.zip";
        String destDir = "output";

        File dir = new File(destDir);
        if (!dir.exists()) dir.mkdirs();

        try (FileInputStream fis = new FileInputStream(zipFile);
             ZipInputStream zis = new ZipInputStream(fis)) {


            ZipEntry zipEntry = zis.getNextEntry();
            while (zipEntry != null) {

                File newFile = newFile(dir, zipEntry);
                if (zipEntry.isDirectory()) {

                    if (!newFile.isDirectory() &amp;&amp; !newFile.mkdirs()) {

                        throw new IOException("Failed to create directory " + newFile);
                    }
                } else {

                    // fix for Windows-created archives
                    File parent = newFile.getParentFile();
                    if (!parent.isDirectory() &amp;&amp; !parent.mkdirs()) {

                        throw new IOException("Failed to create directory " + parent);
                    }

                    try (FileOutputStream fos = new FileOutputStream(newFile)) {

                        int len;
                        byte[] buffer = new byte[1024];
                        while ((len = zis.read(buffer)) &gt; 0) {

                            fos.write(buffer, 0, len);
                        }
                    }
                }
                zipEntry = zis.getNextEntry();
            }
            zis.closeEntry();
        } catch (IOException e) {

            e.printStackTrace();
        }
    }

    private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {

        File destFile = new File(destinationDir, zipEntry.getName());

        String destDirPath = destinationDir.getCanonicalPath();
        String destFilePath = destFile.getCanonicalPath();

        if (!destFilePath.startsWith(destDirPath + File.separator)) {

            throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
        }

        return destFile;
    }
}
</code></pre>
<p>该示例展示了如何使用 <code>ZipInputStream</code> 解压缩 <code>archive.zip</code> 文件，并将其内容提取到指定的目录 <code>output</code> 中。<code>newFile</code> 方法用于防止路径遍历漏洞，确保解压后的文件位于目标目录中。</p>
<h4>4.2 使用 ZipFile 读取 ZIP 文件</h4>
<p><code>ZipFile</code> 类提供了更为高效的随机访问功能，可以直接访问 ZIP 文件中的任意条目，而不需要遍历整个 ZIP 文件。以下是一个示例，展示如何使用 <code>ZipFile</code> 读取 ZIP 文件中的内容：</p>
<pre><code class="prism language-java">import java.io.*;
import java.util.Enumeration;
import java.util.zip.*;

public class ReadZipFileExample {

    public static void main(String[] args) {

        String zipFile = "archive.zip";

        try (ZipFile zip = new ZipFile(zipFile)) {

            Enumeration&lt;? extends ZipEntry&gt; entries = zip.entries();

            while (entries.hasMoreElements()) {

                ZipEntry entry = entries.nextElement();
                System.out.println("Extracting: " + entry.getName());

                File file = new File("output/" + entry.getName());
                if (entry.isDirectory()) {

                    file.mkdirs();
                    continue;
                }

                try (InputStream is = zip.getInputStream(entry);
                     FileOutputStream fos = new FileOutputStream(file)) {


                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = is.read(buffer)) &gt; 0) {

                        fos.write(buffer, 0, length);
                    }
                }
            }
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>该示例展示了如何使用 <code>ZipFile</code> 类读取 <code>archive.zip</code> 文件中的内容，并将其解压到 <code>output</code> 目录中。</p>
<h3>五、ZIP 文件操作的高级技巧</h3>
<h4>5.1 设置压缩级别</h4>
<p>在创建 ZIP 文件时，我们可以通过 <code>ZipOutputStream</code> 设置压缩级别，从而控制压缩率和压缩速度。Java 提供了三种压缩级别：</p>
<p>1</p>
<p>. <code>ZipOutputStream.STORED</code>：不进行压缩，仅存储文件。</p>
<ol>
 <li><code>ZipOutputStream.DEFLATED</code>：使用 Deflate 算法进行压缩（默认）。</li>
 <li><code>ZipOutputStream.NO_COMPRESSION</code>：无压缩。</li>
</ol>
<p>以下示例展示了如何设置压缩级别：</p>
<pre><code class="prism language-java">import java.io.*;
import java.util.zip.*;

public class ZipWithCompressionLevelExample {

    public static void main(String[] args) {

        String sourceFile = "document.txt";
        String zipFile = "document.zip";

        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos);
             FileInputStream fis = new FileInputStream(sourceFile)) {


            zos.setLevel(ZipOutputStream.DEFLATED);  // 设置压缩级别

            ZipEntry zipEntry = new ZipEntry(sourceFile);
            zos.putNextEntry(zipEntry);

            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) &gt;= 0) {

                zos.write(buffer, 0, length);
            }

            zos.closeEntry();

        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<h4>5.2 添加注释</h4>
<p>ZIP 文件支持在文件级和条目级添加注释。以下是一个示例，展示如何为 ZIP 文件和 ZIP 条目添加注释：</p>
<pre><code class="prism language-java">import java.io.*;
import java.util.zip.*;

public class ZipWithCommentsExample {

    public static void main(String[] args) {

        String sourceFile = "document.txt";
        String zipFile = "document_with_comments.zip";

        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos);
             FileInputStream fis = new FileInputStream(sourceFile)) {


            ZipEntry zipEntry = new ZipEntry(sourceFile);
            zipEntry.setComment("This is a comment for the entry");
            zos.putNextEntry(zipEntry);

            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) &gt;= 0) {

                zos.write(buffer, 0, length);
            }

            zos.closeEntry();

            zos.setComment("This is a comment for the ZIP file");

        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<h4>5.3 使用密码保护 ZIP 文件</h4>
<p>标准的 <code>java.util.zip</code> 包并不支持加密，但可以使用第三方库（如 Apache Commons Compress 或 Zip4j）来实现密码保护功能。以下是使用 Zip4j 库实现加密压缩的示例：</p>
<pre><code class="prism language-java">import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;

public class ZipWithPasswordExample {

    public static void main(String[] args) {

        String sourceFile = "document.txt";
        String zipFile = "document_encrypted.zip";
        String password = "securepassword";

        try {

            ZipFile zip = new ZipFile(zipFile);
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
            parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
            parameters.setEncryptFiles(true);
            parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
            parameters.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);
            parameters.setPassword(password);

            zip.addFile(new File(sourceFile), parameters);

        } catch (ZipException e) {

            e.printStackTrace();
        }
    }
}
</code></pre>
<p>以上示例展示了如何使用 Zip4j 库对 ZIP 文件进行 AES 加密，并设置密码保护。</p>
<h3>六、最佳实践</h3>
<h4>6.1 处理大文件</h4>
<p>在处理大文件时，应避免将整个文件读入内存。可以采用缓冲区的方式逐步读取和写入数据，以减少内存占用。例如，在读取和写入过程中使用较大的缓冲区（如 4KB 或 8KB）以提高效率。</p>
<h4>6.2 错误处理</h4>
<p>应始终处理可能出现的 <code>IOException</code> 异常，并在必要时提供有用的错误信息。可以使用日志记录框架（如 Log4j 或 SLF4J）来记录错误信息，便于调试和维护。</p>
<h4>6.3 资源管理</h4>
<p>应确保在完成 ZIP 文件操作后，正确关闭所有打开的流和资源。可以使用 Java 7 引入的 try-with-resources 语法来简化资源管理，确保在块结束时自动关闭资源。</p>
<h4>6.4 防止路径遍历漏洞</h4>
<p>在解压缩 ZIP 文件时，应注意防止路径遍历攻击，确保解压后的文件位于预期的目标目录中。可以通过检查文件的规范路径来实现这一点，防止恶意 ZIP 文件利用相对路径将文件解压到不安全的位置。</p>
<h3>七、总结</h3>
<p>本文详细介绍了如何使用 Java 进行 ZIP 文件的创建、读取和操作，涵盖了基本用法和高级技巧。通过合理使用 <code>java.util.zip</code> 包和第三方库，我们可以高效地处理 ZIP 文件，并应用密码保护和注释等高级功能。在实际应用中，遵循最佳实践可以提高程序的健壮性和安全性。</p>
<p>希望本文对您理解和掌握 Java 中的 ZIP 文件操作有所帮助。如果您有任何问题或建议，欢迎交流讨论。</p>
<p>原文链接: https://blog.csdn.net/weixin_41883161/article/details/139591781</p>]]></description><guid isPermaLink="false">/archives/eff61c91-5a4e-448b-af6e-0d2d0d7c45ed</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:36:41 GMT</pubDate></item><item><title><![CDATA[python基础 --- 爬虫前篇]]></title><link>https://wiki.dgfygk.com/archives/95c3b541-cb8a-4162-b49c-92fdc0de0862</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=python%E5%9F%BA%E7%A1%80%20---%20%E7%88%AC%E8%99%AB%E5%89%8D%E7%AF%87&amp;url=/archives/95c3b541-cb8a-4162-b49c-92fdc0de0862" width="1" height="1" alt="" style="opacity:0;">
<h2>python</h2>
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#python_0">python</a></li>
 <li><a href="https://wiki.dgfygk.com#_3">变量</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_14">变量类型</a></li>
 <li><a href="https://wiki.dgfygk.com#_27">输出</a></li>
 <li><a href="https://wiki.dgfygk.com#_ctrlshiftf10_65">运行程序 ctrl+shift+f10</a></li>
 <li><a href="https://wiki.dgfygk.com#__67">命名规范：字母，数字，下划线 开头不能是数字</a></li>
 <li><a href="https://wiki.dgfygk.com#_ctrl_71">注释： ctrl+？</a></li>
 <li><a href="https://wiki.dgfygk.com#__keyvalue_73">字典 键key：值value</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_97">修改字典的信息</a></li>
 <li><a href="https://wiki.dgfygk.com#_104">字典添加一个键值对</a></li>
 <li><a href="https://wiki.dgfygk.com#_112">字典删除一个键值对</a></li>
 <li><a href="https://wiki.dgfygk.com#_118">实操案例–获取实时天气预报</a></li>
 <li><a href="https://wiki.dgfygk.com#_165">字符串</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_169">转移字符</a></li>
 <li><a href="https://wiki.dgfygk.com#_181">字符串占位符</a></li>
 <li><a href="https://wiki.dgfygk.com#_207">字符串与列表</a></li>
 <li><a href="https://wiki.dgfygk.com#_index_234">判断字符串的子串 index</a></li>
 <li><a href="https://wiki.dgfygk.com#startswithendswith_249">比较字符串的开头和结尾startswith/endswith</a></li>
 <li><a href="https://wiki.dgfygk.com#len_266">字符串的长度函数len</a></li>
 <li><a href="https://wiki.dgfygk.com#list_277">字符串列表化list</a></li>
 <li><a href="https://wiki.dgfygk.com#join_292">列表合并join</a></li>
 <li><a href="https://wiki.dgfygk.com#input_306">input获取信息</a></li>
 <li><a href="https://wiki.dgfygk.com#_313">改进实时天气</a></li>
 <li><a href="https://wiki.dgfygk.com#_331">列表的增删查改</a></li>
 <li><a href="https://wiki.dgfygk.com#if_else_elif_382">判断语句if else elif</a></li>
 <li><a href="https://wiki.dgfygk.com#_for____in___416">循环语句 for + 变量名 + in + 范围：</a></li>
 <li><a href="https://wiki.dgfygk.com#_437">循环语句与字典</a></li>
 <li><a href="https://wiki.dgfygk.com#rangestartendstep_462">range(start,end,step)函数生成序列</a></li>
 <li><a href="https://wiki.dgfygk.com#sum_487">求和函数sum</a></li>
 <li><a href="https://wiki.dgfygk.com#_520">元组()</a></li>
 <li><a href="https://wiki.dgfygk.com#while_531">while循环</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_554">遍历列表</a></li>
 <li><a href="https://wiki.dgfygk.com#_breakcontinue_565">跳出循环 break/continue</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#break_567">break跳出整个循环体</a></li>
 <li><a href="https://wiki.dgfygk.com#continue_571">continue跳出本次循环，不妨碍下一轮循环</a></li>
 <li><a href="https://wiki.dgfygk.com#_577">函数</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_579">函数分类</a></li>
 <li><a href="https://wiki.dgfygk.com#def_589">自定义函数–def+函数名（参数）</a></li>
 <li><a href="https://wiki.dgfygk.com#_613">作用域</a></li>
 <li><a href="https://wiki.dgfygk.com#global_621">global局部变量变成全局变量</a></li>
 <li><a href="https://wiki.dgfygk.com#_627">类和对象</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#class____629">class + 类名： 定义一个类</a></li>
 <li><a href="https://wiki.dgfygk.com#_631">命名规则</a></li>
 <li><a href="https://wiki.dgfygk.com#_637">类的实例化，类的创建</a></li>
 <li><a href="https://wiki.dgfygk.com#pythonfselfnameformatselfname_645">在python里面f'{self.name}'和'{}'.format(self.name)这两种方式的效果一样</a></li>
 <li><a href="https://wiki.dgfygk.com#_658">给类对象增加属性</a></li>
 <li><a href="https://wiki.dgfygk.com#_720">继承类</a></li>
 <li><a href="https://wiki.dgfygk.com#super_772">派生类super</a></li>
 <li><a href="https://wiki.dgfygk.com#_808">类对象的优先级</a></li>
 <li><a href="https://wiki.dgfygk.com#pass_818">空语句占位符pass</a></li>
 <li><a href="https://wiki.dgfygk.com#_824">多态—函数重载</a></li>
 <li><a href="https://wiki.dgfygk.com#____867"><em> </em>私有变量</a></li>
 <li><a href="https://wiki.dgfygk.com#_try_except_else_finally_887">异常捕获 try except else finally</a></li>
 <li><a href="https://wiki.dgfygk.com#_953">文件读取</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#fread_963">f.read()</a></li>
 <li><a href="https://wiki.dgfygk.com#freadline_967">f.readline()</a></li>
 <li><a href="https://wiki.dgfygk.com#freadlines_971">f.readlines()</a></li>
 <li><a href="https://wiki.dgfygk.com#while_977">while循环读取文件每一行</a></li>
 <li><a href="https://wiki.dgfygk.com#with__as__1018">with as: 读取文件，文件操作完成之后自动关闭文件</a></li>
 <li><a href="https://wiki.dgfygk.com#_1044">写入文件</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#writewritelines_1064">write与writelines</a></li>
 <li><a href="https://wiki.dgfygk.com#_1074">打包文件</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_1076">步骤</a></li>
</ul>
<p>
 <br>
</p>
<h2>变量</h2>
<p>创建一个变量 ，直接写想要的变量名称就行，不需要定义变量的类型</p>
<pre><code class="prism language-python">a = 1               整型
b = 'hello world'   字符串
c = a + 1           整型运算   先计算a+1再赋值给c
d = b               字符串
</code></pre>
<h3>变量类型</h3>
<ul>
 <li>
  <p>用type()函数，获取括号里面的数据类型</p>
 </li>
 <li>
  <p>同c语言类型：</p>
  <p>| int | 整型 |
   <br>
   |————|———-|
   <br>
   | float | 浮点型 |
   <br>
   | str | 字符串 |
   <br>
   | True/False | 布尔型，只有真假 |
   <br>
   | list | 列表形 |</p>
 </li>
</ul>
<h2>输出</h2>
<p>print()</p>
<ul>
 <li>python自动识别括号里面的''和”“并消去</li>
</ul>
<pre><code class="prism language-python">print(2)            2
a = 2
print(a)            2
print('hello')      hello       python自动识别括号里面的''和""并消去
b = 'hi'
print(b)            hi
data = [2,4,'hello',True]       一个逗号就代表一个数据，从0开始，data[0]就是2
print(data)         [2,4,'hello',True]  会把data这个列表的所有信息全部打印
print(data[0])      2  打印第0个元素，从左到右是正索引，也就是之前学的
print(data[-4])     2   打印倒数第4个元素，从右往左数，从-1开始
    [2,4,'hello',True]
     0 1    2     3   从左到右
    -4 -3   -2    -1  从右往左
</code></pre>
<ul>
 <li>连续打印多个内容</li>
</ul>
<pre><code class="prism language-python">print('hello',2)            hello 2
</code></pre>
<p>用逗号隔开，实现打印多个字符串</p>
<ul>
 <li>拼接连续打印的字符串</li>
</ul>
<pre><code class="prism language-python">print('hello' + str(2))  // str是字符串类型，这里是把类型强制转换成字符串了，不然会报错
</code></pre>
<p>用加号连接，使字符串连续</p>
<h2>运行程序 ctrl+shift+f10</h2>
<h2>命名规范：字母，数字，下划线 开头不能是数字</h2>
<p>尽量简单易懂，见名知意，和c语言教的那个差不多，不能是关键字啥的</p>
<h2>注释： ctrl+？</h2>
<h2>字典 键key：值value</h2>
<p>通过键，访问值，键和值对应形成键值对（一个名称而已）</p>
<pre><code class="prism language-python">preson = {
   'name':'张三','职位':'工程师'}          用逗号隔开存放多个键值对

print(preson['name'])       张三

一个键可以对应一个列表，这个列表可以存放多个信息，比如
singer = {
   'name':'zjl','songs':['七里香','本草纲目']}   用[]表示一个列表，再用单引号写具体的字符串
然后用数组的形式打印
print(singer)                   {
   'name':'zjl','songs':['七里香','本草纲目']}
print(singer['name'])           zjl
print(singer['song'])           ['七里香','本草纲目']
print(singer['song'][0])        七里香

上面分别是
打印整个字典
打印一个键，这个键对应一个值
打印一个键，对应的一个列表
打印一个键，对应列表的一个元素
</code></pre>
<h3>修改字典的信息</h3>
<pre><code class="prism language-python">singer['name'] = 'mff'
singer['songs'][0] = '忍者'
</code></pre>
<h3>字典添加一个键值对</h3>
<pre><code class="prism language-python">singer['年龄'] = 30
</code></pre>
<p>默认加在字典中已有键值对的后面</p>
<h3>字典删除一个键值对</h3>
<pre><code class="prism language-python">del singer['年龄']
</code></pre>
<h2>实操案例–获取实时天气预报</h2>
<ol>
 <li>引入模块 import</li>
 <li>requests 通过网址请求信息</li>
 <li>pprint 格式化信息</li>
 <li>把字符串转换成字典类型</li>
 <li>填写网址链接</li>
 <li>q=cangzhou，这个等号后面的拼音就是想要获得的城市的拼音</li>
 <li>APPID=f0c3eda7aa680434e17ae00aa2fba619等号后面的这一串是我自己的api密匙，需要去openweather官网自己注册</li>
 <li>存放网址返回的数据</li>
</ol>
<p>​ 我放在了data中</p>
<ol>
 <li>处理返回的数据</li>
 <li>把字符串转成字典 json</li>
 <li>再把杂乱的字典格式化一下 pprint</li>
 <li>再把有用的数据打印出来就完了</li>
</ol>
<pre><code class="prism language-python">import requests
import pprint # 把杂乱的数据格式化，本次是把data_json字典的数据格式化，方便我们自己使用数据
import json # 把像字典的字符串转成字典类型，以便后续的访问 python自带的

url ='http://api.openweathermap.org/data/2.5/weather?q=cangzhou&amp;mode=json&amp;units=metric&amp;lang=zh_cn&amp;APPID=f0c3eda7aa680434e17ae00aa2fba619'

data = requests.get(url)
# print(data) # 获取到&lt;Response [200]&gt;，这个信息，说明requests模块向上面这个网址发送请求，得到了正确信息

# print(data.text) # 查看获取到的内容,以text的格式查看data获取到的信息，此时是一个特大的字符串

data_json = json.loads(data.text) # 把data数据的text格式的内容转换成字典储存在data_json中

# print(type(data_json))  # data_json是dict的类型，即字典类型

pprint.pprint(data_json) # 格式化data_json的数据

# 然后就是提取信息了，用键值对的形式

temp = data_json['main']['temp'] # 挑出温度
city = data_json['name']
weather = data_json['weather'][0]['description']

print(city + '市的温度是' + str(temp) + '℃' + ' 天气状况是：' + weather)
</code></pre>
<h2>字符串</h2>
<p>空字符串的布尔值是0，false</p>
<h3>转移字符</h3>
<p>在拼写文件地址的时候，有很多斜杆来分割，构成了转移字符，在字符串前面加一个小写的r，就可以取消字符串的转义字符了</p>
<pre><code class="prism language-python">print('D:\a\asda')
# 修改一下
print(r'D:\a\asda')
</code></pre>
<h3>字符串占位符</h3>
<p>在字符串中写上{}这个花括号，进行占位，再配合format函数，把数据填充进去</p>
<pre><code class="prism language-python"># 字符串占位符，用{}占位，配合format函数，和%d一样，这个更好用
temp=23
station='小雨'
s1='温度是:' + str(temp) + '度'
print(s1)
s2='温度是:{}度，天气状况是：{}'.format(temp,station)
print(s2)
他们的打印效果是一样的，而且s2更直观，更好修改


# 通过format函数来简化之前天气预报的url的城市和id输入
city='shenzhen'
id='f0c3eda7aa680434e17ae00aa2fba619'
url ='http://api.openweathermap.org/data/2.5/weather?q={}&amp;mode=json&amp;units=metric&amp;lang=zh_cn&amp;APPID={}'.format(city,id)
print(url)

这里我们把对应的城市名的部分和id部分抠出来，再用占位符，后续就可以很方便的修改和使用了
</code></pre>
<h3>字符串与列表</h3>
<p>对于一个字符串可以按字母或者汉字标号</p>
<p>比如，Python爬虫工程师</p>
<pre><code class="prism language-python">s3 = 'Python爬虫工程师'
print(s3[0])        这是s3字符串的第0个字符，也就是P
print(s3[-1])       这是倒数第一个字符
print(s3[0:6])      这是从第0位到第6位，左开右闭，不打印第6位
print(s3[:6])       如果冒号前面不写，默认是从0开始
print(s3[6:12])     从6位到12位的内容
print(s3[6:])       从6位往后的所有内容
print(s3[6:8])      从6到8位，8不算

# P
# 师
# Python
# Python
# 爬虫工程师
# 爬虫工程师
# 爬虫
</code></pre>
<h3>判断字符串的子串 index</h3>
<p>用index函数，在这个函数里面填充子字符串，配合这个函数放在字符串的后面</p>
<p>如果在字符串中能找到子字符串，就会返回子字符串在字符串的起始位置（从0位开始）,否则程序会报错</p>
<pre><code class="prism language-python">s3 = 'Python爬虫工程师'
print(s3.index('Python'))       0
print(s3.index('爬虫'))          6
print(s3.index('123'))          error
</code></pre>
<h3>比较字符串的开头和结尾startswith/endswith</h3>
<p>给你一个字符串判断前面几位或者后几位是什么东西</p>
<p>如果原字符串和比较的字符串相同，就返回True，否则位False</p>
<pre><code class="prism language-python">email = '123456@qq.com'
# 比较开头
print(email.startswith('123'))      True
print(email.startswith('1123'))     False
# 比较结尾
print(email.endswith('qq.com'))     True
</code></pre>
<h3>字符串的长度函数len</h3>
<pre><code class="prism language-python">email = '123456@qq.com'
print(len(email))               13
</code></pre>
<p>就是求字符串的长度</p>
<h3>字符串列表化list</h3>
<pre><code class="prism language-python">email = '123456@qq.com'
email_list = list(email)
print(email_list)       ['1', '2', '3', '4', '5', '6', '@', 'q', 'q', '.', 'c', 'o', 'm']
# 效果就是下面这个
email_list = list(email)
email_list = ['1', '2', '3', '4', '5', '6', '@', 'q', 'q', '.', 'c', 'o', 'm']
</code></pre>
<p>使用list函数把字符串拆分成单个元素在新的变量里面</p>
<h3>列表合并join</h3>
<pre><code class="prism language-python">email_list = ['1', '2', '3', '4', '5', '6', '@', 'q', 'q', '.', 'c', 'o', 'm']
print("".join(email_list))   123456@qq.com
print(''.join(email_list))   123456@qq.com
</code></pre>
<p>在一个空的引号后面使用join函数，把括号里面的列表拼接成完整字符串</p>
<p>单引号和双引号都可以</p>
<h2>input获取信息</h2>
<pre><code class="prism language-python">s5=input('请输入邮箱：')
print('邮箱号是：{}'.format(s5))
</code></pre>
<h2>改进实时天气</h2>
<pre><code class="prism language-python">import requests
import pprint 
import json 

city=input("请输入你所在的城市：")
id='f0c3eda7aa680434e17ae00aa2fba619'
url ='http://api.openweathermap.org/data/2.5/weather?q={}&amp;mode=json&amp;units=metric&amp;lang=zh_cn&amp;APPID={}'.format(city,id)

data = requests.get(url)
data_json = json.loads(data.text) 
pprint.pprint(data_json) 

print("{}市的温度是{}度，现在的天气状况是：{}，今天的气温是：{}到{}度".format(city,data_json['main']['temp'],data_json['weather'][0]['description'],data_json['main']['temp_max'],data_json['main']['temp_min']))
</code></pre>
<h2>列表的增删查改</h2>
<ul>
 <li>增加列表元素append(元素)</li>
</ul>
<p>​ 默认加在列表的队尾</p>
<p>​ 在使用append接口之前要确保列表是已存在的，空列表也行，否则报错</p>
<ul>
 <li>
  <p>删除列表元素del + list[元素位置]</p>
 </li>
 <li>
  <p>查看某位元素list[位次]</p>
 </li>
 <li>
  <p>改，直接把元素调出来当变量用</p>
 </li>
 <li>
  <p>max函数取列表最大</p>
 </li>
 <li>
  <p>min函数取列表最小</p>
 </li>
 <li>
  <p>sort函数对列表进行排序</p>
 </li>
 <li>
  <p>sort(reverse=True) 给sort里面加点条件就是逆序</p>
 </li>
</ul>
<pre><code class="prism language-python">ls1=[1,6,8,5,12,-3,0]
print(ls1)

print(ls1[0])
print(ls1[-1])
print(ls1[:4])
print(ls1[4:6])
print(ls1[4:])          和字符串一样的操作

#      start:end:step
#   开始：结尾：步长（间隔）
print(ls1[0:6:2])       第一个参数是起始位置，第二个是结束位置，第三个是间隔多少取一次元素

print(len(ls1))
print(max(ls1))         找列表中最大的元素
print(min(ls1))         找列表中最小的元素
ls1.sort()          对列表进行排序，从小到大
print(ls1)

ls1.sort(reverse=True)      对列表逆向排序，从大到小
print(ls1)

ls1.append(50)      增加列表元素，默认在队尾
ls1.append(51)
print(ls1)

del ls1[0]          删除列表元素
print(ls1)

ls1[0]=99           修改列表元素
print(ls1)
</code></pre>
<h2>判断语句if else elif</h2>
<pre><code class="prism language-python">a = 3
b = 2

# 只有一个if，叫单分支
if a&gt;b:
    print(a)

b = 5
# if配合else，就是双分支
if a&gt;b:
    print(a)
else:
    print(b)

# 有if和elif配合else就是多分支
sorce=86
if sorce&gt;=90:
    print('A')
elif sorce&gt;=80:
    print('B')
    print('s')
elif sorce&gt;=60:
    print('C')
else:
    print('D')
</code></pre>
<p>在python里面不用写{}来限制满足条件后执行那些语句，只需要回车就行，就像sorce大于80的情况一样</p>
<p>if 后面也不用加括号了，只需要在条件后面加个冒号就行剩下的语句按个tab分割一下就行</p>
<h2>循环语句 for + 变量名 + in + 范围：</h2>
<pre><code class="prism language-python"># 列表
list1 = [25,65,33,61,56,8,56]
for i in list1:
    print(i)
#25 65 33 61 56 8 56

# 字符串
str1 = 'hello world'
for i in str1:
    print(i)

# h e l l o  w o r l d
</code></pre>
<p>for（关键词）+ i （变量）+in（关键词）+ 范围（最后的冒号，也是关键词）：</p>
<h2>循环语句与字典</h2>
<pre><code class="prism language-python">list2 = {
   'A':1,'B':2,'C':3}
for key,value in list2.items():
    print(key,value)
A 1
B 2
C 3
for key in list2.keys():
    print(key)
A
B
C
for value in list2.values():
    print(value)
1
2
3
</code></pre>
<ul>
 <li>使用函数items获取字典里面的键值对，并依次赋值给前面的两个变量，分别赋值key和value</li>
 <li>使用keys这个函数，可以只获取字典里面的键</li>
 <li>使用values这个函数，只获取字典里面的值</li>
</ul>
<h2>range(start,end,step)函数生成序列</h2>
<p>使用range函数，生成一个序列，</p>
<ul>
 <li>
  <p>不是列表，</p>
 </li>
 <li>
  <p>用逗号隔开</p>
 </li>
 <li>
  <p>也是左闭右开</p>
 </li>
</ul>
<pre><code class="prism language-python">a = range(1,10,2) #有三个参数的时候是 start,end,step     左闭右开，取不到end
#   range(1,10)    有两个参数的时候是 start,end         默认步长是1
#   range(10)    只有一个参数就是 end                   默认从0开始
print(a)    range(1, 10, 2)

# 序列列表化
b = list(a)
print(b)    [1, 3, 5, 7, 9]
</code></pre>
<p>range函数生成的不是完整的列表，是一个范围，想要直接打印，需要列表化</p>
<h2>求和函数sum</h2>
<p>这个函数会自动对参数求和</p>
<pre><code class="prism language-python"># 比如，从1到100，求和，可以用for循环，也可以是sum函数 
total = 0
for i in range(1,101):
    total += i
    # total = total + i
print(total)

# 求和函数--sum
print(sum(range(1,101)))

# 应用
# 求1到100所有的奇数和
total = 0
for i in range(1,101,2):
    total += i
print(total)

print(sum(range(1,101,2)))

# 求1到100所有的偶数和
total = 0
for i in range(2,101,2):
    total += i
print(total)

print(sum(range(2,101,2)))
</code></pre>
<h2>元组()</h2>
<p>元组就是不可修改的列表，用法一样</p>
<pre><code class="prism language-python">list1 = [1,2,3,4,5,6,7,8,9]     列表
list2 = (1,2,3,4,5,6,7,8,9)     元组
</code></pre>
<p>元组是（圆括号），列表是[方括号]</p>
<h2>while循环</h2>
<p>**while+条件语句： ** 条件语句成立就执行while</p>
<p>**while + not + 条件语句： ** 条件语句不成立就执行while</p>
<p>和c语言的用法一样，满足条件时循环，否则就跳出</p>
<pre><code class="prism language-python">a = 10
while a&gt;0:
    print(a)
    a -= 1


user = ''   # 空语句的布尔值是False
# 检测用户名
while not user:         # 如果user是空的，就是假，再配合not，就是假假为真
    user = input('请输入用户名：')

print('你好，{}'.format(user))
</code></pre>
<h3>遍历列表</h3>
<pre><code class="prism language-python">list1 = [65,13,54,16,8]
i = 0
while i &lt; len(list1):
    if list1[i] % 2 != 0:
        print(list1[i])
    i += 1
</code></pre>
<h2>跳出循环 break/continue</h2>
<h3>break跳出整个循环体</h3>
<p>在for-in或者while中，用一个if判断，来决定break语句是否执行，一旦执行，循环体结束</p>
<h3>continue跳出本次循环，不妨碍下一轮循环</h3>
<p>在for-in或者while中，用一个if判断，来决定continue语句是否执行，如果条件符合，这一轮的循环结束，不影响下一轮</p>
<h2>函数</h2>
<h3>函数分类</h3>
<ol>
 <li>
  <p>内置函数 python自带的</p>
 </li>
 <li>
  <p>自定义函数 程序员手搓的</p>
 </li>
 <li>
  <p>模块函数 库文件中的函数</p>
 </li>
</ol>
<h3>自定义函数–def+函数名（参数）</h3>
<ul>
 <li>函数的四种常见形式</li>
</ul>
<pre><code class="prism language-python"># 无参无返回值
def are1():
    print(3.14*5*5)
# 有参无返回值
def are2(r):
    print(3.14*r*r)
# 无参有返回值
def are3():
    return 3.14*5*5
# 有参有返回值
def are4(r):
    return 3.14*r*r
# 调用方法
are1()
are2(5)
print(are3())
print(are4(5))
</code></pre>
<h3>作用域</h3>
<p>函数体内定义的局部变量，不能全局使用</p>
<p>全局变量可以在函数体内使用</p>
<p>局部变量优先于全局变量</p>
<h3>global局部变量变成全局变量</h3>
<p>使用global声明局部变量，就会影响下面的全局变量</p>
<p>使用global之后，也可以在函数体外面使用了</p>
<h2>类和对象</h2>
<h3>class + 类名： 定义一个类</h3>
<h3>命名规则</h3>
<p>定义函数第一个字母小写</p>
<p>定义类，第一个字母大写</p>
<h3>类的实例化，类的创建</h3>
<p>在类里面使用def关键字，加上两个下划线，选择<em> </em>init <em> </em>对类进行初始化，给这个对象填充属性</p>
<p>参数里面的self不用管，这个参数就是对象他自己，就像c++那个内置函数一样，都会偷偷干活，不用管</p>
<p>self是隐藏参数，直接忽视</p>
<h3>在python里面f'{self.name}'和'{}'.format(self.name)这两种方式的效果一样</h3>
<p>虽然这两种方式在结果上是相同的，但它们有一些不同之处：</p>
<ol>
 <li><strong>可读性</strong> ：</li>
 <li>f-string（格式化字符串字面量）通常被认为更具可读性，尤其是在插入多个变量时。</li>
 <li><strong>性能</strong> ：</li>
 <li>f-string 在性能上略有优势，通常更快。</li>
 <li><strong>Python 版本</strong> ：</li>
 <li>f-string 从 Python 3.6 开始引入，而 <code>str.format()</code> 在 Python 2.7 和 3.0 中都可以使用。</li>
</ol>
<h3>给类对象增加属性</h3>
<p>直接在对象后面写属性，并赋值，就可以了</p>
<pre><code class="prism language-python"># 增加类的属性
aaa.height=180
print(aaa.height)
</code></pre>
<p>这个属性在设计的时候不存在，也只有这一个对象才多出来一个属性</p>
<pre><code class="prism language-python"># 类--人
class People:
    # 初始化
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
        print('初始化结束')
    # 自定义函数，这个参数是缺一不可的，也就是内联函数的指针
    def data(self):
        print(self.name,self.age,self.sex)

    def speak(self):
        print('{}正在说话'.format(self.name))

# 创建对象--类的实例化
aaa = People('zhangsan',15,'nan')
bbb = People('lisi',18,'nu')
# 调用对象的信息
aaa.data()
bbb.data()
# 修改对象的值
aaa.name = 'xiaozhang'
aaa.data()

aaa.speak()

# 增加类的属性
aaa.height=180
print(aaa.height)

# 打印
初始化结束
初始化结束

zhangsan 15 nan
lisi 18 nu

xiaozhang 15 nan

xiaozhang正在说话

180
</code></pre>
<h2>继承类</h2>
<pre><code class="prism language-python"># 父类
class student:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

    def study(self):
        print(f'{
     self.name}会学习')
# 子类---人工智能学生
class AI_student(student):
    def biancheng(self):
        print(f'{
     self.name}要学习AI了')
# 子类---机器设计学生
class JQ_student(student):
    def sheji(self):
        print(f'{
     self.name}会设计')

zhangsan = AI_student('张三',18,'男')
lisi = JQ_student('李四',19,'男')

zhangsan.study()        使用父类的函数
lisi.study()

zhangsan.biancheng()    使用子类的函数
lisi.sheji()

张三会学习
李四会学习
张三要学习AI了
李四会设计
</code></pre>
<p>对于一些类来说，有一些相同的属性，如果每个类都写这些属性，就会代码冗余，质量太差</p>
<p>所以，把这部分抽离出来，做成一个父类</p>
<p>其他一些特性，做成子类</p>
<p>就像上面的student这个类，是父类，包含姓名，年龄，性别</p>
<p>而子类，有人工智能学生和机器设计学生，他们分别又独立的会学习ai或设计机器</p>
<p>子类只需要在参数里面写上父类名，就可以继承父类的属性了</p>
<h2>派生类super</h2>
<p>针对的问题是：继承类无法创建子类自己的属性</p>
<p>利用super，可以在子类里面再初始化的时候增加自己的属性</p>
<pre><code class="prism language-python">class student:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

    def study(self):
        print(f'{
     self.name}会学习')

class AI_student(student):
    def __init__(self,name,age,sex,score,major):    参数按顺序补全，在后面加上自己的参数
        super().__init__(name,age,sex)              利用super直接借用父类的初始化函数
        self.score = score                          再对自己独特的参数初始化
        self.major = major

    def learn(self):
        print(f'{
     self.name}的专业是：{
     self.major},成绩是：{
     self.score}')  发现可以使用父类的参数也可以使用子类参数



zhangsan = AI_student('张三',18,'男',100,'python')

zhangsan.study()

zhangsan.learn()
</code></pre>
<h2>类对象的优先级</h2>
<p>使用变量时的优先级：</p>
<p>实例化的对象 &gt; 子类里面定义的 &gt; 父类定义 &gt; 系统默认</p>
<p>zhangsan.name&gt; AI_student.name&gt; student.name&gt;…</p>
<h2>空语句占位符pass</h2>
<p>比如，写了一个if语句，给他一个条件，但是目前不知道要他干啥，程序又必须有一个可以执行的命令，所以用pass占位，告诉程序你没错，目前用不到而已</p>
<h2>多态—函数重载</h2>
<p>就是在同时含有子类和父类的程序里面，调用变量的时候，程序可以自行选择子类</p>
<pre><code class="prism language-python"># 父类
class Animal:
    def speak(self):
        print('叫')

# 子类
class DDog(Animal):
    def speak(self):
        print('wangwangwang')

class CCat(Animal):
    def speak(self):
        print('mimimi')

dog=DDog()
cat = CCat()

dog.speak()
cat.speak()

# 多态
def speak(Anmail):
    Anmail.speak()

speak(dog)
speak(cat)

就像之前，使用len函数求长度的时候，既可以求字符串的长度，也可以求数列的长度，还可以求字典元素的个数，
就像c++里面的那个函数重载一样，同一个函数名，不同的参数，程序会根据你给的参数自动配对相应的函数

len('adsad')
len([1,2,3,5,4])
len({
   'a':1,'b':2})
</code></pre>
<h2><em> </em>私有变量</h2>
<p>就是对类里面的成员变量进行限制，不允许外部变量访问和修改</p>
<p>在创建类成员变量的时候前面加两个下划线</p>
<p>把self.name改成self.<em> </em>name 这里加上了一个空格，是为了方便观看</p>
<p>通过这个方法，哪怕外部使用双划线加变量名，也访问不到</p>
<p>唯一的方法，就是在外部调用的时候，在双划线的成员变量前面再加上类的名称</p>
<p>比如，dog.name &gt;&gt;&gt; dog._<em>name &gt;&gt;&gt; dog.</em>Anmail__name</p>
<p>这上面三个，只有最后面的那个可以访问和修改类里面的成员变量</p>
<h2>异常捕获 try except else finally</h2>
<p>把可能出错的语句，写在try语句里面，假如真的出现异常，就执行except语句，没用出现异常就执行else里面的语句，</p>
<p>最后无论发生哪种情况都会执行finally里面的语句</p>
<p>就是说，try是个条件语句，这个语句很危险，为了防止程序运行到此处崩溃，就给了两个选项，告诉程序，如果try里面的语句挂了，就执行except的语句，否则就是else，无论哪种情况都要执行finally语句</p>
<p>有始有终</p>
<p>好处就是，程序不会挂掉，会继续按照代码执行完程序</p>
<p>主要针对的问题，是在以后使用爬虫的时候，可以判断出了什么问题，比如服务器崩了，或者之前代码有误等等</p>
<p>相当于一种程序自动调试，自己处理遇到的问题</p>
<pre><code class="prism language-python">a=1
b=2
try: 
    c=a/b
except:
    print('error')
else:
    print('a/b={}'.format(c))
finally:
    print('程序结束')

# a/b=0.5 程序结束

a=1
b=2
try:                b不能为0
    c=a/b
except:
    print('error')
else:
    print('a/b={}'.format(c))
finally:
    print('程序结束')

# error  程序结束


b=2
try:            少一个变量
    c=a/b
except:
    print('error')
else:
    print('a/b={}'.format(c))
finally:
    print('程序结束')
# error  程序结束
</code></pre>
<h2>文件读取</h2>
<p>使用open函数，把绝对/完整路径放到第一个参数就可以打开文件了</p>
<p>第二个参数是：mode='r'，意思就是以读的形式打开一个文件</p>
<p>第三个参数是：encoding='utf-8' 这是一种文件打开的编码方式</p>
<h3>f.read()</h3>
<p>一次读取整个文件</p>
<h3>f.readline()</h3>
<p>一次读取一行</p>
<h3>f.readlines()</h3>
<p>也是一次读取整个文件，只是把文件内容放进一个列表里面</p>
<h3>while循环读取文件每一行</h3>
<p>使用while语句，再搭配读一行的函数，就可以分批循环读取数据了</p>
<pre><code class="prism language-python"># 打开文件
f = open(r'E:\pycharmxiangmu\pythonProject2\abc',mode='r',encoding='utf-8')


# 文件操作
# 读全部
txt = f.read()
print(txt)
# 读一行
txt = f.readline()      # 111
# 读全部，并转换成列表
txt = f.readlines()     # ['111\n', '222\n', '333']
print(txt)



while txt:
    txt = f.readline()
    print(txt)

111

222

333   发现是有空行的，因为print函数自动换行，在文件里面也有换行符，所以要消掉一个
while txt:
    txt = f.readline()
    print(txt,end='')   # 让print里面end的换行符为空，就不会换行了

# 关闭文件
f.close()

# 检查是否关闭文件
print(f.closed)     # 如果已经关闭文件，返回True，否则False
</code></pre>
<h3>with as: 读取文件，文件操作完成之后自动关闭文件</h3>
<pre><code class="prism language-python"># 打开文件
with open(r'E:\pycharmxiangmu\pythonProject2\abc',mode='r',encoding='utf-8') as f:
    # 文件操作
    # 读一行
    txt = f.readline()    
    print(txt, end='')
    while txt:
        txt = f.readline()
        print(txt,end='')   


# 检查是否关闭文件
print(f.closed)     # True  成功关闭
</code></pre>
<p>用 with + open() + as + 变量名： 完成对文件的打开</p>
<p>然后对文件操作的过程全部tab一下</p>
<p>最后自动关闭文件</p>
<h2>写入文件</h2>
<pre><code class="prism language-python"># 以只读的形式打开文件abc.txt，并把文件指针放在f
f = open(r'E:\pycharmxiangmu\pythonProject2\abc.txt',mode='r',encoding='utf-8')
# 以只写的方式创建一个文件abc1.txt
f1 = open(r'E:\pycharmxiangmu\pythonProject2\abc1.txt',mode='w',encoding='utf-8')
# 以追加的方式打开文件abc1.txt
f1 = open(r'E:\pycharmxiangmu\pythonProject2\abc1.txt',mode='a',encoding='utf-8')

txt = f.readline() txt只获取一行的内容

while txt:          判断txt不为空
    f1.write(txt)   把txt的内容写入f1的文件中
    txt = f.readline()  txt再读下一行

f.close()
f1.close()
</code></pre>
<h3>write与writelines</h3>
<p>writelines会把列表以字符串的形式写在文件里，</p>
<p>而write只能填写字符串，或者把列表类型转换成字符串，但是write会把整个列表写入</p>
<h2>打包文件</h2>
<h3>步骤</h3>
<p>找到pyinstaller的安装路径，也就是这个库所在的文件夹</p>
<ul>
 <li>找到python的安装文件夹</li>
 <li>打开Lib
  <ul>
   <li>再打开site-packages</li>
   <li>最后找到库文件PyInstaller，再点开，就是库文件夹了</li>
  </ul></li>
</ul>
<p>把想要打包的文件拷贝到库文件里面</p>
<p>再从网上找一个图片，自己转换成ico的后缀，这个后缀就是图标专用后缀</p>
<p>再打开命令行</p>
<p>使用命令语句打包：</p>
<ul>
 <li>打包成一个exe文件：pyinstaller -i 111.ico -F -c 111.py</li>
 <li>打包成一个文件夹：pyinstaller -i 111.ico -D -c 111.py</li>
</ul>
<p>​ pyinstaller</p>
<p>​ -i 的意思是设置图标</p>
<p>​ 111.ico 你想要设置的图标名称</p>
<p>​ -F 就是exe文件</p>
<p>​ -c 是说明有窗口</p>
<p>​ 最后是你想要打包的文件</p>
<p>然后就会在一个dist的文件里面生成好了</p>
<p>上面那个流程是正确的但是出了点小问题，需要调整参数如下</p>
<pre><code>pyinstaller --onefile --hidden-import=requests --icon=D:\Python\Python312\Lib\site-packages\PyInstaller\111.ico hello.py

以下是您提供的 `pyinstaller` 命令的参数解释：

### 参数解释

1. **`pyinstaller`**:
   - 这是调用 PyInstaller 工具的命令。

2. **`--onefile`**:
   - 这个选项指示 PyInstaller 将所有文件打包成一个单独的可执行文件（`.exe`）。没有这个参数时，PyInstaller 将创建一个包含多个文件和资源的文件夹。

这个参数就可以调整为-F和-D了，使用-F或者-D可以使打包速度更快

3. **`--hidden-import=requests`**:
   - 此参数指定了一个隐式依赖项。某些库可能在运行时被动态导入，PyInstaller 可能无法自动检测到它们。通过使用此参数，您可以确保 `requests` 模块被包含在打包文件中。
我感觉应该是我的电脑没找到我的库在哪里，需要额外声明一下   


4. **`--icon=D:\Python\Python312\Lib\site-packages\PyInstaller\111.ico`**:
   - 这个选项允许您为生成的可执行文件指定一个图标。在这个例子中，路径 `D:\Python\Python312\Lib\site-packages\PyInstaller\111.ico` 是图标文件的位置。图标必须是 `.ico` 格式。
这个就是我想要设置的图标的绝对路径


5. **`hello.py`**:
   - 最后，这是要打包的 Python 脚本的文件名。PyInstaller 将分析这个文件，并根据文件中的代码生成可执行文件。

### 总结

这个命令通过将 `hello.py` 打包成一个单独的可执行文件，确保 `requests` 库被正确导入，并且为最终的 `.exe` 文件指定了一个自定义图标。
</code></pre>
<p>原文链接: https://blog.csdn.net/FZ51111/article/details/142023237</p>]]></description><guid isPermaLink="false">/archives/95c3b541-cb8a-4162-b49c-92fdc0de0862</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Sun, 29 Sep 2024 03:36:30 GMT</pubDate></item><item><title><![CDATA[如何使用Spring Boot框架整合Redis：超详细案例教程]]></title><link>https://wiki.dgfygk.com/archives/733d8c15-61bc-4d3e-adfa-960b9aef56b1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Spring%20Boot%E6%A1%86%E6%9E%B6%E6%95%B4%E5%90%88Redis%EF%BC%9A%E8%B6%85%E8%AF%A6%E7%BB%86%E6%A1%88%E4%BE%8B%E6%95%99%E7%A8%8B&amp;url=/archives/733d8c15-61bc-4d3e-adfa-960b9aef56b1" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%23%20%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9Spring%20Boot%E4%B8%8ERedis%E6%95%B4%E5%90%88%EF%BC%9F"># 为什么选择Spring Boot与Redis整合？</a></p>
<p><a href="https://wiki.dgfygk.com#1.%20%E6%9B%B4%E6%96%B0%20pom.xml">1. 更新 pom.xml</a></p>
<p><a href="https://wiki.dgfygk.com#2.%20%E9%85%8D%E7%BD%AEapplication.yml">2. 配置application.yml</a></p>
<p><a href="https://wiki.dgfygk.com#3.%20%E5%88%9B%E5%BB%BA%20Redis%20%E9%85%8D%E7%BD%AE%E7%B1%BB">3. 创建 Redis 配置类</a></p>
<p><a href="https://wiki.dgfygk.com#4.%20Redis%20%E6%93%8D%E4%BD%9C%E7%B1%BB">4. Redis 操作类</a></p>
<p><a href="https://wiki.dgfygk.com#5.%20%E5%88%9B%E5%BB%BA%E6%8E%A7%E5%88%B6%E5%99%A8">5. 创建控制器</a></p>
<p><a href="https://wiki.dgfygk.com#6.%20%E5%90%AF%E5%8A%A8%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F">6. 启动应用程序</a></p>
<p><a href="https://wiki.dgfygk.com#7.%20%E6%B5%8B%E8%AF%95">7. 测试</a></p>
<hr>
<h4># 为什么选择Spring Boot与Redis整合？</h4>
<p>将Spring Boot与Redis整合可以充分利用两者的优势，简化开发并提升应用性能。具体好处包括：</p>
<ol>
 <li><strong>缓存支持</strong>：使用Redis作为缓存层可以极大提高应用的响应速度和可扩展性。</li>
 <li><strong>会话管理</strong>：将用户会话存储在Redis中，可以实现分布式会话管理。</li>
 <li><strong>数据持久性</strong>：Redis的持久化功能可以确保数据的可靠性。</li>
 <li><strong>简化配置</strong>：Spring Boot的自动配置和Redis Starter可以简化配置工作。</li>
</ol>
<p>了解了这些基础知识后，接下来将详细介绍如何在Spring Boot项目中整合Redis，包括依赖配置、连接设置、基本操作和应用案例。</p>
<h4>1. 更新 pom.xml</h4>
<p>确保你的项目包含了以下依赖。如果使用 Spring Initializr 已经添加了 spring-boot-starter-data-redis，这里就无需再次添加。</p>
<pre><code class="XML">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
        &lt;artifactId&gt;commons-pool2&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<p>commons-pool2 是用于 Lettuce 连接池配置的依赖。</p>
<h4>2. 配置application.yml</h4>
<blockquote>
 <p>补充：</p>
 <ul>
  <li>
   <p><strong>Spring Boot 1.x 和 Spring Boot 2.x</strong> 中，<code>spring.redis.host</code> 用于配置 Redis 连接属性。</p>
  </li>
  <li>
   <p><strong>Spring Boot 3.x</strong> 中，<code>spring.redis.host</code> 已经弃用。</p>
  </li>
  <li>
   <p>从 <strong>Spring Boot 2.x</strong> 开始，引入了 <code>spring.data.redis</code> 作为配置 Redis 连接的方式，并且在 <strong>Spring Boot 3.x</strong> 中也可以使用 <code>spring.data.redis</code> 进行配置。</p>
  </li>
 </ul>
</blockquote>
<p>在 src/main/resources 目录下，添加 Redis 连接池的配置。</p>
<p><strong><code>application.yml</code></strong>:</p>
<pre><code class="ruby">server:
  port: 8080

spring:
  redis:
    host: 127.0.0.1  # 地址
    port: 6379  # 端口号
    database: 0  # 数据库索引（默认为0）
    timeout: 1800000  # 连接超时时间（毫秒）
    lettuce:
      pool:
        max-active: 20  # 连接池最大连接数（使用负值表示没有限制）
        max-wait: -1  # 最大阻塞等待时间（负数表示没有限制）
        max-idle: 5  # 连接池中最大空闲连接
        min-idle: 0  # 连接池中最小空闲连接
</code></pre>
<h4>3. 创建 Redis 配置类</h4>
<p>在 com.lei.my_redis.config 包中创建或更新 RedisConfig 类，使用连接池配置 LettuceConnectionFactory。</p>
<pre><code class="java">package com.lei.my_redis.config;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@EnableCaching  // 开启缓存
@Configuration  // 配置类
public class RedisConfig extends CachingConfigurerSupport {


    /**
     * 配置 Redis 连接工厂
     * 意义: LettuceConnectionFactory 是连接 Redis 服务器的入口，它使用了 Lettuce 客户端，并且配置了连接池来提高性能和资源管理
     * @return LettuceConnectionFactory
     */
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // 配置 Redis 服务器的连接信息
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName("localhost");
        redisStandaloneConfiguration.setPort(6379);
        // redisStandaloneConfiguration.setPassword("password"); // 取消注释以设置密码

        // 配置连接池
        GenericObjectPoolConfig&lt;Object&gt; poolConfig = new GenericObjectPoolConfig&lt;&gt;();
        poolConfig.setMaxTotal(10);       // 连接池中的最大连接数
        poolConfig.setMaxIdle(5);         // 连接池中的最大空闲连接数
        poolConfig.setMinIdle(1);         // 连接池中的最小空闲连接数
        poolConfig.setMaxWaitMillis(2000); // 连接池获取连接的最大等待时间

        // 创建一个带有连接池配置的 Lettuce 客户端配置
        LettucePoolingClientConfiguration lettucePoolingClientConfiguration =
                LettucePoolingClientConfiguration.builder()
                        .poolConfig(poolConfig)
                        .build();

        // 返回带有连接池配置的 Redis 连接工厂
        return new LettuceConnectionFactory(redisStandaloneConfiguration, lettucePoolingClientConfiguration);
    }

    /**
     * 配置并返回一个 RedisTemplate 实例，用于执行 Redis 操作
     * 意义: RedisTemplate 提供了一种高级抽象，使得开发者可以通过模板方法操作 Redis，而无需直接处理底层的 Redis 命令。
     * 它支持多种 Redis 操作，例如值操作、哈希操作、列表操作等
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate&lt;String, Object&gt; redisTemplate() {
        /*
            1.创建 RedisTemplate: 这是 Spring 用于与 Redis 交互的核心类，简化了与 Redis 的交互。
            2.设置连接工厂: 使用前面定义的 LettuceConnectionFactory。
            3.设置序列化器: 设置键和值的序列化器，这里使用 StringRedisSerializer 来将键和值序列化为字符串。
         */
        RedisTemplate&lt;String, Object&gt; template = new RedisTemplate&lt;&gt;();
        template.setConnectionFactory(redisConnectionFactory());  // 设置连接工厂
        template.setKeySerializer(new StringRedisSerializer());  // 设置键的序列化器
        template.setValueSerializer(new StringRedisSerializer()); // 设置值的序列化器
        return template;
    }
}
</code></pre>
<h4>4. Redis 操作类</h4>
<p>保持 Redis 操作类和控制器不变。它们已经实现了基本的 Redis 操作。这里只需更新 RedisService 类以支持连接池即可。</p>
<pre><code class="java">package com.lei.my_redis.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * RedisService 类提供了简化的 Redis 操作接口，用于在 Spring Boot 应用中存储和检索数据。
 * 它通过 RedisTemplate 与 Redis 服务器交互，执行常见的操作如设置值、获取值、设置值带过期时间和删除值。
 */
@Service
public class RedisService {

    /*
        意义: RedisTemplate 是 Spring 提供的一个 Redis 操作模板，它抽象了 Redis 的底层访问，
        使开发者可以用 Java 对象操作 Redis。使用 @Autowired 注解，Spring 自动将配置好的 RedisTemplate 注入到 RedisService 类中
     */
    @Autowired
    private RedisTemplate&lt;String, Object&gt; redisTemplate;

    // 作用: 向 Redis 中存储一个键值对
    public void setValue(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 作用: 从 Redis 中获取指定键的值
    public Object getValue(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 作用: 向 Redis 中存储一个键值对，并设置其过期时间
    // timeout 指定时间量，timeUnit 指定时间单位
    public void setValueWithExpiry(String key, Object value, long timeout, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    // 作用: 从 Redis 中删除指定键及其对应的值
    public void deleteValue(String key) {
        redisTemplate.delete(key);
    }
}
</code></pre>
<h4>5. 创建控制器</h4>
<p>RedisController 类的实现保持不变。</p>
<pre><code class="java">package com.lei.my_redis.controller;

import com.lei.my_redis.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * RedisController 是一个 REST 控制器类，用于处理客户端发来的 HTTP 请求，并通过调用 RedisService
 * 执行 Redis 数据操作。它提供了三个 API 端点：设置值、获取值和删除值
 * RestController注解作用: 表示这个类是一个 RESTful 控制器，它的所有方法的返回值都会直接作为 HTTP 响应体返回。
 * RestController注解意义: 结合 @Controller 和 @ResponseBody 的功能，简化了返回 JSON 格式数据的开发工作。
 */
@RestController
public class RedisController {

    @Autowired
    private RedisService redisService;


    /**
     * 作用: 处理 HTTP POST 请求，设置 Redis 中的键值对
     * 1.使用 @RequestParam 注解获取请求参数 key 和 value
     * 2.调用 redisService.setValue(key, value) 方法，将 key 和 value 存储到 Redis 中
     * 3.返回一个简单的字符串 "Value set" 作为响应
     * 请求：POST /set?key=myKey&amp;value=myValue
     * 响应："Value set"
     * RequestParam注解：获取请求参数: 它从 HTTP 请求中获取参数，并将这些参数绑定到控制器方法的参数上
     */
//    @PostMapping("/set")
    @GetMapping("/set")
    public String setValue(@RequestParam String key, @RequestParam String value) {
        redisService.setValue(key, value);
        return "Value：(" + key + ") set";
    }

    /**
     * 作用: 处理 HTTP GET 请求，从 Redis 中获取指定键的值
     * 1.使用 @RequestParam 注解获取请求参数 key
     * 2.调用 redisService.getValue(key) 方法，从 Redis 中获取 key 对应的值，并将结果转换为 String 类型
     * 3.返回获取的值
     * 请求：GET /get?key=myKey
     * 响应："myValue" // Redis 中对应 `myKey` 的值
     */
    @GetMapping("/get")
    public String getValue(@RequestParam String key) {
        return (String) redisService.getValue(key);
    }

    /**
     * 作用: 处理 HTTP POST 请求，删除 Redis 中指定键的值
     * 1.使用 @RequestParam 注解获取请求参数 key
     * 2.调用 redisService.deleteValue(key) 方法，从 Redis 中删除 key 对应的键值对
     * 3.返回一个简单的字符串 "Value deleted" 作为响应
     * 请求：POST /delete?key=myKey
     * 响应："Value deleted"
     */
//    @PostMapping("/delete")
    @GetMapping("/delete")
    public String deleteValue(@RequestParam String key) {
        redisService.deleteValue(key);
        return "Value：(" + key + ") deleted";
    }
}
</code></pre>
<h4>6. 启动应用程序</h4>
<p>SpringBootRedisApplication 主类保持不变。</p>
<pre><code class="java">package com.lei.my_redis;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyRedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyRedisApplication.class, args);
    }

}
</code></pre>
<h4>7. 测试</h4>
<p>启动应用程序后，可以选中通过 Postman 或浏览器访问以下 URL，验证 Redis 操作是否成功。</p>
<ul>
 <li>设置值: POST http://localhost:8080/set?key=mykey&amp;value=myvalue</li>
 <li>获取值: GET http://localhost:8080/get?key=mykey</li>
 <li>删除值: POST http://localhost:8080/delete?key=mykey</li>
</ul>
<p>案例1：设置值</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fff3a6929205dcf0ab4f86b582baf7eec.png&amp;size=m" alt=""></p>
<p>案例2：获取值</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F71ac6c855d1bfd3cce6b25083e9607d3.png&amp;size=m" alt=""></p>
<p>案例3：删除值</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4ea788b65e4c3e7a9090359597329270.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcf7248f89159dd9687514d54496b5638.png&amp;size=m" alt=""></p>
<p>成功在 Spring Boot 项目中整合了 Redis 并配置了连接池。我们使用连接池可以显著提高 Redis 操作的性能和资源管理效率。</p>
<p>原文链接: https://blog.csdn.net/weixin_63514301/article/details/139691864</p>]]></description><guid isPermaLink="false">/archives/733d8c15-61bc-4d3e-adfa-960b9aef56b1</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:36:20 GMT</pubDate></item><item><title><![CDATA[三篇文章速通JavaSE到SpringBoot框架 (下) Maven、 接口、MyBatis、Spring、SpringMVC、SpringBoot]]></title><link>https://wiki.dgfygk.com/archives/c191c871-8e7b-4c02-b6d8-2ad8b3db2693</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%89%E7%AF%87%E6%96%87%E7%AB%A0%E9%80%9F%E9%80%9AJavaSE%E5%88%B0SpringBoot%E6%A1%86%E6%9E%B6%20%28%E4%B8%8B%29%20Maven%E3%80%81%20%E6%8E%A5%E5%8F%A3%E3%80%81MyBatis%E3%80%81Spring%E3%80%81SpringMVC%E3%80%81SpringBoot&amp;url=/archives/c191c871-8e7b-4c02-b6d8-2ad8b3db2693" width="1" height="1" alt="" style="opacity:0;">
<h4>Maven</h4>
<h5>非Maven项目的缺点</h5>
<p>问题一：
 <br>
 项目中的jar包资源需要我们自己从网上下载后，手动导入到项目中使用，不好管理。</p>
<p>问题二：
 <br>
 jar包版本控制麻烦</p>
<p>Maven是使用Java语编写的基于项目对象模型(POM)项目管理工具软件，开发者可通过小段描述信息来管理项目构建、报告和文档。使用<code>Maven</code>可以更好的帮助我们完成项目的管理。</p>
<h5>Maven仓库</h5>
<p><strong>中央仓库</strong> （<strong>CentralRepository</strong> ）:Maven官方服务器。里面存放了绝大多数市面上流行的jar。允许用户注册后，上传自己的项目到官方服务器。网址在国外，经常访问不了。https://mvnrepository.com</p>
<p><strong>本地仓库</strong> （<strong>LocalRepository</strong> ）:本机的文件夹作为本地仓库，本地仓库指本机的一份贝，用来缓存远程下载，包含你尚未发布的临时构件</p>
<p><strong>镜像仓库</strong> （<strong>MirrorRepository</strong>）:对于国内来说，访问国外的Maven仓库会特别慢。镜像仓库就是另一台备份/复制了中央仓库的服务器。平时使用时国内开发者多使用阿里云镜像或华为云镜像，这样可以大大提升从中央仓库下载资源的速度。但它的角色仍然是一个远程库。</p>
<h5>Maven的资源坐标</h5>
<p>Groupld:一般是逆向公司域名</p>
<p>。同一个公司的Groupld都是相同的
 <br>
 Artifactld:一般是项目(jar)名mysql-connector-java</p>
<p>Version:版本号8.0.28</p>
<h5>Maven的下载和安装</h5>
<p><code>https://maven.apache.org/download.cgi</code></p>
<h5>Maven常用配置</h5>
<p>在解压出的文件中conf文件夹中settings.xml配置信息</p>
<pre><code class="prism language-xml">//根据他们所在相对应的标签配置
&lt;localRepository&gt;your path&lt;/localRepository&gt;

&lt;mirror&gt;  
  &lt;id&gt;alimaven&lt;/id&gt;  
  &lt;name&gt;aliyun maven&lt;/name&gt;  
  &lt;url&gt;http://maven.aliyun.com/nexus/content/groups/public/&lt;/url&gt;  
  &lt;blocked&gt;central&lt;/blocked&gt;  
&lt;/mirror&gt;  
 &lt;/mirrors&gt;

&lt;profile&gt;  
    &lt;id&gt;jdk-11&lt;/id&gt;  
    &lt;activation&gt;    &lt;activeByDefault&gt;true&lt;/activeByDefault&gt;  
    &lt;jdk&gt;11&lt;/jdk&gt;  
      &lt;/activation&gt;   &lt;properties&gt;   &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;  
    &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;  
     &lt;maven.compiler.compilerVersion&gt;11&lt;/maven.compiler.compilerVersion&gt;  
   &lt;/properties&gt;&lt;/profile&gt;
</code></pre>
<h5>Maven使用</h5>
<p>打开idea 选择新建项目选择 Maven就行
 <br>
 在pom.xml引入包。在相关网站搜索并引入就行<code>https://mvnrepository.com/</code>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F76547bbd687c4f6599a1add4bbadd939.png&amp;size=m" alt=""></p>
<pre><code class="prism language-xml">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  

  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;test01&lt;/artifactId&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;packaging&gt;jar&lt;/packaging&gt;  

  &lt;name&gt;test01&lt;/name&gt;  
  &lt;url&gt;http://maven.apache.org&lt;/url&gt;  

  &lt;properties&gt;    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;  
  &lt;/properties&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;junit&lt;/groupId&gt;  
      &lt;artifactId&gt;junit&lt;/artifactId&gt;  
      &lt;version&gt;3.8.1&lt;/version&gt;  
      &lt;scope&gt;test&lt;/scope&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;mysql&lt;/groupId&gt;  
      &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
      &lt;version&gt;8.0.28&lt;/version&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.mybatis&lt;/groupId&gt;  
      &lt;artifactId&gt;mybatis&lt;/artifactId&gt;  
      &lt;version&gt;3.5.6&lt;/version&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;&lt;/project&gt;
</code></pre>
<h4>接口</h4>
<ol>
 <li>类是类，接口是接口。</li>
 <li>接口：定义规则。实现类：实现接口，实现规则</li>
 <li>接口：用interface来表示。</li>
 <li>在JDK1.8之前，接口中的内容：常量、抽象方法</li>
 <li>实现类实现接口，利用implements关键字</li>
 <li>实现类实现接口后，重写接口中定义的抽象方法</li>
 <li>接口不能创建对象，需要用接口指向实现类的形式创建对象。（多态的形式）</li>
</ol>
<pre><code class="prism language-java">public interface Speak{

/*常量：
它的数值不能改变，一旦给定了值以后就不能更改
接口中的常量前面的修饰符：public static final如果在接口中，可以省略public static final约定俗成的规定，一般定义常量的时候，名字全部大写*/

// 约定俗称 定义常量名字大写
public static final int AGT = 18;
//抽象方法
//在接口中，public abstract可以省略
public abstract void say();
}
</code></pre>
<p>接口不能创建对象 但是可以创建实现类的对象</p>
<pre><code class="prism language-java">public class Chinese implements Speak {

// 重写接口中的抽象方法  
@Override  
public void speak() {

System.out.println("你好");  
}  
}
Speak s = new Chinese(); // 多态 s.speak(); // 调用实现类中的方法
</code></pre>
<h5>接口和继承的区别</h5>
<p>继承：子类对父类的继承，提高代码的复用性。“is-a"的关系。</p>
<p>实现：实现类对接口的实现，实现规则。“has-a"的关系</p>
<h4>框架</h4>
<h5>框架出现的意义</h5>
<p>重复/基础代码封装，同时添加额外功能</p>
<p>释放程序员写代码精力，更关注业务层面</p>
<p>框架是半成品。</p>
<h5>常见框架</h5>
<p>常见Java框架分类</p>
<ol>
 <li>持久层框架。MyBatis、Hibernate、Spring Data、iBatis</li>
 <li>MVC框架。Spring MVC、 Struts1、 Struts2d</li>
 <li>项目管理框架。SpringFramework、Spring Boot</li>
 <li>微服务框架。Springcloud。</li>
 <li>权限管理框架。SpringSecurity、Shiro。</li>
</ol>
<h4>MyBatis</h4>
<p><code>https://mybatis.net.cn/index.html</code>官网
 <br>
 <strong>MyBatis</strong> 是一款优秀的持久层框架，它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO（Plain Old Java Objects，普通老式 Java 对象）为数据库中的记录。</p>
<h5>MyBatis是持久层框架</h5>
<p>持久层是分层开发中专门负责访问数据源的一层，Java项目中每一层都有自己的作用，持久层的作用就是访问数据源，把访问数据源的代码和业务逻辑代码分离开，有利于后期维护和团队分工开发。同时也增加了数据访问代码的复用性。</p>
<h5>MyBatis是ORM框架</h5>
<p>ORM(Object/RelationMapping)中文名称:对象/关系映射。是一种解决数据库发展和面向对象编程语言发展不匹配问题而出现的技术。</p>
<h5>搭建第一个MyBatis框架</h5>
<ol>
 <li>创建数据库表</li>
 <li>创建Maven项目</li>
 <li>添加依赖</li>
</ol>
<pre><code class="prism language-java">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  

  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;test01&lt;/artifactId&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;packaging&gt;jar&lt;/packaging&gt;  

  &lt;name&gt;test01&lt;/name&gt;  
  &lt;url&gt;http://maven.apache.org&lt;/url&gt;  

  &lt;properties&gt;    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;  
  &lt;/properties&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;junit&lt;/groupId&gt;  
      &lt;artifactId&gt;junit&lt;/artifactId&gt;  
      &lt;version&gt;3.8.1&lt;/version&gt;  
      &lt;scope&gt;test&lt;/scope&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;mysql&lt;/groupId&gt;  
      &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
      &lt;version&gt;8.0.28&lt;/version&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.mybatis&lt;/groupId&gt;  
      &lt;artifactId&gt;mybatis&lt;/artifactId&gt;  
      &lt;version&gt;3.5.6&lt;/version&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;&lt;/project&gt;
</code></pre>
<ol>
 <li>创建MyBatis全局配置文件</li>
</ol>
<pre><code class="prism language-java">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE configuration  
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
        "http://mybatis.org/dtd/mybatis-3-config.dtd"&gt;  
&lt;configuration&gt;  
    &lt;environments default="mysql"&gt;  
        &lt;environment id="mysql"&gt;  
            &lt;transactionManager type="JDBC"/&gt;  
            &lt;dataSource type="POOLED"&gt;  
                &lt;property name="driver" value="com.mysql.cj.jdbc.Driver"/&gt;  
                &lt;property name="url" value="jdbc:mysql://127.0.0.1:3306/book?useSSL=false"/&gt;  
                &lt;property name="username" value="yourusername"/&gt;  
                &lt;property name="password" value="yourpassword"/&gt;  
            &lt;/dataSource&gt;        &lt;/environment&gt;    &lt;/environments&gt;    &lt;mappers&gt;        &lt;mapper resource="mapper/BookMapper.xml"&gt;&lt;/mapper&gt; 
    &lt;/mappers&gt;&lt;/configuration&gt;
</code></pre>
<ol>
 <li>创建实体类</li>
</ol>
<pre><code class="prism language-java">//zhai.test.book
package zhai.test.book;  

public class Book {

        private int id;  
        private String title;  

    public int getId() {

        return id;  
    }  

    public void setId(int id) {

        this.id = id;  
    }  

    public String getTitle() {

        return title;  
    }  

    public void setTitle(String title) {

        this.title = title;  
    }  

    public Book() {

    }  

    public Book(int id, String title) {

        this.id = id;  
        this.title = title;  
    }  
}
</code></pre>
<ol>
 <li>创建映射文件</li>
</ol>
<pre><code class="prism language-java">//BookMapper.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE mapper  
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;  
&lt;mapper namespace="a.b"&gt;  
    &lt;select id="selectAllBooks" resultType="zhai.test.book.Book"&gt;  
         select * from book  
    &lt;/select&gt;  
&lt;/mapper&gt;
</code></pre>
<ol>
 <li>编写测试类，启动项目</li>
</ol>
<pre><code class="prism language-java">package com.zhai.test;  

import org.apache.ibatis.io.Resources;  
import org.apache.ibatis.session.SqlSession;  
import org.apache.ibatis.session.SqlSessionFactory;  
import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
import zhai.test.book.Book;  

import java.io.IOException;  
import java.io.InputStream;  
import java.util.List;  

public class test {

    public static void main(String[] args) throws IOException {

        //制定myBatis核心配置文件路径  
        String resource = "mybatis.xml";  
        //获取加载配置文件的输入流  
        InputStream inputStream = Resources.getResourceAsStream(resource);  

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  

        SqlSession sqlSession = sqlSessionFactory.openSession();  

        List list = sqlSession.selectList("a.b.selectAllBooks");  

        for(int i= 0; i&lt; list.size(); i++){

          Book b =(Book)list.get(i);  
            System.out.println(b.getTitle()+ "---" + b.getId());  
        }  
        sqlSession.close();  
    }  
}
</code></pre>
<h5>别名设置</h5>
<p>MyBatis提供了别名机制可以对某个类起别名或给某个包下所有类起别名，简化resultType取值的写法。
 <br>
 在核心配置文件中，通过<typealias>标签明确设置类型的别名。</typealias></p>
<p>type：类型全限定路径</p>
<p>alias:别名名称</p>
<p>当类数较多时，明确指定别名工作量较可以通过<package>标签指定包下全部类的别名。指定后所有类的别名就是类名.(也不区分大小写）</package></p>
<pre><code class="prism language-xml">//mybatis.xml
&lt;typeAliases&gt;  
    &lt;package name="zhai.test.book"&gt;&lt;/package&gt;  
&lt;/typeAliases&gt;
//BookMapper.xml
&lt;mapper namespace="a.b"&gt;  
    &lt;select id="selectAllBooks" resultType="Book"&gt;  
         select * from book  
    &lt;/select&gt;  
&lt;/mapper&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2695ba9f1e354322b331031e12e57415.png&amp;size=m" alt=""></p>
<h5>属性文件配置</h5>
<pre><code class="prism language-java">//db.properties
driver = com.mysql.cj.jdbc.Driver  
url = jdbc:mysql://127.0.0.1:3306/book?useSSL=false  
name = root  
password = zhai2665323601
</code></pre>
<pre><code>```prism language-xml
//mybatis.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE configuration  
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
        "http://mybatis.org/dtd/mybatis-3-config.dtd"&gt;  
&lt;configuration&gt;  
    &lt;properties resource="db.properties"&gt;&lt;/properties&gt;  
    &lt;typeAliases&gt;        &lt;package name="zhai.test.book"&gt;&lt;/package&gt;  
    &lt;/typeAliases&gt;    &lt;environments default="mysql"&gt;  

        &lt;environment id="mysql"&gt;  

            &lt;transactionManager type="JDBC"/&gt;  

            &lt;dataSource type="POOLED"&gt;  
                &lt;property name="driver" value="${driver}"/&gt;  
                &lt;property name="url" value="${url}"/&gt;  
                &lt;property name="username" value="${name}"/&gt;  
                &lt;property name="password" value="${password}"/&gt;  
            &lt;/dataSource&gt;        &lt;/environment&gt;    &lt;/environments&gt;    &lt;mappers&gt;        &lt;mapper resource="mapper/BookMapper.xml"&gt;&lt;/mapper&gt;  
    &lt;/mappers&gt;&lt;/configuration&gt;
</code></pre>
<h5>MyBatis启动日志功能</h5>
<p>MyBatis框架内置日志工厂。日志工厂负责自动加载项目中配置的日志。MyBatis支持以下日志：</p>
<ol>
 <li>SLF4J</li>
 <li>Apache Commons Logging</li>
 <li>Log4j2</li>
 <li>Log4j（deprecated since3.5.9）</li>
 <li>JDK logging
  <br>
  这里我们使用Log4j</li>
</ol>
<pre><code class="prism language-xml">//添加依赖
&lt;dependency&gt;
    &lt;groupId&gt;log4j&lt;/groupId&gt;
    &lt;artifactId&gt;log4j&lt;/artifactId&gt;
    &lt;version&gt;1.2.17&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>在resources中新建log4j.properties配置文件。名称必须叫这个名字，扩展名必须是.properties</p>
<pre><code class="prism language-java">log4j.rootLogger = error,console  

log4j.logger.a.b = TRACE  
### console ###  
log4j.appender.console=org.apache.log4j.ConsoleAppender  
log4j.appender.console.target = System.out  
log4j.appender.console.layout=org.apache.log4j.PatternLayout  
log4j.appender.console.layout.ConversionPattern=%d{
   ISO8601} [%t] %-5p %c %x - %m%n
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8e70e8e72bec463b85073e4a8107a44c.png&amp;size=m" alt=""></p>
<h5>MyBatis参数传递问题</h5>
<p>使用符号：#{}进行获取中名字使用规则
 <br>
 arg、arg1、argM(M为从0开始的数字，和方法参数顺序对应)</p>
<p>param1、param2、paramN(N为从1开始的数字，和方法参数顺序对应)</p>
<h6>如果多个参数且参数有对象，获取参数</h6>
<p>使用符号：进行获取
 <br>
 argM.属性名</p>
<p>paramN.属性名
 <br>
 PS：argM.或者paramN.不可以省略不写</p>
<pre><code class="prism language-xml">&lt;mapper namespace="com.example.UserMapper"&gt;  
&lt;insert id="insertUser"&gt;  
    INSERT INTO users (username, age)  
    VALUES (#{arg0.username}, #{arg0.age})  
&lt;/insert&gt;  

&lt;insert id="insertUserWithParam"&gt;  
    INSERT INTO users (username, age)  
    VALUES (#{param1.username}, #{param1.age})  
&lt;/insert&gt;  
&lt;/mapper&gt;
</code></pre>
<h4>Spring</h4>
<h5>SpringIoCDI介绍</h5>
<p>loc（inversionofControl）中文名称：控制反转，也被称为DIgependencyinjection）：依赖注入。注意：属于同一件事情的两个名称</p>
<p>创建对象的权利或者是控制的位置，由JAVA代码转移到spring容器，由spring的容器控制对象的创建，就是控制反转</p>
<p>由于控制反转概念比较含糊，所以在2004年大师级人物MartinFowler又给出了一个新的名字：“依赖注入”，相对loC而言，“依赖注入"明确描述了"被注入对象依赖IoC容器来配置依赖对象”，DI英文全称为DependencyInjection，中文译名为依赖注入）是loc的别名。</p>
<h5>Spring</h5>
<ol>
 <li>对比以往项目，Spring优势有哪些：方便解耦，简化开发；AOP切面编程；声明式事务；整合各种优秀的框架</li>
 <li>不重复造轮子</li>
 <li>使用Spring所需jar包</li>
 <li>spring官网：去spring官网找：https://spring.io/</li>
</ol>
<h5>相关图例</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F24e0a0fe099d46d28107ed9e67dbedee.png&amp;size=m" alt=""></p>
<h5>第一个spring项目 完成IoC/DI代码实现</h5>
<p>创建maven项目 添加依赖
 <br>
 Spring项目想要运行起来必须包含
 <br>
 spring-context jar它依赖了下面的四个jar。
 <br>
 spring-core jar-它依赖了spring-jcl jar</p>
<p>spring-aop jar</p>
<p>spring-expression jar</p>
<p>spring-beans jar</p>
<p>spring-jcl jar</p>
<p>所以在Maven中想要使用Spring框架只需要在项目中导入spring-context就可以了，其他的jar包根据Maven依赖传递性都可以导入进来</p>
<pre><code class="prism language-xml">  &lt;dependency&gt;  
    &lt;groupId&gt;org.springframework&lt;/groupId&gt;  
    &lt;artifactId&gt;spring-context&lt;/artifactId&gt;  
    &lt;version&gt;5.3.23&lt;/version&gt;  
  &lt;/dependency&gt;&lt;/dependencies&gt;
</code></pre>
<p>实现一个实体类Book之后代码实现</p>
<pre><code class="prism language-java">// resources/applicationContext.xml
&lt;?xml version="1.0" encoding="UTF-8"?&gt;  
&lt;beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
        https://www.springframework.org/schema/beans/spring-beans.xsd"&gt;  
    &lt;bean id="b" class="org.example.Book"&gt;  &lt;/bean&gt;
    &lt;/beans&gt;  
//test.java
package org.example;  

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  

public class test {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
        Book b = (Book)context.getBean("b");  
        Book b1 = (Book)context.getBean("b1");  
        System.out.println(b.getId()+"---"+b.getTitle());  
        System.out.println(b1.getId()+"---"+b1.getTitle());  

    }  
}
</code></pre>
<h5>属性注入方式</h5>
<h6>设值注入</h6>
<p>相关代码实现</p>
<pre><code class="prism language-xml">// resources/applicationContext.xml
&lt;bean id="b" class="org.example.Book"&gt;  
    &lt;!-- collaborators and configuration for this bean go here --&gt;  
    &lt;property name="id" value="1"&gt;&lt;/property&gt;  
    &lt;property name="title" value="三国"&gt;&lt;/property&gt;  
&lt;/bean&gt;
</code></pre>
<h6>构造注入</h6>
<p>相关代码实现</p>
<pre><code class="prism language-xml">//resources/applicationContext.xml
&lt;bean id="b1" class="org.example.Book"&gt;  
    &lt;constructor-arg name="id" value="2"&gt;&lt;/constructor-arg&gt;  
    &lt;constructor-arg name="title" value="水浒"&gt;&lt;/constructor-arg&gt;  
&lt;/bean&gt;
</code></pre>
<h6>属性为复杂引用类型</h6>
<pre><code class="prism language-java">&lt;bean id="b" class="org.example.Boy"&gt;  
    &lt;property name="age" value="18"&gt;&lt;/property&gt;  
    &lt;property name="name" value="芜湖"&gt;&lt;/property&gt;  
&lt;/bean&gt;  
&lt;bean id="g" class="org.example.Girl"&gt;  
    &lt;property name="age" value="18"&gt;&lt;/property&gt;  
    &lt;property name="name" value="小红"&gt;&lt;/property&gt;  
    &lt;property name="boyfriend" ref="b"&gt;&lt;/property&gt;  
&lt;/bean&gt;
</code></pre>
<p>设置属性的值：
 <br>
 方式1：value：简单数据类型（基本数据类型+String）直接设置</p>
<p>方式2：ref:需要引I用另一个bean的id。也就是说这个参数是一个类类型，且这个类的对象也被Spring容器管理。</p>
<h5>IoC/DI相关注解</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8fb8a5f6c56c47039a7c9b22f0836657.png&amp;size=m" alt=""></p>
<p>@Component的使用
 <br>
 在要创建对象的类中加入@Component注解，对象名字默认为：类名首字母变小写
 <br>
 注解在哪个包下？要想找到这些注解，需要将注解所在的包进行扫描：设置需要扫描的包。并且需要在applicationContext.xml中添加context命名空间</p>
<p>前五个注解作用一样，只所以搞出这么多，就是在语义上给你区别，放入不同层用不同的注解，但是作用都是创建对象</p>
<pre><code class="prism language-xml">//
&lt;?xml version="1.0" encoding="UTF-8"?&gt;  
&lt;beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
        https://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context        https://www.springframework.org/schema/context/spring-context.xsd        "&gt;  
    &lt;context:component-scan base-package="org.example"&gt;&lt;/context:component-scan&gt;  
&lt;/beans&gt;
</code></pre>
<p>使用</p>
<pre><code class="prism language-java">//book.java
package org.example;  

import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  

@Component  
public class Book {

    @Value("1")  
    private int id;  
    @Value("三国")  
    private String title;  

    public Book() {

        System.out.println("空构造器");  
    }  

    public Book(int id, String title) {

        System.out.println("有掺构造器");  
        this.id = id;  
        this.title = title;  
    }  

    public int getId() {

        return id;  
    }  

    public void setId(int id) {

        System.out.println("setid");  
        this.id = id;  
    }  

    public String getTitle() {

        return title;  
    }  

    public void setTitle(String title) {

        System.out.println("settitile");  
        this.title = title;  
    }  
}
//test.java
package org.example;  

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  

public class test {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
        Book b = (Book)context.getBean("book");  
        System.out.println(b.getId()+"---"+b.getTitle());  

    }  
}
</code></pre>
<h6>@Value的使用</h6>
<p>给普通数据类型赋值的注解， 普通数据类型包括：八种基本数据类型+String</p>
<p>@Autowired的使用
 <br>
 添加@Autowired注解后会把容器中的对象自动注入进来，并且不需要依赖set方法。</p>
<pre><code class="prism language-java">@Autowired  
private Boy boyfriend;
</code></pre>
<h4>web项目构建</h4>
<h5>java项目和web项目的区别</h5>
<p>Java项目是由main方法来开始的，直接依赖JVM就能被编译执行。Java项目不需要服务器。</p>
<p>Web项目中的Java文件是tomcat服务器来触发的，脱离了web服务器就无法启动。Web项目需要服务器。Web项目部署到服务器上，任何用户都可以通过浏览器来访问。将本地资源共享给外部访问。</p>
<h5>使用服务器</h5>
<p>Tomcat服务器对Servlet，Jsp，JNDI，JavaMail有很好的的支持，并且这个Web容器是开源免费的。(Apache开源免费)</p>
<h5>创建maven webapp项目</h5>
<p>在idea中使用tomcat 插件</p>
<pre><code class="prism language-xml">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  
  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;javaweb01&lt;/artifactId&gt;  
  &lt;packaging&gt;war&lt;/packaging&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;build&gt;    &lt;plugins&gt;      &lt;plugin&gt;        &lt;groupId&gt;org.apache.tomcat.maven&lt;/groupId&gt;  
        &lt;artifactId&gt;tomcat7-maven-plugin&lt;/artifactId&gt;  
        &lt;version&gt;2.2&lt;/version&gt;  
        &lt;configuration&gt;          &lt;path&gt;/javaweb01&lt;/path&gt;  
          &lt;port&gt;8080&lt;/port&gt;  
        &lt;/configuration&gt;      &lt;/plugin&gt;    &lt;/plugins&gt;  &lt;/build&gt;&lt;/project&gt;
</code></pre>
<h4>SpringMVC</h4>
<p>Spring MVC 是一种基于 Java 的 Web 框架，是 Spring Framework 的一部分，用于构建现代化的企业级应用程序。它采用了模型-视图-控制器 (MVC) 设计模式，帮助开发者将应用程序的不同关注点分离，使得代码更加模块化和可维护。
 <br>
 <strong>处理 HTTP 请求</strong> ：接收和处理来自客户端的 HTTP 请求，分发请求到相应的控制器 也就是获取前端传过来的数据 响应内容到前端</p>
<p><strong>分离关注点</strong>：通过模型-视图-控制器 (MVC) 设计模式，将应用程序的业务逻辑、用户界面和输入处理分开，提高代码的可维护性和可测试性。</p>
<h5>相关实现</h5>
<ol>
 <li>
  <p>创建maven-web项目</p>
 </li>
 <li>
  <p>补全目录
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffdd67f3ec81c4a0294557b9c811613a0.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>添加依赖</p>
 </li>
 <li>
  <p>加入tomcat插件</p>
 </li>
</ol>
<pre><code class="prism language-xml">pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  
  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;javaweb01&lt;/artifactId&gt;  
  &lt;packaging&gt;war&lt;/packaging&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework&lt;/groupId&gt;  
      &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt;  
      &lt;version&gt;5.3.16&lt;/version&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;  &lt;build&gt;    &lt;plugins&gt;      &lt;plugin&gt;        &lt;groupId&gt;org.apache.tomcat.maven&lt;/groupId&gt;  
        &lt;artifactId&gt;tomcat7-maven-plugin&lt;/artifactId&gt;  
        &lt;version&gt;2.2&lt;/version&gt;  
        &lt;configuration&gt;          &lt;path&gt;/javaweb01&lt;/path&gt;  
          &lt;port&gt;8080&lt;/port&gt;  
        &lt;/configuration&gt;      &lt;/plugin&gt;    &lt;/plugins&gt;  &lt;/build&gt;&lt;/project&gt;
</code></pre>
<ol>
 <li>编写web.xml内容
  <br>
  这段配置文件用于设置 Spring MVC 的 DispatcherServlet，使其能够接收和处理来自客户端的 HTTP 请求，并通过指定的 Spring 配置文件进行初始化。这是实现基于 Spring 的 MVC 架构的基础配置。</li>
</ol>
<pre><code class="prism language-xml">//web.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"  
         version="4.0"&gt;  
&lt;!-- 这里可以添加 servlet、filter 等其他配置 --&gt;  
    &lt;servlet&gt;  
        &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;  
        &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;  
        &lt;init-param&gt;            &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;  
            &lt;param-value&gt;classpath:springmvc.xml&lt;/param-value&gt;  
        &lt;/init-param&gt;        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;  
    &lt;/servlet&gt;    &lt;servlet-mapping&gt;        &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;  
        &lt;url-pattern&gt;/&lt;/url-pattern&gt;  
    &lt;/servlet-mapping&gt;&lt;/web-app&gt;
</code></pre>
<ol>
 <li>搭配注解环境
  <br>
  这段配置文件的主要作用是设定一个 Spring MVC 应用的基础环境，包括定义组件扫描的路径和启用注解驱动的 MVC 功能。它使得开发者可以通过注解来简化控制器和服务的配置，提高开发效率</li>
</ol>
<pre><code class="prism language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;  
&lt;beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:mvc="http://www.springframework.org/schema/mvc"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
        https://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context        https://www.springframework.org/schema/context/spring-context.xsd        http://www.springframework.org/schema/mvc        https://www.springframework.org/schema/mvc/spring-mvc.xsd        "&gt;  
    &lt;context:component-scan base-package="com.zhai.controller"&gt;&lt;/context:component-scan&gt;  
    &lt;mvc:annotation-driven&gt;&lt;/mvc:annotation-driven&gt;  
&lt;/beans&gt;
</code></pre>
<ol>
 <li>运行tomcat</li>
</ol>
<pre><code class="prism language-java">package com.zhai.controller;  

import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  

@Controller  
public class testController {

    @RequestMapping("/test1")  
    public String test(){

        return "index.jsp";  
    }  
}
</code></pre>
<h5>获取普通参数</h5>
<pre><code class="prism language-java">@RequestMapping("/testParam")  
public String testParam(String name, int age){

    System.out.println(name +"----" + age);  
    return "index.jsp";  
}
</code></pre>
<h5>使用类对象作为控制单元参数</h5>
<p>JavaBean：一个包含私有属性，getter/setter方法和无参构造方法的Java类。，是不是感觉和实体类特别像。其实写法上和实体类相同。唯一区别是实体类是数据库层面的概念，类型中属性要和数据库字段对应。而JavaBean的属性是灵活的，不是必须和哪里对应的。
 <br>
 JavaBean是一个专业概念，可以简单点理解：使用类对象做为控制单元参数，接收请求参数。如果不是特别较真，狭义上可以认为JavaBean就是项目中的实体类
 <br>
 在控制单元中放置一个类型对象，对象名称没有要求，只需要保证请求参数名和类的属性名相同就可以了。</p>
<pre><code class="prism language-java">   @RequestMapping("/testParam2")  
    public String testParam2(person p){

        System.out.println(p.getname() +"----" + p.getage());  
        return "index.jsp";  
    }  
}
</code></pre>
<h4>SSM介绍</h4>
<p>SSM:<strong>Spring + Spring MVC + MyBatis</strong>：一种在Java开发中常用的框架组合。</p>
<h4>SpringBoot</h4>
<h5>springboot概念简介</h5>
<p>SpringBoot是Spring公司的一个顶级项目，和SpringFramework是一个级别的。</p>
<p>SpringBoot实际上是利用SpringFramework4自动配置特性完成。编写项目时不需要编写xml文件。发展到现在，SpringBoot已经县有很很大的生态圈，各种主流技术已经都提供了SpringBoot的启动器。</p>
<h5>springBoot优点</h5>
<p><code>约定大于配置</code></p>
<h5>例子:整合SpringMVC</h5>
<ol>
 <li>创建maven工程</li>
 <li>选择springboot的版本添加到pom.xml</li>
</ol>
<pre><code class="prism language-xml">&lt;dependencyManagement&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
      &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;  
      &lt;version&gt;2.4.5&lt;/version&gt;  
      &lt;type&gt;pom&lt;/type&gt;  
      &lt;scope&gt;import&lt;/scope&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;&lt;/dependencyManagement&gt;
</code></pre>
<ol>
 <li>
  <p>整合springmvc用到的包,添加启动器
   <br>
   什么是启动器</p>
  <p>Spring框架在项目中作用是Spring整合各种其他技术，让其他技术使用更加方便。SpringBoot的启动器实际上就是一个依赖。这个依赖中包含了整个这个技术的相关jar包，还包含了这个技术的自动配置，以前绝大多数XML配置都不需要配置了。以后每次使用SpringBoot整合其他技术时首先需要考虑导入启动器。</p>
  <p>什么是启动类</p>
  <p>SpringBoot的启动类的作用是启动SpringBoot项目，是基于Main方法来运行的</p>
  <p>启动类与启动器区别
   <br>
   启动类表示项目的启动入口</p>
  <p>启动器表示jar包的坐标</p>
 </li>
</ol>
<pre><code class="prism language-xml">&lt;dependencies&gt;  
  &lt;dependency&gt;    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;  
  &lt;/dependency&gt;&lt;/dependencies&gt;
</code></pre>
<ol>
 <li>开发一个Controller控制层</li>
</ol>
<pre><code class="prism language-java">package com.zhai.controller;  

import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.ResponseBody;  

@Controller  
public class MyController {

    @RequestMapping("/test01")  
    @ResponseBody  
    public String test01(){

        return "hi,springboot...";  
    }  
}
</code></pre>
<ol>
 <li>启动类编写</li>
</ol>
<pre><code class="prism language-java">package com.zhai.controller;  

import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  

@SpringBootApplication  
public class testSpringBootApplication {

    public static void main(String[] args) {

        SpringApplication.run(testSpringBootApplication.class,args);  
    }  
}
</code></pre>
<ol>
 <li>运行，游览器访问测试
  <br>
  ![[Pasted image 20240928185546.png]]</li>
</ol>
<h5>springBoot配置文件</h5>
<p>在resources中创建文件application.properties编写内容</p>
<pre><code class="prism language-java">server.port = 8080  
server.servlet.context-path=/springboot01
</code></pre>
<p>官方推荐的配置文件
 <br>
 springboot官方推荐的配置文件是yml文件，yml是用层级来表示关系的一种配置文件。yml中没有标签，而是通过两个空格的缩进来表示层级结构</p>
<p>例如：
 <br>
 properties中：
 <br>
 server.port=8080
 <br>
 yml中：
 <br>
 Server:
 <br>
 port:8080
 <br>
 层级结构怎么找（SpringBoot常见配置，查看官网文档）
 <br>
 https://docs.spring.io/spring-boot/docs/2.7.6/reference/html/application-properties.html#appendix.application-properties
 <br>
 注意：文件名字为：<strong>application</strong> .yml，文件名字application开头，不能随意动</p>
<p>注意冒号后空格</p>
<pre><code class="prism language-yml">server:  
  port: 9999  
  servlet:  
    context-path: /test02
</code></pre>
<h5>SpringBoot整合SSM</h5>
<ol>
 <li>导入依赖</li>
</ol>
<pre><code class="prism language-xml">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  
  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;onessm&lt;/artifactId&gt;  
  &lt;packaging&gt;war&lt;/packaging&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;dependencyManagement&gt;    &lt;dependencies&gt;      &lt;dependency&gt;        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
        &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;  
        &lt;version&gt;2.4.5&lt;/version&gt;  
        &lt;type&gt;pom&lt;/type&gt;  
        &lt;scope&gt;import&lt;/scope&gt;  
      &lt;/dependency&gt;    &lt;/dependencies&gt;  &lt;/dependencyManagement&gt;  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
      &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;  
      &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;  
      &lt;version&gt;2.1.3&lt;/version&gt; &lt;!-- 替换为你需要的版本 --&gt;  
    &lt;/dependency&gt;  
    &lt;dependency&gt;      &lt;groupId&gt;mysql&lt;/groupId&gt;  
      &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;  
&lt;/project&gt;
</code></pre>
<ol>
 <li>编写配置文件</li>
</ol>
<pre><code class="prism language-java">server:  
  port: 9999  
  servlet:  
    context-path: /test02  
spring:  
  datasource:  
    url: jdbc:mysql://127.0.0.1:3306/book?useSSL=false  
    driver-class-name: com.mysql.cj.jdbc.Driver  
    username: root  
    password: yourpassword
</code></pre>
<ol>
 <li>编写实体类，配置文件中加入别名
  <br>
  ![[Pasted image 20240928195008.png]]</li>
 <li>定义mapper接口和定义mapper.xml映射文件</li>
</ol>
<pre><code class="prism language-java">    package com.zhai.mapper;  

import java.util.List;  

public interface BookMapper {

    List selectAllBooks();  
}
// BookMapper.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE mapper  
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;  
&lt;mapper namespace="com.zhai.mapper.BookMapper"&gt;  
    &lt;select id="selectAllBooks" resultType="book"&gt;  
        select * from book  
    &lt;/select&gt;  
&lt;/mapper&gt;
</code></pre>
<ol>
 <li>定义启动类在启动类加入mapper的包扫描</li>
</ol>
<pre><code class="prism language-java">package com.zhai;  

import org.mybatis.spring.annotation.MapperScan;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  

@SpringBootApplication  
@MapperScan("com.zhai.mapper")  
public class testSpringBootApplication {

    public static void main(String[] args) {

        SpringApplication.run(testSpringBootApplication.class,args);  
    }  
}
</code></pre>
<h4>最终效果实现</h4>
<p>![[Pasted image 20240928231954.png]]</p>
<blockquote>
 <p>如果对你有用的话 就点个关注吧 会持续更新技术文章</p>
</blockquote>
<h4>Maven</h4>
<h5>非Maven项目的缺点</h5>
<p>问题一：
 <br>
 项目中的jar包资源需要我们自己从网上下载后，手动导入到项目中使用，不好管理。</p>
<p>问题二：
 <br>
 jar包版本控制麻烦</p>
<p>Maven是使用Java语编写的基于项目对象模型(POM)项目管理工具软件，开发者可通过小段描述信息来管理项目构建、报告和文档。使用<code>Maven</code>可以更好的帮助我们完成项目的管理。</p>
<h5>Maven仓库</h5>
<p><strong>中央仓库</strong> （<strong>CentralRepository</strong> ）:Maven官方服务器。里面存放了绝大多数市面上流行的jar。允许用户注册后，上传自己的项目到官方服务器。网址在国外，经常访问不了。https://mvnrepository.com</p>
<p><strong>本地仓库</strong> （<strong>LocalRepository</strong> ）:本机的文件夹作为本地仓库，本地仓库指本机的一份贝，用来缓存远程下载，包含你尚未发布的临时构件</p>
<p><strong>镜像仓库</strong> （<strong>MirrorRepository</strong>）:对于国内来说，访问国外的Maven仓库会特别慢。镜像仓库就是另一台备份/复制了中央仓库的服务器。平时使用时国内开发者多使用阿里云镜像或华为云镜像，这样可以大大提升从中央仓库下载资源的速度。但它的角色仍然是一个远程库。</p>
<h5>Maven的资源坐标</h5>
<p>Groupld:一般是逆向公司域名</p>
<p>。同一个公司的Groupld都是相同的
 <br>
 Artifactld:一般是项目(jar)名mysql-connector-java</p>
<p>Version:版本号8.0.28</p>
<h5>Maven的下载和安装</h5>
<p><code>https://maven.apache.org/download.cgi</code></p>
<p>如果终端出现问题mvn not found</p>
<h5>Maven常用配置</h5>
<p>在解压出的文件中conf文件夹中settings.xml配置信息</p>
<pre><code class="prism language-xml">//根据他们所在相对应的标签配置
&lt;localRepository&gt;your path&lt;/localRepository&gt;

&lt;mirror&gt;  
  &lt;id&gt;alimaven&lt;/id&gt;  
  &lt;name&gt;aliyun maven&lt;/name&gt;  
  &lt;url&gt;http://maven.aliyun.com/nexus/content/groups/public/&lt;/url&gt;  
  &lt;blocked&gt;central&lt;/blocked&gt;  
&lt;/mirror&gt;  
 &lt;/mirrors&gt;

&lt;profile&gt;  
    &lt;id&gt;jdk-11&lt;/id&gt;  
    &lt;activation&gt;    &lt;activeByDefault&gt;true&lt;/activeByDefault&gt;  
    &lt;jdk&gt;11&lt;/jdk&gt;  
      &lt;/activation&gt;   &lt;properties&gt;   &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;  
    &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;  
     &lt;maven.compiler.compilerVersion&gt;11&lt;/maven.compiler.compilerVersion&gt;  
   &lt;/properties&gt;&lt;/profile&gt;
</code></pre>
<h5>Maven使用</h5>
<p>打开idea 选择新建项目选择 Maven就行
 <br>
 在pom.xml引入包。在相关网站搜索并引入就行<code>https://mvnrepository.com/</code>
 <br>
 ![[Pasted image 20240927145436.png]]</p>
<pre><code class="prism language-xml">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  

  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;test01&lt;/artifactId&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;packaging&gt;jar&lt;/packaging&gt;  

  &lt;name&gt;test01&lt;/name&gt;  
  &lt;url&gt;http://maven.apache.org&lt;/url&gt;  

  &lt;properties&gt;    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;  
  &lt;/properties&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;junit&lt;/groupId&gt;  
      &lt;artifactId&gt;junit&lt;/artifactId&gt;  
      &lt;version&gt;3.8.1&lt;/version&gt;  
      &lt;scope&gt;test&lt;/scope&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;mysql&lt;/groupId&gt;  
      &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
      &lt;version&gt;8.0.28&lt;/version&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.mybatis&lt;/groupId&gt;  
      &lt;artifactId&gt;mybatis&lt;/artifactId&gt;  
      &lt;version&gt;3.5.6&lt;/version&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;&lt;/project&gt;
</code></pre>
<h4>接口</h4>
<ol>
 <li>类是类，接口是接口。</li>
 <li>接口：定义规则。实现类：实现接口，实现规则</li>
 <li>接口：用interface来表示。</li>
 <li>在JDK1.8之前，接口中的内容：常量、抽象方法</li>
 <li>实现类实现接口，利用implements关键字</li>
 <li>实现类实现接口后，重写接口中定义的抽象方法</li>
 <li>接口不能创建对象，需要用接口指向实现类的形式创建对象。（多态的形式）</li>
</ol>
<pre><code class="prism language-java">public interface Speak{

/*常量：
它的数值不能改变，一旦给定了值以后就不能更改
接口中的常量前面的修饰符：public static final如果在接口中，可以省略public static final约定俗成的规定，一般定义常量的时候，名字全部大写*/

// 约定俗称 定义常量名字大写
public static final int AGT = 18;
//抽象方法
//在接口中，public abstract可以省略
public abstract void say();
}
</code></pre>
<p>接口不能创建对象 但是可以创建实现类的对象</p>
<pre><code class="prism language-java">public class Chinese implements Speak {

// 重写接口中的抽象方法  
@Override  
public void speak() {

System.out.println("你好");  
}  
}
Speak s = new Chinese(); // 多态 s.speak(); // 调用实现类中的方法
</code></pre>
<h5>接口和继承的区别</h5>
<p>继承：子类对父类的继承，提高代码的复用性。“is-a"的关系。</p>
<p>实现：实现类对接口的实现，实现规则。“has-a"的关系</p>
<h4>框架</h4>
<h5>框架出现的意义</h5>
<p>重复/基础代码封装，同时添加额外功能</p>
<p>释放程序员写代码精力，更关注业务层面</p>
<p>框架是半成品。</p>
<h5>常见框架</h5>
<p>常见Java框架分类</p>
<ol>
 <li>持久层框架。MyBatis、Hibernate、Spring Data、iBatis</li>
 <li>MVC框架。Spring MVC、 Struts1、 Struts2d</li>
 <li>项目管理框架。SpringFramework、Spring Boot</li>
 <li>微服务框架。Springcloud。</li>
 <li>权限管理框架。SpringSecurity、Shiro。</li>
</ol>
<h4>MyBatis</h4>
<p><code>https://mybatis.net.cn/index.html</code>官网
 <br>
 <strong>MyBatis</strong> 是一款优秀的持久层框架，它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO（Plain Old Java Objects，普通老式 Java 对象）为数据库中的记录。</p>
<h5>MyBatis是持久层框架</h5>
<p>持久层是分层开发中专门负责访问数据源的一层，Java项目中每一层都有自己的作用，持久层的作用就是访问数据源，把访问数据源的代码和业务逻辑代码分离开，有利于后期维护和团队分工开发。同时也增加了数据访问代码的复用性。</p>
<h5>MyBatis是ORM框架</h5>
<p>ORM(Object/RelationMapping)中文名称:对象/关系映射。是一种解决数据库发展和面向对象编程语言发展不匹配问题而出现的技术。</p>
<h5>搭建第一个MyBatis框架</h5>
<ol>
 <li>创建数据库表</li>
 <li>创建Maven项目</li>
 <li>添加依赖</li>
</ol>
<pre><code class="prism language-java">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  

  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;test01&lt;/artifactId&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;packaging&gt;jar&lt;/packaging&gt;  

  &lt;name&gt;test01&lt;/name&gt;  
  &lt;url&gt;http://maven.apache.org&lt;/url&gt;  

  &lt;properties&gt;    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;  
  &lt;/properties&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;junit&lt;/groupId&gt;  
      &lt;artifactId&gt;junit&lt;/artifactId&gt;  
      &lt;version&gt;3.8.1&lt;/version&gt;  
      &lt;scope&gt;test&lt;/scope&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;mysql&lt;/groupId&gt;  
      &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
      &lt;version&gt;8.0.28&lt;/version&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.mybatis&lt;/groupId&gt;  
      &lt;artifactId&gt;mybatis&lt;/artifactId&gt;  
      &lt;version&gt;3.5.6&lt;/version&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;&lt;/project&gt;
</code></pre>
<ol>
 <li>创建MyBatis全局配置文件</li>
</ol>
<pre><code class="prism language-java">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE configuration  
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
        "http://mybatis.org/dtd/mybatis-3-config.dtd"&gt;  
&lt;configuration&gt;  
    &lt;environments default="mysql"&gt;  
        &lt;environment id="mysql"&gt;  
            &lt;transactionManager type="JDBC"/&gt;  
            &lt;dataSource type="POOLED"&gt;  
                &lt;property name="driver" value="com.mysql.cj.jdbc.Driver"/&gt;  
                &lt;property name="url" value="jdbc:mysql://127.0.0.1:3306/book?useSSL=false"/&gt;  
                &lt;property name="username" value="yourusername"/&gt;  
                &lt;property name="password" value="yourpassword"/&gt;  
            &lt;/dataSource&gt;        &lt;/environment&gt;    &lt;/environments&gt;    &lt;mappers&gt;        &lt;mapper resource="mapper/BookMapper.xml"&gt;&lt;/mapper&gt; 
    &lt;/mappers&gt;&lt;/configuration&gt;
</code></pre>
<ol>
 <li>创建实体类</li>
</ol>
<pre><code class="prism language-java">//zhai.test.book
package zhai.test.book;  

public class Book {

        private int id;  
        private String title;  

    public int getId() {

        return id;  
    }  

    public void setId(int id) {

        this.id = id;  
    }  

    public String getTitle() {

        return title;  
    }  

    public void setTitle(String title) {

        this.title = title;  
    }  

    public Book() {

    }  

    public Book(int id, String title) {

        this.id = id;  
        this.title = title;  
    }  
}
</code></pre>
<ol>
 <li>创建映射文件</li>
</ol>
<pre><code class="prism language-java">//BookMapper.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE mapper  
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;  
&lt;mapper namespace="a.b"&gt;  
    &lt;select id="selectAllBooks" resultType="zhai.test.book.Book"&gt;  
         select * from book  
    &lt;/select&gt;  
&lt;/mapper&gt;
</code></pre>
<ol>
 <li>编写测试类，启动项目</li>
</ol>
<pre><code class="prism language-java">package com.zhai.test;  

import org.apache.ibatis.io.Resources;  
import org.apache.ibatis.session.SqlSession;  
import org.apache.ibatis.session.SqlSessionFactory;  
import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
import zhai.test.book.Book;  

import java.io.IOException;  
import java.io.InputStream;  
import java.util.List;  

public class test {

    public static void main(String[] args) throws IOException {

        //制定myBatis核心配置文件路径  
        String resource = "mybatis.xml";  
        //获取加载配置文件的输入流  
        InputStream inputStream = Resources.getResourceAsStream(resource);  

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  

        SqlSession sqlSession = sqlSessionFactory.openSession();  

        List list = sqlSession.selectList("a.b.selectAllBooks");  

        for(int i= 0; i&lt; list.size(); i++){

          Book b =(Book)list.get(i);  
            System.out.println(b.getTitle()+ "---" + b.getId());  
        }  
        sqlSession.close();  
    }  
}
</code></pre>
<h5>别名设置</h5>
<p>MyBatis提供了别名机制可以对某个类起别名或给某个包下所有类起别名，简化resultType取值的写法。
 <br>
 在核心配置文件中，通过<typealias>标签明确设置类型的别名。</typealias></p>
<p>type：类型全限定路径</p>
<p>alias:别名名称</p>
<p>当类数较多时，明确指定别名工作量较可以通过<package>标签指定包下全部类的别名。指定后所有类的别名就是类名.(也不区分大小写）</package></p>
<pre><code class="prism language-xml">//mybatis.xml
&lt;typeAliases&gt;  
    &lt;package name="zhai.test.book"&gt;&lt;/package&gt;  
&lt;/typeAliases&gt;
//BookMapper.xml
&lt;mapper namespace="a.b"&gt;  
    &lt;select id="selectAllBooks" resultType="Book"&gt;  
         select * from book  
    &lt;/select&gt;  
&lt;/mapper&gt;
</code></pre>
<p>![[Pasted image 20240927104012.png]]</p>
<h5>属性文件配置</h5>
<pre><code class="prism language-java">//db.properties
driver = com.mysql.cj.jdbc.Driver  
url = jdbc:mysql://127.0.0.1:3306/book?useSSL=false  
name = root  
password = zhai2665323601
</code></pre>
<pre><code>```prism language-xml
//mybatis.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE configuration  
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
        "http://mybatis.org/dtd/mybatis-3-config.dtd"&gt;  
&lt;configuration&gt;  
    &lt;properties resource="db.properties"&gt;&lt;/properties&gt;  
    &lt;typeAliases&gt;        &lt;package name="zhai.test.book"&gt;&lt;/package&gt;  
    &lt;/typeAliases&gt;    &lt;environments default="mysql"&gt;  

        &lt;environment id="mysql"&gt;  

            &lt;transactionManager type="JDBC"/&gt;  

            &lt;dataSource type="POOLED"&gt;  
                &lt;property name="driver" value="${driver}"/&gt;  
                &lt;property name="url" value="${url}"/&gt;  
                &lt;property name="username" value="${name}"/&gt;  
                &lt;property name="password" value="${password}"/&gt;  
            &lt;/dataSource&gt;        &lt;/environment&gt;    &lt;/environments&gt;    &lt;mappers&gt;        &lt;mapper resource="mapper/BookMapper.xml"&gt;&lt;/mapper&gt;  
    &lt;/mappers&gt;&lt;/configuration&gt;
</code></pre>
<h5>MyBatis启动日志功能</h5>
<p>MyBatis框架内置日志工厂。日志工厂负责自动加载项目中配置的日志。MyBatis支持以下日志：</p>
<ol>
 <li>SLF4J</li>
 <li>Apache Commons Logging</li>
 <li>Log4j2</li>
 <li>Log4j（deprecated since3.5.9）</li>
 <li>JDK logging
  <br>
  这里我们使用Log4j</li>
</ol>
<pre><code class="prism language-xml">//添加依赖
&lt;dependency&gt;
    &lt;groupId&gt;log4j&lt;/groupId&gt;
    &lt;artifactId&gt;log4j&lt;/artifactId&gt;
    &lt;version&gt;1.2.17&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>在resources中新建log4j.properties配置文件。名称必须叫这个名字，扩展名必须是.properties</p>
<pre><code class="prism language-java">log4j.rootLogger = error,console  

log4j.logger.a.b = TRACE  
### console ###  
log4j.appender.console=org.apache.log4j.ConsoleAppender  
log4j.appender.console.target = System.out  
log4j.appender.console.layout=org.apache.log4j.PatternLayout  
log4j.appender.console.layout.ConversionPattern=%d{
   ISO8601} [%t] %-5p %c %x - %m%n
</code></pre>
<p>![[Pasted image 20240927201828.png]]</p>
<h5>MyBatis参数传递问题</h5>
<p>使用符号：#{}进行获取中名字使用规则
 <br>
 arg、arg1、argM(M为从0开始的数字，和方法参数顺序对应)</p>
<p>param1、param2、paramN(N为从1开始的数字，和方法参数顺序对应)</p>
<h6>如果多个参数且参数有对象，获取参数</h6>
<p>使用符号：进行获取
 <br>
 argM.属性名</p>
<p>paramN.属性名
 <br>
 PS：argM.或者paramN.不可以省略不写</p>
<pre><code class="prism language-xml">&lt;mapper namespace="com.example.UserMapper"&gt;  
&lt;insert id="insertUser"&gt;  
    INSERT INTO users (username, age)  
    VALUES (#{arg0.username}, #{arg0.age})  
&lt;/insert&gt;  

&lt;insert id="insertUserWithParam"&gt;  
    INSERT INTO users (username, age)  
    VALUES (#{param1.username}, #{param1.age})  
&lt;/insert&gt;  
&lt;/mapper&gt;
</code></pre>
<h4>Spring</h4>
<h5>SpringIoCDI介绍</h5>
<p>loc（inversionofControl）中文名称：控制反转，也被称为DIgependencyinjection）：依赖注入。注意：属于同一件事情的两个名称</p>
<p>创建对象的权利或者是控制的位置，由JAVA代码转移到spring容器，由spring的容器控制对象的创建，就是控制反转</p>
<p>由于控制反转概念比较含糊，所以在2004年大师级人物MartinFowler又给出了一个新的名字：“依赖注入”，相对loC而言，“依赖注入"明确描述了"被注入对象依赖IoC容器来配置依赖对象”，DI英文全称为DependencyInjection，中文译名为依赖注入）是loc的别名。</p>
<h5>Spring</h5>
<ol>
 <li>对比以往项目，Spring优势有哪些：方便解耦，简化开发；AOP切面编程；声明式事务；整合各种优秀的框架</li>
 <li>不重复造轮子</li>
 <li>使用Spring所需jar包</li>
 <li>spring官网：去spring官网找：https://spring.io/</li>
</ol>
<h5>相关图例</h5>
<p>![[Pasted image 20240927222911.png]]</p>
<p>![[Pasted image 20240927224331.png]]</p>
<h5>第一个spring项目 完成IoC/DI代码实现</h5>
<p>创建maven项目 添加依赖
 <br>
 Spring项目想要运行起来必须包含
 <br>
 spring-context jar它依赖了下面的四个jar。
 <br>
 spring-core jar-它依赖了spring-jcl jar</p>
<p>spring-aop jar</p>
<p>spring-expression jar</p>
<p>spring-beans jar</p>
<p>spring-jcl jar</p>
<p>所以在Maven中想要使用Spring框架只需要在项目中导入spring-context就可以了，其他的jar包根据Maven依赖传递性都可以导入进来</p>
<pre><code class="prism language-xml">  &lt;dependency&gt;  
    &lt;groupId&gt;org.springframework&lt;/groupId&gt;  
    &lt;artifactId&gt;spring-context&lt;/artifactId&gt;  
    &lt;version&gt;5.3.23&lt;/version&gt;  
  &lt;/dependency&gt;&lt;/dependencies&gt;
</code></pre>
<p>实现一个实体类Book之后代码实现</p>
<pre><code class="prism language-java">// resources/applicationContext.xml
&lt;?xml version="1.0" encoding="UTF-8"?&gt;  
&lt;beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
        https://www.springframework.org/schema/beans/spring-beans.xsd"&gt;  
    &lt;bean id="b" class="org.example.Book"&gt;  &lt;/bean&gt;
    &lt;/beans&gt;  
//test.java
package org.example;  

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  

public class test {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
        Book b = (Book)context.getBean("b");  
        Book b1 = (Book)context.getBean("b1");  
        System.out.println(b.getId()+"---"+b.getTitle());  
        System.out.println(b1.getId()+"---"+b1.getTitle());  

    }  
}
</code></pre>
<h5>属性注入方式</h5>
<h6>设值注入</h6>
<p>相关代码实现</p>
<pre><code class="prism language-xml">// resources/applicationContext.xml
&lt;bean id="b" class="org.example.Book"&gt;  
    &lt;!-- collaborators and configuration for this bean go here --&gt;  
    &lt;property name="id" value="1"&gt;&lt;/property&gt;  
    &lt;property name="title" value="三国"&gt;&lt;/property&gt;  
&lt;/bean&gt;
</code></pre>
<h6>构造注入</h6>
<p>相关代码实现</p>
<pre><code class="prism language-xml">//resources/applicationContext.xml
&lt;bean id="b1" class="org.example.Book"&gt;  
    &lt;constructor-arg name="id" value="2"&gt;&lt;/constructor-arg&gt;  
    &lt;constructor-arg name="title" value="水浒"&gt;&lt;/constructor-arg&gt;  
&lt;/bean&gt;
</code></pre>
<h6>属性为复杂引用类型</h6>
<pre><code class="prism language-java">&lt;bean id="b" class="org.example.Boy"&gt;  
    &lt;property name="age" value="18"&gt;&lt;/property&gt;  
    &lt;property name="name" value="芜湖"&gt;&lt;/property&gt;  
&lt;/bean&gt;  
&lt;bean id="g" class="org.example.Girl"&gt;  
    &lt;property name="age" value="18"&gt;&lt;/property&gt;  
    &lt;property name="name" value="小红"&gt;&lt;/property&gt;  
    &lt;property name="boyfriend" ref="b"&gt;&lt;/property&gt;  
&lt;/bean&gt;
</code></pre>
<p>设置属性的值：
 <br>
 方式1：value：简单数据类型（基本数据类型+String）直接设置</p>
<p>方式2：ref:需要引I用另一个bean的id。也就是说这个参数是一个类类型，且这个类的对象也被Spring容器管理。</p>
<h5>IoC/DI相关注解</h5>
<p>![[Pasted image 20240928093846.png]]
 <br>
 @Component的使用
 <br>
 在要创建对象的类中加入@Component注解，对象名字默认为：类名首字母变小写
 <br>
 注解在哪个包下？要想找到这些注解，需要将注解所在的包进行扫描：设置需要扫描的包。并且需要在applicationContext.xml中添加context命名空间</p>
<p>前五个注解作用一样，只所以搞出这么多，就是在语义上给你区别，放入不同层用不同的注解，但是作用都是创建对象</p>
<pre><code class="prism language-xml">//
&lt;?xml version="1.0" encoding="UTF-8"?&gt;  
&lt;beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
        https://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context        https://www.springframework.org/schema/context/spring-context.xsd        "&gt;  
    &lt;context:component-scan base-package="org.example"&gt;&lt;/context:component-scan&gt;  
&lt;/beans&gt;
</code></pre>
<p>使用</p>
<pre><code class="prism language-java">//book.java
package org.example;  

import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  

@Component  
public class Book {

    @Value("1")  
    private int id;  
    @Value("三国")  
    private String title;  

    public Book() {

        System.out.println("空构造器");  
    }  

    public Book(int id, String title) {

        System.out.println("有掺构造器");  
        this.id = id;  
        this.title = title;  
    }  

    public int getId() {

        return id;  
    }  

    public void setId(int id) {

        System.out.println("setid");  
        this.id = id;  
    }  

    public String getTitle() {

        return title;  
    }  

    public void setTitle(String title) {

        System.out.println("settitile");  
        this.title = title;  
    }  
}
//test.java
package org.example;  

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  

public class test {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
        Book b = (Book)context.getBean("book");  
        System.out.println(b.getId()+"---"+b.getTitle());  

    }  
}
</code></pre>
<h6>@Value的使用</h6>
<p>给普通数据类型赋值的注解， 普通数据类型包括：八种基本数据类型+String</p>
<p>@Autowired的使用
 <br>
 添加@Autowired注解后会把容器中的对象自动注入进来，并且不需要依赖set方法。</p>
<pre><code class="prism language-java">@Autowired  
private Boy boyfriend;
</code></pre>
<h4>web项目构建</h4>
<h5>java项目和web项目的区别</h5>
<p>Java项目是由main方法来开始的，直接依赖JVM就能被编译执行。Java项目不需要服务器。</p>
<p>Web项目中的Java文件是tomcat服务器来触发的，脱离了web服务器就无法启动。Web项目需要服务器。Web项目部署到服务器上，任何用户都可以通过浏览器来访问。将本地资源共享给外部访问。</p>
<h5>使用服务器</h5>
<p>Tomcat服务器对Servlet，Jsp，JNDI，JavaMail有很好的的支持，并且这个Web容器是开源免费的。(Apache开源免费)</p>
<h5>创建maven webapp项目</h5>
<p>在idea中使用tomcat 插件</p>
<pre><code class="prism language-xml">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  
  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;javaweb01&lt;/artifactId&gt;  
  &lt;packaging&gt;war&lt;/packaging&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;build&gt;    &lt;plugins&gt;      &lt;plugin&gt;        &lt;groupId&gt;org.apache.tomcat.maven&lt;/groupId&gt;  
        &lt;artifactId&gt;tomcat7-maven-plugin&lt;/artifactId&gt;  
        &lt;version&gt;2.2&lt;/version&gt;  
        &lt;configuration&gt;          &lt;path&gt;/javaweb01&lt;/path&gt;  
          &lt;port&gt;8080&lt;/port&gt;  
        &lt;/configuration&gt;      &lt;/plugin&gt;    &lt;/plugins&gt;  &lt;/build&gt;&lt;/project&gt;
</code></pre>
<h4>SpringMVC</h4>
<p>Spring MVC 是一种基于 Java 的 Web 框架，是 Spring Framework 的一部分，用于构建现代化的企业级应用程序。它采用了模型-视图-控制器 (MVC) 设计模式，帮助开发者将应用程序的不同关注点分离，使得代码更加模块化和可维护。
 <br>
 <strong>处理 HTTP 请求</strong> ：接收和处理来自客户端的 HTTP 请求，分发请求到相应的控制器 也就是获取前端传过来的数据 响应内容到前端</p>
<p><strong>分离关注点</strong>：通过模型-视图-控制器 (MVC) 设计模式，将应用程序的业务逻辑、用户界面和输入处理分开，提高代码的可维护性和可测试性。</p>
<h5>相关实现</h5>
<ol>
 <li>创建maven-web项目</li>
 <li>补全目录
  <br>
  ![[Pasted image 20240928154421.png]]</li>
 <li>添加依赖</li>
 <li>加入tomcat插件</li>
</ol>
<pre><code class="prism language-xml">pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  
  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;javaweb01&lt;/artifactId&gt;  
  &lt;packaging&gt;war&lt;/packaging&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework&lt;/groupId&gt;  
      &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt;  
      &lt;version&gt;5.3.16&lt;/version&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;  &lt;build&gt;    &lt;plugins&gt;      &lt;plugin&gt;        &lt;groupId&gt;org.apache.tomcat.maven&lt;/groupId&gt;  
        &lt;artifactId&gt;tomcat7-maven-plugin&lt;/artifactId&gt;  
        &lt;version&gt;2.2&lt;/version&gt;  
        &lt;configuration&gt;          &lt;path&gt;/javaweb01&lt;/path&gt;  
          &lt;port&gt;8080&lt;/port&gt;  
        &lt;/configuration&gt;      &lt;/plugin&gt;    &lt;/plugins&gt;  &lt;/build&gt;&lt;/project&gt;
</code></pre>
<ol>
 <li>编写web.xml内容
  <br>
  这段配置文件用于设置 Spring MVC 的 DispatcherServlet，使其能够接收和处理来自客户端的 HTTP 请求，并通过指定的 Spring 配置文件进行初始化。这是实现基于 Spring 的 MVC 架构的基础配置。</li>
</ol>
<pre><code class="prism language-xml">//web.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"  
         version="4.0"&gt;  
&lt;!-- 这里可以添加 servlet、filter 等其他配置 --&gt;  
    &lt;servlet&gt;  
        &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;  
        &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;  
        &lt;init-param&gt;            &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;  
            &lt;param-value&gt;classpath:springmvc.xml&lt;/param-value&gt;  
        &lt;/init-param&gt;        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;  
    &lt;/servlet&gt;    &lt;servlet-mapping&gt;        &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;  
        &lt;url-pattern&gt;/&lt;/url-pattern&gt;  
    &lt;/servlet-mapping&gt;&lt;/web-app&gt;
</code></pre>
<ol>
 <li>搭配注解环境
  <br>
  这段配置文件的主要作用是设定一个 Spring MVC 应用的基础环境，包括定义组件扫描的路径和启用注解驱动的 MVC 功能。它使得开发者可以通过注解来简化控制器和服务的配置，提高开发效率</li>
</ol>
<pre><code class="prism language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;  
&lt;beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:mvc="http://www.springframework.org/schema/mvc"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
        https://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context        https://www.springframework.org/schema/context/spring-context.xsd        http://www.springframework.org/schema/mvc        https://www.springframework.org/schema/mvc/spring-mvc.xsd        "&gt;  
    &lt;context:component-scan base-package="com.zhai.controller"&gt;&lt;/context:component-scan&gt;  
    &lt;mvc:annotation-driven&gt;&lt;/mvc:annotation-driven&gt;  
&lt;/beans&gt;
</code></pre>
<ol>
 <li>运行tomcat</li>
</ol>
<pre><code class="prism language-java">package com.zhai.controller;  

import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  

@Controller  
public class testController {

    @RequestMapping("/test1")  
    public String test(){

        return "index.jsp";  
    }  
}
</code></pre>
<h5>获取普通参数</h5>
<pre><code class="prism language-java">@RequestMapping("/testParam")  
public String testParam(String name, int age){

    System.out.println(name +"----" + age);  
    return "index.jsp";  
}
</code></pre>
<h5>使用类对象作为控制单元参数</h5>
<p>JavaBean：一个包含私有属性，getter/setter方法和无参构造方法的Java类。，是不是感觉和实体类特别像。其实写法上和实体类相同。唯一区别是实体类是数据库层面的概念，类型中属性要和数据库字段对应。而JavaBean的属性是灵活的，不是必须和哪里对应的。
 <br>
 JavaBean是一个专业概念，可以简单点理解：使用类对象做为控制单元参数，接收请求参数。如果不是特别较真，狭义上可以认为JavaBean就是项目中的实体类
 <br>
 在控制单元中放置一个类型对象，对象名称没有要求，只需要保证请求参数名和类的属性名相同就可以了。</p>
<pre><code class="prism language-java">   @RequestMapping("/testParam2")  
    public String testParam2(person p){

        System.out.println(p.getname() +"----" + p.getage());  
        return "index.jsp";  
    }  
}
</code></pre>
<h4>SSM介绍</h4>
<p>SSM:<strong>Spring + Spring MVC + MyBatis</strong>：一种在Java开发中常用的框架组合。</p>
<h4>SpringBoot</h4>
<h5>springboot概念简介</h5>
<p>SpringBoot是Spring公司的一个顶级项目，和SpringFramework是一个级别的。</p>
<p>SpringBoot实际上是利用SpringFramework4自动配置特性完成。编写项目时不需要编写xml文件。发展到现在，SpringBoot已经县有很很大的生态圈，各种主流技术已经都提供了SpringBoot的启动器。</p>
<h5>springBoot优点</h5>
<p><code>约定大于配置</code></p>
<h5>例子:整合SpringMVC</h5>
<ol>
 <li>创建maven工程</li>
 <li>选择springboot的版本添加到pom.xml</li>
</ol>
<pre><code class="prism language-xml">&lt;dependencyManagement&gt;  
  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
      &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;  
      &lt;version&gt;2.4.5&lt;/version&gt;  
      &lt;type&gt;pom&lt;/type&gt;  
      &lt;scope&gt;import&lt;/scope&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;&lt;/dependencyManagement&gt;
</code></pre>
<ol>
 <li>
  <p>整合springmvc用到的包,添加启动器
   <br>
   什么是启动器</p>
  <p>Spring框架在项目中作用是Spring整合各种其他技术，让其他技术使用更加方便。SpringBoot的启动器实际上就是一个依赖。这个依赖中包含了整个这个技术的相关jar包，还包含了这个技术的自动配置，以前绝大多数XML配置都不需要配置了。以后每次使用SpringBoot整合其他技术时首先需要考虑导入启动器。</p>
  <p>什么是启动类</p>
  <p>SpringBoot的启动类的作用是启动SpringBoot项目，是基于Main方法来运行的</p>
  <p>启动类与启动器区别
   <br>
   启动类表示项目的启动入口</p>
  <p>启动器表示jar包的坐标</p>
 </li>
</ol>
<pre><code class="prism language-xml">&lt;dependencies&gt;  
  &lt;dependency&gt;    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;  
  &lt;/dependency&gt;&lt;/dependencies&gt;
</code></pre>
<ol>
 <li>开发一个Controller控制层</li>
</ol>
<pre><code class="prism language-java">package com.zhai.controller;  

import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.ResponseBody;  

@Controller  
public class MyController {

    @RequestMapping("/test01")  
    @ResponseBody  
    public String test01(){

        return "hi,springboot...";  
    }  
}
</code></pre>
<ol>
 <li>启动类编写</li>
</ol>
<pre><code class="prism language-java">package com.zhai.controller;  

import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  

@SpringBootApplication  
public class testSpringBootApplication {

    public static void main(String[] args) {

        SpringApplication.run(testSpringBootApplication.class,args);  
    }  
}
</code></pre>
<ol>
 <li>运行，游览器访问测试
  <br>
  ![[Pasted image 20240928185546.png]]</li>
</ol>
<h5>springBoot配置文件</h5>
<p>在resources中创建文件application.properties编写内容</p>
<pre><code class="prism language-java">server.port = 8080  
server.servlet.context-path=/springboot01
</code></pre>
<p>官方推荐的配置文件
 <br>
 springboot官方推荐的配置文件是yml文件，yml是用层级来表示关系的一种配置文件。yml中没有标签，而是通过两个空格的缩进来表示层级结构</p>
<p>例如：
 <br>
 properties中：
 <br>
 server.port=8080
 <br>
 yml中：
 <br>
 Server:
 <br>
 port:8080
 <br>
 层级结构怎么找（SpringBoot常见配置，查看官网文档）
 <br>
 https://docs.spring.io/spring-boot/docs/2.7.6/reference/html/application-properties.html#appendix.application-properties
 <br>
 注意：文件名字为：<strong>application</strong> .yml，文件名字application开头，不能随意动</p>
<p>注意冒号后空格</p>
<pre><code class="prism language-yml">server:  
  port: 9999  
  servlet:  
    context-path: /test02
</code></pre>
<h5>SpringBoot整合SSM</h5>
<ol>
 <li>导入依赖</li>
</ol>
<pre><code class="prism language-xml">//pom.xml
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  
  &lt;groupId&gt;org.example&lt;/groupId&gt;  
  &lt;artifactId&gt;onessm&lt;/artifactId&gt;  
  &lt;packaging&gt;war&lt;/packaging&gt;  
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;  
  &lt;dependencyManagement&gt;    &lt;dependencies&gt;      &lt;dependency&gt;        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
        &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;  
        &lt;version&gt;2.4.5&lt;/version&gt;  
        &lt;type&gt;pom&lt;/type&gt;  
        &lt;scope&gt;import&lt;/scope&gt;  
      &lt;/dependency&gt;    &lt;/dependencies&gt;  &lt;/dependencyManagement&gt;  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;  
      &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;  
    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;  
      &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;  
      &lt;version&gt;2.1.3&lt;/version&gt; &lt;!-- 替换为你需要的版本 --&gt;  
    &lt;/dependency&gt;  
    &lt;dependency&gt;      &lt;groupId&gt;mysql&lt;/groupId&gt;  
      &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;  
    &lt;/dependency&gt;  &lt;/dependencies&gt;  
&lt;/project&gt;
</code></pre>
<ol>
 <li>编写配置文件</li>
</ol>
<pre><code class="prism language-java">server:  
  port: 9999  
  servlet:  
    context-path: /test02  
spring:  
  datasource:  
    url: jdbc:mysql://127.0.0.1:3306/book?useSSL=false  
    driver-class-name: com.mysql.cj.jdbc.Driver  
    username: root  
    password: yourpassword
</code></pre>
<ol>
 <li>编写实体类，配置文件中加入别名
  <br>
  ![[Pasted image 20240928195008.png]]</li>
 <li>定义mapper接口和定义mapper.xml映射文件</li>
</ol>
<pre><code class="prism language-java">    package com.zhai.mapper;  

import java.util.List;  

public interface BookMapper {

    List selectAllBooks();  
}
// BookMapper.xml
&lt;?xml version="1.0" encoding="UTF-8" ?&gt;  
&lt;!DOCTYPE mapper  
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;  
&lt;mapper namespace="com.zhai.mapper.BookMapper"&gt;  
    &lt;select id="selectAllBooks" resultType="book"&gt;  
        select * from book  
    &lt;/select&gt;  
&lt;/mapper&gt;
</code></pre>
<ol>
 <li>定义启动类在启动类加入mapper的包扫描</li>
</ol>
<pre><code class="prism language-java">package com.zhai;  

import org.mybatis.spring.annotation.MapperScan;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  

@SpringBootApplication  
@MapperScan("com.zhai.mapper")  
public class testSpringBootApplication {

    public static void main(String[] args) {

        SpringApplication.run(testSpringBootApplication.class,args);  
    }  
}
</code></pre>
<h4>最终效果实现</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3ade43caba0f4734b7f533c68d50aefe.png&amp;size=m" alt=""></p>
<blockquote>
 <p>如果对你有用的话 就点个关注吧 会持续更新技术文章</p>
</blockquote>
<p>原文链接: https://blog.csdn.net/weixin_63625059/article/details/142623916</p>]]></description><guid isPermaLink="false">/archives/c191c871-8e7b-4c02-b6d8-2ad8b3db2693</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:36:00 GMT</pubDate></item><item><title><![CDATA[三篇文章速通JavaSE到SpringBoot框架 (中) IO 进程线程 网络编程 XML MySQL JDBC相关概念与演示代码]]></title><link>https://wiki.dgfygk.com/archives/753a1a4f-1cfd-4027-8a16-a9a5bb0db6ca</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%89%E7%AF%87%E6%96%87%E7%AB%A0%E9%80%9F%E9%80%9AJavaSE%E5%88%B0SpringBoot%E6%A1%86%E6%9E%B6%20%28%E4%B8%AD%29%20IO%20%E8%BF%9B%E7%A8%8B%E7%BA%BF%E7%A8%8B%20%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%20XML%20MySQL%20JDBC%E7%9B%B8%E5%85%B3%E6%A6%82%E5%BF%B5%E4%B8%8E%E6%BC%94%E7%A4%BA%E4%BB%A3%E7%A0%81&amp;url=/archives/753a1a4f-1cfd-4027-8a16-a9a5bb0db6ca" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*
 <br>
 *</p>
<pre><code>* [IO](#IO_1)
*
  * [file类的作用](#file_3)
  * [I/O的作用](#IO_6)
  * [将上篇文章综合项目使用IO流升级](#IO_52)
  *
    * [所需知识点](#_54)
* [进程 线程](#__164)
*
  * [创建线程的三种方式](#_171)
* [网络编程](#_229)
*
  * [网络编程介绍](#_230)
  * [IP地址](#IP_232)
  * [端口号](#_234)
  * [网络通信协议](#_236)
  * [网络通信协议的分层](#_239)
  * [演示代码](#_248)
* [XML](#XML_316)
*
  * [XML的作用是什么？](#XML_320)
  * [xml特点](#xml_322)
* [注解](#_337)
* [什么是注解？](#_338)
*
  * [注解的使用](#_340)
  * [注解的重要性](#_342)
  * [注解的使用实例](#_344)
* [MySQL](#MySQL_350)
*
  * [代码实现](#_360)
* [JDBC](#JDBC_393)
*
  * [JDBC访问数据库编码步骤](#JDBC_395)
  * [准备资源](#_402)
* [图书管理数据库版](#_423)
</code></pre>
<p>
 <br>
</p>
<h4>IO</h4>
<p>Java IO流是Java中处理输入和输出的一个重要机制。它提供了一系列的类和接口来处理数据的读写，包括文件、网络等。Java IO可以分为两大类：字节流和字符流。</p>
<h5>file类的作用</h5>
<p>File类对象可封装要操作的文件，可通过File类的对象对文件进行操作，如查看文件的大小、判断文件是否隐藏、判断文件是否可读等</p>
<p>局限：File类的相关操作，并不涉及文件内容相关的操作，这是单独依靠File类对象无法实现的操作，此时需要借助I/O流完成</p>
<h5>I/O的作用</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F78c21f53659f470baa8c6542dd363263.png&amp;size=m" alt="">#### 功能划分
 <br>
 方式1：按照方向划分输入流、输出流。</p>
<p>方式2：按照处理单元划分字节流、字符流。</p>
<p>方式3：按照功能划分。节点流、处理流。</p>
<p>想关代码操作<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb779f8962cd2407fb9a5e7a59c842b12.png&amp;size=m" alt=""></p>
<p>文件对程序进行输入操作</p>
<pre><code class="prism language-java">//文件对程序进行输入操作
import java.io.File;  
import java.io.FileReader;  
import java.io.IOException;  

public class iotest {

    public static void main(String[] args) throws IOException {

        //对文件进行操作，必须将文件封装为File对象  
        File f = new File("d:\\test.txt");  
        //"管子"=流=输入字符流 将管子怼到文件上-管子和文件结合  
        FileReader fr = new FileReader(f);  
        //开始动作吸 一口 利用循环吸完  
        int n = fr.read();  
        while(n != 1){

             n = fr.read();  
            System.out.println(n);  
        }  
        //关闭操作  
        fr.close();  
    }  
}
</code></pre>
<p>程序对文件进行输出操作</p>
<pre><code class="prism language-java">public class iotest02 {

    public static void main(String[] args) throws IOException {

        String str = "abc";  
        File f= new File("d:\\demo.txt");  
        FileWriter fw = new FileWriter(f);  
        fw.write(str);  

        fw.close();  
    }  

}
</code></pre>
<h5>将上篇文章综合项目使用IO流升级</h5>
<p>图书管理有个缺点就是 数据不能永久保存</p>
<h6>所需知识点</h6>
<ol>
 <li>对象流：
  <br>
  FilelnputStream、 FileOutputStream
  <br>
  ObjectinputStreamObjectoutputStream</li>
 <li>序列化：
  <br>
  implements Serializable
  <br>
  实现了Serializable接口，才会具备将对象输出到文件的能力</li>
</ol>
<pre><code class="prism language-java">// Book.java
//public class Book  implements Serializable 
J//test01.java
public class test01 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 开始程序主循环，提供用户交互界面  
        while(true) {

            // 打印主菜单  
            System.out.println("----图书管理----");  
            System.out.println("1.展示书籍");  
            System.out.println("2.添加书籍");  
            System.out.println("3.删除书籍");  
            System.out.println("4.退出应用");  

            // 创建 Scanner 对象以获取用户输入  
            Scanner sc = new Scanner(System.in);  
            System.out.println("请输入功能号码");  
            int choice = sc.nextInt(); // 读取用户选择的功能  

            // 根据用户选择的功能执行相应操作  
            if (choice == 1) {
    // 展示书籍  
                System.out.println("--图书展示区域--");  
                File f = new File("demo.txt"); // 创建文件对象，指向 demo.txt                FileInputStream fis = new FileInputStream(f); // 创建文件输入流  
                ObjectInputStream ois = new ObjectInputStream(fis); // 创建对象输入流  
                ArrayList list = (ArrayList)(ois.readObject()); // 读取对象并转换为 ArrayList  
                // 遍历书籍列表并打印每本书的编号和名称  
                for (int i = 0; i &lt; list.size(); i++) {

                    Book b = (Book)(list.get(i));  
                    System.out.println(b.getbNo() + "---" +b.getBName());  
                }  
                ois.close(); // 关闭流以释放资源  

            } else if (choice == 2) {
    // 添加书籍  
                // 提示用户输入书籍信息  
                System.out.println("请输入书籍编号");  
                int bNo =sc.nextInt(); // 读取书籍编号  
                System.out.println("请输入书籍名字");  
                String bName = sc.next(); // 读取书籍名称  

                // 创建新的书籍对象  
                Book b = new Book();  
                b.setbNo(bNo); // 设置书籍编号  
                b.setBName(bName); // 设置书籍名称  

                // 获取文件对象  
                File f = new File("demo.txt");  
                if(f.exists()) {
    // 如果文件存在  
                    // 防止覆盖已有内容，先读取现有书籍  
                    FileInputStream fis  = new FileInputStream(f);  
                    ObjectInputStream ois = new ObjectInputStream(fis);  
                    ArrayList list = (ArrayList)(ois.readObject()); // 读取现有书籍列表  

                    // 将新书籍添加到列表中  
                    list.add(b);  

                    // 将更新后的列表写回文件  
                    FileOutputStream fos = new FileOutputStream(f);  
                    ObjectOutputStream oos = new ObjectOutputStream(fos);  
                    oos.writeObject(list); // 写入更新后的列表  
                    oos.close(); // 关闭流  

                } else {
    // 如果文件不存在  
                    ArrayList list = new ArrayList(); // 创建新的书籍列表  
                    list.add(b); // 添加新书籍  
                    FileOutputStream fos = new FileOutputStream(f);  
                    ObjectOutputStream oos = new ObjectOutputStream(fos);  
                    oos.writeObject(list); // 写入新书籍列表  
                    oos.close(); // 关闭流  
                }  

            } else if (choice == 3) {
    // 删除书籍  
                System.out.println("请输入删除书籍编号");  
                File f = new File("demo.txt"); // 获取文件对象  
                FileInputStream fis = new FileInputStream(f);  
                ObjectInputStream ois = new ObjectInputStream(fis);  
                ArrayList list = (ArrayList)(ois.readObject()); // 读取现有书籍列表  

                int delbNo = sc.nextInt(); // 读取要删除的书籍编号  

                // 遍历书籍列表查找要删除的书籍  
                for (int i = 0; i &lt;list.size() ; i++) {

                    Book b =  (Book)(list.get(i));  
                    if(b.getbNo() == delbNo) {
    // 如果找到匹配的书籍编号  
                        list.remove(b); // 从列表中删除书籍  
                        System.out.println("删除成功"); // 提示用户删除成功  
                        break; // 退出循环  
                    }  
                }  

                // 将更新后的列表写回文件  
                FileOutputStream fos = new FileOutputStream(f);  
                ObjectOutputStream oos = new ObjectOutputStream(fos);  
                oos.writeObject(list); // 写入更新后的列表  
                oos.close(); // 关闭流  

            } else {
    // 如果用户选择退出  
                break; // 结束循环，退出程序  
            }  
        }  
    }  
}
</code></pre>
<h4>进程 线程</h4>
<p>程序:是为完成特定任务、用某种语言编写的一组指令的集合，是一段静态的代码。
 <br>
 (程序是静态的)</p>
<p>进程:是程序的一次执行过程。正在运行的一个程序，进程作为资源分配的单位，在内存中会为每个进程分配不同的内存区域。</p>
<p>线程:进程可进一步细化为线程，是一个进程内部的一条执行路径。若一个进程同一时间并行执行多个线程，就是支持多线程的</p>
<p><strong>进程是操作系统进行资源分配的基本单位</strong></p>
<p><strong>线程是操作系统调度执行的基本单位</strong></p>
<h5>创建线程的三种方式</h5>
<ol>
 <li>方式1：继承Thread类</li>
</ol>
<pre><code class="prism language-java">class MyThread extends Thread {

    @Override  
    public void run() {

        System.out.println("Thread running: " + Thread.currentThread().getName());  
    }  
}  

public class ThreadExample {

    public static void main(String[] args) {

        MyThread thread1 = new MyThread();  
        thread1.start(); // 启动线程  
    }  
}
</code></pre>
<ol>
 <li>方式2： 实现Runnable接口</li>
</ol>
<pre><code class="prism language-java">class MyRunnable implements Runnable {

    @Override  
    public void run() {

        System.out.println("Runnable running: " + Thread.currentThread().getName());  
    }  
}  

public class RunnableExample {

    public static void main(String[] args) {

        Thread thread2 = new Thread(new MyRunnable());  
        thread2.start(); // 启动线程  
    }  
}
</code></pre>
<ol>
 <li>方式3：实现Callable接口</li>
</ol>
<pre><code class="prism language-java">import java.util.concurrent.Callable;  
import java.util.concurrent.FutureTask;  

class MyCallable implements Callable&lt;String&gt; {

    @Override  
    public String call() throws Exception {

        return "Callable running: " + Thread.currentThread().getName();  
    }  
}  

public class CallableExample {

    public static void main(String[] args) throws Exception {

        FutureTask&lt;String&gt; futureTask = new FutureTask&lt;&gt;(new MyCallable());  
        Thread thread3 = new Thread(futureTask);  
        thread3.start(); // 启动线程  

        // 获取线程执行结果  
        String result = futureTask.get();  
        System.out.println(result);  
    }  
}
</code></pre>
<h4>网络编程</h4>
<h5>网络编程介绍</h5>
<p>分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统，从而使众多的计算机可以方便地互相传递信息、共享硬性、软件、数据信息等资源。设备之间在网络中进行数据的传输，发送/接收数据。</p>
<h5>IP地址</h5>
<p>用来标志网络中的一个通信实体的地址。通信实体可以是计算机，路由器等</p>
<h5>端口号</h5>
<p>IP地址用来标志一台计算机，但是一台计算机上可能提供多种应用程序，使用端口来区分这些应用程序。</p>
<h5>网络通信协议</h5>
<p>计算机网络中实现通信必须有一些约定即通信协议，对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准</p>
<p>由于结点间联系复杂，制定协议时，把复杂成份分解成一些简单的成份，再将它们复合起来。最常用的复合方式是层次方式，即同层间可以通信、上一层可以调用下一层，而与再下一层不发生关系</p>
<h5>网络通信协议的分层</h5>
<p>名义上标准：ISO/OSI参考模型</p>
<p>事实上标准：TCP/IP协议栈（Internet使用的协议）
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa519850e09094c5582ad35d0b7f436f0.png&amp;size=m" alt=""></p>
<p>我们开发的网络应用程序位于应用层，TCP和UDP属于传输层协议，在应用层如何使用传输层的服务呢？在应用层和传输层之间，则是使用套接字来进行分离。</p>
<p>套接字就像传输层为应用层开的一个小口，应用程序通过这个小口向远程发送数据，或接收远程发来数据而这个小口以内，也就是数据进入这个口之后，或者数据从这个口出来之前，是不知道也不需要知道的，也不会关心它如何传输，这属于网络其它层次的工作。</p>
<p><strong>Socket实际是传输层供给应用层的编程接口</strong> 。传输层则在网络层的基础上提供进程到进程问的逻辑通道，应用层的进程则利用传输层向另一台主机的某一进程通信。<strong>Socket就是应用层与传输层之间的桥梁</strong></p>
<h5>演示代码</h5>
<p>简单的 TCP 服务器</p>
<pre><code class="prism language-java">    import java.io.*; // 导入输入输出流相关类  
        import java.net.*; // 导入网络相关类  

public class SimpleTCPServer {

    public static void main(String[] args) {

        try (ServerSocket serverSocket = new ServerSocket(12345)) {
    // 创建服务器端Socket，监听端口12345  
            System.out.println("服务器启动，等待连接...");  
            Socket clientSocket = serverSocket.accept(); // 等待并接受客户端连接  
            System.out.println("客户端连接：" + clientSocket.getInetAddress()); // 打印客户端IP地址  

            // 创建输入流以接收数据  
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));  
            // 创建输出流以发送数据  
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);  

            String inputLine;  
            // 持续接收客户端发送的数据  
            while ((inputLine = in.readLine()) != null) {

                System.out.println("收到消息: " + inputLine); // 打印收到的消息  
                out.println("回声: " + inputLine); // 将收到的消息回显给客户端  
            }  

            // 关闭流和Socket连接  
            in.close();  
            out.close();  
            clientSocket.close();  
        } catch (IOException e) {

            e.printStackTrace(); // 捕获异常并打印错误信息  
        }  
    }  
}
</code></pre>
<p>简单的 TCP 客户端</p>
<pre><code class="prism language-java">import java.io.*; // 导入输入输出流相关类  
        import java.net.*; // 导入网络相关类  

public class SimpleTCPClient {

    public static void main(String[] args) {

        try (Socket socket = new Socket("localhost", 12345)) {
    // 连接到本地服务器，端口12345  
            // 创建输出流以发送数据  
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);  
            // 创建输入流以接收数据  
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
            // 创建标准输入流以读取用户输入  
            BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));  

            String userInput;  
            // 持续读取用户输入并发送给服务器  
            while ((userInput = stdIn.readLine()) != null) {

                out.println(userInput); // 发送用户输入到服务器  
                System.out.println("服务器回复: " + in.readLine()); // 接收并打印服务器的回复  
            }  

            // 关闭流和Socket连接  
            in.close();  
            out.close();  
            stdIn.close();  
        } catch (IOException e) {

            e.printStackTrace(); // 捕获异常并打印错误信息  
        }  
    }  
}
</code></pre>
<h4>XML</h4>
<p>XML指可扩展标记语(EXtensibleMarkupLanguage)</p>
<h5>XML的作用是什么？</h5>
<p>XML是不作为的，XML不会做任何事情。XML被设计用来结构化、存储以及传输信息。它仅仅是纯文本而已。它仅仅将信息包装在XML标签中。我们需要编写软件或者程序，才能传送、接收和显示出这个文档。</p>
<h5>xml特点</h5>
<ol>
 <li>必须有声明语句。
  <br>
  XML声明是XML文档的第一句，其格式如下
  <br>
  &lt;7xml version=1.0 encoding=utf-8？&gt;</li>
 <li>XML文档有且只有一个根元素
  <br>
  良好格式的XML文档必须有一个根元素，就是紧接着声明后面建立的第一个元素，其他元素都是这个根元素的子元素，根元素完全包括文档中其他所有的元素</li>
 <li>注意大小写
  <br>
  在XML文档中，大小写是有区别的。 “A"和"a"是不同的标记。</li>
 <li>所有的标记必须有相应的结束标记
  <br>
  所有标记必须成对出现，有一个开始标记，就必须有一个结束标记，否则将被视为错误。</li>
 <li>属性值使用引号
  <br>
  所有属性值必须加引号（可以是单引号，也可以是双引号，建议使用双引号），否则将被视为错误</li>
 <li>XML中可以加入注释
  <br>
  注释格式：<!--&ndash; &ndash;--></li>
</ol>
<h4>注解</h4>
<h4>什么是注解？</h4>
<p>注解其实就是代码里的特殊标记， 这些标记可以在编译、类加载、运行时被读取，并执行相应的处理通过使用注解，程序员可以在不改变原有逻辑的情况下，在源文件中嵌入一些补充信息。代码分析工、开发工具和部署工可以通过这些补充信息进行验证或者进行部署</p>
<h5>注解的使用</h5>
<p>使用注解时要在其前面增加@符号，并把该注解当成一个修饰符使用。用于修饰它支持的程序元素（包、类、构造器、方法、成员变量、参数、局部变量的声明）</p>
<h5>注解的重要性</h5>
<p>在JavaSE中，注解的使用目的比较简单，例如标记过时的功能，忽略警告等。在JavaEE/Arldroid中注解占据了更重要的角色，例如用来配置应用程序的任何切面，代替JavaEE日版中所遗留的繁代码和XML配置等。未来的开发模式都是基于注解的。 一定程度上可以说：框架=注解+反射+设计模式</p>
<h5>注解的使用实例</h5>
<ol>
 <li>标识类的作者@author</li>
 <li>指定类的版本@version</li>
 <li>说明方法的参数@param</li>
 <li>说明方法的返回值类型@return</li>
 <li>限定重写的方法@Override</li>
</ol>
<h4>MySQL</h4>
<p><strong>安装Mysql和MysqlWorkbench</strong>
 <br>
 <code>https://dev.mysql.com/downloads/workbench/</code>
 <br>
 <code>https://dev.mysql.com/downloads/mysql/</code>
 <br>
 <strong>MySQL数据库</strong> 最初是由瑞典MySQLAB公司开发，2008年1月16号被Sun公司收购2009年，SUN又被Oracle收购。</p>
<p>由于MySQL数据库体积小、速度快、成本低、跨平台、开放源码等优点，现已被广泛应用于互联网上的中小型网站中，并且大型网站也开始使用MySQL数据库，如网易、新浪等。</p>
<p>SQL（StructuredQueryLanguage）是一种操作数据库的语言。</p>
<p>在数据库管理系统中，使用SQL语言来实现数据的存取、查询、更新等功能。SQL是一种非过程化语言，只需提出"做什么”，而不需要指明"怎么做”
 <br>
 速成阶段目标：学习创建表SQL、对表中数据进行增删改查的SQL
 <br>
 <strong>MySQL Workbench</strong> 是一个综合性的数据库设计和管理工具，专为 MySQL 数据库开发。它提供了图形用户界面，使用户能够更轻松地与 MySQL 数据库进行交互。</p>
<h5>代码实现</h5>
<pre><code class="prism language-sql">创建一个新的数据库：
CREATE DATABASE my_database;
选择要使用的数据库：
USE my_database;
创建一个名为 `users` 的表
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    age INT
);
向 `users` 表中插入数据：
INSERT INTO users (name, email, age) VALUES ('Alice', 'alice@example.com', 30); INSERT INTO users (name, email, age) VALUES ('Bob', 'bob@example.com', 25);
从 `users` 表中查询所有数据：
SELECT * FROM users;
更新指定用户的年龄
UPDATE users SET age = 31 WHERE name = 'Alice';
删除指定用户的数据：
DELETE FROM users WHERE name = 'Bob';


`ALTER TABLE` 是 SQL 中用于修改现有表结构的命令。通过 `ALTER TABLE` 语句，你可以对表进行多种操作，比如添加、删除或修改列，修改表的约束等

ALTER TABLE table_name ADD column_name datatype;

ALTER TABLE table_name DROP COLUMN column_name;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F43f5bcadcca0429cb0ce1887280f819a.png&amp;size=m" alt=""></p>
<h4>JDBC</h4>
<p>JDBC(Java DataBase Connectivity) 称为Java数据库连接，它是一种用于数据库访问的应用程序API，由一组用Java语言编写的类和接口组成，有了JDBC就可以用同一的语法对多种关系数据库进行访问，而不用担心其数据库操作语言的差异。 有了JDBC，就不必为访问Mysql数据库专门写一个程序，为访问Oracle又专门写一个程序等等。</p>
<h5>JDBC访问数据库编码步骤</h5>
<ol>
 <li>加载Driver驱动</li>
 <li>获取数据库连接(Connection)</li>
 <li>创建会话-SQL命令发送器(Statement)</li>
 <li>通过Statement发送SQL命令并得到结果</li>
 <li>处理结果</li>
 <li>关闭数据库资源（ResultSet、Statement、Connection）</li>
</ol>
<h5>准备资源</h5>
<ol>
 <li>准备数据库</li>
 <li>官网下载数据库连接驱动jar包。<code>https://downloads.mysql.com/archives/c-j/</code></li>
 <li>创建Java项目，在项目下创建Iib文件夹，将下的驱动jar包复制到文件夹里</li>
 <li>选中lib文件夹右键-&gt;Add as Library，与项目集成。</li>
</ol>
<pre><code class="prism language-java">Class.forName("com.mysql.cj.jdbc.Driver");  

String url = "jdbc:mysql://127.0.0.1:3306/book?useSSL=false";  
String username = "yourname";  
String password = "yourpassword";  
Connection conn = DriverManager.getConnection(url,username,password);  
//创建会话  
Statement sta = conn.createStatement();  

ResultSet rs = sta.executeQuery("select * from book where id");

while (rs.next()) {
    System.out.println('1'); }

sta.close(); conn.close();
</code></pre>
<h4>图书管理数据库版</h4>
<pre><code class="prism language-java">import java.sql.*;  
import java.util.ArrayList;  
import java.util.Scanner;  

public class bookadmin {

        public static void main(String[] args) throws SQLException, ClassNotFoundException {


            while(true) {

                System.out.println("----图书管理----");  
                System.out.println("1.根据id展示书籍");  
                System.out.println("2.展示全部书籍");  
                System.out.println("3.根据id删除书籍");  
                System.out.println("4.退出应用");  
                Scanner sc = new Scanner(System.in);  
                System.out.println("请输入功能号码");  
                int choice = sc.nextInt();  
                if (choice == 1) {

                            int bno = sc.nextInt();  
                            Book b = findBookByBno(bno);  

                    System.out.println(b.getId() +"----" +b.getTitle());  
                } else if (choice == 2) {

                    ArrayList books = findBook();  

                    for(int i = 0 ; i&lt; books.size(); i++){

                        Book b =(Book)books.get(i);  
                        System.out.println(b.getId()+"-----"+b.getTitle());  
                    }  
                }else if (choice == 3) {

                    System.out.println("请录入你想要删除的书籍编号");  
                    int bno = sc.nextInt();  
                    int n = delBookById(bno);  

                    System.out.println("删除成功");  
                } else {

                    break;  
                }  

            }  
        }  
        public static Book findBookByBno(int id) throws ClassNotFoundException, SQLException {

            Class.forName("com.mysql.cj.jdbc.Driver");  
            Book b = null;  
            String url = "jdbc:mysql://127.0.0.1:3306/book?useSSL=false";  
            String username = "yourusername";  
            String password = "yourpassword";  
            Connection conn = DriverManager.getConnection(url,username,password);  
            //创建会话  
            Statement sta = conn.createStatement();  

            ResultSet rs = sta.executeQuery("select * from book where id =" + id);  

            if(rs.next()){

                b = new Book();  
              //  System.out.println(rs.getInt("id") + rs.getString("title"));  
                int Bookid = rs.getInt("id");  
                String title = rs.getString("title");  
                b.setId(Bookid);  
                b.setTitle(title);  
            }  
            sta.close();  
            conn.close();  
            return b;  

        }  

        public static ArrayList findBook() throws ClassNotFoundException, SQLException {

            ArrayList list = new ArrayList();  
            Class.forName("com.mysql.cj.jdbc.Driver");  

            String url = "jdbc:mysql://127.0.0.1:3306/book?useSSL=false";  
            String username = "root";  
            String password = "zhai2665323601";  
            Connection conn = DriverManager.getConnection(url,username,password);  
            //创建会话  
            Statement sta = conn.createStatement();  

            ResultSet rs = sta.executeQuery("select * from book");  

            while(rs.next()){

               Book b = new Book();  
                //  System.out.println(rs.getInt("id") + rs.getString("title"));  
                int Bookid = rs.getInt("id");  
                String title = rs.getString("title");  
                b.setId(Bookid);  
                b.setTitle(title);  
                list.add(b);  
            }  
            sta.close();  
            conn.close();  
            return list;  
        }  
        public static int delBookById(int id) throws ClassNotFoundException, SQLException {

            Class.forName("com.mysql.cj.jdbc.Driver");  

            String url = "jdbc:mysql://127.0.0.1:3306/book?useSSL=false";  
            String username = "root";  
            String password = "zhai2665323601";  
            Connection conn = DriverManager.getConnection(url,username,password);  
            //创建会话  
            Statement sta = conn.createStatement();  

            int n = sta.executeUpdate("delete from book where id =" + id);  

            sta.close();  
            conn.close();  
            return n ;  
        }  

    }
</code></pre>
<blockquote>
 <p>如果本篇文章对你有用的话 点个关注吧 会持续更新技术文章</p>
</blockquote>
<p>原文链接: https://blog.csdn.net/weixin_63625059/article/details/142579384</p>]]></description><guid isPermaLink="false">/archives/753a1a4f-1cfd-4027-8a16-a9a5bb0db6ca</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:35:34 GMT</pubDate></item><item><title><![CDATA[三篇文章速通JavaSE到SpringBoot框架 上 JavaSE基础语法]]></title><link>https://wiki.dgfygk.com/archives/b11ae478-8516-448b-b4d3-1869614804e7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%89%E7%AF%87%E6%96%87%E7%AB%A0%E9%80%9F%E9%80%9AJavaSE%E5%88%B0SpringBoot%E6%A1%86%E6%9E%B6%20%E4%B8%8A%20JavaSE%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95&amp;url=/archives/b11ae478-8516-448b-b4d3-1869614804e7" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*
 <br>
 *</p>
<pre><code>* [前置环境](#_3)
* [注释](#_11)
* [变量](#_29)
*
  * [基本数据类型](#_33)
  * [引用数据类型](#_58)
  * [标识符](#_62)
  * [运算符](#_68)
* [流程控制](#_112)
*
  * [三种基本流程结构](#_115)
* [方法](#_174)
*
  * [方法声明格式](#_176)
  * [方法的调用方式](#_180)
  * [方法的重载](#_198)
  * [方法的重写](#_229)
  * [重载和重写的区别](#_266)
* [数组](#_269)
*
  *
    * [数组的特点](#_278)
* [面向对象](#_284)
*
  * [基本概念](#_285)
  * [类的编写和对象的创建与使用](#_289)
  *
    * [类的编写](#_290)
    * [对象的创建和使用](#_295)
  * [构造器](#_302)
  *
    * [构造器特点](#_307)
  * [封装](#_347)
  *
    * [以属性为案例进行封装：](#_352)
  * [继承](#_400)
  * [多态](#_424)
  *
    * [编译时多态（方法重载）](#_428)
    * [运行时多态（方法重写）](#_449)
* [异常处理](#_481)
*
  *
    * [try-catch执行情况](#trycatch_491)
    * [throw和throws的区别：](#throwthrows_535)
* [集合](#_574)
*
  *
    * [数组的缺点也就是集合的优点](#_576)
  * [ArrayList](#ArrayList_582)
* [Scanner](#Scanner_613)
* [根据所学语法做个综合项目](#_648)
</code></pre>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fca21d9f74ee340e5bbe8d6cb182a95b6.png&amp;size=m" alt=""></p>
<h4>前置环境</h4>
<p>java开发工具包
 <br>
 <code>https://www.oracle.com/cn/java/technologies/downloads/</code></p>
<p>idea编辑器
 <br>
 <code>https://www.jetbrains.com/idea/download/?section=mac</code></p>
<h4>注释</h4>
<p>三类 单行注释 多行注释 文档注释</p>
<pre><code class="prism language-java">// 注释
/*
    这是多行注释
    这是多行注释
    这是多行注释
 */

/**
 * 文档注释
 * 这是函数 、类 的的介绍
 * @version 1.0
 * @author zhaimou
 */
</code></pre>
<h4>变量</h4>
<p>变量本质上就是代表一个"可操作的存储空间”，空间位置是确定的，但是里面放置什么值不确定。我们可通过变量名来访问"对应的存储空间”，从而操纵这个"存储空间"存储的值。Java是一种强类型语言，每个变量都必须声明其数据类型。变量的数据类型决定了变量古据存储空间的大小</p>
<p>Java是一种强类型语言，每个变量都必须声明其数据类型Java的数据类型可分为两大类：基本数据类型(primitive data type)和引用数据类型(reference data type)</p>
<h5>基本数据类型</h5>
<ul>
 <li>整形： byte 、short、 int、 long</li>
 <li>浮点型：float 、double</li>
 <li>字符型：char</li>
 <li>布尔型：boolean</li>
</ul>
<pre><code class="prism language-java">public class test2 {

    public static void main(String[] args) {

       // 基本数据类型  
//【1】整数类型  
        byte a=8 ;//数范围：-128~127    布尔型（boolean）  
        short b=2980; //表数范围：正负三万  
        int c =19883; //表数范围：正负21亿   
long d=12345678919L;//表数范国：很大很大，如果表示的数的范国超过int类型表数范国就需要加  
        // 【2】浮点类型：  
        float e =3.14f;  //如果用fLoat类型表示一个小数，后面必须加上f  
        double f=3.14;  
//【3】字符型：  
        char g ='a' ;//单引号引起来的单个字符  
        System.out.println("hello world");//后续学习的字符串是多个单个字符拼接的  
//【4】布尔类型  
        boolean f1ag = true;  //  布尔值只有两个：true、false  
    }  
}
</code></pre>
<h5>引用数据类型</h5>
<ul>
 <li>类 （class）</li>
 <li>接口 （interface）</li>
 <li>数组 （array）</li>
</ul>
<h5>标识符</h5>
<ul>
 <li>由26个英文字母大小写，0-9 ，_或 $ 组成。</li>
 <li>数字不可以开头。</li>
 <li>不可以使用关键字和保留字，但能包含关键字和保留字。</li>
 <li>Java中严格区分大小写，长度无限制。</li>
 <li>标识符不能包含空格。</li>
</ul>
<h5>运算符</h5>
<p>需要注意的是，在二元运算中表达式的结果取决于类型范围最大的那个，且类型最小为int</p>
<p>也就是说，如果两个byte类型的数据进行运算，由于运算的类型最小为int，所以其结果不是byte类型而是int类型，所以无法赋值给byte的变量，需要进行强制类型转换</p>
<p><strong>（1）二元运算符</strong>：+、-、*、/、%</p>
<p><strong>（2）一元运算符</strong>：++、–</p>
<p><strong>（3）赋值运算符</strong>：=、+=、-=、*=、/=、%=</p>
<p><strong>（4）关系运算符</strong>：==、!=、&gt;、&gt;=、 &lt;、&lt;=</p>
<p><strong>（5）逻辑运算符</strong>：\&amp;、\|、^、!、\&amp;\&amp;、\|\|</p>
<p><strong>（6）三元运算符</strong>：exp1 ？ exp2 ： exp3</p>
<pre><code class="prism language-java">int a1 = 10, b1 = 5;  
int sum = a + b; // 加法  
int diff = a - b; // 减法  
int product = a * b; // 乘法  
int quotient = a / b; // 除法  
int remainder = a % b; // 取模  

int num = 5;  
num++; // 自增，num变为6  
num--; // 自减，num变为5  

int x = 10; // 赋值  
x += 5; // 等价于 x = x + 5; x现在为15  
x -= 3; // 等价于 x = x - 3; x现在为12  

int c1 = 10, d1 = 20;  
boolean isEqual = (a == b); // false  
boolean isGreater = (a &gt; b); // false  


boolean flag1 = true, flag2 = false;  
boolean result = flag1 &amp;&amp; flag2; // false  
result = flag1 || flag2; // true  

int max = (a &gt; b) ? a : b; // 如果a大于b，max为a，否则为b
</code></pre>
<h4>流程控制</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc1b4787d844f426bad56e7f3b39a654b.png&amp;size=m" alt=""></p>
<h5>三种基本流程结构</h5>
<ul>
 <li><strong>顺序结构</strong>：程序从上到下逐行地执行，中间没有任何判断和跳转。</li>
 <li><strong>分支结构</strong> ：根据条件，选择性地执行某段代码。
  <br>
  有if…else和switch-case两种分支语句。</li>
</ul>
<pre><code class="prism language-java">public class IfElseExample {

    public static void main(String[] args) {

        int score = 85;  
        if (score &gt;= 60) {

            System.out.println("及格");  
        } else {

            System.out.println("不及格");  
        }  
    int day = 3;  
    switch (day) {

    case 1:  
        System.out.println("星期一");  
        break;  
    case 2:  
        System.out.println("星期二");  
        break;  
    case 3:  
        System.out.println("星期三");  
        break;  
    default:  
        System.out.println("未知的日子");  
}

    } 

}
</code></pre>
<ul>
 <li><strong>循环结构</strong> ：根据循环条件，重复性的执行某段代码。 有while、do…while、for三种循环语句。</li>
</ul>
<pre><code class="prism language-java">public class WhileExample {

    public static void main(String[] args) {

        int count = 0;  
        while (count &lt; 5) {

            System.out.println("Count: " + count);  
            count++;  
        }  
        int count1 = 0;  
    //   先执行一次循环体，然后判断条件。
    do {

    System.out.println("Count: " + count1);  
    count1++;  
    } while (count1 &lt; 5);

for (int i = 0; i &lt; 5; i++) {

    System.out.println("Count: " + i);  
}

    }  

}
</code></pre>
<h4>方法</h4>
<p>方法(method)就是一段用来完成特定功能的代码片段</p>
<h5>方法声明格式</h5>
<p>[修饰符1修饰符2 …]\返回值类型 方法名(形式参数列表){</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>}</p>
<h5>方法的调用方式</h5>
<p>对象名.方法名(实参列表)</p>
<p>方法名(实参列表)</p>
<pre><code class="prism language-java">public class MyClass {


    // 方法声明  
    public int add(int a, int b) {

        return a + b; // 方法体  
    }  

    public static void main(String[] args) {

        MyClass obj = new MyClass(); // 创建对象  
        int result = obj.add(5, 3); // 方法调用  
        System.out.println("结果: " + result); // 输出结果  
    }  
}
</code></pre>
<h5>方法的重载</h5>
<p><code>方法的载只和方法名、形参列表有关，与其它无关</code>
 <br>
 要求：方法名必须相同，形参列表必须不同（类型不同，顺序不同，个数不同）</p>
<pre><code class="prism language-java">public class OverloadExample {


    // 方法重载：无参数  
    public void display() {

        System.out.println("无参数的方法");  
    }  

    // 方法重载：一个整数参数  
    public void display(int a) {

        System.out.println("整数参数: " + a);  
    }  

    // 方法重载：两个字符串参数  
    public void display(String str1, String str2) {

        System.out.println("字符串参数: " + str1 + ", " + str2);  
    }  

    public static void main(String[] args) {

        OverloadExample obj = new OverloadExample();  

        // 调用不同重载的方法  
        obj.display(); // 无参数  
        obj.display(10); // 一个整数参数  
        obj.display("Hello", "World"); // 两个字符串参数  
    }  
}
</code></pre>
<h5>方法的重写</h5>
<p>发生在子类和父类中，当子类对父类提供的方法不满意的时候，要对父类的方法进行重写方法的重写有严格的格式要求
 <br>
 <code>子类的方法名字和父类必须一致，参数列表（个数，类型，顺序）也要和父类一致。</code></p>
<pre><code class="prism language-java">class Animal {

    // 父类方法  
    public void sound() {

        System.out.println("动物发出声音");  
    }  
}  

class Dog extends Animal {

    // 重写父类方法  
    @Override  
    public void sound() {

        System.out.println("汪汪");  
    }  
}  

class Cat extends Animal {

    // 重写父类方法  
    @Override  
    public void sound() {

        System.out.println("喵喵");  
    }  
}  

public class OverrideExample {

    public static void main(String[] args) {

        Animal myDog = new Dog(); // 向上转型  
        Animal myCat = new Cat(); // 向上转型  

        myDog.sound(); // 输出: 汪汪  
        myCat.sound(); // 输出: 喵喵  
    }
</code></pre>
<h5>重载和重写的区别</h5>
<p>重载：在同一个类中，当方法名相同，形参列表不同的时候，多个方法构成了重载
 <br>
 重写：在不同的类中，子类对父类提供的方法不满意，对父类的方法进行重写</p>
<h4>数组</h4>
<p>数组是相同类型数据的有序集合。其中，每一个数据称作一个元素，每个元素可以通过一个索引（下标）来访问它们。</p>
<p>数组的声明 类型标识符数组名</p>
<p>数组的创建 new类型标识符[数组长度]\</p>
<p>数组的赋值 数组名[下标]=具体数值</p>
<p>数组的使用 通过下标访问数组元素</p>
<p>数组的遍历 普通for循环 增强for循环</p>
<h6>数组的特点</h6>
<p>1.长度是确定的。数组一旦被创建，它的大小就是不可以改变的。
 <br>
 2.其元素的类型必须是相同类型，不允许出现混合类型。
 <br>
 3.数组类型可以是任何数据类型，包括基本类型和引用类型
 <br>
 4.数组有索引的：索引从0开始，到数组.length-1结束</p>
<h4>面向对象</h4>
<h5>基本概念</h5>
<p><strong>类</strong> :对对象向上抽取出像的部分、公共的部分以此形成类，类就相当于一个模版</p>
<p><strong>对象</strong> ：模版下具体的产物可以理解为具体对象，对象就是一个一个具体的实例，就相当于这个模版下具体的产品
 <br>
 Java中先定义类，再创建对象</p>
<h5>类的编写和对象的创建与使用</h5>
<h6>类的编写</h6>
<p>1.给类起一个见名知意的名字，首字母大写，驼峰命名原则</p>
<p>2.类的特性编写，特性即类的属性部分。</p>
<p>3.类的行为编写，行为即类的方法部分。</p>
<h6>对象的创建和使用</h6>
<p>创建格式：类名对象名=new类名0</p>
<p>给对象的属性赋值：对象名.属性名=值</p>
<p>调用对象的方法：[返回值类型名字=]对象名.方法名（参数列表）</p>
<p>对于一个类来说 般有三种常见的成员：属性、方法、构造器。
 <br>
 这三种成员都可以定义零个或多个。
 <br>
 构造方法也叫构造器，是一个创建对象时被自动调用的特殊方法，用于对象的初始化。Java通过new关键字来调用构造器，从而返回该类的实例</p>
<h5>构造器</h5>
<p>若无参数列表，称之为无参构造器（空构造器）</p>
<p>若有参数列表，称之为有参构造器</p>
<p>如果一个类没有显式编写构造器的话，那么系统会为这个类默认分配一个空构造器调用构造器以后，对对象进行初始化操作，将对象的地址返回给实例
 <br>
 尽量保证空构造器的存在</p>
<h6>构造器特点</h6>
<ol>
 <li>构造器的方法名必须和类名一致</li>
 <li>构造器通过new关键字调用</li>
 <li>构造器不能定义返回值类型，不能在构造器里使用return关键字来返回某个值。</li>
 <li>如果我们没有定义构造器，则编译器会自动定义一个无参的构造方法。如果已定义则编译器不会自动添加！</li>
</ol>
<pre><code class="prism language-java">class Person {

    private String name;  
    private int age;  

    // 无参构造器（空构造器）  
    public Person() {

        this.name = "未知";  
        this.age = 0;  
    }  

    // 有参构造器  
    public Person(String name, int age) {

        this.name = name;  
        this.age = age;  
    }  

    // 方法用于显示信息  
    public void display() {

        System.out.println("姓名: " + name + ", 年龄: " + age);  
    }  
}  

public class ConstructorExample {

    public static void main(String[] args) {

        // 使用无参构造器创建对象  
        Person person1 = new Person();  
        person1.display(); // 输出: 姓名: 未知, 年龄: 0  

        // 使用有参构造器创建对象  
        Person person2 = new Person("张三", 25);  
        person2.display(); // 输出: 姓名: 张三, 年龄: 25  
    }  
}
</code></pre>
<h5>封装</h5>
<p>我们程序设计追求"高内聚，低耦合”</p>
<p>高内聚：类的内部数据操作细节自己完成，不允许外部干涉</p>
<p>低耦合：仅对外暴露少量的方法用于使用。</p>
<p>隐藏对象内部的复杂性，只对外公开简单的接口。便于外界调用，从而提高系统的可扩展性、可维护性，提高程序的安全性。通俗的说，把该隐藏的隐藏起来，该暴露的暴露出来。这就是封装性的设计思想</p>
<h6>以属性为案例进行封装：</h6>
<ol>
 <li>将属性私有化，被private修饰–》加入权限修饰符
  <br>
  旦加入了权限修饰符，其他人就不可以随意的获取这个属性</li>
 <li>提供public修饰的方法让别人来访问/使用</li>
 <li>即使外界可以通过方法来访问属性了，但是也不能随意访问，因为程序员可以在方法中可以加入限制条件。</li>
</ol>
<pre><code class="prism language-java">class Person {

    // 私有属性  
    private String name;  
    private int age;  

    // 构造器  
    public Person(String name, int age) {

        this.name = name;  
        this.age = age;  
    }  

    // 获取姓名  
    public String getName() {

        return name;  
    }  

    // 获取年龄  
    public int getAge() {

        return age;  
    }  

    // 设置年龄  
    public void setAge(int age) {

        if (age &gt; 0) {

            this.age = age;  
        } else {

            System.out.println("年龄必须大于0");  
        }  
    }  
}  

public class Main {

    public static void main(String[] args) {

        Person person = new Person("张三", 25);  
        System.out.println("姓名: " + person.getName() + ", 年龄: " + person.getAge());  

        person.setAge(30); // 设置新年龄  
        System.out.println("新年龄: " + person.getAge());  
    }  
}
</code></pre>
<h5>继承</h5>
<p>允许一个类（子类）继承另一个类（父类）的属性和方法。通过继承，子类可以重用父类的代码，增加代码的复用性和可维护性。子类还可以扩展或重写父类的方法，以实现特定的功能。</p>
<p>例如，子类可以添加新的属性或方法，或重写父类的方法以改变其行为。使用 <code>extends</code> 关键字来实现继承，子类可以访问父类的公共和保护成员，但不能直接访问私有成员。</p>
<p>继承是对类的抽象</p>
<pre><code class="prism language-java">class Animal {

    void eat() {

        System.out.println("动物在吃");  
    }  
}  

class Dog extends Animal {

    void bark() {

        System.out.println("狗在叫");  
    }  
}  

public class Main {

    public static void main(String[] args) {

        Dog dog = new Dog();  
        dog.eat(); // 继承自 Animal        dog.bark(); // Dog 的方法  
    }  
}
</code></pre>
<h5>多态</h5>
<p>Java 多态是指同一个方法可以在不同的对象上表现出不同的行为。它主要分为两种形式：编译时多态（静态多态）和运行时多态（动态多态）。
 <br>
 <strong>PS</strong> :多态跟属性无关，多态指的是方法的多态，而不是属性的多态。
 <br>
 多态的三要素：继承、重写、父类引用指向子类对象</p>
<h6>编译时多态（方法重载）</h6>
<pre><code class="prism language-java">class MathUtils {

    // 加法方法重载  
    int add(int a, int b) {

        return a + b;  
    }  

    double add(double a, double b) {

        return a + b;  
    }  
}  

public class Main {

    public static void main(String[] args) {

        MathUtils math = new MathUtils();  
        System.out.println(math.add(5, 10)); // 调用 int 加法  
        System.out.println(math.add(5.5, 10.5)); // 调用 double 加法  
    }  
}
</code></pre>
<h6>运行时多态（方法重写）</h6>
<p>运行时多态是通过方法重写实现的，子类可以重写父类的方法。程序在运行时决定调用哪个方法，通常与对象的实际类型有关。</p>
<pre><code class="prism language-java">class Animal {

    void makeSound() {

        System.out.println("动物发出声音");  
    }  
}  

class Dog extends Animal {

    void makeSound() {

        System.out.println("狗在叫");  
    }  
}  

class Cat extends Animal {

    void makeSound() {

        System.out.println("猫在叫");  
    }  
}  

public class Main {

    public static void main(String[] args) {

        Animal myDog = new Dog();  
        Animal myCat = new Cat();  

        myDog.makeSound(); // 输出: 狗在叫  
        myCat.makeSound(); // 输出: 猫在叫  
    }  
}
</code></pre>
<h4>异常处理</h4>
<p>异常就是在程序的运行过程中所发生的不正常的事件，它会中断正在运行的程序</p>
<ol>
 <li>所需文件找不到</li>
 <li>网络连接不通或中断</li>
 <li>算术运算错（被零除.）</li>
 <li>数组下标越界</li>
 <li>装载一个不存在的类或者对null对象操作</li>
 <li>类型转换异常
  <br>
  Java提供异常处理机制。它将异常处理代码和和业务代码分离，使程序更优雅，更好的容错性，高键壮性。
  <br>
  Java的异常处理是通过5个关键字来实现的：<code>try、catch、 finally、throw、throws</code></li>
</ol>
<h6>try-catch执行情况</h6>
<p>情况1：try块中代码没有出现异常
 <br>
 不执行catch块代码，执行catch块后边的代码</p>
<p>情况2：try块中代码出现异常，catch中异常类型匹配（相同或者父类）&lt;
 <br>
 Java会生成相应的异常对象，Java系统寻找匹配的catch块，执行catch块代码，执行catch块后边的代码。try块中尚未执行的语句不会执行。</p>
<p>情况3：try块中代码出现异常，catch中异常类型不匹配
 <br>
 不执行catch块代码，不执行catch块后边的代码，程序会中断运行</p>
<p>catch块中如何处理异常
 <br>
 其中一种方式：自定义内容输出</p>
<pre><code class="prism language-java">public class TryCatchExample {

    public static void main(String[] args) {

        // 情况1：try块中代码没有出现异常  
        try {

            System.out.println("情况1：没有异常的情况。");  
            // 正常执行的代码  
        } catch (Exception e) {

            System.out.println("捕获到异常：" + e.getMessage());  
        }  
        System.out.println("情况1后面的代码继续执行。");  

        // 情况2：try块中代码出现异常，catch中异常类型匹配  
        try {

            System.out.println("情况2：即将抛出异常。");  
            int result = 10 / 0; // 抛出ArithmeticException  
        } catch (ArithmeticException e) {

            System.out.println("捕获到ArithmeticException：" + e.getMessage());  
        }  
        System.out.println("情况2后面的代码继续执行。");  

        // 情况3：try块中代码出现异常，catch中异常类型不匹配  
        try {

            System.out.println("情况3：即将抛出异常。");  
            String str = null;  
            System.out.println(str.length()); // 抛出NullPointerException  
        } catch (NumberFormatException e) {

            // 这个catch块不会被执行  
            System.out.println("捕获到NumberFormatException：" + e.getMessage());  
        }  
        // 如果上面的try块抛出异常，程序会中断，这里不会执行  
        System.out.println("情况3后面的代码将不会执行。");  
    }  
}
</code></pre>
<h6>throw和throws的区别：</h6>
<ol>
 <li>位置不同：
  <br>
  throw：方法内部
  <br>
  throws：方法的签名处，方法的声明处</li>
 <li>内容不同：
  <br>
  throw+异常对象
  <br>
  throws+异常的类型</li>
 <li>作用不同：
  <br>
  throw：异常出现的源头，制造异常
  <br>
  throws：在方法的声明处，告诉方法的调用者，这个方法中可能会出现我声明的这些异常。然后调用者对这个异常进行处理：要么自己处理要么再继续向外抛出异常</li>
</ol>
<pre><code class="prism language-java">public class ThrowVsThrowsExample {


    // 使用 throws 声明异常  
    public static void mayThrowException() throws CustomException {

        // 这里可以根据某种条件抛出异常  
        boolean condition = true; // 修改为 false 以避免抛出异常  
        if (condition) {

            throw new CustomException("自定义异常被抛出！");  
        }  
    }  

    public static void main(String[] args) {

        try {

            // 调用可能抛出异常的方法  
            mayThrowException();  
        } catch (CustomException e) {

            // 捕获并处理异常  
            System.out.println("捕获到异常：" + e.getMessage());  
        }  

        // 下面是 throw 的示例  
        try {

            // 直接在方法内部使用 throw            throw new IllegalArgumentException("非法参数异常被抛出！");  
        } catch (IllegalArgumentException e) {

            System.out.println("捕获到异常：" + e.getMessage());  
        }  
    }
</code></pre>
<h4>集合</h4>
<p>在 Java 中，集合（Collections）是用于存储和操作多个元素的对象。Java 提供了一组用于处理集合的类和接口，主要分为以下几种类型</p>
<h6>数组的缺点也就是集合的优点</h6>
<ol>
 <li>数组一旦指定了长度，那么长度就被确定了，不可以更改</li>
 <li>删除，增加元素效率低</li>
 <li>数组中实际元素的数量是没有办法获取的，没有提供对应的方法或者属性来获取</li>
 <li>数组有序，可重复，对于无序的，不可重复的场合数组不能满足要求
  <br>
  ![[Pasted image 20240923232050.png]]</li>
</ol>
<h5>ArrayList</h5>
<p><code>ArrayList</code> 是 Java 中最常用的集合之一，属于 <code>List</code> 接口的实现。它使用动态数组来存储元素，支持快速随机访问和动态扩展。以下是一些关键特点和基本用法：</p>
<pre><code class="prism language-java">import java.util.ArrayList;  

public class ArrayListExample {

    public static void main(String[] args) {

        // 创建 ArrayList        ArrayList&lt;String&gt; fruits = new ArrayList&lt;&gt;();  

        // 添加元素  
        fruits.add("Apple");  
        fruits.add("Banana");  
        fruits.add("Cherry");  

        // 访问元素  
        System.out.println("First fruit: " + fruits.get(0)); // 输出 Apple  
        // 修改元素  
        fruits.set(1, "Blueberry"); // 将 Banana 改为 Blueberry  
        // 删除元素  
        fruits.remove("Cherry");  

        // 遍历 ArrayList        for (String fruit : fruits) {  
            System.out.println(fruit);  
        }  

        // 输出大小  
        System.out.println("Size: " + fruits.size());  
    }  
}
</code></pre>
<p>这里不多介绍其他的集合</p>
<h4>Scanner</h4>
<p>使用Scanner类从键盘获取不同类型的变量。
 <br>
 具体实现按步骤：</p>
<ol>
 <li>导包：import java.util.Scanner;</li>
 <li>Scanner 的实例化：Scanner scan = new Scanner(System.in);</li>
 <li>调用Scanner类的相关方法（next()/nextXxx()），来获取指定类型的变量
  <br>
  【注意】：</li>
</ol>
<p>需要根据相应的方法。来输入指定类型的值。</p>
<p>如果输入的数据类型与要求的类型不匹配时，会报异常：InputMisMatchException 导致程序终止。</p>
<pre><code class="prism language-java"> import java.util.Scanner;  

public class ScannerExample {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);  

        System.out.print("请输入一个整数: ");  
        if (scanner.hasNextInt()) {

            int number = scanner.nextInt();  
            System.out.println("你输入的整数是: " + number);  
        } else {

            System.out.println("输入不是一个有效的整数。");  
        }  

        System.out.print("请输入你的名字: ");  
        scanner.nextLine(); // 清除前一个输入的换行符  
        String name = scanner.nextLine();  
        System.out.println("你好, " + name + "!");  

        // 关闭 Scanner        scanner.close();  
    }  
}
</code></pre>
<h4>根据所学语法做个综合项目</h4>
<p>一个简单的图书管理</p>
<pre><code class="prism language-java">// test01.java
public class test01 {

    public static void main(String[] args){

        ArrayList list = new ArrayList();  
        while(true) {

            System.out.println("----图书管理----");  
            System.out.println("1.展示书籍");  
            System.out.println("2.添加书籍");  
            System.out.println("3.删除书籍");  
            System.out.println("4.退出应用");  
            Scanner sc = new Scanner(System.in);  
            System.out.println("请输入功能号码");  
            int choice = sc.nextInt();  
            if (choice == 1) {

                System.out.println("--图书展示区域--");  
                for (int i = 0; i &lt; list.size(); i++) {

                    Book b = (Book)(list.get(i));  
                    System.out.println(b.getbNo() + "---" +b.getBName());  
                }  
            } else if (choice == 2) {

                System.out.println("请输入书籍编号");  
                int bNo =sc.nextInt();  
                System.out.println("请输入书籍名字");  
                String bName = sc.next();  
                Book b = new Book();  
                b.setbNo(bNo);  
                b.setBName(bName);  
                list.add(b);  

            } else if (choice == 3) {

                System.out.println("请输入删除书籍编号");  
                int delbNo = sc.nextInt();  
                for (int i = 0; i &lt;list.size() ; i++) {

                    Book b =  (Book)(list.get(i));  
                    if(b.getbNo() == delbNo){

                        list.remove(b);  
                        System.out.println("删除成功");  
                        break;  
                    }  
                }  

            } else {

                break;  
            }  

        }  
    }  
}
//Book.java
package com.zhai.test01;  

public class Book {

    private int bNo;  
    private String BName;  

    public Book(int bNo, String BName) {

        this.bNo = bNo;  
        this.BName = BName;  
    }  

    public Book() {

    }  

    public int getbNo() {

        return bNo;  
    }  

    public String getBName() {

        return BName;  
    }  

    public void setbNo(int bNo) {

        this.bNo = bNo;  
    }  

    public void setBName(String BName) {

        this.BName = BName;  
    }  
}
</code></pre>
<p>原文链接: https://blog.csdn.net/weixin_63625059/article/details/142471485</p>]]></description><guid isPermaLink="false">/archives/b11ae478-8516-448b-b4d3-1869614804e7</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:34:59 GMT</pubDate></item><item><title><![CDATA[【计算机基础】【服务器】服务器和电脑的区别]]></title><link>https://wiki.dgfygk.com/archives/06fd34c6-2401-4046-96ac-ed16b2740248</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80%E3%80%91%E3%80%90%E6%9C%8D%E5%8A%A1%E5%99%A8%E3%80%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%92%8C%E7%94%B5%E8%84%91%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/06fd34c6-2401-4046-96ac-ed16b2740248" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_2">服务器和电脑的区别</a></li>
 <li><a href="https://wiki.dgfygk.com#_93">服务器的硬件知识</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#01__118">01 电源</a></li>
 <li><a href="https://wiki.dgfygk.com#02_CPU_129">02 CPU处理器</a></li>
 <li><a href="https://wiki.dgfygk.com#03_RAM_164">03 内存（RAM）</a></li>
 <li><a href="https://wiki.dgfygk.com#04__196">04 硬盘（磁盘）</a></li>
 <li><a href="https://wiki.dgfygk.com#05_Raid_215">05 Raid卡（阵列卡）</a></li>
 <li><a href="https://wiki.dgfygk.com#06__241">06 远程管理卡</a></li>
 <li><a href="https://wiki.dgfygk.com#07__256">07 主板</a></li>
</ul>
<p>
 <br>
</p>
<h2>服务器和电脑的区别</h2>
<hr>
<p><strong>1. 定义</strong></p>
<ul>
 <li>
  <p><strong>服务器</strong>：</p>
 </li>
 <li>
  <p>服务器是一种专门设计用于提供各种服务的计算机系统，通常位于数据中心或者网络中枢位置。</p>
 </li>
 <li>
  <p><strong>个人电脑</strong>：</p>
 </li>
 <li>
  <p>个人电脑是一种用于个人使用的计算机设备，通常放置在办公室、家庭或个人的工作空间中。</p>
 </li>
</ul>
<hr>
<p><strong>2. 用途</strong></p>
<ul>
 <li>
  <p><strong>服务器</strong>：</p>
 </li>
 <li>
  <p>用于提供服务，如网站托管、数据库存储、文件共享、电子邮件服务等。</p>
 </li>
 <li>
  <p>通常用于承担网络负载，处理大量数据请求。</p>
 </li>
 <li>
  <p><strong>个人电脑</strong>：</p>
 </li>
 <li>
  <p>用于个人办公、娱乐、学习等日常任务。</p>
 </li>
 <li>
  <p>通常用于运行个人应用程序、浏览网页、编辑文档等。</p>
 </li>
</ul>
<hr>
<p><strong>3. 硬件配置</strong></p>
<ul>
 <li>
  <p><strong>服务器</strong>：</p>
 </li>
 <li>
  <p>通常具有高性能的处理器、大容量的内存和存储空间，以应对大规模的数据处理需求。</p>
 </li>
 <li>
  <p>可能具有多个处理器、大容量磁盘阵列和专用网络设备。</p>
 </li>
 <li>
  <p><strong>个人电脑</strong>：</p>
 </li>
 <li>
  <p>通常具有适用于个人使用的标准处理器、内存和存储设备。</p>
 </li>
 <li>
  <p>通常不需要像服务器那样高性能的硬件配置。</p>
 </li>
</ul>
<hr>
<p><strong>4. 操作系统</strong></p>
<ul>
 <li>
  <p><strong>服务器</strong>：</p>
 </li>
 <li>
  <p>可以运行各种服务器级操作系统，如 Linux、Windows Server、Unix 等。</p>
 </li>
 <li>
  <p>通常会选择更稳定、安全的操作系统，并且支持远程管理和配置。</p>
 </li>
 <li>
  <p><strong>个人电脑</strong>：</p>
 </li>
 <li>
  <p>通常运行桌面操作系统，如 Windows、macOS、Linux 等。</p>
 </li>
 <li>
  <p>通常用于支持个人用户界面和应用程序。</p>
 </li>
</ul>
<hr>
<p><strong>5. 网络连接</strong></p>
<ul>
 <li>
  <p><strong>服务器</strong>：</p>
 </li>
 <li>
  <p>通常具有高速网络连接，可连接到互联网或内部网络。</p>
 </li>
 <li>
  <p>通常会配置冗余网络连接以确保可靠性和高可用性。</p>
 </li>
 <li>
  <p><strong>个人电脑</strong>：</p>
 </li>
 <li>
  <p>通常连接到局域网或互联网，使用家庭或办公室的常见网络连接。</p>
 </li>
</ul>
<hr>
<p><strong>6. 管理和维护</strong></p>
<ul>
 <li>
  <p><strong>服务器</strong>：</p>
 </li>
 <li>
  <p>需要专门的管理人员进行配置、监控和维护。</p>
 </li>
 <li>
  <p>需要定期进行更新、备份和安全性检查。</p>
 </li>
 <li>
  <p><strong>个人电脑</strong>：</p>
 </li>
 <li>
  <p>由个人用户自行管理和维护。</p>
 </li>
 <li>
  <p>可能需要进行定期的系统更新、防病毒软件更新等。</p>
 </li>
</ul>
<hr>
<p><strong>7. 安全性</strong></p>
<ul>
 <li>
  <p><strong>服务器</strong>：</p>
 </li>
 <li>
  <p>需要采取严格的安全措施，以防止未经授权的访问和数据泄露。</p>
 </li>
 <li>
  <p>需要使用防火墙、加密和访问控制等技术保障安全性。</p>
 </li>
 <li>
  <p><strong>个人电脑</strong>：</p>
 </li>
 <li>
  <p>通常也需要采取一定的安全措施，如安装防病毒软件、防火墙等。</p>
 </li>
 <li>
  <p>安全性需求相对较低，通常不需要像服务器那样严格的安全策略。</p>
 </li>
</ul>
<hr>
<p><strong>总结</strong></p>
<p>服务器和个人电脑在用途、硬件配置、操作系统、网络连接、管理和维护、安全性等方面存在明显的区别。服务器通常用于提供服务、具有高性能硬件配置和严格的安全措施，而个人电脑则主要用于个人使用，其硬件配置和安全性要求相对较低。</p>
<hr>
<h2>服务器的硬件知识</h2>
<p>身为网络工程师，天天折腾网络那些事儿，少不了要面对服务器这个东西。</p>
<p>对小白来说，可能会有一个疑问：服务器和电脑到底有啥区别啊？</p>
<p>其实，简单来说服务器也是电脑——是为电脑提供服务的电脑。</p>
<p>一台电脑，如果它被需要用来做某些专属用途，就需要安装一些比较特殊的软件，这个时候，这台电脑就可以叫它xx服务器。</p>
<p>这个xx，具体看它被用作什么用途了，用啥就起啥名儿。比如最常见的网站服务器。</p>
<p>但你想登录百度时，你在浏览器里输入http://www.baidu.com，你按下回车，到了百度的首页。</p>
<p>但其实，是你的电脑通过你的操作，访问到了专属于网站服务的电脑，启动了那台电脑里的对应程序，才有了你现在看到的界面。</p>
<p>这种服务器就被叫做网站服务器（web服务器），它啥都不用做，只要响应你的电脑发出的访问请求就完事儿了。</p>
<p>那服务器内部到底长啥样？今天就和大家一起科普下。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F410836f6841bfb94533b52cda318c698.png&amp;size=m" alt=""></p>
<p>可以看到，服务器是由电源、CPU、内存、硬盘、风扇、光驱等几部分构成。今天，将挑选一些重要的硬件，给大家做详细介绍。</p>
<h3>01 电源</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff77efd44c530f5d244aa78ceca4c8811.png&amp;size=m" alt=""></p>
<p>电源相当于人体的心脏，需保障电力供应，如果要买服务器，应选择质量好一点的电源。</p>
<p>另外需要注意的是，在实际使用场景中，如果只是配置一个服务器负责核心业务，那么最好使用双电源，并且分别接不同的机房线路；</p>
<p>如果服务器是集群中的一台（若干机器做一件事），则可以不用双电源。除此之外，运维工作中就不用再过多考虑电源的其他问题了。</p>
<h3>02 CPU处理器</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2f4440372368a61ed9c4bedb1a305e12.png&amp;size=m" alt=""></p>
<p>CPU 处理器相当于人体的大脑，负责整个服务器的运算和控制，是影响其性能效率的最核心部件。</p>
<p>常见的 CPU 种类有如下 2 种：</p>
<p>精简指令集的 CPU：设计代表有 SUN 公司的 SPARC 系列和 ARM 系列等，这类 CPU 的设计特点是指令集精简，每个指令执行的时间很短，操作很简单、效率较高。</p>
<p>复杂指令集的 CPU：设计代表有大家熟知的 Intel 至强系列（XEON）和 AMD 系列（应用不多）等，这类 CPU 的设计特点是指令数量多，指令集复杂，且执行的时间较长，但能处理的事务更多、更丰富。</p>
<p>CPU 一般通过频率（GHz）表示性能的好坏，CPU 频率就是用来表示 CPU 每秒钟的工作次数，即频率越高速度越快。</p>
<p>需要注意的是，每一台服务器中不是只能包含一个 CPU，而是可以容纳多个。服务器 CPU 的颗数，我们称为路数。</p>
<p>企业级常见的物理服务器配置包括如下两种：</p>
<p>（1）一般企业里的服务器</p>
<p>CPU 个（颗）数为 2～4 颗，单个（颗）CPU 是四核。内存总量一般是 16～256GB（32GB、64GB 比较常见）。</p>
<p>（2）用于虚拟化的宿主机（例如，应用 VMware（虚拟化软件）、KVM 的主机）</p>
<p>CPU 颗数可达 4～8 颗，内存总量一般是 48～128GB，常规企业可以同时启动 6～10 个虚拟机甚至更多，主要是根据业务需求决定虚拟机的配置大小。</p>
<p>在企业级系统中，选择 CPU 硬件配置，以及监测和优化服务器系统的 CPU 性能，是一项复杂且需要长期实践和反复观察的工作。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb96df6bd82a2fae652538789336423c8.png&amp;size=m" alt=""></p>
<p>另外值得一提的是，CPU 长时间运行会发热，因此需要配置降温的设备，即 CPU 风扇或散热片，其中散热片主要是用金属铜或者铝制作的，作用是将热量快速传导出去。</p>
<h3>03 内存（RAM）</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe2b06dc2494c4abb079b8e037eb7e797.png&amp;size=m" alt=""></p>
<p>内存是 CPU 和硬盘之间的缓冲设备（如图 5 所示），是临时存储器（用于存放数据），这就意味着，如果突然断电，那么位于内存中的数据很可能会丢失。</p>
<p>程序运行的时候，一般会被调度到内存中执行，服务器关闭或程序关闭之后，数据将自动从内存中释放掉。</p>
<p>提到内存，很多企业在面试时，都会问缓冲区和缓存区的区别，作为新手，可以这样回答:</p>
<p>（1）缓冲区</p>
<p>将数据写入内存，这个数据的内存空间在 Linux 系统里一般称为缓冲区（buffer）。例如，写入到内存缓冲区，即写缓冲。</p>
<p>为了提高写操作性能，数据在写入最终介质或下一层级介质之前会合并放入缓冲区中，这样会增加数据持久写的延时。</p>
<p>因为第一次写入缓冲区后，再向下写入数据之前，还要等待后续的写入，以便凑够数据或者定时写入到永久存储介质中。</p>
<p>（2）缓存区</p>
<p>从内存中读取数据，这个存数据的内存空间在 Linux 系统里一般称为缓存区（cache）。例如，从内存缓存区读取，即读缓存。</p>
<p>操作系统利用缓存提高文件系统的读性能和内存的分配性能，应用程序使用缓存也是为了提高读访问效率。</p>
<p>将经常访问的操作结果保存在缓存中可备随时使用，而非总是执行读硬盘以获取数据等开销较高的操作。</p>
<p>记住这样一句话：</p>
<p>缓存无处不在，无论是电脑硬件、操作系统，还是企业网站集群及其他业务系统。</p>
<h3>04 硬盘（磁盘）</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8a3686975ef9e07b7a0e0063c6a6f974.png&amp;size=m" alt=""></p>
<p>硬盘就是永久存放数据的存储器，如今常用的硬盘都是 3.5 英寸的，且生产的单块硬盘的容量越来越大，体积却越来越小，速度也越来越快，其中常见的硬盘有300GB、600GB、1TB、3TB、4TB等规格。</p>
<p>注意，对于工业级（企业级）硬盘计算，以 1000 为单位进行换算，即1TB=1000GB。</p>
<p>根据实际场景的需要，硬盘的接口有 IDE、SCSI、SAS、SATA 等几类（其中 IDE、SCSI 已退出历史舞台），硬盘的类型也分为机械硬盘和SSD（固态硬盘）两种。</p>
<p>从性能和价格的角度，可以对现有几类硬盘进行排序（从高到低），依次是 SAS接口固态硬盘 &gt; SATA固态硬盘 &gt; SAS机械硬盘 &gt; SATA机械硬盘。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F86aaaea00b1a2f465df0a12bcc3325e0.png&amp;size=m" alt=""></p>
<p>如图所示，即从存储容量和 I/O 读取速度的角度，对服务器中各个硬件做了比较。</p>
<p>可以看到，硬盘 I/O（读取）性能相比内存差很多，目前解决这个问题的普遍方法是先将大量的数据从硬盘缓存到内存，也就是说写入到缓冲区中，这也是当今互联网网站解决访问速度慢的必备方案。</p>
<h3>05 Raid卡（阵列卡）</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbef4961fe98174c8b639d3d6a622027b.png&amp;size=m" alt=""></p>
<p>Raid 称为磁盘冗余阵列，其功能是：</p>
<p>当企业的网站（业务）数据量很大，单块盘装不下的时候，若购买多块硬盘存放数据，就需要利用 Raid 技术将所有硬盘整合成一个大硬盘，然后才能在这个大硬盘上分区（划分隔断、虚拟硬盘）存放数据。</p>
<p>但是，硬盘多了势必会有损坏，可数据是不能丢的。</p>
<p>针对这种情况，Raid 还具备一个功能，就是多块硬盘放置在一起可以配置冗余（备份），由此可以确保即使若干硬盘有损坏，数据也不会丢失。</p>
<p>总的来说，使用 Raid 卡（阵列卡）具有以下好处：</p>
<p>可以将所有硬盘整合到一起（扩充容量）；</p>
<p>可以使得数据更加安全（数据冗余）；</p>
<p>可以获得更高的效率（读写性能）。</p>
<p>如果有 Raid 卡，则一般会将硬盘连接到 Raid 卡上，而不是直接插到主板上，Raid 卡最终将插到主板对应的插槽里。</p>
<p>不过，在企业真正的重要服务器里，Raid 几乎是不被采用的，请读者注意这点。</p>
<h3>06 远程管理卡</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F705059e7ec516f57d449566c7ea065e9.png&amp;size=m" alt=""></p>
<p>远程管理卡是服务器特有的远程管理部件，在家用电脑及笔记本电脑上是不存在的。</p>
<p>它的作用是通过网络远程（异地）开关服务器，并可以查看服务器开关的过程等信息。</p>
<p>不仅如此，远程管理卡还可以细分为服务器自带远程管理卡和独立远程管理卡。</p>
<p>服务器自带的远程管理卡，可以关机、开机，但是看不到开关服务器的过程，因此建议为服务器配备独立的远程管理卡，成本上可能会多花人民币 100 元左右。</p>
<p>但是好处是很明显的，当服务器出现问题时，可以利用管理卡快速查看服务器故障及恢复服务。</p>
<h3>07 主板</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc75288e7f368fbb12ae8a2b28f00361e.png&amp;size=m" alt=""></p>
<p>主板实际上就是一块电路板，相当于人体的骨架，CPU、内存、硬盘、Raid 卡等所有硬件设备最终都要连接在主板上，才能正常工作。</p>
<p>原文链接: https://blog.csdn.net/LeonNo11/article/details/138232199</p>]]></description><guid isPermaLink="false">/archives/06fd34c6-2401-4046-96ac-ed16b2740248</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Sun, 29 Sep 2024 03:34:42 GMT</pubDate></item><item><title><![CDATA[三小时快速上手TypeScript，TS速通教程(上篇、中篇、下篇、附加篇)]]></title><link>https://wiki.dgfygk.com/archives/fcb03852-b155-483b-a0de-f47fbb1c5f3b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%89%E5%B0%8F%E6%97%B6%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8BTypeScript%EF%BC%8CTS%E9%80%9F%E9%80%9A%E6%95%99%E7%A8%8B%28%E4%B8%8A%E7%AF%87%E3%80%81%E4%B8%AD%E7%AF%87%E3%80%81%E4%B8%8B%E7%AF%87%E3%80%81%E9%99%84%E5%8A%A0%E7%AF%87%29&amp;url=/archives/fcb03852-b155-483b-a0de-f47fbb1c5f3b" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>TypeScript速通</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#Typescript_1">Typescript简介</a></li>
 <li><a href="https://wiki.dgfygk.com#TypeScript_9">为什么需要TypeScript</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#JavaScript_11">JavaScript今非昔比</a></li>
 <li><a href="https://wiki.dgfygk.com#JavaScript_18">JavaScript中的困扰</a>
  <br>
  <em> 
   <br>
  </em>
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__20">1. 不清不楚的数据类型</a></li>
   <li><a href="https://wiki.dgfygk.com#2__27">2. 有漏洞的逻辑</a></li>
   <li><a href="https://wiki.dgfygk.com#3__39">3. 访问不存在的属性</a></li>
   <li><a href="https://wiki.dgfygk.com#4__46">4. 低级的拼写错误</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#TypeScript_53">TypeScript静态类型检查</a></li>
 <li><a href="https://wiki.dgfygk.com#_TypeScript_56">编译 TypeScript</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1__58">1. 命令行编译</a></li>
 <li><a href="https://wiki.dgfygk.com#2__63">2. 自动化编译</a></li>
 <li><a href="https://wiki.dgfygk.com#_71">类型声明</a></li>
 <li><a href="https://wiki.dgfygk.com#_106">类型推断</a></li>
 <li><a href="https://wiki.dgfygk.com#_113">类型总览</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#JavaScript_114">JavaScript中的数据类型</a></li>
 <li><a href="https://wiki.dgfygk.com#TypeScript_124">TypeScript中的数据类型</a></li>
 <li><a href="https://wiki.dgfygk.com#_137">常用类型与语法</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_any_139">1. any</a></li>
 <li><a href="https://wiki.dgfygk.com#2_unknow_169">2. unknow</a></li>
 <li><a href="https://wiki.dgfygk.com#3_never_223">3. never</a></li>
 <li><a href="https://wiki.dgfygk.com#4_void_262">4. void</a></li>
 <li><a href="https://wiki.dgfygk.com#5_object_318">5. object</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#object_322">小写`object`</a></li>
   <li><a href="https://wiki.dgfygk.com#Object_345">大写`Object`</a></li>
   <li><a href="https://wiki.dgfygk.com#_373">声明对象类型</a></li>
   <li><a href="https://wiki.dgfygk.com#_413">声明函数类型</a></li>
   <li><a href="https://wiki.dgfygk.com#_425">声明数组类型</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#6_tuple_437">6. tuple</a></li>
 <li><a href="https://wiki.dgfygk.com#7_enum_458">7. enum</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_486">数字枚举</a></li>
   <li><a href="https://wiki.dgfygk.com#_550">字符串枚举</a></li>
   <li><a href="https://wiki.dgfygk.com#_565">常量枚举</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#8_type_604">8. type</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_608">基本用法</a></li>
   <li><a href="https://wiki.dgfygk.com#_618">联合类型</a></li>
   <li><a href="https://wiki.dgfygk.com#_641">交叉类型</a></li>
   <li><a href="https://wiki.dgfygk.com#_669">一个特殊情况</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#9__712">9. 接口</a></li>
 <li><a href="https://wiki.dgfygk.com#_716">类</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#TypeScript_720">TypeScript中的类</a></li>
 <li><a href="https://wiki.dgfygk.com#_742">类的继承</a></li>
 <li><a href="https://wiki.dgfygk.com#_766">属性修饰符</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_768">四种属性修饰符</a></li>
   <li><a href="https://wiki.dgfygk.com#_777">属性的简写形式</a></li>
   <li><a href="https://wiki.dgfygk.com#public__800">public 修饰符</a></li>
   <li><a href="https://wiki.dgfygk.com#protected__833">protected 修饰符</a></li>
   <li><a href="https://wiki.dgfygk.com#private__875">private 修饰符</a></li>
   <li><a href="https://wiki.dgfygk.com#readonly__908">readonly 修饰符</a></li>
   <li><a href="https://wiki.dgfygk.com#_935">抽象类</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_interface_1009">接口 interface</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_1013">定义类结构</a></li>
   <li><a href="https://wiki.dgfygk.com#_1040">定义对象结构</a></li>
   <li><a href="https://wiki.dgfygk.com#_1059">定义函数结构</a></li>
   <li><a href="https://wiki.dgfygk.com#_1070">接口之间的继承</a></li>
   <li><a href="https://wiki.dgfygk.com#_1088">接口自动合并</a></li>
   <li><a href="https://wiki.dgfygk.com#_1120">何时使用接口</a></li>
   <li><a href="https://wiki.dgfygk.com#_1126">一些相似的概念</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#1_interfacetype_1128">1. interface和type</a></li>
   <li><a href="https://wiki.dgfygk.com#2_interface_1220">2. interface和抽象类</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_1258">泛型</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_1262">泛型函数</a></li>
 <li><a href="https://wiki.dgfygk.com#_1287">泛型接口</a></li>
 <li><a href="https://wiki.dgfygk.com#_1301">泛型约束</a></li>
 <li><a href="https://wiki.dgfygk.com#_1316">泛型类</a></li>
 <li><a href="https://wiki.dgfygk.com#_1340">类型声明文件</a></li>
 <li><a href="https://wiki.dgfygk.com#TypeScript_1377">TypeScript装饰器</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_1381">装饰器简介</a></li>
 <li><a href="https://wiki.dgfygk.com#_1387">类装饰器</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__1389">1. 基本语法</a></li>
   <li><a href="https://wiki.dgfygk.com#2__1408">2. 应用举例</a></li>
   <li><a href="https://wiki.dgfygk.com#3__1445">3. 关于返回值</a></li>
   <li><a href="https://wiki.dgfygk.com#4__1474">4. 关于构造类型</a></li>
   <li><a href="https://wiki.dgfygk.com#5__1505">5. 替换被装饰的类</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_1546">装饰器工厂</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__1548">1. 什么是装饰器工厂</a></li>
   <li><a href="https://wiki.dgfygk.com#2__1581">2. 装饰器组合及其执行顺序</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_1626">属性装饰器</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1_1628">1.基本语法</a></li>
   <li><a href="https://wiki.dgfygk.com#2__1656">2. 关于属性遮蔽</a></li>
   <li><a href="https://wiki.dgfygk.com#3__1686">3. 应用举例</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_1735">方法装饰器</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__1737">1. 基本语法</a></li>
   <li><a href="https://wiki.dgfygk.com#2__1767">2. 应用举例</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_1818">访问器装饰器</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__1820">1. 基本语法</a></li>
   <li><a href="https://wiki.dgfygk.com#2__1847">2. 应用举例</a></li>
   <li><a href="https://wiki.dgfygk.com#3__1895">3. 访问器装饰器和方法装饰器的区别</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_1900">参数装饰器</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__1902">1. 基本语法</a></li>
   <li><a href="https://wiki.dgfygk.com#2__1925">2. 应用举例</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_1979">参数装饰器</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1__1981">1. 基本语法</a></li>
   <li><a href="https://wiki.dgfygk.com#2__2004">2. 应用举例</a></li>
  </ul></li>
</ul>
<p>
 <br>
</p>
<h2>Typescript简介</h2>
<p>TypeScript是由<strong>微软</strong> 开发的，基于JavaScript的一个<strong>拓展语言</strong></p>
<ol>
 <li>TypeScript<strong>包含了Javascript</strong> 的所有内容，即TypeScript是JavaScript的<strong>超集</strong></li>
 <li>TypeScript增加了静态类型检查、接口、泛型等多种<strong>现代开发特性</strong> ，更适合<strong>大型项目</strong>的开发</li>
 <li>TypeScript需要<strong>编译</strong>为JavaScript，如何交给浏览器或其他JavaScript运行环境执行</li>
</ol>
<h2>为什么需要TypeScript</h2>
<h3>JavaScript今非昔比</h3>
<ol>
 <li>JavaScript当年诞生时的定位时浏览器<strong>脚本语言</strong> ，用于在网页中嵌入一些<strong>简单的逻辑</strong>，而且代码量很少</li>
 <li>随着时间的推移，JavaScript变得越来越流行，如今的JavaScript已经可以<strong>全栈编程</strong>了</li>
 <li>现如今的JavaScript<strong>应用场景</strong> 比当年<strong>丰富</strong> 的多，<strong>代码量</strong> 也比当年<strong>大</strong>很多，随便一个JavaScript项目代码量可以轻松到达几万行甚至几十万行</li>
 <li>然而JavaScript当年<strong>出生简陋</strong> ，没有考虑到如今的应用场景和代码量，逐渐出现了<strong>很多困扰</strong></li>
</ol>
<h3>JavaScript中的困扰</h3>
<h5>1. 不清不楚的数据类型</h5>
<pre><code class="prism language-JavaScript">let welcome = 'hello'
welcome() // 错误的数据类型
</code></pre>
<h5>2. 有漏洞的逻辑</h5>
<pre><code class="prism language-JavaScript">const str = Date.now() % 2 ? '奇数' : '偶数'

if (str !== '奇数') {
  alert('hello')
} else if (str === '偶数') {
  alert('world') // 不可能到达此行
}
</code></pre>
<h5>3. 访问不存在的属性</h5>
<pre><code class="prism language-JavaScript">const obj = { width: 10, height: 15 }
const area = obj.width * obj.heightTow // 调用了不存在的属性
</code></pre>
<h5>4. 低级的拼写错误</h5>
<pre><code class="prism language-JavaScript">const message = 'hello world'
message.toUperCase() // 少了个p
</code></pre>
<h3>TypeScript静态类型检查</h3>
<p>在代码运行前进行检查，发现代码的错误和不合理之处，运行时异常的出现几率，此种检查叫<strong>静态类型检查</strong> ，TypeScript的核心就是静态类型检查，简言之就是<strong>把运行时的错误前置</strong> 。同样的功能，TypeScript的代码量要大于JavaScript，但是由于TypeScript的代码结构更加清晰，在后期代码的<strong>维护</strong>中TypeScript远胜于JavaScript</p>
<h2>编译 TypeScript</h2>
<p>浏览器不能直接运行TypeScript，需要编译为JavaScript再交由浏览器解析器执行。</p>
<h3>1. 命令行编译</h3>
<p>命令行编译可以不写<code>.ts</code>，编译好后生成同名的<code>.js</code>文件这个方法效率低下，一般不使用</p>
<pre><code class="prism language-shell">tsc index.ts
</code></pre>
<h3>2. 自动化编译</h3>
<p><code>init</code>后会生成<code>tsconfig.json</code>文件，在这里可以设置编译JavaScript的版本、严格模式等，同时建议打开配置文件中的<code>noEmitOnError</code>，避免后续出现错误的代码也被编译为<code>.js</code>文件。</p>
<p><code>watch</code>不写监视的文件就是监视全部ts文件的变化，监测到<code>.ts</code>文件变化自动编译为<code>.js</code></p>
<pre><code class="prism language-shell">tsc --init
tsc --watch index.ts # 或tsc -w
</code></pre>
<h2>类型声明</h2>
<p>TypeScript使⽤<code>:</code>来对变量或函数形参，进⾏类型声明：</p>
<pre><code class="prism language-typescript">let a: string // 变量a只能存储字符串类型，不能是包装类，TS官方推荐写法
let b: number // 变量b只能存储数值类型
let c: boolean // 变量c只能存储布尔值

a = 'hello'
b = -10
c = true

// 参数x必须是数字类型，参数y也必须是数字类型，函数返回值也必须是数字类型
// 参数只能是2个，不能是1个或者3个
function count(x: number, y: number): number {

  return x + y
}

let result = count(10, 20)
console.log(result)
</code></pre>
<p>在<code>:</code>后也可以写字⾯量类型，不过实际开发中⽤的不多</p>
<pre><code class="prism language-typescript">let x: '你好' //a的值只能为字符串"你好"，不能为其他
let y: 100 //b的值只能为数字100，不能为其他
</code></pre>
<p>注意：</p>
<p>大写的<code>String</code>、<code>Number</code>、<code>Boolean</code>表示的是new出来的包装对象，变量<code>x</code>可以是包装对象或基本类型，但不推荐这么写，内存不友好</p>
<pre><code class="prism language-typescript">let x: String // 非推荐写法
let y: Number // 非推荐写法
let z: Boolean // 非推荐写法
</code></pre>
<h2>类型推断</h2>
<p>如果不写<code>: 数据类型</code>进行类型声明，TypeScript也会根据我们书写的情况进行推断，并规范数据类型，但面对复杂的数据类型可能会推断出错，因此建议全部进行类型声明</p>
<pre><code class="prism language-typescript">let d = -1 //TypeScript会推断出变量d的类型是数字
d = false // 这里就出错了
</code></pre>
<h2>类型总览</h2>
<h3>JavaScript中的数据类型</h3>
<ol>
 <li>number</li>
 <li>string</li>
 <li>boolean</li>
 <li>undefined</li>
 <li>null</li>
 <li>bigint</li>
 <li>symbol</li>
 <li>object</li>
</ol>
<h3>TypeScript中的数据类型</h3>
<ol>
 <li>JavaScript中的8个数据类型</li>
 <li>6个新类型</li>
 <li>any</li>
 <li>unknow</li>
 <li>never</li>
 <li>void</li>
 <li>tuple</li>
 <li>enum</li>
 <li>2个用于自定义类型的方式</li>
 <li>type</li>
 <li>interface</li>
</ol>
<h2>常用类型与语法</h2>
<h3>1. any</h3>
<p><code>any</code>表示可以是任意类型，⼀旦将变量类型限制为<code>any</code>，那就意味着放弃了对该变量的类型检查。</p>
<pre><code class="prism language-typescript">// 【显式的any】明确的表示a的类型是any
let a: any
// 以下对a的赋值，均⽆警告
a = 100
a = '你好'
a = false

// 【隐式的any】没有明确的表示b的类型是any，但TS主动推断出来b是any
let b
//以下对b的赋值，均⽆警告
b = 100
b = '你好'
b = false
</code></pre>
<p>坑：<code>any</code>类型的变量，可以赋值给任意类型的变量，且不会产生警告，这会破坏掉原来的类型检查</p>
<pre><code class="prism language-typescript">let c:any
c = 9
let x: string
x = c // ⽆警告
x = 9 // 警告
</code></pre>
<h3>2. unknow</h3>
<p><code>unknown</code>表示<strong>未知类型</strong></p>
<ol>
 <li>其可以理解为一个类型安全的<code>any</code>，适用于不确定的数据类型</li>
</ol>
<pre><code class="prism language-typescript">// 设置a的类型为unknown
let a: unknown
//以下对a的赋值，均符合规范
a = 99
a = false
a = '你好'

// 设置x的数据类型为string，和any的区别在这里，类型安全
let x: string
x = a  // 警告：不能将类型"unknown"分配给类型"string"
</code></pre>
<ol>
 <li><code>unknown</code>会强制开发者在使⽤之前进⾏类型检查，从而提供更强的类型安全性。</li>
</ol>
<pre><code class="prism language-typescript">// 设置a的类型为unknown
let a: unknown
a = 'hello'
//第⼀种⽅式：加类型判断
if(typeof a === 'string'){

 x = a
 console.log(x)
}
//第⼆种⽅式：加断⾔
x = a as string
//第三种⽅式：加断⾔
x = &lt;string&gt;a
</code></pre>
<ol>
 <li>读取<code>any</code>类型数据的任何属性都不会报错，<code>unknown</code>正好与之相反。</li>
</ol>
<pre><code class="prism language-typescript">let str1: string
str1 = 'hello'
str1.toUpperCase() //⽆警告

let str2: any
str2 = 'hello'
str2.toUpperCase() //⽆警告

let str3: unknown
str3 = 'hello'
str3.toUpperCase()//警告："str3"的类型为"未知"

(str3 as string).toUpperCase() // 使⽤断⾔强制指定str3的类型为string，⽆警告
</code></pre>
<h3>3. never</h3>
<p><code>never</code>的含义是：任何值都不是，即：不能有值，例如<code>undefined</code>、<code>null</code>、<code>''</code>、<code>0</code>都不行</p>
<ol>
 <li>几乎不用never去直接限制变量，因为没有意义，例如：</li>
</ol>
<pre><code class="prism language-typescript">// 指定a的类型为never, 那就意味着a以后不能存任何的数据了
let a: never
// 以下对a的所有赋值都会有警告
a = 1
a = true
a = undefined
a = null
</code></pre>
<ol>
 <li>never一般是TypeScript主动推断出来的，例如： 把鼠标放在<code>a</code>上提示<code>a</code>为never</li>
</ol>
<pre><code class="prism language-typescript">// 指定a的类型为string
let a: string
// 给a设置⼀个值
a = 'hello'
if (typeof a === 'string') {

 console.log(a.toUpperCase())
} else {

 console.log(a) // TypeScript会推断出此处的a是never，因为没有任何⼀个值符合此处的逻辑
}
</code></pre>
<ol>
 <li><code>never</code>也可⽤于限制函数的返回值，返回<code>never</code>的函数不能具有可访问的终结点，即不可能正常结束</li>
</ol>
<pre><code class="prism language-typescript">// 限制throwError函数不需要有任何返回值，任何值都不⾏，像undeifned、null都不⾏
function throwError(str: string): never {

 throw new Error('程序异常退出:' + str)
}
</code></pre>
<h3>4. void</h3>
<p><code>void</code>即空，通常用于函数返回值，函数不反悔任何值，调用者也不依赖其返回值进行任何操作</p>
<ol>
 <li><code>void</code>通常用于函数返回值声明</li>
</ol>
<pre><code class="prism language-typescript">function logMessage(msg:string):void{

 console.log(msg)
}
logMessage('hello world')
</code></pre>
<p>因为没有编写<code>return</code>指定函数返回值，所以<code>logMessage</code>函数是没有显式返回值的，但会有⼀个隐式返回值<code>undefined</code>，虽然函数返回类型为<code>void</code>，但也是可以接受<code>undefined</code>的，即：<strong><code>undefined</code>是<code>void</code>可以接受的⼀种"空”</strong>。因此以下写法均规范：</p>
<pre><code class="prism language-typescript">// 不写return⽆警告
function logMessage(msg:string):void{

 console.log(msg)
}
// 只写return⽆警告
function logMessage(msg:string):void{

 console.log(msg)
 return;
}
// return undefined⽆警告
function logMessage(msg:string):void{

 console.log(msg)
 return undefined
}
</code></pre>
<ol>
 <li>限制函数返回值时<code>void</code>和<code>undefined</code>的区别：<strong>调用者也不依赖其返回值进行任何操作</strong></li>
</ol>
<pre><code class="prism language-typescript">function logMessage(msg:string):void{

 console.log(msg)
}
let result = logMessage('hello world')
if(result){
    // 此⾏报错：⽆法测试 "void" 类型的表达式的真实性
 console.log('logMessage有返回值')
}
</code></pre>
<pre><code>```prism language-typescript
function logMessage(msg:string):undefined{

 console.log(msg)
}
let result = logMessage('hello world')
if(result){
    // 此⾏⽆警告
 console.log('logMessage有返回值')
}
</code></pre>
<p>也就是说即使我们知道<code>void</code>返回的时<code>undefined</code>我们也不应该关心函数返回值的问题，不应该依赖返回值进行任何操作</p>
<h3>5. object</h3>
<p><code>object</code>与<code>Object</code>在实际开发中使用相对较少，因为范围太大了</p>
<h4>小写<code>object</code></h4>
<p>表示所有非原始类型，可存储对象、函数、数组等</p>
<pre><code class="prism language-typescript">let a: object
// 以下代码，是将⾮原始类型赋给a，所以均符合要求
a = {
   }
a = {
    name: '张三' }
a = [1, 3, 5, 7, 9]
a = function () {
   }
a = new String('123')
class Person {
   }
a = new Person()

// 以下代码，是将原始类型赋给a，有警告
a = 1
a = true 
a = 'hello world'
a = null
a = undefined
</code></pre>
<h4>大写<code>Object</code></h4>
<p>所有可以调用大<code>Object</code>方法的类型，即<strong>除了<code>undefined</code>和<code>null</code>以外的任何值</strong> ，只要沿着原原型链能找到<code>Object</code>上的方法即可，包括几个基本类型。</p>
<pre><code class="prism language-typescript">//b的值必须是Object的实例对象（除去undefined和null的任何值）
let b:Object 
// Object的实例对象，无警告
b = {
   }
b = {
   name:'张三'}
b = [1,3,5,7,9]
b = function(){
   }
b = new String('123')
class Person {
   }
b = new Person()

// 本身不是Object的实例对象，但其包装对象是Object的实例，无警告
b = 1 
b = true 
b = 'hello world'

// 以下代码均有警告
b = null
b = undefined
</code></pre>
<p>可以看出，大写<code>Object</code>比小写<code>object</code>范围更加广泛，因此范围太大了，实际开发中<strong>使用频率极低</strong></p>
<h4>声明对象类型</h4>
<ol>
 <li>实际开发中，限制一般对象，通常使用以下形式</li>
</ol>
<pre><code class="prism language-typescript">// 限制person1对象必须有name属性，age为可选属性
let person1: {
    name: string; age?: number }
// 含义同上，也能⽤分号做分隔
let person2: {
    name: string; age?: number }
// 含义同上，也能⽤换⾏做分隔
let person3: {

  name: string
  age?: number
}
// 如下赋值均可以
person1 = {
    name: '李四', age: 18 }
person2 = {
    name: '张三' }
person3 = {
    name: '王五' }
// 如下赋值不合法，因为person3的类型限制中，没有对gender属性的说明
person3 = {
    name: '王五', gender: '男' }
</code></pre>
<ol>
 <li>索引签名：允许定义对象可以具有<strong>任意数量</strong> 的属性，这些属性的<strong>键值类型可变</strong>，常⽤于描述类型不确定的属性（具有动态属性的对象）</li>
</ol>
<pre><code class="prism language-typescript">// 限制person对象必须有name属性，可选age属性但值必须是数字，同时可以有任意数量、任意类型的其他属性
let person: {

  name: string
  age?: number
  [key: string]: any // 索引签名，完全可以不⽤key这个单词，换成其他的也可以，只需要保证key为字符串，值为任何值
}
// 赋值合法
person = {

  name: '张三',
  age: 18,
  gender: '男',
  city: '深圳'
}
</code></pre>
<h4>声明函数类型</h4>
<pre><code class="prism language-typescript">let count: (a: number, b: number) =&gt; number // 限制count有两个数字类型的参数，返回值也是数字类型的，这里=&gt;是分隔符
// 这里定义函数时就可以省略书写参数和返回值类型检查了
count = function (x, y) {

  return x + y
}
</code></pre>
<p>函数类型声明还可以使用<strong>接口、自定义类型等</strong>方式，后面详细讲解</p>
<h4>声明数组类型</h4>
<pre><code class="prism language-typescript">let arr1: string[]
let arr2: number[]
let arr3: Array&lt;string&gt; // 泛型

arr1 = ['a', 'b', 'c']
arr2 = [1, 2, 3]
arr3 = ['hello', 'world']
</code></pre>
<h3>6. tuple</h3>
<p>元组Tuple是⼀种<strong>特殊的数组类型</strong> ，可以存储<strong>固定数量</strong> 的元素，并且每个元素的<strong>类型是已知的</strong>且可以不同。元组用于精确描述⼀组值的类型，?表示可选元素</p>
<pre><code class="prism language-typescript">// 第⼀个元素必须是 string 类型，第⼆个元素必须是 number 类型。
let arr1: [string, number]
// 第⼀个元素必须是 number 类型，第⼆个元素是可选的，如果存在，必须是 boolean 类型。
let arr2: [number, boolean?]
// 第⼀个元素必须是 number 类型，后⾯的元素可以是任意数量的 string 类型
let arr3: [number, ...string[]]
// 可以赋值
arr1 = ['hello', 123]
arr2 = [100, false]
arr2 = [200]
arr3 = [100, 'hello', 'world']
arr3 = [100]
// 不可以赋值，arr1声明时是两个元素，赋值的是三个
arr1 = ['hello', 123, false]
</code></pre>
<h3>7. enum</h3>
<p>枚举Enum可以定义<strong>一组命名常量</strong>，它能增强代码的可读性，也让代码更好维护。</p>
<p>如下代码的功能是：根据调⽤<code>walk</code>时传⼊的不同参数，执⾏不同的逻辑，存在的问题是调⽤<code>walk</code>时传参时没有任何提示，开发者很容易写错字符串内容，并且用于逻辑判断的<code>up</code>、<code>down</code>、<code>left</code>、<code>right</code>是连续且相关的⼀组值，那此时就特别适合使⽤枚举（enum）</p>
<pre><code class="prism language-typescript">// 定义移动函数
function walk(str: string) {

  if (str === 'up') {

    console.log('向【上】⾛')
  } else if (str === 'down') {

    console.log('向【下】⾛')
  } else if (str === 'left') {

    console.log('向【左】⾛')
  } else if (str === 'right') {

    console.log('向【右】⾛')
  } else {

    console.log('未知⽅向')
  }
}
// 调用移动函数
walk('up')
walk('down')
walk('left')
walk('right')
</code></pre>
<h4>数字枚举</h4>
<p>数字枚举⼀种最常见的枚举类型，其成员的值会<strong>自动递增</strong> ，且数字枚举还具备反向映射的特点，在下面代码的打印中，不难发现：<strong>可以通过值来获取对应的枚举成员名称</strong> 。</p>
<pre><code class="prism language-typescript">// 定义⼀个描述上下左右⽅向的枚举Direction
enum Direction {
    // 一般首字母大写
  Up,
  Down,
  Left,
  Right,
}
console.log(Direction) // 打印 {0: 'Up', 1: 'Down', 2: 'Left', 3: 'Right', Up: 0, Down: 1, Left: 2, Right: 3}

// 反向映射
console.log(Direction.Up) // 0
console.log(Direction[0]) // Up

// 此⾏代码报错，枚举中的属性是只读的
Direction.Up = 'shang'
</code></pre>
<p>也可以指定枚举成员的初始值，其后的成员值会自动递增。</p>
<pre><code class="prism language-typescript">enum Direction {

  Up = 6,
  Down,
  Left,
  Right,
}
console.log(Direction.Up) // 输出: 6
console.log(Direction.Down) // 输出: 7
</code></pre>
<p>使用枚举之后案例的代码可以变得更加直观可读，类型更加安全，同时易于维护。</p>
<pre><code class="prism language-typescript">// 定义⼀个描述上下左右⽅向的枚举Direction
enum Direction {

  Up,
  Down,
  Left,
  Right,
}
// 定义移动函数
function walk(n: Direction) {

  if (n === Direction.Up) {

    console.log('向【上】⾛')
  } else if (n === Direction.Down) {

    console.log('向【下】⾛')
  } else if (n === Direction.Left) {

    console.log('向【左】⾛')
  } else if (n === Direction.Right) {

    console.log('向【右】⾛')
  } else {

    console.log('未知⽅向')
  }
}
// 调用移动函数
walk(Direction.Up)
walk(Direction.Down)
</code></pre>
<h4>字符串枚举</h4>
<p>枚举成员的值是字符串</p>
<pre><code class="prism language-typescript">enum Direction {

  Up = 'up',
  Down = 'down',
  Left = 'left',
  Right = 'right',
}
let dir: Direction = Direction.Up
console.log(dir) // 输出: "up"
</code></pre>
<h4>常量枚举</h4>
<p>官方描述：常量枚举是一种特殊枚举类型，它使⽤<code>const</code>关键字定义，在<strong>编译时会被内联</strong> ，<strong>避免</strong> 生成一些<strong>额外的代码</strong>，</p>
<p><strong>何为编译时被内联？</strong>所谓"内联"其实就是TypeScript在编译时，会将枚举成员引用替换为它们的实际值，而不是生成额外的枚举对象。这可以<strong>减少生成的JavaScript代码量，并提高运行时效性能</strong>。</p>
<p>还是看一下之前的例子：</p>
<pre><code class="prism language-typescript">enum Directions {

  Up,
  Down,
  Left,
  Right,
}
let x = Directions.Up
</code></pre>
<p>在不加<code>const</code>时被编译为：</p>
<pre><code class="prism language-javascript">"use strict";
var Directions;
(function (Directions) {

    Directions[Directions["Up"] = 0] = "Up";
    Directions[Directions["Down"] = 1] = "Down";
    Directions[Directions["Left"] = 2] = "Left";
    Directions[Directions["Right"] = 3] = "Right";
})(Directions || (Directions = {
   }));
let x = Directions.Up;
</code></pre>
<p>加了<code>const</code>后被编译为：</p>
<pre><code class="prism language-javascript">"use strict";
let x = 0 /* Directions.Up */;
</code></pre>
<h3>8. type</h3>
<p>type可以<strong>为任意类型创建别名</strong>，让代码更简洁、可读性更强，同时能更方便地进行类型复用和扩展。</p>
<h4>基本用法</h4>
<p>类型别名使用type关键字定义，type后跟类型名称，例如下面代码中num是类型别名：</p>
<pre><code class="prism language-typescript">type num = number
let price: num
price = 100
</code></pre>
<h4>联合类型</h4>
<p>联合类型是一种高级类型，它表示一个值可以是<strong>几种不同类型之一</strong> 。通过<code>|</code>定义。</p>
<pre><code class="prism language-typescript">type Status = number | string // Status 可以时number类型也可以时string类型
type Gender = '男' | '⼥' // Gender只能是两种字符串'男'或'女'

function printStatus(status: Status) {

  console.log(status)
}
function logGender(str: Gender) {

  console.log(str)
}

printStatus(404)
printStatus('200')
printStatus('501')
logGender('男')
logGender('⼥')
logGender('未知') // 出现警告
</code></pre>
<h4>交叉类型</h4>
<p>交叉类型（Intersection Types）允许<strong>将多个类型合并为一个类型</strong> 。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。通过<code>&amp;</code>定义。</p>
<pre><code class="prism language-typescript">//⾯积
type Area = {

  height: number //⾼
  width: number //宽
}
//地址
type Address = {

  num: number //楼号
  cell: number //单元号
  room: string //房间号
}

// 定义类型House，且House是Area和Address组成的交叉类型
type House = Area &amp; Address
const house: House = {

  height: 180,
  width: 75,
  num: 6,
  cell: 3,
  room: '702',
}
</code></pre>
<h4>一个特殊情况</h4>
<p>先来看看以下两行段代码：</p>
<p>在函数定义时，限制函数返回值为<code>void</code>，那么函数的返回值就必须是空。</p>
<pre><code class="prism language-typescript">function demo(): void {

  // 返回undefined合法
  return undefined

  // 以下返回均不合法
  return 100
  return false
  return null
  return []
}
demo()
</code></pre>
<p>使用<strong>类型声明</strong> 限制函数返回值为<code>void</code>时，<strong>TypeScript并不会严格要求函数返回空</strong></p>
<pre><code class="prism language-typescript">type LogFunc = () =&gt; void // LogFunc类型的函数：不接收参数、返回值为void
const f1: LogFunc = () =&gt; {

  return 100 // 允许返回⾮空值
}
const f2: LogFunc = () =&gt; 200 // 允许返回⾮空值
const f3: LogFunc = function () {

  return 300 // 允许返回⾮空值
}
</code></pre>
<p>为什么会这样？</p>
<p>是为了确保如下代码成立，我们知道<code>Array.prototype.push</code>的返回值是一个数字，而<code>Array.prototype.forEach</code>方法期望其回调的返回类型是<code>void</code>。不过就算拿到了返回值你也不能拿返回值做任何操作，会报错</p>
<pre><code class="prism language-typescript">const src = [1, 2, 3]
const dst = [0]
src.forEach(el =&gt; dst.push(el)) // 这里要求返回的是void，但是箭头省略了`{}`不一定为void
</code></pre>
<h3>9. 接口</h3>
<p>见类后面部分</p>
<h2>类</h2>
<p>这里和Java、C#中的类高度相似，可以直接到接口</p>
<h3>TypeScript中的类</h3>
<pre><code class="prism language-typescript">class Person {

  // 属性声明，在typescript中必须先进行属性声明
  name: string
  age: number
  // 构造器
  constructor(name: string, age: number) {

    // 这里类型声明也是必须的
    this.name = name
    this.age = age
  }
  // ⽅法
  speak() {

    console.log(`我叫：${
     this.name}，今年${
     this.age}岁`)
  }
}
// Person实例
const p1 = new Person('周杰伦', 38)
</code></pre>
<h3>类的继承</h3>
<pre><code class="prism language-typescript">// 继承
class Student extends Person {

  grade: string
  // 构造器
  constructor(name: string, age: number, grade: string) {

    // 本例中若Student类不需要额外的属性，Student的构造器可以省略
    super(name, age)
    this.grade = grade
  }

  // 重写从⽗类继承的⽅法
  override speak() {
    // 这里加override后如果拼写错误会报错
    console.log(`我是学⽣，我叫：${
     this.name}，今年${
     this.age}岁，在读${
     this.grade}年级`)
  }
  // ⼦类⾃⼰的⽅法
  study() {

    console.log(`${
     this.name}正在努⼒学习中......`)
  }
}
</code></pre>
<h3>属性修饰符</h3>
<h4>四种属性修饰符</h4>
<p>| 修饰符 | 含义 | 具体规则 |
 <br>
 |———–|——|———————-|
 <br>
 | public | 公开的 | 可以被：<strong>类内部、子类、类外部</strong>访问 |
 <br>
 | protected | 受保护的 | 可以被：<strong>类内部、子类</strong>访问 |
 <br>
 | private | 私有的 | 可以被：<strong>类内部</strong>访问 |
 <br>
 | readonly | 只读属性 | 属性无法修改 |</p>
<h4>属性的简写形式</h4>
<p>简写前：</p>
<pre><code class="prism language-typescript">class Person {

  public name: string
  public age: number
  constructor(name: string, age: number) {

    this.name = name
    this.age = age
  }
}
</code></pre>
<p>简写后：</p>
<pre><code class="prism language-typescript">class Person {

  constructor(public name: string, public age: number) {
   }
}
</code></pre>
<h4>public 修饰符</h4>
<pre><code class="prism language-typescript">// 父类
class Person {

  // name写了public修饰符，age没写修饰符，最终都是public修饰符
  public name: string
  age: number
  constructor(name: string, age: number) {

    this.name = name
    this.age = age
  }
  speak() {
    // 这里方法没写修饰符，也是public
    // 类的【内部】可以访问public修饰的name和age
    console.log(`我叫：${
     this.name}，今年${
     this.age}岁`)
  }
}
const p1 = new Person('张三', 18)
// 类的【外部】可以访问public修饰的属性
console.log(p1.name)

// 子类
class Student extends Person {

  constructor(name: string, age: number) {

    super(name, age)
  }
  study() {

    // 【⼦类中】可以访问⽗类中public修饰的：name属性、age属性
    console.log(`${
     this.age}岁的${
     this.name}正在努⼒学习`)
  }
}
</code></pre>
<h4>protected 修饰符</h4>
<pre><code class="prism language-typescript">// 父类
class Person {

  // name和age是受保护属性，不能在类外部访问，但可以在【类】与【⼦类】中访问
  constructor(protected name: string, protected age: number) {
   }
  // getDetails是受保护⽅法，不能在类外部访问，但可以在【类】与【⼦类】中访问
  protected getDetails(): string {

    // 类中能访问受保护的name和age属性
    return `我叫：${
     this.name}，年龄是：${
     this.age}`
  } 
  introduce() {
    // introduce是public⽅法，类、⼦类、类外部都能使⽤
    // 类中能访问受保护的getDetails⽅法
    console.log(this.getDetails())
  }
}
const p1 = new Person('杨超越', 18)
// 可以在类外部访问introduce
p1.introduce()
// 以下代码均报错
// p1.getDetails()
// p1.name
// p1.age


// 子类
class Student extends Person {

  constructor(name: string, age: number) {

    super(name, age)
  }
  study() {

    // ⼦类中可以访问introduce
    this.introduce()
    // ⼦类中可以访问name
    console.log(`${
     this.name}正在努⼒学习`)
  }
}
const s1 = new Student('tom', 17)
s1.introduce()
</code></pre>
<h4>private 修饰符</h4>
<pre><code class="prism language-typescript">class Person {

  constructor(
    public name: string,
    public age: number,
    // IDCard属性为私有的(private)属性，只能在【类内部】使⽤
    private IDCard: string
  ) {
   }
  private getPrivateInfo() {

    // 类内部可以访问私有的(private)属性 ------ IDCard
    return `身份证号码为：${
     this.IDCard}`
  }
  getInfo() {

    // 类内部可以访问受保护的(protected)属性 ------ name和age
    return `我叫: ${
     this.name}, 今年刚满${
     this.age}岁`
  }
  getFullInfo() {

    // 类内部可以访问公开的getInfo⽅法，也可以访问私有的getPrivateInfo⽅法
    return this.getInfo() + '，' + this.getPrivateInfo()
  }
}
const p1 = new Person('张三', 18, '110114198702034432')
console.log(p1.getFullInfo())
console.log(p1.getInfo())
// 以下代码均报错
// p1.name
// p1.age
// p1.IDCard
// p1.getPrivateInfo()
</code></pre>
<h4>readonly 修饰符</h4>
<pre><code class="prism language-typescript">class Car {

 constructor(
 public readonly vin: string, //⻋辆识别码，为只读属性
 public readonly year: number,//出⼚年份，为只读属性
 public color: string,
 public sound: string
 ) {
    }
 // 打印⻋辆信息
 displayInfo() {

 console.log(`
 识别码：${
     this.vin},
 出⼚年份：${
     this.year},
 颜⾊：${
     this.color},
 ⾳响：${
     this.sound}
 `);
 }
}
const car = new Car('1HGCM82633A123456', 2018, '⿊⾊', 'Bose⾳响');
car.displayInfo()
// 以下代码均错误：不能修改 readonly 属性
// car.vin = '897WYE87HA8SGDD8SDGHF'; 
// car.year = 2020;
</code></pre>
<h4>抽象类</h4>
<p><strong>概述：</strong>抽象类是一种<strong>无法被实例化</strong> 的类，专门用来定义类的<strong>结构和行为</strong> ，类中可以写<strong>抽象方法</strong> ，也可以写<strong>具体实现</strong> 。抽象类主要用来为其派生类提供一个基础结构，要求其派生类<strong>必须实现</strong>其中的抽象方法。</p>
<p><strong>简记：</strong>抽象类<strong>不能实例化</strong> ，其意义是可以被继承，抽象类里可以有<strong>普通方法</strong> 、也可以有<strong>抽象方法</strong>。</p>
<p>通过以下场景理解抽象类：</p>
<blockquote>
 <p>我们定义一个抽象类<code>Package</code>，表示所有包裹的基本结构，任何包裹都有重量属性<code>weight</code>，包裹都需要计算运费。但不同类型的包裹（如：标准包裹、特快专递）都有不同的运费计算方式，因此用于计算运费的<code>calculate</code>方法是一个抽象方法，必须由具体的子类来实现。</p>
</blockquote>
<pre><code class="prism language-typescript">// Package类
abstract class Package {

  constructor(public weight: number) {
   }
  // 抽象⽅法：⽤来计算运费，不同类型包裹有不同的计算⽅式
  abstract calculate(): number
  // 通⽤⽅法：打印包裹详情
  printPackage() {

    console.log(`包裹重量为: ${
     this.weight}kg，运费为: ${
     this.calculate()}元`)
  }
}
</code></pre>
<pre><code>```prism language-typescript
// 标准包裹StandardPackage继承了Package类，实现了calculate方法
class StandardPackage extends Package {

  constructor(
    weight: number,
    public unitPrice: number // 每公⽄的固定费率
  ) {

    super(weight)
  }
  // 实现抽象⽅法：计算运费
  calculate(): number {

    return this.weight * this.unitPrice
  }
}
// 创建标准包裹实例
const s1 = new StandardPackage(10, 5)
s1.printPackage()
</code></pre>
<pre><code>```prism language-typescript
// ExpressPackage特快专递继承了Package类，实现了calculate方法
class ExpressPackage extends Package {

  constructor(
    weight: number,
    private unitPrice: number, // 每公⽄的固定费率（快速包裹更⾼）
    private additional: number // 超出10kg以后的附加费
  ) {

    super(weight)
  }
  // 实现抽象⽅法：计算运费
  calculate(): number {

    if (this.weight &gt; 10) {

      // 超出10kg的部分，每公⽄多收additional对应的价格
      return 10 * this.unitPrice + (this.weight - 10) * this.additional
    } else {

      return this.weight * this.unitPrice
    }
  }
}
// 创建特快专递实例
const e1 = new ExpressPackage(13, 8, 2)
e1.printPackage()
</code></pre>
<p>那么何时适合使用抽象类呢？</p>
<ol>
 <li>定义通用接口：为一组相关的类定义通用的行为（方法或属性）时。</li>
 <li>提供基础实现：在抽象类中提供某些方法或为其提供基础实现，这样派生类就可以继承这些实现。</li>
 <li>确保关键实现：强制派生类实现一些关键行为。</li>
 <li>共享代码和逻辑：当多个类需要共享部分代码时，抽象类可以避免代码重复。</li>
</ol>
<h3>接口 interface</h3>
<p><code>interface</code>是一种<strong>定义结构</strong> 的方式，主要作用是为【类、对象、函数】等规定<strong>一种契约</strong> ，这样可以确保代码的一致性和类型安全，但要注意 <code>interface</code><strong>只能定义格式，不能包含任何实现</strong></p>
<h4>定义类结构</h4>
<pre><code class="prism language-typescript">// PersonInterface接⼝，⽤于限制Person类的格式
interface PersonInterface {

  name: string
  age: number
  speak(n: number): void
}

// 定义⼀个类 Person，实现 PersonInterface 接⼝
class Person implements PersonInterface {

  constructor(public name: string, public age: number) {
   }
  // 实现接⼝中的 speak ⽅法
  speak(n: number): void {

    for (let i = 0; i &lt; n; i++) {

      // 打印出包含名字和年龄的问候语句
      console.log(`你好，我叫${
     this.name}，我的年龄是${
     this.age}`)
    }
  }
}

// 创建⼀个 Person 类的实例 p1，传⼊名字 'tom' 和年龄 18
const p1 = new Person('tom', 18)
p1.speak(3)
</code></pre>
<h4>定义对象结构</h4>
<pre><code class="prism language-typescript">interface UserInterface {

  name: string
  readonly gender: string // 只读属性
  age?: number // 可选属性
  run: (n: number) =&gt; void
}
const user: UserInterface = {

  name: '张三',
  gender: '男',
  age: 18,
  run(n) {

    console.log(`奔跑了${
     n}⽶`)
  },
}
</code></pre>
<h4>定义函数结构</h4>
<pre><code class="prism language-typescript">interface CountInterface {

  (a: number, b: number): number // 两个number类型参数，返回值也是number类型的函数
}
const count: CountInterface = (x, y) =&gt; {

  return x + y
}
</code></pre>
<h4>接口之间的继承</h4>
<pre><code class="prism language-typescript">interface PersonInterface {

  name: string 
  age: number
}
// StudentInterface 继承了 PersonInterface 接口实现代码复用
interface StudentInterface extends PersonInterface {

  grade: string
}
const stu: StudentInterface = {

  name: '张三',
  age: 25,
  grade: '⾼三',
}
</code></pre>
<h4>接口自动合并</h4>
<p>以下代码重复定义了<code>PersonInterface</code>接口，<code>PersonInterface</code>接口自动合并了，必须实现两个接口中的全部属性和方法</p>
<pre><code class="prism language-typescript">// PersonInterface接⼝
interface PersonInterface {

  // 属性声明
  name: string
  age: number
}
// 给PersonInterface接⼝添加新属性
interface PersonInterface {

  // ⽅法声明
  speak(): void
}
// Person类实现PersonInterface
class Person implements PersonInterface {

  name: string
  age: number
  // 构造器
  constructor(name: string, age: number) {

    this.name = name
    this.age = age
  }
  // ⽅法
  speak() {

    console.log('你好！我是⽼师:', this.name)
  }
}
</code></pre>
<h4>何时使用接口</h4>
<ol>
 <li>定义对象的格式：描述<strong>数据模型、API响应格式、配置对象</strong>等是开发中接口应用最多的场景。</li>
 <li>类的契约：规定一个类需要实现哪些属性和方法。</li>
 <li>自动和合并：一般用于扩展第三方库的类型，这种特性在大型项目中可能会用到。</li>
</ol>
<h4>一些相似的概念</h4>
<h5>1. interface和type</h5>
<p><strong>相同点：</strong></p>
<ul>
 <li><code>interface</code>和<code>type</code>都可以用于定义对象结构，在定义对象结构时两者可以互换。</li>
</ul>
<p><strong>不同点：</strong></p>
<ul>
 <li>
  <p><code>interface</code>：更专注于定义<strong>对象和类</strong> 的结构，支持<strong>继承、合并</strong>。</p>
 </li>
 <li>
  <p><code>type</code>：可以定义<strong>类型别名、联合类型、交叉类型</strong>，但不支持继承和自动合并。</p>
 </li>
</ul>
<p>两者都可以定义对象的结构：</p>
<pre><code class="prism language-typescript">// 使⽤ interface 定义 Person 对象
interface PersonInterface {

  name: string
  age: number
  speak(): void
}
// 使⽤ type 定义 Person 对象
type PersonType = {

  name: string
  age: number
  speak(): void
}
// 使⽤PersonInterface
/* let person: PersonInterface = {
 name:'张三',
 age:18,
 speak(){
 console.log(`我叫：${this.name}，年龄：${this.age}`)
 }
} */
// 使⽤PersonType
let person: PersonType = {

  name: '张三',
  age: 18,
  speak() {

    console.log(`我叫：${
     this.name}，年龄：${
     this.age}`)
  },
}
</code></pre>
<p><code>interface</code>支持继承、合并</p>
<pre><code class="prism language-typescript">interface PersonInterface {

  name: string // 姓名
  age: number // 年龄
}
interface PersonInterface {

  speak: () =&gt; void
}
interface StudentInterface extends PersonInterface {

  grade: string // 年级
}
const student: StudentInterface = {

  name: '李四',
  age: 18,
  grade: '⾼⼆',
  speak() {

    console.log(this.name, this.age, this.grade)
  },
}
</code></pre>
<p><code>type</code>交叉类型可以实现和<code>interface</code>继承、合并一样的效果，但看起来过于复杂，不方便</p>
<pre><code class="prism language-typescript">// 使⽤ type 定义 Person 类型，并通过交叉类型实现属性的合并
type PersonType = {

  name: string // 姓名
  age: number // 年龄
} &amp; {

  speak: () =&gt; void
}
// 使⽤ type 定义 Student 类型，并通过交叉类型继承 PersonType
type StudentType = PersonType &amp; {

  grade: string // 年级
}
const student: StudentType = {

  name: '李四',
  age: 18,
  grade: '⾼⼆',
  speak() {

    console.log(this.name, this.age, this.grade)
  },
}
</code></pre>
<h5>2. interface和抽象类</h5>
<p><strong>相同点：</strong></p>
<ul>
 <li><code>interface</code>和抽象类均可以用于定义一个<strong>类的格式</strong>（应遵循的契约）</li>
</ul>
<p><strong>不同点：</strong></p>
<ul>
 <li>接口只能描述结构，不能有任何实现代码，一个类可以实现多个接口</li>
 <li>抽象类既可以包含抽象方法，也可以包含具体方法，一个类只能继承一个抽象类</li>
</ul>
<p>类实现多个接口：</p>
<pre><code class="prism language-typescript">// FlyInterface 接⼝
interface FlyInterface {

  fly(): void
}
// 定义 SwimInterface 接⼝
interface SwimInterface {

  swim(): void
}
// Duck 类实现了 FlyInterface 和 SwimInterface 两个接⼝
class Duck implements FlyInterface, SwimInterface {

  fly(): void {

    console.log('鸭⼦可以⻜')
  }
  swim(): void {

    console.log('鸭⼦可以游泳')
  }
}

// 创建⼀个 Duck 实例
const duck = new Duck()
duck.fly() // 输出: 鸭⼦可以⻜
duck.swim() // 输出: 鸭⼦可以游泳
</code></pre>
<h2>泛型</h2>
<p>泛型允许我们在定义函数、类或接口时，使⽤类型参数来表示<strong>未指定的类型</strong> ，这些参数在具体<strong>使用时</strong> ，才被指定<strong>具体的类型</strong> （可以不是基本类型，比如<code>type</code>声明的类型），泛型能让同一段代码适用于多种类型，同时仍然保持类型的安全性。</p>
<h3>泛型函数</h3>
<pre><code class="prism language-typescript">// 定义参数和返回值类型的时候我们不确定，使用泛型规定
function logData&lt;T&gt;(data: T): T {

  console.log(data)
  return data
}
// 在调用时，我们确定了具体的类型
logData&lt;number&gt;(100)
logData&lt;string&gt;('hello')
</code></pre>
<p>泛型可以有多个：</p>
<pre><code class="prism language-typescript">// 这里有两个泛型
function logData&lt;T, U&gt;(data1: T, data2: U): T | U {

  console.log(data1, data2)
  return Date.now() % 2 ? data1 : data2
}
logData&lt;number, string&gt;(100, 'hello')
logData&lt;string, boolean&gt;('ok', false)
</code></pre>
<h3>泛型接口</h3>
<pre><code class="prism language-typescript">interface PersonInterface&lt;T&gt; {

  name: string
  age: number
  extraInfo: T // 这里用到了泛型
}
let p1: PersonInterface&lt;string&gt;
let p2: PersonInterface&lt;number&gt;
p1 = {
    name: '张三', age: 18, extraInfo: '⼀个好⼈' }
p2 = {
    name: '李四', age: 18, extraInfo: 250 }
</code></pre>
<h3>泛型约束</h3>
<pre><code class="prism language-typescript">interface LengthInterface {

  length: number
}
// 约束规则是：传⼊的类型T必须具有 length 属性
function logPerson&lt;T extends LengthInterface&gt;(data: T): void {

  console.log(data.length)
}
logPerson&lt;string&gt;('hello')
// 以下行代码报错：因为number不具备length属性
logPerson&lt;number&gt;(100) 
</code></pre>
<h3>泛型类</h3>
<pre><code class="prism language-typescript">class Person&lt;T&gt; {

  constructor(
    public name: string, 
     public age: number, 
     public extraInfo: T // 这里用到了泛型
    ) {
   }
  speak() {

    console.log(`我叫${
     this.name}今年${
     this.age}岁了`)
    console.log(this.extraInfo)
  }
}
// 测试代码1
const p1 = new Person&lt;number&gt;('tom', 30, 250)
// 测试代码2
type JobInfo = {
    // 这里时typo声明的高级类型
  title: string
  company: string
}
const p2 = new Person&lt;JobInfo&gt;('tom', 30, {
    title: '研发总监', company: '发发发科技公司' })
</code></pre>
<h2>类型声明文件</h2>
<p>类型声明文件是TypeScript中的一种特殊文件，通常以<code>.d.ts</code>作为扩展名。它的主要作用是<strong>为现有的JavaScript代码提供类型信息</strong> ，使得TypeScript能够在使用这些JavaScript库或模块时进行<strong>类型检查和提示</strong>。</p>
<p>假设以下代码<code>demo.js</code>是我们引入的库，是用JavaScript写的：</p>
<pre><code class="prism language-javascript">// demo.js
export function add(a, b) {

  return a + b
}
export function mul(a, b) {

  return a * b
}
</code></pre>
<p>我们现在要引入这两个函数，到我们自己的TypeScript项目<code>index</code>.ts中：</p>
<pre><code class="prism language-typescript">// index.ts
import {
    add, mul } from './demo.js' // 这里会报错，无法找到模块"./demo.js"的声明文件，"demo.js"隐式拥有 "any" 类型。
const x = add(2, 3)
const y = mul(4, 5)
console.log(x, y)
</code></pre>
<p>这是我们就要用到类型声明文件<code>demo.d.ts</code>：</p>
<pre><code class="prism language-typescript">// demo.d.ts
declare function add(a: number, b: number): number;
declare function mul(a: number, b: number): number;
export {
    add, mul };
</code></pre>
<p>类型声明文件一般放在<code>@types</code>文件夹下，大多主流库都有支持，一般不需要开发者自己书写。</p>
<h2>TypeScript装饰器</h2>
<blockquote>
 <p>虽然TypeScript5.0版本中可以直接使用类装饰器，但为了确保其他装饰器可用，现阶段使用时，仍建议使用<code>experimentalDecorators</code>配置来开启装饰器支持。</p>
</blockquote>
<h3>装饰器简介</h3>
<ol>
 <li>装饰器自2015年在ECMAScript6中被提出，其本质是一种特殊的<strong>函数</strong>，它可以对类、属性、方法、参数进行扩展，同时能让代码更简洁。</li>
 <li>截止目前，装饰器依然是<strong>实验性特性</strong> ，需要开发者<strong>手动调整配置</strong>，来开启装饰器支持</li>
 <li>装饰器有5种：<strong>类</strong> 装饰器、<strong>属性</strong> 装饰器、<strong>方法</strong> 装饰器、<strong>访问器</strong> 装饰器、<strong>参数</strong>装饰器</li>
</ol>
<h3>类装饰器</h3>
<h4>1. 基本语法</h4>
<pre><code class="prism language-typescript">// Demo装饰器
function Demo(target: any) {

  console.log(target)
}

@Demo // 打印 [class Person]
class Person {

  name: string
  age: number
  constructor(name: string, age: number) {

    this.name = name
    this.age = age
  }
}
</code></pre>
<h4>2. 应用举例</h4>
<p>定义一个装饰器，实现<code>Person</code>实例调用<code>toString</code>时返回<code>JSON.stringify</code>的执行结果。</p>
<pre><code class="prism language-typescript">// 使用装饰器重写toString方法 + 封闭其原型对象
function CustomToString(target: Function) {

  // 向被装饰类的原型上添加自定义的 toString 方法
  target.prototype.toString = function () {

    return JSON.stringify(this) // 这里不能是target，因为target是类本身，而不是类的实例
  }
  // 封闭其原型对象，禁止随意操作其原型对象
  Object.seal(target.prototype)
}

// 使用 CustomToString 装饰器
@CustomToString
class Person {

  constructor(public name: string, public age: number) {
   }
  speak() {

    console.log('你好呀！')
  }
}

/* 测试代码如下 */
let p1 = new Person('张三', 18)
// 输出：{"name":"张三","age":18}
console.log(p1.toString())

// 禁止随意操作其原型对象
interface Person {

  a: any
}
Person.prototype.a = 100 // 此行运行时会报错：Cannot add property a, object is not extensible
console.log(p1.a)
</code></pre>
<h4>3. 关于返回值</h4>
<ul>
 <li>
  <p>类装饰器有返回值：若类装饰器返回一个新的类，那这个新类将<strong>替换</strong>掉被装饰的类。</p>
 </li>
 <li>
  <p>若类装饰器无返回值：或返回<code>undefined</code>，那被装饰的类<strong>不会</strong>被替换。</p>
 </li>
</ul>
<pre><code class="prism language-typescript">function demo(target: Function) {

  // 装饰器有返回值时，该返回值会替换掉被装饰的类
  return class {

    test() {

      console.log(200)
    }
  }
}

@demo
class Person {

  test() {

    console.log(100)
  }
}

console.log(Person) // [class (anonymous)]

const p1 = new Person()
p1.test() // 打印 200
</code></pre>
<h4>4. 关于构造类型</h4>
<p>在TypeScript中，<code>Function</code>类型所表示的范围十分广泛，包括：普通函数、箭头函数、方法等等。但并非<code>Function</code> 类型的函数都可以被 <code>new</code> 关键字实例化，例如箭头函数是不能被实例化的，那么TypeScript中如何声明一个构造类型呢？有以下两种方式：</p>
<pre><code class="prism language-typescript">// 定义Constructor类型，其含义是构造类型
type Constructor = new (...args: any[]) =&gt; {
   }
 // new     表示：该类型是可以用new操作符调用。
 // ...args 表示：构造器可以接受【任意数量】的参数。
 // any[]   表示：构造器可以接受【任意类型】的参数。
 // {}      表示：返回类型是对象(非null、非undefined的对象)。

function test(fn: Constructor) {
   }
class Person {
   }
test(Person)
</code></pre>
<pre><code>```prism language-typescript
// 定义Constructor类型，且包含一个静态属性 country
type Constructor = {

  new (...args: any[]): {
   } // 构造签名
  country: string // country静态属性
}

function test(fn: Constructor) {
   }
class Person {

  static country = 'cn'
}
test(Person)
</code></pre>
<h4>5. 替换被装饰的类</h4>
<p>对于高级一些的装饰器，不仅仅是覆盖一个原型上的方法，还要有更多功能，例如添加新的方法和状态。比如说：下面设计了一个<code>LogTime</code>装饰器，可以给实例添加一个属性，用于记录实例对象的创建时间，又添加了一个方法用于读取创建时间。</p>
<pre><code class="prism language-typescript">// User接口
interface User {

  getTime(): Date
  log(): void
}

// 自定义类型Class
type Constructor = new (...args: any[]) =&gt; {
   }

// 创建一个装饰器，为类添加日志功能和创建时间
function LogTime&lt;T extends Constructor&gt;(target: T) {

  return class extends target {

    createdTime: Date
    constructor(...args: any[]) {

      super(...args)
      this.createdTime = new Date() // 记录对象创建时间
    }
    getTime() {

      return `该对象创建时间为：${
     this.createdTime}`
    }
  }
}

@LogTime
class User {

  constructor(public name: string, public age: number) {
   }
  speak() {

    console.log(`${
     this.name}说：你好啊！`)
  }
}

const user1 = new User('张三', 13)
user1.speak() // 张三说：你好啊！
console.log(user1.getTime()) // 该对象创建时间为：Sat Sep 28 2024 10:13:46 GMT+0800 (中国标准时间)
</code></pre>
<h3>装饰器工厂</h3>
<h4>1. 什么是装饰器工厂</h4>
<p>一个函数返回装饰器函数就是装饰器工厂：</p>
<pre><code class="prism language-typescript">// 定义一个装饰器工厂 LogInfo，它接受一个参数 n，返回一个类装饰器
function LogInfo(n: number) {
    // 通过装饰器工厂，现在可以给装饰器传参了
  // 装饰器函数，target 是被装饰的类
  return function (target: Function) {

    target.prototype.introduce = function () {

      for (let i = 0; i &lt; n; i++) {

        console.log(`我的名字：${
     this.name}，我的年龄：${
     this.age}`)
      }
    }
  }
}

interface Person {

  introduce(): void
}

@LogInfo(3)
class Person {

  constructor(public name: string, public age: number) {
   }
  speak() {

    console.log('你好呀！')
  }
}

let p1 = new Person('张三', 18)
p1.introduce()
</code></pre>
<h4>2. 装饰器组合及其执行顺序</h4>
<p>装饰器可以组合使用，执行顺序为先<strong>由上到下</strong> 的执行<strong>所有</strong> 的<strong>装饰器工厂</strong> ，依次<strong>获取到装饰器</strong> ，然后再<strong>由下到上</strong> 执行所有的<strong>装饰器</strong>。看代码执行：</p>
<pre><code class="prism language-typescript">//装饰器
function test1(target: Function) {

  console.log('test1')
}
//装饰器工厂
function test2() {

  console.log('test2工厂')
  return function (target: Function) {

    console.log('test2')
  }
}
//装饰器工厂
function test3() {

  console.log('test3工厂')
  return function (target: Function) {

    console.log('test3')
  }
}
//装饰器
function test4(target: Function) {

  console.log('test4')
}

@test1
@test2()
@test3()
@test4
class Person {
   }

/*
  控制台打印：
    test2工厂
    test3工厂
    test4
    test3
    test2
    test1
*/
</code></pre>
<h3>属性装饰器</h3>
<h4>1.基本语法</h4>
<pre><code class="prism language-typescript">function Demo(target: object, propertyKey: string) {

  // target: 对于静态属性来说值是类，对于实例属性来说值是类的原型对象
  // propertyKey: 属性名
  console.log(target, propertyKey)
}

class Person {

  @Demo name: string // 实例属性，类的原型对象
  @Demo age: number // 实例属性，类的原型对象
  @Demo static school: string // 静态属性，类

  constructor(name: string, age: number) {

    this.name = name
    this.age = age
  }
}

const p1 = new Person('张三', 18)

// 打印
// {} name
// {} age
// [class Person] school
</code></pre>
<h4>2. 关于属性遮蔽</h4>
<p>如下代码中：当构造器中的<code>this.age = age</code>试图在实例上赋值时，但没有找到，使用实际上是调用了原型上<code>age</code>属性的<code>set</code>方法。</p>
<pre><code class="prism language-typescript">class Person {

  name: string
  age: number // 注意，这只是类型声明，没有age
  constructor(name: string, age: number) {

    this.name = name
    this.age = age
  }
}

let value = 99
// 使用defineProperty给Person原型添加age属性，并配置对应的get与set
Object.defineProperty(Person.prototype, 'age', {

  get() {

    return value
  },
  set(val) {

    value = val
  },
})

const p1 = new Person('张三', 18)
console.log(p1.age) //18
console.log(Person.prototype.age) //18
</code></pre>
<h4>3. 应用举例</h4>
<p>下面代码定义了一个<code>State</code>属性装饰器，来监视属性的修改。</p>
<pre><code class="prism language-typescript">// 声明一个装饰器函数 State，用于捕获数据的修改
function State(target: object, propertyKey: string) {

  // 存储属性的内部值
  let key: string = `__${
     propertyKey}`

  // 使用 Object.defineProperty 替换类的原始属性
  // 重新定义属性，使其使用自定义的 getter 和 setter
  Object.defineProperty(target, propertyKey, {

    get() {

      // 这里取到this身上就可以不同对象用自己的key了，不会出现共用，只不过会在对象上追加一个缓存值``__${propertyKey}``
      return this[key] // 这里不能用`.`，因为key是字符串
    },
    set(newVal: string) {

      console.log(`${
     propertyKey}的最新值为：${
     newVal}`)
      this[key] = newVal
    },

    enumerable: true, // 可枚举
    configurable: true, // 可配置
  })
}

class Person {

  name: string
  // 使用State装饰器
  @State age: number
  school = 'peking university'
  constructor(name: string, age: number) {

    this.name = name
    this.age = age
  }
}

const p1 = new Person('张三', 18)
const p2 = new Person('李四', 30)

p1.age = 80
p2.age = 90

console.log('------------------')
console.log(p1.age) //80
console.log(p2.age) //90
</code></pre>
<h3>方法装饰器</h3>
<h4>1. 基本语法</h4>
<pre><code class="prism language-typescript">function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor) {

  //  target: 对于静态方法来说值是【类】，对于实例方法来说值是【原型对象】。
  //  propertyKey:方法的名称。
  //  descriptor: 方法的描述对象，其中【value】属性是被装饰的【方法】(不仅仅是方法名)，还有可枚举、可配置、可写属性。

  console.log(target)
  console.log(propertyKey)
  console.log(descriptor)
}

class Person {

  constructor(public name: string, public age: number) {
   }
  // Demo装饰实例方法
  @Demo speak() {

    console.log(`你好，我的名字：${
     this.name}，我的年龄：${
     this.age}`)
  }
  // Demo装饰静态方法
  @Demo static isAdult(age: number) {

    return age &gt;= 18
  }
}

Person.isAdult(18)
const p1 = new Person('张三', 18)
p1.speak()
</code></pre>
<h4>2. 应用举例</h4>
<p>下面代码中<code>Logger</code>方法装饰器在方法前后给其增加了一些额外的逻辑，<code>Validate</code>装饰器用于验证数据</p>
<pre><code class="prism language-typescript">// 打印日志的装饰器
function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor) {

  // 保存原始方法
  const original = descriptor.value
  // 替换原始方法
  descriptor.value = function (...args: any[]) {

    console.log(`${
     propertyKey}开始执行......`)
    const result = original.call(this, ...args)
    console.log(`${
     propertyKey}执行完毕......`)
    return result
  }
}

// 验证参数的装饰器工厂
function Validate(maxValue: number) {

  return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {

    // 保存原始方法
    const original = descriptor.value
    // 替换原始方法
    descriptor.value = function (...args: any[]) {

      // 自定义的验证逻辑
      if (args[0] &gt; maxValue) {

        throw new Error('年龄非法！')
      }
      // 如果所有参数都符合要求，则调用原始方法
      return original.apply(this, args)
    }
  }
}

class Person {

  constructor(public name: string, public age: number) {
   }
  @Logger speak() {

    console.log(`你好，我的名字：${
     this.name}，我的年龄：${
     this.age}`)
  }
  @Validate(120)
  static isAdult(age: number) {

    return age &gt;= 18
  }
}

const p1 = new Person('张三', 18)
p1.speak()
console.log(Person.isAdult(100))
</code></pre>
<h3>访问器装饰器</h3>
<h4>1. 基本语法</h4>
<pre><code class="prism language-typescript">function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor) {

  // target:
  //   1. 对于实例访问器来说值是【所属类的原型对象】
  //   2. 对于静态访问器来说值是【所属类】
  // propertyKey:访问器的名称
  // descriptor: 描述对象，一样的可以通过value取得方法

  console.log(target)
  console.log(propertyKey)
  console.log(descriptor)
}

class Person {

  @Demo
  get address() {

    return '中国深圳'
  }
  @Demo
  static get country() {

    return '中国'
  }
}
</code></pre>
<h4>2. 应用举例</h4>
<p>以下代码对<code>Weather</code>类的<code>temp</code>属性的<code>set</code>访问器进行限制，设置的最低温度<code>-50</code>，最高温度<code>50</code></p>
<pre><code class="prism language-typescript">// 限制访问器值设置范围的装饰器
function RangeValidate(min: number, max: number) {

  return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {

    // 保存原始的 setter 方法，以便在后续调用中使用
    const originalSetter = descriptor.set

    // 重写 setter 方法，加入范围验证逻辑
    descriptor.set = function (value: number) {

      // 检查设置的值是否在指定的最小值和最大值之间
      if (value &lt; min || value &gt; max) {

        // 如果值不在范围内，抛出错误
        throw new Error(`${
     propertyKey}的值应该在 ${
     min} 到 ${
     max}之间！`)
      }

      // 如果值在范围内，且原始 setter 方法存在，则调用原始 setter 方法
      if (originalSetter) {

        originalSetter.call(this, value)
      }
    }
  }
}

class Weather {

  private _temp: number
  constructor(_temp: number) {

    this._temp = _temp
  }
  // 设置温度范围在 -50 到 50 之间
  @RangeValidate(-50, 50)
  set temp(value) {

    this._temp = value
  }
  get temp() {

    return this._temp
  }
}

const w1 = new Weather(25)
console.log(w1)
w1.temp = 67
console.log(w1)
</code></pre>
<h4>3. 访问器装饰器和方法装饰器的区别</h4>
<ul>
 <li>描述器<code>descriptor</code>中的<code>key</code>不同：方法装饰器的描述器的<code>key</code>为<code>value</code>，<code>writable</code>，<code>enumerable</code>，<code>configurable</code>，访问器装饰器的描述器<code>key</code>为<code>get</code>，<code>set</code>，<code>enumerable</code>，<code>configurable</code></li>
 <li>返回值类型不同：访问器装饰器返回一个<code>TypedPropertyDescriptor&lt;any&gt;</code> 类型的对象，而方法装饰器返回一个 <code>TypedPropertyDescriptor&lt;T&gt;</code> 类型的对象，其中T`是方法的返回类型</li>
</ul>
<h3>参数装饰器</h3>
<h4>1. 基本语法</h4>
<pre><code class="prism language-typescript">function Demo(target: object, propertyKey: string, parameterIndex: number) {

  // target:
  //    1.如果修饰的是【实例方法】的参数，target 是类的【原型对象】
  //    2.如果修饰的是【静态方法】的参数，target 是【类】
  // propertyKey：参数所在的方法的名称
  // parameterIndex: 参数在函数参数列表中的索引，从 0 开始
  console.log(target) // {}
  console.log(propertyKey) // speak
  console.log(parameterIndex) // 0
}

// 类定义
class Person {

  constructor(public name: string) {
   }
  speak(@Demo message1: any, mesage2: any) {

    console.log(`${
     this.name}想对说：${
     message1}，${
     mesage2}`)
  }
}
</code></pre>
<h4>2. 应用举例</h4>
<p>下面代码定义了一个方法装饰器<code>Validate</code>，同时搭配参数装饰器<code>NotNumber</code>，来对<code>speak</code>方法的参数类型进行限制</p>
<pre><code class="prism language-typescript">// 参数装饰器定义
function NotNumber(target: any, propertyKey: string, parameterIndex: number) {

  // 初始化或获取当前方法的参数索引列表
  let notNumberArr: number[] = target[`__notNumber_${
     propertyKey}`] || []
  // 将当前参数索引添加到列表中
  notNumberArr.push(parameterIndex)
  // 将列表存储回目标对象
  target[`__notNumber_${
     propertyKey}`] = notNumberArr
}

// 方法装饰器定义
function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

  const method = descriptor.value
  descriptor.value = function (...args: any[]) {

    // 获取被标记为不能为空的参数索引列表
    const notNumberArr: number[] = target[`__notNumber_${
     propertyKey}`] || []
    // 检查参数是否为 null 或 undefined
    for (const index of notNumberArr) {

      if (typeof args[index] === 'number') {

        throw new Error(`方法 ${
     propertyKey} 中索引为 ${
     index} 的参数不能是数字！`)
      }
    }
    // 调用原始方法
    return method.apply(this, args)
  }

  return descriptor
}

// 类定义
class Student {

  name: string
  constructor(name: string) {

    this.name = name
  }
  @Validate
  speak(@NotNumber message1: any, message2: any) {

    console.log(`${
     this.name}想对说：${
     message1}，${
     message2}`)
  }
}

// 使用
const s1 = new Student('张三')
s1.speak('100', 200) // 不抛出异常
s1.speak('100', 200) // 抛出异常
</code></pre>
<p>一个 <code>TypedPropertyDescriptor&lt;T&gt;</code> 类型的对象，其中T`是方法的返回类型</p>
<h3>参数装饰器</h3>
<h4>1. 基本语法</h4>
<pre><code class="prism language-typescript">function Demo(target: object, propertyKey: string, parameterIndex: number) {

  // target:
  //    1.如果修饰的是【实例方法】的参数，target 是类的【原型对象】
  //    2.如果修饰的是【静态方法】的参数，target 是【类】
  // propertyKey：参数所在的方法的名称
  // parameterIndex: 参数在函数参数列表中的索引，从 0 开始
  console.log(target) // {}
  console.log(propertyKey) // speak
  console.log(parameterIndex) // 0
}

// 类定义
class Person {

  constructor(public name: string) {
   }
  speak(@Demo message1: any, mesage2: any) {

    console.log(`${
     this.name}想对说：${
     message1}，${
     mesage2}`)
  }
}
</code></pre>
<h4>2. 应用举例</h4>
<p>下面代码定义了一个方法装饰器<code>Validate</code>，同时搭配参数装饰器<code>NotNumber</code>，来对<code>speak</code>方法的参数类型进行限制</p>
<pre><code class="prism language-typescript">// 参数装饰器定义
function NotNumber(target: any, propertyKey: string, parameterIndex: number) {

  // 初始化或获取当前方法的参数索引列表
  let notNumberArr: number[] = target[`__notNumber_${
     propertyKey}`] || []
  // 将当前参数索引添加到列表中
  notNumberArr.push(parameterIndex)
  // 将列表存储回目标对象
  target[`__notNumber_${
     propertyKey}`] = notNumberArr
}

// 方法装饰器定义
function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

  const method = descriptor.value
  descriptor.value = function (...args: any[]) {

    // 获取被标记为不能为空的参数索引列表
    const notNumberArr: number[] = target[`__notNumber_${
     propertyKey}`] || []
    // 检查参数是否为 null 或 undefined
    for (const index of notNumberArr) {

      if (typeof args[index] === 'number') {

        throw new Error(`方法 ${
     propertyKey} 中索引为 ${
     index} 的参数不能是数字！`)
      }
    }
    // 调用原始方法
    return method.apply(this, args)
  }

  return descriptor
}

// 类定义
class Student {

  name: string
  constructor(name: string) {

    this.name = name
  }
  @Validate
  speak(@NotNumber message1: any, message2: any) {

    console.log(`${
     this.name}想对说：${
     message1}，${
     message2}`)
  }
}

// 使用
const s1 = new Student('张三')
s1.speak('100', 200) // 不抛出异常
s1.speak('100', 200) // 抛出异常
</code></pre>
<p>原文链接: https://blog.csdn.net/hehao1421771020/article/details/142616751</p>]]></description><guid isPermaLink="false">/archives/fcb03852-b155-483b-a0de-f47fbb1c5f3b</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sun, 29 Sep 2024 03:33:49 GMT</pubDate></item><item><title><![CDATA[TCP_IP 协议、HTTP 协议和 FTP 协议详解]]></title><link>https://wiki.dgfygk.com/archives/9ad78f54-ca7f-4220-8f4d-bf6d3004d8d8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=TCP_IP%20%E5%8D%8F%E8%AE%AE%E3%80%81HTTP%20%E5%8D%8F%E8%AE%AE%E5%92%8C%20FTP%20%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3&amp;url=/archives/9ad78f54-ca7f-4220-8f4d-bf6d3004d8d8" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#TCPIP__3">一、TCP/IP 协议</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1_7">1、工作原理</a></li>
   <li><a href="https://wiki.dgfygk.com#2TCPIP__17">2、TCP/IP 协议模型被划分为四个层次</a></li>
   <li><a href="https://wiki.dgfygk.com#2TCPIP__27">2、TCP/IP 五层模型</a></li>
   <li><a href="https://wiki.dgfygk.com#3TCPIP__36">3、TCP/IP 七层模型</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#HTTP__51">二、HTTP 网络协议</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1_67">1、网络协议内容</a></li>
   <li><a href="https://wiki.dgfygk.com#2_80">2、请求协议包组成</a></li>
   <li><a href="https://wiki.dgfygk.com#3_87">3、响应协议包组成</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#FTP__95">三、FTP 网络协议</a></li>
</ul>
<p>
 <br>
</p>
<blockquote>
 <p>常见网络协议主要包括 <code>TCP/IP</code> 协议、<code>HTTP</code> 协议和 <code>FTP</code> 协议等。</p>
</blockquote>
<h3>一、TCP/IP 协议</h3>
<blockquote>
 <p><code>TCP/IP</code> 协议（Transmission Control Protocol/Internet Protocol）是互联网最基础的协议，由 <code>网络层的 IP 协议</code> 和 <code>传输层的 TCP 协议</code> 组成。<code>TCP/IP</code> 定义了电子设备如何连入因特网，以及数据如何在它们之间传输的标准，是Internet国际互联网络的基础。</p>
</blockquote>
<p><code>TCP/IP</code> 协议是计算机网络通信中的核心，它确保了数据的准确传输和网络的稳定运行，为各种互联网应用提供了坚实的基础。</p>
<h4>1、工作原理</h4>
<blockquote>
 <p><code>TCP/IP</code> 协议的工作原理主要基于 <code>数据分割</code>、<code>封装成报文</code>、<code>IP寻址</code> 和 <code>路由选择</code> 以及 <code>数据传输</code> 等步骤。</p>
</blockquote>
<p>在发送数据时，发送方会根据应用层传输的数据，将数据分割成适当大小的数据段，并封装成包含发送方和接收方IP地址、端口号等必要信息的报文。然后，通过IP寻址和路由选择，将报文发送到目标主机。接收方在收到报文后，会进行相应的处理，还原数据并校验其完整性。</p>
<p><code>TCP</code> 负责发现传输的问题，并在发现差错时要求重新传输，直到所有数据都安全正确地传输到目的地。</p>
<p><code>IP</code> 则负责给因特网的每一台联网设备规定一个地址。</p>
<p>这种分层结构和各种协议协同工作的方式，使得TCP/IP协议能够高效、可靠地处理网络通信。</p>
<h4>2、TCP/IP 协议模型被划分为四个层次</h4>
<blockquote>
 <p>应用层、传输层 、网络层、网络接口层（或称为链路层） 。
  <br>
  这四个层次从高层到底层，每一层都负责不同的功能，共同实现网络通信。</p>
</blockquote>
<ul>
 <li><strong>应用层：</strong> 应用层是 <code>TCP/IP</code> 协议栈的 <code>最高层</code>，负责处理特定的应用程序细节。它定义了各种应用协议，如HTTP（用于Web浏览）、FTP（用于文件传输）、SMTP（用于电子邮件）等。这些协议规定了应用程序如何发送和接收数据，以及数据的格式和含义。</li>
 <li><strong>传输层：</strong> 传输层负责在源主机和目标主机之间建立、管理和终止会话。它提供了可靠的数据传输服务（如TCP）或不可靠的数据报服务（如UDP）。TCP提供了数据包的 <code>排序</code>、<code>确认</code> 和 <code>重传</code> 机制，确保数据的完整性和可靠性；而UDP则是一种无连接的协议，不保证数据的可靠性，但通常具有更低的延迟和更高的吞吐量。</li>
 <li><strong>网络层：</strong> 网络层负责将数据包（IP数据报）从源主机路由到目标主机。它处理IP地址的解析和路由选择，确保数据包能够正确地到达目的地。网络层的主要协议是 <code>IP</code>，它定义了数据包的格式和如何在网络上传输这些数据包。此外，ICMP（Internet Control Message Protocol）和其他协议也在这一层起作用，用于网络控制和错误报告。</li>
 <li><strong>网络接口层（或称为链路层）：</strong> 这是 <code>TCP/IP</code> 协议栈的最低层，负责接收IP数据报，并发送到指定的网络上。它还处理数据帧的发送和接收，包括数据的封装和解封装，以及物理地址（如MAC地址）的解析。网络接口层通常与特定的硬件和网络技术（如以太网、WiFi等）紧密相关。</li>
</ul>
<p>这四个层次协同工作，共同实现了TCP/IP网络中的通信。每一层都使用下一层提供的服务，并向上一层提供服务。这种分层结构使得TCP/IP协议栈具有高度的灵活性和可扩展性，能够适应各种不同的网络环境和应用需求。</p>
<h4>2、TCP/IP 五层模型</h4>
<blockquote>
 <p>应用层、传输层、网络层、数据链路层 和 物理层。</p>
</blockquote>
<ul>
 <li><strong>应用层：</strong> 所有用户所面向的应用程序的统称，如HTTP、FTP等协议都工作在这一层。</li>
 <li><strong>传输层：</strong> 实现端到端的数据传输，主要协议有TCP和UDP。TCP提供可靠的数据传输服务，而UDP则是一种无连接的协议。</li>
 <li><strong>网络层：</strong> 实现数据包在不同网络之间的传输，主要定义了IP地址格式，使用IP协议进行路由选择和转发。</li>
 <li><strong>数据链路层（或称为网络接口层）：</strong> 负责接收IP数据包并通过网络发送，或者从网络上接收物理帧，抽出IP数据报交给IP层。</li>
 <li><strong>物理层：</strong> 将比特流转换为物理信号，并通过电缆、光纤等物理媒介进行传输。</li>
</ul>
<h4>3、TCP/IP 七层模型</h4>
<blockquote>
 <p>应用层、表示层、会话层、传输层、网络层、数据链路层 和 物理层 。
  <br>
  在五层模型中，表示层和会话层的功能已经合并到了应用层。</p>
</blockquote>
<ul>
 <li><strong>应用层：</strong> 网络服务与最终用户的一个接口，如HTTP、FTP、SMTP等协议。</li>
 <li><strong>表示层：</strong> 数据的表示、安全、压缩，涉及格式如JPEG、ASCII、加密格式等。</li>
 <li><strong>会话层：</strong> 建立、管理、终止会话，对应主机进程之间的会话。</li>
 <li><code>传输层、网络层、数据链路层和物理层的功能与五层模型中的相应层次相同。</code></li>
 <li><strong>传输层：</strong> 实现端到端的数据传输，主要协议有TCP和UDP。TCP提供可靠的数据传输服务，而UDP则是一种无连接的协议。</li>
 <li><strong>网络层：</strong> 实现数据包在不同网络之间的传输，主要定义了IP地址格式，使用IP协议进行路由选择和转发。</li>
 <li><strong>数据链路层（或称为网络接口层）：</strong> 负责接收IP数据包并通过网络发送，或者从网络上接收物理帧，抽出IP数据报交给IP层。</li>
 <li><strong>物理层：</strong> 将比特流转换为物理信号，并通过电缆、光纤等物理媒介进行传输。</li>
</ul>
<p>TCP/IP协议的四层模型，五层模型和七层模型都是对网络通信过程的一种抽象描述，帮助人们更好地理解和管理网络通信。</p>
<h3>二、HTTP 网络协议</h3>
<blockquote>
 <p>HTTP 网络协议（HyperText Transfer Protocol，超文本传输协议）是一种用于 <code>在计算机网络上传输超文本</code> 的协议。它属于 <code>应用层协议</code>，工作于 <code>客户端-服务器架构</code> 之上，通过 <code>请求-响应模式</code> 进行通信。</p>
</blockquote>
<p>在基于B/S结构下互联网通信过程中，所有在网络中传递信息都是保存在 <code>HTTP 网络协议包</code> 。</p>
<ul>
 <li>简单快速：客户向服务器请求服务时，只需传送请求方法和路径，支持 <code>客户/服务器模式</code> ，网络中传递信息都是 <code>二进制</code> 。</li>
 <li>灵活：HTTP <code>允许传输任意类型</code> 的数据对象，正在传输的类型由Content-Type加以标记。</li>
 <li>网络协议包有一组含有相关信息的 <code>16位二进制数据</code>，0000（IP地址）、0000（端口号）、0000（资源文件名）0000。</li>
 <li><code>无连接</code>：HTTP 不需要在客户端和服务器之间建立持久的连接，每次请求完成后就会关闭连接，减少了网络开销。</li>
 <li><code>无状态</code>：HTTP 对事务的处理没有记忆能力，每次请求都需要提供完整的请求信息，服务器不会记住之前发生的请求和响应。</li>
 <li>面向对象：HTTP 可以传输任何类型的数据对象，包括HTML页面、图像、视频、音频和其他类型的文件。</li>
 <li><code>无阻塞</code>：HTTP 不会限制客户端的请求数量和大小，使得服务器可以处理大量的请求。</li>
 <li><code>可缓存</code>：HTTP 允许客户端缓存服务器响应的内容，以提高响应速度和减少网络流量。</li>
</ul>
<p>HTTP 是数据的传输协议，无论是桌面端的浏览器还是移动端的应用程序，都用于在Web浏览器和Web服务器之间传输HTML页面、API接口等动态和静态资源文件。此外，HTTP协议在API接口设计中也发挥着重要作用，它使得不同语言、不同框架的应用程序在接口层面得到统一，方便数据的交互与共享。</p>
<h4>1、网络协议内容</h4>
<p>HTTP 网络协议的内容分为：<code>HTTP请求协议包</code> 和 <code>HTTP响应协议包</code> 。是HTTP网络协议中的两个重要组成部分，分别负责向服务器发送请求和从服务器接收响应。</p>
<p>请求协议包：</p>
<ul>
 <li>在浏览器准备发送请求时，负责创建一个HTTP请求协议包。浏览器将 <code>请求信息</code> 以 <code>二进制形式</code> 保存在Http请求协议包各个空间，由浏览器负责将HTTP请求协议包推送到指定服务端计算机。</li>
</ul>
<p>响应协议包：</p>
<ul>
 <li>HTTP服务器在定位到被访问的资源文件之后，负责创建一个HTTP响应协议包。HTTP服务器将定位文件内容或者文件命令以 <code>二进制形式</code> 写入到HTTP响应协议包各个空间，由HTTP服务器负责将HTTP响应协议包推送回发起请求的浏览器上。</li>
</ul>
<p>客户端和服务器之间的一次HTTP通信过程：<code>客户端</code> 首先会发送一个 <code>HTTP 请求协议包</code> 给 <code>服务器</code> ，<code>服务器</code> 在接收到请求后，会 <code>处理该请求</code> 并生成一个相应的 <code>HTTP 响应协议包</code> 返回给 <code>客户端</code> 。</p>
<p>HTTP请求协议包和HTTP响应协议包是HTTP通信中的两个重要载体，它们负责在客户端和服务器之间传递请求和响应数据，实现了Web应用中的数据传输和交互功能。</p>
<h4>2、请求协议包组成</h4>
<ul>
 <li><strong>请求行：</strong> 包含请求方法（如GET、POST等）、请求URL（不包括域名）以及HTTP协议版本等信息。请求方法指定了客户端希望执行的操作，如获取资源、提交数据等。</li>
 <li><strong>请求头：</strong> 包含了关于请求的附加信息，如客户端的类型、请求的来源、所接受的响应类型等。这些信息有助于服务器更好地理解请求，并作出相应的响应。</li>
 <li><strong>空行：</strong> 用来分隔请求头和请求体。</li>
 <li><strong>请求体：</strong> 包含了客户端发送给服务器的实际数据，例如表单数据、文件内容等。不是所有的请求都需要请求体，例如GET请求通常不包含请求体。
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff9de52dbbd6759e2c8842a90d222df84.png&amp;size=m" alt=""></li>
</ul>
<h4>3、响应协议包组成</h4>
<ul>
 <li><strong>状态行：</strong> 包含HTTP协议版本、状态码以及状态描述。状态码是一个三位数字，用于表示请求的处理结果，如200表示成功，404表示未找到资源等。状态描述则是对状态码的简单描述，帮助人们更好地理解响应状态。</li>
 <li><strong>响应头：</strong> 与请求头类似，包含了关于响应的附加信息，如服务器的类型、响应的长度、内容类型等。</li>
 <li><strong>空行：</strong> 同样用来分隔响应头和响应体。</li>
 <li><strong>响应体：</strong> 包含了服务器返回给客户端的实际数据，通常是请求的资源内容。
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F22df1d4d3e558c2a4ecdb2de25bd3fc2.png&amp;size=m" alt=""></li>
</ul>
<h3>三、FTP 网络协议</h3>
<p>FTP（File Transfer Protocol，文件传输协议）是用于在网络上进行 <code>文件传输</code> 的一套标准协议。它工作在OSI模型的第七层，即应用层，并使用TCP传输而不是UDP。</p>
<p>在客户与服务器建立连接前，需要经过一个 <code>三次握手</code> 的过程，确保客户与服务器之间的连接是可靠的，并且是面向连接的，为数据传输提供可靠保证。</p>
<p>FTP允许用户以文件操作的方式（如文件的增、删、改、查、传送等）与另一主机相互通信。用户并不真正登录到自己想要存取的计算机上成为完全用户，而是可以用FTP程序访问远程资源，实现用户往返传输文件、目录管理以及访问电子邮件等，即使双方计算机可能配有不同的操作系统和文件存储方式。</p>
<p>FTP的 <code>工作原理</code> 主要包括客户端连接到FTP服务器、建立控制连接、用户身份验证、传输模式选择和执行文件传输等步骤。其中，传输模式可以是主动模式或被动模式，由服务器决定。在文件传输期间，数据连接用于在客户端和服务器之间传输文件数据。</p>
<p>FTP协议具有多种适用场合，包括传输大文件、网站维护、远程备份、多人协作以及跨平台传输等。例如，FTP可以快速传输大文件，支持断点续传，节省时间和成本；同时，它也可以方便地用于网站文件的上传、下载和目录结构维护；此外，FTP还可以作为远程备份工具，保障数据的安全性，并支持压缩和加密功能进一步提高备份数据的安全性。</p>
<p>FTP网络协议在网络文件传输中发挥着重要的作用，其可靠性和灵活性使得它在各种场合中得到了广泛的应用。
 <br>
 ​</p>
<p>原文链接: https://blog.csdn.net/Reggie97/article/details/137384456</p>]]></description><guid isPermaLink="false">/archives/9ad78f54-ca7f-4220-8f4d-bf6d3004d8d8</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sun, 29 Sep 2024 03:33:27 GMT</pubDate></item><item><title><![CDATA[Kafka是什么，以及如何使用SpringBoot对接Kafka]]></title><link>https://wiki.dgfygk.com/archives/79706af0-4f35-4c15-aee3-c34ac91dcb28</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Kafka%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8SpringBoot%E5%AF%B9%E6%8E%A5Kafka&amp;url=/archives/79706af0-4f35-4c15-aee3-c34ac91dcb28" width="1" height="1" alt="" style="opacity:0;">
<h2>系列文章目录</h2>
<p><a href="https://blog.csdn.net/u011709538/article/details/133356141">上手第一关，手把手教你安装kafka与可视化工具kafka-eagle</a>
 <br>
 <a href="https://blog.csdn.net/u011709538/article/details/133791167">架构必备能力——kafka的选型对比及应用场景</a>
 <br>
 <a href="https://blog.csdn.net/u011709538/article/details/133892745">Kafka存取原理与实现分析，打破面试难关</a>
 <br>
 <a href="https://blog.csdn.net/u011709538/article/details/133997855">防止消息丢失与消息重复——Kafka可靠性分析及优化实践</a></p>
<hr>
<p>
 <br>
</p>
<h4>Kafka是什么，以及如何使用SpringBoot对接Kafka</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_0">系列文章目录</a></li>
 <li><a href="https://wiki.dgfygk.com#_16"></a></li>
 <li><a href="https://wiki.dgfygk.com#Kafka_26">一、Kafka与流处理</a></li>
 <li><a href="https://wiki.dgfygk.com#Spring_BootKafkaDemo_35">二、Spring Boot与Kafka的整合Demo</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1_springboot_37">1. 新建springboot工程</a></li>
 <li><a href="https://wiki.dgfygk.com#2_Kafka_48">2. 添加Kafka依赖</a></li>
 <li><a href="https://wiki.dgfygk.com#3_Kafka_60">3. 配置Kafka</a></li>
 <li><a href="https://wiki.dgfygk.com#4_Kafka_70">4. 创建Kafka生产者</a></li>
 <li><a href="https://wiki.dgfygk.com#5_Kafka_95">5. 创建Kafka消费者</a></li>
 <li><a href="https://wiki.dgfygk.com#6__115">6. 应用程序入口</a></li>
 <li><a href="https://wiki.dgfygk.com#_145">三、启动与验证</a></li>
 <li><a href="https://wiki.dgfygk.com#KafkaTemplate__163">四、KafkaTemplate 介绍</a></li>
 <li><a href="https://wiki.dgfygk.com#_191">总结</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F21b9e58f396f3ec9deb900175d039263.png%23pic_center&amp;size=m" alt="">
 <br>
 <code>继上一次教大家手把手安装kafka后，今天我们直接来到入门实操教程，也就是使用SpringBoot该怎么对接和使用kafka。当然，在一开始我们也会比较细致的介绍一下kafka本身。那么话不多说，马上开始今天的学习吧</code></p>
<blockquote>
 <p>📕作者简介：战斧，从事金融IT行业，有着多年一线开发、架构经验；爱好广泛，乐于分享，致力于创作更多高质量内容
  <br>
  📗本文收录于 <a href="https://blog.csdn.net/u011709538/category_12454817.html">kafka</a> 专栏，有需要者，可直接订阅专栏实时获取更新
  <br>
  📘高质量专栏 <a href="https://blog.csdn.net/u011709538/category_12400343.html">云原生</a>、<a href="https://blog.csdn.net/u011709538/category_12356838.html">RabbitMQ</a>、<a href="https://blog.csdn.net/u011709538/category_9161843.html">Spring全家桶</a> 等仍在更新，欢迎指导
  <br>
  📙<a href="https://blog.csdn.net/u011709538/category_12370704.html">Zookeeper</a> Redis <a href="https://blog.csdn.net/u011709538/category_12363619.html">dubbo</a> docker netty等诸多框架，以及架构与分布式专题即将上线，敬请期待</p>
</blockquote>
<h2>一、Kafka与流处理</h2>
<p>我们先来看看比较正式的介绍：Kafka是一种<strong>流处理平台</strong> ，由LinkedIn公司创建，现在是Apache下的开源项目。Kafka通过发布/订阅机制实现消息的异步传输和处理。它<strong>具有高吞吐量、低延迟、可伸缩性和可靠性</strong>等优点，使其成为了流处理和实时数据管道的首选解决方案</p>
<p>介绍其实是比较清晰的，如果你是第一次接触”<strong>流处理</strong>“概念，我们也可以做一点解释，流处理指的是对连续、实时产生的数据流进行实时处理、计算和分析的过程。</p>
<blockquote>
 <p>假设你正在玩一款在线游戏，其他玩家的动作和游戏事件会实时地传到服务器上。这些事件就形成了一条数据流。在流处理中，我们会对这条数据流进行实时处理，例如计算每个玩家的分数、监控游戏区域内的异常情况、统计玩家在线时长等等。这样，游戏管理员就可以实时地监控和管理游戏，而不需要等到游戏结束才进行操作。
  <br>
  类似的，流处理还可以应用在其他实时性要求比较高的场景中，例如金融交易、物联网、实时监测等。通过对数据流进行实时处理，我们可以更加精准地掌握数据变化的情况，并及时做出反应和调整，</p>
</blockquote>
<h2>二、Spring Boot与Kafka的整合Demo</h2>
<h3>1. 新建springboot工程</h3>
<p>如果你没有现成的Spring boot项目，那么我们可以使用IDEA自带的Spring Initializr 来创建一个spring-boot的项目</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F92894ec02b72d28f90ac938f4a0efd4a.png&amp;size=m" alt=""></p>
<p>此时我们可以直接选择使用Apache Kafka，另外项目还可以加个Spring Web准备让前台调用</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fab1586ffa946bea8bac8db5bb06da067.png&amp;size=m" alt=""></p>
<h3>2. 添加Kafka依赖</h3>
<p>如果你不是像上述一样新建的项目，那你也可以选择在已有的Spring Boot应用程序中使用Kafka，那么你需要在pom.xml文件中添加以下依赖：</p>
<pre><code class="prism language-xml">&lt;dependency&gt;
      &lt;groupId&gt;org.springframework.kafka&lt;/groupId&gt;
      &lt;artifactId&gt;spring-kafka&lt;/artifactId&gt;
      &lt;version&gt;2.8.11&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<h3>3. 配置Kafka</h3>
<p>在application.properties文件中添加以下配置：</p>
<pre><code>spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=test_group
</code></pre>
<p>这里我们指定了Kafka服务器的地址和端口，并配置了消费者组的ID，关于消费者组的概念，其实就是某一些消费者具备相同的功能，因此会把他们设为同一个消费者组，这样他们就不会重复消费同一条消息了。更具体地原理，我们会在之后地篇章中介绍。</p>
<h3>4. 创建Kafka生产者</h3>
<p>在Kafka中，生产者是发送消息的应用程序或服务。在Spring Boot中，我们可以使用KafkaTemplate类来创建Kafka生产者</p>
<pre><code class="prism language-java">package com.zhanfu.kafkademo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class KafkaService {


    @Autowired
    private KafkaTemplate&lt;String, String&gt; kafkaTemplate;

    public void sendMessage(String message) {

        kafkaTemplate.send("test_topic", message);
    }
}
</code></pre>
<p>这里我们使用@Autowired注解来自动注入KafkaTemplate，并使用send方法将消息发送到名为"test_topic"的Kafka主题中。</p>
<hr>
<h3>5. 创建Kafka消费者</h3>
<p>在Kafka中，消费者是接收并处理订阅主题消息的应用程序或服务。在Spring Boot中，我们可以使用@KafkaListener注解来创建Kafka消费者。</p>
<pre><code class="prism language-java">package com.zhanfu.kafkademo.listener;

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class KafkaLis {


    @KafkaListener(topics = "test_topic", groupId = "test_group")
    public void receiveMessage(String message) {

        System.out.println("Received message: " + message);
    }
}
</code></pre>
<h3>6. 应用程序入口</h3>
<p>现在我们已经完成了Spring Boot和Kafka的整合。我们可以启动Spring Boot应用程序，然后发送消息并消费它，以测试我们的应用程序是否正确地与Kafka集成。</p>
<pre><code class="prism language-java">package com.zhanfu.kafkademo.controller;

import com.zhanfu.kafkademo.service.KafkaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageController {


    @Autowired
    private KafkaService kafkaService;

    @GetMapping("/send/{message}")
    public String sendMessage(@PathVariable String message) {

        kafkaService.sendMessage(message);
        return "Message sent successfully";
    }
}
</code></pre>
<p>在这个例子中，我们使用@Autowired注解来自动注入KafkaProducer，并通过发送消息的方法来调用sendMessage方法。最终项目整体框架如图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2c50c3e11eb6fe02955ac3aab7897df4.png&amp;size=m" alt=""></p>
<h2>三、启动与验证</h2>
<p>首先自然是启动 Kafka ，怎么启动可参考 <a href="https://blog.csdn.net/u011709538/article/details/133356141">《上手第一关，手把手教你安装kafka与可视化工具kafka-eagle》</a>，然后是启动我们的Spring Boot项目</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Feae8f634a0adb706f4cdd381badb6afb.png&amp;size=m" alt=""></p>
<p>然后在浏览器中输入</p>
<pre><code class="prism language-bash">http://127.0.0.1:8080/send/hello
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7349d1cc4642aafb27dce58a44a2fdd7.png&amp;size=m" alt=""></p>
<p>最后检查我们的项目日志：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5085a05278292abc3752e2bdf334c744.png&amp;size=m" alt=""></p>
<p>可以看到，整个发送和接收的流程都走通了</p>
<h2>四、KafkaTemplate 介绍</h2>
<p>不难看出，在Springboot中，使用kafka的关键在于 <code>KafkaTemplate</code>， 它是 Spring 提供的 Kafka 生产者模版，用于向 Kafka 集群发送消息。并且把 Kafka 的生产者客户端封装成了一个 Spring Bean，提供更加方便易用的 API。</p>
<p>它有三个主要属性：</p>
<ul>
 <li><code>producerFactory</code>：生产者工厂类，用于创建 KafkaProducer 实例。</li>
 <li><code>defaultTopic</code>：默认主题名称，如果在发送消息时没有指定主题名称，则使用该默认主题。</li>
 <li><code>messageConverter</code>：消息转换器，用于将消息对象转换为 Kafka ProducerRecord</li>
</ul>
<p>它的主要方法：</p>
<ul>
 <li>send(ProducerRecord<k,v> record)：向指定的 Kafka 主题发送一条消息。ProducerRecord 包含了主题名称、分区编号、Key 和 Value 等信息。</k,v></li>
 <li>send(String topic, V data)：向指定的 Kafka 主题发送一条消息。</li>
 <li>send(String topic, K key, V data)：向指定的 Kafka 主题发送一条消息，并指定消息的 Key。</li>
 <li>execute(ProducerCallback<k,v> callback)：使用回调方式发送消息，可以自定义消息的创建过程和错误处理过程。</k,v></li>
 <li>inTransaction()：启用事务，多个 send 方法调用将被包装在一个事务中，保证 Kafka 事务的原子性。</li>
</ul>
<p>除了上述方法外，KafkaTemplate 还提供了其他方法，如 <em>sendDefault()</em> 、<em>sendOffsetsToTransaction()</em> 等，可以根据实际需要进行选择和使用。</p>
<p><strong>需要注意的是，在使用 KafkaTemplate 发送消息时应该注意消息的序列化方式、主题和分区的选择以及错误处理等问题，以保证消息的可靠性和正确性。</strong></p>
<p>当然，很多同学可能还注意到一个细节，我们在上面的Demo中，我们直接将其 @Autowired进我们的代码中，这是怎么做到的呢？换句话说，这个 <code>KafkaTemplate</code> 为什么自己就会被spring 容器管理的呢？其实这得益于SpringBoot中对Kafka有了很多自动配置的内容。如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5afab844617c7095417c20f7712e0ee1.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F037bfeb1b18e353b215a2bcddeb18a81.png&amp;size=m" alt=""></p>
<p>如上图，相信对Spring Boot熟悉的同学看到 <code>ConditionalOnClass</code> 、 <code>ConditionalOnMissingBean</code> 应该就明白了。其实Spring Boot 早就贴心的为我们预留了这些自动配置，只要我们引入了 spring-kafka 包，使得项目中出现了 KafkaTemplate 类，那么它就能被自动配置并存入Spring 容器内</p>
<h2>总结</h2>
<p><code>今天我们通过一个Demo讲解了在SpringBoot中如何对接Kafka，也介绍了下关键类 KafkaTemplate ，得益于Spring Boot 的自动配置，开发者要做的配置内容其实并不多，使用也主要是依赖其提供的API，相对简单，相信大家很容易也都学会了，那么在后面的过程中，我们将继续学习其使用，并且会着重讲解 Kafka 的原理与结构</code></p>
<p>原文链接: https://blog.csdn.net/u011709538/article/details/133681195</p>]]></description><guid isPermaLink="false">/archives/79706af0-4f35-4c15-aee3-c34ac91dcb28</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 03:33:09 GMT</pubDate></item><item><title><![CDATA[Spring Boot 中实现任务后台处理的几种常见方式]]></title><link>https://wiki.dgfygk.com/archives/c291e882-8395-4f01-8b0d-abed9194bc68</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%20%E4%B8%AD%E5%AE%9E%E7%8E%B0%E4%BB%BB%E5%8A%A1%E5%90%8E%E5%8F%B0%E5%A4%84%E7%90%86%E7%9A%84%E5%87%A0%E7%A7%8D%E5%B8%B8%E8%A7%81%E6%96%B9%E5%BC%8F&amp;url=/archives/c291e882-8395-4f01-8b0d-abed9194bc68" width="1" height="1" alt="" style="opacity:0;">
<p>​<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fdirect%2F00c61038e0e3496b88bd13b9a293e811.png&amp;size=m" alt=""></p>
<p>博客主页: <a href="https://blog.csdn.net/2301_76419561?spm=1010.2135.3001.5343" title="南来_北往">南来_北往</a></p>
<p>系列专栏：<a href="https://blog.csdn.net/2301_76419561/category_12686718.html?spm=1001.2014.3001.5482" title="Spring Boot实战">Spring Boot实战</a></p>
<hr>
<h2>前言</h2>
<p>在现代应用程序中，后台处理对于处理发送电子邮件、处理文件、生成报告等任务至关重要。 Spring Boot 提供了多种机制来高效地实现后台任务。本文探讨了在 Spring Boot 中处理后台处理的各种方法，包括异步方法、任务调度和使用消息系统。</p>
<h2><strong>使用 @Async 注解</strong></h2>
<ul>
 <li>通过在方法上添加 <code>@Async</code> 注解，可以将该方法标记为异步执行。当调用被 <code>@Async</code> 注解的方法时，它会在一个单独的线程中运行，从而实现后台处理。</li>
</ul>
<pre><code class="java">@Service
public class MyService {
    @Async
    public void performTask() {
        // 后台处理逻辑
    }
}
</code></pre>
<h2><strong>使用 TaskExecutor</strong></h2>
<ul>
 <li>Spring Boot 提供了 <code>TaskExecutor</code> 接口，可以通过实现该接口来创建自定义的任务执行器。然后，可以在需要后台处理的地方注入 <code>TaskExecutor</code>，并使用它的 <code>execute()</code> 方法来提交任务。</li>
</ul>
<pre><code class="java">@Service
public class MyService {
    private final TaskExecutor taskExecutor;

    @Autowired
    public MyService(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    public void performTask() {
        taskExecutor.execute(() -&gt; {
            // 后台处理逻辑
        });
    }
}
</code></pre>
<h2><strong>使用 Scheduled Tasks</strong></h2>
<ul>
 <li>Spring Boot 支持定时任务的执行，可以使用 <code>@Scheduled</code> 注解来标记方法为定时任务。通过配置调度表达式或 cron 表达式，可以指定任务的执行频率和时间。</li>
</ul>
<pre><code class="java">@Service
public class MyService {
    @Scheduled(fixedRate = 5000) // 每5秒执行一次
    public void performTask() {
        // 后台处理逻辑
    }
}
</code></pre>
<h2><strong>使用消息队列</strong></h2>
<ul>
 <li>使用消息队列（如 RabbitMQ、Kafka 等）可以实现任务的异步处理和解耦。生产者将任务发送到队列中，消费者从队列中获取任务并执行。这种方式适用于分布式系统中的任务处理。</li>
</ul>
<p>这些是 Spring Boot 中实现任务后台处理的几种常见方式，根据具体的需求和场景选择合适的方式进行处理。</p>
<p>原文链接: https://blog.csdn.net/2301_76419561/article/details/142456269</p>]]></description><guid isPermaLink="false">/archives/c291e882-8395-4f01-8b0d-abed9194bc68</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 03:32:46 GMT</pubDate></item><item><title><![CDATA[QQ机器人搭建]]></title><link>https://wiki.dgfygk.com/archives/32dbcd44-f1ba-4878-a2bf-141681103be9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=QQ%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%90%AD%E5%BB%BA&amp;url=/archives/32dbcd44-f1ba-4878-a2bf-141681103be9" width="1" height="1" alt="" style="opacity:0;">
<h3>使用QQ官方机器人Python SDK和三方框架搭建QQ群聊机器人</h3>
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#QQPython_SDKQQ_0">使用QQ官方机器人Python SDK和三方框架搭建QQ群聊机器人</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#_4">前言</a></li>
   <li><a href="https://wiki.dgfygk.com#_35">编写机器人代码</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_45">机器人监听群聊进行文字回复</a></li>
   <li><a href="https://wiki.dgfygk.com#_131">机器人监听群聊进行图片回复</a></li>
   <li><a href="https://wiki.dgfygk.com#_170">机器人监听群聊进行文件发送</a></li>
   <li><a href="https://wiki.dgfygk.com#_209">机器人监听群聊进行视频发送</a></li>
   <li><a href="https://wiki.dgfygk.com#_248">机器人监听群聊进行语音发送</a></li>
   <li><a href="https://wiki.dgfygk.com#_290">致谢和更新</a></li>
  </ul></li>
</ul>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F99f8e97af9c944ad97672b69bfff27ce.png%23pic_center&amp;size=m" alt=""></p>
<h4>前言</h4>
<p>本文基于 <a href="https://github.com/tencent-connect/botpy">QQ官方Python SDK</a> 和 <a href="https://qm.qq.com/q/wUALbOZCmW">三方框架aka🐱(进群了解)</a> 搭建QQ群聊机器人，可以实现全局监听群聊。</p>
<p><strong>介绍</strong>
 <br>
 QQ官方机器人Python SDK是QQ提供的一个用于搭建QQ机器人的开发工具包。通过使用该SDK，我们可以编写Python代码来实现QQ机器人的各种功能，如在群聊内接收消息、发送消息等。虽然官方机器人已经满足了基本需求，但是给到开发者的权限很少，无法全局监听群聊消息，本文提供将官方和三方机器人融合的python库：<code>QQbot_Python</code>，实现全局监听的QQ机器人体系。</p>
<p><strong>准备工作</strong>
 <br>
 在开始之前，确保你已经具备以下准备工作：</p>
<ul>
 <li>[x] 已安装Python环境（建议使用Python 3.x版本）</li>
 <li>[x] 拥有一个QQ账号（用于创建三方机器人）</li>
 <li>[x] 获得了官方QQ机器人的APPID和APPSERCET（用于创建官方机器人）</li>
 <li>[x] 安装三方框架aka🐱（用于创建三方机器人）</li>
</ul>
<p><strong>安装相关SDK</strong>
 <br>
 使用pip命令来安装QQ官方机器人Python SDK和三方QQ机器人Python SDK：</p>
<pre><code class="prism language-bash">pip install qq-botpy QQbot_Python -i https://pypi.org/simple
</code></pre>
<p><strong>获取官方QQ机器人的APPID和APPSERCET</strong>
 <br>
 在使用SDK之前，我们需要先创建一个QQ官方机器人账号，并获取其账号的APPID和APPSERCET。具体步骤如下：</p>
<ol>
 <li>打开 <a href="https://q.qq.com/bot/">QQ官方机器人管理平台</a></li>
 <li>创建一个新的机器人账号，按照<a href="https://bot.q.qq.com/wiki">教程</a>完成相关设置</li>
 <li>在机器人管理平台中登录刚刚创建的机器人账号，获取其账号的APPID和APPSERCET</li>
</ol>
<p><strong>配置官方机器人IP白名单</strong>
 <br>
 在创建好了QQ机器人账号之后，我们打开<a href="https://q.qq.com/bot/">QQ官方机器人管理平台</a>，在开发栏中找到开发设置，点击后在界面可以找到IP白名单，在这里你需要配置你的公网IP。如果你是本地开发，你可以使用<code>curl ipconfig.io</code>或者<a href="https://www.lddgo.net/network/getmyip">公网IP查询网站</a>来获取你的公网IP（⚠️本地开发时公网IP随时会变化）；如果你已经要部署在服务器上面了，一般的云服务器都配置了公网IP。最后，将你获取的公网IP填入QQ官方机器人管理平台并保存即可。</p>
<p><strong>配置三方机器人服务器</strong>
 <br>
 你需要安装<a href="https://qm.qq.com/q/wUALbOZCmW">三方框架aka🐱(进群下载)</a>，并且启动正向ws和http监听，ws的端口需要是3001，http的端口需要是3000。</p>
<h4>编写机器人代码</h4>
<p>接下来，我们开始编写机器人代码。</p>
<p><strong>创建配置文件</strong>
 <br>
 在你自己创建的项目的目录下添加配置文件：config.yaml</p>
<pre><code class="prism language-yaml">appid: "" # 填入你在前面步骤获取的APPID
secret: "" # 填入你在前面步骤获取的APPSERCET
</code></pre>
<h5>机器人监听群聊进行文字回复</h5>
<p>首先，分为两种情况：1.你没有配置三方服务器，你将无法享受三方机器人服务。2.你配置了三方机器人并且满足前面提到的要求。</p>
<hr>
<p>情况一：未配置三方机器人
 <br>
 我们导入botpy库，创建机器人实例，进行群聊事件的监听，示例代码如下：</p>
<pre><code class="prism language-python"># -*- coding: utf-8 -*-
import asyncio
import os

import botpy
from botpy import logging
from botpy.ext.cog_yaml import read
from botpy.message import GroupMessage, Message

test_config = read(os.path.join(os.path.dirname(__file__), "config.yaml"))

_log = logging.get_logger()


class MyClient(botpy.Client):
    async def on_ready(self):
        _log.info(f"robot 「{
     self.robot.name}」 on_ready!")

    async def on_group_at_message_create(self, message: GroupMessage):
        messageResult = await message._api.post_group_message(
            group_openid=message.group_openid,
              msg_type=0, 
              msg_id=message.id,
              content=f"收到了消息：{
     message.content}")
        _log.info(messageResult)


if __name__ == "__main__":
    # 通过预设置的类型，设置需要监听的事件通道
    # intents = botpy.Intents.none()
    # intents.public_messages=True

    # 通过kwargs，设置需要监听的事件通道
    intents = botpy.Intents(public_messages=True)
    client = MyClient(intents=intents)
    client.run(appid=test_config["appid"], secret=test_config["secret"])
</code></pre>
<p>运行结果如下：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F32d4e97b4d1747e9b498aa81c63dee36.png&amp;size=m" alt="">
 <br>
 关于官方机器人更多的示例代码请参考：<a href="https://github.com/tencent-connect/botpy/tree/master/examples">官方完整示例</a></p>
<hr>
<p>情况二：配置了三方机器人
 <br>
 我们导入QQbot_Python库，创建机器人实例，进行群聊事件的监听，示例代码如下：</p>
<pre><code class="prism language-python"># -*- coding: utf-8 -*-
import os
import QQbot_Python
from QQbot_Python import logging
from QQbot_Python.ext.cog_yaml import read
from QQbot_Python.message import GroupMessage

test_config = read(os.path.join(os.path.dirname(__file__), "config.yaml"))

_log = logging.get_logger()


class MyClient(QQbot_Python.Client):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.show_heartbeat_info = False
        self.show_system_message = True

    async def on_group_at_message_create(self, message: GroupMessage):
        group_id = message.third_msg.get("group_id")
        user_id = message.third_msg.get("user_id")
        message_id = message.third_msg.get("message_id")
        _log.info(f"收到群{
     group_id}的@消息，用户{
     user_id}，消息{
     message_id}")
        await message.send_text(group_id=group_id,user_id=user_id,text="你好，我是机器人")


if __name__ == "__main__":
    intents = QQbot_Python.Intents(public_messages=True)
    client = MyClient(intents=intents)
    client.run(appid=test_config["appid"], secret=test_config["secret"])
</code></pre>
<p>运行结果如下：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F68e49294130a4f179e4dad98291cce24.png&amp;size=m" alt=""></p>
<h5>机器人监听群聊进行图片回复</h5>
<p>目前官方机器人并不支持群聊回复本地图片，具体以官方文档更新为准。但是使用本文的思路，可以实现回复图片功能，需要配置三方。示例代码如下：</p>
<pre><code class="prism language-python"># -*- coding: utf-8 -*-
import os
import QQbot_Python
from QQbot_Python import logging
from QQbot_Python.ext.cog_yaml import read
from QQbot_Python.message import GroupMessage

test_config = read(os.path.join(os.path.dirname(__file__), "config.yaml"))

_log = logging.get_logger()


class MyClient(QQbot_Python.Client):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.show_heartbeat_info = False
        self.show_system_message = True

    async def on_group_at_message_create(self, message: GroupMessage):
        group_id = message.third_msg.get("group_id")
        user_id = message.third_msg.get("user_id")
        message_id = message.third_msg.get("message_id")
        _log.info(f"收到群{
     group_id}的@消息，用户{
     user_id}，消息{
     message_id}")
        if "/发送图片" in message.content:
            await message.reply(content="发送成功！")
            await message.send_image(group_id=group_id,summary="这是图片描述",file_image="1.png")


if __name__ == "__main__":
    intents = QQbot_Python.Intents(public_messages=True)
    client = MyClient(intents=intents)
    client.run(appid=test_config["appid"], secret=test_config["secret"])
</code></pre>
<p>具体运行如下：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe52be20103ba4ea3b320c2cb61d34f6c.png&amp;size=m" alt=""></p>
<h5>机器人监听群聊进行文件发送</h5>
<p>目前官方机器人并不支持群聊回复本地文件，具体以官方文档更新为准。但是使用本文的思路，可以实现回复图片功能。需要配置三方。示例代码如下：</p>
<pre><code class="prism language-python"># -*- coding: utf-8 -*-
import os
import QQbot_Python
from QQbot_Python import logging
from QQbot_Python.ext.cog_yaml import read
from QQbot_Python.message import GroupMessage

test_config = read(os.path.join(os.path.dirname(__file__), "config.yaml"))

_log = logging.get_logger()


class MyClient(QQbot_Python.Client):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.show_heartbeat_info = False
        self.show_system_message = True

    async def on_group_at_message_create(self, message: GroupMessage):
        group_id = message.third_msg.get("group_id")
        user_id = message.third_msg.get("user_id")
        message_id = message.third_msg.get("message_id")
        _log.info(f"收到群{
     group_id}的@消息，用户{
     user_id}，消息{
     message_id}")
        if "/发送文件" in message.content:
            await message.reply(content="发送成功！")
            await message.send_file(group_id=group_id,file_name="名称可自定义",file_path="botpy.log")


if __name__ == "__main__":
    intents = QQbot_Python.Intents(public_messages=True)
    client = MyClient(intents=intents)
    client.run(appid=test_config["appid"], secret=test_config["secret"])
</code></pre>
<p>运行结果如下：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5994bba2c49b4b44bcbecc7d3eb22d49.png&amp;size=m" alt=""></p>
<h5>机器人监听群聊进行视频发送</h5>
<p>目前官方机器人并不支持群聊回复本地视频，具体以官方文档更新为准。但是使用本文的思路，可以实现回复图片功能。需要配置三方。示例代码如下：</p>
<pre><code class="prism language-python"># -*- coding: utf-8 -*-
import os
import QQbot_Python
from QQbot_Python import logging
from QQbot_Python.ext.cog_yaml import read
from QQbot_Python.message import GroupMessage

test_config = read(os.path.join(os.path.dirname(__file__), "config.yaml"))

_log = logging.get_logger()


class MyClient(QQbot_Python.Client):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.show_heartbeat_info = False
        self.show_system_message = True

    async def on_group_at_message_create(self, message: GroupMessage):
        group_id = message.third_msg.get("group_id")
        user_id = message.third_msg.get("user_id")
        message_id = message.third_msg.get("message_id")
        _log.info(f"收到群{
     group_id}的@消息，用户{
     user_id}，消息{
     message_id}")
        if "/发送视频" in message.content:
            await message.reply(content="发送成功！")
            await message.send_voice(group_id=group_id, file_path="test.mp4",file_name="可以自定义名称")


if __name__ == "__main__":
    intents = QQbot_Python.Intents(public_messages=True)
    client = MyClient(intents=intents)
    client.run(appid=test_config["appid"], secret=test_config["secret"])
</code></pre>
<p>运行结果如下：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff7c14b3e771e41bb96bb200c098e6991.png&amp;size=m" alt=""></p>
<h5>机器人监听群聊进行语音发送</h5>
<p>目前官方机器人并不支持群聊回复语音，具体以官方文档更新为准。但是使用本文的思路，可以实现回复图片功能。发送的语音文件格式只支持silk格式。需要配置三方。示例代码如下：</p>
<pre><code class="prism language-python"># -*- coding: utf-8 -*-
import os
import QQbot_Python
from QQbot_Python import logging
from QQbot_Python.ext.cog_yaml import read
from QQbot_Python.message import GroupMessage

test_config = read(os.path.join(os.path.dirname(__file__), "config.yaml"))

_log = logging.get_logger()


class MyClient(QQbot_Python.Client):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.show_heartbeat_info = False
        self.show_system_message = True

    async def on_group_at_message_create(self, message: GroupMessage):
        group_id = message.third_msg.get("group_id")
        user_id = message.third_msg.get("user_id")
        message_id = message.third_msg.get("message_id")
        _log.info(f"收到群{
     group_id}的@消息，用户{
     user_id}，消息{
     message_id}")
        if "/发送语音" in message.content:
            await message.reply(content="发送成功！")
            await message.send_record(group_id=group_id,file_path="my.ntsilk")


if __name__ == "__main__":
    intents = QQbot_Python.Intents(public_messages=True)
    client = MyClient(intents=intents)
    client.run(appid=test_config["appid"], secret=test_config["secret"])
</code></pre>
<p>运行结果如下：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F83c66eb85c3042a290ffa76852c1a9b5.png&amp;size=m" alt=""></p>
<hr>
<p>除此之外，还有<strong>发送引用消息</strong> 和<strong>发送私聊消息</strong>功能，在这里就不赘述了。</p>
<h4>致谢和更新</h4>
<p><strong><em>本项目采用的都是开源技术</em></strong></p>
<p><strong>致谢</strong> ：<a href="https://github.com/tencent-connect/botpy">QQ官方开发者</a> \| 不方便透露姓名的三方</p>
<blockquote>
 <p>上次更新时间: 9/28/2024, PM</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbf663b2b821c44efa4e7d59bf04e5816.jpeg%23pic_center&amp;size=m" alt="">
 <br>
 如果你想继续进行基于本文进行QQ机器人自定义搭建，请转至此篇文章阅读：
 <br>
 ➡️ [[于 2024/9/25 第2次更新] QQ 腾讯官方机器人搭建（更新中）](https://blog.csdn.net/qq_71745595/article/details/140822468)
 <br>
 👻 <a href="https://qm.qq.com/q/wUALbOZCmW">交流学习</a></p>
<p>原文链接: https://blog.csdn.net/qq_71745595/article/details/142602191</p>]]></description><guid isPermaLink="false">/archives/32dbcd44-f1ba-4878-a2bf-141681103be9</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 03:32:22 GMT</pubDate></item><item><title><![CDATA[【计算机网络】Tcp报文的组成，Tcp是如何实现可靠传输的？]]></title><link>https://wiki.dgfygk.com/archives/8de1e0e6-2a1c-40df-9ee0-ed204116bdba</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E3%80%91Tcp%E6%8A%A5%E6%96%87%E7%9A%84%E7%BB%84%E6%88%90%EF%BC%8CTcp%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%8F%AF%E9%9D%A0%E4%BC%A0%E8%BE%93%E7%9A%84%EF%BC%9F&amp;url=/archives/8de1e0e6-2a1c-40df-9ee0-ed204116bdba" width="1" height="1" alt="" style="opacity:0;">
<h2>Tcp的报文组成</h2>
<p>TCP（传输控制协议）是计算机网络中一种重要的传输协议，其报文组成包括多个字段，每个字段具有特定的含义。以下是TCP报文头的主要组成部分：</p>
<ol>
 <li>
  <p><strong>源端口号（Source Port）</strong>：占用16位，表示发送数据的应用程序在源主机上的端口号。</p>
 </li>
 <li>
  <p><strong>目的端口号（Destination Port）</strong>：占用16位，表示接收数据的应用程序在目的主机上的端口号。</p>
 </li>
 <li>
  <p><strong>序列号（Sequence Number）</strong>：占用32位，表示数据流中的字节顺序，主要用于数据重组和管理。</p>
 </li>
 <li>
  <p><strong>确认号（Acknowledgment Number）</strong>：占用32位，只有在ACK标志位为1时有效，表示接收方期望接收的下一个字节的序号。</p>
 </li>
 <li>
  <p><strong>数据偏移（Data Offset）</strong>：占用4位，表示TCP头部的大小，以32位字为单位，确定数据部分的起始位置。</p>
 </li>
 <li>
  <p><strong>保留位（Reserved）</strong>：占用6位，保留供将来使用，一般设置为0。</p>
 </li>
 <li>
  <p><strong>标志位（Flags）</strong>：占用6位，包含多个控制位，例如URG（紧急指针）、ACK（确认）、PSH（推送）、RST（重置）、SYN（同步）和FIN（结束）等。</p>
 </li>
 <li>
  <p><strong>窗口大小（Window Size）</strong>：占用16位，表示接收方当前可接收的字节数，用于流量控制。</p>
 </li>
 <li>
  <p><strong>校验和（Checksum）</strong>：占用16位，用于错误检测，确保数据在传输过程中未受到损坏。</p>
 </li>
 <li>
  <p><strong>紧急指针（Urgent Pointer）</strong>：占用16位，仅当URG标志位为1时有效，表明紧急数据的偏移量。</p>
 </li>
 <li>
  <p><strong>选项（Options）</strong>：可变长度，用于支持一些扩展功能，如最大报文大小等。</p>
 </li>
 <li>
  <p><strong>填充（Padding）</strong>：用以确保TCP头部的总长度为32位的整数倍。</p>
 </li>
</ol>
<p>以上是TCP报文的基本结构，通过这些字段，TCP能够实现可靠的数据传输和流量控制，为互联网应用提供了重要的支持。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F03b349065236497888adfe65e3a2b98f.jpeg&amp;size=m" alt=""></p>
<h2>什么是可靠传输？</h2>
<p>可靠传输是指在数据通信过程中，确保数据从发送方准确、完整地传递到接收方的一种机制。这一过程涉及多个关键特性，以保证数据的完整性和传输的有效性。以下是可靠传输的一些主要特征：</p>
<ol>
 <li>
  <p><strong>数据完整性</strong>：接收方能够确认接收到的数据与发送方发送的数据完全一致，确保没有数据丢失或损坏。</p>
 </li>
 <li>
  <p><strong>顺序传输</strong>：数据按照发送的顺序到达接收方，有助于避免因网络拥塞或路由变化导致的数据顺序混乱。</p>
 </li>
 <li>
  <p><strong>确认应答</strong>：接收方在成功接收数据之后，发送确认信号（ACK）给发送方，发送方根据确认信号判断数据是否成功送达。</p>
 </li>
 <li>
  <p><strong>超时重传</strong>：当接收方未收到某些数据包，或检测到数据包损坏时，能够请求发送方重新发送这些数据包，确保所有数据最终都能被完整接收。</p>
 </li>
 <li>
  <p><strong>流量控制</strong>：通过调节发送方的数据传输速率，防止接收方因处理速度较慢而导致的缓冲区溢出，从而维持网络传输的稳定性。</p>
 </li>
 <li>
  <p><strong>拥塞控制</strong>：当网络出现拥堵时，传输协议能够动态调整数据发送速率，减少网络负载，避免网络崩溃。</p>
 </li>
</ol>
<p>可靠传输通常由传输层的协议提供，例如TCP（传输控制协议），通过实施上述机制，确保应用层数据在不可靠的网络环境中安全、有效地传输。它对需要高质量数据传输的应用（如文件传输、网页浏览和在线客服等）至关重要。</p>
<h2>Tcp是如何实现可靠传输</h2>
<h3>顺序传输和确认应答机制</h3>
<h4>顺序传输</h4>
<p>顺序传输指的是TCP确保数据包按照发送的顺序到达接收方。具体实现机制如下：</p>
<ul>
 <li>
  <p><strong>序列号</strong>：每个TCP段都有一个序列号，用于标识数据在数据流中的位置。序列号的分配通常是字节级别的，保证发送的数据能够被接收方按照正确的顺序重组成原始数据。</p>
 </li>
 <li>
  <p><strong>重组数据</strong>：接收方在接收到多个TCP段后，会根据序列号对数据进行排序，确保数据按照发送顺序排列，尽管网络中的数据包可能因路由不同而异步到达。</p>
 </li>
 <li>
  <p><strong>丢包处理</strong>：如果接收方收到的数据包中缺少某个序列号的数据段，它会立即注意到，并停止确认后续的段，从而促使发送方重传丢失的数据。</p>
 </li>
</ul>
<p>这种机制特别重要，因为许多应用层协议（如HTTP和FTP）要求数据按顺序处理，以确保数据的完整性和逻辑性。</p>
<h4>确认应答</h4>
<p>确认应答机制用于确保发送的数据包成功到达接收方。具体工作原理如下：</p>
<ul>
 <li><strong>确认号（ACK）</strong>：接收方在成功接收数据段后，会发送一个确认应答给发送方，确认应答中包含下一个期望接收的字节的序列号。这表示接收方已成功接收到序列号之前的所有字节。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F48c9efc4105942fab95955da2ba07590.jpeg&amp;size=m" alt=""></p>
<h3>超时重传机制</h3>
<ul>
 <li><strong>超时重传</strong>：发送方在发送数据后，会启动一个计时器，等待接收方的确认应答。如果在预定时间内没有收到确认，应发送方会重传未确认的数据段。</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc3e2efce63a543e9844554ce78e7d585.jpeg&amp;size=m" alt=""></p>
<h3>三次握手和四次挥手机制</h3>
<p>TCP（三次握手）是一种用于建立连接的协议，它通过三次信息交换过程来确保双方的通信能力和状态，从而实现可靠的数据传输。以下是三次握手的详细描述：</p>
<h4>三次握手过程</h4>
<ol>
 <li>
  <p><strong>第一次握手（SYN）</strong>：</p>
 </li>
 <li>
  <p>客户端向服务器发送一个SYN（Synchronize）报文段，请求建立连接。该报文包含客户端的初始序列号（ISN）。</p>
 </li>
 <li>
  <p>此时，客户端进入SYN_SENT状态，等待服务器的响应。</p>
 </li>
 <li>
  <p><strong>第二次握手（SYN-ACK）</strong>：</p>
 </li>
 <li>
  <p>服务器收到客户端的SYN报文后，响应一个SYN-ACK（Synchronize-Acknowledgment）报文段。</p>
 </li>
 <li>
  <p>该报文不仅确认了客户端的请求（确认号加1），同时也发送了服务器自己的初始序列号（ISN）。</p>
 </li>
 <li>
  <p>服务器在此时进入SYN_RECEIVED状态，等待客户端的进一步确认。</p>
 </li>
 <li>
  <p><strong>第三次握手（ACK）</strong>：</p>
 </li>
 <li>
  <p>客户端收到服务器的SYN-ACK报文后，发送一个ACK（Acknowledgment）报文段，作为对服务器的确认，该报文确认号为服务器序列号加1。</p>
 </li>
 <li>
  <p>此时，客户端进入ESTABLISHED状态，表示连接已成功建立。同时，服务器收到此ACK报文后，也进入ESTABLISHED状态。</p>
 </li>
</ol>
<h4>握手机制的重要性</h4>
<ul>
 <li><strong>确认双方有效性</strong>：三次握手确保了客户端和服务器双方都能正常工作，并且可以进行数据传输。</li>
 <li><strong>初始化序列号的交换</strong>：通过序列号的交换，双方能够了解到各自的数据流起始位置，从而保证后续数据的顺序和完整性。</li>
 <li><strong>避免错误连接</strong>：如果客户端已经有一个与服务器的连接，通过三次握手机制能够避免错误的连接请求。</li>
</ul>
<p>通过三次握手，TCP协议确保了连接的可靠性和稳定性，为后续的数据传输奠定了良好的基础。过程使得TCP成为一个面向连接、可靠、顺序传输的协议，广泛应用于互联网各种场景中。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F132409cfc0f341948ae1557f286dfaca.jpeg&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>TCP（传输控制协议）中的四次挥手是用于终止连接的过程，它确保双方在断开连接时能安全、有效地完成数据传输的关闭。以下是四次挥手的详细描述：</p>
<h4>四次挥手过程</h4>
<ol>
 <li>
  <p><strong>第一次挥手（FIN）</strong>：</p>
 </li>
 <li>
  <p>主动关闭连接的一方（通常是客户端）发送一个FIN（Finish）报文段，表示它已经完成数据的发送，希望关闭连接。</p>
 </li>
 <li>
  <p>发送FIN后，该方进入FIN_WAIT_1状态，等待对方的确认。</p>
 </li>
 <li>
  <p><strong>第二次挥手（ACK）</strong>：</p>
 </li>
 <li>
  <p>被动关闭连接的一方（通常是服务器）收到FIN报文后，发送一个ACK（Acknowledgment）报文段作为确认，确认号为收到的序列号加1。</p>
 </li>
 <li>
  <p>发送ACK后，服务器进入CLOSE_WAIT状态，表示它已经收到关闭请求，但还可能有数据需要发送给客户端。</p>
 </li>
 <li>
  <p><strong>第三次挥手（FIN）</strong>：</p>
 </li>
 <li>
  <p>服务器在确认客户端的FIN后，发送一个自己的FIN报文段，这表示服务器已经没有数据要发送了，请求关闭连接。</p>
 </li>
 <li>
  <p>服务器发送FIN后，进入LAST_ACK状态，等待客户端的确认。</p>
 </li>
 <li>
  <p><strong>第四次挥手（ACK）</strong>：</p>
 </li>
 <li>
  <p>客户端收到服务器的FIN报文后，发送一个ACK报文段作为确认，确认号为服务器的序列号加1。</p>
 </li>
 <li>
  <p>客户端在发送ACK后进入TIME_WAIT状态，等待定时间以确保服务器收到ACK，然后释放连接资源。</p>
 </li>
 <li>
  <p>服务器在收到ACK后进入CLOSED状态，连接正式关闭。</p>
 </li>
</ol>
<h4>挥手机制的重要性</h4>
<ul>
 <li><strong>确保数据完整性</strong> ：四次挥手确保所有数据在连接关闭前都能够完整传输，避免数据丢失。</li>
 <li><strong>明确的关闭状态</strong>：通过明确的状态转变，双方可以确认彼此已经结束数据传输，并安全地关闭连接。</li>
 <li><strong>对称性</strong>：双方都通过FIN和ACK报文段参与到连接的终止中，使得连接关闭过程相对对称，增加了可靠性。</li>
</ul>
<p>在TCP连接的建立与关闭过程中，三次握手和四次挥手机制共同保证了数据传输的可靠性和正确性。四次挥手特别适用于需要确保数据完整且可靠的应用场景，如文件传输和数据库连接等。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F44c7021ec3824bd2aace22d6d379667b.jpeg&amp;size=m" alt=""></p>
<h3>滑动窗口机制</h3>
<p>TCP（传输控制协议）中的滑动窗口机制是一种流量控制方法，它使得数据能够在发送方和接收方之间高效且有序地传输。这一机制通过动态调整发送方可以发送的未确认数据量，从而优化网络资源的使用，避免数据丢失和拥塞。以下是滑动窗口机制的详细描述：</p>
<h4>滑动窗口机制的基本原理</h4>
<ol>
 <li>
  <p><strong>窗口大小</strong>：</p>
 </li>
 <li>
  <p>窗口大小（Window Size）是接收方能够接收的最大数据量（以字节为单位），通过TCP头中的窗口字段进行传递。窗口大小的选择与接收方的缓冲能力及网络状况有关。</p>
 </li>
 <li>
  <p><strong>发送方和接收方的合作</strong>：</p>
 </li>
 <li>
  <p>发送方在发送数据时，必须遵循接收方设置的窗口大小。这意味着发送方在未收到确认应答之前，不能发送超过窗口大小的数据。接收方根据自身的缓冲状态动态调整窗口大小，并通过ACK报文将新的窗口信息反馈给发送方。</p>
 </li>
 <li>
  <p><strong>滑动过程</strong>：</p>
 </li>
 <li>
  <p>随着数据包的发送和确认，窗口在发送方和接收方之间"滑动”。例如，当发送方发送了窗口中的数据并收到确认后，窗口就向前滑动，允许发送更多的数据。</p>
 </li>
 <li>
  <p>通过这种方式，发送方可以在不等待确认的情况下，连续发送多个数据段，从而提高数据传输的效率。</p>
 </li>
</ol>
<h4>滑动窗口机制的优点</h4>
<ol>
 <li>
  <p><strong>减少延迟</strong>：</p>
 </li>
 <li>
  <p>传统的停止等待协议需要在每个数据段后等待确认，而滑动窗口允许连续发送多个段，从而显著减少了延迟，并提高了网络的吞吐量。</p>
 </li>
 <li>
  <p><strong>适应网络状况</strong>：</p>
 </li>
 <li>
  <p>根据当前网络的拥塞情况和接收方的处理能力，窗口大小可以动态调整，确保数据流量的稳定，避免不必要的数据包丢失或拥塞。</p>
 </li>
 <li>
  <p><strong>流量控制</strong>：</p>
 </li>
 <li>
  <p>滑动窗口通过限制未确认的数据量，防止发送方发送速度过快导致接收方的缓冲区溢出，从而有效地保障了网络传输的稳定性和可靠性。</p>
 </li>
</ol>
<h3>流量控制机制</h3>
<p>TCP（传输控制协议）的流量控制机制是为了防止发送方以过快的速度发送数据，从而导致接收方的缓冲区溢出而导致数据丢失。流量控制确保发送方与接收方之间的传输速率相匹配，保障数据的可靠传输和网络的稳定性。以下是TCP流量控制机制的主要组成部分和工作原理。</p>
<h4>1. 流量控制的基本概念</h4>
<p>流量控制是指在数据传输过程中，接收方根据自身的处理能力和可用缓冲区大小，告知发送方当前能够接收的数据量，以避免由于发送方的数据发送过快而导致的接收方溢出或丢包。流量控制主要通过TCP头部中的窗口大小（Window Size）字段实现。</p>
<h4>2. 窗口机制</h4>
<ul>
 <li>
  <p><strong>滑动窗口</strong>：</p>
 </li>
 <li>
  <p>在TCP中，流量控制是通过滑动窗口机制实现的。接收方在TCP连接中维护一个窗口，表示其可以接收的未确认字节的数量。窗口的大小是动态变化的，反映了接收方的缓冲能力。</p>
 </li>
 <li>
  <p><strong>动态调整</strong>：</p>
 </li>
 <li>
  <p>接收方根据自身的处理情况，不断调整窗口大小。在发送ACK报文时，接收方会附带当前的窗口大小信息。发送方根据接收到的窗口大小动态调整发送的数据量，以适应接收方的接收能力。</p>
 </li>
</ul>
<h4>3. 流量控制的实现过程</h4>
<ul>
 <li>
  <p><strong>数据发送</strong>：</p>
 </li>
 <li>
  <p>发送方在发送数据时，会检查当前的窗口大小，确保发送的数据量不超过这个限制。比如，如果窗口大小为4个数据段，发送方将允许发送4个未确认的数据段。</p>
 </li>
 <li>
  <p><strong>接收方反馈</strong>：</p>
 </li>
 <li>
  <p>当接收方接收到数据并处理后，会通过ACK报文向发送方反馈确认信息，包括当前可用的窗口大小。如果接收方的缓冲区接近满载状态，窗口大小会减少，发送方在接收到减小的窗口大小后，必须减少数据发送量。</p>
 </li>
 <li>
  <p><strong>处理拥塞</strong>：</p>
 </li>
 <li>
  <p>如果网络出现拥塞，数据包丢失或延迟，接收方可以通知发送方降低发送速度，从而有效地控制流量。这一机制结合了流量控制和拥塞控制，以保证网络的总体稳定性。</p>
 </li>
</ul>
<h4>4. 优点</h4>
<ul>
 <li>
  <p><strong>防止数据丢失</strong>：</p>
 </li>
 <li>
  <p>通过控制发送速率，流量控制能够有效防止因接收方无法处理过多数据而导致的数据丢失。</p>
 </li>
 <li>
  <p><strong>改善网络资源利用</strong>：</p>
 </li>
 <li>
  <p>动态调整窗口大小使得TCP能够适应不断变化的网络状态，从而提高网络的利用率和数据传输效率。</p>
 </li>
 <li>
  <p><strong>适应不同网络环境</strong>：</p>
 </li>
 <li>
  <p>流量控制机制使得TCP能够适应不同的网络条件（如延迟和带宽），确保数据能够可靠传输。</p>
 </li>
</ul>
<p>总体而言，TCP的流量控制机制通过合理的窗口管理和动态控制，确保了数据传输的可靠性和稳定性，是网络通信中不可或缺的一部分。</p>
<h3>拥塞控制机制</h3>
<p>TCP（传输控制协议）中的拥塞控制机制是为了防止网络拥塞，提高网络利用率，确保数据的可靠传输。拥塞控制对于维持网络的稳定性和传输效率至关重要，特别是在网络条件不稳定的环境中。以下是TCP拥塞控制机制的主要组成部分和工作原理：</p>
<h4>1. 拥塞控制的基本概念</h4>
<p>拥塞控制是一种动态调整数据发送速率的策略，以避免网络过载和数据包丢失。当网络中数据流量超过其承载能力时，就会出现拥塞，导致延迟增加和数据包丢失。TCP通过监测和调整传输速率来控制拥塞，以适应网络的当前状态。</p>
<h4>2. 拥塞控制的主要算法</h4>
<p>TCP中采用了一些经典的拥塞控制算法，主要包括以下几种：</p>
<h5>1. 慢开始（Slow Start）</h5>
<ul>
 <li>在连接建立后，TCP的拥塞窗口（Congestion Window，cwnd）初始设置为一个较小的值，通常为1个数据段。</li>
 <li>随着每次成功的ACK接收，拥塞窗口大小以指数级增长，每收到一个ACK，增加一个数据段，直到达到一个阈值（慢开始阈值，ssthresh）。</li>
 <li>这种增长方式帮助TCP快速找到可用的带宽，但在网络开始拥塞前迅速增加发送速率。</li>
</ul>
<h5>2. 拥塞避免（Congestion Avoidance）</h5>
<ul>
 <li>当拥塞窗口达到慢开始阈值后，TCP进入拥塞避免阶段。在这个阶段，窗口以线性方式增长。</li>
 <li>每经过一个往返延迟（RTT），拥塞窗口增加1个数据段，减少了网络过载的风险。</li>
</ul>
<h5>3. 快重传（Fast Retransmit）</h5>
<ul>
 <li>当接收方发现缺失的数据段（例如，接收到三个重复的ACK），它会立即向发送方请求重传缺失的数据段，而不是等待超时。</li>
 <li>这种机制能够快速恢复数据的传输，降低数据包的丢失延迟。</li>
</ul>
<h5>4. 快恢复（Fast Recovery）</h5>
<ul>
 <li>在快重传之后，TCP会启用快恢复机制，进入重复ACK接收后，TCP会将慢开始阈值减半，然后进行线性增长，避免重启整个慢开始过程。</li>
 <li>这样可以迅速恢复连接的有效传输状态，而不是从最小窗口重新开始增长。</li>
</ul>
<h4>3. 拥塞控制的工作过程</h4>
<ul>
 <li><strong>监测网络状态</strong>：TCP通过技术手段监测网络延迟、丢包率等信息，以判断当前的网络状态。</li>
 <li><strong>调整拥塞窗口</strong>：根据监测到的状态，及时调整拥塞窗口的大小。若发现包丢失，则减少窗口大小，调慢发送速率；若网络状态良好，则逐步增加窗口，提升发送速率。</li>
 <li><strong>动态反馈</strong>：TCP通过ACK报文向发送方反馈接收状态，并且根据反馈的信息实时调整发送速率。</li>
</ul>
<h4>4. 优点</h4>
<ul>
 <li><strong>提高网络利用率</strong>：通过合理控制发送速率，拥塞控制机制能够最大限度地利用网络带宽，而不至于导致拥塞。</li>
 <li><strong>保护网络稳定性</strong>：避免因数据流量过大导致的网络崩溃，维护网络的整体健康状态。</li>
 <li><strong>响应网络变化</strong>：在网络条件变化时，TCP能够快速适应，确保数据传输的可靠性。</li>
</ul>
<p>总结而言，TCP的拥塞控制机制，通过慢开始、拥塞避免、快重传和快恢复等多种算法，能够有效管理网络流量，降低数据传输中的延迟和丢包风险。这些机制确保了在复杂网络环境中，TCP能够持续高效地进行数据通信。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6e5ec315390d4d028a26926be5cec9e3.jpeg&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/yican2580/article/details/142618177</p>]]></description><guid isPermaLink="false">/archives/8de1e0e6-2a1c-40df-9ee0-ed204116bdba</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sun, 29 Sep 2024 03:32:00 GMT</pubDate></item><item><title><![CDATA[计算机网络各层有哪些协议？计算机网络协议解析：从拟定到实现，全面了解各层协议的作用与区别]]></title><link>https://wiki.dgfygk.com/archives/09e59766-ab1d-4ab0-a425-188f367ca45c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%90%84%E5%B1%82%E6%9C%89%E5%93%AA%E4%BA%9B%E5%8D%8F%E8%AE%AE%EF%BC%9F%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E8%A7%A3%E6%9E%90%EF%BC%9A%E4%BB%8E%E6%8B%9F%E5%AE%9A%E5%88%B0%E5%AE%9E%E7%8E%B0%EF%BC%8C%E5%85%A8%E9%9D%A2%E4%BA%86%E8%A7%A3%E5%90%84%E5%B1%82%E5%8D%8F%E8%AE%AE%E7%9A%84%E4%BD%9C%E7%94%A8%E4%B8%8E%E5%8C%BA%E5%88%AB&amp;url=/archives/09e59766-ab1d-4ab0-a425-188f367ca45c" width="1" height="1" alt="" style="opacity:0;">
<p>在数字化时代，计算机网络无处不在，已经成为不可或缺的一部分。为了让不同设备能够有效地进行通信，网络协议作为一种约定和规则，确保了数据在网络中的可靠传输。今天，我们将深入探讨计算机网络的各层协议，详细介绍这些协议的作用、区别以及它们在实际中的应用，甚至还会提供一些代码示例，让你在实际中更好地理解这些概念。</p>
<h3>1. 网络模型简介</h3>
<p>计算机网络通常使用两种主要模型：<strong>OSI模型</strong> 和<strong>TCP/IP模型</strong>。在这篇博文中，我们将重点关注TCP/IP模型，它是互联网协议的核心。TCP/IP模型通常被划分为四层：</p>
<ul>
 <li><strong>应用层</strong></li>
 <li><strong>传输层</strong></li>
 <li><strong>网络层</strong></li>
 <li><strong>数据链路层</strong></li>
</ul>
<h4>1.1 应用层</h4>
<p>应用层位于模型的最上层，主要负责为用户提供各种网络服务。它可以被视为用户和网络之间的接口。该层的主要协议包括：</p>
<ul>
 <li><strong>HTTP（超文本传输协议）</strong>：用于在Web上传输超文本，例如网页。</li>
 <li><strong>FTP（文件传输协议）</strong>：用于文件的上传和下载。</li>
 <li><strong>SMTP（简单邮件传输协议）</strong>：用于电子邮件的发送。</li>
 <li><strong>DNS（域名系统）</strong>：将域名解析为IP地址。</li>
</ul>
<h5>示例代码：HTTP请求</h5>
<p>使用Python的<code>requests</code>库，可以快速实现HTTP请求：</p>
<pre><code class="prism language-python">import requests

url = 'http://www.example.com'
response = requests.get(url)

print("Status Code:", response.status_code)
print("Response Body:", response.text[:300])  # 打印前300个字符
</code></pre>
<h4>1.2 传输层</h4>
<p>传输层负责端到端的可靠性和数据流控制。它提供了两个主要的协议：</p>
<ul>
 <li><strong>TCP（传输控制协议）</strong>：面向连接的，保证数据的可靠传输。</li>
 <li><strong>UDP（用户数据报协议）</strong>：无连接的，支持快速数据传输但不保证可靠性。</li>
</ul>
<h5>TCP与UDP的区别</h5>
<p>| 特性 | TCP | UDP |
 <br>
 |——|————-|——————|
 <br>
 | 连接性 | 面向连接 | 无连接 |
 <br>
 | 可靠性 | 提供可靠交付，重传机制 | 不提供可靠性 |
 <br>
 | 顺序性 | 数据包顺序到达 | 数据包无顺序保证 |
 <br>
 | 速度 | 相对较慢，造成延迟 | 快速，低延迟 |
 <br>
 | 使用场景 | 网页浏览、文件传输 | 实时应用（如视频会议、在线游戏） |</p>
<h5>示例代码：TCP与UDP丛生</h5>
<p>使用Python创建一个简单的TCP和UDP服务器。</p>
<p><strong>TCP 服务器示例：</strong></p>
<pre><code class="prism language-python">import socket

tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server.bind(('localhost', 8080))
tcp_server.listen(5)
print("TCP Server is running...")

while True:
    client_socket, addr = tcp_server.accept()
    print(f'Connection from {
     addr} has been established!')
    client_socket.send(b'Hello, TCP Client!')
    client_socket.close()
</code></pre>
<p><strong>UDP 服务器示例：</strong></p>
<pre><code class="prism language-python">import socket

udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_server.bind(('localhost', 8080))
print("UDP Server is running...")

while True:
    data, addr = udp_server.recvfrom(1024)
    print(f'Received message: {
     data} from {
     addr}')
    udp_server.sendto(b'Hello, UDP Client!', addr)
</code></pre>
<h4>1.3 网络层</h4>
<p>网络层的主要任务是将数据包从源地址传输到目标地址，负责路由的选择。主要协议包括：</p>
<ul>
 <li><strong>IP（互联网协议）</strong>：负责数据包的寻址和分发。</li>
 <li><strong>ICMP（互联网控制消息协议）</strong>：用于发送错误消息和操作信息。</li>
 <li><strong>ARP（地址解析协议）</strong>：用于将IP地址解析为MAC地址。</li>
</ul>
<h5>示例代码：使用socket库获取本机IP地址</h5>
<pre><code class="prism language-python">import socket

hostname = socket.gethostname()
ip_address = socket.gethostbyname(hostname)

print("Hostname:", hostname)
print("IP Address:", ip_address)
</code></pre>
<h4>1.4 数据链路层</h4>
<p>数据链路层负责在物理网络上可靠地传输数据帧。该层处理错误检测和流控制。主要协议包括：</p>
<ul>
 <li><strong>Ethernet（以太网）</strong>：最常用的局域网技术。</li>
 <li><strong>PPP（点对点协议）</strong>：用于直接连接两个网络节点。</li>
 <li><strong>Wi-Fi（无线保真）</strong>：用于无线网络连接。</li>
</ul>
<h5>示例代码：获取本机的MAC地址</h5>
<p>我们可以使用<code>uuid</code>库来获取本机的MAC地址：</p>
<pre><code class="prism language-python">import uuid

mac_address = ':'.join(['{:02x}'.format((uuid.getnode() &gt;&gt; elements) &amp; 0xff) for elements in range(0,2*6,2)][::-1])
print("MAC Address:", mac_address)
</code></pre>
<h3>2. 各层协议作用总结</h3>
<ul>
 <li><strong>应用层</strong>：为用户提供网络服务，优化用户体验。</li>
 <li><strong>传输层</strong>：确保数据在网络中的完整性和顺序，提供可靠或不可靠的传输方式。</li>
 <li><strong>网络层</strong>：负责路由和寻址，使数据包从源到目的地有效传输。</li>
 <li><strong>数据链路层</strong>：处理数据在物理连接间的传输，确保数据帧的完整性。</li>
</ul>
<h3>3. 未来的展望</h3>
<p>随着技术的不断进步，网络协议也在不断演化。新兴的协议（如QUIC）将会在性能与安全性上提供更好的支持，应对不断增长的网络需求。在学习和应用计算机网络协议的过程中，了解基础协议的功能和区别，将为你日后的网络技术学习与开发打下坚实的基础。</p>
<h3>小结</h3>
<p>本文详细介绍了计算机网络的各层协议，涵盖了各层的主要协议及其作用并进行了对比分析。理解这些协议对于开发高效且可靠的网络应用至关重要。希望通过这篇博文，能够帮助你更加深入地理解计算机网络的世界，让你在未来的学习和工作中信心满满。网络之路，越走越宽！</p>
<p>原文链接: https://blog.csdn.net/qq_52341510/article/details/142619179</p>]]></description><guid isPermaLink="false">/archives/09e59766-ab1d-4ab0-a425-188f367ca45c</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sun, 29 Sep 2024 03:31:14 GMT</pubDate></item><item><title><![CDATA[js中的事件冒泡是什么？]]></title><link>https://wiki.dgfygk.com/archives/651a2fb2-1e7c-4052-96ca-f8e4f0dbc2f1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=js%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;url=/archives/651a2fb2-1e7c-4052-96ca-f8e4f0dbc2f1" width="1" height="1" alt="" style="opacity:0;">
<p>事件冒泡（Event Bubbling）是JavaScript中一种事件传播机制。当一个事件（如点击、键盘输入等）发生在某个元素上时，浏览器会首先触发该元素上的特定事件处理函数（如果存在的话）。然后，该事件会从当前元素向其父元素逐级传播，直到达到元素或整个文档树。这种从触发元素开始，逐级向上传播的事件机制被称为事件冒泡。</p>
<p>事件冒泡的主要优点是可以在一个父元素上监听多个子元素的事件，从而避免在每个子元素上都绑定事件处理函数，减少内存占用并简化代码。
 <br>
 如何阻止事件冒泡</p>
<p>在JavaScript中，可以使用event.stopPropagation()方法来阻止事件冒泡。这将阻止事件继续向上级元素传播。以下是一个示例：</p>
<p>
 <br>
</p>
<pre><code>document.querySelector('#child-element').addEventListener('click', function(event) {
  alert('Child element clicked');
  event.stopPropagation(); // 阻止事件冒泡
});

document.querySelector('#parent-element').addEventListener('click', function() {
  alert('Parent element clicked');
});
</code></pre>
<p>在这个示例中，点击子元素时，将只弹出"Child element clicked"提示框，而不会弹出"Parent element clicked"提示框，因为event.stopPropagation()阻止了事件冒泡。
 <br>
 如何阻止浏览器默认事件</p>
<p>在某些情况下，可能需要阻止浏览器的默认行为。例如，在点击链接时，浏览器默认会跳转到链接的href属性指向的地址。可以使用event.preventDefault()方法来阻止浏览器的默认行为。以下是一个示例：</p>
<pre><code>document.querySelector('a').addEventListener('click', function(event) {
  event.preventDefault(); // 阻止浏览器默认行为
  alert('Link clicked');
});
</code></pre>
<p>在这个示例中，点击链接时将只弹出"Link clicked"提示框，而不会跳转到链接的href属性指向的地址。
 <br>
 总结</p>
<p>事件冒泡是一种事件传播机制，允许事件从触发元素向上级元素逐级传播。
 <br>
 使用event.stopPropagation()方法可以阻止事件冒泡。
 <br>
 使用event.preventDefault()方法可以阻止浏览器的默认行为。
 <br>
 根据实际需求合理地使用这些方法，以实现所需的功能并优化用户体验。</p>
<p>原文链接: https://blog.csdn.net/2301_79420857/article/details/142625924</p>]]></description><guid isPermaLink="false">/archives/651a2fb2-1e7c-4052-96ca-f8e4f0dbc2f1</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sun, 29 Sep 2024 03:30:56 GMT</pubDate></item><item><title><![CDATA[分布式技术------Kafka]]></title><link>https://wiki.dgfygk.com/archives/9c4636fd-2c46-410d-a6f1-332609c6bfde</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%86%E5%B8%83%E5%BC%8F%E6%8A%80%E6%9C%AF------Kafka&amp;url=/archives/9c4636fd-2c46-410d-a6f1-332609c6bfde" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%20Kafka%20%E6%A6%82%E8%BF%B0">一 Kafka 概述</a></p>
<p><a href="https://wiki.dgfygk.com#1.1%20%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%EF%BC%88MQ%EF%BC%89">1.1 为什么需要消息队列（MQ）</a></p>
<p><a href="https://wiki.dgfygk.com#1.2%20%E5%BD%93%E5%89%8D%E6%AF%94%E8%BE%83%E5%B8%B8%E8%A7%81%E7%9A%84%20MQ%20%E4%B8%AD%E9%97%B4%E4%BB%B6%E6%9C%89%EF%BC%9A">1.2 当前比较常见的 MQ 中间件有：</a></p>
<p><a href="https://wiki.dgfygk.com#1.3%20%E4%BD%BF%E7%94%A8%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E7%9A%84%E5%A5%BD%E5%A4%84">1.3 使用消息队列的好处</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%881%EF%BC%89%E8%A7%A3%E8%80%A6">（1）解耦</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%882%EF%BC%89%E5%8F%AF%E6%81%A2%E5%A4%8D%E6%80%A7">（2）可恢复性</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%883%EF%BC%89%E7%BC%93%E5%86%B2">（3）缓冲</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%884%EF%BC%89%E7%81%B5%E6%B4%BB%E6%80%A7%20%26%20%E5%B3%B0%E5%80%BC%E5%A4%84%E7%90%86%E8%83%BD%E5%8A%9B">（4）灵活性 \&amp; 峰值处理能力</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%885%EF%BC%89%E5%BC%82%E6%AD%A5%E9%80%9A%E4%BF%A1">（5）异步通信</a></p>
<p><a href="https://wiki.dgfygk.com#1.4%20%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%A8%A1%E5%BC%8F">1.4 消息队列的两种模式</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%881%EF%BC%89%E7%82%B9%E5%AF%B9%E7%82%B9%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%80%E5%AF%B9%E4%B8%80%EF%BC%8C%E6%B6%88%E8%B4%B9%E8%80%85%E4%B8%BB%E5%8A%A8%E6%8B%89%E5%8F%96%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%B6%88%E6%81%AF%E6%94%B6%E5%88%B0%E5%90%8E%E6%B6%88%E6%81%AF%E6%B8%85%E9%99%A4%EF%BC%89">（1）点对点模式（一对一，消费者主动拉取数据，消息收到后消息清除）</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%882%EF%BC%89%E5%8F%91%E5%B8%83%2F%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%80%E5%AF%B9%E5%A4%9A%EF%BC%8C%E5%8F%88%E5%8F%AB%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%EF%BC%8C%E6%B6%88%E8%B4%B9%E8%80%85%E6%B6%88%E8%B4%B9%E6%95%B0%E6%8D%AE%E4%B9%8B%E5%90%8E%E4%B8%8D%E4%BC%9A%E6%B8%85%E9%99%A4%E6%B6%88%E6%81%AF%EF%BC%89">（2）发布/订阅模式（一对多，又叫观察者模式，消费者消费数据之后不会清除消息）</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%20Kafka%20%E5%AE%9A%E4%B9%89">二 Kafka 定义</a></p>
<p><a href="https://wiki.dgfygk.com#2.1%20Kafka%20%E7%AE%80%E4%BB%8B">2.1 Kafka 简介</a></p>
<p><a href="https://wiki.dgfygk.com#2.2%20Kafka%20%E7%9A%84%E7%89%B9%E6%80%A7">2.2 Kafka 的特性</a></p>
<p><a href="https://wiki.dgfygk.com#2.2.1%20%E9%AB%98%E5%90%9E%E5%90%90%E9%87%8F%E3%80%81%E4%BD%8E%E5%BB%B6%E8%BF%9F">2.2.1 高吞吐量、低延迟</a></p>
<p><a href="https://wiki.dgfygk.com#2.2.2%20%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7">2.2.2 可扩展性</a></p>
<p><a href="https://wiki.dgfygk.com#2.2.3%20%E6%8C%81%E4%B9%85%E6%80%A7%E3%80%81%E5%8F%AF%E9%9D%A0%E6%80%A7">2.2.3 持久性、可靠性</a></p>
<p><a href="https://wiki.dgfygk.com#2.2.4%20%E5%AE%B9%E9%94%99%E6%80%A7">2.2.4 容错性</a></p>
<p><a href="https://wiki.dgfygk.com#2.2.5%C2%A0%E9%AB%98%E5%B9%B6%E5%8F%91">2.2.5 高并发</a></p>
<p><a href="https://wiki.dgfygk.com#2.3%20Kafka%20%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84">2.3 Kafka 系统架构</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%881%EF%BC%89Broker%20%C2%A0%20%C2%A0%20%E6%9C%8D%E5%8A%A1%E5%99%A8">（1）Broker 服务器</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%882%EF%BC%89Topic%20%C2%A0%20%E4%B8%BB%E9%A2%98">（2）Topic 主题</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%883%EF%BC%89Partition%20%C2%A0%E5%88%86%E5%8C%BA">（3）Partition 分区</a></p>
<p><a href="https://wiki.dgfygk.com#2.4%20Partation%20%E6%95%B0%E6%8D%AE%E8%B7%AF%E7%94%B1%E8%A7%84%E5%88%99%EF%BC%9A">2.4 Partation 数据路由规则：</a></p>
<p><a href="https://wiki.dgfygk.com#2.5%20%E5%88%86%E5%8C%BA%E7%9A%84%E5%8E%9F%E5%9B%A0">2.5 分区的原因</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%881%EF%BC%89Replica">（1）Replica</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%882%EF%BC%89Leader">（2）Leader</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%883%EF%BC%89Follower">（3）Follower</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%884%EF%BC%89%20producer">（4） producer</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%885%EF%BC%89Consumer">（5）Consumer</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%886%EF%BC%89Consumer%20Group%EF%BC%88CG%EF%BC%89">（6）Consumer Group（CG）</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%887%EF%BC%89offset%20%E5%81%8F%E7%A7%BB%E9%87%8F">（7）offset 偏移量</a></p>
<p><a href="https://wiki.dgfygk.com#%EF%BC%888%EF%BC%89Zookeeper">（8）Zookeeper</a></p>
<p><a href="https://wiki.dgfygk.com#2.6%C2%A0kafka%20follower%E5%A6%82%E4%BD%95%E4%B8%8Eleader%20%E5%90%8C%E6%AD%A5%E6%95%B0%E6%8D%AE">2.6 kafka follower如何与leader 同步数据</a></p>
<p><a href="https://wiki.dgfygk.com#2.7%20kafka%20%E4%B8%AD%E7%9A%84broker%20%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%9A%84">2.7 kafka 中的broker 是干什么的</a></p>
<p><a href="https://wiki.dgfygk.com#2.8%C2%A0kafka%20%E4%B8%AD%E7%9A%84zookeeper%E8%B5%B7%E5%88%B0%E4%BB%80%E4%B9%88%E4%BD%9C%E7%94%A8%EF%BC%8C%E5%8F%AF%E4%BB%A5%E4%B8%8D%E7%94%A8zookeeper%E5%90%97%EF%BC%9F">2.8 kafka 中的zookeeper起到什么作用，可以不用zookeeper吗？</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%20%E9%83%A8%E7%BD%B2%20kafka%20%2Bzookeeper%E9%9B%86%E7%BE%A4">三 部署 kafka +zookeeper集群</a></p>
<p><a href="https://wiki.dgfygk.com#1.%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E5%8C%85">1.下载安装包</a></p>
<p><a href="https://wiki.dgfygk.com#2.%E5%AE%89%E8%A3%85%20Kafka%C2%A0">2.安装 Kafka</a></p>
<p><a href="https://wiki.dgfygk.com#2%20%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%C2%A0">2 修改配置文件</a></p>
<p><a href="https://wiki.dgfygk.com#3%20%E4%BF%AE%E6%94%B9%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%C2%A0">3 修改环境变量</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%80%8B%E7%BC%96%E8%BE%91%E2%80%8B%E7%BC%96%E8%BE%91%C2%A04%20%E9%85%8D%E7%BD%AE%20Zookeeper%20%E5%90%AF%E5%8A%A8%E8%84%9A%E6%9C%AC">​编辑​编辑 4 配置 Zookeeper 启动脚本</a></p>
<p><a href="https://wiki.dgfygk.com#5%E8%AE%BE%E7%BD%AE%E5%BC%80%E6%9C%BA%E8%87%AA%E5%90%AF%C2%A0">5设置开机自启</a></p>
<p><a href="https://wiki.dgfygk.com#6%20Kafka%20%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%93%8D%E4%BD%9C%EF%BC%8C%E5%88%9B%E5%BB%BAtopic%C2%A0">6 Kafka 命令行操作，创建topic</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%80%8B%E7%BC%96%E8%BE%91">​编辑</a></p>
<p><a href="https://wiki.dgfygk.com#7%20%E6%9F%A5%E7%9C%8B%E5%BD%93%E5%89%8D%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%20topic">7 查看当前服务器中的所有 topic</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%80%8B%E7%BC%96%E8%BE%91%C2%A08%20%E6%9F%A5%E7%9C%8B%E6%9F%90%E4%B8%AA%20topic%20%E7%9A%84%E8%AF%A6%E6%83%85">​编辑 8 查看某个 topic 的详情</a></p>
<p><a href="https://wiki.dgfygk.com#9%E5%8F%91%E5%B8%83%E6%B6%88%E6%81%AF%C2%A0">9发布消息</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%80%8B%E7%BC%96%E8%BE%91">​编辑</a></p>
<p><a href="https://wiki.dgfygk.com#10%20%E6%B6%88%E8%B4%B9%E6%B6%88%E6%81%AF">10 消费消息</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%B5%8B%E8%AF%95%EF%BC%9A">测试：</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%80%8B%E7%BC%96%E8%BE%9111%20%E4%BF%AE%E6%94%B9%E5%88%86%E5%8C%BA%E6%95%B0">​编辑11 修改分区数</a></p>
<p><a href="https://wiki.dgfygk.com#%E2%80%8B%E7%BC%96%E8%BE%9112%20%E5%88%A0%E9%99%A4%20topic%C2%A0">​编辑12 删除 topic</a></p>
<hr>
<p>
 <br>
</p>
<h3>一 Kafka 概述</h3>
<h4>1.1 为什么需要消息队列（MQ）</h4>
<p>主要原因是由于在高并发环境下，同步请求来不及处理，请求往往会发生阻塞。</p>
<p>比如大量的请求并发访问数据库，导致行锁表锁，最后请求线程会堆积过多，从而触发 too many connection 错误，引发雪崩效应。</p>
<p>使用消息队列，通过异步处理请求，从而缓解系统的压力。</p>
<p>消息队列常应用于异步处理，流量削峰，应用解耦，消息通讯等场景。</p>
<h4>1.2 当前比较常见的 MQ 中间件有：</h4>
<p>ActiveMQ、RabbitMQ、RocketMQ、Kafka 等。</p>
<h4>1.3 使用消息队列的好处</h4>
<h5>（1）解耦</h5>
<p>允许你独立的扩展或修改两边的处理过程，只要确保它们遵守同样的接口约束。</p>
<h5>（2）可恢复性</h5>
<p>系统的一部分组件失效时，不会影响到整个系统。消息队列降低了进程间的耦合度，所以即使一个</p>
<p>处理消息的进程挂掉，加入队列中的消息仍然可以在系统恢复后被处理。</p>
<h5>（3）缓冲</h5>
<p>有助于控制和优化数据流经过系统的速度，解决生产消息和消费消息的处理速度不一致的情况。</p>
<h5>（4）灵活性 \&amp; 峰值处理能力</h5>
<p>在访问量剧增的情况下，应用仍然需要继续发挥作用，但是这样的突发流量并不常见。如果为以能</p>
<p>处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶</p>
<p>住突发的访问压力，而不会因为突发的超负荷的请求而完全崩溃。</p>
<h5>（5）异步通信</h5>
<p>很多时候，用户不想也不需要立即处理消息。消息队列提供了异步处理机制，允许用户把一个消息</p>
<p>放入队列，但并不立即处理它。想向队列中放入多少消息就放多少，然后在需要的时候再去处理它</p>
<p>们。</p>
<h4>1.4 消息队列的两种模式</h4>
<h5>（1）点对点模式（一对一，消费者主动拉取数据，消息收到后消息清除）</h5>
<p>消息生产者生产消息发送到消息队列中，然后消息消费者从消息队列中取出并且消费消息。消息被</p>
<p>消费以后，消息队列中不再有存储，所以消息消费者不可能消费到已经被消费的消息。消息队列支</p>
<p>持存在多个消费者，但是对一个消息而言，只会有一个消费者可以消费。</p>
<h5>（2）发布/订阅模式（一对多，又叫观察者模式，消费者消费数据之后不会清除消息）</h5>
<p>消息生产者（发布）将消息发布到 topic 中，同时有多个消息消费者（订阅）消费该消息。和点对</p>
<p>点方式不同，发布到 topic 的消息会被所有订阅者消费。</p>
<p>发布/订阅模式是定义对象间一种一对多的依赖关系，使得每当一个对象（目对标象）的状态发生</p>
<p>改变，则所有依赖于它的对象（观察者对象）都会得到通知并自动更新。</p>
<h3>二 Kafka 定义</h3>
<p>Kafka 是一个分布式的基于发布/订阅模式的消息队列（MQ，Message Queue），主要应用于大数</p>
<p>据实时处理领域。</p>
<h4>2.1 Kafka 简介</h4>
<p>Kafka 是最初由 Linkedin 公司开发，是一个分布式、支持分区的（partition）、多副本的</p>
<p>（replica），基于 Zookeeper 协调的分布式消息中间件系统，它的最大的特性就是可以实时的处</p>
<p>理大量数据以满足各种需求场景，比如基于 hadoop 的批处理系统、低延迟的实时系统、</p>
<p>Spark/Flink 流式处理引擎，nginx 访问日志，消息服务等等，用 scala 语言编写，</p>
<p>Linkedin 于 2010 年贡献给了 Apache 基金会并成为顶级开源项目。</p>
<h4>2.2 Kafka 的特性</h4>
<h5>2.2.1 高吞吐量、低延迟</h5>
<p>Kafka 每秒可以处理几十万条消息，它的延迟最低只有几毫秒。每个 topic 可以分多个 Partition，</p>
<p>Consumer Group 对 Partition 进行消费操作，提高负载均衡能力和消费能力。</p>
<h5>2.2.2 可扩展性</h5>
<p>kafka 集群支持热扩展</p>
<h5>2.2.3 持久性、可靠性</h5>
<p>消息被持久化到本地磁盘，并且支持数据备份防止数据丢失</p>
<h5>2.2.4 容错性</h5>
<p>允许集群中节点失败（多副本情况下，若副本数量为 n，则允许 n-1 个节点失败）</p>
<h5>2.2.5 高并发</h5>
<p>支持数千个客户端同时读写</p>
<h4>2.3 Kafka 系统架构</h4>
<h5>（1）Broker 服务器</h5>
<p>一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。</p>
<h5>（2）Topic 主题</h5>
<p>可以理解为一个队列，生产者和消费者面向的都是一个 topic。</p>
<p>类似于数据库的表名或者 ES 的 index</p>
<p>物理上不同 topic 的消息分开存储</p>
<h5>（3）Partition 分区</h5>
<p>为了实现扩展性，一个非常大的 topic 可以分布到多个 broker（即服务器）上，一个 topic 可以分</p>
<p>割为一个或多个 partition，每个 partition 是一个有序的队列。Kafka 只保证 partition 内的记录是有</p>
<p>序的，而不保证 topic 中不同 partition 的顺序。</p>
<p>每个 topic 至少有一个 partition，当生产者产生数据的时候，会根据分配策略选择分区，然后将消</p>
<p>息追加到指定的分区的队列末尾。</p>
<h4>2.4 Partation 数据路由规则：</h4>
<p>1．指定了 patition，则直接使用；</p>
<p>2．未指定 patition 但指定 key（相当于消息中某个属性），通过对 key 的 value 进行 hash 取模，</p>
<p>选出一个 patition；</p>
<p>3．patition 和 key 都未指定，使用轮询选出一个 patition。</p>
<p>每条消息都会有一个自增的编号，用于标识消息的偏移量，标识顺序从 0 开始。</p>
<p>每个 partition 中的数据使用多个 segment 文件存储。</p>
<p>如果 topic 有多个 partition，消费数据时就不能保证数据的顺序。严格保证消息的消费顺序的场景</p>
<p>下（例如商品秒杀、 抢红包），需要将 partition 数目设为 1。</p>
<p>●broker 存储 topic 的数据。如果某 topic 有 N 个 partition，集群有 N 个 broker，那么每个 broker</p>
<p>存储该 topic 的一个 partition。</p>
<p>●如果某 topic 有 N 个 partition，集群有 (N+M) 个 broker，那么其中有 N 个 broker 存储 topic 的</p>
<p>一个 partition， 剩下的 M 个 broker 不存储该 topic 的 partition 数据。</p>
<p>●如果某 topic 有 N 个 partition，集群中 broker 数目少于 N 个，那么一个 broker 存储该 topic 的一</p>
<p>个或多个 partition。在实际生产环境中，尽量避免这种情况的发生，这种情况容易导致 Kafka 集群</p>
<p>数据不均衡。</p>
<h4>2.5 分区的原因</h4>
<p>方便在集群中扩展，每个Partition可以通过调整以适应它所在的机器，而一个topic又可以有多个</p>
<p>Partition组成，因此整个集群就可以适应任意大小的数据了；</p>
<p>可以提高并发，因为可以以Partition为单位读写了。</p>
<h5>（1）Replica</h5>
<p>副本，为保证集群中的某个节点发生故障时，该节点上的 partition 数据不丢失，且 kafka 仍然能够</p>
<p>继续工作，kafka 提供了副本机制，一个 topic 的每个分区都有若干个副本，一个 leader 和若干个</p>
<p>follower。</p>
<h5>（2）Leader</h5>
<p>每个 partition 有多个副本，其中有且仅有一个作为 Leader，Leader 是当前负责数据的读写的</p>
<p>partition。</p>
<h5>（3）Follower</h5>
<p>Follower 跟随 Leader，所有写请求都通过 Leader 路由，数据变更会广播给所有 Follower，</p>
<p>Follower 与 Leader 保持数据同步。Follower 只负责备份，不负责数据的读写。</p>
<p>如果 Leader 故障，则从 Follower 中选举出一个新的 Leader。</p>
<p>当 Follower 挂掉、卡住或者同步太慢，Leader 会把这个 Follower 从 ISR（Leader 维护的一个和</p>
<p>Leader 保持同步的 Follower 集合） 列表中删除，重新创建一个 Follower。</p>
<h5>（4） producer</h5>
<p>生产者即数据的发布者，该角色将消息 push 发布到 Kafka 的 topic 中。</p>
<p>broker 接收到生产者发送的消息后，broker 将该消息追加到当前用于追加数据的 segment 文件中。</p>
<p>生产者发送的消息，存储到一个 partition 中，生产者也可以指定数据存储的 partition。</p>
<h5>（5）Consumer</h5>
<p>消费者可以从 broker 中 pull 拉取数据。消费者可以消费多个 topic 中的数据。</p>
<h5>（6）Consumer Group（CG）</h5>
<p>消费者组，由多个 consumer 组成。</p>
<p>所有的消费者都属于某个消费者组，即消费者组是逻辑上的一个订阅者。可为每个消费者指定组</p>
<p>名，若不指定组名则属于默认的组。</p>
<p>将多个消费者集中到一起去处理某一个 Topic 的数据，可以更快的提高数据的消费能力。</p>
<p>消费者组内每个消费者负责消费不同分区的数据，一个分区只能由一个组内消费者消费，防止数据</p>
<p>被重复读取。消费者组之间互不影响。</p>
<h5>（7）offset 偏移量</h5>
<p>可以唯一的标识一条消息。</p>
<p>偏移量决定读取数据的位置，不会有线程安全的问题，消费者通过偏移量来决定下次读取的消息</p>
<p>（即消费位置）。</p>
<p>消息被消费之后，并不被马上删除，这样多个业务就可以重复使用 Kafka 的消息。</p>
<p>某一个业务也可以通过修改偏移量达到重新读取消息的目的，偏移量由用户控制。</p>
<p>消息最终还是会被删除的，默认生命周期为 1 周（7*24小时）。</p>
<h5><strong>（8）Zookeeper</strong></h5>
<p>Kafka 通过 Zookeeper 来存储集群的 meta 信息。</p>
<p>由于 consumer 在消费过程中可能会出现断电宕机等故障，consumer 恢复后，需要从故障前的位</p>
<p>置的继续消费，所以 consumer 需要实时记录自己消费到了哪个 offset，以便故障恢复后继续消</p>
<p>费。
 <br>
 Kafka 0.9 版本之前，consumer 默认将 offset 保存在 Zookeeper 中；从 0.9 版本开始，consumer</p>
<p>默认将 offset 保存在 Kafka 一个内置的 topic 中，该 topic 为 __consumer_offsets。</p>
<p>也就是说，zookeeper的作用就是：</p>
<p>生产者push数据到kafka集群，就必须要找到kafka集群的节点</p>
<p>在哪里，这些都是通过zookeeper去寻找的。消费者消费哪一条数据，也需要zookeeper的支持，</p>
<p>从zookeeper获得offset，offset记录上一次消费的数据消费到哪里，这样就可以接着下一条数据进</p>
<p>行消费。</p>
<h4>2.6 kafka follower如何与leader 同步数据</h4>
<p>Kafka的复制机制既不是完全的同步复制，也不是单纯的异步复制。</p>
<p>完全同步复制要求All Alive Follower都复制完，这条消息才会被认为commit，这种复制方式极大的</p>
<p>影响了吞吐率。</p>
<p>而异步复制方式下，Follower异步的从Leader复制数据，数据只要被Leader写入log就被认为已经</p>
<p>commit，这种情况下，如果leader挂掉，会丢失数据，</p>
<p>kafka使用ISR的方式很好的均衡了确保数据不丢失以及吞吐率。Follower可以批量的从Leader复制</p>
<p>数据，而且Leader充分利用磁盘顺序读以及send file(zero copy)机制，这样极大的提高复制性能，</p>
<p>内部批量写磁盘，大幅减少了Follower与Leader的消息量差</p>
<h4>2.7 kafka 中的broker 是干什么的</h4>
<p>broker 是消息的代理，Producers往Brokers里面的指定Topic中写消息，Consumers从Brokers里面</p>
<p>拉取指定Topic的消息，然后进行业务处理，broker在中间起到一个代理保存消息的中转站</p>
<h4>2.8 kafka 中的zookeeper起到什么作用，可以不用zookeeper吗？</h4>
<p>①zookeeper 是一个分布式的协调组件，</p>
<p>②早期版本的kafka用zk做meta信息存储，consumer的消费状态，group的管理以及 offset的值。</p>
<p>考虑到zk本身的一些因素以及整个架构较大概率存在单点问题，</p>
<p>③新版本中逐渐弱化了zookeeper的作用。新的consumer使用了kafka内部的group coordination</p>
<p>协议，也减少了对zookeeper的依赖，但是broker依然依赖于ZK，zookeeper 在kafka中还用来选举</p>
<p>controller 和 检测broker是否存活等等</p>
<h3>三 部署 kafka +zookeeper集群</h3>
<h4>1.下载安装包</h4>
<pre><code>1.下载安装包
官方下载地址：http://kafka.apache.org/downloads.html

cd /opt
wget https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.7.1/kafka_2.13-2.7.1.tgz
</code></pre>
<h4>2.安装 Kafka</h4>
<pre><code>cd /opt/
tar zxvf kafka_2.13-2.7.1.tgz
mv kafka_2.13-2.7.1 /usr/local/kafka
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbd63240190b8457ef739aeae9d4fe023.png&amp;size=m" alt=""></p>
<h4>2 修改配置文件</h4>
<pre><code>[root@zk-kfk01 opt]# cd /usr/local/kafka/config/
[root@zk-kfk01 config]# ls
connect-console-sink.properties    consumer.properties
connect-console-source.properties  log4j.properties
connect-distributed.properties     producer.properties
connect-file-sink.properties       server.properties
connect-file-source.properties     tools-log4j.properties
connect-log4j.properties           trogdor.conf
connect-mirror-maker.properties    zookeeper.properties
connect-standalone.properties
[root@zk-kfk01 config]# cp server.properties{,_bak}
[root@zk-kfk01 config]# ls
connect-console-sink.properties    consumer.properties
connect-console-source.properties  log4j.properties
connect-distributed.properties     producer.properties
connect-file-sink.properties       server.properties
connect-file-source.properties     server.properties_bak
connect-log4j.properties           tools-log4j.properties
connect-mirror-maker.properties    trogdor.conf
connect-standalone.properties      zookeeper.properties

 ​[root@zk-kfk01 config]# vim server.properties
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F11e4c8816c63e96bfdd136386a430084.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fee3bba162394b3754923a55e8ca9ff7c.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8ccd7a843d20edfc9fb64a0b5e34b695.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1d045536de3de740584ed435c05e3484.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fed69f34032eb04f67576d204659e1ff4.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F97d87b2c7a6a0f8d6962d53a40be5a2a.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F337a295ca0fbc5246619a17b582868bd.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa21b7f302130e651f746ea24f573db4e.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1bf6937c66204074287727093c6d6b26.png&amp;size=m" alt=""></p>
<h4>3 修改环境变量</h4>
<pre><code>vim /etc/profile
export KAFKA_HOME=/usr/local/kafka
export PATH=$PATH:$KAFKA_HOME/bin
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdef6a2dc8975939e20d3a98b6e75862c.png&amp;size=m" alt=""></p>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5ad747a3e655eb916dedf6c74054676e.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4f22824afcfa1a539b30f987b3c4d25e.png&amp;size=m" alt=""> 4 配置 Zookeeper 启动脚本</h4>
<pre><code>vim /etc/init.d/kafka

#!/bin/bash
#chkconfig:2345 22 88
#description:Kafka Service Control Script
KAFKA_HOME='/usr/local/kafka'
case $1 in
start)
    echo "---------- Kafka 启动 ------------"
    ${KAFKA_HOME}/bin/kafka-server-start.sh -daemon ${KAFKA_HOME}/config/server.properties
;;
stop)
    echo "---------- Kafka 停止 ------------"
    ${KAFKA_HOME}/bin/kafka-server-stop.sh
;;
restart)
    $0 stop
    $0 start
;;
status)
    echo "---------- Kafka 状态 ------------"
    count=$(ps -ef | grep kafka | egrep -cv "grep|$$")
    if [ "$count" -eq 0 ];then
        echo "kafka is not running"
    else
        echo "kafka is running"
    fi
;;
*)
    echo "Usage: $0 {start|stop|restart|status}"
esac
</code></pre>
<h4>5设置开机自启</h4>
<pre><code>chmod +x /etc/init.d/kafka
chkconfig --add kafka
</code></pre>
<p>分别启动 Kafka</p>
<pre><code>service kafka start
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa1cd709dc74e60d7edd09dda021802bc.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa29ad6e85e8f7c1d6137f76ccb6de913.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fea76ff57a410702a4c124258ac49e8a7.png&amp;size=m" alt=""></p>
<h4>6 Kafka 命令行操作，创建topic</h4>
<pre><code>[root@zk-kfk01 config]# kafka-topics.sh --create --zookeeper 192.168.11.4:2181,192.168.11.5:2181,192.168.11.11:2181 --replication-factor 2 --partitions 3 --topic test
Created topic test.
</code></pre>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6959cc707f2998462a1ab2264f6102bc.png&amp;size=m" alt=""></h4>
<h4>7 查看当前服务器中的所有 topic</h4>
<pre><code>[root@zk-kfk01 config]# kafka-topics.sh --list --zookeeper192.168.11.4:2181,192.168.11.5:2181,192.168.11.11:2181
</code></pre>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6da8e8280934047f53cbf4896720121e.png&amp;size=m" alt=""> 8 查看某个 topic 的详情</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0a90972dc5fb3d3f4f97797a70a9b46b.png&amp;size=m" alt=""></p>
<h4>9发布消息</h4>
<pre><code>[root@zk-kfk01 config]# kafka-console-producer.sh 192.168.11.4:9092,192.168.11.5:9092,192.168.11.11:9092 --topic test
Missing required option(s) [bootstrap-server]
Option                                   Description     
</code></pre>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F615ba7e570aff9da06e2fce1d2649050.png&amp;size=m" alt=""></h4>
<h4>10 消费消息</h4>
<pre><code>kafka-console-consumer.sh --bootstrap-server 192.168.11.4:9092,192.168.11.5:9092,192.168.11.11:9092 --topic test --from-beginning
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdfffd4e7b410d65875ce21ed27a0abd9.png&amp;size=m" alt=""></p>
<h6>测试：</h6>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F89577f9b78e73f4595262d13fb575e12.png&amp;size=m" alt="">11 修改分区数</h4>
<pre><code>kafka-topics.sh --zookeeper 192.168.11.4:2181,192.168.11.5:2181,192.168.11.5:2181 --alter --topic test --partitions 6
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc221396000daeafce104881c66a6a1a7.png&amp;size=m" alt=""></p>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F09c9a1b5dd53aefc82c70b8cc8346cd5.png&amp;size=m" alt="">12 删除 topic</h4>
<pre><code>kafka-topics.sh --delete --zookeeper 192.168.11.4:2181,192.168.11.5:2181,192.168.11.11:2181 --topic test
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/MCB134/article/details/137688368</p>]]></description><guid isPermaLink="false">/archives/9c4636fd-2c46-410d-a6f1-332609c6bfde</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 03:26:41 GMT</pubDate></item><item><title><![CDATA[Linux信号学习三步走及知识脉络]]></title><link>https://wiki.dgfygk.com/archives/320a3ca4-6add-4245-843b-fd4fd3288570</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E4%BF%A1%E5%8F%B7%E5%AD%A6%E4%B9%A0%E4%B8%89%E6%AD%A5%E8%B5%B0%E5%8F%8A%E7%9F%A5%E8%AF%86%E8%84%89%E7%BB%9C&amp;url=/archives/320a3ca4-6add-4245-843b-fd4fd3288570" width="1" height="1" alt="" style="opacity:0;">
<h4>Linux信号学习三步走及知识脉络</h4>
<p>在Linux系统中，信号（Signal）是一个非常重要的概念，它作为一种异步通知机制，用于通知进程发生了某些事件。信号的本质是软件层次上对中断机制的一种模拟，是进程间通信机制中唯一的异步通信方式。学习Linux信号，可以通过以下三步走，并打通相关的知识脉络。</p>
<h5>一、信号的基本概念与分类</h5>
<p><strong>1. 信号的定义</strong></p>
<p>信号，又称为软中断信号，是Linux系统响应某些条件而产生的一个事件。它是操作系统向一个进程或者线程发送的一种异步通知，用于通知该进程或线程某种事件已经发生，需要做出相应的处理。信号本质是一种异步通知机制，用户或操作系统通过发送信号来通知进程某些事情已经发生，进程可以进行后续处理。</p>
<p><strong>2. 信号的产生</strong></p>
<p>信号可以由多种方式产生，包括：</p>
<ul>
 <li><strong>通过终端按键产生信号</strong>：例如，在终端上按Ctrl+C键通常产生中断信号（SIGINT），终止进程。</li>
 <li><strong>调用系统函数向进程发信号</strong> ：如<code>kill()</code>函数、<code>raise()</code>函数等。</li>
 <li><strong>由软件条件产生信号</strong> ：例如，使用<code>alarm()</code>函数设置的闹钟超时后会产生SIGALRM信号。</li>
 <li><strong>硬件异常产生信号</strong>：如除数为0、无效的内存引用等，这些条件通常由硬件检测到，并通知内核，然后内核为发生此错误的进程发送相应的信号。</li>
</ul>
<p><strong>3. 信号的分类</strong></p>
<p>Linux系统支持多种信号，大致可以分为两大类：非实时信号（也称为不可靠信号）和实时信号（也称为可靠信号）。非实时信号的值范围是1到31，是从UNIX系统中继承下来的；实时信号的值范围是34到64。非实时信号不支持排队，可能会丢失，而实时信号支持排队，可以确保信号按顺序处理。</p>
<p>可以使用<code>kill -l</code>命令查看系统中支持的所有信号及其编号和名称。每种信号都有其特定的含义和默认处理动作，例如，SIGKILL（信号值为9）的默认处理动作是立即终止进程，且不能被捕捉或忽略。</p>
<h5>二、信号的处理与捕捉</h5>
<p><strong>1. 信号的处理方式</strong></p>
<p>当进程接收到信号时，可以采取以下三种方式之一来处理信号：</p>
<ul>
 <li><strong>忽略信号</strong>：大部分信号都可以被忽略，但SIGSTOP和SIGKILL两个信号除外，因为它们是给超级用户提供杀掉或停止任何进程的手段。</li>
 <li><strong>执行默认处理动作</strong>：系统为每种信号都定义了一个默认的处理动作，如终止进程、生成core dump文件等。</li>
 <li><strong>捕捉信号</strong>：进程可以注册一个信号处理函数来捕捉特定的信号，并在接收到该信号时执行相应的处理逻辑。</li>
</ul>
<p><strong>2. 信号的捕捉</strong></p>
<p>在Linux中，可以通过<code>signal()</code>函数或<code>sigaction()</code>函数来设置信号处理函数。<code>signal()</code>函数是UNIX系统的遗留接口，它相对简单但功能有限；而<code>sigaction()</code>函数提供了更丰富的功能，是更推荐使用的接口。</p>
<p><code>sigaction()</code>函数允许进程为特定的信号指定一个处理函数，并可以修改信号的行为，如阻塞信号、查询信号等。其结构体<code>sigaction</code>包含了信号的处理函数、信号屏蔽字等信息。</p>
<p><strong>示例代码</strong>：</p>
<pre><code class="prism language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;signal.h&gt;
#include &lt;unistd.h&gt;

void sig_handler(int signum) {

    printf("Caught signal %d\n", signum);
    // 清理资源、关闭文件等操作
    exit(signum);
}

int main() {

    struct sigaction act;
    act.sa_handler = sig_handler;
    sigemptyset(&amp;act.sa_mask);
    act.sa_flags = 0;

    sigaction(SIGINT, &amp;act, NULL);

    while(1) {

        printf("Looping...\n");
        sleep(1);
    }

    return 0;
}
</code></pre>
<p>在这个例子中，我们为SIGINT信号设置了处理函数<code>sig_handler</code>，当进程接收到SIGINT信号时，会执行<code>sig_handler</code>函数，并打印出接收到的信号编号，然后退出程序。</p>
<p><strong>3. 信号的阻塞与解除</strong></p>
<p>进程可以选择阻塞某些信号，被阻塞的信号在产生时将保持在未决状态，直到进程解除对该信号的阻塞，才执行递达的动作。信号的阻塞和解除可以通过<code>sigprocmask()</code>函数来实现。</p>
<p><code>sigprocmask()</code>函数允许进程指定一个新的信号屏蔽字（即阻塞哪些信号），并可以获取旧的信号屏蔽字。通过设置信号屏蔽字，进程可以控制哪些信号被阻塞，哪些信号被允许递达。</p>
<h5>三、信号的保存与递达</h5>
<p><strong>1. 信号的保存</strong></p>
<p>Linux内核为每个进程维护了与信号相关的数据结构，主要包括三张表：阻塞表（Block表）、未决表（Pending表）和处理函数表（Handler表）。</p>
<ul>
 <li><strong>阻塞表</strong>：一个位图，用于表示进程当前阻塞了哪些信号。</li>
 <li><strong>未决表</strong>：同样是一个位图，用于表示进程当前有哪些信号处于未决状态。</li>
 <li><strong>处理函数表</strong>：一个函数指针数组，用于表示当信号递达时应该执行的处理动作。</li>
</ul>
<p>当信号产生时，内核会在进程控制块（PCB）的Pending表中设置对应信号位的值为1，表示该信号已经产生且处于未决状态。然后，内核会检查Block表，查看该信号是否被阻塞。如果被阻塞，则信号会保持在未决状态；如果没有被阻塞，则根据Handler表中的处理函数指针执行相应的处理动作。</p>
<p><strong>2. 信号的递达</strong></p>
<p>信号的递达是指信号从产生到被处理的过程。在Linux中，信号的递达通常是在内核态和用户态之间切换时进行的。当进程从内核态返回到用户态时，内核会检查待处理的信号，并根据信号的处理方式执行相应的动作。</p>
<p>如果进程为某个信号设置了自定义处理函数，则在信号递达时，内核会切换到用户态并调用该处理函数。如果进程选择忽略信号或执行默认处理动作，则内核会根据相应的规则来处理信号。</p>
<h5>四、总结与知识脉络</h5>
<p>学习Linux信号需要打通以下知识脉络：</p>
<ol>
 <li><strong>信号的基本概念</strong>：理解信号的定义、产生方式、分类以及默认处理动作。</li>
 <li><strong>信号的处理与捕捉</strong> ：掌握如何通过<code>signal()</code>函数或<code>sigaction()</code>函数来设置信号处理函数，并了解信号捕捉的机制和细节。</li>
 <li><strong>信号的阻塞与解除</strong> ：了解如何通过<code>sigprocmask()</code>函数来设置和修改进程的信号屏蔽字，从而控制哪些信号被阻塞或允许递达。</li>
 <li><strong>信号的保存与递达</strong>：理解Linux内核如何为进程维护信号相关的数据结构，以及信号从产生到递达的过程。</li>
</ol>
<p>通过这三步走，可以系统地学习Linux信号的相关知识，并在实际编程中灵活运用信号机制来实现进程间的异步通信和错误处理。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142553708</p>]]></description><guid isPermaLink="false">/archives/320a3ca4-6add-4245-843b-fd4fd3288570</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sun, 29 Sep 2024 00:53:45 GMT</pubDate></item><item><title><![CDATA[Apache Cordova和PhoneGap]]></title><link>https://wiki.dgfygk.com/archives/d92bca0c-6f94-4836-9766-e0573fcc7902</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Apache%20Cordova%E5%92%8CPhoneGap&amp;url=/archives/d92bca0c-6f94-4836-9766-e0573fcc7902" width="1" height="1" alt="" style="opacity:0;">
<p>Apache Cordova和PhoneGap是两个在移动应用开发领域备受关注的开源框架，它们有着紧密的联系和显著的区别。本文将从起源与发展、技术特点、功能与应用、社区与文档资源、性能与限制以及未来发展趋势等多个方面，对Apache Cordova和PhoneGap进行详细探讨。</p>
<h4>一、起源与发展</h4>
<h5>Apache Cordova</h5>
<p>Apache Cordova是一个完全开源的移动应用开发框架，它的前身是PhoneGap。在Adobe将PhoneGap的核心代码捐赠给Apache软件基金会后，Apache Cordova应运而生。作为一个独立的开源项目，Apache Cordova遵循Apache许可证，允许开发者自由地修改和分发其代码。这使得Cordova在开发者社区中获得了广泛的认可和支持。</p>
<h5>PhoneGap</h5>
<p>PhoneGap最初由Nitobi公司创建，专注于使用HTML、CSS和JavaScript等Web技术来开发跨平台的移动应用。2011年，Adobe公司收购了Nitobi，并继续推动PhoneGap的发展。尽管PhoneGap现在更多地被视为Apache Cordova的一个分发版，但它在移动应用开发领域的历史和影响力不容忽视。PhoneGap提供了一些额外的服务和资源，这些服务可能需要付费，但对于希望快速上手并利用现有资源的开发者来说，PhoneGap仍然是一个有吸引力的选择。</p>
<h4>二、技术特点</h4>
<h5>跨平台开发</h5>
<p>Apache Cordova和PhoneGap都允许开发者使用HTML、CSS和JavaScript等Web技术来编写跨平台的移动应用。这意味着开发者可以编写一次代码，然后将其部署到iOS、Android、Windows Phone等多个平台上，大大减少了开发和维护的工作量。</p>
<h5>访问原生设备功能</h5>
<p>通过插件机制，Apache Cordova和PhoneGap应用可以访问设备的原生功能，如相机、位置服务、加速计等。这种能力使得开发者能够创建更加丰富和强大的移动应用。Apache Cordova提供了丰富的设备API，而PhoneGap则拥有大量的插件，这使得它在某些情况下更具吸引力。</p>
<h5>命令行工具</h5>
<p>两者都提供了命令行工具，用于创建新项目、添加插件、构建和部署应用。这些工具简化了开发流程，提高了开发效率。</p>
<h4>三、功能与应用</h4>
<p>Apache Cordova和PhoneGap都广泛应用于各种移动应用开发中。从简单的信息展示类应用到复杂的企业级应用，都可以使用这两个框架来构建。特别是对于那些需要快速开发和部署的应用，Cordova和PhoneGap提供了极大的便利。</p>
<p>此外，这两个框架还支持热更新功能，即在应用运行时实时更新代码，而无需重新编译和部署应用。这对于快速迭代和测试非常有用。</p>
<h4>四、社区与文档资源</h4>
<h5>社区支持</h5>
<p>Apache Cordova拥有一个活跃的开发者社区，许多开发者在这里分享经验、教程和问题解答。作为一个开源项目，Cordova的社区支持非常强大，开发者可以轻松地找到所需的帮助和资源。相比之下，PhoneGap的社区支持虽然不如Cordova那么活跃，但Adobe提供了商业支持和培训服务，这对于一些需要专业支持的开发者来说是一个不错的选择。</p>
<h5>文档资源</h5>
<p>在文档资源方面，PhoneGap提供了丰富的中文资源、在线文档和视频教程。例如，PhoneGap API有详细的中文帮助文档，这对于中文开发者来说非常友好。而Apache Cordova的文档资源相对较少提及，但其社区活跃度较高，通过社区支持也能获得较好的文档资源。</p>
<h4>五、性能与限制</h4>
<h5>性能表现</h5>
<p>由于Apache Cordova和PhoneGap都是基于Web技术开发的，因此它们在性能上可能无法与原生应用相媲美。特别是在图形密集型游戏和高性能计算方面，它们的性能可能会受到限制。然而，通过优化和使用原生插件，这两个框架的应用可以提供接近原生应用的性能。具体来说，Cordova通过优化和使用原生插件来提高性能，而PhoneGap在性能上可能存在一些问题，如加载时间较长以及操作平台资源不灵活等。</p>
<h5>限制与不足</h5>
<p>除了性能上的限制外，Apache Cordova和PhoneGap在用户体验上也存在一些不足。由于它们的应用界面是用Web技术构建的，因此在观感和感觉上可能与原生应用有所区别。用户可能会注意到过渡不太流畅、加载时间更长等问题。此外，这两个框架在处理一些复杂的功能时也可能需要更多的工作量和时间。</p>
<h4>六、未来发展趋势</h4>
<p>随着移动设备的多样化和技术的不断进步，Apache Cordova和PhoneGap作为跨平台移动应用开发框架，其未来发展前景广阔。从历史背景来看，PhoneGap是Apache Cordova的一个分发版，两者在技术上有着紧密的联系。未来，它们将继续致力于提供跨平台的移动应用开发解决方案，并不断优化性能和用户体验。</p>
<p>在技术方面，随着人工智能、物联网等领域的进步，这些技术可能会被整合到Cordova和PhoneGap中，以提升它们的功能和用户体验。例如，通过集成AI技术，开发者可以创建更加智能和个性化的移动应用；通过物联网技术，开发者可以实现设备之间的互联互通和智能控制。</p>
<p>在社区支持方面，PhoneGap计划进一步加强与社区的合作，鼓励更多的开发者贡献代码和插件，共同推动该框架的发展。这种社区驱动的模式有助于保持产品的创新性和适应性，从而更好地满足市场需求。</p>
<p>总之，Apache Cordova和PhoneGap作为跨平台移动应用开发框架，在解决移动应用开发中的常见问题、提高开发效率和降低开发成本方面发挥了重要作用。虽然它们在性能上可能无法与原生应用相媲美，但通过优化和使用原生插件等手段，它们的应用已经能够提供接近原生应用的性能和用户体验。未来，随着技术的不断进步和社区支持的加强，这两个框架将继续在移动应用开发领域发挥重要作用。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142451184</p>]]></description><guid isPermaLink="false">/archives/d92bca0c-6f94-4836-9766-e0573fcc7902</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:53:34 GMT</pubDate></item><item><title><![CDATA[MySQL数据库的备份与恢复]]></title><link>https://wiki.dgfygk.com/archives/ab060b56-6b8a-4888-ad62-d907e43d553b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%A4%87%E4%BB%BD%E4%B8%8E%E6%81%A2%E5%A4%8D&amp;url=/archives/ab060b56-6b8a-4888-ad62-d907e43d553b" width="1" height="1" alt="" style="opacity:0;">
<h4>MySQL数据库的备份与恢复</h4>
<p>在现代信息时代，数据已成为企业和个人的重要资产，数据的安全性和可恢复性直接影响到业务的连续性和稳定性。MySQL作为广泛使用的关系型数据库管理系统，其数据的备份与恢复显得尤为重要。本文将详细探讨MySQL数据库的备份与恢复策略，包括备份的重要性、备份类型、常用备份方法及其操作步骤。</p>
<h5>一、数据备份的重要性</h5>
<p>数据备份的首要目的在于灾难恢复。当系统遭遇突发事件（如硬件故障、网络攻击、自然灾害等）时，通过备份可以有效恢复数据，确保业务的连续性。在生产环境中，数据是企业运营的核心，确保数据的完整性和可用性是业务正常运作的基础。没有可靠的数据备份，企业可能会面临严重的财务和信誉损失。</p>
<p>造成数据丢失的原因多种多样，包括但不限于程序错误、人为操作错误、运算错误、磁盘故障、灾难（如火灾、地震）和盗窃等。因此，定期进行数据备份是防止数据丢失的关键措施。</p>
<h5>二、备份类型</h5>
<p>MySQL数据库的备份可以分为物理备份和逻辑备份两大类，每种备份类型又可根据操作方式细分为不同的子类型。</p>
<h6>1. 物理备份</h6>
<p>物理备份是对数据库操作系统的物理文件（如数据文件、日志文件等）的备份。这种备份类型在数据恢复时可以迅速恢复整个数据库，适合大型和关键数据库。物理备份根据备份时数据库的状态，可分为冷备份（脱机备份）、热备份（联机备份）和温备份。</p>
<ul>
 <li>
  <p><strong>冷备份（脱机备份）</strong>：在数据库关闭的情况下进行的备份操作。由于数据库在备份期间完全不可用，因此冷备份通常被称为全备份。冷备份的优点是备份过程简单全面，不会受到数据库运行状态的干扰；缺点是备份期间数据库不可用，这在生产环境中可能难以接受。</p>
 </li>
 <li>
  <p><strong>热备份（联机备份）</strong>：数据库处于运行状态，依赖于数据库的日志文件进行备份。这种备份方式允许系统在不中断服务的情况下进行备份，适用于需要24x7全天候运行的系统。热备份的优点是可以在不影响业务的情况下进行备份，但由于备份过程中数据库仍在运行，需要小心处理备份数据的一致性，且备份过程可能消耗较多资源。</p>
 </li>
 <li>
  <p><strong>温备份</strong>：介于冷备份和热备份之间，通常指在数据库锁定部分表格（不可写入但可读）的状态下进行的备份操作。</p>
 </li>
</ul>
<h6>2. 逻辑备份</h6>
<p>逻辑备份是对数据库逻辑组件（如表等数据库对象）的备份，即以SQL语句的形式，把库、表结构、表数据保存下来。逻辑备份包括完全备份、差异备份和增量备份。</p>
<ul>
 <li>
  <p><strong>完全备份（全量备份）</strong>：每次对数据库进行完整的备份。这种备份方式简单可靠，恢复过程直接，但存储空间需求大，备份时间长。</p>
 </li>
 <li>
  <p><strong>差异备份</strong>：备份自从上次完全备份之后被修改过的文件。差异备份减少了存储空间的需求，因为只备份自上次完全备份以来的变化数据。恢复时需要使用上一次完全备份和最新的一次差异备份。</p>
 </li>
 <li>
  <p><strong>增量备份</strong>：只备份自上次完全备份或增量备份以来发生变化的数据。增量备份记录的是自上次备份后的数据变化，因此备份数据量较小，存储空间占用少，但恢复时需要按顺序恢复完全备份及所有增量备份。</p>
 </li>
</ul>
<h5>三、常用的备份方法和工具</h5>
<h6>1. 物理冷备份</h6>
<p>物理冷备份是最简单的备份方式，备份时数据库处于关闭状态，直接打包数据库文件。这种方法备份速度快，恢复时也是最简单的，但缺点是备份期间数据库不可用，可能导致业务中断。</p>
<p><strong>操作步骤</strong>：</p>
<ol>
 <li>关闭MySQL数据库服务。</li>
 <li>使用tar命令直接打包数据库文件夹（如<code>/usr/local/mysql/data/</code>）。</li>
 <li>将打包文件保存到安全位置。</li>
</ol>
<p><strong>恢复步骤</strong>：</p>
<ol>
 <li>关闭MySQL数据库服务。</li>
 <li>删除现有的数据库文件（如果需要）。</li>
 <li>解压备份文件到数据库文件目录。</li>
 <li>重新启动MySQL数据库服务。</li>
</ol>
<h6>2. mysqldump备份</h6>
<p><code>mysqldump</code>是MySQL自带的逻辑备份工具，能够生成数据库的SQL脚本文件，包含数据库的结构和数据。该工具支持多种数据库表类型，备份内容以SQL脚本形式存储，恢复灵活。</p>
<p><strong>完全备份一个或多个库</strong>：</p>
<pre><code class="prism language-bash">mysqldump -u root -p[密码] --databases 库名1 [库名2] ... &gt; /备份路径/备份文件名.sql
</code></pre>
<p><strong>完全备份所有库</strong>：</p>
<pre><code class="prism language-bash">mysqldump -u root -p[密码] --all-databases &gt; /备份路径/备份文件名.sql
</code></pre>
<p><strong>恢复步骤</strong>：</p>
<ol>
 <li>登录MySQL数据库。</li>
 <li>使用<code>mysql</code>命令导入备份文件：</li>
</ol>
<pre><code class="prism language-bash">mysql -u root -p &lt; /备份路径/备份文件名.sql
</code></pre>
<h6>3. 启用二进制日志进行增量备份</h6>
<p>增量备份通过启用二进制日志（binary logs）来记录所有数据库更改。这种备份方式可以减少备份数据量，但恢复时需要按顺序恢复所有备份文件。</p>
<p><strong>启用二进制日志</strong>：</p>
<p>在MySQL的配置文件（如<code>/etc/my.cnf</code>）中设置：</p>
<pre><code class="prism language-bash">log-bin=mysql-bin
binlog_format=MIXED
</code></pre>
<p><strong>进行增量备份</strong>：</p>
<ol>
 <li>进行完全备份。</li>
 <li>定期刷新二进制日志，生成新的日志文件。</li>
 <li>备份新的二进制日志文件。</li>
</ol>
<p><strong>恢复步骤</strong>：</p>
<p>使用<code>mysqlbinlog</code>工具将二进制日志中的更改应用到数据库中。</p>
<h5>四、增量备份与恢复</h5>
<p>增量备份因其备份量小、时间短的特点，在生产环境中得到广泛应用。但恢复时相对复杂，需要按顺序恢复完全备份及所有增量备份。</p>
<p><strong>恢复步骤</strong>：</p>
<ol>
 <li>恢复完全备份。</li>
 <li>依次恢复所有增量备份，直到目标时间点。</li>
</ol>
<p>在恢复过程中，可以使用<code>mysqlbinlog</code>工具的<code>--start-position</code>和<code>--stop-position</code>参数来指定恢复的数据范围。</p>
<h5>五、数据库上云迁移</h5>
<p>随着云计算技术的发展，越来越多的企业选择将数据库迁移到云平台上。MySQL数据库的迁移可以分为冷迁移和热迁移两种方式。</p>
<ul>
 <li><strong>冷迁移</strong>：在数据库停止服务的情况下进行迁移，适用于对数据一致性要求较高的场景。</li>
 <li><strong>热迁移</strong>：在数据库运行状态下进行迁移，适用于需要不间断服务的场景。热迁移通常使用专门的迁移工具或服务，如AWS DMS（Database Migration Service）等。</li>
</ul>
<h5>六、总结</h5>
<p>MySQL数据库的备份与恢复是保障数据安全的重要手段。选择合适的备份类型和备份方法，可以最大限度地减少数据丢失的风险。在生产环境中，应定期进行数据备份，并测试备份文件的可恢复性，以确保在发生灾难时能够迅速恢复数据，保障业务的连续性。同时，随着云计算技术的发展，数据库的云迁移也成为了一种趋势，企业应根据自身需求选择合适的迁移方案。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142433001</p>]]></description><guid isPermaLink="false">/archives/ab060b56-6b8a-4888-ad62-d907e43d553b</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Sun, 29 Sep 2024 00:53:17 GMT</pubDate></item><item><title><![CDATA[SpringBoot 与 Maven 快速上手指南]]></title><link>https://wiki.dgfygk.com/archives/aee0bb72-a21a-452a-a7eb-3abefeb44ec5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%20%E4%B8%8E%20Maven%20%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E6%8C%87%E5%8D%97&amp;url=/archives/aee0bb72-a21a-452a-a7eb-3abefeb44ec5" width="1" height="1" alt="" style="opacity:0;">
<h4>SpringBoot 与 Maven 快速上手指南</h4>
<p>在Java开发领域，Spring Boot和Maven是两个极其重要的工具，它们极大地简化了企业级应用的开发和构建过程。Spring Boot通过自动配置和起步依赖等特性，让开发者能够快速搭建起一个Spring应用；而Maven则是一个强大的项目管理和构建工具，能够帮助我们管理项目的依赖、构建生命周期以及自动化测试等。本文将详细介绍如何快速上手SpringBoot和Maven，包括环境搭建、项目创建、依赖管理、项目构建及运行等方面的内容。</p>
<h5>一、环境准备</h5>
<p>在开始之前，请确保你的开发环境已经安装了以下软件：</p>
<ol>
 <li>
  <p><strong>JDK（Java Development Kit）</strong> ：Spring Boot要求JDK 1.8及以上版本。可以从<a href="https://www.oracle.com/java/technologies/javase-jdk11-downloads.html">Oracle官网</a>或<a href="https://openjdk.java.net/install/">OpenJDK官网</a>下载并安装。</p>
 </li>
 <li>
  <p><strong>Maven</strong> ：Maven是Java项目的构建工具，Spring Boot项目推荐使用Maven来管理项目依赖。可以从<a href="https://maven.apache.org/download.cgi">Maven官网</a>下载并安装Maven。安装完成后，确保<code>mvn -v</code>命令能在命令行中正确执行，以验证Maven安装成功。</p>
 </li>
 <li>
  <p><strong>IDE（集成开发环境）</strong>：虽然Maven和Spring Boot可以通过命令行工具来操作，但使用IDE（如IntelliJ IDEA、Eclipse等）可以大大提高开发效率。这些IDE通常提供了对Maven和Spring Boot的良好支持。</p>
 </li>
</ol>
<h5>二、Spring Boot项目创建</h5>
<h6>使用Spring Initializr快速生成项目</h6>
<p>Spring Initializr（<a href="https://start.spring.io/">https://start.spring.io/</a>）是一个Web应用，可以帮助我们快速生成Spring Boot项目的基础结构。</p>
<ol>
 <li>访问<a href="https://start.spring.io/">Spring Initializr</a>网站。</li>
 <li>选择项目的一些基本信息，如项目类型（Maven Project）、语言（Java）、Spring Boot版本等。</li>
 <li>添加项目依赖。Spring Initializr提供了丰富的起步依赖供我们选择，如Spring Web、Spring Data JPA、MySQL Driver等。根据项目需求选择合适的依赖。</li>
 <li>生成项目。填写完所有信息后，点击"Generate"按钮，Spring Initializr会生成一个压缩包，解压后即可得到项目的基础结构。</li>
</ol>
<h6>使用IDE创建Spring Boot项目</h6>
<p>大多数现代IDE都提供了对Spring Boot项目的支持，可以直接通过IDE来创建Spring Boot项目。</p>
<p>以IntelliJ IDEA为例：</p>
<ol>
 <li>打开IntelliJ IDEA，选择"Create New Project”。</li>
 <li>在左侧菜单中选择"Spring Initializr”，然后点击"Next”。</li>
 <li>填写项目的基本信息，如Group、Artifact等，并选择合适的JDK版本和Spring Boot版本。</li>
 <li>在"Dependencies"标签页中，添加所需的起步依赖。</li>
 <li>点击"Finish"按钮，IDEA会自动生成并配置好Spring Boot项目。</li>
</ol>
<h5>三、Maven依赖管理</h5>
<p>Maven通过<code>pom.xml</code>文件来管理项目的依赖。在Spring Boot项目中，<code>pom.xml</code>文件通常会包含以下关键部分：</p>
<ol>
 <li>
  <p><strong>Spring Boot起步依赖</strong> ：Spring Boot提供了大量的起步依赖（starter），这些起步依赖封装了常用的库和配置，使得开发者能够轻松地引入和使用这些库。例如，<code>spring-boot-starter-web</code>包含了Spring MVC和Tomcat等依赖，用于开发Web应用。</p>
  <pre><code class="prism language-xml">&lt;dependencies&gt;
   &lt;dependency&gt;
       &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
       &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
   &lt;/dependency&gt;
   &lt;!-- 其他依赖 --&gt;
&lt;/dependencies&gt;
</code></pre>
 </li>
 <li>
  <p><strong>依赖管理</strong> ：Maven支持依赖继承、依赖管理和依赖范围等特性，可以帮助我们有效地管理项目的依赖。例如，通过<code>&lt;parent&gt;</code>标签可以指定一个父POM，该父POM中定义了所有子模块共享的依赖版本。</p>
 </li>
 <li>
  <p><strong>插件配置</strong> ：Maven插件是Maven的核心功能之一，用于扩展Maven的功能。在Spring Boot项目中，常用的插件包括<code>spring-boot-maven-plugin</code>，它提供了对Spring Boot应用的打包和运行支持。</p>
  <pre><code class="prism language-xml">&lt;build&gt;
   &lt;plugins&gt;
       &lt;plugin&gt;
           &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
           &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
       &lt;/plugin&gt;
       &lt;!-- 其他插件 --&gt;
   &lt;/plugins&gt;
&lt;/build&gt;
</code></pre>
 </li>
</ol>
<h5>四、项目构建与运行</h5>
<h6>构建项目</h6>
<p>在命令行中，可以通过Maven命令来构建项目。在项目根目录下执行以下命令：</p>
<pre><code class="prism language-bash">mvn clean install
</code></pre>
<p>这个命令会执行清理（clean）、编译（compile）、测试（test）、打包（package）等构建生命周期阶段，最终生成可部署的jar或war包。</p>
<h6>运行项目</h6>
<p>对于Spring Boot应用，可以直接通过Maven插件来运行项目，而无需打包成jar或war文件。在项目根目录下执行以下命令：</p>
<pre><code class="prism language-bash">mvn spring-boot:run
</code></pre>
<p>这条命令会启动Spring Boot应用，并监听默认端口（通常是8080端口）。你可以通过浏览器访问<code>http://localhost:8080</code>来查看应用的首页（如果配置了的话）。</p>
<h5>五、总结</h5>
<p>Spring Boot和Maven是现代Java开发中不可或缺的工具。Spring Boot通过其自动配置和起步依赖等特性，极大地简化了Spring应用的开发；而Maven则通过其强大的项目管理和构建能力，帮助我们高效地管理项目的依赖、构建生命周期和自动化测试等。通过本文的介绍，你应该已经掌握了如何快速上手Spring Boot和Maven，包括环境搭建、项目创建、依赖管理、项目构建及运行等方面的内容。希望这些信息能对你的Java开发工作有所帮助。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142515057</p>]]></description><guid isPermaLink="false">/archives/aee0bb72-a21a-452a-a7eb-3abefeb44ec5</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 00:53:05 GMT</pubDate></item><item><title><![CDATA[Spring全家桶]]></title><link>https://wiki.dgfygk.com/archives/f8d3701f-5569-4520-a2ed-9fae808d7d5d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%E5%85%A8%E5%AE%B6%E6%A1%B6&amp;url=/archives/f8d3701f-5569-4520-a2ed-9fae808d7d5d" width="1" height="1" alt="" style="opacity:0;">
<p>Spring全家桶是一套广泛使用的Java企业级开发框架，它集成了多个子项目和组件，旨在简化企业级应用的开发、部署和管理。以下是一个详细的Spring全家桶使用教程，涵盖了Spring框架的基本概念、核心组件、常用模块以及如何使用这些组件构建企业级应用。</p>
<h4>一、Spring框架概述</h4>
<p><strong>1. Spring简介</strong></p>
<p>Spring是一个开源的、全面的、一站式的企业级应用开发框架，它提供了广泛的功能，包括控制反转（IoC）、面向切面编程（AOP）、事务管理、持久层集成等。Spring的设计目标是简化企业级应用开发，通过非侵入式的设计和基于POJO（Plain Old Java Objects）的编程模型，让开发者能够更加专注于业务逻辑的实现。</p>
<p><strong>2. Spring的核心组件</strong></p>
<p>Spring的核心组件包括：</p>
<ul>
 <li><strong>Beans</strong>：Spring的Bean是Spring框架的核心，它代表了被Spring管理的对象。Spring的Bean容器负责创建、配置和管理这些对象。</li>
 <li><strong>Core</strong>：Spring的核心类库，提供了框架的基本功能，如依赖注入、类型转换等。</li>
 <li><strong>Context</strong>：Spring的上下文支持，提供了访问框架不同部分的功能，如访问配置文件、资源文件等。</li>
 <li><strong>AOP</strong>：面向切面编程的支持，允许开发者将横切关注点（如日志、事务管理等）与业务逻辑代码分离。</li>
 <li><strong>JDBC和ORM</strong>：Spring提供了对JDBC和ORM框架（如Hibernate、MyBatis等）的支持，简化了数据访问层的开发。</li>
 <li><strong>Web</strong>：Spring的Web模块提供了构建Web应用的功能，包括MVC框架、RESTful支持等。</li>
</ul>
<h4>二、Spring全家桶介绍</h4>
<p>Spring全家桶包含了多个子项目和组件，它们共同构成了Spring生态系统。以下是一些常用的Spring子项目和组件：</p>
<ul>
 <li><strong>Spring Boot</strong>：Spring Boot是Spring的简化版，它自动配置了Spring应用中的许多常见设置，使得开发者能够快速启动和运行Spring应用。</li>
 <li><strong>Spring Cloud</strong>：Spring Cloud提供了一套完整的微服务解决方案，包括服务发现、配置管理、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话和集群状态等。</li>
 <li><strong>Spring Data</strong>：Spring Data是一个用于简化数据库访问和数据操作的项目，它提供了对关系型数据库和非关系型数据库的支持。</li>
 <li><strong>Spring Security</strong>：Spring Security是一个功能强大的、高度可定制的身份验证和访问控制框架，它为基于Java的应用提供了全面的安全解决方案。</li>
 <li><strong>Spring AMQP</strong>：Spring AMQP是一个对AMQP协议的高级抽象，它提供了对RabbitMQ等消息代理的支持。</li>
 <li><strong>Spring Batch</strong>：Spring Batch是一个轻量级的、全面的批处理框架，它支持简单的、复杂的和大量数据的批处理作业。</li>
</ul>
<h4>三、Spring全家桶使用教程</h4>
<h5>1. 搭建Spring Boot项目</h5>
<p><strong>步骤一：创建项目</strong></p>
<p>可以使用Spring Initializr（<a href="https://start.spring.io/">https://start.spring.io/</a>）快速生成Spring Boot项目的基础结构。在Spring Initializr中，可以选择需要的Spring Boot版本、项目类型、依赖项等。</p>
<p><strong>步骤二：添加依赖</strong></p>
<p>根据项目需求，在<code>pom.xml</code>或<code>build.gradle</code>文件中添加相应的依赖项。例如，如果需要Web支持，可以添加Spring Web Starter依赖。</p>
<p><strong>步骤三：编写代码</strong></p>
<ul>
 <li><strong>创建Controller</strong>：编写用于处理HTTP请求的Controller类。</li>
 <li><strong>创建Service</strong>：编写业务逻辑处理类，并注入到Controller中。</li>
 <li><strong>创建Repository</strong>：编写数据访问层的Repository接口，并使用Spring Data JPA或MyBatis等ORM框架实现。</li>
</ul>
<p><strong>步骤四：配置应用</strong></p>
<p>在<code>application.properties</code>或<code>application.yml</code>文件中配置应用的各项设置，如数据库连接、服务器端口等。</p>
<p><strong>步骤五：运行应用</strong></p>
<p>使用Spring Boot的Maven或Gradle插件运行应用，或者将应用打包成可执行JAR文件后运行。</p>
<h5>2. 使用Spring Cloud构建微服务</h5>
<p><strong>步骤一：搭建服务注册中心</strong></p>
<p>可以使用Eureka、Nacos等作为服务注册中心。以Nacos为例，首先需要在项目中添加Nacos Discovery的依赖，并在<code>application.properties</code>中配置Nacos的地址和服务的注册信息。</p>
<p><strong>步骤二：创建微服务</strong></p>
<p>创建多个微服务，每个微服务都是一个独立的Spring Boot应用。在每个微服务中，同样需要添加Nacos Discovery的依赖，并配置服务的注册信息。</p>
<p><strong>步骤三：服务调用</strong></p>
<p>微服务之间可以通过RESTful接口进行调用，也可以使用OpenFeign等客户端进行声明式服务调用。在服务调用时，可以利用Spring Cloud的负载均衡机制，实现服务的高可用。</p>
<p><strong>步骤四：配置管理</strong></p>
<p>使用Spring Cloud Config等配置中心，将配置信息存储在外部，微服务启动时从配置中心拉取配置信息。这样可以实现配置的动态更新和集中管理。</p>
<p><strong>步骤五：链路追踪</strong></p>
<p>使用Spring Cloud Sleuth和Zipkin等工具进行分布式链路追踪，可以清晰地看到服务之间的调用关系和调用耗时，有助于问题的排查和性能的优化。</p>
<h4>四、总结</h4>
<p>Spring全家桶提供了一套完整的企业级应用开发解决方案，涵盖了从底层框架到上层应用的各个方面。通过学习和使用Spring全家桶，开发者可以更加高效地构建高质量的企业级应用。在使用Spring全家桶时，需要注意各个组件之间的版本兼容性，以及合理的架构设计和代码规范，以确保应用的稳定性和可维护性。</p>
<p>以上就是一个简要的Spring全家桶使用教程，希望对你有所帮助。由于篇幅限制，这里只介绍了部分内容和步骤，更多详细信息和高级功能，请参考Spring官方文档和相关资料。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142432979</p>]]></description><guid isPermaLink="false">/archives/f8d3701f-5569-4520-a2ed-9fae808d7d5d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 00:52:50 GMT</pubDate></item><item><title><![CDATA[Spring事务类型及传播行为实战指南]]></title><link>https://wiki.dgfygk.com/archives/cc1df41d-9c87-4d12-a6bb-56e781907eba</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%E4%BA%8B%E5%8A%A1%E7%B1%BB%E5%9E%8B%E5%8F%8A%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97&amp;url=/archives/cc1df41d-9c87-4d12-a6bb-56e781907eba" width="1" height="1" alt="" style="opacity:0;">
<h2>Spring事务类型及传播行为实战指南</h2>
<p>在Spring框架中，事务管理是确保数据一致性和完整性的关键部分。通过合理的事务管理，可以确保在数据库操作过程中的原子性、一致性、隔离性和持久性。本文将从Spring事务的基础概念、事务类型、传播行为以及实战案例等方面进行详细讲解。</p>
<h3>一、Spring事务基础</h3>
<h4>1.1 事务概述</h4>
<p>事务是一组数据库操作的逻辑单元，具有原子性、一致性、隔离性和持久性（ACID）四个特性。在Spring中，事务管理提供了一种可靠且灵活的方式来管理这些操作，确保数据的一致性和完整性。</p>
<ul>
 <li><strong>原子性（Atomicity）</strong>：事务中的所有操作要么全部成功执行并提交，要么全部失败并回滚，保持数据的一致性。</li>
 <li><strong>一致性（Consistency）</strong>：事务的执行过程中，数据库从一个一致的状态转变为另一个一致的状态，不会破坏数据的完整性。</li>
 <li><strong>隔离性（Isolation）</strong>：多个并发事务之间应该相互隔离，每个事务的操作应该看起来像是在独立执行，避免数据冲突和不一致性。</li>
 <li><strong>持久性（Durability）</strong>：一旦事务提交，其对数据库的修改应该是永久性的，即使在系统故障或重启后也能够恢复。</li>
</ul>
<h4>1.2 事务管理器</h4>
<p>事务管理器是Spring提供的一个接口，用于管理事务的开始、提交和回滚操作。它与底层的数据库或持久化框架进行交互，并确保事务的正确执行。Spring提供了多种事务管理器，如<code>DataSourceTransactionManager</code>、<code>JpaTransactionManager</code>、<code>HibernateTransactionManager</code>和<code>JtaTransactionManager</code>等，以适应不同的应用场景和开发需求。</p>
<h4>1.3 事务定义和注解</h4>
<p>事务定义（Transaction Definition）定义了事务的属性，如隔离级别、传播行为、超时设置等。在Spring中，事务定义可以通过编程方式或声明式方式来定义。声明式事务定义是通过在方法或类级别上使用注解或XML配置来定义事务的属性。Spring提供了<code>@Transactional</code>注解，用于在方法级别上定义事务的属性。</p>
<h3>二、事务类型</h3>
<p>Spring支持两种主要的事务管理方式：编程式事务管理和声明式事务管理。</p>
<h4>2.1 编程式事务管理</h4>
<p>编程式事务管理通过编写代码来显式地管理事务的开始、提交和回滚。这种方式提供了更高的灵活性，但代码量较大，容易出错。Spring提供了<code>TransactionTemplate</code>等工具类来简化编程式事务管理的实现。</p>
<h4>2.2 声明式事务管理</h4>
<p>声明式事务管理通过注解或XML配置来定义事务的边界和属性，无需在代码中显式地编写事务管理代码。这种方式使事务管理与业务逻辑分离，简化了代码，提高了可维护性。<code>@Transactional</code>注解是声明式事务管理中最常用的方式。</p>
<h3>三、事务传播行为</h3>
<p>事务传播行为（propagation behavior）指的是当一个事务方法被另一个事务方法调用时，这个事务方法应该如何运行。Spring在<code>TransactionDefinition</code>接口中规定了7种类型的事务传播行为。</p>
<h4>3.1 REQUIRED</h4>
<ul>
 <li><strong>行为描述</strong>：如果当前存在事务，则加入该事务中执行；如果当前没有事务，则创建一个新的事务执行。这是Spring默认的传播行为。</li>
 <li><strong>应用场景</strong>：适用于大多数需要事务支持的方法，特别是当方法可能被其他事务方法调用时。</li>
</ul>
<h4>3.2 REQUIRES_NEW</h4>
<ul>
 <li><strong>行为描述</strong>：总是创建一个新的事务，并挂起当前事务（如果存在）。在新的事务中执行方法，不受外部事务的影响。</li>
 <li><strong>应用场景</strong>：适用于需要确保方法执行完全独立于外部事务的场景，例如，在嵌套事务中需要完全隔离的操作。</li>
</ul>
<h4>3.3 SUPPORTS</h4>
<ul>
 <li><strong>行为描述</strong>：如果当前存在事务，就加入该事务；如果当前不存在事务，就以非事务方式执行。</li>
 <li><strong>应用场景</strong>：适用于那些对事务没有强制要求的方法，但在有事务存在时可以参与。</li>
</ul>
<h4>3.4 NOT_SUPPORTED</h4>
<ul>
 <li><strong>行为描述</strong>：如果当前存在事务，就把当前事务挂起。以非事务方式执行方法。</li>
 <li><strong>应用场景</strong>：适用于那些不需要事务支持的操作，例如，读取数据但不修改数据的操作。</li>
</ul>
<h4>3.5 MANDATORY</h4>
<ul>
 <li><strong>行为描述</strong>：如果当前存在事务，就加入该事务；如果当前不存在事务，就抛出异常。</li>
 <li><strong>应用场景</strong>：适用于那些必须在事务中执行的方法，确保操作的原子性和一致性。</li>
</ul>
<h4>3.6 NEVER</h4>
<ul>
 <li><strong>行为描述</strong>：以非事务方式执行方法，如果当前存在事务，则抛出异常。</li>
 <li><strong>应用场景</strong>：适用于那些绝对不能在事务中执行的操作，确保操作的独立性。</li>
</ul>
<h4>3.7 NESTED</h4>
<ul>
 <li><strong>行为描述</strong>：如果当前存在事务，则在嵌套事务内执行。如果当前没有事务，就创建一个新事务。</li>
 <li><strong>应用场景</strong>：适用于需要在当前事务内部执行一个具有部分回滚能力的子事务的场景。</li>
</ul>
<h3>四、实战案例</h3>
<h4>4.1 场景描述</h4>
<p>假设有一个电商系统，其中包括用户购买商品的操作。购买操作涉及到多个数据库操作，如库存减少、用户余额减少等，这些操作需要在一个事务中完成，以确保数据的一致性。</p>
<h4>4.2 实现步骤</h4>
<ol>
 <li>
  <p><strong>定义服务层接口和实现</strong></p>
  <p>定义一个<code>BookShopService</code>接口和它的实现类<code>BookShopServiceImpl</code>，用于处理购买商品的业务逻辑。</p>
  <pre><code class="prism language-java">public interface BookShopService {

   void purchase(String username, String isbn);
}

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class BookShopServiceImpl implements BookShopService {

   @Autowired
   private BookShopDao bookShopDao;

   @Override
   public void purchase(String username, String isbn) {

       // 1. 获取书的单价
       int price = bookShopDao.findBookPriceByIsbn(isbn);
       // 2. 更新书的库存
       bookShopDao.updateBookStock(isbn);
       // 3. 更新用户的余额
       bookShopDao.updateUserAccount(username, price);
   }
}
</code></pre>
  <p>注意，在<code>BookShopServiceImpl</code>类上使用了<code>@Transactional</code>注解，并指定了传播行为为<code>REQUIRED</code>。这意味着如果当前存在事务，则购买操作将加入该事务；如果当前没有事务，则创建一个新的事务。</p>
 </li>
 <li>
  <p><strong>调用服务层方法</strong></p>
  <p>在需要购买商品的地方，直接调用<code>BookShopService</code>的<code>purchase</code>方法即可。</p>
  <pre><code class="prism language-java">@Autowired
private BookShopService bookShopService;

public void checkout(String username, List&lt;String&gt; isbns) {

   for (String isbn : isbns) {

       bookShopService.purchase(username, isbn);
   }
}
</code></pre>
  <p>由于<code>purchase</code>方法已经通过<code>@Transactional</code>注解声明了事务，因此在<code>checkout</code>方法中调用<code>purchase</code>方法时，所有的购买操作将在一个事务中完成，确保数据的一致性。</p>
 </li>
</ol>
<h4>4.3 隔离级别和错误处理</h4>
<p>在实际应用中，还需要根据业务需求设置合适的隔离级别，并在发生错误时进行适当的错误处理和事务回滚。</p>
<ul>
 <li><strong>设置隔离级别</strong> ：可以通过<code>@Transactional</code>注解的<code>isolation</code>属性来设置隔离级别。</li>
 <li><strong>错误处理</strong> ：在<code>purchase</code>方法中，如果发生异常，Spring将自动回滚事务，确保数据的一致性。</li>
</ul>
<h3>五、总结</h3>
<p>Spring事务管理提供了可靠且灵活的方式来管理数据库操作中的事务，确保数据的一致性和完整性。通过合理使用事务类型和传播行为，可以灵活地控制事务的边界和行为，满足各种业务需求。在实际开发中，应根据具体业务场景选择合适的事务管理方式，并合理设置事务属性和隔离级别，以确保系统的稳定性和可靠性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142483723</p>]]></description><guid isPermaLink="false">/archives/cc1df41d-9c87-4d12-a6bb-56e781907eba</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 00:52:38 GMT</pubDate></item><item><title><![CDATA[机器学习中的元强化学习]]></title><link>https://wiki.dgfygk.com/archives/e9f10f8e-10e7-4b95-8bc7-2982ce0f9b06</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E5%85%83%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0&amp;url=/archives/e9f10f8e-10e7-4b95-8bc7-2982ce0f9b06" width="1" height="1" alt="" style="opacity:0;">
<h4>机器学习中的元强化学习</h4>
<h5>引言</h5>
<p>随着人工智能技术的不断发展，机器学习作为其核心分支之一，在各个领域取得了显著进展。其中，强化学习（Reinforcement Learning, RL）作为机器学习的重要分支，通过让智能体（Agent）在环境中不断探索和学习，以最大化累积奖励为目标，逐渐成为解决复杂决策问题的重要工具。然而，传统的强化学习在面对新任务时往往需要从零开始学习，导致训练成本高、样本效率低等问题。为解决这些问题，元强化学习（Meta-Reinforcement Learning, Meta-RL）应运而生，它结合了元学习（Meta-Learning）和强化学习的优势，旨在通过迁移已有知识来提升新任务上的学习效率。</p>
<h5>强化学习基础</h5>
<p><strong>定义与框架</strong></p>
<p>强化学习是一种通过智能体与环境交互，以最大化累积奖励为目标的学习方法。其基本框架包括智能体、环境、状态、动作和奖励五个部分。智能体根据当前状态执行动作，环境根据动作更新状态并返回奖励。智能体通过不断试错和学习，最终找到最大化累积奖励的策略。</p>
<p><strong>核心要素</strong></p>
<ul>
 <li><strong>状态（State）</strong>：表示智能体在环境中的当前情况，是智能体进行决策的基础。</li>
 <li><strong>动作（Action）</strong>：智能体在环境中可以采取的操作，决定了环境如何变化。</li>
 <li><strong>奖励（Reward）</strong>：环境对智能体执行动作后的反馈，用于评价动作的好坏。</li>
 <li><strong>策略（Policy）</strong>：智能体在不同状态下选择动作的规则，是智能体学习的核心。</li>
 <li><strong>值函数（Value Function）</strong>：表示智能体在状态下遵循策略能够获得的累积奖励的期望值。</li>
</ul>
<p><strong>算法原理</strong></p>
<p>强化学习算法主要分为基于值函数和基于策略梯度两类。基于值函数的算法通过估计状态-动作值函数（Q函数）来选择最优动作；而基于策略梯度的算法则直接优化策略参数，以最大化累积奖励。</p>
<h5>元强化学习概述</h5>
<p><strong>定义与背景</strong></p>
<p>元强化学习是强化学习与元学习的交叉领域，旨在通过迁移已有知识来提升新任务上的学习效率。元学习，也被称为学习如何学习（Learning-to-Learn），关注于如何使学习算法本身具有更强的学习能力和适应性。通过将元学习的思想引入强化学习，元强化学习期望解决传统强化学习在新任务上从零开始学习的局限性。</p>
<p><strong>核心思想</strong></p>
<p>元强化学习的核心思想是在多个任务上学习"元"知识，这些"元"知识可以是任务之间的共性、策略的结构、优化算法的参数等。通过在新任务上利用这些"元"知识，元强化学习可以显著降低学习成本，提高学习效率。</p>
<p><strong>应用场景</strong></p>
<p>元强化学习在游戏、机器人控制、资源管理、金融交易等多个领域具有广泛的应用前景。例如，在游戏AI开发中，元强化学习可以帮助智能体快速适应不同游戏场景和规则；在机器人控制中，元强化学习可以使机器人更快地学会新技能，提高任务完成效率。</p>
<h5>元强化学习算法与实现</h5>
<p><strong>算法框架</strong></p>
<p>元强化学习的算法框架通常包括两个阶段：元训练阶段和元测试阶段。在元训练阶段，算法在多个任务上训练，学习"元"知识；在元测试阶段，算法利用学习到的"元"知识在新任务上进行快速学习。</p>
<p><strong>关键技术</strong></p>
<ul>
 <li><strong>任务采样</strong>：从任务分布中随机采样多个任务，用于元训练。</li>
 <li><strong>元知识表示</strong>：通过神经网络等模型表示任务之间的共性、策略结构等"元"知识。</li>
 <li><strong>策略优化</strong>：在元训练阶段，通过优化算法更新策略参数和"元"知识表示，以最大化累积奖励。</li>
 <li><strong>快速适应</strong>：在元测试阶段，利用学习到的"元"知识在新任务上进行快速学习，找到最优策略。</li>
</ul>
<p><strong>实现示例</strong></p>
<p>以MAML（Model-Agnostic Meta-Learning）框架为例，MAML是一种通用的元学习算法，也可以应用于元强化学习。在MAML中，算法首先在不同任务上训练一个初始策略，然后对每个任务进行微调，以找到每个任务上的最优策略。最后，算法通过优化初始策略的参数，使得微调后的策略在所有任务上都能取得较好的效果。在元强化学习中，可以类似地应用MAML框架，首先在多个任务上训练一个初始策略，然后在新任务上进行微调，以快速找到最优策略。</p>
<h5>挑战与前景</h5>
<p><strong>挑战</strong></p>
<ul>
 <li><strong>样本效率</strong>：虽然元强化学习可以提高新任务上的学习效率，但其本身也需要大量的训练数据来学习"元"知识。因此，如何提高样本效率是元强化学习面临的重要挑战。</li>
 <li><strong>泛化能力</strong>：元强化学习需要学习的"元"知识需要具有足够的泛化能力，以便能够应用于不同的任务。然而，如何设计有效的算法来提取和表示泛化能力强的"元"知识是一个难题。</li>
 <li><strong>计算复杂度</strong>：元强化学习需要在多个任务上进行训练，导致计算复杂度较高。因此，如何降低计算复杂度，提高算法的可扩展性也是需要考虑的问题。</li>
</ul>
<p><strong>前景</strong></p>
<ul>
 <li><strong>算法优化</strong>：随着算法研究的不断深入，元强化学习有望在样本效率、泛化能力和计算复杂度等方面取得更大的突破。</li>
 <li><strong>应用场景拓展</strong>：随着技术的不断发展，元强化学习有望在更多的领域得到应用，如自动驾驶、智能制造、医疗健康等。</li>
 <li><strong>与深度学习结合</strong>：深度学习技术为元强化学习提供了强大的表示和学习能力。未来，元强化学习与深度学习的结合有望产生更加高效、智能的算法和应用。</li>
</ul>
<h5>结论</h5>
<p>元强化学习作为强化学习与元学习的交叉领域，通过迁移已有知识来提升新任务上的学习效率，为解决传统强化学习在新任务上从零开始学习的局限性提供了新的思路。尽管目前元强化学习仍面临诸多挑战，但随着算法研究的不断深入和技术的不断发展，其应用前景将越来越广阔。未来，元强化学习有望在更多领域发挥重要作用，推动人工智能技术的进一步发展。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142553747</p>]]></description><guid isPermaLink="false">/archives/e9f10f8e-10e7-4b95-8bc7-2982ce0f9b06</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:52:25 GMT</pubDate></item><item><title><![CDATA[数据链路层功能、组帧、流量控制与可靠传输机制]]></title><link>https://wiki.dgfygk.com/archives/8d7923db-8554-483b-814b-651c7ece915d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF%E5%B1%82%E5%8A%9F%E8%83%BD%E3%80%81%E7%BB%84%E5%B8%A7%E3%80%81%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6%E4%B8%8E%E5%8F%AF%E9%9D%A0%E4%BC%A0%E8%BE%93%E6%9C%BA%E5%88%B6&amp;url=/archives/8d7923db-8554-483b-814b-651c7ece915d" width="1" height="1" alt="" style="opacity:0;">
<h4>数据链路层功能、组帧、流量控制与可靠传输机制</h4>
<p>在计算机网络中，数据链路层（Data Link Layer）扮演着至关重要的角色，它位于物理层和网络层之间，负责处理数据帧的封装、传输、差错控制、流量控制等任务。本文将从数据链路层的功能、组帧方法、流量控制以及可靠传输机制等方面进行详细阐述。</p>
<h5>一、数据链路层的功能</h5>
<p>数据链路层的主要功能可以概括为以下几个方面：</p>
<ol>
 <li>
  <p><strong>成帧（帧同步）</strong> ：
   <br>
   数据链路层将来自网络层的数据组合成数据块，并封装成帧。帧是数据链路层的协议数据单元（PDU），用于在物理层上进行传输。成帧技术确保了数据的完整性，使接收方能够正确地从接收到的比特流中识别出帧的起始和结束。</p>
 </li>
 <li>
  <p><strong>差错控制</strong> ：
   <br>
   在数据传输过程中，由于物理链路的性能和网络通信环境等因素，可能会出现错误。数据链路层通过差错控制机制来检测和纠正这些错误。常见的差错控制方法包括前向纠错（FEC）、反馈检测（ARQ）、混合纠错（HEC）和信息反馈（IRQ）。</p>
 </li>
 <li>
  <p><strong>流量控制</strong> ：
   <br>
   流量控制是指控制发送方发送数据的速率，使接收方有足够的缓冲空间来接收和处理数据。这主要通过某种反馈机制来实现，以确保数据的传输既不过快也不过慢，避免数据丢失和缓冲区溢出。</p>
 </li>
 <li>
  <p><strong>链路管理</strong> ：
   <br>
   链路管理包括数据链路的建立、维持和释放。在通信双方开始数据传输之前，需要先建立一条数据链路，以确保数据的可靠传输。数据传输结束后，需要释放链路资源，以便其他设备使用。</p>
 </li>
 <li>
  <p><strong>MAC寻址</strong> ：
   <br>
   在以太网等网络中，数据链路层使用MAC地址进行寻址。MAC地址是网卡的物理地址，用于在局域网中唯一标识设备。</p>
 </li>
 <li>
  <p><strong>区分数据和控制信息</strong> ：
   <br>
   数据链路层需要区分数据帧和控制帧，以便正确处理它们。控制帧通常用于流量控制、差错控制等目的。</p>
 </li>
 <li>
  <p><strong>透明传输</strong> ：
   <br>
   透明传输是指无论传输何种类型的数据，数据链路层都应能正确处理，而不会将数据误认为是控制信息。</p>
 </li>
</ol>
<h5>二、组帧方法</h5>
<p>组帧是将网络层递交的数据分组封装成帧的过程。常见的组帧方法有以下几种：</p>
<ol>
 <li>
  <p><strong>字符计数法</strong> ：
   <br>
   在帧头部使用一个计数字段来标明帧内字符数。但这种方法存在同步问题，如果计数字段出错，收发双方将失去同步。</p>
 </li>
 <li>
  <p><strong>字符填充的首尾定界符法</strong> ：
   <br>
   使用特定的字符作为帧的开始和结束标志。为了防止帧内数据中出现与定界符相同的字符，需要在这些字符前添加转义字符。</p>
 </li>
 <li>
  <p><strong>零比特填充的首尾标志法</strong> ：
   <br>
   使用特定的比特模式（如01111110）作为帧的开始和结束标志。在发送信息时，如果遇到与标志字段相同的比特序列，则在其后添加额外的零比特进行填充。</p>
 </li>
 <li>
  <p><strong>违规编码法</strong> ：
   <br>
   在物理层进行比特编码时，采用违规编码来定界帧的起始和终止。例如，在曼彻斯特编码中，使用"高-高"或"低-低"电平对作为违规编码来定界帧。</p>
 </li>
</ol>
<h5>三、流量控制机制</h5>
<p>流量控制是数据链路层的重要功能之一，它用于控制发送方发送数据的速率，确保接收方能够及时处理接收到的数据。常见的流量控制机制包括停止-等待协议和滑动窗口协议。</p>
<ol>
 <li>
  <p><strong>停止-等待协议</strong> ：
   <br>
   这是最简单的流量控制方法。发送方每次只允许发送一个帧，并等待接收方的确认信号。只有收到确认信号后，发送方才能发送下一个帧。这种方法效率较低，但实现简单。</p>
 </li>
 <li>
  <p><strong>滑动窗口协议</strong> ：
   <br>
   滑动窗口协议是一种更高效的流量控制方法。发送方和接收方都维护一个窗口，用于控制可以发送和接收的帧的范围。发送窗口表示在还未收到对方确认信息的情况下，发送方最多还可以发送多少个帧；接收窗口表示接收方还可以接收哪些帧。通过调整窗口的大小，可以实现灵活的流量控制。</p>
 </li>
 <li>
  <p><strong>后退N帧协议（GBN）</strong>：在这种协议中，发送窗口的大小可以大于1，但接收窗口的大小通常为1。如果某个帧出错，发送方需要重传该帧及其之后的所有帧。</p>
 </li>
 <li>
  <p><strong>选择重传协议（SR）</strong>：与GBN协议不同，SR协议允许接收窗口的大小大于1。这样，即使某个帧出错，也只需要重传该帧，而不需要重传后续的所有帧。</p>
 </li>
</ol>
<h5>四、可靠传输机制</h5>
<p>可靠传输是指发送方发送的数据都能被接收方正确地接收。数据链路层通常采用确认和超时重传两种机制来实现可靠传输。</p>
<ol>
 <li>
  <p><strong>确认</strong> ：
   <br>
   接收方每收到一个数据帧，都需要向发送方发送一个确认帧，表示已正确收到该数据帧。确认帧中通常包含已接收帧的序号，以便发送方知道哪些帧已被成功接收。</p>
 </li>
 <li>
  <p><strong>超时重传</strong> ：
   <br>
   发送方在发送一个数据帧后，会启动一个计时器。如果在规定时间内没有收到接收方的确认帧，则发送方认为该帧可能已丢失或出错，于是重传该帧。这个过程会一直重复，直到收到确认帧为止。</p>
 </li>
</ol>
<p>结合确认和超时重传机制，可以实现自动重传请求（ARQ）协议。ARQ协议分为停止-等待协议、后退N帧协议和选择重传协议等。这些协议都利用滑动窗口技术来实现流量控制和可靠传输。</p>
<h5>五、总结</h5>
<p>数据链路层是计算机网络中至关重要的一层，它负责处理数据帧的封装、传输、差错控制、流量控制等任务。通过成帧技术，数据链路层能够将网络层的数据封装成帧，并通过物理层进行传输。通过差错控制机制，数据链路层能够检测和纠正数据传输过程中的错误。通过流量控制机制，数据链路层能够控制发送方发送数据的速率，确保接收方能够及时处理接收到的数据。同时，数据链路层还采用确认和超时重传机制来实现可靠传输。这些功能的实现离不开组帧方法和滑动窗口协议等技术的支持。</p>
<p>总的来说，数据链路层的设计目标是为网络层提供可靠、高效的数据传输服务。通过不断优化和完善相关技术和协议，可以进一步提高数据链路层的性能和可靠性，为整个计算机网络的发展提供有力支持。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142451052</p>]]></description><guid isPermaLink="false">/archives/8d7923db-8554-483b-814b-651c7ece915d</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:52:12 GMT</pubDate></item><item><title><![CDATA[探索 TensorFlow：构建强大的机器学习模型]]></title><link>https://wiki.dgfygk.com/archives/49fc8f15-a894-484e-9d20-03eb28c91e95</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8E%A2%E7%B4%A2%20TensorFlow%EF%BC%9A%E6%9E%84%E5%BB%BA%E5%BC%BA%E5%A4%A7%E7%9A%84%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E6%A8%A1%E5%9E%8B&amp;url=/archives/49fc8f15-a894-484e-9d20-03eb28c91e95" width="1" height="1" alt="" style="opacity:0;">
<h4>探索 TensorFlow：构建强大的机器学习模型</h4>
<p>TensorFlow，由Google开发并维护，是一个开源的机器学习库，广泛用于深度学习研究和生产。它提供了丰富的API和工具，使得构建、训练和部署复杂的机器学习模型变得简单而高效。在本文中，我们将深入探讨TensorFlow的基本概念、安装方法、构建模型的基本步骤、优化策略以及实际应用案例，旨在帮助读者理解并掌握如何使用TensorFlow来构建强大的机器学习模型。</p>
<h5>一、TensorFlow基础</h5>
<h6>1.1 TensorFlow简介</h6>
<p>TensorFlow的核心是一个计算图（Graph），图中的节点（Nodes）代表数学操作，而边（Edges）则代表在这些节点之间流动的多维数组（Tensors）。这种设计使得TensorFlow能够高效地执行大规模数值计算，特别是在GPU和TPU等硬件上。</p>
<p>TensorFlow支持多种编程范式，包括静态图（Eager Execution之前的模式）和动态图（Eager Execution）。静态图模式下，计算图在运行时被构建并优化，而动态图模式下，操作会立即执行，使得调试和原型设计更加直观。</p>
<h6>1.2 安装TensorFlow</h6>
<p>安装TensorFlow非常简单，可以通过pip命令直接安装。对于大多数用户来说，安装CPU版本的TensorFlow就足够了，但如果你需要利用GPU加速，则需要确保你的系统满足CUDA和cuDNN的依赖要求。</p>
<pre><code class="prism language-bash">pip install tensorflow  # 安装CPU版本
# 或者
pip install tensorflow-gpu  # 安装GPU版本（注意：tensorflow-gpu已在新版本中合并到tensorflow）
</code></pre>
<h5>二、构建TensorFlow模型的基本步骤</h5>
<h6>2.1 数据准备</h6>
<p>在构建任何机器学习模型之前，首先需要准备数据。这包括数据的收集、清洗、转换和划分（训练集、验证集和测试集）。TensorFlow提供了多种工具来帮助处理数据，如<code>tf.data</code> API，它允许你构建复杂的数据输入管道。</p>
<h6>2.2 模型定义</h6>
<p>在TensorFlow中，模型通常是通过继承<code>tf.keras.Model</code>类并定义其<code>__init__</code>和<code>call</code>方法来构建的。<code>__init__</code>方法用于初始化模型的层，而<code>call</code>方法则定义了数据通过这些层时的计算流程。</p>
<pre><code class="prism language-python">import tensorflow as tf

class MyModel(tf.keras.Model):
    def __init__(self):
        super(MyModel, self).__init__()
        self.dense1 = tf.keras.layers.Dense(64, activation='relu')
        self.dense2 = tf.keras.layers.Dense(10)

    def call(self, inputs):
        x = self.dense1(inputs)
        return self.dense2(x)
</code></pre>
<h6>2.3 编译模型</h6>
<p>在模型训练之前，需要对其进行编译。编译时，你需要指定优化器、损失函数和评估指标。这些参数将指导模型的训练过程。</p>
<pre><code class="prism language-python">model = MyModel()
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
</code></pre>
<h6>2.4 训练模型</h6>
<p>使用准备好的训练数据对模型进行训练。TensorFlow提供了<code>fit</code>方法来简化训练过程。</p>
<pre><code class="prism language-python">model.fit(train_dataset, epochs=5, validation_data=validation_dataset)
</code></pre>
<h6>2.5 评估与预测</h6>
<p>训练完成后，使用测试集评估模型的性能，并使用模型进行预测。</p>
<pre><code class="prism language-python">test_loss, test_acc = model.evaluate(test_dataset)
print(f'Test accuracy: {
     test_acc}')

predictions = model.predict(test_images)
</code></pre>
<h5>三、优化策略</h5>
<h6>3.1 超参数调优</h6>
<p>超参数（如学习率、批量大小、层数等）对模型的性能有重要影响。TensorFlow提供了多种工具来帮助进行超参数调优，如<code>tf.keras.callbacks.LearningRateScheduler</code>用于动态调整学习率，<code>tf.keras.wrappers.scikit_learn.KerasClassifier</code>可以与scikit-learn的网格搜索（GridSearchCV）结合使用来寻找最佳超参数组合。</p>
<h6>3.2 正则化与Dropout</h6>
<p>为了防止过拟合，可以在模型中加入正则化项（如L1、L2正则化）或使用Dropout层。Dropout层在训练过程中随机丢弃一部分神经元的输出，有助于模型学习到更加鲁棒的特征。</p>
<h6>3.3 批量归一化</h6>
<p>批量归一化（Batch Normalization）是一种通过规范化层输入来加速训练过程并减少过拟合的技术。它可以使模型更加稳定，并允许使用更高的学习率。</p>
<h5>四、实际应用案例</h5>
<h6>4.1 图像分类</h6>
<p>TensorFlow在图像分类任务中表现出色。使用预训练的卷积神经网络（如ResNet、VGG等）作为特征提取器，并在其基础上添加自定义层进行微调，可以快速构建出高性能的图像分类模型。</p>
<h6>4.2 自然语言处理</h6>
<p>TensorFlow也广泛应用于自然语言处理领域。通过结合Transformer等先进的模型架构，TensorFlow能够处理复杂的NLP任务，如文本分类、情感分析、机器翻译等。</p>
<h6>4.3 序列预测</h6>
<p>在时间序列分析领域，TensorFlow同样发挥着重要作用。使用LSTM或GRU等循环神经网络（RNN）结构，TensorFlow可以捕捉序列数据中的时间依赖关系，实现准确的序列预测。</p>
<h5>五、结论</h5>
<p>TensorFlow作为一个功能强大的机器学习库，为构建、训练和部署复杂的机器学习模型提供了全面的支持。通过掌握TensorFlow的基本概念和操作，结合适当的优化策略，我们可以构建出性能卓越、适应性强的机器学习模型，以应对各种复杂的实际问题。随着TensorFlow的不断发展和完善，相信它将在未来的机器学习领域发挥更加重要的作用。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142483688</p>]]></description><guid isPermaLink="false">/archives/49fc8f15-a894-484e-9d20-03eb28c91e95</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:51:41 GMT</pubDate></item><item><title><![CDATA[决策树算法在机器学习中的应用]]></title><link>https://wiki.dgfygk.com/archives/53b3ccb0-e819-4169-921e-6ab2f83b9127</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%86%B3%E7%AD%96%E6%A0%91%E7%AE%97%E6%B3%95%E5%9C%A8%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8&amp;url=/archives/53b3ccb0-e819-4169-921e-6ab2f83b9127" width="1" height="1" alt="" style="opacity:0;">
<h4>决策树算法在机器学习中的应用</h4>
<p>决策树（Decision Tree）算法是一种基本的分类与回归方法，它通过树状结构对数据进行建模，以解决分类和回归问题。决策树算法在机器学习中具有广泛的应用，其直观性、易于理解和实现的特点使其成为数据挖掘和数据分析中的常用工具。本文将详细探讨决策树算法的基本原理、算法实现、优缺点以及应用场景。</p>
<h5>一、决策树的基本原理</h5>
<p>决策树模型通过树状结构将数据集划分为若干子集，每个子集对应树的一个节点。在分类问题中，树中的内部节点表示特征或属性的判断条件，分支表示不同的判断结果，叶子节点则表示最终的分类结果。在回归问题中，叶子节点表示预测的连续值。</p>
<p>决策树的学习通常包括三个主要步骤：特征选择、决策树的生成和决策树的修剪。</p>
<ol>
 <li>
  <p><strong>特征选择</strong>：选择合适的特征作为节点，可以快速地分类，减少决策树的深度。特征选择的目标是使得分类后的数据集更加纯净，常用的选择准则包括信息增益、信息增益率、基尼指数等。</p>
 </li>
 <li>
  <p><strong>决策树的生成</strong>：根据选择的特征，递归地构建决策树。在每一步，选择最优的特征对数据集进行划分，直至满足停止条件（如所有样本属于同一类、达到预设的树深度、信息增益小于阈值等）。</p>
 </li>
 <li>
  <p><strong>决策树的修剪</strong>：由于决策树容易过拟合，即在训练集上表现良好，但在测试集上表现不佳，因此需要通过剪枝来简化模型，提高泛化能力。剪枝分为预剪枝和后剪枝两种，前者在决策树生成过程中提前停止树的生长，后者则在树完全生成后自底向上进行修剪。</p>
 </li>
</ol>
<h5>二、决策树算法的实现</h5>
<p>决策树算法的实现包括多种具体的算法，如ID3、C4.5、CART（Classification And Regression Tree）等。这些算法在特征选择、树的生成和修剪等方面有所不同，但基本思想是一致的。</p>
<p>以下是一个简单的决策树构建过程示例，使用信息增益作为特征选择的准则：</p>
<ol>
 <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>：对每个子集重复步骤2-4，直至满足停止条件。</p>
 </li>
 <li>
  <p><strong>剪枝处理</strong>：根据需要进行预剪枝或后剪枝，以减少过拟合的风险。</p>
 </li>
</ol>
<h5>三、决策树算法的优缺点</h5>
<h6>优点</h6>
<ol>
 <li>
  <p><strong>易于理解和解释</strong>：决策树模型可以可视化展示，直观易懂，便于非专业人员理解和使用。</p>
 </li>
 <li>
  <p><strong>可以处理多种数据类型</strong>：决策树算法可以处理离散型和连续型的特征，适用范围广泛。</p>
 </li>
 <li>
  <p><strong>可以处理大规模数据集</strong>：决策树算法的训练速度相对较快，在处理大规模数据集时具有一定的优势。</p>
 </li>
 <li>
  <p><strong>无需数据预处理</strong>：决策树算法对数据的预处理要求较低，无需进行复杂的特征缩放或标准化处理。</p>
 </li>
</ol>
<h6>缺点</h6>
<ol>
 <li>
  <p><strong>容易过拟合</strong>：决策树算法容易在训练集上过拟合，导致在测试集上表现不佳。</p>
 </li>
 <li>
  <p><strong>对噪声和缺失数据敏感</strong>：决策树算法对噪声和缺失数据非常敏感，容易产生不稳定的模型。</p>
 </li>
 <li>
  <p><strong>无法处理连续值输出</strong>：决策树算法只能生成离散型的输出，无法处理连续值输出的问题。</p>
 </li>
 <li>
  <p><strong>需要选择合适的停止条件</strong>：决策树的生成过程中需要选择合适的停止条件，以防止模型过于复杂或过于简单。</p>
 </li>
</ol>
<h5>四、决策树算法的应用场景</h5>
<p>决策树算法在机器学习中具有广泛的应用场景，包括但不限于以下几个方面：</p>
<ol>
 <li>
  <p><strong>分类问题</strong>：决策树算法是分类问题中的常用方法，可以用于医疗诊断、信用评估、垃圾邮件识别等领域。</p>
 </li>
 <li>
  <p><strong>回归问题</strong>：虽然决策树主要用于分类问题，但也可以通过修改算法实现回归问题的求解，如CART算法。</p>
 </li>
 <li>
  <p><strong>特征选择</strong>：决策树算法在特征选择中也具有重要意义，可以通过计算特征的信息增益或基尼指数来评估特征的重要性。</p>
 </li>
 <li>
  <p><strong>集成学习</strong>：决策树算法是集成学习方法（如随机森林、梯度提升树等）的基础，通过组合多个决策树来提高模型的稳定性和泛化能力。</p>
 </li>
</ol>
<h5>五、总结</h5>
<p>决策树算法作为一种基本的分类与回归方法，在机器学习中具有广泛的应用。其直观性、易于理解和实现的特点使其成为数据挖掘和数据分析中的常用工具。然而，决策树算法也存在一些缺点，如容易过拟合、对噪声和缺失数据敏感等。因此，在实际应用中需要根据具体问题选择合适的算法和参数，以获得更好的性能。</p>
<p>通过不断的研究和改进，决策树算法将在更多领域发挥重要作用，为机器学习和数据科学的发展贡献力量。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142515132</p>]]></description><guid isPermaLink="false">/archives/53b3ccb0-e819-4169-921e-6ab2f83b9127</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:50:15 GMT</pubDate></item><item><title><![CDATA[NumPy的基本特性]]></title><link>https://wiki.dgfygk.com/archives/5e71b2b1-8020-41b1-bd5b-d73aa4a860e2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=NumPy%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%89%B9%E6%80%A7&amp;url=/archives/5e71b2b1-8020-41b1-bd5b-d73aa4a860e2" width="1" height="1" alt="" style="opacity:0;">
<p>NumPy作为Python中用于科学计算的基础库，不仅提供了强大的多维数组对象，还包含了一系列用于操作这些数组的函数和工具。它极大地简化了数组操作，提高了数据处理的效率和灵活性，是数据科学、机器学习、图像处理等领域不可或缺的工具。以下是对NumPy的深入探索，包括其基本特性、高级功能、应用场景以及性能优化等方面。</p>
<h4>一、NumPy的基本特性</h4>
<h5>1.1 多维数组对象（ndarray）</h5>
<p>NumPy的核心是ndarray对象，这是一个固定大小的同类型元素的多维数组。与Python的内置列表（list）相比，ndarray在存储和计算上更加高效，因为它在内存中连续存储，且大小在创建时确定。ndarray支持大量的数学和科学计算操作，如元素级运算、广播、切片、索引等。</p>
<h5>1.2 数据类型</h5>
<p>NumPy支持多种数据类型，包括整数、浮点数、复数等。在创建数组时，可以指定数组的数据类型（dtype），这有助于节省内存并提高计算效率。例如，如果数据范围较小，可以使用较小的整数类型（如int8、int16）代替默认的int64。</p>
<h5>1.3 广播机制</h5>
<p>广播是NumPy中一个非常重要的特性，它允许不同形状的数组进行数学运算。在运算过程中，NumPy会自动扩展较小数组的形状，使其与较大数组的形状相匹配，然后逐元素进行运算。这种机制极大地简化了数组操作，避免了显式的循环和数组重塑。</p>
<h4>二、NumPy的高级功能</h4>
<h5>2.1 索引与切片</h5>
<p>NumPy支持灵活的索引和切片操作，允许用户以多种方式选择数组中的元素。除了基本的整数索引和切片外，还支持布尔索引、花式索引（fancy indexing）以及整数数组索引等高级索引方式。这些索引方式为用户提供了强大的数据选择能力，使得数据处理更加灵活和高效。</p>
<h5>2.2 随机数生成</h5>
<p>NumPy提供了多种生成随机数的函数，支持不同的分布（如均匀分布、正态分布等）。这些函数在模拟实验、统计分析等领域有着广泛的应用。通过随机数生成，用户可以轻松创建大量随机数据，用于测试算法、训练模型等。</p>
<h5>2.3 线性代数操作</h5>
<p>NumPy的linalg模块提供了丰富的线性代数功能，包括矩阵乘法、求逆、行列式计算、特征值分解等。这些功能对于解决线性方程组、计算矩阵的逆、进行矩阵分解等任务至关重要。在机器学习、图像处理等领域，线性代数操作是不可或缺的。</p>
<h5>2.4 统计函数</h5>
<p>NumPy提供了大量的统计函数，如mean（均值）、std（标准差）、sum（求和）等，用于对数组进行统计分析。这些函数不仅计算速度快，而且支持对多维数组进行操作，为用户提供了便捷的数据分析工具。</p>
<h5>2.5 与其他库的整合</h5>
<p>NumPy可以与Pandas、Matplotlib等库无缝整合，构建更强大的数据处理和可视化流程。Pandas提供了更高级的数据结构和数据分析功能，而Matplotlib则提供了强大的数据可视化能力。通过将这些库结合使用，用户可以更加高效地处理和分析数据。</p>
<h4>三、NumPy的应用场景</h4>
<h5>3.1 科学计算</h5>
<p>在科学计算领域，NumPy凭借其高效的数组操作和丰富的数学函数库，成为了不可或缺的工具。无论是物理模拟、化学计算还是生物学数据分析，NumPy都能够提供强大的支持。</p>
<h5>3.2 数据处理</h5>
<p>在数据处理领域，NumPy的ndarray对象和多维数组操作功能使得数据处理变得更加简单和高效。用户可以使用NumPy对数据进行清洗、转换、聚合等操作，为后续的数据分析和机器学习模型训练提供高质量的数据支持。</p>
<h5>3.3 机器学习</h5>
<p>在机器学习领域，NumPy同样发挥着重要作用。机器学习算法通常需要对大量数据进行处理和分析，而NumPy提供的高效数组操作和线性代数功能能够极大地加速这一过程。此外，NumPy还可以与scikit-learn等机器学习库结合使用，为用户提供更加便捷的机器学习解决方案。</p>
<h5>3.4 图像处理</h5>
<p>在图像处理领域，NumPy同样具有广泛的应用。图像可以被视为二维或三维数组（对于彩色图像），因此可以使用NumPy进行图像的读取、显示、处理等操作。例如，可以使用NumPy对图像进行滤波、边缘检测、图像增强等处理。</p>
<h4>四、NumPy的性能优化</h4>
<p>尽管NumPy已经提供了高效的数组操作功能，但在处理大规模数据时仍然需要注意性能优化。以下是一些性能优化的技巧：</p>
<h5>4.1 理解NumPy数组的内存布局</h5>
<p>NumPy数组在内存中是连续存储的，这意味着对数组元素的访问非常快速。但是，如果不当地操作数组（如频繁地改变数组的形状或数据类型），可能会导致内存的不连续访问，从而降低性能。因此，理解NumPy数组的内存布局是优化性能的第一步。</p>
<h5>4.2 避免不必要的数组复制</h5>
<p>在NumPy中，某些操作（如切片、视图操作）会返回原始数组的一个视图而不是副本。这意味着这些操作不会复制数据，从而节省内存并提高性能。然而，在某些情况下（如数据类型转换、形状改变等），NumPy可能会隐式地复制数据。为了避免不必要的数组复制，应该尽量使用视图和原地操作（in-place operations）。</p>
<h5>4.3 利用向量化操作</h5>
<p>向量化操作是NumPy性能优化的关键。与Python的原生循环相比，向量化操作可以显著提高代码执行速度。向量化操作是指对整个数组执行操作而不是逐个元素地处理数组。NumPy的内部实现使用了高度优化的C语言代码，因此向量化操作通常比纯Python循环快得多。</p>
<h5>4.4 利用并行计算</h5>
<p>现代计算机通常具备多核处理器，因此可以利用并行计算来进一步提高NumPy代码的性能。Python的<code>threading</code>模块和<code>multiprocessing</code>模块提供了多线程和多进程的支持，可以用于并行处理NumPy数组操作。但是需要注意的是，由于Python的全局解释器锁（GIL）的存在，多线程在CPU密集型任务上可能并不会带来显著的性能提升。在这种情况下，多进程可能是一个更好的选择。</p>
<h5>4.5 使用优化库</h5>
<p>除了NumPy本身外，还有一些优化库（如Cython、Numba等）可以进一步提高NumPy代码的性能。这些库可以将Python代码编译成更快的机器码或直接在Python环境中执行优化的C/C++代码。通过使用这些优化库，可以进一步提高NumPy代码的执行效率。</p>
<p>综上所述，NumPy作为Python中用于科学计算的基础库，具有强大的多维数组对象和丰富的数学函数库。通过深入理解NumPy的基本特性、高级功能以及性能优化技巧，用户可以更加高效地利用NumPy进行数据处理、科学计算和机器学习等任务。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142374146</p>]]></description><guid isPermaLink="false">/archives/5e71b2b1-8020-41b1-bd5b-d73aa4a860e2</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Sun, 29 Sep 2024 00:50:00 GMT</pubDate></item><item><title><![CDATA[PyTorch框架]]></title><link>https://wiki.dgfygk.com/archives/aa92035e-de53-45dc-aef3-5a076b8708ac</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=PyTorch%E6%A1%86%E6%9E%B6&amp;url=/archives/aa92035e-de53-45dc-aef3-5a076b8708ac" width="1" height="1" alt="" style="opacity:0;">
<p>PyTorch是一个开源的深度学习框架，由Facebook AI Research（FAIR）团队开发，自2017年发布以来，凭借其出色的灵活性、易用性和强大的功能，在深度学习和机器学习领域得到了广泛的应用和认可。以下是对PyTorch框架的详细认识，包括其特点、优势、应用场景以及基本使用方法等方面。</p>
<h4>一、PyTorch的特点</h4>
<ol>
 <li>
  <p><strong>动态计算图</strong>：PyTorch采用动态计算图（Dynamic Computation Graph）机制，这是其最显著的特点之一。与传统的静态计算图（如TensorFlow 1.x）不同，PyTorch的计算图是在运行时构建的，可以在运行时进行灵活的修改和调整。这种机制使得PyTorch在模型开发、调试和实验阶段更加灵活和便捷。</p>
 </li>
 <li>
  <p><strong>易用性</strong>：PyTorch的API设计简洁直观，易于学习和使用。由于PyTorch是基于Python编写的，因此它继承了Python的简洁性和易用性，对于大多数熟悉Python的开发者来说，上手PyTorch并不困难。此外，PyTorch还提供了丰富的文档和教程，帮助用户快速掌握其使用方法。</p>
 </li>
 <li>
  <p><strong>灵活性</strong>：PyTorch提供了大量的灵活性，使得用户可以自由地定制自己的模型和训练流程。从模型的定义、训练到评估，PyTorch都允许用户进行高度自定义的操作。这种灵活性使得PyTorch在研究和实验开发方面具有很大的优势。</p>
 </li>
 <li>
  <p><strong>GPU加速</strong>：PyTorch支持GPU加速，可以显著提高深度学习模型的训练速度。通过利用GPU的强大计算能力，PyTorch可以加速模型的训练过程，缩短开发周期。此外，PyTorch还提供了对多GPU和分布式训练的支持，使得用户可以在更大的数据集上进行高效的训练。</p>
 </li>
 <li>
  <p><strong>社区支持</strong>：PyTorch拥有一个活跃的社区，提供了大量的文档、教程和代码示例。这个社区不仅为用户提供了技术支持和资源共享的平台，还促进了PyTorch技术的不断发展和完善。</p>
 </li>
</ol>
<h4>二、PyTorch的优势</h4>
<ol>
 <li>
  <p><strong>快速原型设计</strong>：由于PyTorch的动态计算图机制和易用性，用户可以快速地进行模型原型设计和实验。这种快速原型设计的能力使得PyTorch在研究和开发新算法方面具有很大的优势。</p>
 </li>
 <li>
  <p><strong>广泛的应用场景</strong>：PyTorch在深度学习和机器学习领域有着广泛的应用场景，包括计算机视觉、自然语言处理、强化学习等。无论是构建图像分类模型、进行文本生成任务还是训练智能体解决复杂任务，PyTorch都能提供强大的支持。</p>
 </li>
 <li>
  <p><strong>强大的扩展性</strong>：PyTorch提供了丰富的API和工具，使得用户可以轻松地扩展其功能。用户可以通过自定义模块、优化器等来扩展PyTorch的功能，以满足自己的特定需求。</p>
 </li>
 <li>
  <p><strong>跨平台支持</strong>：PyTorch支持多种操作系统和硬件平台，包括Windows、Linux和macOS等。这使得用户可以在不同的环境中灵活地部署和使用PyTorch。</p>
 </li>
</ol>
<h4>三、PyTorch的应用场景</h4>
<ol>
 <li>
  <p><strong>深度学习模型开发</strong>：PyTorch提供了丰富的深度学习模型库和工具，用户可以使用这些库和工具来构建、训练和评估各种类型的神经网络模型，如卷积神经网络（CNN）、循环神经网络（RNN）和生成对抗网络（GAN）等。</p>
 </li>
 <li>
  <p><strong>自然语言处理（NLP）</strong>：PyTorch在NLP领域有着广泛的应用，如文本分类、情感分析、机器翻译和文本生成等任务。其灵活的动态计算图机制使得处理可变长度的序列数据更加方便。</p>
 </li>
 <li>
  <p><strong>计算机视觉</strong>：PyTorch提供了丰富的计算机视觉库和工具，可以用于图像分类、目标检测、图像分割和图像生成等任务。同时，PyTorch还支持使用预训练的模型进行迁移学习，使得在计算机视觉任务上能够更快地实现高性能模型。</p>
 </li>
 <li>
  <p><strong>强化学习</strong>：PyTorch在强化学习领域也有广泛的应用，如使用深度强化学习算法（如深度Q网络和策略梯度方法）来训练智能体解决各种复杂的任务，如游戏玩耍和机器人控制等。</p>
 </li>
 <li>
  <p><strong>生成模型</strong>：PyTorch支持各种生成模型的建模和训练，如变分自编码器（VAE）、生成对抗网络（GAN）和自回归模型（如LSTM和Transformer）等。这些模型在图像生成、语音合成和自动文本摘要等任务上具有广泛的应用。</p>
 </li>
</ol>
<h4>四、PyTorch的基本使用方法</h4>
<ol>
 <li>
  <p><strong>安装PyTorch</strong>：用户可以通过PyTorch的官方网站下载并安装PyTorch。安装过程通常包括选择合适的PyTorch版本、操作系统和CUDA版本（如果需要使用GPU加速）等步骤。</p>
 </li>
 <li>
  <p><strong>导入PyTorch</strong> ：在Python代码中，用户可以通过导入torch模块来使用PyTorch的功能。例如，<code>import torch</code> 可以导入PyTorch的基本功能。</p>
 </li>
 <li>
  <p><strong>创建张量</strong>：张量是PyTorch中最基本的数据结构，类似于多维数组。用户可以使用torch.tensor()等函数来创建张量，并进行各种数学运算。</p>
 </li>
 <li>
  <p><strong>构建神经网络</strong>：PyTorch提供了丰富的神经网络层、损失函数和优化器等工具，用户可以使用这些工具来构建自己的神经网络。通常，用户需要定义一个继承自nn.Module的类来构建自己的网络结构。</p>
 </li>
 <li>
  <p><strong>训练模型</strong>：在训练模型时，用户需要定义损失函数和优化器，并通过迭代数据集来更新模型的参数。PyTorch提供了自动微分功能（autograd），可以自动计算梯度并更新模型参数。</p>
 </li>
 <li>
  <p><strong>评估模型</strong>：在模型训练完成后，用户可以使用测试数据集来评估模型的性能。评估指标通常包括准确率、召回率、F1分数等。</p>
 </li>
</ol>
<p>综上所述，PyTorch是一个功能强大、灵活易用的深度学习框架，在深度学习和机器学习领域有着广泛的应用场景。通过掌握PyTorch的基本使用方法和技巧，用户可以更加高效地进行模型开发、训练和评估等工作。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142350785</p>]]></description><guid isPermaLink="false">/archives/aa92035e-de53-45dc-aef3-5a076b8708ac</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:49:40 GMT</pubDate></item><item><title><![CDATA[Vue.js 的 Mixins]]></title><link>https://wiki.dgfygk.com/archives/1260547b-d2bb-47bc-859e-2118e58d2f4a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue.js%20%E7%9A%84%20Mixins&amp;url=/archives/1260547b-d2bb-47bc-859e-2118e58d2f4a" width="1" height="1" alt="" style="opacity:0;">
<p>Vue.js 的 Mixins 是一种非常强大且灵活的功能，它允许你封装可复用的 Vue 组件选项。Mixins 实际上是一种分发 Vue 组件可复用功能的非常灵活的方式。一个 mixin 对象可以包含任意组件选项。当组件使用 mixin 时，所有 mixin 选项将被"混入"该组件本身的选项。</p>
<h4>Mixins 的基本概念</h4>
<p>Mixins 本质上是一种将组件中的可重用部分抽离成独立可复用单元的方法。在 Vue 中，你可以定义一个 mixin 对象，它包含任意组件选项，如数据、方法、生命周期钩子等。当组件使用 mixin 时，mixin 中的所有选项将被混入该组件的选项中。如果组件和 mixin 含有同名选项，组件本身的选项将优先于 mixin 中的选项。</p>
<h4>Mixins 的优点</h4>
<ol>
 <li><strong>代码复用</strong>：Mixins 允许你在多个组件间复用代码，这减少了代码的重复，使你的应用更加模块化。</li>
 <li><strong>灵活性</strong>：Mixins 提供了高度的灵活性，因为它们可以包含任意组件选项，而不仅仅是方法或数据。</li>
 <li><strong>简单性</strong>：相比其他高级组件复用模式（如高阶组件、Renderless Components），Mixins 在 Vue 中使用起来更为简单直接。</li>
</ol>
<h4>Mixins 的缺点</h4>
<ol>
 <li><strong>命名冲突</strong>：当多个 mixins 包含同名选项时，可能需要特别注意合并策略或命名冲突的问题。</li>
 <li><strong>调试困难</strong>：由于 mixins 可能会将多个来源的代码混入一个组件中，这可能会使得调试变得困难，特别是当多个 mixins 相互依赖或修改相同的状态时。</li>
 <li><strong>理解成本高</strong>：对于不熟悉 Vue Mixins 的开发者来说，理解其工作原理和潜在问题可能需要一些时间。</li>
</ol>
<h4>如何定义和使用 Mixins</h4>
<h5>定义 Mixins</h5>
<p>Mixins 是一个普通的 JavaScript 对象，可以包含任何组件选项。</p>
<pre><code class="prism language-javascript">// 定义一个名为 myMixin 的 mixin
const myMixin = {

  created() {

    this.hello();
  },
  methods: {

    hello() {

      console.log('hello from mixin!');
    }
  }
}
</code></pre>
<h5>使用 Mixins</h5>
<p>你可以通过全局或局部的方式在组件中使用 mixin。</p>
<h6>全局注册</h6>
<p>通过 <code>Vue.mixin()</code> 方法，你可以创建一个全局 mixin，这会影响每个之后创建的 Vue 实例。</p>
<pre><code class="prism language-javascript">// 全局注册 mixin
Vue.mixin({

  created() {

    console.log('全局 mixin 钩子被调用');
  }
})

// 创建一个新的 Vue 实例
new Vue({

  // ...
})
// =&gt; "全局 mixin 钩子被调用"
</code></pre>
<h6>局部注册</h6>
<p>在组件内部，你也可以通过 <code>mixins</code> 选项来局部注册 mixin。这允许你将 mixin 封装到特定的组件中，而不是影响到全局的 Vue 实例。</p>
<pre><code class="prism language-javascript">// 局部注册 mixin
const Component = Vue.extend({

  mixins: [myMixin],
  created() {

    console.log('组件的 created 钩子');
    // "hello from mixin!" 将首先被调用
    // 因为 myMixin 的 created 钩子在组件的 created 钩子之前调用
  }
})
</code></pre>
<h4>Mixins 与组件选项的合并</h4>
<p>当组件和 mixin 含有同名选项时，Vue 会以合理的方式合并它们。例如，如果 mixin 和组件都定义了 <code>data</code> 函数，Vue 将合并这两个函数返回的对象的所有属性，并使用组件自身的属性来覆盖 mixin 中同名的属性。</p>
<p>对于生命周期钩子，mixin 中的钩子将在组件自身的钩子之前调用。如果 mixin 和组件都定义了相同的钩子，则 mixin 中的钩子会首先被调用，然后是组件自身的钩子。</p>
<h4>Mixins 的应用场景</h4>
<p>Mixins 在 Vue 应用中有多种应用场景，包括但不限于：</p>
<ol>
 <li><strong>跨组件的共享逻辑</strong>：当你需要在多个组件中共享相同的方法或数据时，可以使用 mixin 来封装这些共享的逻辑。</li>
 <li><strong>组件选项的增强</strong>：如果你想要为某个组件选项添加额外的功能，但又不想修改原始组件，你可以创建一个 mixin 来扩展该选项。</li>
 <li><strong>条件渲染的封装</strong>：Mixins 可以用来封装复杂的条件渲染逻辑，使得组件的模板更加简洁明了。</li>
</ol>
<h4>Mixins 与其他组件复用模式的比较</h4>
<p>虽然 Mixins 在 Vue 中非常有用，但它们并不是解决所有组件复用问题的唯一方法。Vue 还提供了其他几种组件复用模式，如高阶组件（HOC）、插槽（Slots）和 Renderless Components。每种模式都有其适用场景和优缺点，因此在实际应用中应根据具体情况选择最合适的复用方式。</p>
<h4>总结</h4>
<p>Vue Mixins 是一种强大的代码复用方式，它允许你封装可复用的组件选项并在多个组件之间共享它们。然而，由于它们可能会引起命名冲突和调试困难等问题，因此在使用时需要谨慎。通过合理地定义和使用 Mixins，你可以有效地提高你的 Vue 应用的可维护性和可扩展性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142374157</p>]]></description><guid isPermaLink="false">/archives/1260547b-d2bb-47bc-859e-2118e58d2f4a</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sun, 29 Sep 2024 00:47:59 GMT</pubDate></item><item><title><![CDATA[Spring Boot中的响应与分层解耦架构]]></title><link>https://wiki.dgfygk.com/archives/0674433a-d730-4182-87ef-7572501d04be</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%E4%B8%AD%E7%9A%84%E5%93%8D%E5%BA%94%E4%B8%8E%E5%88%86%E5%B1%82%E8%A7%A3%E8%80%A6%E6%9E%B6%E6%9E%84&amp;url=/archives/0674433a-d730-4182-87ef-7572501d04be" width="1" height="1" alt="" style="opacity:0;">
<h4>Spring Boot中的响应与分层解耦架构</h4>
<p>在Spring Boot框架中，响应与分层解耦架构是两个核心概念，它们共同促进了应用程序的高效性、可维护性和可扩展性。下面将详细探讨这两个方面，包括Spring Boot的响应机制、分层解耦的三层架构以及它们在实际开发中的应用。</p>
<h5>一、Spring Boot的响应机制</h5>
<p>Spring Boot的响应机制主要依赖于其内置的Servlet容器（如Tomcat）和Spring MVC框架。当浏览器或客户端发起HTTP请求时，请求首先被Servlet容器接收，然后转发给Spring MVC的DispatcherServlet。DispatcherServlet作为核心控制器，负责根据请求的URL和配置的映射关系，将请求分发给相应的Controller进行处理。</p>
<h6>1. 控制器（Controller）</h6>
<p>在Spring Boot中，Controller层负责接收前端发送的请求，并处理这些请求，最终返回响应数据给前端。Controller层通常使用<code>@RestController</code>或<code>@Controller</code>注解来标识，其中<code>@RestController</code>是<code>@Controller</code>和<code>@ResponseBody</code>的组合注解，表示该控制器中的所有方法都会将返回值直接写入HTTP响应体中，并通常转换为JSON格式。</p>
<h6>2. 响应数据</h6>
<p>Controller层处理完请求后，需要返回响应数据给前端。在Spring Boot中，可以通过多种方式来返回响应数据，包括直接返回对象（自动转换为JSON）、返回<code>ResponseEntity</code>对象、使用<code>@ResponseBody</code>注解等。此外，Spring Boot还提供了丰富的HTTP状态码和消息转换器，以便开发者能够灵活地控制响应的状态和内容。</p>
<h6>3. 响应式编程</h6>
<p>除了传统的同步请求-响应模式外，Spring Boot还支持响应式编程。响应式编程是一种基于异步数据流和观察者模式的编程范式，它强调数据流的变化会触发依赖数据的组件进行响应。在Spring Boot中，响应式编程主要通过Reactor库和WebFlux框架来实现。WebFlux框架为开发者提供了基于反应式流的Web开发支持，能够处理大量并发请求而不会阻塞线程，从而提高了系统的吞吐量和性能。</p>
<h5>二、分层解耦的三层架构</h5>
<p>在Spring Boot应用程序中，分层解耦的三层架构是一种常见的设计模式，它将应用程序分为表示层、业务逻辑层和数据访问层（也称为持久层），以提高代码的可维护性、可扩展性和复用性。</p>
<h6>1. 表示层（Presentation Layer）</h6>
<p>表示层主要负责与用户进行交互，接收用户的请求并返回响应数据。在Spring Boot中，表示层通常由Controller层实现。Controller层接收来自前端的HTTP请求，并调用业务逻辑层来处理这些请求。处理完成后，Controller层将结果返回给前端进行展示。</p>
<h6>2. 业务逻辑层（Business Logic Layer）</h6>
<p>业务逻辑层是应用程序的核心部分，它负责处理具体的业务规则和流程。在Spring Boot中，业务逻辑层通常由Service层实现。Service层接收来自Controller层的请求，并调用数据访问层来获取或修改数据。处理完成后，Service层将结果返回给Controller层进行响应。</p>
<p>Service层的设计应遵循单一职责原则，即一个Service类只负责一块功能。这样做可以降低类的复杂度，提高代码的可读性和可维护性。同时，Service层还可以进行事务管理、安全控制等操作，以确保业务逻辑的正确性和安全性。</p>
<h6>3. 数据访问层（Data Access Layer）</h6>
<p>数据访问层负责与数据库进行交互，执行数据的增、删、改、查等操作。在Spring Boot中，数据访问层通常由DAO（Data Access Object）或Repository接口实现。这些接口定义了与数据库交互的方法，并由具体的实现类来完成这些方法的具体实现。</p>
<p>数据访问层的设计应遵循高内聚低耦合的原则，即各个模块之间的依赖关系应尽可能少，而模块内部的功能应尽可能紧密。这样做可以提高代码的复用性和可维护性。同时，数据访问层还可以利用Spring Boot提供的ORM框架（如JPA、MyBatis等）来简化数据库操作，提高开发效率。</p>
<h5>三、分层解耦的优势</h5>
<h6>1. 提高代码的可维护性</h6>
<p>分层解耦的设计使得各个层之间的职责更加明确，降低了层与层之间的耦合度。当需要修改某个层的功能时，只需要在该层内部进行修改，而不会影响到其他层。这样做可以大大降低代码的维护成本。</p>
<h6>2. 提高代码的可扩展性</h6>
<p>分层解耦的设计使得应用程序更加灵活，易于扩展。当需要添加新的功能时，只需要在相应的层中添加新的类或方法即可，而不需要修改其他层的代码。这样做可以大大提高代码的可扩展性。</p>
<h6>3. 提高代码的复用性</h6>
<p>分层解耦的设计使得各个层之间的代码更加独立，可以被不同的应用程序或模块复用。例如，一个数据访问层的实现可以被多个业务逻辑层复用，从而避免了代码的重复编写。</p>
<h5>四、实际应用中的注意事项</h5>
<h6>1. 合理划分层次</h6>
<p>在实际应用中，应根据应用程序的具体需求和业务逻辑来合理划分层次。不要过度划分层次，以免增加系统的复杂度和维护成本；也不要划分得过于粗糙，以免降低代码的复用性和可扩展性。</p>
<h6>2. 遵循设计原则</h6>
<p>在分层解耦的设计过程中，应遵循单一职责原则、高内聚低耦合原则等设计原则。这些原则有助于设计出更加清晰、简洁、易于维护的代码结构。</p>
<h6>3. 合理利用框架和工具</h6>
<p>Spring Boot提供了丰富的框架和工具来支持分层解耦的设计。例如，可以使用Spring MVC来构建表示层；使用Spring Service来构建业务逻辑层；使用Spring Data JPA或MyBatis来构建数据访问层等。这些框架和工具可以大大简化开发过程，提高开发效率。</p>
<h6>4. 注意性能优化</h6>
<p>在分层解耦的设计过程中，还需要注意性能优化。例如，在数据访问层中，可以通过合理的索引设计、查询优化等方式来提高数据库查询的效率；在业务逻辑层中，可以通过算法优化、缓存机制等方式来提高业务处理的效率；在表示层中，可以通过页面优化、资源压缩等方式来提高页面的加载速度。</p>
<p>综上所述，Spring Boot中的响应与分层解耦架构是构建高效、可维护、可扩展应用程序的重要基础。通过合理设计响应机制和分层解耦的三层架构，可以大大提高应用程序的性能和可维护性，降低开发成本和维护成本。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142350817</p>]]></description><guid isPermaLink="false">/archives/0674433a-d730-4182-87ef-7572501d04be</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 00:47:48 GMT</pubDate></item><item><title><![CDATA[单片机（Microcontroller）原理及应用]]></title><link>https://wiki.dgfygk.com/archives/f672e538-2e0d-4fc0-b216-d70b4b68fbd8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8D%95%E7%89%87%E6%9C%BA%EF%BC%88Microcontroller%EF%BC%89%E5%8E%9F%E7%90%86%E5%8F%8A%E5%BA%94%E7%94%A8&amp;url=/archives/f672e538-2e0d-4fc0-b216-d70b4b68fbd8" width="1" height="1" alt="" style="opacity:0;">
<p>单片机（Microcontroller）原理及应用是一个广泛而深入的话题，涵盖了单片机的硬件结构、工作原理、应用领域以及开发方法等多个方面。以下是对单片机原理及应用的详细阐述，旨在为读者提供一个全面而深入的理解。</p>
<h4>一、单片机原理</h4>
<p>单片机原理主要是指一种在线式实时控制计算机的原理方式，其核心在于通过集成的CPU、存储器、定时器、输入输出接口等功能模块，实现对外部设备的控制和数据处理。</p>
<h5>1. 硬件结构</h5>
<p>单片机通常由以下几个核心部分组成：</p>
<ul>
 <li><strong>CPU（中央处理器）</strong>：单片机的核心，负责执行程序指令，控制单片机的各项操作。</li>
 <li><strong>存储器</strong>：包括ROM（只读存储器）和RAM（随机存取存储器）。ROM用于存储单片机的程序代码和初始数据，通常是不可修改的；RAM则用于存储运行时的数据和变量，是易失性存储器。</li>
 <li><strong>定时器/计数器</strong>：用于时间延迟、计时、事件计数等功能，是单片机的重要组成部分。</li>
 <li><strong>输入输出接口（I/O接口）</strong>：单片机提供了多种I/O接口，如GPIO（通用输入输出口）、UART（通用异步收发传输器）、SPI（串行外设接口）、I2C（集成电路总线）等，用于与外部设备通信和控制。</li>
 <li><strong>时钟系统</strong>：单片机有一个时钟振荡器，提供CPU运行时的时钟信号。时钟信号的频率直接影响单片机的运行速度。</li>
</ul>
<h5>2. 工作原理</h5>
<p>单片机的工作原理可以概括为"取指-执行"的循环过程。具体来说，单片机通过程序计数器（PC）指向内存中的程序首地址，取出指令，经过指令寄存器（IR）送到指令译码器，译出操作码（opcode），产生相应的控制信号，通过控制总线送至各个部件执行相应的操作。同时，单片机的I/O接口与外部设备相连，通过读写操作实现数据的输入输出。</p>
<h4>二、单片机应用</h4>
<p>单片机因其低成本、低功耗、小体积、易于集成等特点，在各个领域得到了广泛的应用。以下是一些主要的应用领域：</p>
<h5>1. 家用电器</h5>
<p>单片机在家用电器中的应用非常普遍，如洗衣机、空调、微波炉等。通过单片机实现按键控制、温度传感、定时功能等，使得家用电器的操作更加智能化和便捷化。</p>
<h5>2. 工业控制</h5>
<p>单片机在工业控制领域发挥着重要作用。例如，在工业自动化中，单片机作为PLC（可编程逻辑控制器）中的核心控制单元，负责采集传感器信号、执行控制算法并驱动执行机构等任务。此外，单片机还广泛应用于各种测控系统、过程控制、机电一体化等领域。</p>
<h5>3. 汽车电子</h5>
<p>现代汽车中包含许多电子控制单元（ECU），每个ECU中都使用单片机来执行特定的控制任务。如发动机控制、防抱死制动系统（ABS）、气囊控制等。单片机在汽车电子中的应用提高了汽车的安全性、舒适性和燃油经济性。</p>
<h5>4. 智能家居</h5>
<p>在物联网时代，单片机被广泛应用于智能家居领域。如智能门锁、智能灯光、环境监测设备等，通过单片机实现智能化控制和远程操作，为用户提供更加便捷和舒适的生活体验。</p>
<h5>5. 医疗设备</h5>
<p>单片机在医疗设备中也有重要应用。如便携式医疗设备中的血糖仪、心率监测器等，通过单片机提供数据采集和处理功能，为医疗诊断和治疗提供有力支持。</p>
<h5>6. 通信设备</h5>
<p>单片机在无线通信模块、路由器、蓝牙设备等通信设备中起着重要的控制和通信作用。通过单片机实现数据的传输和处理，使得通信设备更加稳定和可靠。</p>
<h4>三、单片机开发</h4>
<p>单片机开发是指根据具体的应用需求，选择合适的单片机型号和开发工具链，编写程序并调试以实现特定功能的过程。以下是一些单片机开发的基本步骤：</p>
<ol>
 <li><strong>需求分析</strong>：明确单片机的应用需求和性能指标。</li>
 <li><strong>选择单片机型号</strong>：根据需求分析结果选择合适的单片机型号和开发工具链。</li>
 <li><strong>硬件设计</strong>：设计单片机系统的硬件电路图，包括单片机与外设的连接方式等。</li>
 <li><strong>软件开发</strong>：编写单片机程序，包括初始化设置、中断服务程序、主程序等。</li>
 <li><strong>调试与测试</strong>：使用开发工具链对单片机程序进行调试和测试，确保程序能够正确运行并满足需求。</li>
 <li><strong>系统集成</strong>：将单片机系统与其他硬件模块进行集成，形成完整的嵌入式系统。</li>
</ol>
<p>总之，单片机原理及应用是一个涉及多个方面的复杂话题。通过深入理解单片机的硬件结构和工作原理以及其在各个领域的应用实例和开发方法，可以更好地掌握单片机技术并应用于实际工程中。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142324015</p>]]></description><guid isPermaLink="false">/archives/f672e538-2e0d-4fc0-b216-d70b4b68fbd8</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:46:23 GMT</pubDate></item><item><title><![CDATA[前端框架对比和选择]]></title><link>https://wiki.dgfygk.com/archives/bebcb3b9-622b-4f54-9ce3-ef60e00c4f70</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E5%AF%B9%E6%AF%94%E5%92%8C%E9%80%89%E6%8B%A9&amp;url=/archives/bebcb3b9-622b-4f54-9ce3-ef60e00c4f70" width="1" height="1" alt="" style="opacity:0;">
<p>在前端开发的广阔领域中，框架的选择对于项目的成功至关重要。不同的框架各具特色，适用于不同的开发需求和项目规模。本文将对当前最流行的三大前端框架——React、Vue和Angular进行详细对比，并探讨如何根据项目需求选择合适的框架。</p>
<h4>一、React</h4>
<p><strong>1. 简介</strong></p>
<p>React是由Facebook开发和维护的一个用于构建用户界面的JavaScript库。它主要关注于视图层，并采用组件化的开发模式。React本质上是一个库，而不是一个完整的框架，因此开发者可以根据需要选择和组合各种工具和库。</p>
<p><strong>2. 优点</strong></p>
<ul>
 <li><strong>灵活性高</strong>：React的灵活性体现在它可以与其他库或框架无缝集成，如Redux用于状态管理，React Router用于路由管理等。</li>
 <li><strong>组件化</strong>：React的组件化开发模式使得组件可以复用，易于管理和维护。</li>
 <li><strong>虚拟DOM</strong>：通过虚拟DOM技术，React能够比较新旧DOM的差异，只更新变化的部分，从而提高页面渲染性能。</li>
 <li><strong>广泛的社区支持</strong>：React拥有庞大的社区和丰富的第三方库和工具，这为开发者提供了强大的支持。</li>
 <li><strong>React Native</strong>：React Native允许开发者使用相同的代码库构建移动应用，进一步提高了开发效率。</li>
</ul>
<p><strong>3. 缺点</strong></p>
<ul>
 <li><strong>JSX学习曲线</strong>：JSX语法虽然强大，但对于新手来说有一定的学习曲线。</li>
 <li><strong>需要配合其他库</strong>：为了实现完整的功能，开发者通常需要额外学习和集成其他库。</li>
</ul>
<h4>二、Vue</h4>
<p><strong>1. 简介</strong></p>
<p>Vue.js是由华人开发者尤雨溪开发的渐进式JavaScript框架，适合构建用户界面和单页面应用。Vue的设计理念是"简单而优雅”，它允许开发者以自底向上的方式构建用户界面。</p>
<p><strong>2. 优点</strong></p>
<ul>
 <li><strong>易于上手</strong>：Vue的文档详细且易于理解，适合初学者快速上手。</li>
 <li><strong>双向数据绑定</strong>：Vue通过双向数据绑定，使得数据和视图同步更加简单。</li>
 <li><strong>单文件组件</strong>：Vue使用单文件组件(.vue)，将模板、脚本和样式整合在一个文件中，便于管理。</li>
 <li><strong>渐进式框架</strong>：Vue可以根据项目需求逐步引入其特性，不需要一次性学习整个框架。</li>
 <li><strong>强大的生态系统</strong>：Vue拥有Vue Router用于路由管理，Vuex用于状态管理等丰富的工具和插件。</li>
</ul>
<p><strong>3. 缺点</strong></p>
<ul>
 <li><strong>社区相对较小</strong>：相比于React和Angular，Vue的社区和企业采用率相对较小。</li>
 <li><strong>灵活性过高</strong>：过高的灵活性有时会导致项目代码风格不统一。</li>
</ul>
<h4>三、Angular</h4>
<p><strong>1. 简介</strong></p>
<p>Angular是由Google开发和维护的一个功能全面的前端框架，适用于构建复杂的大型应用。Angular提供了从开发到测试的全套解决方案，包括路由、状态管理、HTTP请求等。</p>
<p><strong>2. 优点</strong></p>
<ul>
 <li><strong>全面的框架</strong>：Angular提供了丰富的功能和工具，几乎涵盖了前端开发的各个方面。</li>
 <li><strong>TypeScript支持</strong>：Angular默认使用TypeScript，提高了代码的可维护性和可读性。</li>
 <li><strong>双向数据绑定</strong>：Angular自动同步数据和视图，简化了开发。</li>
 <li><strong>模块化</strong>：通过模块化设计，Angular可以更好地管理和组织代码。</li>
 <li><strong>企业级支持</strong>：Angular受到许多大公司的青睐，适合大型项目。</li>
</ul>
<p><strong>3. 缺点</strong></p>
<ul>
 <li><strong>学习曲线陡峭</strong>：由于其复杂性和全面性，新手需要较长时间学习和掌握。</li>
 <li><strong>性能问题</strong>：在处理大量数据和复杂界面时，可能会遇到性能瓶颈，需要优化。</li>
</ul>
<h4>四、框架对比与选择</h4>
<p><strong>1. 灵活性</strong></p>
<ul>
 <li><strong>React</strong>：提供了极高的灵活性，允许开发者根据需要选择和组合各种工具和库。</li>
 <li><strong>Vue</strong>：也具有较高的灵活性，但相比React，Vue的渐进式特性使得开发者可以根据项目需求逐步引入特性。</li>
 <li><strong>Angular</strong>：虽然功能全面，但相对较为封闭，不如React和Vue灵活。</li>
</ul>
<p><strong>2. 学习曲线</strong></p>
<ul>
 <li><strong>React</strong>：JSX语法和生态系统的学习需要一定时间，但一旦掌握，可以大大提高开发效率。</li>
 <li><strong>Vue</strong>：文档详细，易于上手，适合初学者快速掌握。</li>
 <li><strong>Angular</strong>：由于其复杂性和全面性，学习曲线最陡峭，需要较长时间的学习和实践。</li>
</ul>
<p><strong>3. 社区支持</strong></p>
<ul>
 <li><strong>React</strong>：拥有庞大的社区和丰富的第三方库和工具。</li>
 <li><strong>Vue</strong>：社区虽然不如React大，但在中国拥有强大的社区支持，且社区生态良好。</li>
 <li><strong>Angular</strong>：也有较为活跃的社区，但相比于React和Vue，可能略显小众。</li>
</ul>
<p><strong>4. 项目需求</strong></p>
<ul>
 <li><strong>高灵活性和自定义</strong>：如果项目需要高灵活性和自定义，且团队熟悉JavaScript和JSX，React是理想选择。</li>
 <li><strong>快速开发和易上手</strong>：如果追求快速开发和易上手，且项目规模较小到中等，Vue是不错的选择。</li>
 <li><strong>大型企业级应用</strong>：如果是大型企业级应用，且需要严格的开发规范和TypeScript支持，Angular是最佳选择。</li>
</ul>
<h4>五、结论</h4>
<p>前端框架的选择应根据项目需求、团队技能和个人偏好来决定。React、Vue和Angular各有优缺点，但都是当前最流行的前端框架之一。React以其灵活性和丰富的生态系统赢得了广泛应用；Vue以其简单易用和强大的生态系统在中国市场占据重要地位；Angular则以其全面的功能和企业级支持受到大型项目的青睐。</p>
<p>在选择框架时，开发者应充分考虑项目的实际需求和团队的技能水平，选择最适合自己的框架。同时，随着前端技术的不断发展，开发者也应保持学习的热情，不断关注前端技术的最新动态和发展趋势，以便在激烈的竞争中立于不败之地。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142410994</p>]]></description><guid isPermaLink="false">/archives/bebcb3b9-622b-4f54-9ce3-ef60e00c4f70</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sun, 29 Sep 2024 00:46:11 GMT</pubDate></item><item><title><![CDATA[物联网智能项目]]></title><link>https://wiki.dgfygk.com/archives/f0dbd997-4fba-4852-a80b-39808d3575f0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%89%A9%E8%81%94%E7%BD%91%E6%99%BA%E8%83%BD%E9%A1%B9%E7%9B%AE&amp;url=/archives/f0dbd997-4fba-4852-a80b-39808d3575f0" width="1" height="1" alt="" style="opacity:0;">
<p>物联网智能项目是一个涉及多个领域和技术的综合性项目，旨在通过互联网将各种物理设备连接起来，实现数据的采集、传输、处理和分析，进而实现智能化控制和管理。以下是对物联网智能项目的详细阐述，包括其定义、关键技术、应用领域、发展趋势以及案例分析。</p>
<h4>一、物联网智能项目定义</h4>
<p>物联网（Internet of Things, IoT）是指通过信息传感设备，如射频识别（RFID）、红外感应器、全球定位系统、激光扫描器等装置与技术，将任何物品与互联网连接起来，进行信息交换和通信，以实现智能化识别、定位、跟踪、监控和管理的一种网络。物联网智能项目则是基于物联网技术，结合人工智能（AI）、大数据、云计算等先进技术，开发出的具有智能化、自动化、远程监控等功能的项目。</p>
<h4>二、关键技术</h4>
<p>物联网智能项目涉及多个关键技术，主要包括以下几个方面：</p>
<ol>
 <li>
  <p><strong>感知技术</strong>：包括传感器技术、RFID技术等，用于采集物理世界的数据，如温度、湿度、光照、位置等。这些技术是物联网智能项目的基础，为系统提供实时、准确的数据支持。</p>
 </li>
 <li>
  <p><strong>网络通信技术</strong>：包括无线网络（如Wi-Fi、蓝牙、Zigbee等）和有线网络（如以太网），用于实现设备之间的数据传输和通信。网络通信技术确保了物联网智能项目中数据的实时传输和共享。</p>
 </li>
 <li>
  <p><strong>数据处理与分析技术</strong>：包括大数据处理、云计算、数据挖掘等，用于对收集到的数据进行处理和分析，提取有价值的信息。这些技术为物联网智能项目提供了强大的数据处理能力，支持复杂的数据分析和决策支持。</p>
 </li>
 <li>
  <p><strong>人工智能与机器学习</strong>：利用AI算法和机器学习模型，对数据进行智能分析和预测，实现自主决策和优化控制。人工智能与机器学习的引入，使得物联网智能项目能够具备更高的智能化水平和更强的自主学习能力。</p>
 </li>
</ol>
<h4>三、应用领域</h4>
<p>物联网智能项目广泛应用于多个领域，以下是几个典型的应用场景：</p>
<ol>
 <li>
  <p><strong>智能家居</strong>：通过智能家电、智能照明、智能安防等设备，实现家庭环境的智能化控制和管理。用户可以通过手机APP远程控制家电设备，设置自动化场景，如离家自动关闭电源、回家自动开启灯光等。智能家居提高了家庭生活的便捷性和舒适度。</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>
</ol>
<h4>四、发展趋势</h4>
<p>随着技术的不断进步和应用场景的不断拓展，物联网智能项目呈现出以下发展趋势：</p>
<ol>
 <li>
  <p><strong>技术融合</strong>：物联网技术将与人工智能、大数据、云计算等先进技术深度融合，推动物联网智能项目的智能化水平不断提升。</p>
 </li>
 <li>
  <p><strong>标准化发展</strong>：随着物联网技术的广泛应用，标准化问题日益凸显。未来，物联网技术标准将不断完善和统一，推动物联网产业的健康发展。</p>
 </li>
 <li>
  <p><strong>安全性能提升</strong>：物联网智能项目涉及大量数据的传输和共享，因此安全性能至关重要。未来，物联网智能项目将加强安全防护措施，确保数据的安全性和隐私保护。</p>
 </li>
 <li>
  <p><strong>应用场景拓展</strong>：随着技术的不断成熟和应用场景的不断拓展，物联网智能项目将在更多领域得到应用，如智慧城市、智慧环保、智慧能源等。</p>
 </li>
</ol>
<h4>五、案例分析</h4>
<p>以智能城市为例，物联网智能项目在智能城市建设中发挥着重要作用。智能城市利用物联网技术、大数据分析、人工智能等技术手段，将城市的各种设施、设备、车辆等物体与互联网连接起来，实现信息的传输、共享和交互。通过智能交通系统、智能能源管理系统、智能建筑系统、智能医疗系统等子系统的建设和应用，提高城市的管理效率、人民生活水平和可持续发展能力。</p>
<p>在智能交通系统中，物联网技术可以实现对交通流量、路况信息等的实时监测和分析。通过智能交通信号控制系统优化交通信号灯控制策略减少拥堵和提升交通效率；通过智能公交系统实现公交车的实时调度和运营管理；通过智能停车系统实现停车位的实时监测和智能引导等。这些应用不仅提高了交通效率和安全性还提升了居民出行的便捷性和舒适度。</p>
<p>在智能能源管理系统中，物联网技术可以实现对能源使用的实时监测和分析。通过智能电表、智能水表等设备实时监测能源使用情况；通过数据分析预测能源需求趋势制定合理的能源供应计划；通过智能能源管理系统实现能源的优化分配和调度等。这些应用有助于降低能源消耗提高能源利用效率推动城市的可持续发展。</p>
<p>综上所述，物联网智能项目是一个具有广泛应用前景和巨大发展潜力的领域。随着技术的不断进步和应用场景的不断拓展，物联网智能项目将在更多领域发挥重要作用，推动社会进步和经济发展。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142323992</p>]]></description><guid isPermaLink="false">/archives/f0dbd997-4fba-4852-a80b-39808d3575f0</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:45:59 GMT</pubDate></item><item><title><![CDATA[自然语言处理实战项目]]></title><link>https://wiki.dgfygk.com/archives/69f82040-ba6f-4d64-8525-5a611fcb768b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86%E5%AE%9E%E6%88%98%E9%A1%B9%E7%9B%AE&amp;url=/archives/69f82040-ba6f-4d64-8525-5a611fcb768b" width="1" height="1" alt="" style="opacity:0;">
<p>自然语言处理（NLP）作为人工智能的一个重要分支，近年来随着深度学习、神经网络和大数据技术的飞速发展，取得了显著的进步，并被广泛应用于文本分类、情感分析、机器翻译、文本生成、对话系统等多个领域。下面将详细介绍几个自然语言处理的实战项目，涵盖项目背景、项目描述、数据处理、模型选择与实现、以及项目成果与展望.</p>
<h4>1. 文本分类项目：新闻分类</h4>
<p><strong>项目背景</strong> ：
 <br>
 在信息爆炸的时代，新闻分类成为了信息检索和推荐系统的关键技术之一。通过自动化的新闻分类，可以快速地将海量新闻内容按照主题或类别进行划分，提高信息处理的效率和准确性。</p>
<p><strong>项目描述</strong> ：
 <br>
 本项目基于News20 Dataset（或其他新闻文本分类数据集）进行新闻文本的分类。目标是构建一个能够自动识别新闻所属类别的分类器，如政治、体育、娱乐、科技等。</p>
<p><strong>数据处理</strong>：</p>
<ol>
 <li><strong>数据清洗</strong>：去除文本中的噪声数据，如HTML标签、特殊符号等。</li>
 <li><strong>文本预处理</strong>：进行分词、去除停用词、词形还原等步骤，以减少词汇冗余和提高文本向量化效率。</li>
 <li><strong>文本向量化</strong>：使用TF-IDF或Word2Vec等算法将文本转换为数值向量，便于机器学习模型处理。</li>
</ol>
<p><strong>模型选择与实现</strong>：</p>
<ul>
 <li><strong>模型选择</strong>：考虑到新闻文本的分类任务通常涉及多类别分类，可以选择朴素贝叶斯、SVM、Logistic Regression或深度学习模型（如TextCNN、BERT等）进行训练。</li>
 <li><strong>训练过程</strong>：将数据集划分为训练集和测试集，使用训练集数据对模型进行训练，并通过测试集评估模型的性能。</li>
 <li><strong>参数调优</strong>：通过交叉验证、网格搜索等方法对模型参数进行优化，以提高分类准确率。</li>
</ul>
<p><strong>项目成果与展望</strong> ：
 <br>
 通过本项目，成功构建了一个新闻文本分类器，实现了对新闻内容的自动分类。未来可以进一步探索更高效的文本表示方法、更复杂的模型结构以及多模态信息融合等技术，以提高分类的准确性和泛化能力。</p>
<h4>2. 情感分析项目：社交媒体情感分析</h4>
<p><strong>项目背景</strong> ：
 <br>
 随着社交媒体的普及，用户生成的内容（UGC）已成为企业了解用户需求和情感倾向的重要来源。情感分析技术能够自动分析文本中的情感倾向（正面、负面或中性），为企业决策提供有力支持。</p>
<p><strong>项目描述</strong> ：
 <br>
 本项目基于IMDB电影评论数据集（或其他社交媒体情感分析数据集）进行情感分析。目标是构建一个能够自动识别文本情感倾向的分类器。</p>
<p><strong>数据处理</strong>：</p>
<ol>
 <li><strong>数据标注</strong>：对原始文本进行情感标签的标注，形成有监督学习的数据集。</li>
 <li><strong>文本预处理</strong>：进行分词、去除停用词、词形还原等步骤，提高文本处理的准确性。</li>
 <li><strong>文本向量化</strong>：使用词嵌入技术（如Word2Vec、GloVe等）将文本转换为数值向量。</li>
</ol>
<p><strong>模型选择与实现</strong>：</p>
<ul>
 <li><strong>模型选择</strong>：鉴于情感分析任务的复杂性，可以选择LSTM、GRU等循环神经网络（RNN）或Transformer等深度学习模型进行训练。这些模型能够捕捉文本中的长期依赖关系，提高情感分析的准确性。</li>
 <li><strong>训练过程</strong>：使用标注好的数据集对模型进行训练，并通过交叉验证等方法评估模型的性能。</li>
 <li><strong>优化策略</strong>：引入注意力机制（Attention）等高级技术，提升模型对关键信息的关注度，进一步提高情感分析的准确率。</li>
</ul>
<p><strong>项目成果与展望</strong> ：
 <br>
 通过本项目，成功构建了一个社交媒体情感分析系统，能够自动分析文本中的情感倾向。未来可以探索更多领域的数据集和更复杂的情感分析任务（如多语言情感分析、细粒度情感分析等），以及将情感分析技术应用于更多实际场景中。</p>
<h4>3. 机器翻译项目：中英机器翻译</h4>
<p><strong>项目背景</strong> ：
 <br>
 随着全球化的深入发展，机器翻译技术成为了跨语言交流的重要工具。中英机器翻译作为最常见的翻译任务之一，具有广泛的应用前景。</p>
<p><strong>项目描述</strong> ：
 <br>
 本项目旨在构建一个中英机器翻译系统，实现中文到英文和英文到中文的自动翻译。</p>
<p><strong>数据处理</strong>：</p>
<ol>
 <li><strong>数据收集</strong>：收集大量中英对照的文本数据作为训练集。</li>
 <li><strong>数据预处理</strong>：进行分词、去除噪声数据等步骤，确保数据质量。</li>
 <li><strong>数据对齐</strong>：确保中文和英文句子在语义上是对齐的，便于模型学习翻译规则。</li>
</ol>
<p><strong>模型选择与实现</strong>：</p>
<ul>
 <li><strong>模型选择</strong>：选择基于Seq2Seq（序列到序列）的模型结构进行机器翻译。其中，编码器（Encoder）负责将源语言句子编码为向量表示；解码器（Decoder）负责根据向量表示生成目标语言句子。</li>
 <li><strong>技术实现</strong>：可以使用RNN、LSTM、GRU等作为编码器和解码器的核心组件；同时引入注意力机制（Attention）提升翻译效果；此外，还可以考虑使用Transformer等更先进的模型结构进行训练。</li>
 <li><strong>训练与优化</strong>：使用TensorFlow或PyTorch等深度学习框架进行模型训练；通过调整模型参数、优化算法等策略提高翻译质量。</li>
</ul>
<p><strong>项目成果与展望</strong> ：
 <br>
 通过本项目，成功构建了一个中英机器翻译系统，实现了中文到英文和英文到中文的自动翻译。未来可以进一步优化模型结构、提高翻译质量；同时探索多语言翻译、跨领域翻译等更复杂的翻译任务；此外还可以将机器翻译技术应用于更多实际场景中如跨境电商、国际会议等。</p>
<h4>4. 对话系统项目：智能客服对话系统</h4>
<p><strong>项目背景</strong> ：
 <br>
 随着智能客服需求的不断增长，对话系统成为了自然语言处理领域的研究热点之一。智能客服对话系统能够模拟人类对话过程，为用户提供便捷、高效的服务体验。</p>
<p><strong>项目描述</strong> ：
 <br>
 本项目旨在构建一个智能客服对话系统，能够处理用户的各种查询和请求，并提供相应的回答和建议。</p>
<p><strong>系统架构</strong>：</p>
<ol>
 <li><strong>自然语言理解（NLU）</strong>：负责解析用户输入的文本信息，提取其中的意图和实体信息。</li>
 <li><strong>对话管理（DM）</strong>：根据用户的意图和上下文信息决定系统的回应策略和内容。</li>
 <li><strong>自然语言生成（NLG）</strong>：将系统的回应内容转换为自然语言文本输出给用户。</li>
</ol>
<p><strong>模型选择与实现</strong>：</p>
<ul>
 <li><strong>NLU模块</strong>：可以采用基于规则的模型或深度学习模型（如BERT等）进行意图识别和实体抽取。</li>
 <li><strong>DM模块</strong>：可以使用状态机、有限自动机等模型实现对话流程的管理和控制。</li>
 <li><strong>NLG模块</strong>：可以使用模板生成、基于检索的方法或深度学习模型（如GPT等）进行自然语言生成。</li>
</ul>
<p><strong>训练与优化</strong>：</p>
<ul>
 <li><strong>数据收集与标注</strong>：收集大量用户与客服的对话数据并进行标注形成训练集。</li>
 <li><strong>模型训练</strong>：使用训练集数据对NLU、DM和NLG模块进行分别训练或联合训练。</li>
 <li><strong>优化策略</strong>：通过引入强化学习等技术提升对话系统的交互体验和用户满意度。</li>
</ul>
<p><strong>项目成果与展望</strong> ：
 <br>
 通过本项目，成功构建了一个智能客服对话系统，能够处理用户的各种查询和请求并提供相应的回答和建议。未来可以进一步优化对话系统的性能和稳定性；同时探索更多应用场景如智能家居、医疗咨询等；此外还可以结合其他人工智能技术如计算机视觉、语音识别等实现更加智能和便捷的服务体验。</p>
<p>综上所述，自然语言处理实战项目涵盖了文本分类、情感分析、机器翻译和对话系统等多个领域。通过参与这些项目，不仅可以深入理解自然语言处理的核心技术和算法原理；还可以积累宝贵的实践经验和技能储备；为未来的职业发展和学术研究打下坚实的基础。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142311884</p>]]></description><guid isPermaLink="false">/archives/69f82040-ba6f-4d64-8525-5a611fcb768b</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:45:46 GMT</pubDate></item><item><title><![CDATA[图像生成大模型Imagen]]></title><link>https://wiki.dgfygk.com/archives/c59333f7-5bfc-42de-80bf-c5d787b1ce7d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9B%BE%E5%83%8F%E7%94%9F%E6%88%90%E5%A4%A7%E6%A8%A1%E5%9E%8BImagen&amp;url=/archives/c59333f7-5bfc-42de-80bf-c5d787b1ce7d" width="1" height="1" alt="" style="opacity:0;">
<p><strong>图像生成大模型Imagen</strong></p>
<p>Imagen是由Google Research开发的一款基于深度学习的图像生成模型，其在文本到图像（Text-to-Image）的转换技术上取得了显著突破。Imagen通过结合大型Transformer语言模型的强大能力和高保真图像生成技术，实现了前所未有的照片级真实感和深度语言理解能力，成为当前效果最好的text-to-image方案之一。以下将从Imagen的技术原理、技术架构、应用前景、面临的挑战及未来发展趋势等多个方面进行详细阐述。</p>
<h4>一、技术原理</h4>
<p>Imagen模型的核心技术依赖于深度学习，特别是扩散模型（Diffusion Models）和大规模预训练的自然语言处理（NLP）模型。扩散模型通过逐步添加噪声再逐步去噪的方式生成高质量图像，相较于传统的生成对抗网络（GANs），具有训练更加稳定、收敛性更好的优点。Imagen利用预训练的语言模型（如T5）来编码文本语义信息，然后将这些编码的文本嵌入映射到图像空间中，最终生成与文本描述高度一致的图像。</p>
<h4>二、技术架构</h4>
<p>Imagen的技术架构主要包括文本编码、基础扩散模型、超分辨率模型以及噪声调节增强等部分。</p>
<ol>
 <li>
  <p><strong>文本编码</strong>：输入的文本通过一个大型的固定T5-XXL编码器进行编码，生成文本嵌入（text embeddings）。这一步骤确保了模型能够准确理解文本中的语义信息。</p>
 </li>
 <li>
  <p><strong>基础扩散模型</strong>：这些文本嵌入被输入到一个条件扩散模型中，该模型生成一个初始的低分辨率图像（如64x64分辨率）。条件扩散模型能够根据文本嵌入生成与之对应的图像内容。</p>
 </li>
 <li>
  <p><strong>超分辨率模型</strong>：为了将初始的低分辨率图像上采样到更高的分辨率，Imagen使用了两个超分辨率扩散模型。第一个超分辨率模型将64x64的图像上采样到256x256，第二个超分辨率模型再将256x256的图像上采样到最终的1024x1024分辨率。这种多阶段生成策略确保了图像在细节、色彩、光影等方面表现出色。</p>
 </li>
 <li>
  <p><strong>噪声调节增强</strong>：在上采样的过程中，Imagen对两个超分辨率模型都使用了噪声调节增强技术，这对于生成高逼真度的图像至关重要。噪声调节增强技术有助于减少图像伪影，提高图像的清晰度和真实感。</p>
 </li>
</ol>
<p>此外，Imagen 3还引入了潜在扩散模型（Latent Diffusion Model），通过降低维度以提高计算效率。潜在扩散模型在较低维度的潜在空间中操作，极大地减少了计算开销，使得Imagen 3在保持高质量生成的同时，大幅提升了生成速度并减少了对计算资源的需求。</p>
<h4>三、应用前景</h4>
<p>Imagen模型在多个领域展现出了广泛的应用前景，包括但不限于以下几个方面：</p>
<ol>
 <li>
  <p><strong>艺术创作</strong>：艺术家可以通过输入简单的文本描述，快速生成高质量的艺术作品。Imagen不仅能够复现文本描述的场景，还能在此基础上进行创意扩展，为艺术创作提供新的灵感和可能性。</p>
 </li>
 <li>
  <p><strong>广告与市场营销</strong>：企业可以利用Imagen快速生成吸引人的视觉广告内容，更有效地吸引目标客户。Imagen生成的图像具有高度的观赏性和吸引力，非常适合用于广告宣传和品牌推广。</p>
 </li>
 <li>
  <p><strong>媒体与娱乐</strong>：在电影、游戏和动画制作中，Imagen可以预先设计场景与角色，提高生产效率并降低成本。Imagen生成的图像质量高、细节丰富，能够满足专业视觉内容的需求。</p>
 </li>
 <li>
  <p><strong>教育与培训</strong>：通过生成具体的视觉内容，帮助学生更好地理解复杂的概念和知识点。Imagen可以生成各种教学素材和实验图像，提升教学效果和学习体验。</p>
 </li>
 <li>
  <p><strong>虚拟现实与增强现实</strong>：Imagen与虚拟现实（VR）和增强现实（AR）技术结合，可以生成更加逼真的虚拟场景和融合图像，为用户提供沉浸式的体验。</p>
 </li>
</ol>
<h4>四、面临的挑战</h4>
<p>尽管Imagen在图像生成方面取得了显著成果，但仍面临诸多挑战：</p>
<ol>
 <li>
  <p><strong>数据隐私与安全</strong>：Imagen模型在训练过程中需要大量的图像数据。如何确保用户数据的安全性和合法性，是Imagen发展过程中需要解决的重要问题。</p>
 </li>
 <li>
  <p><strong>版权与知识产权</strong>：Imagen能够生成高质量的图像作品，这可能导致版权和知识产权的纠纷。如何界定生成图像的版权归属以及防止恶意侵权行为的发生，是Imagen面临的另一大挑战。</p>
 </li>
 <li>
  <p><strong>技术瓶颈</strong>：尽管Imagen在图像生成方面取得了显著进步，但仍存在诸多技术瓶颈。如何进一步优化模型结构、提高生成速度和质量，是Imagen未来发展的关键所在。</p>
 </li>
 <li>
  <p><strong>伦理与法律问题</strong>：AI生成内容的真实性可能会对公众信息的真实性造成影响。随着Imagen等技术的普及和应用，相应的伦理和法律问题也应得到充分的重视和解决。</p>
 </li>
</ol>
<h4>五、未来发展趋势</h4>
<p>展望未来，Imagen模型在图像生成领域的发展将呈现以下几个趋势：</p>
<ol>
 <li>
  <p><strong>技术不断迭代</strong>：随着深度学习技术的不断进步，Imagen的生成能力将得到进一步提升。未来可以期待更高分辨率、更逼真细节的图像生成，甚至可能实现实时生成和交互式操作。</p>
 </li>
 <li>
  <p><strong>跨领域融合</strong>：Imagen作为一种图像生成技术，未来有望与其他技术相结合，拓展更广阔的应用领域。例如，与VR/AR技术结合生成更加逼真的虚拟场景；与人工智能技术结合实现更加智能化的图像生成和处理。</p>
 </li>
 <li>
  <p><strong>商业化应用</strong>：随着Imagen技术的商业化应用逐渐落地，未来将形成更加完善的产业链。从数据收集、模型训练到图像生成、应用服务，各个环节都将有专业的企业和团队参与其中，共同推动Imagen技术的快速发展和广泛应用。</p>
 </li>
 <li>
  <p><strong>用户体验优化</strong>：为了提升用户体验，Imagen模型将不断优化其生成速度和准确性。同时，通过引入用户反馈机制和改进算法，Imagen将更好地满足用户的个性化需求。</p>
 </li>
</ol>
<p>总之，Imagen作为一款颠覆性的AI图像生成模型，为我们带来了前所未有的视觉体验。在欣赏这一技术成果的同时，我们也应关注其背后所面临的挑战和问题。只有不断探索和创新，才能让Imagen在人工智能领域绽放出更加耀眼的光芒。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142311830</p>]]></description><guid isPermaLink="false">/archives/c59333f7-5bfc-42de-80bf-c5d787b1ce7d</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:44:51 GMT</pubDate></item><item><title><![CDATA[MATLAB图像处理]]></title><link>https://wiki.dgfygk.com/archives/ba9fb31b-73c1-47dc-9d71-16e3bbc55dfe</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MATLAB%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86&amp;url=/archives/ba9fb31b-73c1-47dc-9d71-16e3bbc55dfe" width="1" height="1" alt="" style="opacity:0;">
<h4>MATLAB图像处理</h4>
<p>MATLAB，作为美国MathWorks公司出品的商业数学软件，以其强大的矩阵运算能力和丰富的函数库，在图像处理领域得到了广泛的应用。MATLAB不仅提供了基础的图像处理功能，还通过图像处理工具箱（Image Processing Toolbox）等高级工具，为用户提供了从图像读取、显示、转换到高级分析和处理的一系列功能。以下将详细介绍MATLAB在图像处理方面的应用。</p>
<h5>一、MATLAB图像处理基础</h5>
<h6>1. 图像数据类型</h6>
<p>MATLAB中常用的图像数据类型包括<code>uint8</code>、<code>double</code>和<code>logical</code>。<code>uint8</code>类型用于表示8位无符号整数，范围从0到255，常用于存储彩色图像或灰度图像。<code>double</code>类型是双精度浮点数，范围从-Inf到+Inf，但在图像处理中，通常将其归一化到0到1之间，表示图像的灰度值或颜色强度。<code>logical</code>类型是布尔类型，用于表示二值图像，其中0表示黑色，1表示白色。</p>
<h6>2. 基本图像处理函数</h6>
<ul>
 <li><strong>图像读取</strong> ：使用<code>imread</code>函数从文件中读取图像，并将其存储为MATLAB中的变量。例如，<code>I = imread('image.jpg')</code>将读取名为'image.jpg'的图像文件，并将其存储在变量I中。</li>
 <li><strong>图像显示</strong> ：使用<code>imshow</code>函数显示图像。例如，<code>imshow(I)</code>将显示存储在变量I中的图像。</li>
 <li><strong>图像类型转换</strong> ：MATLAB提供了多种函数用于图像类型转换，如<code>im2uint8</code>、<code>im2double</code>、<code>im2bw</code>等。这些函数可以将图像从一种类型转换为另一种类型，以适应不同的处理需求。</li>
</ul>
<h6>3. 矩阵运算与图像处理</h6>
<p>MATLAB的基本数据单位是矩阵，图像处理中的许多操作都可以转化为矩阵运算。例如，图像的灰度调整、滤波、边缘检测等都可以通过矩阵运算来实现。</p>
<h5>二、MATLAB图像处理工具箱</h5>
<p>MATLAB图像处理工具箱提供了大量的函数和工具，用于执行各种图像处理任务。以下是一些常用的工具箱功能和函数。</p>
<h6>1. 图像读取与显示</h6>
<p>除了基本的<code>imread</code>和<code>imshow</code>函数外，工具箱还提供了<code>imfinfo</code>函数用于获取图像文件的信息，如大小、颜色类型等。</p>
<h6>2. 图像转换</h6>
<ul>
 <li><strong>灰度转换</strong> ：使用<code>rgb2gray</code>函数将彩色图像转换为灰度图像。</li>
 <li><strong>二值化</strong> ：使用<code>imbinarize</code>或<code>im2bw</code>函数将灰度图像转换为二值图像。这些函数可以根据指定的阈值或自动计算的最优阈值来转换图像。</li>
</ul>
<h6>3. 图像增强</h6>
<ul>
 <li><strong>直方图均衡化</strong> ：使用<code>histeq</code>函数对图像进行直方图均衡化，以改善图像的对比度。</li>
 <li><strong>滤波</strong> ：MATLAB提供了多种滤波函数，如<code>imfilter</code>用于自定义滤波操作，<code>imgaussfilt</code>用于高斯滤波，<code>medfilt2</code>用于中值滤波等。这些滤波操作可以用于去噪、平滑或锐化图像。</li>
 <li><strong>对比度调整</strong> ：使用<code>imadjust</code>函数调整图像的对比度和亮度。该函数允许用户指定输入和输出灰度级的映射关系，从而实现对比度的调整。</li>
</ul>
<h6>4. 图像分割</h6>
<p>图像分割是将图像划分为具有不同特性的区域的过程。MATLAB提供了多种图像分割技术，如基于阈值的分割、基于区域的分割、基于边缘的分割等。例如，可以使用<code>bwlabel</code>函数对二值图像进行连通组件标记，从而分割出不同的对象。</p>
<h6>5. 特征提取</h6>
<p>特征提取是从图像中提取有用信息的过程。MATLAB提供了多种特征提取函数，如<code>edge</code>用于边缘检测，<code>hough</code>函数用于霍夫变换以检测直线或圆等。</p>
<h6>6. 形态学操作</h6>
<p>形态学操作是图像处理中的一种重要技术，它基于图像的几何结构进行处理。MATLAB提供了多种形态学操作函数，如<code>imdilate</code>用于膨胀操作，<code>imerode</code>用于腐蚀操作，<code>imopen</code>和<code>imclose</code>分别用于开运算和闭运算等。这些操作可以用于去除噪声、填补孔洞、分割图像等。</p>
<h5>三、MATLAB图像处理的高级应用</h5>
<h6>1. 图像分析与理解</h6>
<p>MATLAB还提供了用于图像分析和理解的函数和工具。例如，可以使用计算机视觉系统工具箱中的函数进行对象检测、识别和跟踪。这些功能在机器人视觉、自动驾驶、医疗影像分析等领域具有广泛的应用。</p>
<h6>2. 机器学习在图像处理中的应用</h6>
<p>随着机器学习技术的不断发展，MATLAB也将其应用于图像处理领域。用户可以使用MATLAB的机器学习工具箱来训练模型，以自动识别和分类图像中的对象。这些模型可以应用于各种领域，如安全监控、工业检测、医学影像分析等。</p>
<h6>3. 图像处理与深度学习</h6>
<p>近年来，深度学习在图像处理领域取得了显著的进展。MATLAB提供了深度学习工具箱（Deep Learning Toolbox），使得用户能够轻松构建和训练深度学习模型来处理图像数据。这些模型可以用于图像分类、目标检测、图像生成等多种任务。</p>
<h5>四、MATLAB图像处理的优势与挑战</h5>
<h6>优势</h6>
<ol>
 <li><strong>强大的矩阵运算能力</strong>：MATLAB以矩阵运算为核心，为图像处理提供了高效的数据处理能力。</li>
 <li><strong>丰富的函数库和工具箱</strong>：MATLAB提供了大量的图像处理函数和工具箱，覆盖了从基础到高级的各种图像处理任务。</li>
 <li><strong>灵活的编程环境</strong>：MATLAB的交互式编程环境使得用户可以快速编写、调试和测试图像处理代码。</li>
 <li><strong>广泛的应用领域</strong>：MATLAB的图像处理功能在医学、工程、科学研究等多个领域得到了广泛应用。</li>
</ol>
<h6>挑战</h6>
<ol>
 <li><strong>计算资源消耗</strong>：复杂的图像处理任务可能需要大量的计算资源，对硬件要求较高。</li>
 <li><strong>算法选择与优化</strong>：不同的图像处理任务需要选择合适的算法，并进行优化以获得最佳性能。</li>
 <li><strong>数据隐私与安全</strong>：在处理敏感图像数据时，需要关注数据隐私和安全问题。</li>
</ol>
<h5>五、结论</h5>
<p>MATLAB作为一款功能强大的数学软件，在图像处理领域具有广泛的应用前景。通过利用MATLAB的矩阵运算能力、丰富的函数库和工具箱以及灵活的编程环境，用户可以轻松实现各种图像处理任务。同时，随着机器学习和深度学习技术的不断发展，MATLAB在图像处理领域的应用也将更加深入和广泛。然而，也需要注意到在使用MATLAB进行图像处理时可能面临的挑战，如计算资源消耗、算法选择与优化以及数据隐私与安全等问题。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142257469</p>]]></description><guid isPermaLink="false">/archives/ba9fb31b-73c1-47dc-9d71-16e3bbc55dfe</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:44:30 GMT</pubDate></item><item><title><![CDATA[Nacos和Eureka的区别]]></title><link>https://wiki.dgfygk.com/archives/bcf6211e-db63-4258-b973-d1117c181649</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Nacos%E5%92%8CEureka%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/bcf6211e-db63-4258-b973-d1117c181649" width="1" height="1" alt="" style="opacity:0;">
<p>Nacos和Eureka都是流行的服务注册与发现中间件，它们在微服务架构中扮演着重要的角色。然而，两者在多个方面存在显著的差异。以下是对Nacos和Eureka之间区别的详细探讨，涵盖了功能特性、实现方式、支持模式、健康检查、连接方式等多个维度。</p>
<h4>一、功能特性与定位</h4>
<p><strong>Eureka</strong>：</p>
<ul>
 <li>Eureka是Netflix开源的一款基于REST的服务注册与发现组件，广泛应用于基于Spring Cloud构建的微服务架构中。</li>
 <li>作为服务治理的核心组件之一，Eureka负责维护服务注册表，使得各个分布式服务能够相互发现并进行通信。</li>
 <li>Eureka提供了服务注册、服务续约、服务发现、服务剔除等基本功能。</li>
</ul>
<p><strong>Nacos</strong>：</p>
<ul>
 <li>Nacos是由阿里巴巴开源的一款用于动态服务发现、配置管理和服务管理的平台。</li>
 <li>Nacos的全称是"Dynamic Naming and Configuration Service”，旨在帮助用户实现微服务架构中的服务注册与发现、配置管理、动态DNS服务等功能。</li>
 <li>除了服务注册与发现外，Nacos还提供了配置管理、服务健康检查、服务治理、路由控制等高级功能。</li>
</ul>
<h4>二、实现方式</h4>
<p><strong>Eureka</strong>：</p>
<ul>
 <li>Eureka通过客户端心跳的方式来维护服务实例的存活状态。</li>
 <li>服务提供者需要定时向Eureka Server发送心跳，以证明其仍然存活且可用。</li>
 <li>Eureka Server会定期清理那些长时间未发送心跳的服务实例，避免消费者访问已失效的服务。</li>
</ul>
<p><strong>Nacos</strong>：</p>
<ul>
 <li>Nacos支持临时实例和永久实例两种类型的服务注册。</li>
 <li>对于临时实例，Nacos采用心跳模式进行健康检查，类似于Eureka。</li>
 <li>但对于永久实例，Nacos则采用主动请求的方式来检测服务的健康状态，这提供了更灵活的健康检查机制。</li>
</ul>
<h4>三、支持模式</h4>
<p><strong>Eureka</strong>：</p>
<ul>
 <li>Eureka主要支持AP（可用性和分区容错性）模式。</li>
 <li>在网络分区发生时，Eureka会优先保证服务的可用性，即使这可能导致数据不一致。</li>
</ul>
<p><strong>Nacos</strong>：</p>
<ul>
 <li>Nacos同时支持CP（一致性和分区容错性）和AP两种模式。</li>
 <li>Nacos根据配置（如服务注册时是否指定为临时实例）自动识别并应用相应的模式。</li>
 <li>当注册为临时实例时，Nacos的行为类似于Eureka，提供AP模式的服务。而当注册为永久实例时，Nacos则提供CP模式的服务，确保数据的一致性。</li>
</ul>
<h4>四、健康检查</h4>
<p><strong>Eureka</strong>：</p>
<ul>
 <li>Eureka主要依赖心跳机制来检测服务的健康状态。</li>
 <li>如果服务提供者在一定时间内未发送心跳，Eureka Server会将其视为不健康并可能从注册表中剔除。</li>
</ul>
<p><strong>Nacos</strong>：</p>
<ul>
 <li>Nacos提供了更丰富的健康检查方式，包括心跳模式和主动请求模式。</li>
 <li>对于临时实例，Nacos采用心跳模式进行健康检查。而对于永久实例，则采用主动请求的方式来检测服务的健康状态。</li>
 <li>这种机制使得Nacos能够更准确地反映服务的实际状态，提高系统的稳定性和可靠性。</li>
</ul>
<h4>五、连接方式</h4>
<p><strong>Eureka</strong>：</p>
<ul>
 <li>Eureka采用短连接方式，即客户端定时向Eureka Server发送请求以获取服务列表。</li>
 <li>这种方式在网络延迟较高或请求频繁时可能会导致性能瓶颈。</li>
</ul>
<p><strong>Nacos</strong>：</p>
<ul>
 <li>Nacos基于Netty实现长连接机制。</li>
 <li>客户端与Nacos Server之间建立持久连接，可以实时接收服务列表的更新通知。</li>
 <li>这种机制减少了网络请求的次数和延迟，提高了系统的响应速度和效率。</li>
</ul>
<h4>六、其他差异</h4>
<p><strong>配置管理</strong>：</p>
<ul>
 <li>Eureka主要关注服务注册与发现，不提供配置管理功能。</li>
 <li>而Nacos除了服务注册与发现外，还提供了集中化的配置管理功能。用户可以通过Nacos管理配置项，并在配置变化时自动推送到应用中，确保配置的一致性和实时性。</li>
</ul>
<p><strong>部署与扩展</strong>：</p>
<ul>
 <li>Eureka可以部署为单节点或多节点集群以实现高可用性和容错性。但随着微服务数量的增加和复杂度的提高，Eureka的扩展性和性能可能会受到限制。</li>
 <li>Nacos采用集群架构并支持多节点部署，能够处理大规模的服务注册和配置管理请求。同时，Nacos提供了丰富的扩展接口和插件机制，方便用户根据实际需求进行定制和扩展。</li>
</ul>
<p><strong>社区支持与维护</strong>：</p>
<ul>
 <li>Eureka作为Netflix开源的项目，在Netflix内部得到广泛应用和支持。但随着Netflix逐渐转向其他技术栈（如AWS服务），Eureka的更新和维护速度可能逐渐放缓。</li>
 <li>Nacos由阿里巴巴开源并持续维护更新，拥有活跃的社区和丰富的文档资源。随着微服务架构的普及和阿里巴巴等大厂的推动，Nacos的普及度和影响力也在不断提升。</li>
</ul>
<p>综上所述，Nacos和Eureka在功能特性、实现方式、支持模式、健康检查、连接方式等多个方面存在显著差异。在选择服务注册与发现中间件时，用户应根据自身的业务需求和技术栈特点进行综合考虑和选择。对于需要同时解决服务注册与发现和配置管理问题的用户来说，Nacos可能是一个更加合适的选择。而对于已经基于Spring Cloud构建微服务架构并希望保持技术栈一致性的用户来说，Eureka则可能是一个更加成熟和稳定的选择。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142305620</p>]]></description><guid isPermaLink="false">/archives/bcf6211e-db63-4258-b973-d1117c181649</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:44:01 GMT</pubDate></item><item><title><![CDATA[PHP环境搭建]]></title><link>https://wiki.dgfygk.com/archives/280e21aa-5b20-4658-ac06-11489a904c4c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=PHP%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA&amp;url=/archives/280e21aa-5b20-4658-ac06-11489a904c4c" width="1" height="1" alt="" style="opacity:0;">
<p>PHP环境搭建是一个涉及多个组件配置的过程，主要包括安装Apache服务器（或其他Web服务器）、PHP解释器以及数据库（如MySQL）等。以下是一个详细的PHP环境搭建教程，旨在帮助初学者逐步完成环境的配置。</p>
<h4>一、前期准备</h4>
<h5>1. 确定操作系统</h5>
<p>首先，你需要确定你的操作系统类型（如Windows、Linux或macOS），因为不同操作系统的安装步骤和工具可能有所不同。</p>
<h5>2. 下载软件</h5>
<ul>
 <li><strong>Apache服务器</strong>：访问Apache官方网站（https://httpd.apache.org/）下载适合你操作系统的Apache版本。</li>
 <li><strong>PHP解释器</strong>：访问PHP官方网站（https://www.php.net/downloads）下载适合你操作系统的PHP版本。注意，如果是与Apache搭配使用，建议选择Thread Safe（线程安全）版本。</li>
 <li><strong>MySQL数据库</strong>（可选）：访问MySQL官方网站（https://dev.mysql.com/downloads/mysql/）下载MySQL数据库。虽然PHP环境搭建不一定需要MySQL，但它是PHP开发中常用的数据库之一。</li>
</ul>
<h4>二、安装Apache服务器</h4>
<h5>1. Windows系统</h5>
<ul>
 <li>下载Apache安装包后，双击运行，按照提示进行安装。注意选择安装路径和配置端口（默认是80端口）。</li>
 <li>安装完成后，在浏览器中输入<code>http://localhost</code>或<code>http://127.0.0.1</code>，如果看到Apache的默认页面，说明安装成功。</li>
</ul>
<h5>2. Linux/macOS系统</h5>
<ul>
 <li>对于Linux系统，可以使用包管理器（如apt-get、yum等）安装Apache。例如，在Ubuntu上，可以使用命令<code>sudo apt-get install apache2</code>来安装。</li>
 <li>安装完成后，需要启动Apache服务，并设置开机自启。可以使用命令<code>sudo systemctl start apache2</code>来启动服务，使用<code>sudo systemctl enable apache2</code>来设置开机自启。</li>
 <li>在浏览器中访问<code>http://localhost</code>或<code>http://127.0.0.1</code>来验证安装是否成功。</li>
</ul>
<h4>三、安装PHP解释器</h4>
<h5>1. 解压PHP安装包</h5>
<ul>
 <li>将下载的PHP安装包解压到指定目录。例如，在Windows上，你可以解压到<code>C:\php</code>；在Linux上，可以解压到<code>/usr/local/php</code>。</li>
</ul>
<h5>2. 配置php.ini文件</h5>
<ul>
 <li>找到PHP安装目录下的<code>php.ini-development</code>或<code>php.ini-production</code>文件，将其复制一份并重命名为<code>php.ini</code>。这个文件是PHP的配置文件。</li>
 <li>使用文本编辑器打开<code>php.ini</code>文件，根据需要修改配置项。例如，你可以取消注释<code>extension_dir</code>指令并指定扩展文件夹的路径（例如<code>extension_dir="ext"</code>），然后取消注释需要的扩展（如<code>extension=mysqli</code>）以启用它们。</li>
 <li>确保<code>display_errors</code>设置为<code>On</code>，以便在开发过程中能够显示错误信息。</li>
</ul>
<h5>3. 将PHP与Apache集成</h5>
<ul>
 <li>在Apache的配置文件<code>httpd.conf</code>（Windows上可能是<code>httpd-vhosts.conf</code>）中，加载PHP模块。对于Windows系统，通常需要在文件中添加类似<code>LoadModule php7_module "C:/php/php7apache2_4.dll"</code>的指令（注意版本号与你的PHP版本相匹配）。</li>
 <li>配置Apache以解析PHP文件。在<code>httpd.conf</code>文件中，找到<code>&lt;IfModule mime_module&gt;</code>部分，添加或修改<code>.php</code>文件的MIME类型为<code>application/x-httpd-php</code>。</li>
 <li>设置Apache的<code>DocumentRoot</code>和<code>&lt;Directory&gt;</code>指令，指定Web站点的根目录和PHP文件的处理规则。</li>
</ul>
<h4>四、安装MySQL数据库（可选）</h4>
<h5>1. 安装MySQL</h5>
<ul>
 <li>按照MySQL官方网站的安装指南进行安装。对于Windows系统，可以下载MSI安装包进行图形化安装；对于Linux系统，可以使用包管理器安装。</li>
</ul>
<h5>2. 启动MySQL服务</h5>
<ul>
 <li>在Windows上，可以通过服务管理器启动MySQL服务；在Linux上，可以使用<code>sudo systemctl start mysql</code>命令启动服务。</li>
</ul>
<h5>3. 设置MySQL用户和密码</h5>
<ul>
 <li>MySQL安装后，通常需要设置一个root用户的密码。在MySQL的命令行工具中，使用<code>ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';</code>命令来设置密码（注意替换<code>新密码</code>为实际密码）。</li>
</ul>
<h4>五、测试PHP环境</h4>
<h5>1. 创建PHP测试文件</h5>
<ul>
 <li>
  <p>在Apache的<code>DocumentRoot</code>指定的目录下（例如<code>C:\Apache24\htdocs</code>或<code>/var/www/html</code>），创建一个名为<code>test.php</code>的文件。</p>
 </li>
 <li>
  <p>在<code>test.php</code>文件中编写以下PHP代码：</p>
  <pre><code class="prism language-php">&lt;?php
phpinfo();
?&gt;
</code></pre>
 </li>
</ul>
<h5>2. 访问测试文件</h5>
<ul>
 <li>在浏览器中访问<code>http://localhost/test.php</code>（或根据你的Apache配置进行相应调整）。如果看到PHP的配置信息页面，说明PHP环境已经成功搭建并与Apache集成。</li>
</ul>
<h4>六、优化和安全性配置</h4>
<ul>
 <li><strong>调整内存限制</strong> ：在<code>php.ini</code>文件中调整<code>memory_limit</code>指令，为PHP脚本分配足够的内存。</li>
 <li><strong>开启防火墙</strong>：确保你的服务器开启了防火墙，并配置了适当的规则来限制外部访问。</li>
 <li><strong>使用SSL/TLS</strong>：为Apache服务器配置SSL/TLS加密，以提高数据传输的安全性。</li>
 <li><strong>定期更新</strong>：定期更新Apache、PHP和MySQL到最新版本，以修复已知的安全漏洞。</li>
</ul>
<h4>七、使用集成开发环境（IDE）</h4>
<p>虽然本教程主要介绍了如何手动搭建PHP环境，但你也可以选择使用集成开发环境（IDE）来简化开发过程。IDE如PhpStorm、Visual Studio Code等提供了丰富的功能和工具来支持PHP开发，包括代码补全、调试、版本控制等。</p>
<h4>八、结论</h4>
<p>通过以上步骤，你可以成功搭建一个基本的PHP开发环境。当然，根据你的具体需求，可能还需要进行更多的配置和优化。不过，有了这个基础，你就可以开始使用PHP进行Web开发了。记得在开发过程中不断学习和实践，以提升自己的技能水平。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142305643</p>]]></description><guid isPermaLink="false">/archives/280e21aa-5b20-4658-ac06-11489a904c4c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:43:41 GMT</pubDate></item><item><title><![CDATA[机器学习和深度学习区别]]></title><link>https://wiki.dgfygk.com/archives/82c0a8ff-ec56-4d3a-b313-808507ce06fa</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%8C%BA%E5%88%AB&amp;url=/archives/82c0a8ff-ec56-4d3a-b313-808507ce06fa" width="1" height="1" alt="" style="opacity:0;">
<p>机器学习和深度学习作为人工智能领域的两大重要分支，虽然有着紧密的联系，但在多个方面存在显著的差异。以下将从定义与起源、技术基础、模型复杂度、数据需求、计算资源需求、应用领域以及学习方式与特点等角度，详细阐述机器学习和深度学习的区别。</p>
<h4>一、定义与起源</h4>
<p><strong>机器学习</strong>：是人工智能的一个分支，它让计算机能够在没有明确编程的情况下，通过观察和分析大量数据来学习并做出预测或决策。机器学习起源于20世纪50年代，随着算法的不断发展，逐渐形成了多种经典算法，如决策树、支持向量机、随机森林等。</p>
<p><strong>深度学习</strong>：则是机器学习的一个子领域，它主要依赖于深度神经网络模型进行学习和预测。深度学习在21世纪初开始兴起，特别是随着计算能力的提升和大数据的普及，深度学习算法得以广泛应用，并取得了显著成效。</p>
<h4>二、技术基础</h4>
<p><strong>机器学习</strong>：基于统计学、概率论、逼近论、线性代数、高等数学等多学科交叉的知识体系，通过构建各种算法模型，使计算机能够自动学习并优化模型参数，从而实现对新数据的预测或决策。</p>
<p><strong>深度学习</strong>：则主要依赖于人工神经网络，特别是深度神经网络模型。深度学习通过构建多层神经元之间的连接，模拟人脑神经元的工作方式，实现对复杂数据的自动学习和特征提取。</p>
<h4>三、模型复杂度</h4>
<p><strong>机器学习</strong>：模型复杂度相对较低，常用的模型如线性回归、支持向量机等，参数数量较少，计算复杂度相对较低。这些模型适用于处理相对简单的数据结构和问题。</p>
<p><strong>深度学习</strong>：模型复杂度较高，通常使用多层的神经网络模型，如卷积神经网络（CNN）、递归神经网络（RNN）等。这些模型包含大量的参数和计算单元，能够处理更复杂的数据结构和问题。然而，这也导致了深度学习模型对计算资源的高需求。</p>
<h4>四、数据需求</h4>
<p><strong>机器学习</strong>：对数据的需求相对较低，部分算法可以在小数据集上表现出色。机器学习算法更注重数据的质量而非数量，通过合理的特征工程和算法选择，可以在有限的数据下获得较好的预测效果。</p>
<p><strong>深度学习</strong>：对数据的需求较高，特别是需要大量标记数据来训练复杂的模型。深度学习模型通过自动学习特征的方式，需要足够的数据来避免过拟合等问题，并提升模型的泛化能力。因此，深度学习在大数据环境下表现尤为出色。</p>
<h4>五、计算资源需求</h4>
<p><strong>机器学习</strong>：通常可以在普通的计算机上进行训练和推理，对计算资源的需求相对较低。一些简单的机器学习模型甚至可以在嵌入式设备上运行，实现实时预测和决策。</p>
<p><strong>深度学习</strong>：由于模型的复杂性和数据量的庞大，深度学习通常需要高性能的计算资源来支持模型的训练和推理。GPU或专用硬件如TPU等成为了深度学习训练的首选工具，它们能够显著加速矩阵计算和神经网络的训练过程。</p>
<h4>六、应用领域</h4>
<p><strong>机器学习</strong>：具有广泛的应用领域，如金融预测、医疗诊断、广告推荐等。机器学习算法能够处理各种类型的数据，并通过学习数据中的规律来优化决策过程。</p>
<p><strong>深度学习</strong>：特别适合于处理图像、语音、自然语言等复杂数据类型。深度学习算法在图像识别、语音识别、自然语言处理等领域取得了显著成果，推动了人工智能在这些领域的快速发展。此外，深度学习还在自动驾驶、智能机器人等领域展现出巨大的潜力。</p>
<h4>七、学习方式与特点</h4>
<p><strong>机器学习</strong>：侧重于特征工程，需要人工选择和提取数据的特征。机器学习算法的性能很大程度上取决于特征工程的质量。此外，机器学习算法通常具有明确的数学基础和可解释性，便于人们理解和优化模型。</p>
<p><strong>深度学习</strong>：可以自动从原始数据中学习特征，减少了人工干预和特征工程的需求。深度学习算法通过构建多层神经网络模型，自动提取数据中的高层特征，并用于后续的预测或决策。然而，深度学习算法的可解释性相对较差，人们往往难以直接理解模型内部的决策过程。</p>
<p>综上所述，机器学习和深度学习在定义与起源、技术基础、模型复杂度、数据需求、计算资源需求、应用领域以及学习方式与特点等方面存在显著的差异。随着人工智能技术的不断发展，机器学习和深度学习将继续在各自擅长的领域发挥重要作用，并推动人工智能技术的不断进步和创新。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142129823</p>]]></description><guid isPermaLink="false">/archives/82c0a8ff-ec56-4d3a-b313-808507ce06fa</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:43:28 GMT</pubDate></item><item><title><![CDATA[Web安全与网络安全：SQL漏洞注入]]></title><link>https://wiki.dgfygk.com/archives/6bba745a-d190-47e3-b1b3-c4cb80fcbd7f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Web%E5%AE%89%E5%85%A8%E4%B8%8E%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%EF%BC%9ASQL%E6%BC%8F%E6%B4%9E%E6%B3%A8%E5%85%A5&amp;url=/archives/6bba745a-d190-47e3-b1b3-c4cb80fcbd7f" width="1" height="1" alt="" style="opacity:0;">
<h2>Web安全与网络安全：SQL漏洞注入</h2>
<h3>引言</h3>
<p>在Web安全领域，SQL注入漏洞（SQL Injection Vulnerability）是一种极具破坏性的安全威胁。它允许攻击者通过向Web应用程序的输入字段中插入或"注入"恶意的SQL代码片段，从而操纵后台数据库系统，执行未授权的数据库查询，甚至可能获取数据库管理权限，进而对整个系统造成严重的安全损害。本文将从SQL注入的原理、分类、危害及防御策略等方面进行详细阐述。</p>
<h3>SQL注入的原理</h3>
<p>SQL注入的核心原理在于，攻击者通过构造特殊的输入数据，这些数据在应用程序中未被恰当地过滤或转义，便直接作为SQL语句的一部分被数据库执行。这种行为破坏了原有SQL语句的结构和意图，导致数据库执行了攻击者期望的操作，而非程序开发者预期的操作。</p>
<p>具体来说，当Web应用程序使用用户输入来构造数据库查询语句时，如果直接将用户输入拼接到SQL语句中，而没有进行必要的过滤和转义，就可能导致SQL注入漏洞。例如，一个简单的登录查询可能如下所示：</p>
<pre><code class="prism language-sql">SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
</code></pre>
<p>如果攻击者将<code>username</code>设置为<code>admin' --</code>，则最终的SQL语句将变为：</p>
<pre><code class="prism language-sql">SELECT * FROM users WHERE username = 'admin' --' AND password = 'anyvalue';
</code></pre>
<p>由于SQL注释<code>--</code>的存在，密码部分的条件被注释掉了，攻击者可以无需密码即可登录为<code>admin</code>用户。</p>
<h3>SQL注入的分类</h3>
<p>SQL注入漏洞可以根据不同的标准进行分类，常见的分类方式包括：</p>
<ol>
 <li>
  <p><strong>基于注入点的位置</strong>：</p>
 </li>
 <li>
  <p>GET注入：通过URL的查询字符串进行注入。</p>
 </li>
 <li>
  <p>POST注入：通过HTTP POST请求的表单数据进行注入。</p>
 </li>
 <li>
  <p>Cookie注入：通过HTTP Cookie进行注入。</p>
 </li>
 <li>
  <p>HTTP头注入：通过HTTP请求头（如User-Agent、Referer等）进行注入。</p>
 </li>
 <li>
  <p><strong>基于注入的效果</strong>：</p>
 </li>
 <li>
  <p>回显注入：攻击者可以从应用程序的响应中直接获取数据库查询的结果。</p>
 </li>
 <li>
  <p>报错注入：通过触发数据库错误消息来获取数据库结构或数据。</p>
 </li>
 <li>
  <p>盲注：攻击者无法直接从响应中获取数据，但可以通过观察应用程序的响应时间、页面内容变化等间接信息来推断数据库内容。</p>
 </li>
 <li>
  <p><strong>基于注入的技巧</strong>：</p>
 </li>
 <li>
  <p>联合查询注入（Union Injection）：利用SQL的UNION SELECT语句将多个查询结果合并返回。</p>
 </li>
 <li>
  <p>布尔注入（Boolean Injection）：通过构造SQL条件语句，观察应用程序的布尔响应（如真/假）来推断数据库内容。</p>
 </li>
 <li>
  <p>时间盲注（Time-based Blind Injection）：利用数据库查询的延时响应来推断数据库内容。</p>
 </li>
 <li>
  <p>堆叠查询注入（Stacked Queries Injection）：允许攻击者在一个查询中执行多条SQL语句。</p>
 </li>
</ol>
<h3>SQL注入的危害</h3>
<p>SQL注入漏洞的危害极大，一旦攻击者成功利用该漏洞，可能实现以下攻击行为：</p>
<ol>
 <li><strong>数据泄露</strong>：攻击者可以查询数据库中的敏感信息，如用户密码、信用卡号码等。</li>
 <li><strong>数据篡改</strong>：攻击者可以修改数据库中的数据，如修改用户权限、订单状态等。</li>
 <li><strong>数据删除</strong>：攻击者可以删除数据库中的重要数据，导致业务中断或数据丢失。</li>
 <li><strong>数据库控制</strong>：如果数据库权限配置不当，攻击者甚至可能获取数据库管理员权限，进而控制整个数据库系统。</li>
 <li><strong>内网渗透</strong>：通过数据库系统作为跳板，攻击者可能进一步渗透至内网其他系统，造成更大的安全威胁。</li>
</ol>
<h3>SQL注入的防御策略</h3>
<p>为了有效防御SQL注入攻击，可以采取以下策略：</p>
<ol>
 <li>
  <p><strong>输入验证</strong>：</p>
 </li>
 <li>
  <p>对所有用户输入进行严格的验证和过滤，确保输入数据符合预期的数据类型和格式。</p>
 </li>
 <li>
  <p>使用白名单验证技术，只允许特定的字符或格式通过。</p>
 </li>
 <li>
  <p><strong>使用参数化查询</strong>：</p>
 </li>
 <li>
  <p>采用参数化查询（也称为预处理语句）来构建SQL语句。这种方法将SQL语句的固定部分与变量部分分开处理，变量部分通过参数传递给数据库，从而避免了SQL注入攻击。</p>
 </li>
 <li>
  <p>大多数现代数据库和编程语言都支持参数化查询。</p>
 </li>
 <li>
  <p><strong>使用ORM框架</strong>：</p>
 </li>
 <li>
  <p>对象关系映射（ORM）框架可以自动处理SQL语句的生成和参数化查询，减少了开发者直接编写SQL语句的机会，从而降低了SQL注入的风险。</p>
 </li>
 <li>
  <p><strong>限制数据库权限</strong>：</p>
 </li>
 <li>
  <p>为数据库用户分配最低必要的权限，避免使用具有广泛权限的数据库账户。</p>
 </li>
 <li>
  <p>定期审查和更新数据库权限设置。</p>
 </li>
 <li>
  <p><strong>错误处理</strong>：</p>
 </li>
 <li>
  <p>避免在应用程序中直接返回数据库错误信息给最终用户。错误信息可能泄露数据库的内部结构或配置信息，从而帮助攻击者实施进一步的攻击。</p>
 </li>
 <li>
  <p>使用自定义的错误页面或日志记录机制来处理数据库错误。</p>
 </li>
 <li>
  <p><strong>使用Web应用防火墙（WAF）</strong>：</p>
 </li>
 <li>
  <p>WAF可以检测和拦截SQL注入等Web攻击，为Web应用程序提供额外的安全保护。</p>
 </li>
 <li>
  <p>WAF可以配置为监控和过滤HTTP请求和响应，检测并阻止潜在的恶意流量。</p>
 </li>
 <li>
  <p><strong>定期安全审计和漏洞扫描</strong>：</p>
 </li>
 <li>
  <p>定期对Web应用程序进行安全审计和漏洞扫描，及时发现并修复潜在的安全漏洞。</p>
 </li>
 <li>
  <p>使用自动化工具（如sqlmap）来辅助进行SQL注入漏洞的检测和验证。</p>
 </li>
 <li>
  <p><strong>安全意识培训</strong>：</p>
 </li>
 <li>
  <p>对开发人员和运维人员进行定期的安全意识培训，提高他们的安全意识和技能水平。</p>
 </li>
 <li>
  <p>强调安全编码实践的重要性，并鼓励开发人员遵循最佳的安全编程规范。</p>
 </li>
</ol>
<h3>结论</h3>
<p>SQL注入漏洞是Web安全领域中的一个重要威胁，它允许攻击者通过构造恶意的输入数据来操纵数据库系统，进而对Web应用程序乃至整个系统造成严重的安全损害。为了有效防御SQL注入攻击，需要采取多种策略相结合的方法，包括输入验证、使用参数化查询、限制数据库权限、错误处理、使用WAF、定期安全审计和漏洞扫描以及安全意识培训等。只有综合运用这些技术和方法，才能确保Web应用程序的安全性并保护用户数据免受攻击。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142129764</p>]]></description><guid isPermaLink="false">/archives/6bba745a-d190-47e3-b1b3-c4cb80fcbd7f</guid><dc:creator>黄健</dc:creator><category>网络运维</category><pubDate>Sun, 29 Sep 2024 00:43:16 GMT</pubDate></item><item><title><![CDATA[计算机视觉学习路线]]></title><link>https://wiki.dgfygk.com/archives/afbfa353-e969-4f8a-acac-c45f040cd631</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF&amp;url=/archives/afbfa353-e969-4f8a-acac-c45f040cd631" width="1" height="1" alt="" style="opacity:0;">
<p>计算机视觉学习路线是一个系统而全面的过程，它涵盖了从基础知识到高级应用的多个方面。以下是一个详细的学习路线，旨在帮助学习者逐步掌握计算机视觉的核心技术和应用。</p>
<h4>一、基础知识与编程技能</h4>
<p><strong>1. 编程语言学习</strong></p>
<ul>
 <li><strong>Python</strong>：作为计算机视觉领域最受欢迎的编程语言之一，Python以其简洁易读、拥有丰富的库和工具而著称。学习者需要掌握Python的基础语法、函数、面向对象编程等概念，并熟悉常用的编程环境如PyCharm、Jupyter Notebook等。</li>
 <li><strong>其他编程语言</strong>：虽然Python是首选，但了解C++、MATLAB等其他编程语言也有助于拓宽视野和应对特定需求。</li>
</ul>
<p><strong>2. 数学基础</strong></p>
<ul>
 <li>计算机视觉涉及大量的数学知识，包括线性代数、概率论与数理统计、微积分等。这些数学工具在计算机视觉算法的设计和实现中发挥着重要作用。</li>
 <li>学习者应掌握矩阵运算、概率分布、统计推断、微积分等基本概念和方法。</li>
</ul>
<h4>二、图像处理与计算机视觉基础</h4>
<p><strong>1. 数字图像处理</strong></p>
<ul>
 <li>学习图像的表示方式（像素、灰度、二值化等）、色彩空间转换（RGB、HSV等）、滤波操作（均值滤波、高斯滤波、中值滤波等）、形态学操作（腐蚀、膨胀、开运算、闭运算等）、影像增强（直方图均衡化、自适应均衡化等）等基本概念和技术。</li>
 <li>熟悉常用的图像处理库，如OpenCV（Open Source Computer Vision Library），它包含了大量的图像处理函数和算法。</li>
</ul>
<p><strong>2. 特征提取与匹配</strong></p>
<ul>
 <li>学习SIFT（尺度不变特征变换）、SURF、ORB等特征检测和描述算法，这些算法在图像匹配、物体识别等领域有着广泛的应用。</li>
 <li>了解HOG（方向梯度直方图）、LBP等用于图像表示的特征，以及它们在图像分类、物体检测等任务中的作用。</li>
</ul>
<h4>三、机器学习与深度学习</h4>
<p><strong>1. 经典机器学习算法</strong></p>
<ul>
 <li>学习SVM（支持向量机）、KNN（k-最近邻）、决策树等经典机器学习算法，并了解它们在图像分类、目标检测等任务中的应用。</li>
</ul>
<p><strong>2. 深度学习基础</strong></p>
<ul>
 <li>深入学习卷积神经网络（CNN）的基本原理和结构，包括卷积层、池化层、全连接层等。</li>
 <li>熟悉LeNet、AlexNet、VGGNet、ResNet等经典CNN模型，并了解它们在图像识别、分类等任务中的表现。</li>
</ul>
<p><strong>3. 深度学习框架</strong></p>
<ul>
 <li>掌握TensorFlow或PyTorch等深度学习框架的使用，了解它们的架构、特点和优势。</li>
 <li>通过实践项目，如手写数字识别、物体分类等，加深对深度学习框架的理解和掌握。</li>
</ul>
<h4>四、高级算法与前沿技术</h4>
<p><strong>1. 深度神经网络的进阶</strong></p>
<ul>
 <li>学习更深度的神经网络模型，如DenseNet、MobileNet等，了解它们在图像识别、分类等任务中的优化和改进。</li>
</ul>
<p><strong>2. 生成对抗网络（GAN）</strong></p>
<ul>
 <li>学习GAN的基本原理和应用场景，了解它在图像生成、超分辨率重建等方面的潜力。</li>
</ul>
<p><strong>3. 实例分割与强化学习</strong></p>
<ul>
 <li>学习Mask R-CNN等实例分割算法，了解它们在物体检测和分割任务中的优势。</li>
 <li>探索强化学习在计算机视觉中的应用，如通过视觉反馈进行机器人导航和物体抓取等。</li>
</ul>
<h4>五、实践与应用</h4>
<p><strong>1. 实践项目</strong></p>
<ul>
 <li>通过实现简单的物体分类器、基于特征匹配的图像查找、使用CNN进行手写数字识别、基于深度学习的目标检测等实践项目，巩固所学知识并提升实践能力。</li>
</ul>
<p><strong>2. 参与开源项目</strong></p>
<ul>
 <li>关注计算机视觉领域的顶级会议（如CVPR、ICCV等）的最新研究进展，参与Github上相关的开源项目，了解最新的技术和方法。</li>
</ul>
<p><strong>3. 行业应用</strong></p>
<ul>
 <li>了解计算机视觉在自动驾驶、智能视频监控、医疗图像分析、增强现实等领域的应用案例，加深对计算机视觉技术在实际应用中的理解。</li>
</ul>
<h4>六、持续学习与更新</h4>
<ul>
 <li>计算机视觉是一个快速发展的领域，新的技术和方法层出不穷。学习者需要保持对前沿技术的关注，定期阅读最新的学术论文、博客文章和技术文档。</li>
 <li>参加相关的学术会议、研讨会和培训班，与同行交流心得和经验，拓宽视野并提升自己的专业水平。</li>
</ul>
<p>总之，计算机视觉学习路线是一个循序渐进的过程，需要学习者在掌握基础知识的同时不断实践和探索。通过不断学习和积累经验，学习者将能够逐步掌握计算机视觉的核心技术和应用方法，并在实际工作中发挥重要作用。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142257294</p>]]></description><guid isPermaLink="false">/archives/afbfa353-e969-4f8a-acac-c45f040cd631</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:42:31 GMT</pubDate></item><item><title><![CDATA[切换淘宝最新npm镜像源是]]></title><link>https://wiki.dgfygk.com/archives/75fa57cc-dcf9-4ebc-a56b-50f4f8818b6a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%87%E6%8D%A2%E6%B7%98%E5%AE%9D%E6%9C%80%E6%96%B0npm%E9%95%9C%E5%83%8F%E6%BA%90%E6%98%AF&amp;url=/archives/75fa57cc-dcf9-4ebc-a56b-50f4f8818b6a" width="1" height="1" alt="" style="opacity:0;">
<p>切换淘宝最新npm镜像源是一个相对简单的过程，但首先需要明确当前淘宝npm镜像源的状态和最新的镜像地址。由于网络环境和服务更新，镜像源的具体地址可能会发生变化，因此，我将基于当前可获取的信息，提供一个通用的切换步骤，并附上最新的镜像地址（截至回答时）。</p>
<h4>一、了解npm镜像源</h4>
<p>npm（Node Package Manager）是JavaScript的包管理器，用于安装、更新和管理项目依赖。由于npm官方仓库位于国外，国内用户访问时可能会遇到速度较慢或不稳定的问题。因此，国内开发者常常会使用淘宝npm镜像（或其他国内镜像）来加速包的下载和安装。</p>
<h4>二、淘宝npm镜像的最新状态</h4>
<p><strong>截至当前时间（2024年9月15日），淘宝npm镜像的最新地址是</strong> ：<a href="https://registry.npmmirror.com/">https://registry.npmmirror.com/</a>。这个地址是淘宝镜像在旧域名（npm.taobao.org和registry.npm.taobao.org）证书过期后更新的新地址。旧域名已于2022年5月31日停止服务，HTTPS证书也于2024年1月22日正式到期。</p>
<h4>三、切换淘宝最新npm镜像源的步骤</h4>
<h5>1. 打开命令行工具</h5>
<p>首先，打开你的命令行工具，如Windows的CMD、PowerShell或Linux/macOS的Terminal。</p>
<h5>2. 查看当前npm镜像源</h5>
<p>在命令行中，输入以下命令来查看当前的npm镜像源：</p>
<pre><code class="prism language-bash">npm config get registry
</code></pre>
<p>这个命令会显示当前npm配置的镜像源地址。如果地址不是淘宝的npm镜像地址，那么你需要进行切换。</p>
<h5>3. 切换npm镜像源到淘宝</h5>
<p>接下来，使用以下命令将npm镜像源切换到淘宝的最新镜像地址：</p>
<pre><code class="prism language-bash">npm config set registry https://registry.npmmirror.com/
</code></pre>
<p>执行完这个命令后，npm就会使用淘宝的最新镜像源来下载和安装包了。</p>
<h5>4. 验证npm镜像源是否切换成功</h5>
<p>为了确认npm镜像源已经成功切换到淘宝，你可以再次运行查看镜像源的命令：</p>
<pre><code class="prism language-bash">npm config get registry
</code></pre>
<p>如果返回的地址是<code>https://registry.npmmirror.com/</code>，那么说明你已经成功切换到淘宝的npm镜像源了。</p>
<h4>四、其他切换npm镜像源的方法</h4>
<p>除了使用<code>npm config set registry</code>命令外，还有其他几种方法可以切换npm镜像源：</p>
<h5>1. 使用cnpm</h5>
<p>cnpm是淘宝npm镜像的命令行工具，它支持npm的所有命令，并且默认使用淘宝npm镜像源。你可以通过以下命令安装cnpm：</p>
<pre><code class="prism language-bash">npm install -g cnpm --registry=https://registry.npmmirror.com/
</code></pre>
<p>安装完成后，你就可以使用<code>cnpm</code>命令来代替<code>npm</code>命令，享受更快的包下载和安装速度了。</p>
<h5>2. 修改npm配置文件</h5>
<p>你也可以直接在npm的配置文件（通常是用户目录下的<code>.npmrc</code>文件）中添加或修改<code>registry</code>配置项，将其设置为淘宝npm镜像的地址。这样，每次使用npm时，它都会自动使用这个镜像源。</p>
<h5>3. 使用npm的alias命令</h5>
<p>如果你不想改变全局的npm镜像源，但又想在特定项目中使用淘宝npm镜像源，你可以使用npm的alias功能来创建一个新的命令，该命令在执行时会默认使用淘宝npm镜像源。例如：</p>
<pre><code class="prism language-bash">alias cnpm="npm --registry=https://registry.npmmirror.com/ \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npmmirror.com/mirrors/node \
--userconfig=$HOME/.cnpmrc"
</code></pre>
<p>然后，你就可以使用<code>cnpm</code>命令来执行npm操作了，它会使用淘宝npm镜像源。</p>
<h4>五、注意事项</h4>
<ul>
 <li>切换npm镜像源后，可能会影响某些依赖包的下载和安装。如果遇到问题，可以尝试切换回官方npm镜像源或检查依赖包是否支持淘宝npm镜像源。</li>
 <li>淘宝npm镜像源是一个第三方提供的服务，其稳定性和可用性可能会受到网络环境、服务器负载等因素的影响。如果遇到问题，可以联系淘宝npm镜像的维护团队或查看其官方公告和社区动态。</li>
 <li>由于npm镜像源地址可能会发生变化，建议定期检查和更新你的npm镜像源设置，以确保能够享受到最快的包下载和安装速度。</li>
</ul>
<p>通过以上步骤和注意事项，你应该能够成功切换淘宝最新npm镜像源，并享受更快的包下载和安装体验。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142281282</p>]]></description><guid isPermaLink="false">/archives/75fa57cc-dcf9-4ebc-a56b-50f4f8818b6a</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sun, 29 Sep 2024 00:42:22 GMT</pubDate></item><item><title><![CDATA[深度学习算法]]></title><link>https://wiki.dgfygk.com/archives/b9b8bbe7-13bd-4d76-9831-0c8169b2a670</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%AE%97%E6%B3%95&amp;url=/archives/b9b8bbe7-13bd-4d76-9831-0c8169b2a670" width="1" height="1" alt="" style="opacity:0;">
<p>深度学习算法是人工智能领域中的一个重要分支，它通过模拟人脑神经网络的结构和功能，从大量数据中自动提取特征并进行学习，以解决各种复杂的任务。以下是对深度学习算法的详细探讨，包括其基本原理、核心算法、应用领域及未来发展趋势。</p>
<h4>一、深度学习算法的基本原理</h4>
<p>深度学习算法的核心在于构建深层的神经网络模型，这些模型通过多层非线性处理单元（神经元）的堆叠，能够自动从原始数据中提取出高级抽象特征，进而实现分类、回归、聚类等任务。神经网络的基本单元是神经元，它接收输入信号，经过加权求和和激活函数处理后产生输出信号。多个神经元组成一层，网络通常包含输入层、多个隐藏层和输出层。</p>
<p>深度学习的学习过程主要包括前向传播和反向传播两个阶段。在前向传播过程中，输入数据经过网络各层的神经元，逐层传递并计算，最终得到输出结果。在反向传播过程中，根据输出结果与真实标签之间的误差，通过梯度下降等优化算法反向调整网络参数（如权重和偏置），以减小误差并提高模型性能。</p>
<h4>二、核心深度学习算法</h4>
<h5>1. 卷积神经网络（CNN）</h5>
<p>CNN是深度学习中最著名的算法之一，特别适用于处理具有网格结构的数据（如图像和视频）。CNN通过卷积层、池化层和全连接层等组件，实现对输入数据的特征提取和分类。卷积层利用卷积核在输入数据上进行滑动窗口操作，提取局部特征；池化层通过下采样减少数据维度和计算量；全连接层则将学习到的特征映射到样本标记空间。CNN在图像识别、目标检测和人脸识别等领域取得了巨大成功。</p>
<h5>2. 循环神经网络（RNN）及其变种</h5>
<p>RNN是一种专门用于处理序列数据的神经网络，如自然语言文本、时间序列数据等。RNN通过引入循环连接，使得网络能够捕捉序列数据中的时间依赖性。然而，传统RNN在处理长序列时容易出现梯度消失或梯度爆炸问题。为了解决这一问题，研究者们提出了长短时记忆网络（LSTM）和门控循环单元（GRU）等RNN的变种。LSTM和GRU通过引入门控机制，有效地控制信息的记忆和遗忘过程，从而提高了对长序列数据的处理能力。</p>
<h5>3. 深度学习优化算法</h5>
<p>深度学习中的优化算法用于调整模型参数以最小化损失函数。常见的优化算法包括梯度下降（Gradient Descent）、随机梯度下降（SGD）、Adam和RMSProp等。这些算法通过计算损失函数对网络参数的梯度，并沿着梯度下降的方向更新参数值，从而逐步逼近最优解。其中，Adam算法结合了动量法和RMSProp算法的优点，能够自适应地调整学习率并加快收敛速度。</p>
<h5>4. 激活函数</h5>
<p>激活函数是神经网络中引入非线性变换的关键组件。常见的激活函数包括Sigmoid、Tanh、ReLU（Rectified Linear Unit）和Leaky ReLU等。这些函数使得神经网络能够模拟复杂的非线性关系，从而具备更强的学习能力。ReLU函数因其简单性和有效性在深度学习中得到了广泛应用。</p>
<h4>三、深度学习算法的应用领域</h4>
<h5>1. 计算机视觉</h5>
<p>深度学习在计算机视觉领域取得了显著进展，特别是在图像识别、目标检测和图像分割等任务中。CNN作为该领域的核心算法之一，被广泛应用于智能手机、安防系统、自动驾驶等领域。</p>
<h5>2. 自然语言处理（NLP）</h5>
<p>NLP是深度学习的另一个重要应用领域。RNN及其变种LSTM和GRU在自然语言处理中表现出色，特别是在文本分类、情感分析、机器翻译和对话系统等方面。此外，基于Transformer的预训练模型（如BERT和GPT系列）的兴起进一步推动了NLP的发展。</p>
<h5>3. 语音识别</h5>
<p>深度学习在语音识别领域也取得了重要突破。通过端到端的深度学习模型（如基于Transformer的模型），语音识别的性能和鲁棒性得到了显著提升。这些模型能够自动从大量语音数据中学习特征表示并进行准确的识别。</p>
<h5>4. 其他领域</h5>
<p>除了上述领域外，深度学习还广泛应用于医疗健康、金融、教育、智能制造等多个领域。例如，在医疗健康领域，深度学习可以帮助医生进行疾病诊断、药物研发和患者管理等；在金融领域，深度学习可以应用于风险评估、欺诈检测和智能投顾等方面。</p>
<h4>四、深度学习算法的未来发展趋势</h4>
<h5>1. 模型效能的提升</h5>
<p>未来的深度学习模型将更加复杂和庞大，利用更多数据进行训练以提高模型的效能和泛化能力。模型的结构和参数优化将成为研究的热点之一。</p>
<h5>2. 自监督学习</h5>
<p>自监督学习是一种无监督学习的形式，通过模型自身生成标签进行学习。这一方法有望解决数据标注的问题并提高模型在数据稀缺领域的性能。</p>
<h5>3. 跨模态学习</h5>
<p>未来深度学习将更加注重跨模态学习，即在多种数据类型之间进行学习和知识迁移。这将使得模型能够更全面地理解世界并处理更加复杂的任务。</p>
<h5>4. 可解释性与鲁棒性</h5>
<p>随着深度学习应用的扩大对模型的解释性和鲁棒性需求日益增长。未来的研究将聚焦于如何使得深度学习模型更容易理解和解释，并提高其抵抗噪声数据和对抗攻击的能力。</p>
<p>综上所述，深度学习算法作为人工智能领域的重要分支正在不断发展壮大。通过不断创新和优化算法模型以及拓展应用领域，深度学习将为人类社会的各个领域带来更多便利和进步。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142166454</p>]]></description><guid isPermaLink="false">/archives/b9b8bbe7-13bd-4d76-9831-0c8169b2a670</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:42:11 GMT</pubDate></item><item><title><![CDATA[深入探索从ES6到ES2023]]></title><link>https://wiki.dgfygk.com/archives/d526e425-9859-4755-b61d-771bd866aae9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E6%8E%A2%E7%B4%A2%E4%BB%8EES6%E5%88%B0ES2023&amp;url=/archives/d526e425-9859-4755-b61d-771bd866aae9" width="1" height="1" alt="" style="opacity:0;">
<p>从ES6到ES2023，我们深入探索ECMAScript（简称ES）的演变与发展，了解这一JavaScript标准背后的技术革新和进步。ECMAScript作为JavaScript的标准化版本，每年都在不断推出新版本，为开发者带来更加丰富和强大的功能。本文将从ES6的引入开始，逐步介绍到最新的ES2023，同时探讨这些新特性对现代Web开发的影响。</p>
<h4>ECMAScript简介与JavaScript的关系</h4>
<p>ECMAScript，全称European Computer Manufacturers Association Script，是由欧洲计算机制造商协会（ECMA）制定的JavaScript语言的国际标准。ECMAScript为JavaScript提供了标准化的语法和语义，使得JavaScript能够在不同的浏览器和环境中保持一致的行为。换句话说，ECMAScript是JavaScript背后的"规矩”，它确保了JavaScript的跨平台兼容性。</p>
<h4>ES6（2015）的大改革</h4>
<p>ES6，也被称为ECMAScript 2015，是JavaScript发展历程中的一个重要里程碑。它引入了许多新特性，极大地提升了JavaScript的编程体验和表达能力。以下是一些ES6的核心新特性：</p>
<ol>
 <li>
  <p><strong>let和const</strong> ：
   <br>
   ES6引入了<code>let</code>和<code>const</code>关键字，用于声明变量和常量。与之前的<code>var</code>关键字相比，<code>let</code>声明的变量具有块级作用域，而<code>const</code>声明的常量则不能被重新赋值。这一改变使得JavaScript的变量声明更加安全，避免了变量泄露和覆盖的问题。</p>
 </li>
 <li>
  <p><strong>箭头函数</strong> ：
   <br>
   箭头函数是ES6中引入的一种更简洁的函数书写方式。它不仅语法更简洁，还自动绑定了其所在上下文的<code>this</code>值，解决了传统函数中<code>this</code>指向不明确的问题。例如：</p>
  <pre><code class="prism language-javascript">const sum = (x, y) =&gt; x + y;
console.log(sum(5, 10)); // 输出: 15
</code></pre>
 </li>
 <li>
  <p><strong>模板字符串</strong> ：
   <br>
   模板字符串允许在字符串中嵌入表达式，使用反引号（`）包围，并使用<code>${}</code>来包裹表达式。这一特性使得字符串的拼接和格式化变得更加方便和直观。例如：</p>
  <pre><code class="prism language-javascript">const name = "Alice";
const message = `Hello, ${
      name}!`;
console.log(message); // 输出: Hello, Alice!
</code></pre>
 </li>
</ol>
<h4>ES7至ES2023的持续进化</h4>
<p>自ES6之后，ECMAScript每年都会推出新的版本，每个版本都带来了一系列的新特性和改进。以下是一些从ES7到ES2023的重要新特性概览：</p>
<ol>
 <li>
  <p><strong>异步函数（Async/Await）</strong> ：
   <br>
   ES8（ECMAScript 2017）引入了异步函数，使得异步操作更加简洁和易于理解。通过<code>async</code>和<code>await</code>关键字，开发者可以以同步的方式编写异步代码，极大地简化了异步编程的复杂度。</p>
 </li>
 <li>
  <p><strong>对象展开运算符（Object Rest/Spread Properties）</strong> ：
   <br>
   ES9（ECMAScript 2018）引入了对象展开运算符，允许在对象字面量中通过<code>...</code>语法来复制可枚举属性。这一特性在对象合并和属性拷贝时非常有用。</p>
 </li>
 <li>
  <p><strong>Promise.finally()</strong> ：
   <br>
   ES2018还添加了<code>Promise.finally()</code>方法，该方法无论Promise对象最终状态如何，都会执行指定的回调函数。这对于在Promise链中执行清理操作非常有用。</p>
 </li>
 <li>
  <p><strong>可选链（Optional Chaining）</strong> ：
   <br>
   ES2020引入了可选链操作符<code>?.</code>，允许你安全地读取深层嵌套对象的属性，而无需显式地验证每一级是否存在。这减少了代码量，并提高了代码的可读性和健壮性。</p>
 </li>
 <li>
  <p><strong>空值合并运算符（Nullish Coalescing Operator）</strong> ：
   <br>
   同样是ES2020的一部分，空值合并运算符<code>??</code>允许你提供一个默认值，当左侧的表达式结果为<code>null</code>或<code>undefined</code>时，将使用默认值。这避免了使用逻辑或（<code>||</code>）运算符时可能引入的意外行为。</p>
 </li>
 <li>
  <p><strong>BigInt</strong> ：
   <br>
   ES2020还引入了<code>BigInt</code>类型，允许JavaScript处理大于<code>2^53 - 1</code>的整数。这对于处理大整数和加密操作等场景非常重要。</p>
 </li>
 <li>
  <p><strong>逻辑赋值运算符（Logical Assignment Operators）</strong> ：
   <br>
   ES2021引入了逻辑赋值运算符，如<code>&amp;&amp;=</code>、<code>||=</code>和<code>??=</code>，这些运算符结合了逻辑操作和赋值操作，使得代码更加简洁。</p>
 </li>
 <li>
  <p><strong>顶级await</strong> ：
   <br>
   ES2022允许在模块顶层使用<code>await</code>表达式，这使得在模块加载时能够等待异步操作完成。这对于需要在模块加载时加载数据的场景非常有用。</p>
 </li>
 <li>
  <p><strong>类字段和私有类字段</strong> ：
   <br>
   ES2022还引入了类字段和私有类字段的语法，使得在类中定义和访问字段变得更加直接和灵活。</p>
 </li>
 <li>
  <p><strong>错误处理改进</strong> ：
   <br>
   从ES2022开始，JavaScript在错误处理方面也有了改进，例如引入了<code>AggregateError</code>构造函数，用于处理多个错误作为单个错误的情况。</p>
 </li>
</ol>
<h4>ES2023及未来展望</h4>
<p>截至本文撰写时，ES2023的具体新特性尚未完全公布，但我们可以期待ECMAScript继续沿着其既定的发展路径前进，为JavaScript带来更加丰富的功能和更加健壮的语法。未来的ECMAScript版本可能会进一步简化异步编程、增强类型系统、提升性能，并引入更多与Web标准和现代Web开发相关的特性。</p>
<h4>浏览器支持情况</h4>
<p>值得庆幸的是，现代主流浏览器如Chrome、Firefox、Safari和Edge都积极跟进ECMAScript标准的更新，基本上已经支持到了最新的ES版本（包括ES2023及之前的版本）。这意味着开发者可以放心地使用这些新特性，而无需担心兼容性问题。当然，在实际开发中，仍然需要通过Babel等转译工具来确保代码在旧版浏览器中的兼容性。</p>
<h4>结语</h4>
<p>从ES6到ES2023，ECMAScript的每一次更新都带来了JavaScript编程语言的巨大进步。这些新特性不仅简化了代码编写，提高了开发效率，还增强了JavaScript的表达能力，使得它更加适合构建大型、复杂的Web应用。随着Web技术的不断发展，我们有理由相信，未来的ECMAScript将会为JavaScript带来更加广阔的应用前景和更加丰富的功能特性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142092033</p>]]></description><guid isPermaLink="false">/archives/d526e425-9859-4755-b61d-771bd866aae9</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sun, 29 Sep 2024 00:41:55 GMT</pubDate></item><item><title><![CDATA[掌握CentOS的入门必备基础知识]]></title><link>https://wiki.dgfygk.com/archives/c99432fb-7719-4f29-a40b-abc35e86c393</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8E%8C%E6%8F%A1CentOS%E7%9A%84%E5%85%A5%E9%97%A8%E5%BF%85%E5%A4%87%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86&amp;url=/archives/c99432fb-7719-4f29-a40b-abc35e86c393" width="1" height="1" alt="" style="opacity:0;">
<p>CentOS（Community Enterprise Operating System）是一个基于Red Hat Enterprise Linux（RHEL）的开源操作系统，广泛用于服务器和企业级计算环境。对于初学者来说，掌握CentOS的入门必备基础知识是非常重要的。以下将详细介绍CentOS的多个方面，包括文件管理、命令行操作、软件包管理、系统监控以及用户管理等，以帮助读者快速入门。</p>
<h4>一、文件与目录管理</h4>
<h5>1.1 目录结构</h5>
<p>CentOS的文件系统以树状结构组织，根目录（/）是所有文件和目录的起点。重要的目录包括：</p>
<ul>
 <li><code>/bin</code>：存放普通用户可执行的指令。</li>
 <li><code>/dev</code>：设备目录，所有的硬件设备及周边均放置在这里。</li>
 <li><code>/boot</code>：开机引导目录，包括Linux内核文件与开机所需要的文件。</li>
 <li><code>/home</code>：存放用户的个人数据，每个用户都有自己的用户目录，位置为<code>/home/用户名</code>（root用户除外）。</li>
 <li><code>/usr</code>：应用程序放置目录，包含用户程序、库文件等。</li>
 <li><code>/lib</code>：开机时常用的动态链接库，<code>bin</code>及<code>sbin</code>指令也会调用对应的<code>lib</code>库。</li>
 <li><code>/tmp</code>：临时文件存放目录。</li>
 <li><code>/etc</code>：各种配置文件目录，大部分配置属性均存放在这里。</li>
</ul>
<h5>1.2 常用命令</h5>
<ul>
 <li>
  <p><code>ls</code>：列出目录内容。</p>
 </li>
 <li>
  <p><code>ls -l</code>：显示详细列表。</p>
 </li>
 <li>
  <p><code>ls -a</code>：显示所有文件，包括隐藏文件。</p>
 </li>
 <li>
  <p><code>ls -F</code>：在文件或目录名后附加类型标识（如<code>/</code>表示目录）。</p>
 </li>
 <li>
  <p><code>cd</code>：切换目录。</p>
 </li>
 <li>
  <p><code>cd /</code>：切换到根目录。</p>
 </li>
 <li>
  <p><code>cd ~</code>：切换到用户主目录。</p>
 </li>
 <li>
  <p><code>cd ..</code>：切换到上一级目录。</p>
 </li>
 <li>
  <p><code>pwd</code>：显示当前工作目录的完整路径。</p>
 </li>
 <li>
  <p><code>mkdir</code>：创建新目录。</p>
 </li>
 <li>
  <p><code>mkdir dir1</code>：创建一个名为<code>dir1</code>的目录。</p>
 </li>
 <li>
  <p><code>mkdir -p /tmp/dir1/dir2</code>：递归创建目录树。</p>
 </li>
 <li>
  <p><code>rm</code>：删除文件或目录。</p>
 </li>
 <li>
  <p><code>rm file1</code>：删除名为<code>file1</code>的文件。</p>
 </li>
 <li>
  <p><code>rm -r dir1</code>：递归删除<code>dir1</code>目录及其内容。</p>
 </li>
 <li>
  <p><code>rm -f file1</code>：强制删除文件，不提示确认。</p>
 </li>
 <li>
  <p><code>cp</code>：复制文件或目录。</p>
 </li>
 <li>
  <p><code>cp file1 file2</code>：复制<code>file1</code>到<code>file2</code>。</p>
 </li>
 <li>
  <p><code>cp -r dir1 dir2</code>：递归复制目录。</p>
 </li>
 <li>
  <p><code>mv</code>：移动或重命名文件或目录。</p>
 </li>
 <li>
  <p><code>mv file1 new_file1</code>：重命名文件。</p>
 </li>
 <li>
  <p><code>mv dir1 new_dir1</code>：移动或重命名目录。</p>
 </li>
</ul>
<h4>二、命令行操作</h4>
<p>CentOS使用Bash作为默认的命令行界面，用户可以通过命令行执行各种系统管理任务。</p>
<h5>2.1 常用快捷键</h5>
<ul>
 <li><code>Tab</code>：自动补全命令或文件名。</li>
 <li><code>Ctrl + C</code>：中断当前命令。</li>
 <li><code>Ctrl + D</code>：退出当前会话。</li>
 <li><code>Ctrl + R</code>：搜索历史命令。</li>
</ul>
<h5>2.2 管道与重定向</h5>
<ul>
 <li>
  <p>管道符（<code>|</code>）：将一个命令的输出作为另一个命令的输入。</p>
 </li>
 <li>
  <p>示例：<code>ls /etc | grep pass*</code></p>
 </li>
 <li>
  <p>重定向：</p>
 </li>
 <li>
  <p><code>&gt;</code>：将命令的输出重定向到文件，覆盖原有内容。</p>
 </li>
 <li>
  <p><code>&gt;&gt;</code>：将命令的输出追加到文件末尾。</p>
 </li>
 <li>
  <p><code>&lt;</code>：将文件的内容作为命令的输入。</p>
 </li>
</ul>
<h4>三、软件包管理</h4>
<p>CentOS提供了多种软件包管理工具，用于安装、更新、卸载软件包。</p>
<h5>3.1 yum</h5>
<p>yum是CentOS中常用的软件包管理器，它自动处理软件包之间的依赖关系。</p>
<ul>
 <li>安装软件包：<code>yum install 包名</code></li>
 <li>更新软件包：<code>yum update</code></li>
 <li>卸载软件包：<code>yum remove 包名</code></li>
 <li>搜索软件包：<code>yum search 关键字</code></li>
</ul>
<h5>3.2 rpm</h5>
<p>rpm是另一种软件包管理工具，它提供了更底层的软件包管理功能。</p>
<ul>
 <li>安装软件包：<code>rpm -ivh 包文件</code></li>
 <li>查询已安装的软件包：<code>rpm -qa</code></li>
 <li>卸载软件包：<code>rpm -e 包名</code></li>
</ul>
<h5>3.3 dnf</h5>
<p>在较新的CentOS版本中，dnf作为yum的替代品出现，它提供了更快的包管理和更多的特性。</p>
<ul>
 <li>安装软件包：<code>dnf install 包名</code></li>
 <li>其他操作与yum类似。</li>
</ul>
<h4>四、系统监控与管理</h4>
<h5>4.1 进程管理</h5>
<ul>
 <li>查看进程：<code>ps aux</code>、<code>ps -le</code>、<code>top</code>、<code>htop</code></li>
 <li>杀死进程：<code>kill -9 PID</code></li>
 <li>修改进程优先级：<code>nice</code>命令用于给新执行的命令指定优先级，<code>renice</code>命令用于修改已存在进程的优先级。</li>
</ul>
<h5>4.2 系统资源监控</h5>
<ul>
 <li><code>free</code>：查看内存使用情况。</li>
 <li><code>df -h</code>：查看磁盘空间使用情况。</li>
 <li><code>du -sh 目录名</code>：估算指定目录的磁盘使用空间。</li>
 <li><code>iostat</code>：查看CPU和磁盘I/O统计信息。</li>
</ul>
<h4>五、用户与权限管理</h4>
<h5>5.1 用户管理</h5>
<ul>
 <li>创建用户：<code>useradd 用户名</code></li>
 <li>删除用户：<code>userdel -r 用户名</code></li>
 <li>修改用户密码：<code>passwd 用户名</code></li>
 <li>切换用户：<code>su 用户名</code></li>
</ul>
<h5>5.2 权限管理</h5>
<ul>
 <li>文件权限：使用<code>chmod</code>命令修改文件或目录的权限。</li>
 <li>用户组：使用<code>groupadd</code>、<code>groupdel</code>、<code>usermod</code>等命令管理用户组。</li>
</ul>
<h4>六、网络配置与管理</h4>
<h5>6.1 网络接口配置</h5>
<ul>
 <li>使用<code>ifconfig</code>命令查看和配置网络接口（在较新的CentOS版本中可能已被<code>ip</code>命令替代）。</li>
 <li><code>ip addr show</code>：显示网络接口信息。</li>
 <li><code>ip link set dev eth0 up</code>：激活网络接口。</li>
</ul>
<h5>6.2 网络服务管理</h5>
<ul>
 <li>使用<code>systemctl</code>命令管理服务（CentOS 7及更高版本）。</li>
 <li>启动服务：<code>systemctl start 服务名</code></li>
 <li>停止服务：<code>systemctl stop 服务名</code></li>
 <li>查看服务状态：<code>systemctl status 服务名</code></li>
</ul>
<h4>七、系统安全与维护</h4>
<h5>7.1 防火墙配置</h5>
<ul>
 <li>CentOS使用firewalld作为默认的防火墙管理工具。</li>
 <li>使用<code>firewall-cmd</code>命令配置防火墙规则。</li>
</ul>
<h5>7.2 SELinux配置</h5>
<ul>
 <li>SELinux（Security-Enhanced Linux）提供了额外的安全策略。</li>
 <li>使用<code>sestatus</code>命令查看SELinux状态。</li>
 <li>使用<code>setenforce</code>命令切换SELinux模式（enforcing、permissive、disabled）。</li>
</ul>
<h5>7.3 系统更新与补丁</h5>
<ul>
 <li>定期使用<code>yum update</code>命令更新系统软件包，以保持系统安全。</li>
 <li>关注CentOS官方公告，及时应用安全补丁。</li>
</ul>
<h4>八、系统启动与关机</h4>
<h5>8.1 关机与重启</h5>
<ul>
 <li>关机：<code>shutdown -h now</code>、<code>init 0</code>、<code>telinit 0</code></li>
 <li>重启：<code>shutdown -r now</code>、<code>reboot</code></li>
</ul>
<h5>8.2 系统启动流程</h5>
<ul>
 <li>了解CentOS的启动流程，包括GRUB引导加载器、内核加载、init进程启动等。</li>
 <li>学习如何编辑GRUB配置文件（通常位于<code>/etc/default/grub</code>或<code>/boot/grub2/grub.cfg</code>），以调整启动参数。</li>
</ul>
<h4>结语</h4>
<p>以上就是CentOS入门必备的基础知识。通过掌握这些内容，你将能够初步了解CentOS的文件系统结构、命令行操作、软件包管理、系统监控与管理、用户与权限管理、网络配置与管理、系统安全与维护以及系统启动与关机等方面的知识。当然，CentOS的功能远不止于此，随着你对系统的深入使用和学习，你将能够掌握更多高级特性和技巧。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142281297</p>]]></description><guid isPermaLink="false">/archives/c99432fb-7719-4f29-a40b-abc35e86c393</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sun, 29 Sep 2024 00:41:43 GMT</pubDate></item><item><title><![CDATA[Docker部署Doris数据库]]></title><link>https://wiki.dgfygk.com/archives/59e1d8a7-5a51-4db4-ab03-8b23c15f57d4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Docker%E9%83%A8%E7%BD%B2Doris%E6%95%B0%E6%8D%AE%E5%BA%93&amp;url=/archives/59e1d8a7-5a51-4db4-ab03-8b23c15f57d4" width="1" height="1" alt="" style="opacity:0;">
<p>Docker部署Doris数据库是一个涉及多个步骤的过程，包括环境准备、Docker镜像拉取、配置文件创建、容器启动与配置、以及后续的数据库操作等。以下是一个详细的Docker部署Doris数据库的教程，旨在帮助用户成功在Docker环境中部署并运行Doris数据库。</p>
<h4>一、环境准备</h4>
<p>在部署Doris之前，需要确保你的系统已经安装了Docker。Docker是一个开源的容器化平台，可以帮助你轻松地部署和运行应用程序。你可以根据操作系统的要求和版本，从Docker官网下载并安装适当的Docker版本。</p>
<h4>二、Docker镜像拉取</h4>
<p>Doris提供了官方的Docker镜像，你可以在Docker Hub上找到并拉取这个镜像。使用以下命令拉取最新版本的Doris镜像：</p>
<pre><code class="prism language-bash">docker pull apache/doris:latest
</code></pre>
<p>如果你需要特定版本的Doris镜像，可以在<code>apache/doris</code>后面加上版本号，例如<code>apache/doris:1.2.4.1</code>。</p>
<h4>三、配置文件创建</h4>
<p>Doris在启动时需要配置文件来指定集群配置、数据库配置等。虽然Docker镜像可能已经包含了一些默认配置，但你可能需要根据自己的需求进行调整。</p>
<ol>
 <li>
  <p><strong>FE（Frontend）配置文件</strong> ：FE是Doris的前端节点，负责处理客户端的查询请求，并管理元数据。你需要创建一个<code>fe.conf</code>文件，并根据需求进行配置。配置文件可以包括网络配置、存储配置、Java环境配置等。</p>
 </li>
 <li>
  <p><strong>BE（Backend）配置文件</strong> ：BE是Doris的后端节点，负责数据存储和查询处理。同样，你需要创建一个<code>be.conf</code>文件，并根据需求进行配置。配置文件可以包括数据存储路径、网络配置、Java环境配置等。</p>
 </li>
</ol>
<p>请注意，配置文件的具体内容会根据Doris的版本和部署需求而有所不同。你可以参考Doris的官方文档来获取详细的配置选项和说明。</p>
<h4>四、启动Doris容器</h4>
<p>在准备好配置文件后，你可以使用Docker命令来启动Doris容器。以下是一个基本的启动命令示例，该命令将FE和BE作为单独的容器启动：</p>
<pre><code class="prism language-bash"># 启动FE容器
docker run -d --name doris-fe \
    -p 8030:8030 -p 9030:9030 \
    -v /path/to/doris/fe/conf:/opt/doris/fe/conf \
    -v /path/to/doris/fe/data:/opt/doris/fe/data \
    apache/doris:latest \
    /opt/doris/fe/bin/start_fe.sh --daemon

# 启动BE容器
docker run -d --privileged --name doris-be \
    -p 9050:9050 -p 9060:9060 \
    -v /path/to/doris/be/conf:/opt/doris/be/conf \
    -v /path/to/doris/be/data:/opt/doris/be/data \
    apache/doris:latest \
    /opt/doris/be/bin/start_be.sh --daemon
</code></pre>
<p>在这个命令中，<code>-d</code>参数表示在后台运行容器，<code>--name</code>参数指定了容器的名称，<code>-p</code>参数将容器内的端口映射到宿主机的端口上，<code>-v</code>参数将宿主机的目录挂载到容器内的指定目录上。请根据你的实际路径和端口需求进行调整。</p>
<h4>五、容器配置与启动后检查</h4>
<p>在容器启动后，你可能需要进行一些额外的配置，例如设置FE和BE之间的网络互通、调整Java环境变量等。这些配置通常需要在容器内部通过命令行进行。</p>
<p>你可以使用<code>docker exec</code>命令进入容器内部：</p>
<pre><code class="prism language-bash">docker exec -it doris-fe /bin/bash
</code></pre>
<p>然后，你可以根据需要修改配置文件或执行其他命令。</p>
<p>启动后，你应该检查Doris的各个组件是否正常运行。你可以通过访问FE的Web界面（通常是<code>http://localhost:8030</code>，如果你是在本地部署的话）来查看集群状态和元数据信息。此外，你还可以通过执行SQL查询来测试Doris的数据处理能力。</p>
<h4>六、高级配置与集群管理</h4>
<p>如果你需要部署一个包含多个FE和BE节点的Doris集群，你需要进行更复杂的配置和管理。这包括设置集群的元数据复制、数据分区和分片、负载均衡等。</p>
<p>在Doris中，你可以通过FE的Web界面或命令行工具来管理集群。你可以添加或删除BE节点、调整集群配置、查看集群状态等。此外，Doris还提供了一套丰富的SQL语句来支持数据查询、数据定义和数据管理操作。</p>
<h4>七、常见问题与解决方案</h4>
<p>在部署和使用Doris时，你可能会遇到一些常见问题，例如容器无法启动、数据无法加载、查询性能不佳等。以下是一些可能的解决方案：</p>
<ol>
 <li>
  <p><strong>容器无法启动</strong>：检查Docker日志和Doris日志文件，查看是否有错误信息。确保你的配置文件没有错误，并且Docker镜像是最新的。</p>
 </li>
 <li>
  <p><strong>数据无法加载</strong>：检查数据源连接配置是否正确，确保数据源是可访问的。同时，检查Doris的BE节点是否有足够的磁盘空间和内存来存储数据。</p>
 </li>
 <li>
  <p><strong>查询性能不佳</strong>：优化查询语句，确保它们能够高效地利用Doris的索引和分区。此外，你还可以考虑增加BE节点的数量或提升节点的硬件配置来提高查询性能。</p>
 </li>
</ol>
<h4>八、总结</h4>
<p>Docker部署Doris数据库是一个涉及多个步骤的过程，包括环境准备、Docker镜像拉取、配置文件创建、容器启动与配置等。通过遵循上述步骤和注意事项，你可以成功在Docker环境中部署并运行Doris数据库。同时，你还需要关注Doris的官方文档和社区动态，以便及时了解最新的功能和最佳实践。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142092000</p>]]></description><guid isPermaLink="false">/archives/59e1d8a7-5a51-4db4-ab03-8b23c15f57d4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:41:27 GMT</pubDate></item><item><title><![CDATA[虚拟机中安装macOS系统]]></title><link>https://wiki.dgfygk.com/archives/6790a336-16c1-4b6c-ab27-c9a8cd0d3539</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%AD%E5%AE%89%E8%A3%85macOS%E7%B3%BB%E7%BB%9F&amp;url=/archives/6790a336-16c1-4b6c-ab27-c9a8cd0d3539" width="1" height="1" alt="" style="opacity:0;">
<p>虚拟机安装macOS系统是一个相对复杂但功能强大的过程，它允许用户在非macOS操作系统的计算机上运行macOS系统。以下是一个详细的步骤指南，帮助用户通过虚拟机（如VMware Workstation）安装macOS系统。</p>
<h4>一、准备工作</h4>
<h5>1. 系统环境要求</h5>
<ul>
 <li><strong>宿主机操作系统</strong>：Windows 10及以上版本，因为较老的操作系统可能不支持最新的虚拟机软件或macOS版本。</li>
 <li><strong>虚拟机软件</strong>：VMware Workstation Pro，建议使用最新版本以获得最佳兼容性和性能。</li>
 <li><strong>macOS系统映像</strong>：获取合法的macOS系统安装映像（ISO或CDR文件），这通常需要通过Apple的开发者计划或其他合法渠道获得。</li>
 <li><strong>解锁工具</strong>：由于VMware默认不支持macOS的安装，因此需要使用解锁工具（如Unlocker）来解锁VMware对macOS的支持。</li>
</ul>
<h5>2. BIOS/UEFI设置</h5>
<ul>
 <li>确保计算机的BIOS/UEFI设置中启用了虚拟化技术（如Intel VT-x或AMD-V）。</li>
 <li>根据计算机型号和品牌的不同，进入BIOS/UEFI的方法可能有所不同，通常是在计算机启动时按特定的键（如F2、Del或Esc）。</li>
</ul>
<h4>二、安装VMware Workstation</h4>
<ol>
 <li><strong>下载并安装VMware Workstation</strong>：从VMware官方网站下载最新版本的VMware Workstation Pro，并按照安装向导的指示完成安装。</li>
 <li><strong>注册并激活</strong>：使用有效的许可证密钥注册并激活VMware Workstation。</li>
</ol>
<h4>三、使用Unlocker解锁macOS支持</h4>
<ol>
 <li><strong>下载Unlocker</strong>：从可靠的来源下载适用于VMware Workstation版本的Unlocker。</li>
 <li><strong>解压并运行Unlocker</strong> ：解压下载的Unlocker文件，并右键点击其中的安装程序（如<code>win-install.cmd</code>），选择"以管理员身份运行”。</li>
 <li><strong>验证解锁</strong>：Unlocker运行完成后，可以通过创建一个新的虚拟机并检查是否有macOS的选项来验证是否成功解锁。</li>
</ol>
<h4>四、创建macOS虚拟机</h4>
<ol>
 <li><strong>打开VMware Workstation</strong>：启动VMware Workstation并点击"创建新的虚拟机”。</li>
 <li><strong>选择自定义安装</strong>：在创建虚拟机向导中，选择"自定义（高级）“选项以进行更详细的配置。</li>
 <li><strong>选择操作系统</strong>：在"选择操作系统"步骤中，选择"Apple Mac OS X"作为操作系统类型，并根据需要选择相应的macOS版本。</li>
 <li><strong>配置虚拟机硬件</strong>：根据宿主机的硬件资源（如CPU、内存和磁盘空间）来配置虚拟机的硬件设置。</li>
 <li><strong>创建虚拟磁盘</strong>：为虚拟机创建一个新的虚拟磁盘或选择一个现有的磁盘文件。</li>
 <li><strong>完成虚拟机创建</strong>：按照向导的指示完成虚拟机的创建过程。</li>
</ol>
<h4>五、安装macOS系统</h4>
<ol>
 <li><strong>加载系统映像</strong>：在虚拟机设置中，将macOS系统映像（ISO或CDR文件）加载到虚拟机的CD/DVD驱动器中。</li>
 <li><strong>启动虚拟机</strong>：启动虚拟机，并按照屏幕上的指示开始macOS的安装过程。</li>
 <li><strong>选择安装选项</strong>：在macOS安装界面中，选择适当的安装选项（如磁盘工具进行磁盘分区和格式化）。</li>
 <li><strong>安装macOS</strong>：选择安装位置并继续安装过程。安装过程中可能需要一些时间来完成文件的复制和系统的配置。</li>
 <li><strong>设置macOS</strong>：安装完成后，根据屏幕上的提示设置macOS的基本设置（如语言、地区、网络、用户账户等）。</li>
</ol>
<h4>六、安装VMware Tools</h4>
<ol>
 <li><strong>启动macOS虚拟机</strong>：确保macOS虚拟机正在运行。</li>
 <li><strong>安装VMware Tools</strong>：在VMware Workstation中，点击虚拟机菜单栏中的"虚拟机”-&gt;“安装VMware Tools”。</li>
 <li><strong>按照向导安装</strong>：在macOS虚拟机中，找到并打开VMware Tools的安装程序，并按照屏幕上的指示完成安装过程。</li>
</ol>
<h4>七、后续步骤</h4>
<ul>
 <li><strong>更新macOS</strong>：安装完成后，建议检查并更新macOS到最新版本以获得最佳的安全性和性能。</li>
 <li><strong>安装必要的软件</strong>：根据需要安装常用的软件、浏览器和工具等。</li>
 <li><strong>优化虚拟机性能</strong>：根据需要调整虚拟机的硬件设置（如CPU和内存分配）以优化性能。</li>
</ul>
<h4>八、注意事项</h4>
<ul>
 <li><strong>合法性</strong>：确保你拥有合法的macOS系统映像和VMware Workstation许可证。</li>
 <li><strong>兼容性</strong>：不同版本的macOS和VMware Workstation之间可能存在兼容性问题。请确保你使用的版本相互兼容。</li>
 <li><strong>备份数据</strong>：在安装过程中，请务必备份重要数据以防止数据丢失。</li>
</ul>
<p>通过以上步骤，你可以在虚拟机中成功安装并运行macOS系统。这将为你提供一个灵活的环境来测试、开发和运行macOS应用程序，而无需购买额外的Mac硬件。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142052949</p>]]></description><guid isPermaLink="false">/archives/6790a336-16c1-4b6c-ab27-c9a8cd0d3539</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sun, 29 Sep 2024 00:40:52 GMT</pubDate></item><item><title><![CDATA[Arduino IDE安装]]></title><link>https://wiki.dgfygk.com/archives/ac6e4287-f1d2-4fab-8400-a55312a44824</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Arduino%20IDE%E5%AE%89%E8%A3%85&amp;url=/archives/ac6e4287-f1d2-4fab-8400-a55312a44824" width="1" height="1" alt="" style="opacity:0;">
<p>Arduino IDE（集成开发环境）的安装过程是一个相对直观且易于操作的流程，主要步骤包括下载、安装和配置。以下将详细阐述Arduino IDE的安装过程，同时提供一些背景信息和注意事项，确保安装过程顺利进行。</p>
<h4>一、Arduino IDE下载</h4>
<p><strong>1. 访问Arduino官网</strong></p>
<p>首先，用户需要访问Arduino的官方网站（<a href="https://www.arduino.cc">https://www.arduino.cc</a>），这是获取Arduino IDE最新版本的官方渠道。Arduino官网提供了丰富的资源和支持，确保用户能够获取到最稳定、最新的开发环境。</p>
<p><strong>2. 选择合适的下载版本</strong></p>
<p>在Arduino官网的"Software"或"下载"页面，用户可以看到不同操作系统的Arduino IDE版本。由于Arduino IDE支持多种操作系统，包括Windows、macOS和Linux，用户需要根据自己的操作系统选择合适的版本进行下载。对于Windows用户，可以选择”.exe"后缀的安装包；对于macOS用户，则选择”.dmg"后缀的安装包；Linux用户则可能需要从源码编译或使用适用于其发行版的软件包管理工具进行安装。</p>
<p><strong>3. 开始下载</strong></p>
<p>选定合适的版本后，用户只需点击"DOWNLOAD"按钮即可开始下载Arduino IDE安装包。部分情况下，官网可能会要求用户填写邮箱地址或进行捐赠，但这些都是可选步骤，用户也可以直接选择"JUST DOWNLOAD"来跳过这些步骤，直接下载安装包。</p>
<h4>二、Arduino IDE安装</h4>
<p><strong>1. 解压安装包（仅限部分用户）</strong></p>
<p>对于Linux用户或需要手动管理安装包的Windows/macOS用户，可能需要先解压下载的安装包。解压后，通常会得到一个可执行文件或安装程序。</p>
<p><strong>2. 运行安装程序</strong></p>
<p>对于Windows和macOS用户，双击下载的安装包（.exe或.dmg文件）即可开始安装过程。安装程序会引导用户完成后续步骤。</p>
<p><strong>3. 接受许可协议</strong></p>
<p>在安装过程中，用户需要接受Arduino IDE的许可协议。这是使用Arduino IDE的必要条件，用户应仔细阅读协议内容，并确保同意后方可继续安装。</p>
<p><strong>4. 选择安装位置</strong></p>
<p>在安装过程中，用户可以选择Arduino IDE的安装位置。建议用户选择非系统盘（如D盘、E盘等）进行安装，以避免占用系统盘空间并降低系统运行风险。</p>
<p><strong>5. 完成安装</strong></p>
<p>按照安装程序的提示完成剩余步骤后，Arduino IDE将被成功安装到用户的计算机上。安装完成后，用户可以在桌面或开始菜单中找到Arduino IDE的快捷方式，双击即可启动。</p>
<h4>三、Arduino IDE配置</h4>
<p><strong>1. 语言设置</strong></p>
<p>首次启动Arduino IDE时，其界面语言可能默认为英文。用户可以通过点击菜单栏中的"File”-&gt;“Preferences"选项来设置语言为中文（简体）。在"Preferences"窗口中，找到"Language"选项并选择"中文（简体）“后，点击"OK"按钮即可完成语言设置。</p>
<p><strong>2. 开发板管理器</strong></p>
<p>Arduino IDE支持多种开发板，用户可以通过开发板管理器来安装和管理这些开发板的支持库。点击菜单栏中的"工具”-&gt;“开发板”-&gt;“开发板管理器"选项，可以打开开发板管理器窗口。在窗口中，用户可以搜索并安装自己需要的开发板支持库。</p>
<p><strong>3. 端口设置</strong></p>
<p>在上传程序到Arduino开发板之前，用户需要确保Arduino IDE能够正确识别并连接到开发板。这通常涉及到端口设置。点击菜单栏中的"工具”-&gt;“端口"选项，用户可以看到当前计算机上所有可用的串行端口。选择与开发板对应的端口后，Arduino IDE就可以与开发板进行通信了。</p>
<p><strong>4. 其他设置</strong></p>
<p>除了上述设置外，用户还可以根据自己的需要调整Arduino IDE的其他设置，如项目文件夹位置、编译器字体大小、IDE主题颜色等。这些设置都可以在"Preferences"窗口中进行调整。</p>
<h4>四、注意事项</h4>
<p><strong>1. 安装JDK（仅限部分用户）</strong></p>
<p>需要注意的是，Arduino IDE依赖于Java开发环境。对于某些版本的Arduino IDE或操作系统配置，用户可能需要事先安装Java开发工具包（JDK）。然而，在大多数情况下，Arduino IDE安装包已经包含了所需的Java运行时环境（JRE），用户无需单独安装JDK。</p>
<p><strong>2. 更新Arduino IDE</strong></p>
<p>随着Arduino平台和硬件的不断发展，Arduino IDE也会不断更新以支持新的功能和修复已知问题。因此，建议用户定期检查并更新Arduino IDE到最新版本。更新过程通常非常简单，用户只需在Arduino IDE中选择"帮助”-&gt;“检查更新"选项即可。</p>
<p><strong>3. 兼容性问题</strong></p>
<p>虽然Arduino IDE努力保持对多种操作系统和开发板的兼容性，但在某些情况下仍可能出现兼容性问题。如果遇到无法解决的问题，建议用户查阅Arduino官方文档或社区论坛以获取帮助。</p>
<p><strong>4. 安全提示</strong></p>
<p>在下载和安装Arduino IDE时，请确保从官方渠道下载安装包，并仔细检查安装包的大小和校验码等信息以防止下载到恶意软件。此外，在安装过程中请留意安装程序是否有捆绑其他不必要的软件或更改系统设置的行为。</p>
<p>综上所述，Arduino IDE的安装过程相对简单直观，只需按照官方提供的步骤进行操作即可。在安装完成后，用户还可以根据自己的需要进行相应的配置和设置以优化开发环境。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141925319</p>]]></description><guid isPermaLink="false">/archives/ac6e4287-f1d2-4fab-8400-a55312a44824</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sun, 29 Sep 2024 00:40:13 GMT</pubDate></item><item><title><![CDATA[Docker 容器技术在简化 MySQL 主从复制部署与优化]]></title><link>https://wiki.dgfygk.com/archives/39da81b0-5446-4f48-82af-5665d5715377</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Docker%20%E5%AE%B9%E5%99%A8%E6%8A%80%E6%9C%AF%E5%9C%A8%E7%AE%80%E5%8C%96%20MySQL%20%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E9%83%A8%E7%BD%B2%E4%B8%8E%E4%BC%98%E5%8C%96&amp;url=/archives/39da81b0-5446-4f48-82af-5665d5715377" width="1" height="1" alt="" style="opacity:0;">
<p>Docker 容器技术在简化 MySQL 主从复制部署与优化方面发挥了重要作用。以下是对这一过程的详细阐述：</p>
<h4>一、Docker 容器技术简介</h4>
<p>Docker 容器技术通过打包应用程序及其依赖到一个轻量级、可移植的容器中，使得应用的部署和运行变得更加简单和高效。对于数据库如 MySQL，Docker 同样能够极大地简化其部署和管理过程。</p>
<h4>二、MySQL 主从复制简介</h4>
<p>MySQL 主从复制是一种数据同步技术，允许数据从一个 MySQL 数据库服务器（主服务器）复制到一个或多个 MySQL 数据库服务器（从服务器）。这种复制功能通常用于备份、故障恢复、读取扩展以及数据分析等场景。</p>
<h4>三、Docker 部署 MySQL 主从复制的步骤</h4>
<h5>1. 环境准备</h5>
<ul>
 <li>
  <p><strong>创建 MySQL 主库镜像</strong> ：
   <br>
   使用 Docker 命令创建 MySQL 主库容器，并设置必要的环境变量（如 <code>MYSQL_ROOT_PASSWORD</code>）和挂载目录（如日志、数据、配置文件等）。</p>
 </li>
 <li>
  <p><strong>配置主库</strong> ：
   <br>
   在主库容器中创建配置文件（如 <code>my.cnf</code>），设置 <code>server-id</code>（确保唯一）、开启二进制日志（<code>log-bin</code>）、指定不需要同步的数据库（<code>binlog-ignore-db</code>）等。</p>
 </li>
 <li>
  <p><strong>创建 MySQL 从库镜像</strong> ：
   <br>
   步骤与创建主库类似，但注意要修改 <code>server-id</code> 和其他必要的配置，以区分从库。</p>
 </li>
</ul>
<h5>2. 搭建主从复制</h5>
<ul>
 <li>
  <p><strong>主库设置</strong> ：
   <br>
   进入主库容器，创建用于数据同步的用户并授权。然后，查询主库的状态，记录下二进制日志文件名和位置，这些信息将在配置从库时使用。</p>
 </li>
 <li>
  <p><strong>从库配置</strong> ：
   <br>
   进入从库容器，使用 <code>CHANGE MASTER TO</code> 语句配置从库以连接到主库。需要指定主库的 IP 地址、端口、用户、密码、二进制日志文件名和位置等信息。</p>
 </li>
 <li>
  <p><strong>启动从库复制</strong> ：
   <br>
   在从库上执行 <code>START SLAVE;</code> 命令启动复制过程，并通过 <code>SHOW SLAVE STATUS\G;</code> 命令查看从库的状态，确保复制正常运行。</p>
 </li>
</ul>
<h4>四、优化建议</h4>
<ul>
 <li>
  <p><strong>网络优化</strong> ：
   <br>
   确保 Docker 容器之间的网络延迟尽可能低，可以通过优化网络配置或使用更快的网络硬件来实现。</p>
 </li>
 <li>
  <p><strong>数据一致性检查</strong> ：
   <br>
   定期检查主从库之间的数据一致性，避免数据不一致导致的错误。</p>
 </li>
 <li>
  <p><strong>日志管理</strong> ：
   <br>
   合理配置和管理二进制日志和中继日志，确保不会因日志过大而影响系统性能。</p>
 </li>
 <li>
  <p><strong>资源分配</strong> ：
   <br>
   根据实际需求合理分配 Docker 容器的 CPU 和内存资源，确保 MySQL 服务的稳定运行。</p>
 </li>
 <li>
  <p><strong>备份与恢复</strong> ：
   <br>
   定期备份 MySQL 数据，并测试恢复流程，确保在出现故障时能够迅速恢复服务。</p>
 </li>
 <li>
  <p><strong>监控与告警</strong> ：
   <br>
   实施监控策略，对 MySQL 服务的性能指标进行实时监控，并在出现异常情况时及时发出告警。</p>
 </li>
</ul>
<h4>五、总结</h4>
<p>Docker 容器技术为 MySQL 主从复制的部署与优化提供了极大的便利。通过合理使用 Docker 容器和配置优化策略，可以显著提高 MySQL 数据库的可用性和性能。同时，也需要注意网络、数据一致性、日志管理、资源分配、备份恢复以及监控告警等方面的优化工作，以确保 MySQL 服务的稳定运行。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141963517</p>]]></description><guid isPermaLink="false">/archives/39da81b0-5446-4f48-82af-5665d5715377</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:39:53 GMT</pubDate></item><item><title><![CDATA[Docker进入容器运行命令]]></title><link>https://wiki.dgfygk.com/archives/1933b92f-d537-43eb-9332-e2f5752e0590</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Docker%E8%BF%9B%E5%85%A5%E5%AE%B9%E5%99%A8%E8%BF%90%E8%A1%8C%E5%91%BD%E4%BB%A4&amp;url=/archives/1933b92f-d537-43eb-9332-e2f5752e0590" width="1" height="1" alt="" style="opacity:0;">
<p>Docker 是一种开源的容器化平台，它允许开发者打包他们的应用以及依赖包到一个可移植的容器中，然后发布到任何流行的 Linux 机器上，也可以实现虚拟化。容器是完全使用沙箱机制，相互之间不会有任何接口（类似 iPhone 的 app）。更重要的是，容器性能开销极低。Docker 容器可以在任何安装了 Docker 的系统上运行，这使得应用的部署变得前所未有的简单。</p>
<p>进入 Docker 容器内部运行命令是 Docker 使用中的一个常见需求，主要用于调试、查看日志、执行临时任务等。Docker 提供了几种不同的方式来进入容器内部，每种方式都有其适用场景。下面将详细介绍几种常用的方法，并探讨它们的使用场景和优缺点。</p>
<h4>1. 使用 <code>docker exec</code> 命令</h4>
<p><code>docker exec</code> 命令是 Docker 提供的一个非常强大的工具，它允许你在运行的容器内部执行命令。这是进入容器内部执行命令的首选方法，因为它不会改变容器的 PID（进程ID）命名空间，也不会启动一个新的 shell 进程。</p>
<h5>基本用法</h5>
<pre><code class="prism language-bash">docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
</code></pre>
<ul>
 <li><code>OPTIONS</code>：可以指定一些选项，比如 <code>-it</code> 用于分配一个伪终端（pseudo-TTY）并保持 STDIN 开放，这样你就可以像在本地终端中一样与容器交互。</li>
 <li><code>CONTAINER</code>：要执行命令的容器ID或名称。</li>
 <li><code>COMMAND</code>：要在容器内部执行的命令。</li>
</ul>
<h5>示例</h5>
<p>假设你有一个名为 <code>my_container</code> 的容器正在运行，你想进入该容器内部并启动一个 bash shell：</p>
<pre><code class="prism language-bash">docker exec -it my_container bash
</code></pre>
<p>如果容器内部没有 bash，你可以尝试使用 <code>sh</code> 或其他可用的 shell。</p>
<h5>优点</h5>
<ul>
 <li>不会改变容器的 PID 命名空间。</li>
 <li>可以执行任何命令，而不仅仅是启动 shell。</li>
 <li>适用于需要临时访问容器内部的情况。</li>
</ul>
<h5>缺点</h5>
<ul>
 <li>需要容器内部有可用的 shell。</li>
 <li>如果容器没有运行前台进程（如 web 服务器），则可能需要额外的配置来保持容器运行。</li>
</ul>
<h4>2. 使用 <code>docker attach</code> 命令</h4>
<p><code>docker attach</code> 命令允许你附加到一个正在运行的容器的标准输入、输出和错误流。然而，需要注意的是，<code>docker attach</code> 主要用于查看容器的输出或向容器发送输入，而不是用于执行命令。</p>
<h5>基本用法</h5>
<pre><code class="prism language-bash">docker attach [OPTIONS] CONTAINER
</code></pre>
<ul>
 <li><code>OPTIONS</code>：可以指定一些选项，但通常不需要。</li>
 <li><code>CONTAINER</code>：要附加的容器ID或名称。</li>
</ul>
<h5>示例</h5>
<pre><code class="prism language-bash">docker attach my_container
</code></pre>
<h5>注意事项</h5>
<ul>
 <li>如果容器没有在前台运行任何进程（如 <code>tail -f /dev/null</code>），则 <code>docker attach</code> 可能会立即退出。</li>
 <li><code>docker attach</code> 会接管容器的标准输入，因此如果你只是想查看容器的输出，可能需要使用其他方法（如 <code>docker logs</code>）。</li>
 <li><code>docker attach</code> 不适合用于执行命令，因为它会附加到容器的当前进程，而不是启动新的进程。</li>
</ul>
<h4>3. 使用 <code>nsenter</code> 工具（非 Docker 原生）</h4>
<p><code>nsenter</code> 是一个用于在 Linux 命名空间中运行程序的工具，它可以用来进入 Docker 容器的命名空间。然而，需要注意的是，<code>nsenter</code> 不是 Docker 的一部分，也不是 Docker 官方推荐的方法。</p>
<h5>使用步骤</h5>
<ol>
 <li>
  <p>首先，你需要找到容器的 PID 命名空间。这可以通过查看容器的 <code>/proc/[pid]/ns/pid</code> 链接来实现，其中 <code>[pid]</code> 是容器的 PID（可以通过 <code>docker inspect</code> 命令获取）。</p>
 </li>
 <li>
  <p>然后，使用 <code>nsenter</code> 工具进入容器的 PID 命名空间。这通常涉及到一些复杂的命令行操作，因为你需要找到正确的 PID 命名空间文件，并将其作为 <code>nsenter</code> 的参数。</p>
 </li>
</ol>
<h5>注意事项</h5>
<ul>
 <li><code>nsenter</code> 不是 Docker 官方支持的工具，因此在使用时可能会遇到兼容性问题。</li>
 <li><code>nsenter</code> 的使用相对复杂，需要一定的 Linux 命名空间知识。</li>
 <li>由于 <code>nsenter</code> 不是 Docker 的一部分，因此它可能无法与 Docker 的未来版本完全兼容。</li>
</ul>
<h4>结论</h4>
<p>进入 Docker 容器内部运行命令是 Docker 使用中的一个常见需求。<code>docker exec</code> 是实现这一需求的首选方法，因为它简单、强大且易于使用。<code>docker attach</code> 主要用于查看容器的输出或向容器发送输入，而不是用于执行命令。而 <code>nsenter</code> 虽然可以实现类似的功能，但由于它不是 Docker 官方支持的工具，因此在使用时需要谨慎。</p>
<p>在选择进入容器的方法时，你应该根据你的具体需求来选择最合适的方法。如果你只是想临时进入容器内部执行一些命令或查看日志，那么 <code>docker exec</code> 是最好的选择。如果你需要持续监控容器的输出或与之交互，那么 <code>docker attach</code> 可能更适合你的需求。然而，在大多数情况下，<code>docker exec</code> 将是满足你需求的最简单、最直接的方法。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141963450</p>]]></description><guid isPermaLink="false">/archives/1933b92f-d537-43eb-9332-e2f5752e0590</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sun, 29 Sep 2024 00:39:39 GMT</pubDate></item><item><title><![CDATA[Linux中的Vim文本编辑器]]></title><link>https://wiki.dgfygk.com/archives/be32c15a-1aef-42a3-b5a5-e8cc2f36a1bd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E4%B8%AD%E7%9A%84Vim%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E5%99%A8&amp;url=/archives/be32c15a-1aef-42a3-b5a5-e8cc2f36a1bd" width="1" height="1" alt="" style="opacity:0;">
<p>Linux中的Vim是一个非常强大的文本编辑器，它提供了丰富的命令来支持各种文本编辑操作。以下是一个Vim常用命令的详细总结，涵盖了基本操作、编辑命令、移动光标、查找替换、保存退出等多个方面。</p>
<h4>一、基本操作</h4>
<ol>
 <li>
  <p><strong>启动Vim</strong></p>
 </li>
 <li>
  <p><code>vim</code>：直接启动Vim编辑器。</p>
 </li>
 <li>
  <p><code>vim filename</code>：打开或创建文件并启动Vim进行编辑。</p>
 </li>
 <li>
  <p><strong>模式切换</strong></p>
 </li>
 <li>
  <p>Vim有三种主要模式：正常模式（Normal Mode）、插入模式（Insert Mode）和命令行模式（Command-line Mode）。</p>
  <ul>
   <li>正常模式：默认模式，用于执行大多数命令。</li>
   <li>插入模式：用于输入文本。</li>
   <li>命令行模式：用于执行保存、退出等命令。</li>
  </ul>
 </li>
 <li>
  <p>切换模式：</p>
  <ul>
   <li>从正常模式到插入模式：<code>i</code>（在当前位置插入）、<code>I</code>（在当前行首插入）、<code>a</code>（在当前位置后插入）、<code>A</code>（在当前行尾插入）、<code>o</code>（在当前行下新开一行插入）等。</li>
   <li>从插入模式到正常模式：按<code>Esc</code>键。</li>
  </ul>
 </li>
</ol>
<h4>二、编辑命令</h4>
<ol>
 <li>
  <p><strong>复制和粘贴</strong></p>
 </li>
 <li>
  <p>复制：<code>yy</code>（复制当前行）、<code>y$</code>（复制至行尾）、<code>yw</code>（复制一个单词）、<code>yG</code>（复制到文件末尾）等。</p>
 </li>
 <li>
  <p>粘贴：<code>p</code>（在光标后粘贴）、<code>P</code>（在光标前粘贴）。</p>
 </li>
 <li>
  <p><strong>删除</strong></p>
 </li>
 <li>
  <p><code>x</code>：删除当前字符。</p>
 </li>
 <li>
  <p><code>X</code>：删除前一个字符（等价于<code>dh</code>）。</p>
 </li>
 <li>
  <p><code>dd</code>：删除当前行。</p>
 </li>
 <li>
  <p><code>D</code>或<code>d$</code>：删除从当前光标位置到行尾的所有字符。</p>
 </li>
 <li>
  <p><code>dw</code>：删除一个单词。</p>
 </li>
 <li>
  <p><strong>撤销和重做</strong></p>
 </li>
 <li>
  <p><code>u</code>：撤销上一次操作。</p>
 </li>
 <li>
  <p><code>Ctrl + r</code>：重做上一次撤销的操作。</p>
 </li>
</ol>
<h4>三、移动光标</h4>
<ol>
 <li>
  <p><strong>基本移动</strong></p>
 </li>
 <li>
  <p><code>h</code>：左移一个字符。</p>
 </li>
 <li>
  <p><code>j</code>：下移一行。</p>
 </li>
 <li>
  <p><code>k</code>：上移一行。</p>
 </li>
 <li>
  <p><code>l</code>：右移一个字符。</p>
 </li>
 <li>
  <p><strong>单词间移动</strong></p>
 </li>
 <li>
  <p><code>w</code>：向前移动到下一个单词的开头。</p>
 </li>
 <li>
  <p><code>e</code>：向前移动到下一个单词的末尾。</p>
 </li>
 <li>
  <p><code>b</code>：向后移动到上一个单词的开头。</p>
 </li>
 <li>
  <p><strong>行内移动</strong></p>
 </li>
 <li>
  <p><code>0</code>（数字零）：移动到行首。</p>
 </li>
 <li>
  <p><code>^</code>：移动到行首的第一个非空白字符。</p>
 </li>
 <li>
  <p><code>$</code>：移动到行尾。</p>
 </li>
 <li>
  <p><strong>页面移动</strong></p>
 </li>
 <li>
  <p><code>Ctrl + f</code>：向下滚动一页。</p>
 </li>
 <li>
  <p><code>Ctrl + b</code>：向上滚动一页。</p>
 </li>
 <li>
  <p><code>Ctrl + d</code>：向下滚动半页。</p>
 </li>
 <li>
  <p><code>Ctrl + u</code>：向上滚动半页。</p>
 </li>
 <li>
  <p><strong>文件内移动</strong></p>
 </li>
 <li>
  <p><code>gg</code>：移动到文件的第一行。</p>
 </li>
 <li>
  <p><code>G</code>：移动到文件的最后一行。</p>
 </li>
 <li>
  <p><code>:n</code>（n为行号）：移动到文件的第n行。</p>
 </li>
</ol>
<h4>四、查找和替换</h4>
<ol>
 <li>
  <p><strong>查找</strong></p>
 </li>
 <li>
  <p><code>/text</code>：向前查找text。</p>
 </li>
 <li>
  <p><code>?text</code>：向后查找text。</p>
 </li>
 <li>
  <p><code>n</code>：查找下一个匹配项。</p>
 </li>
 <li>
  <p><code>N</code>：查找上一个匹配项。</p>
 </li>
 <li>
  <p><strong>替换</strong></p>
 </li>
 <li>
  <p><code>:s/old/new/</code>：将当前行的第一个old替换为new。</p>
 </li>
 <li>
  <p><code>:s/old/new/g</code>：将当前行的所有old替换为new。</p>
 </li>
 <li>
  <p><code>:%s/old/new/g</code>：将文件中的所有old替换为new。</p>
 </li>
</ol>
<h4>五、保存和退出</h4>
<ol>
 <li>
  <p><strong>保存</strong></p>
 </li>
 <li>
  <p><code>:w</code>：保存当前文件。</p>
 </li>
 <li>
  <p><code>:w filename</code>：将当前文件另存为filename。</p>
 </li>
 <li>
  <p><strong>退出</strong></p>
 </li>
 <li>
  <p><code>:q</code>：如果文件未修改，则退出Vim。</p>
 </li>
 <li>
  <p><code>:q!</code>：强制退出Vim，不保存任何修改。</p>
 </li>
 <li>
  <p><code>:wq</code>或<code>ZZ</code>：保存并退出Vim。</p>
 </li>
</ol>
<h4>六、其他常用命令</h4>
<ol>
 <li>
  <p><strong>分屏操作</strong></p>
 </li>
 <li>
  <p><code>:split</code>或<code>:sp</code>：水平分割窗口。</p>
 </li>
 <li>
  <p><code>:vsplit</code>或<code>:vsp</code>：垂直分割窗口。</p>
 </li>
 <li>
  <p><code>Ctrl + w + w</code>：在分屏间切换。</p>
 </li>
 <li>
  <p><strong>设置</strong></p>
 </li>
 <li>
  <p><code>:set nu</code>：显示行号。</p>
 </li>
 <li>
  <p><code>:set nonu</code>：不显示行号。</p>
 </li>
 <li>
  <p><code>:set hlsearch</code>：高亮显示搜索结果。</p>
 </li>
 <li>
  <p><code>:set nohlsearch</code>：关闭高亮显示。</p>
 </li>
 <li>
  <p><strong>录制宏</strong></p>
 </li>
 <li>
  <p><code>qa</code>：开始录制宏a。</p>
 </li>
 <li>
  <p>…（执行一系列命令）</p>
 </li>
 <li>
  <p><code>q</code>：结束录制。</p>
 </li>
 <li>
  <p><code>@a</code>：执行宏a。</p>
 </li>
 <li>
  <p><strong>执行外部命令</strong></p>
 </li>
 <li>
  <p><code>:!command</code>：在Vim中执行外部命令。</p>
 </li>
</ol>
<h4>七、高级技巧</h4>
<ol>
 <li>
  <p><strong>多文件编辑</strong></p>
 </li>
 <li>
  <p><code>vim file1 file2</code>：同时打开多个文件。</p>
 </li>
 <li>
  <p><code>:next</code>或<code>:n</code>：切换到下一个文件。</p>
 </li>
 <li>
  <p><code>:prev</code>或<code>:p</code>：切换到上一个文件。</p>
 </li>
 <li>
  <p><code>:first</code>：切换到第一个文件。</p>
 </li>
 <li>
  <p><code>:last</code>：切换到最后一个文件。</p>
 </li>
 <li>
  <p><strong>文本对象操作</strong></p>
 </li>
 <li>
  <p>Vim支持多种文本对象，如<code>iw</code>（内部单词）、<code>aw</code>（一个单词）、<code>ip</code>（内部段落）等，可以与删除、复制等命令结合使用。</p>
 </li>
 <li>
  <p><strong>可视模式</strong></p>
 </li>
 <li>
  <p>在正常模式下，按<code>v</code>进入可视模式，按<code>V</code>进入可视行模式，按<code>Ctrl + v</code>进入可视块模式。在可视模式下，可以选择文本块并执行删除、复制等操作。</p>
 </li>
</ol>
<p>以上就是Vim编辑器的一些常用命令和高级技巧。Vim的功能非常强大，这里只是列举了一部分常用的命令和技巧。通过不断的学习和实践，你可以逐渐掌握Vim的更多高级功能和技巧，从而提高你的文本编辑效率。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141983399</p>]]></description><guid isPermaLink="false">/archives/be32c15a-1aef-42a3-b5a5-e8cc2f36a1bd</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sun, 29 Sep 2024 00:39:24 GMT</pubDate></item><item><title><![CDATA[SpringBoot自动装配中的Condition机制]]></title><link>https://wiki.dgfygk.com/archives/09d567e4-630f-4ed3-8d80-2167d8953e49</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D%E4%B8%AD%E7%9A%84Condition%E6%9C%BA%E5%88%B6&amp;url=/archives/09d567e4-630f-4ed3-8d80-2167d8953e49" width="1" height="1" alt="" style="opacity:0;">
<h4>SpringBoot自动装配中的Condition机制</h4>
<p>在Spring Boot中，自动装配（Auto-Configuration）是一个核心特性，它极大地简化了Spring应用的配置过程。自动装配通过条件判断（Condition）机制，根据应用的环境和依赖来决定是否创建和配置Bean。这一机制在Spring 4.0中引入，主要通过<code>@Conditional</code>注解及其一系列变体实现。本文将详细探讨SpringBoot中的Condition机制，包括其定义、使用方式、内置条件注解以及自定义条件注解的实现。</p>
<h5>一、Condition机制概述</h5>
<p>Condition机制是Spring框架中用于条件化Bean创建和配置的一种机制。它允许开发者根据特定的条件（如类路径下的类是否存在、某个Bean是否存在、环境变量或配置属性的值等）来决定是否创建和注册Bean。这一机制通过<code>@Conditional</code>注解及其一系列变体实现，为Spring Boot的自动装配提供了强大的灵活性。</p>
<h5>二、@Conditional注解</h5>
<p><code>@Conditional</code>注解是Condition机制的核心，它定义在<code>org.springframework.context.annotation</code>包中。该注解可以放在类或方法上，用于指定一个或多个条件类（这些类必须实现<code>Condition</code>接口）。当Spring容器在创建Bean时，会检查这些条件类中的<code>matches</code>方法返回值，如果返回<code>true</code>，则继续Bean的创建过程；如果返回<code>false</code>，则跳过该Bean的创建。</p>
<p><code>@Conditional</code>注解的定义如下：</p>
<pre><code class="prism language-java">@Target({
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    Class&lt;? extends Condition&gt;[] value();
}
</code></pre>
<p>其中，<code>value</code>属性是一个<code>Condition</code>接口实现类的数组，用于指定一个或多个条件类。</p>
<h5>三、Condition接口</h5>
<p><code>Condition</code>接口定义在<code>org.springframework.context.annotation</code>包中，它是一个函数式接口，只包含一个<code>matches</code>方法。该方法的返回值决定了是否满足条件。</p>
<pre><code class="prism language-java">@FunctionalInterface
public interface Condition {

    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
</code></pre>
<ul>
 <li><code>ConditionContext</code>：提供了访问环境变量、类加载器、Bean定义注册表等信息的接口。</li>
 <li><code>AnnotatedTypeMetadata</code>：用于访问注解的元信息，如注解的属性值等。</li>
</ul>
<h5>四、内置条件注解</h5>
<p>Spring Boot提供了一系列内置的条件注解，这些注解可以直接在配置类或Bean声明的方法上使用，而无需手动实现<code>Condition</code>接口。这些内置注解包括：</p>
<ol>
 <li><strong>@ConditionalOnBean</strong>：当指定的Bean存在时。</li>
 <li><strong>@ConditionalOnMissingBean</strong>：当指定的Bean不存在时。</li>
 <li><strong>@ConditionalOnClass</strong>：当指定的类路径下的类存在时。</li>
 <li><strong>@ConditionalOnMissingClass</strong>：当指定的类路径下的类不存在时。</li>
 <li><strong>@ConditionalOnProperty</strong>：当指定的属性有指定的值时。</li>
 <li><strong>@ConditionalOnResource</strong>：当指定的资源存在时。</li>
 <li><strong>@ConditionalOnWebApplication</strong>：当项目是一个Web应用程序时。</li>
 <li><strong>@ConditionalOnNotWebApplication</strong>：当项目不是一个Web应用程序时。</li>
</ol>
<p>这些内置注解极大地简化了条件判断的逻辑，使得开发者可以更加专注于业务逻辑的实现。</p>
<h5>五、自定义条件注解</h5>
<p>虽然Spring Boot提供了丰富的内置条件注解，但在某些情况下，开发者可能需要根据特定的业务逻辑来定义自己的条件注解。自定义条件注解通常包括以下几个步骤：</p>
<ol>
 <li><strong>定义注解</strong> ：创建一个新的注解，并使用<code>@Conditional</code>注解来指定条件类。</li>
</ol>
<pre><code class="prism language-java">@Target({
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(CustomCondition.class)
public @interface ConditionalOnCustom {

    // 可以定义一些属性，用于传递条件判断所需的信息
}
</code></pre>
<ol>
 <li><strong>实现Condition接口</strong> ：创建一个实现了<code>Condition</code>接口的类，并重写<code>matches</code>方法来实现自定义的条件判断逻辑。</li>
</ol>
<pre><code class="prism language-java">public class CustomCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        // 实现自定义的条件判断逻辑
        // 例如，检查某个类是否存在，或者某个环境变量的值是否满足特定条件
        return true; // 或者 false
    }
}
</code></pre>
<ol>
 <li><strong>使用自定义注解</strong>：在配置类或Bean声明的方法上使用自定义注解。</li>
</ol>
<pre><code class="prism language-java">@Configuration
public class MyConfig {


    @Bean
    @ConditionalOnCustom
    public MyBean myBean() {

        return new MyBean();
    }
}
</code></pre>
<h5>六、示例</h5>
<p>假设我们有一个需求：当项目中引入了Jedis依赖时，才创建一个Redis相关的Bean。我们可以通过自定义条件注解来实现这一需求。</p>
<ol>
 <li><strong>定义注解</strong>：</li>
</ol>
<pre><code class="prism language-java">@Target({
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJedisClassCondition.class)
public @interface ConditionalOnJedis {

}
</code></pre>
<ol>
 <li><strong>实现Condition接口</strong>：</li>
</ol>
<pre><code class="prism language-java">public class OnJedisClassCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        try {

            Class.forName("redis.clients.jedis.Jedis");
            return true;
        } catch (ClassNotFoundException e) {

            return false;
        }
    }
}
</code></pre>
<ol>
 <li><strong>使用注解</strong>：</li>
</ol>
<pre><code class="prism language-java">@Configuration
public class RedisConfig {


    @Bean
    @ConditionalOnJedis
    public RedisTemplate&lt;String, Object&gt; redisTemplate(RedisConnectionFactory connectionFactory) {

        // 配置RedisTemplate
        return new RedisTemplate&lt;&gt;();
    }
}
</code></pre>
<p>在这个例子中，当项目中引入了Jedis依赖时，<code>OnJedisClassCondition</code>的<code>matches</code>方法会返回<code>true</code>，从而触发<code>redisTemplate</code> Bean的创建。如果没有引入Jedis依赖，则不会创建该Bean。</p>
<h5>七、总结</h5>
<p>SpringBoot的自动装配通过Condition机制提供了强大的灵活性，允许开发者根据特定的条件来决定是否创建和配置Bean。通过内置条件注解和自定义条件注解，开发者可以轻松地实现复杂的条件判断逻辑，从而优化应用的配置和启动过程。在实际开发中，合理利用Condition机制可以显著提高开发效率和应用的可维护性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142052916</p>]]></description><guid isPermaLink="false">/archives/09d567e4-630f-4ed3-8d80-2167d8953e49</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sun, 29 Sep 2024 00:38:34 GMT</pubDate></item><item><title><![CDATA[uni-app开发微信小程序]]></title><link>https://wiki.dgfygk.com/archives/1710ef54-68b0-4d87-b61b-2645920877a1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=uni-app%E5%BC%80%E5%8F%91%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F&amp;url=/archives/1710ef54-68b0-4d87-b61b-2645920877a1" width="1" height="1" alt="" style="opacity:0;">
<p>uni-app 是一个使用 Vue.js 开发所有前端应用的框架，它允许开发者编写一次代码，然后发布到iOS、Android、Web（包括各种小程序平台如微信小程序、支付宝小程序、百度智能小程序等）以及各种快应用平台上。对于使用uni-app开发微信小程序来说，这一框架极大地简化了跨平台开发的复杂性，让开发者能够专注于业务逻辑和用户体验的实现。以下是对uni-app开发微信小程序的详细探讨，包括环境搭建、项目结构、开发流程、性能优化、调试与发布等多个方面。</p>
<h4>一、环境搭建</h4>
<h5>1. 安装HBuilderX</h5>
<p>HBuilderX 是 DCloud 官方推出的一款前端开发工具，专为uni-app设计，提供了代码高亮、代码提示、真机调试、一键编译等功能。开发者首先需要下载并安装HBuilderX。</p>
<h5>2. 配置微信开发者工具</h5>
<p>虽然uni-app支持在HBuilderX中直接预览和调试微信小程序，但为了更深入地了解小程序在微信平台上的表现，以及使用微信开发者工具提供的特定功能（如真机调试、性能分析等），建议也安装并配置微信开发者工具。</p>
<h5>3. 创建uni-app项目</h5>
<p>在HBuilderX中，可以通过选择"文件”-&gt;“新建”-&gt;“项目"来创建一个新的uni-app项目。在创建项目时，需要选择模板、设置项目名称、选择项目位置等。uni-app提供了多种模板供开发者选择，包括默认模板、Hello uni-app模板等。</p>
<h4>二、项目结构</h4>
<p>uni-app项目的结构相对清晰，主要包括以下几个部分：</p>
<h5>1. <code>pages</code> 目录</h5>
<p>用于存放所有页面文件，每个页面通常包括四个文件：<code>.vue</code>（页面结构）、<code>.js</code>（页面逻辑）、<code>.json</code>（页面配置）、<code>.css</code>（页面样式，但在uni-app中通常使用<code>.scss</code>或<code>.less</code>进行样式编写）。</p>
<h5>2. <code>static</code> 目录</h5>
<p>用于存放静态资源文件，如图片、字体、音频等。这些文件在项目编译时会被直接复制到输出目录中，可以通过相对路径在项目中引用。</p>
<h5>3. <code>main.js</code> 和 <code>App.vue</code></h5>
<p><code>main.js</code> 是项目的入口文件，用于创建Vue实例和全局配置。<code>App.vue</code> 是项目的根组件，所有页面都会挂载在这个组件上。</p>
<h5>4. <code>manifest.json</code></h5>
<p>用于配置应用的基本信息，如应用名称、图标、版本、权限等。这些信息会影响到应用在不同平台上的表现。</p>
<h5>5. <code>pages.json</code></h5>
<p>用于配置页面路由、导航条、选项卡等页面级配置项。uni-app支持在<code>pages.json</code>中通过简单的配置来定义应用的页面结构和页面跳转规则。</p>
<h4>三、开发流程</h4>
<h5>1. 页面开发</h5>
<p>在<code>pages</code>目录下创建新的页面文件，按照<code>.vue</code>、<code>.js</code>、<code>.json</code>、<code>.css</code>（或<code>.scss</code>、<code>.less</code>）的结构进行开发。在<code>.vue</code>文件中，使用Vue的模板语法来编写页面结构，使用JavaScript来编写页面逻辑，使用CSS或预处理器来编写页面样式。</p>
<h5>2. 数据交互</h5>
<p>uni-app支持多种数据交互方式，包括使用uni.request发起网络请求、使用uni.getStorage和uni.setStorage进行本地存储等。开发者可以根据项目需求选择合适的数据交互方式。</p>
<h5>3. 组件开发</h5>
<p>uni-app提供了丰富的组件库，包括基础组件、表单组件、导航组件等。开发者可以直接使用这些组件来构建页面，也可以基于这些组件进行扩展，开发自定义组件。</p>
<h5>4. API调用</h5>
<p>uni-app封装了微信小程序的大部分API，使得开发者可以使用统一的接口在不同平台上进行开发。同时，uni-app也提供了自己的扩展API，以满足更多场景下的需求。</p>
<h4>四、性能优化</h4>
<h5>1. 代码优化</h5>
<ul>
 <li><strong>避免全局变量</strong>：尽量减少全局变量的使用，使用Vue的响应式系统来管理数据。</li>
 <li><strong>组件化开发</strong>：将可复用的部分封装成组件，减少代码冗余，提高开发效率。</li>
 <li><strong>懒加载</strong>：对于非首屏加载的页面或组件，可以使用懒加载来提高应用的加载速度。</li>
</ul>
<h5>2. 资源优化</h5>
<ul>
 <li><strong>图片压缩</strong>：使用图片压缩工具对图片进行压缩，减少图片体积。</li>
 <li><strong>代码分割</strong>：使用uni-app的代码分割功能，将代码分割成多个包，减少单个包的体积。</li>
 <li><strong>静态资源缓存</strong>：利用浏览器的缓存机制，对静态资源进行缓存，减少网络请求。</li>
</ul>
<h5>3. 渲染优化</h5>
<ul>
 <li><strong>减少DOM操作</strong>：在Vue中尽量使用数据绑定和指令来更新视图，减少直接的DOM操作。</li>
 <li><strong>使用虚拟滚动</strong>：对于长列表的渲染，可以使用虚拟滚动技术来优化性能。</li>
 <li><strong>CSS优化</strong>：优化CSS选择器的效率，避免使用过于复杂的选择器。</li>
</ul>
<h4>五、调试与发布</h4>
<h5>1. 调试</h5>
<ul>
 <li><strong>真机调试</strong>：使用HBuilderX或微信开发者工具进行真机调试，查看应用在不同设备上的表现。</li>
 <li><strong>控制台调试</strong>：利用浏览器的控制台或微信开发者工具的控制台来查看应用的日志和错误信息。</li>
 <li><strong>性能分析</strong>：使用微信开发者工具的性能分析工具来分析应用的性能瓶颈。</li>
</ul>
<h5>2. 发布</h5>
<ul>
 <li><strong>构建项目</strong>：在HBuilderX中，可以通过"发行”-&gt;“小程序-微信"来构建项目。构建完成后，会生成一个包含所有编译后文件的目录。</li>
 <li><strong>上传代码</strong>：使用微信开发者工具将构建后的代码上传至微信小程序后台。上传时，需要填写版本信息、项目设置等。</li>
 <li><strong>审核与发布</strong>：上传代码后，需要等待微信官方的审核。审核通过后，可以在小程序后台进行发布操作。发布后，用户即可在微信中搜索到并使用该小程序。</li>
</ul>
<h4>六、总结</h4>
<p>使用uni-app开发微信小程序，可以极大地提高开发效率，降低开发成本。通过统一的开发框架和跨平台的能力，开发者可以更加专注于业务逻辑和用户体验的实现。同时，uni-app还提供了丰富的组件库和API支持，使得开发者可以轻松地构建出功能丰富、性能优良的小程序应用。在开发过程中，需要注意代码优化、资源优化和渲染优化等方面的问题，以确保应用的性能和用户体验。最后，通过真机调试和性能分析等手段来发现并解决潜在的问题，确保应用的稳定性和可靠性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142017653</p>]]></description><guid isPermaLink="false">/archives/1710ef54-68b0-4d87-b61b-2645920877a1</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sun, 29 Sep 2024 00:38:19 GMT</pubDate></item><item><title><![CDATA[软件测试之压力测试知识总结]]></title><link>https://wiki.dgfygk.com/archives/44cf437e-1729-4027-81af-aafd5d965c10</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95%E4%B9%8B%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95%E7%9F%A5%E8%AF%86%E6%80%BB%E7%BB%93&amp;url=/archives/44cf437e-1729-4027-81af-aafd5d965c10" width="1" height="1" alt="" style="opacity:0;">
<h4>软件测试之压力测试知识总结</h4>
<h5>引言</h5>
<p>随着信息技术的飞速发展，软件系统在各个行业中的应用越来越广泛，其稳定性和可靠性成为用户关注的焦点。压力测试作为软件测试中的一种重要方法，对于确保软件在高负载环境下的稳定性和可靠性具有重要意义。本文将从压力测试的定义、目的、流程、类型、工具以及实际案例等多个方面对压力测试进行全面总结。</p>
<h5>一、压力测试的定义与目的</h5>
<p><strong>定义</strong>：压力测试是一种软件测试方法，旨在模拟软件在极端负载条件下的运行情况，以验证其稳定性和可靠性。这种测试方法通过向系统施加超出正常工作负载的压力，观察并记录系统的响应和行为，从而发现潜在的性能瓶颈和缺陷。</p>
<p><strong>目的</strong>：压力测试的主要目的包括以下几个方面：</p>
<ol>
 <li><strong>验证软件在极限条件下的稳定性</strong>：通过模拟高负载环境，检查软件是否能够稳定运行，避免崩溃或性能急剧下降。</li>
 <li><strong>评估软件的错误处理能力</strong>：在高负载情况下，软件应能够正确处理和响应错误，确保用户体验不受影响。</li>
 <li><strong>确定系统崩溃的极限</strong>：通过不断增加负载，找到系统崩溃的临界点，为系统优化提供依据。</li>
 <li><strong>检查系统的可恢复性</strong>：在系统发生故障后，检查其是否能够快速恢复正常运行。</li>
 <li><strong>优化系统性能</strong>：根据测试结果，对系统进行针对性的优化，提升整体性能。</li>
</ol>
<h5>二、压力测试的流程</h5>
<p>压力测试通常遵循以下流程：</p>
<ol>
 <li>
  <p><strong>计划压力测试</strong>：</p>
 </li>
 <li>
  <p>收集系统数据，分析系统架构和性能瓶颈。</p>
 </li>
 <li>
  <p>定义测试目标，如最大并发用户数、响应时间等。</p>
 </li>
 <li>
  <p>编写压力测试计划，明确测试范围、测试环境、测试工具等。</p>
 </li>
 <li>
  <p><strong>准备测试环境</strong>：</p>
 </li>
 <li>
  <p>搭建与生产环境相似的测试环境，确保测试结果的准确性。</p>
 </li>
 <li>
  <p>准备足够的测试机器和网络资源，以支持预期的并发请求。</p>
 </li>
 <li>
  <p><strong>创建自动化脚本</strong>：</p>
 </li>
 <li>
  <p>使用自动化测试工具编写测试脚本，定义用户增长模型、请求发送频率等。</p>
 </li>
 <li>
  <p>确保脚本能够模拟真实用户的行为，包括登录、浏览、交易等。</p>
 </li>
 <li>
  <p><strong>执行压力测试</strong>：</p>
 </li>
 <li>
  <p>运行测试脚本，逐步增加并发用户数或加载强度。</p>
 </li>
 <li>
  <p>监控CPU、内存、网络等系统资源的使用情况。</p>
 </li>
 <li>
  <p>记录响应时间、错误率等性能指标。</p>
 </li>
 <li>
  <p><strong>结果分析</strong>：</p>
 </li>
 <li>
  <p>分析测试结果，识别系统瓶颈和潜在缺陷。</p>
 </li>
 <li>
  <p>提出优化建议，包括代码优化、配置调整、硬件升级等。</p>
 </li>
 <li>
  <p><strong>调整和优化</strong>：</p>
 </li>
 <li>
  <p>根据测试结果进行系统优化，改进性能瓶颈。</p>
 </li>
 <li>
  <p>重新执行测试，验证优化效果。</p>
 </li>
</ol>
<h5>三、压力测试的类型</h5>
<p>压力测试根据测试目标和场景的不同，可分为多种类型：</p>
<ol>
 <li><strong>系统压力测试</strong>：对整个系统进行压力测试，以评估其整体性能。</li>
 <li><strong>分布式压力测试</strong>：在分布式系统中，对多个客户端和服务器进行压力测试，以检查系统间的交互和协作能力。</li>
 <li><strong>事务压力测试</strong>：针对特定事务进行压力测试，以评估事务处理能力和性能瓶颈。</li>
 <li><strong>探索性压力测试</strong>：使用不寻常的测试条件来识别实际情况下的意外缺陷。</li>
 <li><strong>大数据量测试</strong>：针对系统存储、传输、统计、查询等业务进行独立数据量测试，以评估系统在大数据量下的性能表现。</li>
</ol>
<h5>四、压力测试工具</h5>
<p>市场上存在多种压力测试工具，各有其特点和优势。以下是一些常用的压力测试工具：</p>
<ol>
 <li>
  <p><strong>LoadRunner</strong>：</p>
 </li>
 <li>
  <p>由Micro Focus开发的商业性能测试工具，支持多种协议和技术，适用于复杂的性能测试场景。</p>
 </li>
 <li>
  <p>提供丰富的测试脚本和报告功能，易于使用和扩展。</p>
 </li>
 <li>
  <p><strong>JMeter</strong>：</p>
 </li>
 <li>
  <p>开源免费的压测产品，最初被设计用于Web应用功能测试，如今已广泛应用于性能测试。</p>
 </li>
 <li>
  <p>支持多种协议和接口测试，易于录制和编辑测试脚本。</p>
 </li>
 <li>
  <p><strong>NeoLoad</strong>：</p>
 </li>
 <li>
  <p>Neotys出品的一种负载和性能测试工具，可真实地模拟用户活动并监视基础架构运行状态。</p>
 </li>
 <li>
  <p>提供无脚本GUI和一系列自动化功能，提高测试设计速度和效率。</p>
 </li>
 <li>
  <p><strong>Locust</strong>：</p>
 </li>
 <li>
  <p>基于Python编程语言的性能测试工具，采用Pure Python描述测试脚本。</p>
 </li>
 <li>
  <p>支持HTTP/HTTPS协议及其他协议的测试，具有较高的灵活性和可扩展性。</p>
 </li>
 <li>
  <p><strong>WebLOAD</strong>：</p>
 </li>
 <li>
  <p>Radview公司的负载测试工具，支持多种协议和技术的测试。</p>
 </li>
 <li>
  <p>提供JavaScript和集成的COM/Java对象编写的测试脚本，易于学习和使用。</p>
 </li>
</ol>
<h5>五、实际案例分析</h5>
<p>以下是一些压力测试的实际案例，展示了压力测试在实际应用中的重要作用：</p>
<p><strong>案例一：系统CPU等指标正常，但偶发请求耗时高</strong></p>
<ul>
 <li><strong>问题描述</strong>：在某次压力测试中，系统CPU等指标表现正常，但偶发时间请求耗时特别高。</li>
 <li><strong>分析过程</strong>：通过查看JVM GC日志，发现存在Full GC导致的"Stop the World"现象。</li>
 <li><strong>解决方案</strong>：优化JVM配置，减少Full GC时间；调整老年代内存设置，降低GC频率。</li>
</ul>
<p><strong>案例二：php-fpm内存增长，OOM导致服务挂掉</strong></p>
<ul>
 <li><strong>问题描述</strong>：在某次压力测试中，php-fpm内存不断增长，最终导致OOM（Out of Memory）并服务挂掉。</li>
 <li><strong>分析过程</strong>：通过排查发现使用了某第三方so插件进行JSON解析，该插件存在内存泄漏问题。</li>
 <li><strong>解决方案</strong>：更换或修复第三方so插件；设置php-fpm的Max-request和fast-cgi固定请求数后重启。</li>
</ul>
<p><strong>案例三：数据库CPU飙升导致性能下降</strong></p>
<ul>
 <li><strong>问题描述</strong>：在压力测试中，同样并发TPS下，前期性能良好，但后期数据库CPU飙升导致性能急剧下降。</li>
 <li><strong>分析过程</strong>：检查压测数据发现不合理，导致统一设备关联多个用户，服务端不做限制的in查询。</li>
 <li><strong>解决方案</strong>：优化压测数据生成策略；使用Batch接口减轻数据库压力；优化数据库查询语句和索引。</li>
</ul>
<h5>六、结论与展望</h5>
<p>压力测试作为软件测试中的重要环节，对于确保软件在极端负载条件下的稳定性和可靠性具有重要意义。通过合理的测试计划和流程、选择合适的测试工具和方法、以及深入的分析和优化，可以显著提升软件系统的整体性能。随着信息技术的不断发展，压力测试技术也将不断演进和完善，为软件质量的提升提供更加有力的保障。</p>
<p>未来，随着云计算、大数据、人工智能等技术的广泛应用，压力测试将面临更多新的挑战和机遇。测试人员需要不断学习和掌握新技术、新方法，以适应软件系统的不断发展变化。同时，企业也应加强对软件测试的重视和投入，建立完善的测试体系和流程，确保软件系统的质量和稳定性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141983500</p>]]></description><guid isPermaLink="false">/archives/44cf437e-1729-4027-81af-aafd5d965c10</guid><dc:creator>黄健</dc:creator><category>测试</category><pubDate>Sun, 29 Sep 2024 00:37:53 GMT</pubDate></item><item><title><![CDATA[微信小程序开发项目]]></title><link>https://wiki.dgfygk.com/archives/d7ff1c80-7b4e-4e14-b53b-bc9dc32e5e7c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91%E9%A1%B9%E7%9B%AE&amp;url=/archives/d7ff1c80-7b4e-4e14-b53b-bc9dc32e5e7c" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序开发项目是一个复杂而细致的过程，它涉及到从项目规划、设计、开发到测试、发布及维护等多个环节。以下是对微信小程序开发项目的详细探讨，包括项目准备、开发流程、项目架构、优化与发布等方面。</p>
<h4>一、项目准备</h4>
<h5>1. 明确项目目标与需求</h5>
<p>在开始微信小程序开发项目之前，首先需要明确项目的目标和需求。这包括确定小程序的功能模块、用户群体、使用场景以及预期达到的效果等。通过市场调研和竞品分析，了解用户需求和市场趋势，为项目定位提供依据。</p>
<h5>2. 申请开发者账号</h5>
<p>在微信公众平台申请成为小程序开发者，获取开发者权限。注册并登录微信公众平台，按照指引填写相关信息，完成开发者身份认证。</p>
<h5>3. 准备开发环境</h5>
<p>下载并安装微信开发者工具，这是官方提供的小程序开发和调试工具，集成了代码编辑、预览、调试等功能。同时，确保开发环境具备稳定的网络连接和足够的存储空间。</p>
<h4>二、开发流程</h4>
<h5>1. 需求分析</h5>
<p>深入理解项目需求，明确每个功能模块的具体要求。通过用户故事、用例图等方式，将需求转化为可操作的任务列表。</p>
<h5>2. 设计阶段</h5>
<p><strong>（1）界面设计</strong>：根据需求设计小程序的界面布局和样式。可以使用Sketch、Figma等设计工具进行UI设计，确保界面简洁、美观且符合用户体验。</p>
<p><strong>（2）架构设计</strong>：设计小程序的整体架构，包括前端页面结构、后端服务接口、数据库设计等。确保架构清晰、合理，便于后续开发和维护。</p>
<h5>3. 编码实现</h5>
<p><strong>（1）页面开发</strong>：使用WXML（WeiXin Markup Language）和WXSS（WeiXin Style Sheets）进行页面布局和样式设计。WXML用于描述页面的结构，WXSS用于定义页面的样式。</p>
<p><strong>（2）逻辑开发</strong>：使用JavaScript进行页面逻辑的开发。在小程序中，逻辑通常封装在页面的JS文件中，通过调用微信提供的API接口实现功能。</p>
<p><strong>（3）数据交互</strong>：使用微信提供的API接口进行数据交互，包括网络请求、数据存储等。可以使用wx.request方法发起网络请求，获取远程数据；使用wx.setStorage和wx.getStorage等方法进行本地数据存储和读取。</p>
<h5>4. 调试与测试</h5>
<p>使用微信开发者工具进行代码的调试和预览。在开发过程中，及时修复发现的bug，并进行性能优化。测试阶段需要覆盖所有功能模块，确保小程序的稳定性和可用性。</p>
<h4>三、项目架构</h4>
<p>微信小程序的项目架构通常包括以下几个部分：</p>
<h5>1. 全局配置文件（app.json）</h5>
<p>用于配置小程序的全局设置，如页面路径、窗口表现、底部tab等。通过修改这个文件，可以调整小程序的整体布局和样式。</p>
<h5>2. 全局脚本文件（app.js）</h5>
<p>用于注册小程序的全局变量和生命周期函数。在app.js中，可以定义一些全局的函数或变量，供其他页面或组件使用。</p>
<h5>3. 页面配置文件（page.json）</h5>
<p>每个页面都有自己的配置文件，用于配置当前页面的窗口表现、导航条样式等。这些配置会覆盖app.json中的全局配置。</p>
<h5>4. 页面布局文件（page.wxml）</h5>
<p>用于描述页面的结构。在wxml文件中，可以使用微信提供的组件来构建页面布局。</p>
<h5>5. 页面样式文件（page.wxss）</h5>
<p>用于定义页面的样式。在wxss文件中，可以编写CSS代码来定义页面的布局、颜色、字体等样式。</p>
<h5>6. 页面脚本文件（page.js）</h5>
<p>用于处理页面的逻辑。在js文件中，可以编写JavaScript代码来处理用户的交互事件、发起网络请求等。</p>
<h4>四、优化与发布</h4>
<h5>1. 性能优化</h5>
<p><strong>（1）代码包体积优化</strong>：通过分包加载、避免非必要的全局自定义组件和插件、控制代码包内的资源文件等方式，减小代码包体积，提高加载速度。</p>
<p><strong>（2）代码注入优化</strong>：使用按需注入和用时注入等技术，减少不必要的代码注入，提高页面渲染速度。</p>
<p><strong>（3）首屏渲染优化</strong>：精简首屏数据，避免引用未使用的自定义组件，根据页面内容优先级延迟更新非关键部分或不可见部分。</p>
<h5>2. 兼容性测试</h5>
<p>在不同版本的微信客户端和不同品牌的手机上进行测试，确保小程序在不同设备和平台上的兼容性和稳定性。</p>
<h5>3. 发布与审核</h5>
<p>完成开发和测试后，通过微信开发者工具将小程序提交至微信开放平台进行审核。审核通过后，用户即可在微信中搜索和使用小程序。</p>
<h4>五、总结</h4>
<p>微信小程序开发项目是一个涉及多个环节和技术的综合性项目。在项目准备阶段，需要明确项目目标和需求，准备开发环境；在开发流程中，需要经历需求分析、设计、编码实现、调试与测试等环节；在项目架构上，需要合理设计全局配置文件、全局脚本文件、页面配置文件、页面布局文件、页面样式文件和页面脚本文件等组成部分；在优化与发布阶段，需要进行性能优化、兼容性测试和发布审核等工作。通过这些步骤的共同努力，可以开发出功能完善、性能优良、用户体验良好的微信小程序。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/142017300</p>]]></description><guid isPermaLink="false">/archives/d7ff1c80-7b4e-4e14-b53b-bc9dc32e5e7c</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Sun, 29 Sep 2024 00:37:40 GMT</pubDate></item><item><title><![CDATA[MySQL中创建数据库和表]]></title><link>https://wiki.dgfygk.com/archives/46f30cd8-6f3a-4e02-9a30-656c5b809660</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MySQL%E4%B8%AD%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93%E5%92%8C%E8%A1%A8&amp;url=/archives/46f30cd8-6f3a-4e02-9a30-656c5b809660" width="1" height="1" alt="" style="opacity:0;">
<p>在MySQL中创建数据库和表是数据库管理的基础步骤，它们构成了数据存储和操作的基础结构。下面，我将详细介绍如何在MySQL中创建数据库和表，包括创建数据库的基本语法、设计表的考虑因素、表的创建过程、数据类型和约束的使用等，力求内容详尽且不少于2000字。</p>
<h4>一、创建数据库</h4>
<p>在MySQL中，数据库是一个创建简单的操作，使用<code>CREATE DATABASE</code>语句即可完成。这个语句允许你指定数据库的名称，并可选地指定一些数据库级别的选项。</p>
<p><strong>基本语法</strong>：</p>
<pre><code class="prism language-sql">CREATE DATABASE [IF NOT EXISTS] 数据库名
    [DEFAULT CHARACTER SET 字符集名]
    [COLLATE 排序规则名];
</code></pre>
<ul>
 <li><code>[IF NOT EXISTS]</code> 是一个可选参数，用于在尝试创建已存在的数据库时避免错误。</li>
 <li><code>[DEFAULT CHARACTER SET 字符集名]</code> 用于指定数据库的默认字符集，这会影响数据库中所有表的默认字符集。</li>
 <li><code>[COLLATE 排序规则名]</code> 用于指定数据库的默认排序规则，它与字符集紧密相关，定义了字符的比较和排序方式。</li>
</ul>
<p><strong>示例</strong>：</p>
<pre><code class="prism language-sql">CREATE DATABASE IF NOT EXISTS mydb
    DEFAULT CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;
</code></pre>
<p>这个示例创建了一个名为<code>mydb</code>的数据库，如果<code>mydb</code>已存在则不会执行任何操作。数据库的默认字符集被设置为<code>utf8mb4</code>，这是一种支持更多Unicode字符的字符集，包括一些特殊表情符号。排序规则被设置为<code>utf8mb4_unicode_ci</code>，这是一种不区分大小写的排序规则。</p>
<h4>二、设计表</h4>
<p>在创建表之前，需要仔细设计表的结构。表的设计应该基于数据的特性和业务逻辑的需求，确保数据的完整性和效率。</p>
<p><strong>设计表的考虑因素</strong>：</p>
<ol>
 <li><strong>表名</strong>：表名应该简洁明了，能够反映表的内容或用途。</li>
 <li><strong>列（字段）</strong> ：</li>
 <li>列名：同样应该简洁明了，避免使用MySQL的保留字。</li>
 <li>数据类型：根据数据的性质选择合适的数据类型，如整数、浮点数、字符串、日期等。</li>
 <li>长度：对于字符串类型，需要指定最大长度。</li>
 <li>默认值：可以为某些列指定默认值，以便在插入记录时自动填充。</li>
 <li>允许空值：指定某些列是否允许存储NULL值。</li>
 <li><strong>主键</strong>：主键是表中每条记录的唯一标识。它可以是单个字段，也可以是多个字段的组合（复合主键）。</li>
 <li><strong>外键</strong>：外键用于在两个表之间建立关系，确保数据的引用完整性。</li>
 <li><strong>索引</strong>：为了提高查询效率，可以为表中的某些列创建索引。</li>
 <li><strong>约束</strong>：包括NOT NULL、UNIQUE、CHECK等，用于限制表中数据的取值范围和格式。</li>
</ol>
<h4>三、创建表</h4>
<p>在MySQL中，使用<code>CREATE TABLE</code>语句创建表。这个语句允许你定义表的结构，包括列的定义、主键、外键、索引等。</p>
<p><strong>基本语法</strong>：</p>
<pre><code class="prism language-sql">CREATE TABLE [IF NOT EXISTS] 表名 (
    列名1 数据类型 [约束],
    列名2 数据类型 [约束],
    ...,
    [PRIMARY KEY (主键列名)],
    [FOREIGN KEY (外键列名) REFERENCES 父表名(父表列名)],
    [INDEX 索引名 (索引列名)],
    ...
) [ENGINE=存储引擎] [DEFAULT CHARSET=字符集名] [COLLATE=排序规则名];
</code></pre>
<p><strong>示例</strong>：</p>
<p>假设我们需要创建一个用户表<code>users</code>，包含用户ID（主键）、用户名、密码、邮箱和注册时间等字段。</p>
<pre><code class="prism language-sql">CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
</code></pre>
<p>在这个示例中，<code>users</code>表包含五个字段：</p>
<ul>
 <li><code>id</code>：用户ID，是表的主键，自动增长。</li>
 <li><code>username</code>：用户名，不允许为空且在整个表中唯一。</li>
 <li><code>password</code>：密码，不允许为空。</li>
 <li><code>email</code>：邮箱，不允许为空且在整个表中唯一。</li>
 <li><code>registered_at</code>：注册时间，默认为当前时间戳。</li>
</ul>
<p>表使用InnoDB存储引擎，支持事务处理、行级锁定和外键约束等高级功能。字符集和排序规则与前面创建的数据库相同。</p>
<h4>四、数据类型和约束</h4>
<p>在创建表时，数据类型和约束的选择非常重要，它们决定了表中数据的格式和取值范围。</p>
<p><strong>常用数据类型</strong>：</p>
<ul>
 <li>整数类型：TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT等，用于存储整数。</li>
 <li>浮点数和定点数类型：FLOAT、DOUBLE、DECIMAL等，用于存储小数。</li>
 <li>字符串类型：CHAR、VARCHAR、TEXT等，用于存储文本数据。</li>
 <li>日期和时间类型：DATE、TIME、DATETIME、TIMESTAMP等，用于存储日期和时间。</li>
</ul>
<p><strong>常用约束</strong>：</p>
<ul>
 <li><code>NOT NULL</code>：列不允许存储NULL值。</li>
 <li><code>UNIQUE</code>：列中的所有值必须唯一。</li>
 <li><code>PRIMARY KEY</code>：主键约束，列的值必须唯一且不允许为NULL。</li>
 <li><code>FOREIGN KEY</code>：外键约束，用于建立两个表之间的关系。</li>
 <li><code>CHECK</code>：检查约束，用于限制列中值的范围或格式（MySQL 8.0.16及以上版本支持）。</li>
</ul>
<h4>五、总结</h4>
<p>在MySQL中创建数据库和表是数据库管理的基础步骤。通过合理设计表的结构，选择合适的数据类型和约束，可以确保数据的完整性和效率。在实际应用中，还需要根据业务逻辑的需求，考虑表的索引、优化查询性能、管理数据库事务等方面的问题。希望本文的介绍能够帮助你更好地理解和应用MySQL的数据库和表创建功能。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141774667</p>]]></description><guid isPermaLink="false">/archives/46f30cd8-6f3a-4e02-9a30-656c5b809660</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Sun, 29 Sep 2024 00:37:20 GMT</pubDate></item><item><title><![CDATA[Redis的持久化]]></title><link>https://wiki.dgfygk.com/archives/97182bd7-81c0-4100-8689-29d41b2c91eb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%E7%9A%84%E6%8C%81%E4%B9%85%E5%8C%96&amp;url=/archives/97182bd7-81c0-4100-8689-29d41b2c91eb" width="1" height="1" alt="" style="opacity:0;">
<p>Redis的持久化是Redis数据管理中至关重要的一环，它确保了Redis在重启或发生故障后能够恢复数据。Redis提供了两种主要的持久化方式：AOF（Append Only File）和RDB（Redis Database Backup file）。下面将详细阐述这两种持久化方式的原理、实现过程、优缺点以及在实际应用中的选择策略。</p>
<h4>一、AOF持久化</h4>
<h5>1. 原理与实现过程</h5>
<p>AOF持久化通过保存Redis服务器所执行的写命令来记录数据库的状态。每当Redis执行一个写命令时，该命令就会被追加到AOF文件的末尾。这样，Redis重启时就可以通过重新执行AOF文件中的命令来恢复数据。</p>
<p>AOF持久化的实现过程主要包括三个步骤：命令追加、文件写入和文件同步。</p>
<ul>
 <li><strong>命令追加</strong>：当AOF持久化功能处于开启状态时，服务器在执行完一个写命令后，会以协议格式将写命令追加到AOF文件数据缓冲区（aof_buf）中。</li>
 <li><strong>文件写入</strong>：Redis服务器进程是一个事件循环，文件事件负责接收客户端命令请求并向客户端回复命令请求执行结果。在每一个事件循环结束前，都会调用flushAppendOnlyFile函数，将aof_buf中的内容写入AOF文件。</li>
 <li><strong>文件同步</strong>：AOF文件会根据appendfsync参数设置的持久化策略持久化到磁盘。appendfsync参数有三个可选值：always（每次写命令都同步）、everysec（每秒同步一次）和no（由操作系统控制同步时机）。</li>
</ul>
<h5>2. 优缺点</h5>
<p><strong>优点</strong>：</p>
<ul>
 <li><strong>持久性高</strong>：AOF可以设置为每秒同步一次或每次写操作都同步，因此数据丢失的风险较低。</li>
 <li><strong>易于理解和恢复</strong>：AOF文件以Redis命令的形式保存，易于理解和分析，也便于数据恢复。</li>
 <li><strong>数据完整性高</strong>：AOF记录了所有的写操作，因此可以确保数据的完整性。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
 <li><strong>文件体积大</strong>：AOF文件会随着时间的推移而不断增大，需要定期重写以减小文件体积。</li>
 <li><strong>恢复速度慢</strong>：在数据量大时，AOF文件的恢复速度可能较慢。</li>
 <li><strong>性能影响</strong>：频繁的写操作会导致AOF文件频繁同步，可能会对性能产生一定影响。</li>
</ul>
<h5>3. AOF重写</h5>
<p>为了解决AOF文件体积过大的问题，Redis引入了AOF重写机制。AOF重写会遍历内存中的数据，并使用尽可能少的命令来重新生成AOF文件。重写过程不会读取旧的AOF文件，而是直接根据内存中的数据生成新的AOF文件。</p>
<p>AOF重写的触发条件可以在redis.conf配置文件中设置，通常包括文件大小增长比例和文件最小大小两个条件。当满足这些条件时，Redis会fork出一个子进程来进行AOF重写。</p>
<h4>二、RDB持久化</h4>
<h5>1. 原理与实现过程</h5>
<p>RDB持久化通过创建Redis数据库在某个时间点的快照（snapshot）来实现数据的持久化。快照文件是一个包含Redis数据库所有键值对的二进制文件，通常以.rdb作为文件后缀。</p>
<p>RDB持久化的实现过程主要包括以下几个步骤：</p>
<ul>
 <li><strong>触发条件</strong>：RDB持久化可以在多种情况下触发，包括执行save命令、执行bgsave命令、Redis停机以及达到配置文件中设置的自动保存条件。</li>
 <li><strong>fork子进程</strong>：Redis会fork出一个子进程来进行持久化操作，以避免阻塞主进程。</li>
 <li><strong>生成快照文件</strong>：子进程会将内存中的数据写入到一个临时文件中，待写入完成后，再用这个临时文件替换旧的快照文件。</li>
</ul>
<h5>2. 优缺点</h5>
<p><strong>优点</strong>：</p>
<ul>
 <li><strong>恢复速度快</strong>：RDB文件是一个紧凑的二进制文件，恢复数据的速度比AOF快。</li>
 <li><strong>文件体积小</strong>：相对于AOF文件，RDB文件的体积通常更小。</li>
 <li><strong>适合备份</strong>：RDB文件非常适合用于备份和迁移。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
 <li><strong>数据丢失风险</strong>：RDB持久化是间隔性地将数据写入磁盘，因此在两次快照之间发生的数据变化可能会丢失。</li>
 <li><strong>性能影响</strong>：在fork子进程和写入快照文件时，可能会对Redis的性能产生一定影响。</li>
</ul>
<h5>3. 配置与触发</h5>
<p>RDB持久化的配置通常在Redis的配置文件redis.conf中进行，包括RDB文件的保存路径、自动保存的规则等。自动保存规则可以通过设置save指令来定义，例如"save m n"表示在m秒内数据集存在n次修改时自动触发bgsave命令。</p>
<h4>三、AOF与RDB的选择策略</h4>
<p>在实际应用中，AOF和RDB各有优缺点，因此通常会根据具体需求来选择使用哪种持久化方式或结合使用两者。</p>
<ul>
 <li><strong>对数据安全性要求较高</strong>：如果非常关心数据的安全性，且可以承受一定的性能损失，可以选择AOF持久化或结合使用AOF和RDB。</li>
 <li><strong>对数据恢复速度有较高要求</strong>：如果希望数据恢复速度更快，且对数据丢失的容忍度较高，可以选择RDB持久化。</li>
 <li><strong>综合使用</strong>：为了兼顾数据的安全性和恢复速度，可以同时开启AOF和RDB持久化。Redis在启动时会优先加载AOF文件来恢复数据，因为AOF文件通常比RDB文件保存的数据集更完整。</li>
</ul>
<h4>四、总结</h4>
<p>Redis的AOF和RDB持久化方式各有特点，适用于不同的场景和需求。AOF持久化通过记录写命令来确保数据的完整性和持久性，但可能会带来文件体积大和恢复速度慢的问题；RDB持久化通过创建快照文件来快速恢复数据，但存在数据丢失的风险。在实际应用中，可以根据具体需求选择适合的持久化方式或结合使用两者来确保数据的安全性和恢复速度。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141858918</p>]]></description><guid isPermaLink="false">/archives/97182bd7-81c0-4100-8689-29d41b2c91eb</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 10:01:18 GMT</pubDate></item><item><title><![CDATA[Ansible自动化运维项目]]></title><link>https://wiki.dgfygk.com/archives/8e063f2e-5e5c-4886-9f3a-36d34f53ca96</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Ansible%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E9%A1%B9%E7%9B%AE&amp;url=/archives/8e063f2e-5e5c-4886-9f3a-36d34f53ca96" width="1" height="1" alt="" style="opacity:0;">
<p>Ansible自动化运维项目是一个复杂而强大的解决方案，它利用Ansible这一开源自动化平台来简化IT基础设施的配置管理、应用部署和任务自动化过程。以下是一个关于Ansible自动化运维项目的详细探讨，包括其基本概念、架构、优势、应用场景、实施步骤、最佳实践以及未来发展等方面。</p>
<h4>一、Ansible基本概念</h4>
<p>Ansible是一款基于Python开发的开源自动化平台，它提供了一种简单而强大的方式用于发布、管理和编排计算机系统。Ansible通过其无代理（agentless）架构，利用SSH协议进行通信，无需在目标节点上安装任何客户端软件，从而降低了系统复杂性和维护成本。Ansible的核心组件包括Playbooks、Inventory、Modules和Roles等，它们共同构成了Ansible自动化运维的基础。</p>
<h4>二、Ansible架构</h4>
<p>Ansible的架构主要由控制节点和受管主机两部分组成。控制节点是运行Ansible软件并执行Playbooks的机器，它可以是物理机或虚拟机。受管主机是Ansible需要管理和配置的目标机器，它们可以是物理机、虚拟机、云实例或容器等。Ansible通过Inventory文件来定义和管理受管主机的列表，这些主机可以组织成不同的组以便于集中管理。</p>
<h4>三、Ansible优势</h4>
<ol>
 <li><strong>无代理架构</strong>：Ansible通过SSH协议与受管主机通信，无需在目标节点上安装任何客户端软件，降低了系统复杂性和维护成本。</li>
 <li><strong>易于学习和使用</strong>：Ansible使用YAML格式编写Playbooks，这种格式简单明了，易于阅读和编写。</li>
 <li><strong>强大的模块库</strong>：Ansible拥有大量的预定义模块，可以执行各种任务，如文件管理、软件包安装、系统服务等。</li>
 <li><strong>可扩展性</strong>：Ansible支持自定义模块和插件，可以根据需要扩展其功能。</li>
 <li><strong>社区支持</strong>：Ansible拥有活跃的开源社区和丰富的文档资源，便于用户学习和交流。</li>
</ol>
<h4>四、Ansible应用场景</h4>
<p>Ansible自动化运维项目广泛应用于各种IT基础设施的配置管理、应用部署和任务自动化中。具体应用场景包括：</p>
<ol>
 <li><strong>系统配置管理</strong>：自动化安装和配置操作系统、软件包、服务等，确保所有节点的一致性。</li>
 <li><strong>应用部署</strong>：自动化部署应用程序及其依赖项，包括代码、数据库和配置文件等。</li>
 <li><strong>任务自动化</strong>：自动化执行日常运维任务，如备份、日志管理、安全扫描等。</li>
 <li><strong>持续集成/持续部署（CI/CD）</strong>：与CI/CD工具集成，实现自动化测试和部署。</li>
</ol>
<h4>五、Ansible自动化运维项目实施步骤</h4>
<ol>
 <li><strong>环境准备</strong>：安装Ansible软件到控制节点上，并配置Python环境。</li>
 <li><strong>Inventory配置</strong>：定义受管主机的列表，并组织成不同的组。</li>
 <li><strong>Playbooks编写</strong>：根据需求编写Playbooks，定义自动化任务的执行流程。</li>
 <li><strong>测试验证</strong>：在测试环境中运行Playbooks，验证其正确性和有效性。</li>
 <li><strong>生产部署</strong>：将验证通过的Playbooks部署到生产环境中，执行自动化运维任务。</li>
</ol>
<h4>六、Ansible自动化运维项目最佳实践</h4>
<ol>
 <li><strong>使用Roles组织Playbooks</strong>：通过将Playbooks、任务和变量等组织成Roles，可以提高代码的可读性和重用性。</li>
 <li><strong>利用Ansible Galaxy</strong>：Ansible Galaxy是一个共享Ansible Roles的在线社区，用户可以从中下载和分享各种预定义的Roles，以加快自动化脚本的构建过程。</li>
 <li><strong>版本控制</strong>：将Playbooks和Inventory等文件纳入版本控制系统（如Git），以便跟踪变更历史和协同工作。</li>
 <li><strong>安全加固</strong>：在Ansible配置和使用过程中加强安全措施，如使用SSH密钥认证、限制受管主机的访问权限等。</li>
 <li><strong>持续监控和审计</strong>：对Ansible自动化运维过程进行持续监控和审计，以确保其安全性和有效性。</li>
</ol>
<h4>七、Ansible未来发展</h4>
<p>随着云计算、容器化和DevOps等技术的不断发展，Ansible自动化运维项目将面临更多的机遇和挑战。未来，Ansible可能会继续加强其与其他技术的集成能力，如与Kubernetes、Docker等容器化技术的集成；同时，Ansible也可能会推出更多新的功能和模块，以满足用户对自动化运维的更高需求。</p>
<p>总之，Ansible自动化运维项目是一个功能强大且易于使用的解决方案，它可以帮助企业简化IT基础设施的配置管理、应用部署和任务自动化过程。通过实施Ansible自动化运维项目，企业可以提高运维效率、降低运维成本并增强系统的稳定性和安全性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141774781</p>]]></description><guid isPermaLink="false">/archives/8e063f2e-5e5c-4886-9f3a-36d34f53ca96</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 09:42:18 GMT</pubDate></item><item><title><![CDATA[JavaScript在网页设计]]></title><link>https://wiki.dgfygk.com/archives/a6645832-bd80-4977-a2ae-0204fe629808</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%E5%9C%A8%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1&amp;url=/archives/a6645832-bd80-4977-a2ae-0204fe629808" width="1" height="1" alt="" style="opacity:0;">
<p>JavaScript在网页设计中扮演着至关重要的角色，它不仅能够增强网页的交互性，还能为用户提供更加丰富的体验。以下将详细介绍几个典型的JavaScript网页设计案例，每个案例都将从功能描述、实现思路、代码示例等方面进行阐述。</p>
<h4>1. 图片画廊（Image Gallery）</h4>
<p><strong>功能描述</strong>：</p>
<p>图片画廊允许用户浏览一组图片，并支持点击放大查看图片细节。这种设计常见于产品展示、摄影作品集等场景。</p>
<p><strong>实现思路</strong>：</p>
<ol>
 <li>使用HTML和CSS创建图片网格布局。</li>
 <li>使用JavaScript监听图片的点击事件。</li>
 <li>当图片被点击时，显示一个模态框（Modal），并在其中加载并显示被点击的图片。</li>
 <li>提供一个关闭按钮，用于关闭模态框。</li>
</ol>
<p><strong>代码示例</strong>：</p>
<pre><code class="prism language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Image Gallery&lt;/title&gt;
    &lt;style&gt;
        /* CSS样式省略，用于创建图片网格和模态框样式 */
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="gallery"&gt;
        &lt;img src="image1.jpg" alt="Image 1"&gt;
        &lt;img src="image2.jpg" alt="Image 2"&gt;
        &lt;img src="image3.jpg" alt="Image 3"&gt;
        &lt;!-- 更多图片 --&gt;
    &lt;/div&gt;
    &lt;div id="modal" class="modal"&gt;
        &lt;span class="close"&gt;&amp;times;&lt;/span&gt;
        &lt;img class="modal-content" id="modalImg"&gt;
    &lt;/div&gt;
    &lt;script&gt;
        // JavaScript代码省略，用于监听图片点击事件并显示模态框
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h4>2. 待办事项列表（To-Do List）</h4>
<p><strong>功能描述</strong>：</p>
<p>待办事项列表允许用户添加、删除和标记待办事项。这种设计常见于任务管理、日程规划等场景。</p>
<p><strong>实现思路</strong>：</p>
<ol>
 <li>使用HTML创建一个输入框和一个按钮，用于添加待办事项。</li>
 <li>使用一个无序列表（<code>&lt;ul&gt;</code>）来显示待办事项。</li>
 <li>使用JavaScript监听按钮的点击事件，将输入框中的内容添加到列表中。</li>
 <li>为每个待办事项添加点击事件，用于标记该事项为已完成（例如，添加删除线）。</li>
 <li>提供删除功能，允许用户从列表中移除待办事项。</li>
</ol>
<p><strong>代码示例</strong>：</p>
<pre><code class="prism language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;To-Do List&lt;/title&gt;
    &lt;style&gt;
        /* CSS样式省略，用于美化待办事项列表 */
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;To-Do List&lt;/h1&gt;
    &lt;input type="text" id="taskInput" placeholder="Add a new task"&gt;
    &lt;button id="addTask"&gt;Add&lt;/button&gt;
    &lt;ul id="taskList"&gt;&lt;/ul&gt;
    &lt;script&gt;
        // JavaScript代码省略，用于管理待办事项的增删改查
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h4>3. 简易天气应用（Weather App）</h4>
<p><strong>功能描述</strong>：</p>
<p>简易天气应用允许用户输入城市名，并显示该城市的实时天气信息。这种设计常见于旅游规划、日常生活查询等场景。</p>
<p><strong>实现思路</strong>：</p>
<ol>
 <li>使用HTML创建一个输入框和一个按钮，用于输入城市名和获取天气信息。</li>
 <li>使用一个<code>&lt;div&gt;</code>元素来显示天气信息。</li>
 <li>使用JavaScript监听按钮的点击事件，通过API（如OpenWeatherMap）获取天气数据。</li>
 <li>将获取到的天气数据显示在页面上。</li>
</ol>
<p><strong>代码示例</strong>：</p>
<pre><code class="prism language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Weather App&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Weather App&lt;/h1&gt;
    &lt;input type="text" id="cityInput" placeholder="Enter city"&gt;
    &lt;button id="getWeather"&gt;Get Weather&lt;/button&gt;
    &lt;div id="weatherResult"&gt;&lt;/div&gt;
    &lt;script&gt;
        const apiKey = 'YOUR_API_KEY'; // 替换为你的API密钥
        const getWeatherButton = document.getElementById('getWeather');
        const weatherResult = document.getElementById('weatherResult');

        getWeatherButton.onclick = function() {

            const city = document.getElementById('cityInput').value;
            fetch(`https://api.openweathermap.org/data/2.5/weather?q=${
       city}&amp;appid=${
       apiKey}&amp;units=metric`)
                .then(response =&gt; response.json())
                .then(data =&gt; {

                    const temp = data.main.temp;
                    const weatherDescription = data.weather[0].description;
                    weatherResult.innerHTML = `Temperature: ${
       temp}°C&lt;br&gt;Description: ${
       weatherDescription}`;
                })
                .catch(error =&gt; {

                    weatherResult.innerHTML = 'City not found.';
                });
        };
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h4>4. 简易计算器（Simple Calculator）</h4>
<p><strong>功能描述</strong>：</p>
<p>简易计算器允许用户输入两个数字和一个运算符，然后显示运算结果。这种设计常见于教育、日常生活等场景。</p>
<p><strong>实现思路</strong>：</p>
<ol>
 <li>使用HTML创建两个输入框（用于输入数字）、一个下拉菜单（用于选择运算符）和一个按钮（用于触发计算）。</li>
 <li>使用一个<code>&lt;span&gt;</code>元素来显示运算结果。</li>
 <li>使用JavaScript监听按钮的点击事件，获取输入框中的数字和下拉菜单中的运算符，然后进行计算。</li>
 <li>将计算结果显示在页面上。</li>
</ol>
<p><strong>代码示例</strong>：</p>
<pre><code class="prism language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;简单计算器&lt;/title&gt;
    &lt;script&gt;
        function calculate() {

            var num1 = document.getElementById("num1").value;
            var num2 = document.getElementById("num2").value;
            var operator = document.getElementById("operator").value;
            var result = 0;
            if (operator == "+") {

                result = Number(num1) + Number(num2);
            } else if (operator == "-") {

                result = num1 - num2;
            } else if (operator == "*") {

                result = num1 * num2;
            } else if (operator == "/") {

                if (num2 != 0) {

                    result = num1 / num2;
                } else {

                    alert("除数不能为0！");
                    return;
                }
            }
            document.getElementById("result").innerHTML = result;
        }
    &lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;简单计算器&lt;/h1&gt;
    &lt;form&gt;
        &lt;label for="num1"&gt;数字1:&lt;/label&gt;
        &lt;input type="number" id="num1" name="num1"&gt;&lt;br&gt;&lt;br&gt;
        &lt;label for="num2"&gt;数字2:&lt;/label&gt;
        &lt;input type="number" id="num2" name="num2"&gt;&lt;br&gt;&lt;br&gt;
        &lt;label for="operator"&gt;运算符:&lt;/label&gt;
        &lt;select id="operator" name="operator"&gt;
            &lt;option value="+"&gt;+&lt;/option&gt;
            &lt;option value="-"&gt;-&lt;/option&gt;
            &lt;option value="*"&gt;*&lt;/option&gt;
            &lt;option value="/"&gt;/&lt;/option&gt;
        &lt;/select&gt;&lt;br&gt;&lt;br&gt;
        &lt;input type="button" value="计算" onclick="calculate()"&gt;&lt;br&gt;&lt;br&gt;
        &lt;label for="result"&gt;结果:&lt;/label&gt;
        &lt;span id="result"&gt;&lt;/span&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>以上案例展示了JavaScript在网页设计中的应用，涵盖了图片展示、任务管理、数据获取和简单计算等多个方面。通过这些案例，你可以更好地理解JavaScript在前端开发中的重要作用，并学习如何将其应用于实际项目中。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141753112</p>]]></description><guid isPermaLink="false">/archives/a6645832-bd80-4977-a2ae-0204fe629808</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sat, 28 Sep 2024 09:39:43 GMT</pubDate></item><item><title><![CDATA[Nginx部署前端Vue项目]]></title><link>https://wiki.dgfygk.com/archives/6b151627-f6a8-4257-bd8d-6d36c27c30e5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Nginx%E9%83%A8%E7%BD%B2%E5%89%8D%E7%AB%AFVue%E9%A1%B9%E7%9B%AE&amp;url=/archives/6b151627-f6a8-4257-bd8d-6d36c27c30e5" width="1" height="1" alt="" style="opacity:0;">
<p>在使用Nginx部署前端Vue项目时，我们主要关注的是如何高效地将静态资源（HTML、CSS、JavaScript、图片等）提供给客户端浏览器。Vue.js是一个流行的JavaScript框架，用于构建用户界面，而Nginx作为一个高性能的HTTP和反向代理服务器，非常适合用于静态资源的托管。以下将详细介绍如何使用Nginx部署Vue项目，包括环境准备、Vue项目构建、Nginx配置、部署过程以及优化策略，总字数将超过2000字。</p>
<h4>一、环境准备</h4>
<h5>1. 安装Node.js和npm</h5>
<p>Vue项目是基于Node.js环境的，首先需要确保你的系统上安装了Node.js。Node.js自带npm（Node Package Manager），这是JavaScript的包管理工具，用于安装和管理Vue CLI和其他依赖。</p>
<ul>
 <li>访问<a href="https://nodejs.org/">Node.js官网</a>下载并安装对应操作系统的Node.js版本。</li>
 <li>安装完成后，在命令行中运行<code>node -v</code>和<code>npm -v</code>来检查是否安装成功。</li>
</ul>
<h5>2. 安装Vue CLI</h5>
<p>Vue CLI是一个基于Vue.js进行快速开发的完整系统，提供了零配置原型开发、运行时的配置、构建和部署等功能。</p>
<ul>
 <li>通过npm全局安装Vue CLI：<code>npm install -g @vue/cli</code></li>
 <li>安装完成后，运行<code>vue --version</code>来检查是否安装成功。</li>
</ul>
<h5>3. 安装Nginx</h5>
<p>Nginx的安装方式取决于你的操作系统。</p>
<ul>
 <li><strong>对于Ubuntu/Debian系统</strong> ：<code>sudo apt-get update &amp;&amp; sudo apt-get install nginx</code></li>
 <li><strong>对于CentOS/RHEL系统</strong> ：可能需要先启用EPEL仓库，然后使用<code>yum</code>或<code>dnf</code>进行安装。</li>
 <li><strong>对于Windows系统</strong> ：可以从<a href="http://nginx.org/en/download.html">Nginx官网</a>下载Windows版本的Nginx，解压后运行即可。</li>
</ul>
<p>安装完成后，你可以通过运行<code>nginx -v</code>来检查Nginx的版本，并启动Nginx服务（通常是通过<code>sudo systemctl start nginx</code>或<code>nginx</code>命令，取决于你的系统和Nginx的安装方式）。</p>
<h4>二、Vue项目构建</h4>
<h5>1. 创建Vue项目</h5>
<p>如果你还没有Vue项目，可以使用Vue CLI创建一个新项目：</p>
<pre><code class="prism language-bash">vue create my-vue-project
cd my-vue-project
</code></pre>
<p>按照提示选择配置（或者选择默认配置）。</p>
<h5>2. 编译Vue项目</h5>
<p>Vue CLI项目可以通过<code>npm run build</code>命令来构建生产环境的版本。这个命令会生成一个<code>dist/</code>目录，其中包含了所有用于部署的静态资源。</p>
<pre><code class="prism language-bash">npm run build
</code></pre>
<p>构建完成后，你可以预览生成的<code>dist/</code>目录，里面包含了HTML、CSS、JavaScript和图片等资源。</p>
<h4>三、Nginx配置</h4>
<h5>1. 创建Nginx配置文件</h5>
<p>通常，Nginx的配置文件位于<code>/etc/nginx/sites-available/</code>目录下。你可以创建一个新的配置文件，比如<code>my-vue-app</code>，然后将以下内容复制进去：</p>
<pre><code class="prism language-nginx">server {
    listen 80;
    server_name yourdomain.com; # 修改为你的域名

    location / {
        root /path/to/your/vue/project/dist; # 修改为你的Vue项目dist目录的路径
        try_files $uri $uri/ /index.html;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
</code></pre>
<p>注意：</p>
<ul>
 <li><code>listen 80;</code> 表示监听80端口，HTTP的默认端口。</li>
 <li><code>server_name yourdomain.com;</code> 替换为你的域名或IP地址。</li>
 <li><code>root</code> 指令后面跟的是Vue项目构建后<code>dist/</code>目录的路径。</li>
 <li><code>try_files $uri $uri/ /index.html;</code> 这一行是关键，它确保了对Vue的路由请求能够正确处理，即使它们是单页面应用（SPA）的"深链接”。</li>
</ul>
<h5>2. 启用并测试配置文件</h5>
<p>首先，你可能需要将你的配置文件链接到<code>/etc/nginx/sites-enabled/</code>目录下（对于某些Nginx配置来说），或者根据你的Nginx安装和配置方式，可能需要不同的步骤来启用新的站点。</p>
<p>然后，检查Nginx配置文件的语法是否正确：</p>
<pre><code class="prism language-bash">sudo nginx -t
</code></pre>
<p>如果一切正常，重新加载Nginx以使更改生效：</p>
<pre><code class="prism language-bash">sudo systemctl reload nginx
# 或者
sudo nginx -s reload
</code></pre>
<p>最后，在浏览器中访问你的域名或IP地址，你应该能看到你的Vue应用正在运行。</p>
<h4>四、部署过程</h4>
<p>部署过程大致可以总结为以下几个步骤：</p>
<ol>
 <li><strong>构建Vue项目</strong> ：使用<code>npm run build</code>命令生成生产环境的静态资源。</li>
 <li><strong>准备Nginx</strong>：确保Nginx已安装并配置好。</li>
 <li><strong>配置Nginx</strong> ：创建或修改Nginx配置文件，指定Vue项目的<code>dist/</code>目录为根目录，并配置路由转发。</li>
 <li><strong>启用并测试Nginx配置</strong> ：使用<code>nginx -t</code>测试配置文件的语法，然后使用<code>nginx -s reload</code>重新加载Nginx。</li>
 <li><strong>访问应用</strong>：在浏览器中访问你的域名或IP地址，查看Vue应用是否成功运行。</li>
</ol>
<h4>五、优化策略</h4>
<h5>1. 缓存静态资源</h5>
<p>在Nginx配置中，你可以为静态资源设置缓存策略，以减少服务器的负载和提高加载速度。例如，可以为CSS、JavaScript和图片文件设置缓存头：</p>
<pre><code class="prism language-nginx">location ~* \.(?:css|js|png|jpg|jpeg|gif|ico)$ {
    expires 30d;
    add_header Cache-Control "public";
}
</code></pre>
<h5>2. 使用Gzip压缩</h5>
<p>Nginx支持Gzip压缩，可以减小传输文件的大小，从而加快加载速度。在Nginx配置中启用Gzip压缩：</p>
<pre><code class="prism language-nginx">gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
</code></pre>
<h5>3. 启用HTTPS</h5>
<p>虽然这不是直接针对Vue项目的优化，但使用HTTPS可以为你的网站提供加密连接，增强安全性。你可以通过Let's Encrypt等证书颁发机构获取免费的SSL证书，并在Nginx中配置HTTPS。</p>
<h5>4. 监控和日志</h5>
<p>Nginx提供了强大的日志和监控功能，可以帮助你了解网站的运行状况和用户行为。确保你启用了访问日志和错误日志，并定期查看和分析这些日志。</p>
<h4>六、总结</h4>
<p>使用Nginx部署Vue项目是一个相对简单且高效的过程。通过遵循上述步骤，你可以轻松地将Vue应用部署到生产环境中，并通过一些优化策略来提升应用的性能和用户体验。当然，随着Vue和Nginx的不断发展，可能会有更多的新特性和最佳实践出现，因此建议定期关注相关的社区和文档，以保持对最新技术的了解。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141807329</p>]]></description><guid isPermaLink="false">/archives/6b151627-f6a8-4257-bd8d-6d36c27c30e5</guid><dc:creator>黄健</dc:creator><category>Nginx专题</category><pubDate>Sat, 28 Sep 2024 09:39:30 GMT</pubDate></item><item><title><![CDATA[Notepad下载安装]]></title><link>https://wiki.dgfygk.com/archives/78861a46-da38-4358-9371-d00d8ffc9f43</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Notepad%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85&amp;url=/archives/78861a46-da38-4358-9371-d00d8ffc9f43" width="1" height="1" alt="" style="opacity:0;">
<p>Notepad（通常指的是Notepad++，一个功能更强大的文本和源代码编辑器）的下载与安装过程相对简单直接。以下是一个详细的Notepad++下载安装教程，旨在帮助用户顺利完成安装并开始使用。</p>
<h4>一、准备工作</h4>
<p>在开始下载和安装Notepad++之前，请确保您的计算机已连接到互联网，并且您有足够的权限来安装软件（通常需要管理员权限）。</p>
<h4>二、下载Notepad++</h4>
<ol>
 <li>
  <p><strong>打开浏览器</strong>：首先，使用您喜欢的网页浏览器（如Chrome、Firefox、Edge等）打开一个新的标签页。</p>
 </li>
 <li>
  <p><strong>访问官方网站</strong> ：在浏览器的地址栏中输入Notepad++的官方网址（<a href="https://notepad-plus-plus.org/">https://notepad-plus-plus.org/</a>），然后按下回车键。如果直接访问官网有困难，也可以尝试在其他可信的软件下载网站上搜索Notepad++的下载链接，但要确保下载来源的可靠性。</p>
 </li>
 <li>
  <p><strong>下载软件</strong>：在Notepad++的官方网站上，找到并点击"Download”（下载）按钮。根据您的操作系统（如Windows、Linux、macOS）选择合适的版本进行下载。对于大多数Windows用户来说，您需要选择"Windows"下的相应安装程序（通常是.exe文件）。</p>
 </li>
 <li>
  <p><strong>等待下载完成</strong>：下载过程可能需要一些时间，具体时间取决于您的网络速度和Notepad++版本的大小。请耐心等待，直到下载完成。</p>
 </li>
</ol>
<h4>三、安装Notepad++</h4>
<ol>
 <li>
  <p><strong>找到下载的安装程序</strong>：下载完成后，通常会在浏览器的下载文件夹中找到Notepad++的安装程序（如notepad-plus-xx.yy.z-installer.exe，其中xx.yy.z是版本号）。</p>
 </li>
 <li>
  <p><strong>双击安装程序</strong>：双击下载的安装程序文件，启动安装向导。</p>
 </li>
 <li>
  <p><strong>选择安装语言</strong>（如果提供）：在某些情况下，安装程序可能会要求您选择安装语言。根据您的偏好选择合适的语言，然后点击"OK"或"继续”。</p>
 </li>
 <li>
  <p><strong>阅读并接受许可协议</strong>：安装向导会展示软件的许可协议。请仔细阅读协议内容，如果您同意协议中的条款，请勾选"我接受许可协议"选项，然后点击"下一步”。</p>
 </li>
 <li>
  <p><strong>选择安装路径</strong>：接下来，安装向导会要求您选择软件的安装位置。您可以使用默认的安装路径，也可以点击"浏览"按钮选择自定义的安装路径。选择完成后，点击"下一步”。</p>
 </li>
 <li>
  <p><strong>选择组件和快捷方式</strong>：在某些版本的Notepad++安装程序中，您可能会被要求选择需要安装的组件（如插件）以及是否创建桌面快捷方式或开始菜单快捷方式。根据您的需求进行选择，然后点击"下一步”。</p>
 </li>
 <li>
  <p><strong>开始安装</strong>：确认所有设置无误后，点击"安装"按钮开始安装过程。安装程序会自动将Notepad++的文件复制到指定的位置，并创建必要的快捷方式（如果已选择）。</p>
 </li>
 <li>
  <p><strong>等待安装完成</strong>：安装过程可能需要一些时间，具体时间取决于您的计算机性能和Notepad++版本的大小。请耐心等待，直到安装完成。</p>
 </li>
 <li>
  <p><strong>完成安装</strong>：安装完成后，安装向导会显示一个完成界面。在这里，您可以勾选"启动Notepad++“选项来立即启动软件，或者稍后手动启动。点击"完成"按钮退出安装向导。</p>
 </li>
</ol>
<h4>四、启动和使用Notepad++</h4>
<ol>
 <li>
  <p><strong>启动Notepad++</strong>：如果您在安装时勾选了"启动Notepad++“选项，那么安装完成后软件将自动启动。否则，您可以通过在开始菜单或桌面上找到的快捷方式手动启动Notepad++。</p>
 </li>
 <li>
  <p><strong>使用Notepad++</strong>：启动Notepad++后，您就可以开始使用它进行文本编辑和代码编写了。Notepad++提供了丰富的功能和插件来支持多种编程语言和文本编辑需求。</p>
 </li>
</ol>
<h4>五、注意事项</h4>
<ol>
 <li>
  <p><strong>权限问题</strong>：在安装过程中，请确保您有足够的权限来安装软件。如果需要管理员权限，请右键点击安装程序并选择"以管理员身份运行”。</p>
 </li>
 <li>
  <p><strong>软件来源</strong>：为了确保软件的安全性和稳定性，请务必从官方网站或可信的软件下载网站下载Notepad++的安装程序。避免从不明来源下载软件以防止恶意软件的感染。</p>
 </li>
 <li>
  <p><strong>版本更新</strong>：Notepad++是一个不断更新的软件。为了获得最新的功能和修复已知的问题，请定期检查并更新到最新版本。您可以在官方网站或软件内部查看最新版本的信息并进行更新。</p>
 </li>
</ol>
<p>通过以上步骤，您应该能够成功下载并安装Notepad++，并开始享受它提供的强大文本编辑和代码编写功能。如果您在安装过程中遇到任何问题或需要进一步的帮助，请查阅Notepad++的官方文档或寻求社区的帮助。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141925237</p>]]></description><guid isPermaLink="false">/archives/78861a46-da38-4358-9371-d00d8ffc9f43</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 09:39:07 GMT</pubDate></item><item><title><![CDATA[Spring AOP切面类的封装与解析]]></title><link>https://wiki.dgfygk.com/archives/49cc79f0-9ad2-48c8-9507-a2f6546d459c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20AOP%E5%88%87%E9%9D%A2%E7%B1%BB%E7%9A%84%E5%B0%81%E8%A3%85%E4%B8%8E%E8%A7%A3%E6%9E%90&amp;url=/archives/49cc79f0-9ad2-48c8-9507-a2f6546d459c" width="1" height="1" alt="" style="opacity:0;">
<h4>Spring AOP切面类的封装与解析</h4>
<h5>一、引言</h5>
<p>在软件开发中，随着系统复杂度的增加，代码中会频繁出现一些横切关注点（Cross-cutting Concerns），如日志记录、安全认证、事务管理等。这些关注点并非业务逻辑的一部分，但却需要被多个业务模块所共享。为了解决这一问题，面向切面编程（Aspect-Oriented Programming，AOP）应运而生。Spring AOP作为Spring框架中的一个重要组件，提供了对AOP思想的实现，通过切面（Aspect）的方式将横切关注点与业务逻辑代码分离，从而提高了代码的可维护性和可重用性。</p>
<h5>二、Spring AOP的基本概念</h5>
<h6>1. AOP的定义</h6>
<p>AOP是一种编程范式，旨在通过横切关注点来增强软件模块。它将软件系统分为核心关注点（如业务逻辑）和横切关注点（如日志、安全、事务等），并通过切面的方式将横切关注点封装起来，以便在不修改源代码的情况下为系统增加额外的功能。</p>
<h6>2. Spring AOP的核心概念</h6>
<ul>
 <li><strong>切面（Aspect）</strong>：切面是AOP的核心，它定义了横切关注点的行为，包括何时、何地以及如何将这些行为应用到目标对象上。在Spring中，切面可以通过注解或XML配置来定义。</li>
 <li><strong>连接点（Join Point）</strong>：连接点是程序执行中的一个点，例如方法的执行或异常的抛出。在Spring AOP中，连接点通常指的是方法的执行点。</li>
 <li><strong>切点（Pointcut）</strong>：切点用于定义切面与哪些连接点进行关联。切点表达式用于匹配特定的连接点，以便切面可以对其进行增强。</li>
 <li><strong>通知（Advice）</strong>：通知是切面的具体实现，它定义了切面在特定连接点执行时的行为。通知有多种类型，包括前置通知（Before）、后置通知（After）、返回后通知（After Returning）、抛出异常后通知（After Throwing）和环绕通知（Around）。</li>
</ul>
<h5>三、Spring AOP的实现方式</h5>
<p>Spring AOP的实现主要依赖于动态代理技术，包括JDK动态代理和CGLIB动态代理。</p>
<h6>1. JDK动态代理</h6>
<p>JDK动态代理主要基于Java反射机制实现，它要求被代理的类必须实现一个或多个接口。JDK动态代理通过动态生成代理类来拦截对目标方法的调用，并在调用前后执行相应的通知逻辑。</p>
<h6>2. CGLIB动态代理</h6>
<p>CGLIB（Code Generation Library）是一个强大的、高性能的代码生成库，它可以在运行时扩展Java类和实现接口，而无需修改类的源代码。与JDK动态代理不同，CGLIB可以代理没有实现接口的类。在Spring AOP中，如果目标类没有实现接口，Spring将使用CGLIB来创建代理。</p>
<h5>四、Spring AOP切面类的封装</h5>
<p>在Spring中，切面类通常是一个包含切点定义和通知实现的类。切面类通过注解或XML配置与Spring容器集成，以便在运行时动态地为目标对象添加额外的功能。</p>
<h6>1. 使用注解定义切面</h6>
<p>在Spring中，可以通过<code>@Aspect</code>注解来定义一个切面类，并通过<code>@Component</code>注解将其注册为Spring容器中的一个Bean。然后，可以使用<code>@Pointcut</code>注解来定义切点，并使用其他通知注解（如<code>@Before</code>、<code>@After</code>、<code>@AfterReturning</code>、<code>@AfterThrowing</code>、<code>@Around</code>）来定义具体的通知逻辑。</p>
<p>示例代码如下：</p>
<pre><code class="prism language-java">import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {


    // 定义切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayerExecution() {
   }

    // 前置通知
    @Before("serviceLayerExecution()")
    public void logBeforeServiceMethod() {

        System.out.println("Before service method execution");
    }

    // 其他通知...
}
</code></pre>
<h6>2. 使用XML配置定义切面</h6>
<p>除了使用注解外，Spring还支持通过XML配置来定义切面。在XML配置中，可以使用<code>&lt;aop:aspect&gt;</code>元素来定义切面，并使用<code>&lt;aop:before&gt;</code>、<code>&lt;aop:after&gt;</code>等子元素来定义通知。然而，随着Spring版本的更新和注解的普及，XML配置方式已逐渐被注解方式所取代。</p>
<h5>五、Spring AOP切面类的解析</h5>
<p>当Spring容器启动时，它会扫描所有带有<code>@Aspect</code>注解的Bean，并解析这些Bean中定义的切点和通知。然后，Spring会使用动态代理技术为目标对象创建代理，并在代理中织入切面的通知逻辑。</p>
<h6>1. 代理的创建</h6>
<p>Spring会根据目标对象是否实现了接口来选择使用JDK动态代理还是CGLIB动态代理。如果目标对象实现了接口，Spring将使用JDK动态代理；否则，将使用CGLIB动态代理。</p>
<h6>2. 通知的织入</h6>
<p>在代理中，Spring会拦截对目标方法的调用，并根据切点表达式判断是否需要执行通知逻辑。如果需要执行通知逻辑，Spring将按照通知的优先级（如果有的话）和类型（前置、后置、环绕等）来执行相应的通知方法。</p>
<h6>3. 环绕通知的特殊性</h6>
<p>环绕通知（<code>@Around</code>）是一种特殊的通知类型，它可以在目标方法执行前后执行自定义逻辑，并可以控制目标方法的执行。在环绕通知中，可以通过<code>ProceedingJoinPoint</code>对象的<code>proceed()</code>方法来调用目标方法，并可以捕获和处理目标方法抛出的异常。</p>
<h5>六、Spring AOP的优势与局限性</h5>
<h6>1. 优势</h6>
<ul>
 <li><strong>解耦</strong>：通过切面将横切关注点与业务逻辑代码分离，降低了模块之间的耦合度。</li>
 <li><strong>可重用性</strong>：切面可以被多个业务模块共享，提高了代码的可重用性。</li>
 <li><strong>灵活性</strong>：切面可以在不修改源代码的情况下为系统增加额外的功能。</li>
</ul>
<h6>2. 局限性</h6>
<ul>
 <li><strong>性能开销</strong>：动态代理技术虽然强大，但也会带来一定的性能开销。</li>
 <li><strong>复杂性</strong>：对于初学者来说，AOP的概念和实现方式可能较为复杂。</li>
 <li><strong>依赖注入</strong>：切面类需要被Spring容器管理，因此需要通过依赖注入等方式来获取其他Bean的引用。</li>
</ul>
<h5>七、结论</h5>
<p>Spring AOP通过切面技术将横切关注点与业务逻辑代码分离，提高了代码的可维护性和可重用性。在Spring框架中，切面类通常通过注解或XML配置来定义，并依赖于动态代理技术来实现。虽然Spring AOP带来了诸多优势，但也存在一定的局限性。因此，在实际应用中需要根据具体情况来选择合适的实现方式。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141807574</p>]]></description><guid isPermaLink="false">/archives/49cc79f0-9ad2-48c8-9507-a2f6546d459c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 09:38:47 GMT</pubDate></item><item><title><![CDATA[哪个编程工具让我的工作效率翻倍？]]></title><link>https://wiki.dgfygk.com/archives/ae515cbc-1824-4e9b-9438-3a702963bae8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%93%AA%E4%B8%AA%E7%BC%96%E7%A8%8B%E5%B7%A5%E5%85%B7%E8%AE%A9%E6%88%91%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%95%88%E7%8E%87%E7%BF%BB%E5%80%8D%EF%BC%9F&amp;url=/archives/ae515cbc-1824-4e9b-9438-3a702963bae8" width="1" height="1" alt="" style="opacity:0;">
<h3>哪个编程工具让我的工作效率翻倍？</h3>
<p>在编程的世界里，选择合适的工具就如同工匠挑选趁手的锤子或锯子，能够极大地提升工作效率与创造力。随着技术的飞速发展，市场上涌现了众多编程工具，它们各自在代码编辑、项目管理、团队协作、自动化测试、版本控制、性能优化等方面展现出了强大的功能。对于我个人而言，没有一个单一的"银弹"能够适用于所有场景，但有几个工具组合起来，确实让我的工作效率实现了质的飞跃。下面，我将详细介绍几个我认为至关重要的编程工具及其如何助力我的工作。</p>
<h4>1. <strong>Visual Studio Code (VS Code)</strong></h4>
<p><strong>概述</strong> ：
 <br>
 Visual Studio Code（VS Code）是微软开发的一款轻量级但功能强大的源代码编辑器，支持几乎所有主流编程语言的语法高亮、智能代码补全、代码片段、代码重构、Git集成等功能。其丰富的插件生态系统（Marketplace）更是让VS Code成为了一个几乎无所不能的开发平台。</p>
<p><strong>提升效率的方式</strong>：</p>
<ul>
 <li><strong>智能提示与代码补全</strong>：VS Code能够基于当前上下文提供智能的代码补全建议，减少打字错误，加速编码过程。</li>
 <li><strong>版本控制集成</strong>：内置的Git支持让我能够直接在编辑器中完成代码的提交、推送、拉取等操作，极大地简化了版本管理流程。</li>
 <li><strong>强大的调试功能</strong>：支持断点、单步执行、变量监视等调试功能，使得调试过程更加直观高效。</li>
 <li><strong>丰富的插件</strong>：无论是前端开发中的ESLint、Prettier，还是后端开发中的Python、Java等语言的支持，VS Code都有相应的插件来满足需求，极大地扩展了其功能边界。</li>
</ul>
<h4>2. <strong>Docker</strong></h4>
<p><strong>概述</strong> ：
 <br>
 Docker是一个开源的应用容器引擎，让开发者可以打包他们的应用以及依赖包到一个可移植的容器中，然后发布到任何流行的Linux机器上，也可以实现虚拟化。容器是完全使用沙箱机制，相互之间不会有任何接口（类似iPhone的app）。</p>
<p><strong>提升效率的方式</strong>：</p>
<ul>
 <li><strong>环境一致性</strong>：Docker容器确保了开发、测试、生产环境的一致性，减少了因环境差异导致的"在我的机器上能运行"问题。</li>
 <li><strong>快速部署</strong>：通过Dockerfile和Docker Compose，可以轻松地构建和部署应用，极大地缩短了从开发到上线的周期。</li>
 <li><strong>资源隔离</strong>：容器之间是相互隔离的，这意味着可以在同一台物理机上同时运行多个应用，而不会相互干扰。</li>
</ul>
<h4>3. <strong>Git</strong></h4>
<p><strong>概述</strong> ：
 <br>
 Git是一个开源的分布式版本控制系统，可以有效、高速地处理从很小到非常大的项目版本管理。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。</p>
<p><strong>提升效率的方式</strong>：</p>
<ul>
 <li><strong>版本控制</strong>：Git能够记录每次代码的修改，并允许开发者随时回滚到之前的版本，保证了代码的可追溯性和可恢复性。</li>
 <li><strong>分支管理</strong>：支持多分支并行开发，团队成员可以在不同的分支上工作，互不干扰，待功能稳定后再合并到主分支。</li>
 <li><strong>协作开发</strong>：通过远程仓库（如GitHub、GitLab等），团队成员可以轻松地分享代码、评论代码、合并代码，促进团队协作。</li>
</ul>
<h4>4. <strong>JIRA/Trello</strong></h4>
<p><strong>概述</strong> ：
 <br>
 JIRA和Trello是两款流行的项目管理工具，它们以不同的方式帮助团队跟踪任务、分配工作、管理进度。JIRA更侧重于企业级项目管理，而Trello则以其简洁的界面和灵活的卡片系统受到了众多团队的喜爱。</p>
<p><strong>提升效率的方式</strong>：</p>
<ul>
 <li><strong>任务跟踪</strong>：清晰地列出项目中的每个任务，包括任务的负责人、截止日期、优先级等信息，帮助团队成员了解自己的工作量和进度。</li>
 <li><strong>协作沟通</strong>：在任务卡片上添加评论、附件、标签等，方便团队成员之间的沟通和协作。</li>
 <li><strong>可视化管理</strong>：通过看板（Kanban）等方式，将项目的进度和状态以可视化的形式展现出来，帮助团队快速识别问题并作出调整。</li>
</ul>
<h4>5. <strong>Postman</strong></h4>
<p><strong>概述</strong> ：
 <br>
 Postman是一款API开发助手，它简化了与HTTP请求相关的所有工作，如发送请求、接收响应、测试API等。Postman提供了一个直观的界面来构建请求、发送请求并查看响应，同时还支持将请求保存为集合以便重用。</p>
<p><strong>提升效率的方式</strong>：</p>
<ul>
 <li><strong>快速测试API</strong>：无需编写额外的代码，即可快速测试API接口的功能和性能。</li>
 <li><strong>构建请求集合</strong>：将常用的请求保存为集合，方便在开发过程中重复使用，减少重复劳动。</li>
 <li><strong>团队协作</strong>：支持将请求集合共享给团队成员，促进团队协作和API文档的编写。</li>
</ul>
<h4>总结</h4>
<p>虽然没有一个编程工具能够单独让我的工作效率翻倍，但VS Code、Docker、Git、JIRA/Trello以及Postman等工具的组合使用，确实极大地提升了我的工作效率。VS Code作为我的主要代码编辑器，提供了强大的代码编辑和调试功能；Docker和Git则帮助我解决了环境一致性和版本控制的问题；JIRA/Trello帮助我更好地管理项目和团队协作；而Postman则是我测试API和开发RESTful服务时的得力助手。这些工具相互补充、相互配合，共同构成了我高效工作的基石。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141885862</p>]]></description><guid isPermaLink="false">/archives/ae515cbc-1824-4e9b-9438-3a702963bae8</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 09:38:32 GMT</pubDate></item><item><title><![CDATA[压缩大型语言模型（LLMs）]]></title><link>https://wiki.dgfygk.com/archives/29041c06-75d2-4830-89f4-2e7e10f3e604</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8E%8B%E7%BC%A9%E5%A4%A7%E5%9E%8B%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%88LLMs%EF%BC%89&amp;url=/archives/29041c06-75d2-4830-89f4-2e7e10f3e604" width="1" height="1" alt="" style="opacity:0;">
<h4>压缩大型语言模型（LLMs）</h4>
<p>随着人工智能技术的迅猛发展，大型语言模型（LLMs）如GPT系列已成为自然语言处理领域的明星。然而，这些模型通常包含数十亿甚至上万亿的参数，导致巨大的计算和存储需求，限制了它们在消费级设备上的广泛应用。因此，如何在保持性能的同时压缩LLMs，成为了一个亟待解决的问题。本文将深入探讨如何通过多种技术手段，如量化、剪枝和知识蒸馏等，将LLMs的体积缩小10倍，同时保持其原有的性能。</p>
<h5>一、引言</h5>
<p>近年来，LLMs在文本生成、问答系统、对话机器人等多个领域取得了显著进展。然而，这些模型的庞大体积和计算复杂度成为了实际应用中的瓶颈。传统的"更大即更好"的思路虽然在一定程度上推动了模型性能的提升，但也带来了高昂的部署成本。因此，如何在保持性能的前提下压缩LLMs，成为了当前研究的热点。</p>
<h5>二、LLMs压缩技术概述</h5>
<p>压缩LLMs的方法多种多样，主要包括量化、剪枝和知识蒸馏三大类。这些方法可以单独使用，也可以结合使用，以实现更高效的压缩效果。</p>
<h6>1. 量化</h6>
<p>量化是指将模型中的浮点数参数转换为低精度的整数或浮点数，以减少模型的存储和计算需求。量化技术主要分为两类：训练后量化（PTQ）和量化感知训练（QAT）。</p>
<ul>
 <li><strong>训练后量化（PTQ）</strong>：在模型训练完成后，直接将模型参数转换为低精度数据类型，如从FP32转换为INT8。这种方法简单快捷，但可能会导致一定的性能损失。</li>
 <li><strong>量化感知训练（QAT）</strong>：在模型训练过程中引入量化约束，使用低精度数据类型进行训练，以最小化量化带来的性能损失。这种方法虽然技术难度较高，但能够产生更小的且性能良好的模型。</li>
</ul>
<h6>2. 剪枝</h6>
<p>剪枝是一种移除模型中不必要组件的技术，旨在减少模型的参数数量和计算复杂度。剪枝方法可以分为非结构化剪枝和结构化剪枝两类。</p>
<ul>
 <li><strong>非结构化剪枝</strong>：移除神经网络中不重要的权重，即将其值设为零。这种方法可以显著减少参数数量，但通常需要专门的硬件来支持稀疏矩阵运算。</li>
 <li><strong>结构化剪枝</strong>：移除整个结构，如神经元、层或注意力头。这种方法避免了稀疏矩阵运算的问题，可以直接从模型中删除整个组件，而不会影响其他部分的计算。</li>
</ul>
<h6>3. 知识蒸馏</h6>
<p>知识蒸馏是一种将知识从大型教师模型转移到小型学生模型的技术。教师模型通常是一个性能优异但计算复杂的大型模型，而学生模型则是一个参数较少、计算简单的小型模型。通过让学生模型学习教师模型的输出或中间表示，可以实现知识的有效传递，从而在不损失太多性能的情况下减小模型体积。</p>
<h5>三、具体压缩策略与实现</h5>
<p>为了将LLMs的体积缩小10倍同时保持性能，我们可以结合上述压缩技术，制定具体的压缩策略。</p>
<h6>1. 量化与剪枝结合</h6>
<p>首先，我们可以采用量化技术降低模型参数的精度。由于LLMs的权重数量巨大，通过量化可以显著减少模型的存储需求。然而，单纯的量化可能会导致性能下降，因此我们需要结合剪枝技术来进一步优化模型。</p>
<p>在剪枝过程中，我们可以采用结构化剪枝方法，移除对模型性能影响较小的层或注意力头。这种方法不仅可以减少模型的参数数量，还可以避免稀疏矩阵运算的问题。同时，我们可以根据剪枝后模型的性能反馈进行迭代剪枝，以找到最佳的剪枝比例和剪枝策略。</p>
<h6>2. 知识蒸馏优化</h6>
<p>在量化与剪枝的基础上，我们可以进一步利用知识蒸馏技术来优化学生模型的性能。首先，我们选择一个性能优异的大型教师模型作为知识源。然后，我们使用教师模型的输出或中间表示来训练学生模型。在训练过程中，我们可以通过调整损失函数和训练策略来最大化地保留教师模型的知识。</p>
<p>需要注意的是，知识蒸馏的效果很大程度上取决于教师模型的选择和学生模型的初始化。因此，在选择教师模型时，我们需要确保其性能优异且易于迁移。同时，在初始化学生模型时，我们可以采用预训练的方法来提高其学习效率。</p>
<h6>3. 示例：SliceGPT的应用</h6>
<p>SliceGPT是一种新型的模型压缩方法，通过删除权重矩阵中的行和列来降低模型的嵌入维数，同时保持性能。这种方法与传统的剪枝方法不同，因为它剪掉的是整个行或列，而不是单个元素。这种剪枝方式能够更有效地降低计算需求，同时保持模型的性能。</p>
<p>在SliceGPT的实现过程中，我们首先对网络进行转换，使预测结果保持不变。然后，我们对权重矩阵进行稀疏化处理，将某些元素设置为零。通过选择合适的稀疏模式，我们可以在保持模型性能的同时降低其计算复杂度。最后，我们对稀疏化后的模型进行剪枝处理，移除不必要的行和列。</p>
<p>实验表明，SliceGPT在多个大型语言模型上取得了显著成果。例如，在LLAMA-2 70B、OPT 66B和Phi-2模型上，使用SliceGPT处理后，参数量可以减少多达25%，同时保持密集模型99%、99%和90%的零样本任务性能。这表明SliceGPT在降低计算成本和保持性能方面具有显著优势。</p>
<h5>四、挑战与未来展望</h5>
<p>尽管压缩LLMs的方法多种多样，但在实际应用中仍面临诸多挑战。首先，如何在压缩过程中保持模型的性能是一个关键问题。过度的压缩可能会导致模型性能显著下降，从而影响其在实际应用中的效果。其次，压缩后的模型需要能够在不同的硬件平台上高效运行。这要求我们在压缩过程中充分考虑硬件的特性和限制。</p>
<p>未来，随着深度学习技术的不断发展，我们期待更多创新的压缩方法涌现。例如，可以结合自动机器学习（AutoML）技术来自动搜索最优的压缩策略；可以利用神经网络架构搜索（NAS）技术来设计更加紧凑高效的模型结构；还可以探索新的训练方法和优化算法来提高压缩后模型的性能。</p>
<h5>五、结论</h5>
<p>通过量化、剪枝和知识蒸馏等多种技术手段的结合应用，我们可以将LLMs的体积缩小10倍同时保持其原有的性能。这不仅有助于降低模型的存储和计算需求，还有助于推动LLMs在消费级设备上的广泛应用。未来，随着技术的不断进步和创新方法的不断涌现，我们有理由相信LLMs的压缩技术将会取得更加显著的成果。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141858905</p>]]></description><guid isPermaLink="false">/archives/29041c06-75d2-4830-89f4-2e7e10f3e604</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 09:38:18 GMT</pubDate></item><item><title><![CDATA[Adobe Acrobat Reader DC]]></title><link>https://wiki.dgfygk.com/archives/ac509f67-c109-4952-a134-9abf2b40e523</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Adobe%20Acrobat%20Reader%20DC&amp;url=/archives/ac509f67-c109-4952-a134-9abf2b40e523" width="1" height="1" alt="" style="opacity:0;">
<p>Adobe Acrobat Reader DC作为PDF领域的佼佼者，不仅提供了基本的PDF查看和阅读功能，还集成了众多高级功能，极大地提升了用户处理PDF文件的效率和便捷性。以下是对Adobe Acrobat Reader DC高级功能的深入探索：</p>
<h4>一、编辑与注释功能</h4>
<h5>1. 文本与图片编辑</h5>
<p>Adobe Acrobat Reader DC允许用户直接在PDF文件上进行文本和图片的编辑。这意味着用户无需将PDF转换为其他格式即可进行修改，大大提高了工作效率。用户可以轻松地添加、删除或修改文本内容，调整字体、大小和颜色，以及移动、缩放或旋转图片。这一功能对于需要快速修改PDF文件内容的用户来说，无疑是一大福音。</p>
<h5>2. 注释与标记</h5>
<p>除了编辑功能外，Adobe Acrobat Reader DC还提供了丰富的注释和标记工具。用户可以在PDF文件中添加注释、高亮文本、划下划线、添加文本框等，以便对文档进行详细的说明和标注。这些注释和标记可以保存为文档的一部分，方便后续查阅和分享。</p>
<h4>二、表单填写与签名</h4>
<h5>1. 表单填写</h5>
<p>Adobe Acrobat Reader DC支持用户在线填写PDF表单，无需打印和手工填写。用户只需点击表单中的相应字段，即可输入所需信息。这一功能不仅提高了表单填写的效率，还减少了纸张浪费，符合环保理念。</p>
<h5>2. 电子签名</h5>
<p>在数字化时代，电子签名已成为一种常见的文件签署方式。Adobe Acrobat Reader DC提供了强大的电子签名功能，允许用户在PDF文件上添加电子签名。这些签名可以是手写签名、数字证书签名或图片签名等。电子签名的使用不仅确保了文件的真实性和可信度，还简化了签署流程，提高了工作效率。</p>
<h4>三、文件管理与协作</h4>
<h5>1. 文件管理</h5>
<p>Adobe Acrobat Reader DC提供了强大的文件管理功能，用户可以轻松地浏览、搜索和组织PDF文件。软件支持多种文件排序和分组方式，让用户能够快速地找到所需文件。此外，用户还可以将PDF文件上传到Adobe Document Cloud，实现跨设备的云存储和访问。</p>
<h5>2. 协作共享</h5>
<p>在团队协作中，共享和编辑PDF文件是常见的需求。Adobe Acrobat Reader DC支持在线协作功能，允许多个用户同时编辑和评论一个PDF文件。这种协作方式不仅提高了工作效率，还确保了文档在不同用户间的一致性。此外，用户还可以通过电子邮件、社交媒体等方式分享PDF文件，方便快捷地与他人共享信息。</p>
<h4>四、安全与隐私保护</h4>
<h5>1. 密码保护</h5>
<p>Adobe Acrobat Reader DC允许用户为PDF文件设置密码保护，以防止未经授权的访问和修改。这一功能对于保护敏感信息和隐私数据具有重要意义。用户可以根据自己的需求设置不同的密码保护级别，以确保文件的安全性。</p>
<h5>2. 权限设置</h5>
<p>除了密码保护外，Adobe Acrobat Reader DC还提供了权限设置功能。用户可以为PDF文件设置不同的权限级别，如打印、编辑、复制等，以控制文件的使用方式。这一功能有助于确保文件在传输和使用过程中的安全性和合规性。</p>
<h4>五、其他高级功能</h4>
<h5>1. 丰富的阅读模式</h5>
<p>Adobe Acrobat Reader DC提供了多种阅读模式，如单页视图、连续视图、双页视图等，以满足用户在不同场景下的阅读需求。此外，软件还支持全屏阅读模式和夜间模式，为用户提供更加舒适的阅读体验。</p>
<h5>2. 搜索与查找</h5>
<p>用户可以通过Adobe Acrobat Reader DC的搜索和查找功能快速定位PDF文件中的特定内容或关键词。这一功能对于处理大量PDF文件或查找特定信息的用户来说非常实用。</p>
<h5>3. 导出与转换</h5>
<p>用户可以将PDF文件导出为其他格式，如Word、Excel或图片文件等，以便进行进一步的编辑和处理。此外，软件还支持将其他格式的文件转换为PDF格式，以满足用户的不同需求。</p>
<h4>六、总结</h4>
<p>Adobe Acrobat Reader DC作为一款功能强大的PDF阅读软件，不仅提供了基本的阅读功能，还集成了众多高级功能，如编辑与注释、表单填写与签名、文件管理与协作、安全与隐私保护等。这些功能极大地提升了用户处理PDF文件的效率和便捷性，使得Adobe Acrobat Reader DC成为众多用户处理PDF文件的首选工具。无论是个人用户还是企业用户，都能从Adobe Acrobat Reader DC中受益，实现更高效的文档处理和管理。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141704268</p>]]></description><guid isPermaLink="false">/archives/ac509f67-c109-4952-a134-9abf2b40e523</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 09:37:55 GMT</pubDate></item><item><title><![CDATA[CentOS 7的yum源更换为国内源]]></title><link>https://wiki.dgfygk.com/archives/10333ae1-6841-425a-ad99-356b4f5350d7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=CentOS%207%E7%9A%84yum%E6%BA%90%E6%9B%B4%E6%8D%A2%E4%B8%BA%E5%9B%BD%E5%86%85%E6%BA%90&amp;url=/archives/10333ae1-6841-425a-ad99-356b4f5350d7" width="1" height="1" alt="" style="opacity:0;">
<p>将CentOS 7的yum源更换为国内源是一个相对简单的操作，可以显著提高软件包的下载速度和稳定性。以下是一个保姆级教程，帮助用户逐步完成这一过程：</p>
<h4>一、前期准备</h4>
<ol>
 <li>
  <p><strong>确保已安装wget或curl工具</strong> ：这些工具用于从网上下载文件。如果系统中未安装，可以通过运行<code>yum install -y wget</code>或<code>yum install -y curl</code>来安装。</p>
 </li>
 <li>
  <p><strong>备份原yum源配置文件</strong> ：在进行任何更改之前，建议备份原配置文件。可以通过运行<code>sudo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup</code>来实现。</p>
 </li>
</ol>
<h4>二、选择并下载国内yum源</h4>
<ol>
 <li>
  <p><strong>选择国内镜像源</strong> ：国内有许多可靠的镜像源可供选择，如阿里云、网易、中科大等。以阿里云为例，其CentOS 7的yum源配置文件URL为<code>http://mirrors.aliyun.com/repo/Centos-7.repo</code>。</p>
 </li>
 <li>
  <p><strong>下载国内yum源配置文件</strong> ：使用wget或curl工具下载选定的yum源配置文件。以wget为例，运行<code>sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo</code>。如果选择其他镜像源，只需将URL替换为相应镜像源的URL即可。</p>
 </li>
</ol>
<h4>三、清理并重建yum缓存</h4>
<ol>
 <li>
  <p><strong>清理yum缓存</strong> ：运行<code>sudo yum clean all</code>来清理旧的yum缓存数据。</p>
 </li>
 <li>
  <p><strong>重建yum缓存</strong> ：运行<code>sudo yum makecache</code>来根据新的yum源配置文件重建缓存。如果在此步骤中遇到错误，可能是因为配置文件中的某些变量（如<code>$releasever</code>）未正确解析。此时，可以尝试手动编辑<code>/etc/yum.repos.d/CentOS-Base.repo</code>文件，将<code>$releasever</code>替换为具体的版本号（如7）。</p>
 </li>
</ol>
<h4>四、验证yum源更换是否成功</h4>
<ol>
 <li>
  <p><strong>列出可用的软件包仓库</strong> ：运行<code>sudo yum repolist</code>来列出当前可用的软件包仓库及其状态。如果看到列表中的仓库地址已经更改为国内镜像源的地址，则表示yum源更换成功。</p>
 </li>
 <li>
  <p><strong>尝试安装软件包</strong> ：为了验证新的yum源是否可用，可以尝试安装一个软件包，如<code>sudo yum install -y httpd</code>。如果安装成功，则表示新的yum源已经可以正常工作了。</p>
 </li>
</ol>
<h4>五、其他注意事项</h4>
<ul>
 <li><strong>备份重要数据</strong>：在进行系统更改之前，建议备份重要数据以防万一。</li>
 <li><strong>定期检查yum源</strong>：由于网络环境和镜像源状态可能会发生变化，建议定期检查并更新yum源配置文件以确保其可用性和稳定性。</li>
 <li><strong>注意权限问题</strong>：在执行上述命令时，请确保具有足够的权限（通常需要使用sudo前缀或以root用户身份登录）。</li>
</ul>
<p>通过以上步骤，您可以轻松地将CentOS 7的yum源更换为国内源，从而提高软件包的下载速度和稳定性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141609916</p>]]></description><guid isPermaLink="false">/archives/10333ae1-6841-425a-ad99-356b4f5350d7</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 09:37:45 GMT</pubDate></item><item><title><![CDATA[PowerShell脚本在自动化]]></title><link>https://wiki.dgfygk.com/archives/29fe48cb-5f3b-485b-ae6a-725f5ee8c3c4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=PowerShell%E8%84%9A%E6%9C%AC%E5%9C%A8%E8%87%AA%E5%8A%A8%E5%8C%96&amp;url=/archives/29fe48cb-5f3b-485b-ae6a-725f5ee8c3c4" width="1" height="1" alt="" style="opacity:0;">
<p>在Windows开发环境中，PowerShell脚本编写是自动化工作流程的强大工具。PowerShell不仅提供了丰富的命令集来管理Windows系统，还支持脚本编写，允许开发者创建复杂的自动化任务来简化日常开发流程。以下将详细探讨如何使用PowerShell脚本自动化Windows开发工作流程，涵盖环境设置、依赖管理、代码编译、测试执行、部署以及持续集成/持续部署（CI/CD）等方面。</p>
<h4>一、环境设置</h4>
<h5>1. 安装开发工具</h5>
<p>首先，开发环境的自动化设置通常包括安装必要的开发工具，如Visual Studio、.NET SDK、Node.js（如果项目涉及JavaScript/TypeScript）等。PowerShell可以使用<code>chocolatey</code>这样的包管理器来自动化安装过程。</p>
<pre><code class="prism language-powershell">choco install visualstudio2019community
choco install nodejs.install
choco install dotnet-sdk
</code></pre>
<h5>2. 配置环境变量</h5>
<p>环境变量是Windows系统中用于指定操作系统运行环境的一些动态值，如系统路径、库文件位置等。PowerShell可以很容易地添加、修改或删除环境变量。</p>
<pre><code class="prism language-powershell"># 添加环境变量
$env:Path += ";C:\MyTools\bin"
[Environment]::SetEnvironmentVariable("Path", $env:Path, [EnvironmentVariableTarget]::Machine)

# 或者修改注册表来永久添加
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name "Path" -Value ($env:Path + ";C:\MyTools\bin")
</code></pre>
<h4>二、依赖管理</h4>
<h5>1. NuGet包管理</h5>
<p>对于.NET项目，NuGet是主要的包管理工具。PowerShell脚本可以使用<code>dotnet</code> CLI来自动化NuGet包的安装和更新。</p>
<pre><code class="prism language-powershell"># 还原NuGet包
dotnet restore

# 添加NuGet包
dotnet add package Newtonsoft.Json

# 更新NuGet包
dotnet outdated
# 接着手动或通过脚本处理更新
</code></pre>
<h5>2. npm包管理</h5>
<p>对于Node.js项目，npm是主要的包管理工具。PowerShell脚本可以调用npm命令来管理依赖。</p>
<pre><code class="prism language-powershell"># 安装npm包
npm install express

# 更新npm包
npm update

# 清理未使用的npm包
npm prune
</code></pre>
<h4>三、代码编译</h4>
<h5>1. .NET项目</h5>
<p>对于.NET项目，<code>dotnet</code> CLI提供了强大的编译功能。</p>
<pre><code class="prism language-powershell"># 编译项目
dotnet build

# 发布项目
dotnet publish -c Release -o ./output
</code></pre>
<h5>2. C/C++项目</h5>
<p>如果项目包含C/C++代码，可能需要使用MSBuild或CMake等工具进行编译。</p>
<pre><code class="prism language-powershell"># 使用MSBuild编译C#和C++项目
msbuild MyProject.sln /p:Configuration=Release

# 使用CMake编译C++项目
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
</code></pre>
<h4>四、测试执行</h4>
<p>自动化测试是现代软件开发中不可或缺的一部分。PowerShell可以调用测试框架来执行单元测试、集成测试等。</p>
<h5>1. NUnit测试</h5>
<p>对于.NET项目，NUnit是一个流行的单元测试框架。</p>
<pre><code class="prism language-powershell"># 使用dotnet CLI执行NUnit测试
dotnet test

# 或者直接使用NUnit Console
nunit3-console.exe .\MyTests\bin\Debug\netcoreapp3.1\MyTests.dll
</code></pre>
<h5>2. Jest测试</h5>
<p>对于JavaScript/TypeScript项目，Jest是一个流行的测试框架。</p>
<pre><code class="prism language-powershell"># 执行Jest测试
npx jest
</code></pre>
<h4>五、部署</h4>
<p>部署是将软件应用程序和配置文件安装到目标环境的过程。PowerShell脚本可以自动化部署过程，包括复制文件、更新配置文件、重启服务等。</p>
<pre><code class="prism language-powershell"># 复制文件到部署目录
xcopy /s /y .\output\* C:\DeploymentFolder\

# 更新配置文件
(Get-Content C:\DeploymentFolder\appsettings.json) | 
    ForEach-Object {
    $_ -replace '"OldValue"', '"NewValue"' } | 
    Set-Content C:\DeploymentFolder\appsettings.json

# 重启服务
Restart-Service MyService
</code></pre>
<h4>六、持续集成/持续部署（CI/CD）</h4>
<p>PowerShell脚本可以与CI/CD工具（如Azure DevOps、GitHub Actions、Jenkins等）集成，实现自动化的构建、测试和部署流程。</p>
<h5>1. Azure DevOps</h5>
<p>在Azure DevOps中，可以创建Pipeline来定义CI/CD流程。PowerShell脚本可以作为Pipeline中的任务来执行。</p>
<pre><code class="prism language-yaml"># Azure Pipelines YAML
steps:
- task: PowerShell@2
  inputs:
    targetType: 'filePath'
    filePath: 'build.ps1'
    arguments: '-configuration Release'
</code></pre>
<h5>2. GitHub Actions</h5>
<p>GitHub Actions允许你在GitHub仓库中直接定义CI/CD工作流。PowerShell脚本可以作为工作流中的一个步骤执行。</p>
<pre><code class="prism language-yaml"># GitHub Actions YAML
jobs:
  build:
    runs-on: windows-latest
    steps:
    - name: Execute PowerShell script
      run: .\build.ps1 -configuration Release
</code></pre>
<h4>七、总结</h4>
<p>PowerShell脚本在自动化Windows开发工作流程中发挥着重要作用。通过编写PowerShell脚本，开发者可以轻松地管理开发环境、依赖、编译、测试、部署以及CI/CD流程，从而提高开发效率，减少人为错误。随着PowerShell功能的不断完善和扩展，其在Windows开发领域的应用前景将更加广阔。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141704100</p>]]></description><guid isPermaLink="false">/archives/29fe48cb-5f3b-485b-ae6a-725f5ee8c3c4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 09:37:30 GMT</pubDate></item><item><title><![CDATA[Windows Edge浏览器的兼容性问题]]></title><link>https://wiki.dgfygk.com/archives/1f89b3c3-3777-4dc2-8fb2-22494fec4fdc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Windows%20Edge%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84%E5%85%BC%E5%AE%B9%E6%80%A7%E9%97%AE%E9%A2%98&amp;url=/archives/1f89b3c3-3777-4dc2-8fb2-22494fec4fdc" width="1" height="1" alt="" style="opacity:0;">
<p>针对Windows Edge浏览器的兼容性问题，我们可以采取一系列措施来尝试修复。以下是一系列详细的解决步骤和策略，旨在帮助用户解决在使用Edge浏览器时遇到的兼容性问题。</p>
<h4>一、检查并更新Edge浏览器</h4>
<p><strong>1. 自动更新检查</strong></p>
<ul>
 <li>打开Edge浏览器，点击右上角的三个点图标，选择"帮助和反馈"或"设置”（根据Edge版本可能有所不同）。</li>
 <li>在弹出的菜单中，找到"关于Microsoft Edge"选项并点击。</li>
 <li>系统将自动检查是否有可用的更新。如果有，点击"更新"按钮进行安装。</li>
</ul>
<p><strong>2. 手动下载最新版本</strong></p>
<ul>
 <li>如果自动更新未解决问题，您可以访问微软官网下载Edge浏览器的最新版本安装包。</li>
 <li>下载后，按照安装向导的指示进行安装。</li>
</ul>
<h4>二、禁用或管理扩展程序</h4>
<p><strong>1. 禁用扩展程序</strong></p>
<ul>
 <li>第三方扩展程序有时可能与Edge浏览器产生冲突，导致兼容性问题。</li>
 <li>打开Edge浏览器，点击右上角的三个点图标，选择"扩展"或"管理扩展”。</li>
 <li>在扩展管理界面中，逐一禁用扩展程序，然后重新加载网页以查看问题是否解决。</li>
</ul>
<p><strong>2. 查找并删除不兼容的扩展</strong></p>
<ul>
 <li>如果确定某个扩展程序与Edge不兼容，可以选择将其从浏览器中删除。</li>
</ul>
<h4>三、清除缓存和Cookie</h4>
<p><strong>1. 清除浏览数据</strong></p>
<ul>
 <li>缓存和Cookie文件可能导致浏览器运行缓慢或出现兼容性问题。</li>
 <li>打开Edge浏览器设置，找到"隐私、搜索和服务"选项。</li>
 <li>在左侧菜单中选择"清除浏览数据”，然后选择要清除的数据类型（如缓存文件、Cookies等），点击"清除数据"按钮。</li>
</ul>
<h4>四、使用兼容性视图或IE模式</h4>
<p><strong>1. 兼容性视图设置</strong></p>
<ul>
 <li>如果网站是为旧版浏览器设计的，Edge浏览器可能无法正确显示。</li>
 <li>尝试在Edge浏览器中启用兼容性视图。这通常可以通过右键点击页面，查看是否有"显示此页面的兼容性视图"选项来实现（注意：此选项并非所有版本的Edge都支持）。</li>
</ul>
<p><strong>2. IE模式</strong></p>
<ul>
 <li>Edge浏览器内置了IE模式，可以模拟旧版IE浏览器的行为。</li>
 <li>打开Edge浏览器设置，寻找与"IE模式"相关的设置选项，启用并配置需要使用IE模式的网站列表。</li>
</ul>
<h4>五、调整注册表设置</h4>
<p><strong>注意：修改注册表是一项高级操作，如果不熟悉，请务必谨慎进行，并在操作前备份注册表。</strong></p>
<ul>
 <li>使用Win+R组合键打开运行对话框，输入<code>regedit</code>并按回车键，打开注册表编辑器。</li>
 <li>导航到<code>HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge</code>（对于某些用户，路径可能略有不同）。</li>
 <li>在右侧窗口中，右键单击并选择新建一个DWORD（32位）值。</li>
 <li>将新建的值命名为<code>RendererCodeIntegrityEnabled</code>，并将值数据设置为<code>0</code>。</li>
 <li>关闭注册表编辑器并重启Edge浏览器，查看问题是否解决。</li>
</ul>
<h4>六、禁用硬件加速</h4>
<ul>
 <li>某些兼容性问题可能是由硬件加速引起的。</li>
 <li>打开Edge浏览器设置，找到"系统"选项。</li>
 <li>在其中找到"使用硬件加速"选项，并关闭它。</li>
</ul>
<h4>七、重置浏览器设置</h4>
<ul>
 <li>如果以上方法都无法解决问题，可以尝试重置Edge浏览器的设置。</li>
 <li>在浏览器设置中找到"重置设置"选项，并选择将浏览器设置还原为默认值。请注意，这将删除所有已保存的浏览器数据（如书签、历史记录等），因此请确保提前备份重要数据。</li>
</ul>
<h4>八、检查系统和软件冲突</h4>
<ul>
 <li>有时，兼容性问题可能是由操作系统或其他软件与Edge浏览器的冲突引起的。</li>
 <li>确保您的操作系统和所有其他软件都是最新版本，并检查是否有任何已知的冲突问题。</li>
</ul>
<h4>九、寻求专业帮助</h4>
<ul>
 <li>如果以上方法都不能解决您的兼容性问题，建议向微软官方或专业的技术支持团队寻求帮助。</li>
 <li>您可以访问微软官网的支持页面，查找相关的帮助文档或联系客服人员。</li>
</ul>
<h4>十、考虑使用其他浏览器</h4>
<ul>
 <li>如果Edge浏览器的兼容性问题持续存在且无法解决，您可以考虑暂时使用其他主流浏览器（如Chrome、Firefox等）作为替代方案。这些浏览器也提供了丰富的功能和良好的兼容性支持。</li>
</ul>
<p>总之，解决Windows Edge浏览器的兼容性问题需要耐心和细致的操作。通过上述方法，您应该能够解决大部分常见的兼容性问题。如果问题依然存在，请不要犹豫向专业人士寻求帮助。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141642555</p>]]></description><guid isPermaLink="false">/archives/1f89b3c3-3777-4dc2-8fb2-22494fec4fdc</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 09:25:24 GMT</pubDate></item><item><title><![CDATA[基于Prometheus和Grafana的现代服务器监控体系构建]]></title><link>https://wiki.dgfygk.com/archives/efc372e1-6e42-4cc8-b6c3-23dc4c238432</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8EPrometheus%E5%92%8CGrafana%E7%9A%84%E7%8E%B0%E4%BB%A3%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9B%91%E6%8E%A7%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA&amp;url=/archives/efc372e1-6e42-4cc8-b6c3-23dc4c238432" width="1" height="1" alt="" style="opacity:0;">
<h4><strong>基于Prometheus和Grafana的现代服务器监控体系构建</strong></h4>
<p>在当前的数字时代，实时监控已经成为企业和组织运营中不可或缺的一部分。随着微服务架构和容器技术的普及，传统的监控方法已难以满足业务需求。Prometheus和Grafana作为两个非常受欢迎的开源项目，能够帮助我们构建高效、可扩展的实时监控系统。本文将详细介绍如何基于Prometheus和Grafana构建现代服务器监控体系。</p>
<h5>一、引言</h5>
<p>监控系统是运维系统的基础，它实时跟踪服务器、应用系统以及其他第三方组件的运行状态。一个完善的监控系统能够提高应用的可用性和可靠性，降低运维投入和工作量，为用户带来更多的商业利益和客户体验。Prometheus和Grafana的结合为构建现代监控体系提供了强有力的支持。</p>
<h5>二、Prometheus与Grafana简介</h5>
<h6>2.1 Prometheus</h6>
<p>Prometheus是一个开源的实时监控系统，由云原生计算基金会（CNCF）托管。它提供了一个高性能的时间序列数据库和一个强大的查询语言（PromQL）。Prometheus的主要特点包括：</p>
<ul>
 <li><strong>多维数据模型</strong>：通过指标名称和键值维度集定义的时间序列。</li>
 <li><strong>PromQL</strong>：一种强大且灵活的查询语言，可用于复杂的数据查询和聚合。</li>
 <li><strong>不依赖分布式存储</strong>：单个服务器节点是自治的，易于部署和维护。</li>
 <li><strong>HTTP拉取模型</strong>：通过HTTP协议从配置的目标拉取指标数据。</li>
 <li><strong>服务发现</strong>：支持通过静态配置或自动发现机制发现监控目标。</li>
</ul>
<h6>2.2 Grafana</h6>
<p>Grafana是一个开源的数据可视化平台，用于监控、分析和展示数据。Grafana支持多种数据源，包括Prometheus、InfluxDB、Graphite等，并且提供了丰富的可视化图表和仪表盘。其主要特点包括：</p>
<ul>
 <li><strong>多数据源支持</strong>：可以连接多种数据源，提供统一的数据展示平台。</li>
 <li><strong>丰富的可视化选项</strong>：支持多种图表类型，如折线图、饼图、热力图等。</li>
 <li><strong>灵活的仪表盘配置</strong>：用户可以自定义仪表盘布局和图表配置。</li>
 <li><strong>报警功能</strong>：支持设置报警规则，并在触发报警时发送通知。</li>
</ul>
<h5>三、Prometheus与Grafana的集成</h5>
<h6>3.1 安装与启动</h6>
<p>首先，需要安装Prometheus和Grafana。这里以Docker容器为例进行说明。</p>
<ul>
 <li>
  <p><strong>安装Prometheus</strong>：</p>
  <pre><code class="prism language-bash">docker pull prom/prometheus:v2.35.0
docker run --name prometheus -d -p 9090:9090 prom/prometheus:v2.35.0
</code></pre>
 </li>
 <li>
  <p><strong>安装Grafana</strong>：</p>
  <pre><code class="prism language-bash">docker pull grafana/grafana:7.5.16
docker run -d --name=grafana -p 3000:3000 grafana/grafana:7.5.16
</code></pre>
 </li>
</ul>
<h6>3.2 配置Prometheus</h6>
<p>Prometheus的配置主要通过<code>prometheus.yml</code>文件实现。该文件定义了Prometheus如何发现监控目标、采集指标以及处理规则等。</p>
<ul>
 <li>
  <p><strong>示例配置</strong> ：</p>
  <pre><code class="prism language-yaml">global:
scrape_interval: 15s
evaluation_interval: 15s

scrape_configs:
- job_name: 'prometheus'
  static_configs:
    - targets: ['localhost:9090']
- job_name: 'linux'
  static_configs:
    - targets: ['&lt;node-exporter-ip&gt;:9100']
</code></pre>
 </li>
</ul>
<p>其中，<code>&lt;node-exporter-ip&gt;</code>是Node Exporter的IP地址，用于采集Linux服务器的性能指标。</p>
<h6>3.3 配置Grafana</h6>
<p>在Grafana中，需要配置Prometheus作为数据源。</p>
<ul>
 <li><strong>添加数据源</strong> ：</li>
 <li>登录Grafana（默认用户名和密码为admin）。</li>
 <li>进入"配置”-&gt;“数据源”-&gt;“添加数据源”。</li>
 <li>选择Prometheus作为数据源类型，并填写Prometheus的地址（如<code>http://&lt;prometheus-ip&gt;:9090</code>）。</li>
</ul>
<h6>3.4 创建仪表盘和图表</h6>
<p>在Grafana中，可以创建仪表盘并添加图表来展示Prometheus采集的数据。</p>
<ul>
 <li><strong>创建仪表盘</strong> ：</li>
 <li>点击”+“-&gt;“仪表盘”-&gt;“新建仪表盘”。</li>
 <li>选择"添加面板"来添加图表。</li>
 <li>在"数据源"中选择之前配置的Prometheus数据源。</li>
 <li>使用PromQL编写查询语句来获取需要展示的数据。</li>
</ul>
<h5>四、监控对象与指标</h5>
<p>监控系统主要关注服务器资源、应用监控和数据库中间件等方面。以下是一些常见的监控对象和指标：</p>
<h6>4.1 服务器资源监控</h6>
<ul>
 <li><strong>CPU使用率</strong>：表示CPU的繁忙程度。</li>
 <li><strong>内存使用率</strong>：表示内存的使用情况。</li>
 <li><strong>磁盘使用率</strong>：表示磁盘的占用情况。</li>
 <li><strong>网络流量</strong>：表示进出服务器的网络数据量。</li>
</ul>
<p>可以使用Node Exporter来采集这些指标。Node Exporter是一个独立的程序，能够获取到Linux系统的各种性能指标，并通过HTTP服务暴露出来。</p>
<h6>4.2 应用监控</h6>
<p>应用监控主要关注应用的运行状态和性能指标，如响应时间、吞吐量等。这些指标可以通过应用本身暴露的HTTP接口或使用Exporter来采集。</p>
<h6>4.3 数据库监控</h6>
<p>数据库监控关注数据库的TPS、QPS、连接数、慢查询等指标。对于MySQL数据库，可以使用mysqld_exporter来采集这些指标。</p>
<h5>五、警报与通知</h5>
<p>监控系统的一个重要功能是警报与通知。当监控到异常情况时，系统能够自动触发警报，并通过邮件、短信、Slack等方式通知相关人员。</p>
<ul>
 <li>
  <p><strong>Prometheus Alertmanager</strong> ：
   <br>
   Prometheus提供了Alertmanager组件来处理警报。Alertmanager负责接收Prometheus发送的警报，并根据配置进行去重、分组、路由和静默等操作。最终，Alertmanager将警报发送给指定的通知渠道。</p>
 </li>
 <li>
  <p><strong>Grafana警报</strong> ：
   <br>
   Grafana也支持警报功能，但它更多地是作为一个展示平台。Grafana可以接收Prometheus发送的警报，并在仪表盘上显示警报状态。同时，Grafana也可以将警报信息传递给外部系统，如Alertmanager。</p>
 </li>
</ul>
<h5>六、优化与扩展</h5>
<h6>6.1 性能优化</h6>
<p>随着监控规模的扩大，Prometheus和Grafana的性能可能会成为瓶颈。以下是一些性能优化的建议：</p>
<ul>
 <li><strong>合理设置采样频率</strong>：避免过高的采样频率导致数据过多。</li>
 <li><strong>使用分区与分片</strong>：对于大规模集群，可以将数据分区存储在不同节点上。</li>
 <li><strong>使用缓存</strong>：对于频繁查询的数据，可以使用缓存来提高查询效率。</li>
</ul>
<h6>6.2 扩展性</h6>
<p>为了支持更大规模的监控需求，Prometheus和Grafana提供了多种扩展方式：</p>
<ul>
 <li><strong>Prometheus联邦集群</strong>：通过Prometheus联邦集群实现跨多个Prometheus实例的数据聚合和查询。</li>
 <li><strong>Grafana插件</strong>：Grafana支持丰富的插件生态，可以通过安装插件来扩展功能。</li>
</ul>
<h5>七、总结</h5>
<p>基于Prometheus和Grafana的现代服务器监控体系构建是一个复杂但高效的过程。通过合理的配置和扩展，可以构建一个功能强大、灵活易用的监控系统。该系统能够实时监控服务器、应用和数据库等关键组件的运行状态，并在出现异常情况时及时发出警报。这不仅能够提高应用的可用性和可靠性，还能够降低运维投入和工作量，为用户带来更多的商业利益和客户体验。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141676831</p>]]></description><guid isPermaLink="false">/archives/efc372e1-6e42-4cc8-b6c3-23dc4c238432</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 09:17:04 GMT</pubDate></item><item><title><![CDATA[网络自动化：利用Python和Ansible实现网络配置管理]]></title><link>https://wiki.dgfygk.com/archives/75b02011-eb3a-4ad1-add8-441b7cd37926</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%BB%9C%E8%87%AA%E5%8A%A8%E5%8C%96%EF%BC%9A%E5%88%A9%E7%94%A8Python%E5%92%8CAnsible%E5%AE%9E%E7%8E%B0%E7%BD%91%E7%BB%9C%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86&amp;url=/archives/75b02011-eb3a-4ad1-add8-441b7cd37926" width="1" height="1" alt="" style="opacity:0;">
<h4>网络自动化：利用Python和Ansible实现网络配置管理</h4>
<p>在当今快速变化的IT环境中，网络自动化已成为提升运维效率、减少人为错误、加速服务交付的关键技术之一。Python和Ansible作为两种强大的工具，在网络配置管理中发挥着重要作用。本文将深入探讨如何利用Python和Ansible实现网络自动化，包括它们的基本概念、优势、应用场景、实施步骤以及最佳实践。</p>
<h5>一、引言</h5>
<p>网络配置管理是指对网络设备进行配置、监控、维护和优化的过程。传统上，这些任务大多依赖人工手动执行，不仅效率低下，而且容易出错。随着云计算、SDN（软件定义网络）和自动化技术的兴起，网络自动化成为解决这些问题的有效途径。Python和Ansible作为自动化领域的佼佼者，因其易用性、灵活性和强大的功能而备受青睐。</p>
<h5>二、Python在网络自动化中的角色</h5>
<p>Python是一种高级编程语言，以其简洁的语法、丰富的库和强大的社区支持而闻名。在网络自动化中，Python主要用于以下几个方面：</p>
<ol>
 <li><strong>脚本编写</strong>：Python可以编写各种自动化脚本，用于执行网络设备的配置、状态检查、数据收集等任务。</li>
 <li><strong>API交互</strong>：许多网络设备支持通过REST API或NetConf等协议进行远程管理。Python通过其强大的库（如requests、paramiko等）可以轻松与这些API交互，实现自动化配置和管理。</li>
 <li><strong>数据处理</strong>：Python拥有强大的数据处理能力，可以处理和分析从网络设备收集的大量数据，为网络优化和故障排除提供有力支持。</li>
 <li><strong>集成与扩展</strong>：Python可以与其他自动化工具（如Ansible）无缝集成，扩展自动化能力，实现更复杂的网络管理任务。</li>
</ol>
<h5>三、Ansible在网络自动化中的应用</h5>
<p>Ansible是一个开源的自动化平台，它使用SSH协议与远程节点通信，无需在远程节点上安装代理或客户端软件。Ansible通过"playbooks”（剧本）来定义自动化任务，这些剧本由一系列的任务（tasks）组成，每个任务都执行特定的操作。在网络自动化中，Ansible的优势包括：</p>
<ol>
 <li><strong>简单易用</strong>：Ansible的语法简单易懂，学习曲线平缓，即使是初学者也能快速上手。</li>
 <li><strong>跨平台支持</strong>：Ansible支持多种操作系统和网络设备，能够统一管理异构网络环境。</li>
 <li><strong>幂等性</strong>：Ansible的任务执行具有幂等性，即无论执行多少次，结果都保持一致，这有助于减少配置漂移。</li>
 <li><strong>模块化设计</strong>：Ansible拥有丰富的模块库，涵盖了网络配置、系统管理、应用部署等多个方面，用户可以根据需要选择合适的模块来构建自动化任务。</li>
</ol>
<h5>四、实施步骤</h5>
<p>利用Python和Ansible实现网络自动化通常包括以下几个步骤：</p>
<ol>
 <li><strong>需求分析</strong>：明确自动化目标，确定需要自动化的网络设备和任务。</li>
 <li><strong>环境准备</strong>：安装Python和Ansible，配置必要的库和模块。</li>
 <li><strong>网络设备准备</strong>：确保网络设备支持远程管理，并配置好相应的访问权限。</li>
 <li><strong>编写剧本</strong>：使用Ansible编写playbooks，定义自动化任务。对于需要更复杂逻辑处理的任务，可以使用Python编写自定义模块或脚本，并在Ansible中调用。</li>
 <li><strong>测试与验证</strong>：在测试环境中执行playbooks，验证自动化任务的正确性和有效性。</li>
 <li><strong>部署与监控</strong>：将验证通过的playbooks部署到生产环境，并设置监控机制以确保自动化任务的稳定运行。</li>
</ol>
<h5>五、最佳实践</h5>
<ol>
 <li><strong>版本控制</strong>：将Ansible的playbooks和Python脚本纳入版本控制系统（如Git），以便跟踪变更历史和团队协作。</li>
 <li><strong>文档化</strong>：为playbooks和Python脚本编写详细的文档，说明其用途、参数、依赖关系等，以便后续维护和扩展。</li>
 <li><strong>权限管理</strong>：严格控制对Ansible控制节点和网络设备的访问权限，防止未授权操作。</li>
 <li><strong>日志记录</strong>：启用Ansible和Python的日志记录功能，记录自动化任务的执行过程和结果，以便故障排查和性能优化。</li>
 <li><strong>持续集成/持续部署（CI/CD）</strong>：将自动化测试、部署和监控集成到CI/CD流程中，提高自动化任务的可靠性和效率。</li>
</ol>
<h5>六、案例分析</h5>
<p>假设我们需要自动化配置一组Cisco交换机的VLAN和接口设置。我们可以使用Ansible的<code>cisco.ios</code>模块来编写playbooks，实现这一需求。同时，如果需要对收集到的网络数据进行进一步处理或分析，我们可以编写Python脚本来完成这些任务，并在Ansible中调用这些脚本。</p>
<h5>七、结论</h5>
<p>网络自动化是现代网络管理不可或缺的一部分。Python和Ansible作为两种强大的工具，在网络配置管理中发挥着重要作用。通过结合使用Python和Ansible，我们可以实现高效、可靠、可扩展的网络自动化解决方案，提升运维效率，降低运维成本，为企业的数字化转型提供有力支持。未来，随着技术的不断发展，网络自动化将变得更加智能化和自动化，为企业创造更大的价值。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141677043</p>]]></description><guid isPermaLink="false">/archives/75b02011-eb3a-4ad1-add8-441b7cd37926</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Sat, 28 Sep 2024 08:47:03 GMT</pubDate></item><item><title><![CDATA[python读取excel数据]]></title><link>https://wiki.dgfygk.com/archives/d88dd14b-8653-4c34-99af-890e87895659</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=python%E8%AF%BB%E5%8F%96excel%E6%95%B0%E6%8D%AE&amp;url=/archives/d88dd14b-8653-4c34-99af-890e87895659" width="1" height="1" alt="" style="opacity:0;">
<p>在处理Excel数据时，Python 提供了多种强大的库来读取、处理以及分析这些数据。最常用的库之一是 <code>pandas</code>，它建立在 <code>numpy</code>、<code>matplotlib</code> 和 <code>scipy</code> 等库之上，为数据分析和操作提供了高级的、易于使用的数据结构和数据分析工具。另一个流行的库是 <code>openpyxl</code>，它专门用于读写Excel 2010 xlsx/xlsm/xltx/xltm文件。</p>
<h4>一、引言</h4>
<p>在数据科学、机器学习、财务分析等多个领域，Excel数据读取是一项基本技能。Excel以其直观的界面和强大的功能，成为数据收集和初步整理的首选工具。然而，对于复杂的数据分析任务，手动操作Excel可能既耗时又容易出错。因此，自动化地读取Excel数据变得尤为重要。Python的灵活性和强大的数据处理能力使其成为处理Excel数据的理想选择。</p>
<h4>二、准备工作</h4>
<h5>1. 安装必要的库</h5>
<p>在Python中读取Excel数据，首先需要安装 <code>pandas</code> 和 <code>openpyxl</code>（如果需要操作<code>.xlsx</code>文件）。可以通过pip命令来安装这些库：</p>
<pre><code class="prism language-bash">pip install pandas openpyxl
</code></pre>
<h5>2. Excel文件准备</h5>
<p>确保你有一个Excel文件（如<code>data.xlsx</code>），其中包含了你想读取的数据。为了演示，我们可以假设这个Excel文件有一个工作表（Sheet1），其中包含了若干列数据，如姓名、年龄、性别等。</p>
<h4>三、使用pandas读取Excel数据</h4>
<h5>1. 基本读取</h5>
<p><code>pandas</code>的<code>read_excel</code>函数是读取Excel文件的核心。它默认使用<code>xlrd</code>来读取<code>.xls</code>文件，但对于<code>.xlsx</code>文件，<code>pandas</code>建议使用<code>openpyxl</code>或<code>xlrd</code>（取决于你的pandas版本和安装的库）。</p>
<pre><code class="prism language-python">import pandas as pd

# 使用pandas读取Excel文件
df = pd.read_excel('data.xlsx', sheet_name='Sheet1')

# 显示前几行数据
print(df.head())
</code></pre>
<p>这里，<code>sheet_name</code>参数指定了要读取的工作表名称，如果是读取第一个工作表，也可以省略此参数或将其设置为0（注意索引是从0开始的）。</p>
<h5>2. 读取多个工作表</h5>
<p>如果你需要一次性读取Excel文件中的所有工作表，可以将<code>sheet_name</code>设置为<code>None</code>，这会返回一个字典，键是工作表名称，值是对应的数据框（DataFrame）。</p>
<pre><code class="prism language-python">dfs = pd.read_excel('data.xlsx', sheet_name=None)

# 遍历字典并打印每个工作表的前几行
for sheet_name, df in dfs.items():
    print(f"Sheet Name: {
     sheet_name}")
    print(df.head())
    print("\n")
</code></pre>
<h5>3. 指定列名和索引</h5>
<p>有时，Excel文件的列名并不符合你的分析需求，或者你可能想要将某一列设置为DataFrame的索引。<code>read_excel</code>函数允许你通过<code>header</code>和<code>index_col</code>参数来指定列名和索引列。</p>
<pre><code class="prism language-python"># 假设第一行是数据的一部分，从第二行开始是列名
df = pd.read_excel('data.xlsx', sheet_name='Sheet1', header=1)

# 假设你想将'ID'列设置为索引
df = pd.read_excel('data.xlsx', sheet_name='Sheet1', index_col='ID')
</code></pre>
<h5>4. 读取指定区域</h5>
<p><code>pandas</code>没有直接提供读取Excel文件指定区域的函数，但你可以通过<code>openpyxl</code>来读取数据后，再将其转换为<code>pandas</code>的DataFrame。不过，对于大多数需求，使用<code>pandas</code>的现有功能（如<code>skiprows</code>、<code>nrows</code>等）已经足够。</p>
<h4>四、使用openpyxl读取Excel数据</h4>
<p>虽然<code>pandas</code>在处理Excel数据时非常方便，但如果你需要更细致地控制读取过程（如读取非连续的区域、处理特定单元格的样式等），那么<code>openpyxl</code>将是更好的选择。</p>
<h5>1. 加载工作簿和工作表</h5>
<pre><code class="prism language-python">from openpyxl import load_workbook

# 加载工作簿
wb = load_workbook('data.xlsx')

# 获取工作表
sheet = wb['Sheet1']
</code></pre>
<h5>2. 读取单元格数据</h5>
<p><code>openpyxl</code>允许你通过行号和列号（或列名，如果定义了）来访问单元格数据。</p>
<pre><code class="prism language-python"># 通过行号和列号读取
cell_value = sheet.cell(row=1, column=2).value

# 如果设置了列名，并且希望通过列名访问（这通常需要额外处理）
# 例如，我们假设第一行是列名，我们可以遍历它们来找到我们想要的列
col_names = [cell.value for cell in sheet[1]]  # 假设第一行是列名
column_index = col_names.index('特定列名') + 1  # 索引从1开始
cell_value = sheet.cell(row=2, column=column_index).value
</code></pre>
<h5>3. 读取整行或整列</h5>
<pre><code class="prism language-python"># 读取整行
row_values = [cell.value for cell in sheet[1]]  # 读取第一行的所有值

# 读取整列（假设第一列是A列）
column_values = [sheet[f'A{
     i}'].value for i in range(1, sheet.max_row + 1)]
</code></pre>
<h5>4. 读取数据为列表或字典</h5>
<p>为了将数据从<code>openpyxl</code>工作表转换为列表或字典（类似<code>pandas</code> DataFrame的结构），你可能需要编写一些额外的代码来遍历行和列，并收集数据。</p>
<h4>五、性能与兼容性</h4>
<h5>1. 性能</h5>
<p>对于大型Excel文件，<code>pandas</code>和<code>openpyxl</code>的性能可能会有所不同。<code>pandas</code>在处理大量数据时通常更高效，因为它将数据加载到内存中，并提供了向量化操作来加速计算。然而，如果你的机器内存有限，或者你只需要读取文件的一小部分，那么<code>openpyxl</code>可能更适合。</p>
<h5>2. 兼容性</h5>
<p><code>pandas</code>和<code>openpyxl</code>都支持较新版本的Excel文件（<code>.xlsx</code>），但<code>pandas</code>在早期版本中可能依赖<code>xlrd</code>来读取<code>.xls</code>文件。从<code>pandas</code> 1.2.0版本开始，<code>xlrd</code>不再支持<code>.xlsx</code>文件，如果你需要读取<code>.xls</code>文件，请确保安装了适当版本的<code>xlrd</code>（或考虑将文件转换为<code>.xlsx</code>）。</p>
<h4>六、结论</h4>
<p>Python提供了多种方法来读取Excel数据，其中<code>pandas</code>和<code>openpyxl</code>是最常用的两个库。<code>pandas</code>以其易用性和强大的数据处理能力而广受欢迎，适合大多数数据分析任务。而<code>openpyxl</code>则提供了对Excel文件更细粒度的控制，适合需要直接操作单元格、行、列或样式的场景。选择哪个库取决于你的具体需求和个人偏好。希望本文能帮助你更好地理解如何在Python中读取Excel数据。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141885796</p>]]></description><guid isPermaLink="false">/archives/d88dd14b-8653-4c34-99af-890e87895659</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Sat, 28 Sep 2024 08:45:10 GMT</pubDate></item><item><title><![CDATA[在线图书管理系统”的Vue.js项目]]></title><link>https://wiki.dgfygk.com/archives/17c19070-59e3-417e-ac7c-e35387dd26fb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8%E7%BA%BF%E5%9B%BE%E4%B9%A6%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F%E2%80%9D%E7%9A%84Vue.js%E9%A1%B9%E7%9B%AE&amp;url=/archives/17c19070-59e3-417e-ac7c-e35387dd26fb" width="1" height="1" alt="" style="opacity:0;">
<h4>项目概述</h4>
<p>假设我们正在构建一个名为"在线图书管理系统"的Vue.js项目。该系统允许用户浏览图书列表、搜索图书、查看图书详情、添加新书到购物车以及进行用户认证（登录、注册）。我们将使用Vue.js 2.x（或Vue 3.x，视你偏好而定），结合Vue Router进行页面路由管理，Vuex进行状态管理，以及Axios进行HTTP请求。</p>
<h4>项目结构</h4>
<pre><code>/online-bookstore
  /src
    /assets
      - logo.png
    /components
      /BookList.vue
      /BookDetail.vue
      /Cart.vue
      /Login.vue
      /Register.vue
      ...
    /router
      - index.js
    /store
      - index.js
    /views
      /Home.vue
      /Search.vue
      /Profile.vue
      ...
    App.vue
    main.js
  /public
    - index.html
  package.json
  README.md
  ...
</code></pre>
<h4>关键技术点</h4>
<ol>
 <li><strong>Vue.js</strong>: 构建用户界面。</li>
 <li><strong>Vue Router</strong>: 管理单页面应用（SPA）的路由。</li>
 <li><strong>Vuex</strong>: 集中管理应用的所有组件的状态。</li>
 <li><strong>Axios</strong>: 发送HTTP请求到后端API。</li>
 <li><strong>Element UI</strong>（或其他UI库）: 提供丰富的UI组件，加快开发速度。</li>
</ol>
<h4>实现流程</h4>
<h5>1. 初始化项目</h5>
<p>使用Vue CLI工具创建一个新项目：</p>
<pre><code class="prism language-bash">vue create online-bookstore
</code></pre>
<p>选择合适的配置（如Vue 2.x/3.x，Babel, Router, Vuex, Linter等）。</p>
<h5>2. 安装依赖</h5>
<p>安装Axios和Element UI（或其他UI库）：</p>
<pre><code class="prism language-bash">npm install axios element-ui
</code></pre>
<p>在<code>main.js</code>中全局引入Element UI：</p>
<pre><code class="prism language-javascript">import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

new Vue({

  router,
  store,
  render: h =&gt; h(App),
}).$mount('#app')
</code></pre>
<h5>3. 设置路由</h5>
<p>在<code>router/index.js</code>中配置路由：</p>
<pre><code class="prism language-javascript">import Vue from 'vue'
import Router from 'vue-router'
import Home from '../views/Home.vue'
import BookDetail from '../components/BookDetail.vue'
import Login from '../components/Login.vue'

Vue.use(Router)

export default new Router({

  routes: [
    {

      path: '/',
      name: 'home',
      component: Home
    },
    {

      path: '/book/:id',
      name: 'bookDetail',
      component: BookDetail
    },
    {

      path: '/login',
      name: 'login',
      component: Login
    },
    // 更多路由...
  ]
})
</code></pre>
<h5>4. 使用Vuex管理状态</h5>
<p>在<code>store/index.js</code>中定义状态、mutations和actions：</p>
<pre><code class="prism language-javascript">import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({

  state: {

    books: [],
    cart: [],
    user: null
  },
  mutations: {

    SET_BOOKS(state, books) {

      state.books = books
    },
    ADD_TO_CART(state, bookId) {

      // 逻辑添加书籍到购物车
    },
    LOGIN_USER(state, user) {

      state.user = user
    }
    // 更多mutations...
  },
  actions: {

    fetchBooks({
     commit }) {

      // 发送请求获取书籍列表
      // commit('SET_BOOKS', books)
    },
    // 更多actions...
  }
})
</code></pre>
<h5>5. 组件开发</h5>
<p>开发各个组件，如<code>BookList.vue</code>显示图书列表，<code>BookDetail.vue</code>显示图书详情，<code>Cart.vue</code>显示购物车等。</p>
<h5>6. 编写API接口</h5>
<p>虽然这通常是由后端完成的，但你需要知道API的URL和请求方式（GET, POST等），以便在Vue组件中通过Axios调用它们。</p>
<h4>部分关键代码片段</h4>
<p><strong>BookList.vue</strong>:</p>
<pre><code class="prism language-vue">&lt;template&gt;
  &lt;div&gt;
    &lt;ul&gt;
      &lt;li v-for="book in books" :key="book.id"&gt;
        &lt;router-link :to="`/book/${book.id}`"&gt;{
  { book.title }}&lt;/router-link&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  computed: {
    books() {
      return this.$store.state.books;
    }
  },
  created() {
    this.$store.dispatch('fetchBooks');
  }
}
&lt;/script&gt;
</code></pre>
<h4>总结</h4>
<p>以上概述了一个基于Vue.js的"在线图书管理系统"项目的核心构建过程。实际开发中，你需要处理更多细节，如错误处理、用户权限管理、性能优化等。希望这个概述能帮助你理解如何开始并构建一个完整的Vue.js项目。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141753072</p>]]></description><guid isPermaLink="false">/archives/17c19070-59e3-417e-ac7c-e35387dd26fb</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sat, 28 Sep 2024 08:44:51 GMT</pubDate></item><item><title><![CDATA[PhpStorm中配置调试功能]]></title><link>https://wiki.dgfygk.com/archives/af860a19-a688-4200-9952-8643c0e6ced1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=PhpStorm%E4%B8%AD%E9%85%8D%E7%BD%AE%E8%B0%83%E8%AF%95%E5%8A%9F%E8%83%BD&amp;url=/archives/af860a19-a688-4200-9952-8643c0e6ced1" width="1" height="1" alt="" style="opacity:0;">
<p>在PhpStorm中配置调试功能是一个相对直接且强大的过程，它允许开发者在开发过程中高效地定位和解决代码问题。以下是一个详细的步骤指南，涵盖了从安装PhpStorm到配置调试环境的整个过程，以及如何在PhpStorm中使用调试功能。</p>
<h4>一、安装PhpStorm</h4>
<p>首先，确保你已经从JetBrains的官方网站下载了最新版本的PhpStorm。安装过程相对简单，只需按照安装向导的指示逐步进行即可。在安装过程中，你可以选择安装路径、语言、主题等选项。</p>
<h4>二、配置PHP环境</h4>
<p>在配置PhpStorm的调试功能之前，你需要确保你的计算机上已经安装了PHP环境，并将其加入到系统的环境变量中。这样，PhpStorm才能正确地识别和执行PHP代码。</p>
<ol>
 <li><strong>安装PHP</strong>：从PHP的官方网站下载适合你操作系统的PHP版本，并按照安装指南进行安装。</li>
 <li><strong>配置环境变量</strong>：将PHP的安装目录添加到系统的环境变量中，以便在命令行中全局访问PHP。</li>
</ol>
<h4>三、配置PhpStorm的PHP解释器</h4>
<p>PhpStorm需要知道PHP解释器的位置，以便能够执行和调试PHP代码。</p>
<ol>
 <li>打开PhpStorm，点击菜单栏中的"File” &gt; “Settings”（Windows/Linux）或"PhpStorm” &gt; “Preferences”（macOS）。</li>
 <li>在弹出的设置窗口中，选择"Languages \&amp; Frameworks” &gt; “PHP”。</li>
 <li>点击右侧的”…“按钮，选择你的PHP解释器。如果你已经正确配置了环境变量，PhpStorm应该能够自动检测到PHP解释器的位置。如果没有，你可以手动浏览到你的PHP安装目录，并选择<code>php.exe</code>（Windows）或<code>php</code>（macOS/Linux）文件。</li>
</ol>
<h4>四、配置调试环境</h4>
<p>在PhpStorm中配置调试环境通常涉及以下几个步骤：</p>
<ol>
 <li>
  <p><strong>安装Xdebug或Zend Debugger</strong>：这些是PHP的调试扩展，允许PhpStorm与PHP代码进行交互，从而实现断点调试、变量查看等功能。推荐使用Xdebug，因为它提供了更多的功能和更好的性能。</p>
 </li>
 <li>
  <p>下载与你的PHP版本相匹配的Xdebug扩展。</p>
 </li>
 <li>
  <p>将下载的Xdebug扩展文件（如<code>php_xdebug.dll</code>或<code>php_xdebug.so</code>）放到PHP的<code>ext</code>目录下。</p>
 </li>
 <li>
  <p>在<code>php.ini</code>文件中添加相应的配置行来启用Xdebug，并设置其参数（如监听端口、远程主机等）。</p>
 </li>
 <li>
  <p><strong>配置PhpStorm的调试设置</strong>：</p>
 </li>
 <li>
  <p>在PhpStorm的设置窗口中，选择"Languages \&amp; Frameworks” &gt; “PHP” &gt; “Debug”。</p>
 </li>
 <li>
  <p>在这里，你可以设置Xdebug的端口号（确保它与<code>php.ini</code>中的设置相匹配），以及是否启用"Listen for incoming connections”。</p>
 </li>
 <li>
  <p><strong>配置运行/调试配置</strong>：</p>
 </li>
 <li>
  <p>点击菜单栏中的"Run” &gt; “Edit Configurations”。</p>
 </li>
 <li>
  <p>在弹出的窗口中，点击左上角的”+“按钮，选择适合你的调试需求的配置类型（如"PHP Web Application”、“PHP Script"等）。</p>
 </li>
 <li>
  <p>根据你的项目需求填写相应的配置信息，如服务器地址、端口号、启动URL等。</p>
 </li>
</ol>
<h4>五、使用调试功能</h4>
<p>配置好调试环境后，你就可以在PhpStorm中使用调试功能了。</p>
<ol>
 <li>
  <p><strong>设置断点</strong>：在需要调试的代码行左侧点击，设置断点。断点将以红色圆点的形式显示。</p>
 </li>
 <li>
  <p><strong>启动调试</strong>：点击工具栏上的调试按钮（通常是一个带有虫子图标的按钮）或按下快捷键（如F9），启动调试会话。PhpStorm将尝试连接到你的PHP服务器，并在达到断点时暂停执行。</p>
 </li>
 <li>
  <p><strong>调试控制</strong>：在调试过程中，你可以使用调试工具栏上的按钮来控制程序的执行，如继续执行（F8）、单步执行（F7）、逐过程执行（Shift+F8）等。</p>
 </li>
 <li>
  <p><strong>查看变量和调用栈</strong>：在调试过程中，你可以通过变量窗口查看当前作用域中的变量值，通过调用栈窗口查看函数的调用顺序。</p>
 </li>
 <li>
  <p><strong>修改变量值</strong>：在调试过程中，你还可以直接修改变量的值，以测试不同的代码路径或修复问题。</p>
 </li>
</ol>
<h4>六、高级调试技巧</h4>
<p>除了基本的调试功能外，PhpStorm还提供了一些高级调试技巧，如条件断点、日志断点等。</p>
<ul>
 <li><strong>条件断点</strong>：在设置断点时，你可以指定一个条件。只有当条件为真时，断点才会被触发。这有助于你更精确地控制调试过程。</li>
 <li><strong>日志断点</strong>：与条件断点类似，但当你达到断点时，它不会暂停程序执行，而是记录一条日志消息。这有助于你在不中断程序执行的情况下收集调试信息。</li>
</ul>
<h4>七、总结</h4>
<p>通过以上步骤，你可以在PhpStorm中成功配置调试环境，并使用其强大的调试功能来提高你的开发效率。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141642526</p>]]></description><guid isPermaLink="false">/archives/af860a19-a688-4200-9952-8643c0e6ced1</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 08:43:22 GMT</pubDate></item><item><title><![CDATA[Ubuntu 16.04下Firefox版本更新]]></title><link>https://wiki.dgfygk.com/archives/a86124e5-82fb-4c95-9758-846f78d78b9e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Ubuntu%2016.04%E4%B8%8BFirefox%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0&amp;url=/archives/a86124e5-82fb-4c95-9758-846f78d78b9e" width="1" height="1" alt="" style="opacity:0;">
<p>在Ubuntu 16.04系统下更新Firefox浏览器到最新版本是一个相对直接但需要注意细节的过程。由于Ubuntu 16.04是一个较旧的操作系统版本，其默认的软件包管理器apt可能无法提供最新版本的Firefox。因此，用户通常需要手动下载Firefox的最新版本并进行安装。以下是一个详细的步骤指南，帮助您在Ubuntu 16.04系统下更新Firefox浏览器。</p>
<h4>一、准备工作</h4>
<ol>
 <li>
  <p><strong>备份当前Firefox设置（可选）</strong> ：
   <br>
   在更新Firefox之前，建议备份当前的浏览器设置、书签、密码等。虽然手动更新Firefox通常不会覆盖这些设置，但备份总是一个好习惯。</p>
 </li>
 <li>
  <p><strong>查看当前Firefox版本</strong> ：
   <br>
   打开终端（Terminal），输入<code>firefox --version</code>命令查看当前安装的Firefox版本。了解当前版本有助于您确定需要更新的版本范围。</p>
 </li>
 <li>
  <p><strong>确定需要的Firefox版本</strong> ：
   <br>
   根据需求（如特定软件的兼容性要求），确定您需要的Firefox版本。通常，最新版本的Firefox提供了更好的性能、安全性和功能。</p>
 </li>
</ol>
<h4>二、下载Firefox新版本</h4>
<ol>
 <li>
  <p><strong>访问Firefox官方网站</strong> ：
   <br>
   使用浏览器访问<a href="https://www.mozilla.org/firefox/new/">Mozilla Firefox的官方网站</a>或<a href="https://www.mozilla.org/zh-CN/firefox/new/">Firefox的下载页面</a>。</p>
 </li>
 <li>
  <p><strong>下载最新版本的Firefox</strong> ：
   <br>
   在Firefox下载页面上，选择适合您操作系统的版本。对于Ubuntu 16.04，您可能需要下载Linux版本（通常是.tar.bz2格式的压缩包）。</p>
 </li>
 <li>
  <p><strong>保存下载文件</strong> ：
   <br>
   将下载的Firefox压缩包保存到您的Ubuntu系统的某个目录下，例如<code>/Downloads</code>或<code>/home/您的用户名/Downloads</code>。</p>
 </li>
</ol>
<h4>三、卸载旧版本的Firefox（可选）</h4>
<p>如果您想完全替换旧版本的Firefox，可以首先卸载它。打开终端，输入以下命令：</p>
<pre><code class="prism language-bash">sudo apt-get remove firefox
</code></pre>
<p>请注意，这个命令只会卸载Firefox软件包，不会删除您的个人数据（如书签、历史记录等），这些数据通常存储在用户的主目录下。</p>
<h4>四、安装新版本的Firefox</h4>
<ol>
 <li>
  <p><strong>解压下载的Firefox压缩包</strong> ：
   <br>
   打开终端，导航到包含Firefox压缩包的目录。使用<code>tar</code>命令解压压缩包。例如，如果您的压缩包名为<code>firefox-xxx.tar.bz2</code>（其中<code>xxx</code>是版本号），则可以使用以下命令解压：</p>
  <pre><code class="prism language-bash">cd /path/to/your/download/directory
sudo tar xvf firefox-xxx.tar.bz2 -C /opt/
</code></pre>
  <p>这里<code>-C /opt/</code>参数指定了解压目录，但您也可以根据需要选择其他目录。</p>
 </li>
 <li>
  <p><strong>创建符号链接</strong> ：
   <br>
   为了方便在终端中启动Firefox，您可以在<code>/usr/bin/</code>目录下创建一个指向Firefox可执行文件的符号链接。首先，确保您知道Firefox可执行文件的确切路径（通常位于您解压的目录中）。然后，使用<code>ln</code>命令创建链接：</p>
  <pre><code class="prism language-bash">sudo ln -s /opt/firefox/firefox /usr/bin/firefox
</code></pre>
  <p>注意：这里的<code>/opt/firefox/firefox</code>是假设的Firefox可执行文件路径，您需要根据实际情况进行修改。</p>
 </li>
 <li>
  <p><strong>（可选）创建桌面快捷方式</strong> ：
   <br>
   如果您想从图形界面快速启动Firefox，可以创建一个桌面快捷方式。使用文本编辑器（如<code>gedit</code>或<code>nano</code>）在<code>/usr/share/applications/</code>目录下创建一个新的<code>.desktop</code>文件，并填写相应的内容。例如：</p>
  <pre><code class="prism language-bash">sudo nano /usr/share/applications/firefox.desktop
</code></pre>
  <p>然后，在文件中输入以下内容（请根据实际情况修改路径和图标）：</p>
  <pre><code class="prism language-ini">[Desktop Entry]
Version=1.0
Type=Application
Name=Firefox
Comment=Browse the Web
Exec=/opt/firefox/firefox
Icon=/opt/firefox/browser/chrome/icons/default/default128.png
Terminal=false
Categories=Application;Network;WebBrowser;
StartupWMClass=Firefox
</code></pre>
  <p>保存并关闭文件。现在，您应该可以在应用程序菜单中找到Firefox的快捷方式了。</p>
 </li>
</ol>
<h4>五、验证安装</h4>
<ol>
 <li>
  <p><strong>启动Firefox</strong> ：
   <br>
   在终端中输入<code>firefox</code>命令，或者在图形界面中双击Firefox的快捷方式，启动浏览器。</p>
 </li>
 <li>
  <p><strong>检查版本</strong> ：
   <br>
   在Firefox中，点击菜单按钮（通常位于屏幕右上角），选择"帮助”&gt;“关于Firefox"来检查浏览器版本。</p>
 </li>
</ol>
<h4>六、注意事项</h4>
<ol>
 <li>
  <p><strong>更新过程中的问题</strong> ：
   <br>
   如果在更新过程中遇到任何问题（如权限错误、依赖问题等），请仔细检查错误信息，并根据情况采取相应措施。有时，问题可能是由于之前未完全卸载旧版本的Firefox导致的，或者是由于缺少某些系统库文件。在Ubuntu中，您可以使用<code>apt-get install</code>命令来安装缺失的依赖项，或者使用<code>sudo</code>权限来执行需要管理员权限的命令。</p>
 </li>
 <li>
  <p><strong>定期更新</strong> ：
   <br>
   手动下载和安装Firefox的新版本虽然有效，但可能不是最方便的更新方法。考虑到Ubuntu 16.04的软件仓库可能不包含最新版本的Firefox，您可以考虑使用其他方法来自动更新Firefox，例如使用Mozilla提供的Firefox PPA（个人软件包存档）或第三方工具，如<code>snap</code>或<code>flatpak</code>。然而，这些选项可能不适用于所有用户，因为它们的设置和维护可能比简单地使用系统包管理器更复杂。</p>
 </li>
 <li>
  <p><strong>安全和稳定性</strong> ：
   <br>
   虽然最新版本的Firefox通常提供了更好的安全性和功能，但某些新特性可能与您的特定工作环境或插件不兼容。在升级到新版本之前，建议查阅Mozilla的发布说明和兼容性列表，以确保新版本符合您的需求。</p>
 </li>
 <li>
  <p><strong>考虑升级操作系统</strong> ：
   <br>
   由于Ubuntu 16.04是一个较旧的操作系统版本，它可能不再接收最新的安全更新和软件更新。如果您的硬件条件允许，并且您需要最新的软件版本和安全性改进，考虑升级到更新的Ubuntu版本可能是一个好主意。新的Ubuntu版本通常提供了更好的性能、更多的功能和更强的安全性。</p>
 </li>
 <li>
  <p><strong>社区支持</strong> ：
   <br>
   如果您在更新Firefox或解决相关问题时遇到困难，可以寻求Ubuntu社区或Mozilla支持论坛的帮助。这些社区中有大量的热心用户和专家，他们可能能够提供解决您问题的建议和解决方案。</p>
 </li>
 <li>
  <p><strong>备份重要数据</strong> ：
   <br>
   在进行任何系统或软件更新之前，请务必备份您的重要数据。虽然更新Firefox通常不会导致数据丢失，但最佳实践是定期备份所有重要文件，以防万一。</p>
 </li>
 <li>
  <p><strong>了解未来计划</strong> ：
   <br>
   对于仍在使用Ubuntu 16.04的用户来说，了解Ubuntu的发布周期和未来计划也很重要。Ubuntu定期发布新版本，而旧版本在一段时间后可能不再受支持。因此，了解Ubuntu的发布计划和长期支持策略可以帮助您做出关于何时升级操作系统的明智决策。</p>
 </li>
</ol>
<p>总结来说，在Ubuntu 16.04下更新Firefox浏览器到最新版本涉及多个步骤，包括下载新版本、解压安装、验证安装以及注意更新过程中的问题。由于Ubuntu 16.04的软件仓库可能不包含最新版本的Firefox，因此用户可能需要采取一些额外的措施来确保浏览器的安全性和功能性。同时，考虑到系统的长期支持和稳定性，用户还应该考虑升级操作系统以获取更好的性能和安全性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141609888</p>]]></description><guid isPermaLink="false">/archives/a86124e5-82fb-4c95-9758-846f78d78b9e</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 08:27:29 GMT</pubDate></item><item><title><![CDATA[Notion使用详解一基础教程]]></title><link>https://wiki.dgfygk.com/archives/1545d942-8433-40d8-b493-de8a65bb90af</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Notion%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3%E4%B8%80%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B&amp;url=/archives/1545d942-8433-40d8-b493-de8a65bb90af" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Notion使用详解一基础教程</strong></p>
<p>Notion，这款被誉为"来自未来的笔记协作工具”，自问世以来就凭借其强大的功能和独特的设计理念吸引了众多用户。它不仅集成了笔记、知识库、任务管理等多种功能于一体，还通过模块化设计、无限层级页面以及动态编辑器等特点，为用户提供了前所未有的使用体验。以下是对Notion使用的基础教程，旨在帮助初学者快速上手并充分利用其功能。</p>
<h4>一、Notion简介</h4>
<p>Notion是一款集笔记、知识库、任务管理、数据库等多种功能于一体的协作平台。它采用模块化设计，通过Block（块）的形式组织内容，使得用户可以灵活快速地编辑和整理笔记。同时，Notion支持跨平台使用，包括Web网页、Windows、Mac、iOS、Android等，用户可以在任何设备上无缝同步和访问自己的笔记。</p>
<h4>二、注册与登录</h4>
<ol>
 <li><strong>访问官网</strong> ：首先，在浏览器中打开Notion的官方网站（<a href="https://www.notion.so/">notion.so</a>）。</li>
 <li><strong>注册账号</strong>：如果还没有Notion账号，可以选择通过Gmail或Apple账户快速登录，或者使用邮箱注册。在注册页面输入邮箱地址并点击"Continue with Email”，Notion会自动发送验证码到你的邮箱，输入验证码后完成注册。</li>
 <li><strong>登录账号</strong>：使用注册时填写的邮箱和密码登录Notion，或者通过Gmail、Apple账户等方式登录。</li>
</ol>
<h4>三、基础操作</h4>
<h5>1. 创建Page（笔记页）</h5>
<p>在Notion中，Page是最基本的内容组织单位，相当于传统笔记应用中的"笔记本"或"笔记”。</p>
<ul>
 <li><strong>新建Page</strong>：在左侧边栏点击”+ Add a page”，即可创建一个新的Page。你也可以在现有Page中通过点击空白行左侧的”+“号，选择"Page"来创建子Page。</li>
 <li><strong>修改Page样式</strong>：新建Page后，你可以通过点击右上角的三个点（···），选择"Page settings"来修改Page的字体、宽度、封面等样式。</li>
</ul>
<h5>2. 编写笔记</h5>
<p>在Page中编写笔记非常简单，你可以直接输入文字、粘贴图片、插入表格等。Notion的编辑器支持Markdown语法，同时也提供了丰富的排版选项。</p>
<ul>
 <li><strong>添加内容</strong>：点击Page中的空白区域，即可开始输入文字。你也可以通过点击空白行左侧的”+“号，选择不同的内容类型（如图片、标题、列表等）来插入内容。</li>
 <li><strong>修改样式</strong>：选中需要修改的文字或内容块，即可在弹出的编辑菜单中修改样式（如字体、颜色、超链接等）。</li>
</ul>
<h5>3. 使用Block（块）</h5>
<p>Notion的Block是其最具特色的功能之一，它允许用户以块的形式组织内容，每个块都可以是一个独立的单元，支持拖拽移动和单独设置样式。</p>
<ul>
 <li><strong>创建Block</strong>：在编辑区域输入”/“并选择相应的Block类型（如文本、页面、数据库等），即可快速创建一个新的Block。</li>
 <li><strong>操作Block</strong>：你可以通过拖拽Block来改变其在页面中的位置，也可以通过点击Block右上角的三个点（···）来进行更多操作（如复制、删除、设置样式等）。</li>
</ul>
<h4>四、高级功能</h4>
<h5>1. Database（数据库）</h5>
<p>Notion的Database功能是其区别于传统笔记应用的重要特点之一。它允许用户创建类似于数据库的表格，用于整理和存储结构化数据。</p>
<ul>
 <li><strong>新建Database</strong>：在左侧边栏点击”+ Add a page”，选择"Database"即可创建一个新的Database页面。你也可以在现有Page中插入Inline Database（内联数据库）。</li>
 <li><strong>操作Database</strong>：在Database页面中，你可以添加字段（如文本、数字、日期等）、设置筛选条件、排序规则等。同时，你还可以创建Linked Database（链接数据库），在其他Page中引用Database中的数据。</li>
</ul>
<h5>2. 模板与协作</h5>
<p>Notion提供了丰富的模板库，用户可以根据自己的需求选择合适的模板来创建Page或Database。此外，Notion还支持多人协作，用户可以邀请团队成员一起编辑和查看笔记。</p>
<ul>
 <li><strong>使用模板</strong>：在空白Page中，点击右上角的"Templates"按钮，即可浏览并选择Notion提供的模板。你也可以通过搜索或分类来快速找到需要的模板。</li>
 <li><strong>邀请协作</strong>：在Page的"Share"选项卡中，你可以邀请团队成员加入协作。你可以设置不同的权限级别（如全部权限、允许修改、允许评论等），以确保团队成员之间的协作顺畅进行。</li>
</ul>
<h4>五、技巧与提示</h4>
<ol>
 <li>
  <p><strong>快捷键</strong>：Notion提供了丰富的快捷键操作，如Ctrl+N（新建Page）、Ctrl+P（快速查找页面）等。掌握这些快捷键可以大大提高你的工作效率。</p>
 </li>
 <li>
  <p><strong>自定义封面和图标</strong>：为Page添加自定义封面和图标可以让你的笔记更加个性化和易于识别。在Page的标题上方，你可以直接点击或拖拽图片来设置封面，而在Page的侧边栏或设置选项中，你可以选择一个图标来代表这个Page。</p>
 </li>
 <li>
  <p><strong>利用Kanban视图管理任务</strong>：在Database中，你可以轻松切换到Kanban（看板）视图，以卡片的形式直观地展示任务和待办事项。通过拖拽卡片，你可以快速调整任务的优先级或将其归类到不同的列（如"待办”、“进行中”、“已完成"等）。</p>
 </li>
 <li>
  <p><strong>跨页面链接</strong> ：在Notion中，你可以通过简单的<code>/page</code>命令或选择"Insert Link"选项来创建跨页面的链接。这使得在不同Page之间导航变得非常方便，尤其是当你需要在一个大型项目或研究中频繁引用其他部分时。</p>
 </li>
 <li>
  <p><strong>公式与数学表达式</strong> ：对于需要处理数据和公式的用户来说，Notion支持LaTeX语法，允许你插入复杂的数学表达式。只需在文本块中输入<code>$$</code>来开启LaTeX模式，然后输入你的公式即可。</p>
 </li>
 <li>
  <p><strong>整合其他应用</strong>：Notion提供了丰富的集成选项，允许你与其他工具（如Google Drive、Dropbox、Slack等）无缝连接。通过这些集成，你可以直接在Notion中访问和使用其他应用的数据和功能，进一步提高工作效率。</p>
 </li>
 <li>
  <p><strong>利用Tags（标签）进行分类</strong>：虽然Notion没有内置的Tag系统，但你可以通过创建包含特定标签名称的数据库字段来模拟这一功能。这样，你就可以为Page或Database中的项目添加标签，以便进行更细致的分类和搜索。</p>
 </li>
 <li>
  <p><strong>定期备份与导出</strong>：尽管Notion提供了强大的云同步功能，但定期备份和导出你的笔记仍然是一个好习惯。你可以通过选择"Export"选项来导出整个Page或Database为Markdown、PDF或其他格式的文件。</p>
 </li>
 <li>
  <p><strong>探索社区与模板</strong>：Notion拥有一个活跃的社区，其中包含了大量的用户创建的模板和教程。通过浏览社区资源，你可以发现许多实用的模板和技巧，从而更快地掌握Notion的高级功能。</p>
 </li>
 <li>
  <p><strong>利用模板库快速启动项目</strong>：Notion的模板库包含了各种类型的模板，从项目管理到学习笔记，再到个人博客和待办事项列表。当你需要开始一个新的项目或任务时，可以先在模板库中搜索合适的模板，然后根据需要进行修改和定制。</p>
 </li>
</ol>
<h4>六、总结</h4>
<p>Notion作为一款功能强大的笔记协作工具，凭借其模块化设计、无限层级页面、动态编辑器以及丰富的集成选项等特点，为用户提供了前所未有的使用体验。通过本教程的介绍，你应该已经对Notion的基础操作和高级功能有了初步的了解。然而，要充分利用Notion的潜力，还需要不断实践和探索。希望你在使用Notion的过程中能够发现更多实用的功能和技巧，并享受到它带来的便利和乐趣。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141495838</p>]]></description><guid isPermaLink="false">/archives/1545d942-8433-40d8-b493-de8a65bb90af</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 08:27:17 GMT</pubDate></item><item><title><![CDATA[npm install报错，解决记录]]></title><link>https://wiki.dgfygk.com/archives/9d9c3307-0cbe-4ec7-bedd-6bbee0932e93</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=npm%20install%E6%8A%A5%E9%94%99%EF%BC%8C%E8%A7%A3%E5%86%B3%E8%AE%B0%E5%BD%95&amp;url=/archives/9d9c3307-0cbe-4ec7-bedd-6bbee0932e93" width="1" height="1" alt="" style="opacity:0;">
<p>处理 <code>npm install</code> 报错是一个常见且可能涉及多个层面的挑战，因为错误的根源可以非常多样化，包括但不限于网络问题、权限问题、依赖冲突、npm配置错误、Node.js版本不兼容等。下面我将详细讨论几种常见的 <code>npm install</code> 报错情况及其解决方案，力求覆盖到至少2000字的详细分析。</p>
<h4>一、网络问题</h4>
<p><strong>问题描述</strong> ：
 <br>
 在进行 <code>npm install</code> 时，最常见的错误之一是网络问题。这可能是因为npm默认使用国外镜像，导致访问速度慢或完全无法访问。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>使用npm镜像</strong> ：
   <br>
   可以使用国内的npm镜像源，如淘宝npm镜像（cnpm）、阿里云npm镜像等，以提高访问速度。以淘宝npm为例，你可以通过以下命令设置npm的registry：</p>
  <pre><code class="prism language-bash">npm config set registry https://registry.npm.taobao.org
</code></pre>
  <p>或者，你可以使用cnpm（淘宝npm的命令行工具），它内置了淘宝npm镜像：</p>
  <pre><code class="prism language-bash">npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install
</code></pre>
 </li>
 <li>
  <p><strong>检查网络连接</strong> ：
   <br>
   确保你的设备可以正常访问互联网。可以尝试ping一些常用的网站或域名，如<code>www.google.com</code>，来检查网络连接是否稳定。</p>
 </li>
 <li>
  <p><strong>VPN或代理</strong> ：
   <br>
   如果你处于网络受限的地区或环境中，可能需要使用VPN或代理来绕过网络限制。</p>
 </li>
</ol>
<h4>二、权限问题</h4>
<p><strong>问题描述</strong> ：
 <br>
 在某些操作系统（尤其是Unix/Linux和macOS）上，如果没有足够的权限，npm可能会在安装全局包或修改全局npm配置时失败。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>使用sudo</strong> ：
   <br>
   对于需要管理员权限的操作，可以在命令前加上<code>sudo</code>来获取必要的权限。例如：</p>
  <pre><code class="prism language-bash">sudo npm install -g &lt;package-name&gt;
</code></pre>
  <p>但请注意，频繁使用<code>sudo</code>安装npm包可能会带来安全风险，因为某些包可能会尝试修改系统级配置。</p>
 </li>
 <li>
  <p><strong>更改npm的默认目录</strong> ：
   <br>
   你可以将npm的全局包安装目录更改为一个不需要sudo权限的目录。这可以通过设置<code>prefix</code>配置来实现：</p>
  <pre><code class="prism language-bash">npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH
source ~/.bashrc  # 或者你使用的shell的配置文件
</code></pre>
 </li>
</ol>
<h4>三、依赖冲突</h4>
<p><strong>问题描述</strong> ：
 <br>
 项目中的依赖项之间可能存在版本冲突，导致 <code>npm install</code> 失败。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>检查<code>package.json</code>和<code>package-lock.json</code></strong> ：
   <br>
   确保<code>package.json</code>中列出的依赖项没有相互冲突的版本要求。同时，检查<code>package-lock.json</code>文件，它锁定了项目的依赖项版本，以确保可重复性。</p>
 </li>
 <li>
  <p><strong>使用npm的依赖管理工具</strong> ：
   <br>
   npm提供了<code>npm ls</code>命令来检查项目依赖树，以及<code>npm prune</code>命令来移除未使用的依赖项。此外，还可以使用第三方工具如<code>npm-check-updates</code>来更新项目的依赖项。</p>
 </li>
 <li>
  <p><strong>解决具体冲突</strong> ：
   <br>
   如果确定了具体的依赖冲突，你可能需要手动修改<code>package.json</code>中的版本号，或者查找是否有可用的补丁版本。</p>
 </li>
</ol>
<h4>四、npm配置错误</h4>
<p><strong>问题描述</strong> ：
 <br>
 npm的配置文件（如<code>.npmrc</code>）中可能存在错误或不一致的设置，导致安装失败。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>检查<code>.npmrc</code>文件</strong> ：
   <br>
   在项目根目录或用户主目录下查找<code>.npmrc</code>文件，检查其中的配置是否正确。特别是<code>registry</code>和<code>prefix</code>等配置项。</p>
 </li>
 <li>
  <p><strong>重置npm配置</strong> ：
   <br>
   如果<code>.npmrc</code>文件中的配置过于复杂或不确定哪些设置是必需的，你可以考虑删除该文件（注意备份），然后让npm使用其默认配置。</p>
 </li>
</ol>
<h4>五、Node.js版本不兼容</h4>
<p><strong>问题描述</strong> ：
 <br>
 某些npm包可能要求特定版本的Node.js才能正常工作。如果你的Node.js版本与包的要求不匹配，<code>npm install</code>可能会失败。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>检查Node.js版本</strong> ：
   <br>
   使用<code>node -v</code>命令查看当前Node.js的版本。</p>
 </li>
 <li>
  <p><strong>更新或降级Node.js</strong> ：
   <br>
   如果Node.js版本不符合要求，你需要更新或降级Node.js。可以使用如<code>nvm</code>（Node Version Manager）这样的工具来管理多个Node.js版本。</p>
  <p>安装nvm后，你可以使用以下命令来安装或切换Node.js版本：</p>
  <pre><code class="prism language-bash">nvm install &lt;version&gt;
nvm use&lt;version&gt;
</code></pre>
  <p>其中<code>&lt;version&gt;</code>是你想要安装或切换到的Node.js版本号。</p>
 </li>
</ol>
<h4>六、缓存问题</h4>
<p><strong>问题描述</strong> ：
 <br>
 npm的缓存可能会因为各种原因（如损坏、过时）导致安装失败。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>清除npm缓存</strong> ：
   <br>
   你可以使用<code>npm cache clean --force</code>命令来清除npm的缓存。请注意，这个命令在npm 5及更高版本中已被弃用，因为npm现在自动管理缓存。但如果你遇到与缓存相关的问题，尝试这个命令可能仍然有帮助。</p>
  <p>对于npm 5及更高版本，你应该让npm自动处理缓存问题，或者使用<code>npm cache verify</code>来验证缓存的完整性。</p>
 </li>
 <li>
  <p><strong>删除<code>node_modules</code>和<code>package-lock.json</code></strong> ：
   <br>
   有时，简单地删除<code>node_modules</code>文件夹和<code>package-lock.json</code>文件，然后重新运行<code>npm install</code>可以解决依赖问题。这确保了npm将从头开始解析和安装依赖项。</p>
 </li>
</ol>
<h4>七、使用<code>yarn</code>作为替代</h4>
<p><strong>问题描述</strong> ：
 <br>
 如果你持续遇到npm的问题，并且它们似乎与npm的特定行为或限制有关，你可以考虑使用<code>yarn</code>作为npm的替代方案。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>安装yarn</strong> ：
   <br>
   你可以从<a href="https://yarnpkg.com/">yarn的官方网站</a>下载并安装yarn。对于大多数系统，都有预编译的包和安装脚本可用。</p>
 </li>
 <li>
  <p><strong>使用yarn管理依赖</strong> ：
   <br>
   安装yarn后，你可以使用<code>yarn add &lt;package-name&gt;</code>来安装依赖项，使用<code>yarn</code>来运行安装脚本，以及使用<code>yarn remove &lt;package-name&gt;</code>来移除依赖项。yarn还提供了许多其他有用的命令和特性，如离线模式和工作空间（monorepos）。</p>
 </li>
</ol>
<h4>八、深入日志和调试</h4>
<p><strong>问题描述</strong> ：
 <br>
 当<code>npm install</code>失败时，npm会输出一个错误日志，但这个日志可能非常冗长且难以理解。</p>
<p><strong>解决方案</strong>：</p>
<ol>
 <li>
  <p><strong>仔细阅读错误日志</strong> ：
   <br>
   尽管错误日志可能很长，但通常错误的根源会在日志的顶部或接近顶部的位置。注意查找"ERROR"或"warn"这样的关键字，以及任何与你的项目或依赖项相关的路径。</p>
 </li>
 <li>
  <p><strong>增加日志详细度</strong> ：
   <br>
   你可以使用<code>npm install --verbose</code>命令来增加npm命令的日志详细度。这将输出更多关于npm正在做什么的信息，可能有助于你诊断问题。</p>
 </li>
 <li>
  <p><strong>搜索和询问</strong> ：
   <br>
   如果错误日志中的某个特定错误消息或代码看起来不熟悉，尝试在网上搜索它。很可能其他开发者已经遇到了类似的问题，并分享了他们的解决方案。你也可以在Stack Overflow等开发者社区中提问，寻求帮助。</p>
 </li>
</ol>
<h4>九、结论</h4>
<p>处理<code>npm install</code>报错可能是一个复杂且耗时的过程，因为错误的根源可能多种多样。然而，通过遵循上述步骤和建议，你应该能够诊断并解决大多数常见的npm安装问题。记住，保持耐心和细致是关键。如果你在某个问题上卡住了，不妨休息一下，然后再回来尝试新的解决方案。在开发过程中遇到问题是正常的，它们是我们学习和成长的机会。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141451161</p>]]></description><guid isPermaLink="false">/archives/9d9c3307-0cbe-4ec7-bedd-6bbee0932e93</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sat, 28 Sep 2024 08:25:13 GMT</pubDate></item><item><title><![CDATA[工厂模式和策略模式]]></title><link>https://wiki.dgfygk.com/archives/d1ab77c2-41b9-465c-afb1-12cc0d7e46be</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%E5%92%8C%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F&amp;url=/archives/d1ab77c2-41b9-465c-afb1-12cc0d7e46be" width="1" height="1" alt="" style="opacity:0;">
<p>工厂模式和策略模式是两种常见且重要的设计模式，它们在软件开发中各自扮演着不同的角色，具有显著的区别和各自的应用场景。以下将详细阐述这两种模式的区别，包括定义、结构、应用场景、优缺点等方面，以满足不少于2000字的要求。</p>
<h4>一、定义与结构</h4>
<h5>1. 工厂模式</h5>
<p>工厂模式（Factory Pattern）是一种创建型设计模式，它提供了一种创建对象的最佳方式。在工厂模式中，对象的创建逻辑被封装在工厂类中，客户端通过调用工厂类的方法来创建所需的对象，而无需直接实例化对象。这样做的好处是隐藏了对象创建的复杂性，提高了代码的封装性和可维护性。</p>
<p>工厂模式通常包括以下几种类型：</p>
<ul>
 <li><strong>简单工厂模式</strong>：通过一个工厂类根据传入的参数来决定创建哪一个产品类的实例。这种模式的缺点是工厂类承担了所有的产品创建责任，违反了开闭原则（即对扩展开放，对修改关闭）。</li>
 <li><strong>工厂方法模式</strong>：定义一个用于创建对象的接口，让子类决定实例化哪一个类。工厂方法让类的实例化推迟到子类中进行，从而实现了开闭原则。</li>
 <li><strong>抽象工厂模式</strong>：提供一个接口，用于创建相关或依赖对象的家族，而不需要明确指定具体类。抽象工厂模式用于创建一组相互依赖的对象，这些对象属于同一产品族。</li>
</ul>
<h5>2. 策略模式</h5>
<p>策略模式（Strategy Pattern）是一种行为型设计模式，它定义了一系列算法，并将每一个算法封装起来，使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。在策略模式中，通常包括三个角色：</p>
<ul>
 <li><strong>抽象策略角色</strong>：定义了一个公共接口，所有的具体策略类都必须实现这个接口。</li>
 <li><strong>具体策略角色</strong>：实现了抽象策略角色定义的接口，封装了相关的算法或行为。</li>
 <li><strong>环境角色</strong>（Context）：持有一个策略类的引用，并根据需要调用其定义的算法。</li>
</ul>
<h4>二、应用场景</h4>
<h5>1. 工厂模式的应用场景</h5>
<p>工厂模式主要用于以下场景：</p>
<ul>
 <li><strong>当对象的创建逻辑较为复杂</strong>时，通过工厂模式可以隐藏对象创建的细节，使得客户端代码更加简洁。</li>
 <li><strong>当需要根据不同的条件创建不同的对象</strong>时，工厂模式可以根据输入参数动态地创建相应的对象，提高了代码的灵活性。</li>
 <li><strong>当需要创建的对象种类很多</strong>时，使用工厂方法模式或抽象工厂模式可以避免创建大量的if-else或switch-case语句，使得代码更加清晰。</li>
</ul>
<p>例如，在一个电商系统中，可能需要根据不同的用户类型（如普通用户、会员用户）创建不同的订单对象。这时可以使用工厂模式，根据用户类型来创建相应的订单对象。</p>
<h5>2. 策略模式的应用场景</h5>
<p>策略模式主要用于以下场景：</p>
<ul>
 <li><strong>当多个类只区别在表现行为不同</strong>时，可以使用策略模式来封装这些不同的行为，并在运行时动态地选择具体的行为。</li>
 <li><strong>当需要在不同的算法或策略中进行选择和切换</strong>时，策略模式提供了灵活的方式来替换算法或策略。</li>
 <li><strong>当需要隐藏具体策略（算法）的实现细节</strong>时，策略模式可以将算法封装在策略类中，客户端只需关注策略的选择和调用，而无需关心算法的具体实现。</li>
</ul>
<p>例如，在一个支付系统中，可能需要支持多种支付方式（如支付宝、微信支付、银行卡支付等），每种支付方式都有不同的支付逻辑。这时可以使用策略模式来封装不同的支付算法，并在支付时根据用户选择的支付方式动态地选择相应的支付策略。</p>
<h4>三、优缺点</h4>
<h5>1. 工厂模式的优缺点</h5>
<p><strong>优点</strong>：</p>
<ul>
 <li><strong>封装性好</strong>：客户端不需要知道具体产品的创建过程，只需通过工厂类即可获取所需对象。</li>
 <li><strong>扩展性好</strong>：当需要增加新的产品类型时，只需增加相应的具体产品类和对应的工厂类（或修改工厂方法），而无需修改客户端代码，符合开闭原则。</li>
 <li><strong>解耦</strong>：将对象的创建与使用分离，降低了系统的耦合度。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
 <li><strong>增加了类的数量</strong>：每增加一个产品类，都需要增加一个对应的工厂类（或修改工厂方法），这可能会增加系统的复杂度。</li>
 <li><strong>违反了开闭原则（简单工厂模式）</strong>：简单工厂模式中，所有产品的创建逻辑都封装在一个工厂类中，当新增产品时需要修改工厂类，违反了开闭原则。</li>
</ul>
<h5>2. 策略模式的优缺点</h5>
<p><strong>优点</strong>：</p>
<ul>
 <li><strong>策略替换性好</strong>：客户端可以自由地更换策略，而不需要修改原有代码，提高了系统的灵活性。</li>
 <li><strong>扩展性好</strong>：当需要增加新的策略时，只需增加一个新的策略类，并修改环境类中的策略选择逻辑即可，无需修改其他代码。</li>
 <li><strong>避免使用多重条件转移语句</strong>：策略模式将多个条件转移语句替换为策略类的选择调用，提高了代码的可读性和可维护性。</li>
</ul>
<p>**
 <br>
 <strong>缺点</strong>：</p>
<ul>
 <li><strong>增加策略类的数量</strong>：随着策略的增加，系统中会引入更多的策略类，这可能会增加系统的复杂性和管理难度。</li>
 <li><strong>客户端需要了解策略</strong>：虽然策略的选择是在环境类中进行的，但客户端通常需要知道有哪些策略可用，并能够在运行时选择适当的策略。这要求客户端对策略有一定的了解，可能会增加学习成本。</li>
 <li><strong>策略间的通信问题</strong>：在某些情况下，不同的策略之间可能需要相互通信或共享数据。策略模式本身并不直接支持这种通信机制，因此需要额外的设计来处理这些问题。</li>
</ul>
<h4>四、比较与总结</h4>
<h5>1. 比较</h5>
<ul>
 <li><strong>关注点</strong>：工厂模式主要关注对象的创建过程，将对象的创建与使用分离；而策略模式则主要关注算法或行为的封装和选择，将算法或行为的变化与算法的调用者分离。</li>
 <li><strong>结构差异</strong>：工厂模式通常包括工厂类和产品类（可能还有抽象产品类），工厂类负责创建产品对象；而策略模式则包括抽象策略角色、具体策略角色和环境角色，环境角色持有策略角色的引用并调用其定义的算法。</li>
 <li><strong>应用场景</strong>：工厂模式适用于需要根据不同条件创建不同对象的情况；而策略模式适用于需要根据不同算法或行为来执行操作的情况。</li>
</ul>
<h5>2. 总结</h5>
<p>工厂模式和策略模式是两种非常实用的设计模式，它们在软件开发中扮演着重要的角色。工厂模式通过封装对象的创建过程，提高了代码的封装性和可维护性；而策略模式则通过封装算法或行为，提供了灵活的方式来选择和切换算法或行为。虽然它们在关注点、结构和应用场景上有所不同，但都是面向对象设计中重要的设计模式，能够有效地解决软件开发中的常见问题。</p>
<p>在实际应用中，我们可以根据具体的需求和场景来选择合适的设计模式。例如，在需要动态创建对象时，可以考虑使用工厂模式；在需要根据不同算法或行为来执行操作时，可以考虑使用策略模式。同时，我们也可以将这两种设计模式结合起来使用，以实现更加灵活和强大的功能。例如，在一个复杂的电商系统中，我们可以使用工厂模式来创建订单对象，并使用策略模式来封装不同的支付算法，从而实现订单的创建和支付功能的灵活组合。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141526704</p>]]></description><guid isPermaLink="false">/archives/d1ab77c2-41b9-465c-afb1-12cc0d7e46be</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 08:24:55 GMT</pubDate></item><item><title><![CDATA[命令模式在手游后端的应用]]></title><link>https://wiki.dgfygk.com/archives/f25a504b-f902-4230-a1cb-7c5156919466</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F%E5%9C%A8%E6%89%8B%E6%B8%B8%E5%90%8E%E7%AB%AF%E7%9A%84%E5%BA%94%E7%94%A8&amp;url=/archives/f25a504b-f902-4230-a1cb-7c5156919466" width="1" height="1" alt="" style="opacity:0;">
<p>利用命令模式（Command Pattern）在游戏后端架构中实现复杂功能的模块化、解耦以及灵活控制，是一种高级设计技巧。尤其是在手游这种需要处理大量玩家交互、实时数据更新、游戏逻辑复杂且需要高度可扩展性的环境中，命令模式显得尤为重要。下面，我将详细阐述如何在手游后端架构中应用命令模式，包括其概念、设计、实现及优势等方面，总字数将远远超过2000字，但我会尽量在框架内详细阐述核心要点。</p>
<h4>一、命令模式概述</h4>
<p>命令模式是一种行为设计模式，它将一个请求封装为一个对象，从而使你可用不同的请求、队列、日志来参数化其他对象。命令模式也支持可撤销的操作。在游戏后端架构中，这意味着可以将游戏逻辑中的各种操作（如角色移动、技能释放、物品使用等）封装成独立的命令对象，从而实现高度的模块化和灵活性。</p>
<h4>二、手游后端架构特点</h4>
<p>手游后端架构通常需要考虑以下几个方面：</p>
<ol>
 <li><strong>高并发</strong>：支持大量玩家同时在线。</li>
 <li><strong>实时性</strong>：快速响应玩家操作，确保游戏流畅。</li>
 <li><strong>可扩展性</strong>：支持新功能和新玩法的快速迭代。</li>
 <li><strong>稳定性</strong>：保证游戏服务的持续稳定运行。</li>
 <li><strong>安全性</strong>：保护玩家数据，防止作弊和外挂。</li>
</ol>
<h4>三、命令模式在手游后端的应用</h4>
<h5>1. 设计思路</h5>
<p>在游戏后端设计中，命令模式的应用主要体现在以下几个方面：</p>
<ul>
 <li><strong>封装游戏操作</strong>：将游戏中的各种操作（如移动、攻击、释放技能等）封装成独立的命令对象。</li>
 <li><strong>接收并处理玩家指令</strong>：服务器接收玩家发来的指令，将其转换为相应的命令对象并执行。</li>
 <li><strong>命令队列与并发处理</strong>：通过命令队列实现指令的并发处理，优化资源利用和响应时间。</li>
 <li><strong>命令撤销与重做</strong>：对于需要支持撤销/重做功能的操作，实现可撤销的命令。</li>
</ul>
<h5>2. 实现步骤</h5>
<h6>a. 定义命令接口</h6>
<p>首先，定义一个命令接口，规定所有命令必须实现的方法。例如：</p>
<pre><code class="prism language-java">public interface Command {

    void execute();
    void undo(); // 可选，用于支持撤销操作
}
</code></pre>
<h6>b. 实现具体命令</h6>
<p>接着，为每个游戏操作实现具体的命令类。例如，实现一个角色移动命令：</p>
<pre><code class="prism language-java">public class MoveCommand implements Command {

    private Character character;
    private Vector2D startPosition;
    private Vector2D endPosition;

    public MoveCommand(Character character, Vector2D endPosition) {

        this.character = character;
        this.startPosition = character.getPosition();
        this.endPosition = endPosition;
    }

    @Override
    public void execute() {

        character.moveTo(endPosition);
    }

    @Override
    public void undo() {

        character.moveTo(startPosition);
    }
}
</code></pre>
<h6>c. 命令接收与执行</h6>
<p>在游戏服务器的处理逻辑中，接收玩家的操作指令，并创建相应的命令对象执行。这通常涉及到网络消息处理、命令工厂模式（用于根据指令类型创建不同的命令对象）等机制。</p>
<h6>d. 命令队列与并发处理</h6>
<p>为了提高系统性能，可以使用命令队列来批量处理玩家的操作指令。此外，可以结合线程池等技术，实现命令的并发执行。</p>
<pre><code class="prism language-java">public class CommandQueue {

    private Queue&lt;Command&gt; queue = new LinkedList&lt;&gt;();

    public void enqueue(Command command) {

        queue.add(command);
    }

    public void processCommands() {

        while (!queue.isEmpty()) {

            Command command = queue.poll();
            command.execute();
            // 如果有需要，可以在这里加入异常处理、日志记录等
        }
    }
}
</code></pre>
<h4>四、优势与考量</h4>
<h5>优势</h5>
<ol>
 <li><strong>解耦</strong>：命令模式将请求者和接收者解耦，增加了系统的灵活性和可扩展性。</li>
 <li><strong>灵活性</strong>：通过添加新的命令类可以轻松扩展游戏功能，无需修改现有代码。</li>
 <li><strong>易于维护</strong>：每个命令都封装了自己的逻辑，使得代码更加模块化和易于管理。</li>
 <li><strong>支持撤销/重做</strong>：对于需要此类功能的游戏，可以很方便地实现。</li>
</ol>
<h5>考量</h5>
<ol>
 <li><strong>性能开销</strong>：虽然命令模式带来了诸多好处，但额外的对象创建和方法调用可能会带来一定的性能开销。因此，在设计时需要权衡性能和灵活性。</li>
 <li><strong>设计复杂度</strong>：命令模式增加了系统的设计复杂度，特别是在游戏逻辑非常复杂的情况下，需要仔细设计命令的接口和继承结构。</li>
 <li><strong>并发处理</strong>：在处理大量并发请求时，需要确保命令队列和并发执行机制能够高效地工作，避免出现死锁、竞态条件或性能瓶颈。</li>
</ol>
<h4>五、深入应用与优化</h4>
<h5>1. 命令工厂模式</h5>
<p>在手游后端中，为了根据接收到的玩家指令动态创建不同类型的命令对象，可以使用命令工厂模式（Factory Pattern）与命令模式结合。命令工厂类负责根据指令类型或参数创建并返回相应的命令对象。这样做既简化了命令对象的创建过程，又提高了代码的可维护性和可扩展性。</p>
<h5>2. 命令链模式</h5>
<p>在某些情况下，一个命令的执行可能依赖于另一个命令的执行结果，或者需要多个命令按顺序执行。这时，可以使用命令链模式（Chain of Responsibility Pattern）来组织命令对象。在命令链中，每个命令对象都包含对下一个命令对象的引用，当执行当前命令时，可以决定是否继续执行链中的下一个命令。</p>
<h5>3. 并发控制</h5>
<p>在手游后端中，并发处理是一个重要的问题。当多个玩家同时发送操作指令时，服务器需要能够高效地处理这些指令，并确保数据的一致性和完整性。对于命令队列的并发处理，可以使用线程池、并发队列等机制来提高效率。同时，还需要考虑使用锁、信号量等同步机制来避免竞态条件和死锁问题。</p>
<h5>4. 持久化与恢复</h5>
<p>在手游中，玩家的游戏进度和状态通常需要持久化存储到数据库中，以便在玩家下次登录时能够恢复。对于使用命令模式实现的游戏逻辑，可以通过记录执行过的命令序列来实现持久化。当玩家重新登录时，可以通过重新执行这些命令来恢复游戏状态。这要求命令对象需要具有可序列化和反序列化的能力。</p>
<h5>5. 日志与监控</h5>
<p>在手游后端架构中，日志记录和监控是非常重要的环节。通过记录命令的执行日志，可以追踪游戏逻辑的执行情况，定位问题并进行调试。同时，还可以通过分析日志数据来优化游戏性能和用户体验。因此，在实现命令模式时，需要考虑如何将命令的执行情况记录到日志系统中，并设计合理的日志级别和格式。</p>
<h4>六、案例分析与实战</h4>
<p>假设我们正在开发一款MMORPG（大型多人在线角色扮演游戏）的后端架构，其中包含了复杂的角色交互、战斗系统、任务系统等。我们可以利用命令模式来组织这些系统的逻辑：</p>
<ul>
 <li><strong>角色交互</strong>：将角色的移动、对话、交易等操作封装为不同的命令对象。这些命令对象可以根据接收到的玩家指令动态创建并执行。</li>
 <li><strong>战斗系统</strong>：将攻击、防御、技能释放等战斗操作封装为命令对象。通过命令链模式，可以将多个战斗命令组合成一个战斗流程，如先释放技能再进行普通攻击。</li>
 <li><strong>任务系统</strong>：将任务的接受、完成、提交等操作封装为命令对象。这些命令对象可以与角色交互和战斗系统的命令对象相互协作，共同完成复杂的任务逻辑。</li>
</ul>
<p>在实战中，我们还需要考虑如何将这些命令对象与游戏世界的状态管理相结合。例如，可以使用观察者模式（Observer Pattern）来监听游戏世界状态的变化，并根据需要触发相应的命令执行。同时，还需要考虑如何处理玩家之间的同步问题，确保所有玩家都能看到一致的游戏状态。</p>
<h4>七、结论</h4>
<p>命令模式在手游后端架构中的应用，为游戏逻辑的模块化、解耦和灵活控制提供了有力的支持。通过合理设计命令接口和继承结构，结合命令工厂模式、命令链模式等设计模式，可以构建出高效、可扩展且易于维护的游戏后端架构。同时，还需要注意并发处理、持久化与恢复、日志与监控等关键问题的处理，以确保游戏服务的稳定性和性能。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141550811</p>]]></description><guid isPermaLink="false">/archives/f25a504b-f902-4230-a1cb-7c5156919466</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 07:50:58 GMT</pubDate></item><item><title><![CDATA[火绒安全：一款强大且高效的国产杀毒软件]]></title><link>https://wiki.dgfygk.com/archives/78f69bae-c368-494c-82a0-2591d44110e2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%81%AB%E7%BB%92%E5%AE%89%E5%85%A8%EF%BC%9A%E4%B8%80%E6%AC%BE%E5%BC%BA%E5%A4%A7%E4%B8%94%E9%AB%98%E6%95%88%E7%9A%84%E5%9B%BD%E4%BA%A7%E6%9D%80%E6%AF%92%E8%BD%AF%E4%BB%B6&amp;url=/archives/78f69bae-c368-494c-82a0-2591d44110e2" width="1" height="1" alt="" style="opacity:0;">
<p><strong>火绒安全：一款强大且高效的国产杀毒软件</strong></p>
<p>在当今数字化时代，网络安全威胁日益严峻，各种病毒、木马、勒索软件层出不穷，对个人和企业的信息安全构成了巨大挑战。在这样的背景下，一款强大且高效的杀毒软件显得尤为重要。火绒安全（Huorong Security），作为一款国产杀毒软件，凭借其出色的性能、全面的防护功能以及低系统资源占用的特点，赢得了广泛的认可和用户的好评。本文将从火绒安全的特点、功能、优势、用户体验以及技术创新等多个方面进行深入探讨。</p>
<h4>一、火绒安全的特点</h4>
<p><strong>1. 轻量高效</strong></p>
<p>火绒安全软件采用轻量级设计，安装包仅有几兆大小，运行时占用系统资源极少。这种设计使得火绒安全软件在不影响用户正常工作和娱乐体验的前提下，能够流畅运行在各种配置的电脑上，尤其是中低配电脑。这一特点在当前硬件配置参差不齐的环境下显得尤为重要，也是火绒安全受到广泛欢迎的重要原因之一。</p>
<p><strong>2. 高效性能</strong></p>
<p>得益于火绒反病毒引擎的强大支持，火绒安全软件在病毒查杀、漏洞修复等方面表现出色。其扫描速率快、查杀精准、误报率低，能够为用户提供高效的安全防护。火绒反病毒引擎采用"通用脱壳"技术，通过启发式逻辑评估待扫描样本，在虚拟环境中还原被保护的代码、数据和行为，从而有效识别自定义壳、代码混淆器等代码级对抗，并结合启发式分析算法评估其恶意行为。此外，火绒还具备内存防护、高级启发式扫描等功能，能够进一步提升对复杂恶意软件的识别能力。</p>
<p><strong>3. 全面防护</strong></p>
<p>火绒安全软件具备实时监控、主动防御、病毒查杀、漏洞修复等多种功能，为用户打造全方位的安全防护网。它不仅能够识别和清除多种类型的病毒、木马、恶意软件，还提供了防火墙、防勒索软件、防钓鱼网站等防护功能。此外，火绒还具备智能拦截技术，能够自动识别并阻止恶意软件、广告弹窗等干扰用户正常使用电脑的行为。</p>
<p><strong>4. 无广告干扰</strong></p>
<p>与市场上一些免费杀毒软件不同，火绒安全软件在运行过程中不会弹出广告或推荐其他软件。这一特点为用户提供了更加纯粹的安全保护体验，避免了因广告干扰而影响使用感受的情况发生。</p>
<h4>二、火绒安全的功能</h4>
<p><strong>1. 病毒查杀</strong></p>
<p>火绒安全软件支持快速扫描、全盘扫描等多种扫描方式，能够全面检测并清除电脑中的病毒、木马等恶意软件。同时，火绒还具备病毒库定期更新功能，以应对新出现的安全威胁。</p>
<p><strong>2. 漏洞修复</strong></p>
<p>火绒安全软件会定期检查系统漏洞并提供修复建议。用户可以根据提示进行操作以修复系统漏洞并提高电脑安全性。这一功能对于提升系统的整体防御能力具有重要意义。</p>
<p><strong>3. 防火墙</strong></p>
<p>火绒安全软件内置防火墙功能，能够监控和拦截网络中的恶意流量和攻击行为，保护用户的网络安全。防火墙的智能化设置使得用户可以根据自己的需求进行灵活配置。</p>
<p><strong>4. 防勒索软件</strong></p>
<p>勒索软件是当前网络安全领域的一大威胁。火绒安全软件提供了防勒索软件功能，能够在检测到勒索软件时尝试恢复被加密的数据，减少用户损失。</p>
<p><strong>5. 智能拦截</strong></p>
<p>火绒安全软件的智能拦截技术能够自动识别并阻止恶意软件和广告弹窗等干扰用户正常使用电脑的行为。这一功能不仅提升了用户体验，还增强了系统的整体安全性。</p>
<h4>三、火绒安全的优势</h4>
<p><strong>1. 自主研发的技术</strong></p>
<p>火绒安全软件由一群拥有多年反病毒经验的专家团队创建，他们具备深厚的安全技术积累和经验。火绒反病毒引擎是火绒安全的核心技术之一，具有自主知识产权和高效的虚拟沙盒技术。这些技术使得火绒安全在病毒查杀、漏洞修复等方面表现出色。</p>
<p><strong>2. 贴近国内需求</strong></p>
<p>作为一款国产杀毒软件，火绒安全软件更懂国内用户需求。它能够捕捉病毒核心特征并高效查杀潜在威胁，为用户提供更加贴心的服务和支持。此外，火绒还提供了多种符合国内用户习惯的功能和设置选项。</p>
<p><strong>3. 良好的用户口碑</strong></p>
<p>火绒安全软件凭借其出色的性能、强大的防御能力以及低干扰的用户体验赢得了广大用户的认可和好评。在多个评测和排名中均名列前茅，成为许多用户的首选杀毒软件之一。</p>
<h4>四、用户体验</h4>
<p><strong>1. 界面简洁直观</strong></p>
<p>火绒安全软件的界面设计简洁直观，分为病毒查杀、防护中心、访问控制和安全工具四大部分。用户能够轻松找到所需功能并进行操作。此外，火绒还提供了一些隐藏功能如自动拦截捆绑软件等以满足用户的个性化需求。</p>
<p><strong>2. 定制化安全策略</strong></p>
<p>火绒安全软件允许用户自定义规则和设置以满足不同用户的个性化需求。例如用户可以设置邮件监控、U盘使用控制等高级防护功能以提升系统的整体安全性。</p>
<p><strong>3. 实时监控与主动防御</strong></p>
<p>火绒安全软件的实时监控功能如同一位无形的守护者，时刻监测着电脑的运行状态。它能够即时识别并阻止恶意软件的入侵，包括那些试图通过隐藏进程、注入系统关键组件等手段进行潜伏的威胁。同时，火绒的主动防御机制通过智能分析软件行为，预判潜在的安全风险，并在恶意行为发生前进行拦截，从而有效提升了系统的防护能力。</p>
<p><strong>4. 丰富的安全工具</strong></p>
<p>除了核心的防病毒功能外，火绒安全软件还提供了丰富的安全工具，如网络流量监控、弹窗拦截、软件净化等。这些工具不仅能够帮助用户更好地管理电脑资源，提升使用效率，还能有效抵御网络攻击和恶意广告的侵扰。例如，网络流量监控功能可以实时显示电脑的上传下载速度，帮助用户识别并阻止异常的网络活动；弹窗拦截功能则能够自动过滤掉大部分广告弹窗，为用户营造一个更加清爽的上网环境。</p>
<p><strong>5. 贴心的用户服务</strong></p>
<p>火绒安全软件注重用户体验，提供了完善的用户服务。无论是产品安装、功能使用还是问题咨询，用户都可以通过官方网站、客服热线或在线社区等途径获得及时的帮助和支持。此外，火绒还定期发布安全资讯和防护指南，帮助用户了解最新的安全威胁和防护方法，提升整体的安全意识。</p>
<h4>五、技术创新</h4>
<p><strong>1. 人工智能技术的应用</strong></p>
<p>火绒安全软件在技术研发方面不断探索和创新，将人工智能技术应用于病毒检测和防御中。通过机器学习和深度学习算法，火绒能够更准确地识别未知威胁和恶意软件变种，提高检测的准确性和效率。同时，人工智能技术还能够帮助火绒优化资源分配和性能表现，确保软件在占用较少系统资源的同时提供高效的安全防护。</p>
<p><strong>2. 云端与本地结合的防护体系</strong></p>
<p>火绒安全软件构建了云端与本地相结合的防护体系。云端防护通过实时更新病毒库和威胁情报，为软件提供最新的防护策略和规则。本地防护则通过实时监控和主动防御机制，确保电脑在离线状态下也能得到有效保护。这种云端与本地相结合的防护体系能够全面提升软件的防护能力和响应速度。</p>
<p><strong>3. 定制化安全解决方案</strong></p>
<p>针对不同行业和用户群体的特定需求，火绒安全软件提供了定制化安全解决方案。这些解决方案结合了行业特点和用户需求，通过优化防护策略、增加专用工具和提供专属服务等方式，为用户提供更加全面和个性化的安全防护。例如，针对企业用户，火绒可以提供网络版安全软件，实现对企业内部网络的整体防护和管理；针对教育用户，火绒则可以提供校园版安全软件，为师生提供更加安全、稳定的学习环境。</p>
<h4>六、结论</h4>
<p>综上所述，火绒安全作为一款强大且高效的国产杀毒软件，凭借其轻量高效、全面防护、无广告干扰等特点以及自主研发的技术、贴近国内需求等优势赢得了广泛的认可和用户的好评。在未来的发展中，火绒安全将继续坚持技术创新和用户至上的原则，不断提升产品的性能和服务质量，为用户提供更加全面、高效、便捷的安全防护体验。同时，我们也期待火绒安全能够在网络安全领域发挥更大的作用，为构建更加安全、可信的网络环境贡献力量。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141495641</p>]]></description><guid isPermaLink="false">/archives/78f69bae-c368-494c-82a0-2591d44110e2</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 07:46:50 GMT</pubDate></item><item><title><![CDATA[设计模式反模式：UML图示常见误用案例分析]]></title><link>https://wiki.dgfygk.com/archives/5a91277d-589c-4789-b0a8-bcfff2104f62</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8F%8D%E6%A8%A1%E5%BC%8F%EF%BC%9AUML%E5%9B%BE%E7%A4%BA%E5%B8%B8%E8%A7%81%E8%AF%AF%E7%94%A8%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90&amp;url=/archives/5a91277d-589c-4789-b0a8-bcfff2104f62" width="1" height="1" alt="" style="opacity:0;">
<h4>设计模式反模式：UML图示常见误用案例分析</h4>
<p>在软件开发过程中，设计模式（Design Patterns）作为解决常见设计问题的最佳实践，被广泛地应用于提升代码质量和系统可维护性。然而，当这些设计模式被误用或滥用时，它们可能会转变为反模式（Anti-Patterns），导致系统架构的复杂性增加，甚至引发一系列问题。特别是在使用UML（统一建模语言）图示时，这些误用表现得尤为明显。本文将通过几个具体的案例分析，探讨UML图示中常见的设计模式反模式及其影响。</p>
<h5>一、类关系混淆</h5>
<p><strong>案例描述</strong>：</p>
<p>在一个用于管理图书馆的系统中，设计人员使用UML类图来表示图书（Book）、借阅者（Borrower）和借阅记录（BorrowRecord）之间的关系。然而，他们错误地将"借阅者"类与"借阅记录"类之间使用了关联关系（Association），而不是聚合关系（Aggregation）。</p>
<p><strong>UML图示</strong>：</p>
<pre><code class="prism language-plaintext">+---------------+    +---------------+
|     Book      |    |   Borrower    |
+---------------+    +---------------+
|- title        |&lt;&gt;--|- name         |
|- author       |    |               |
+---------------+    +---------------+
                       |
                       v
                +---------------+
                | BorrowRecord  |
                +---------------+
                |- borrower     |
                |- book         |
                |- borrowDate   |
                |- returnDate   |
                +---------------+
</code></pre>
<p><strong>误用分析</strong>：</p>
<ol>
 <li>
  <p><strong>误解类之间的关系</strong>：关联关系表示两个类在时间上是相互独立的，而聚合关系则表示一个类包含另一个类的生命周期。在这个案例中，借阅记录是随着借阅者的借阅行为而产生的，并且其生命周期与借阅者的借阅行为紧密相关。因此，使用关联关系表示"借阅者"和"借阅记录"之间的关系是不准确的。</p>
 </li>
 <li>
  <p><strong>可维护性降低</strong>：混淆关系可能导致后续的代码修改变得复杂，维护人员可能会误解类之间的依赖关系。在UML类图中，应准确选择类之间的关系类型。对于"借阅者"与"借阅记录"之间的关系，应该使用聚合关系来表示借阅者与他们的借阅记录之间的包含关系。</p>
 </li>
</ol>
<h5>二、过度复杂的继承关系</h5>
<p><strong>案例描述</strong>：</p>
<p>在一个电商平台的UML类图中，设计人员为商品（Product）、电子商品（ElectronicItem）和服装商品（ClothingItem）设计了过于复杂的继承关系。</p>
<p><strong>UML图示</strong>：</p>
<pre><code class="prism language-plaintext">+---------------+     +---------------+
|    Product    |     | ElectronicItem|
+---------------+     +---------------+
|- name         |     |- batteryLife  |
| ...           |     | ...           |
+---------------+     +---------------+
             \         /
              \       /
               \     /
                +---------------+
                | ClothingItem  |
                +---------------+
                |- size         |
                | ...           |
                +---------------+
</code></pre>
<p><strong>误用分析</strong>：</p>
<ol>
 <li>
  <p><strong>增加复杂性</strong>：过度复杂的继承关系可能导致理解上的困难，降低了代码的可读性和可维护性。在这种设计中，电子商品和服装商品都继承自商品类，但同时又各自拥有特定的属性，这种设计方式使得类的层次结构变得复杂且难以管理。</p>
 </li>
 <li>
  <p><strong>潜在的脆弱性</strong>：子类的改变可能会对父类产生严重影响，实现了对称的耦合关系，增加了系统的脆弱性。例如，如果商品类中添加了一个新的属性或方法，所有子类都需要进行相应的调整，这增加了维护的难度和出错的风险。</p>
 </li>
</ol>
<p><strong>解决方案</strong>：</p>
<p>在设计类图时，尽量使用组合而非继承。可以通过接口或一些共享的功能类来实现多态性，而不是创建深层次的继承结构。例如，可以定义一个商品接口（IProduct），然后让电子商品和服装商品都实现这个接口，同时各自拥有自己特定的属性和方法。</p>
<h5>三、接口与实现的关系模糊</h5>
<p><strong>案例描述</strong>：</p>
<p>在一个订单处理系统的UML类图中，设计人员将接口（OrderService）和实现类（OrderServiceImpl）的关系表示得不够清晰。</p>
<p><strong>UML图示</strong>：</p>
<pre><code class="prism language-plaintext">+---------------+     +----------------------+
|   OrderService  |   |   OrderServiceImpl   |
+---------------+     +----------------------+
|+createOrder()|     |+createOrder()         |
+---------------+     |+...                  |
                      +----------------------+
</code></pre>
<p><strong>误用分析</strong>：</p>
<ol>
 <li>
  <p><strong>接口实现不明确</strong>：在图中没有明确标示出实现关系，可能使得开发人员在处理时混淆了接口和实现的角色。这会导致在后续的开发和维护过程中，团队成员可能不清楚哪些类实现了哪些接口，从而增加了出错的风险。</p>
 </li>
 <li>
  <p><strong>潜在的代码不一致</strong>：开发团队可能在实现接口时产生不一致，导致后续的错误。例如，如果接口中的方法签名发生了变化，但实现类中的方法签名没有相应更新，就会导致编译错误或运行时异常。</p>
 </li>
</ol>
<p><strong>解决方案</strong>：</p>
<p>在UML图示中，应明确标示出接口和实现类之间的关系。通常，这可以通过在接口和实现类之间使用一条带有空心箭头的线来表示，箭头指向实现类。同时，在UML工具中，还可以为这种关系添加标签，如"implements"或”&lt;&gt;“，以进一步明确这种关系。</p>
<pre><code class="prism language-plaintext">+---------------+     +----------------------+
|   OrderService  |   |   OrderServiceImpl   |
+---------------+     +----------------------+
|+createOrder()|-----&gt;|+createOrder()         |
+---------------+     |+...                  |
                      +----------------------+
                       (implements)
</code></pre>
<h5>四、滥用枚举类型</h5>
<p><strong>案例描述</strong>：</p>
<p>在一个订单状态管理系统中，设计人员错误地将所有可能的订单状态（如待支付、已支付、已发货、已完成等）都定义在一个庞大的枚举类型（OrderStatus）中。</p>
<p><strong>UML图示</strong>：</p>
<pre><code class="prism language-plaintext">+---------------+
|   OrderStatus |
+---------------+
|+PENDING       |
|+PAID          |
|+SHIPPED       |
|+COMPLETED     |
|+...           |
+---------------+
</code></pre>
<p><strong>误用分析</strong>：</p>
<ol>
 <li>
  <p><strong>可扩展性差</strong>：随着系统的发展，可能需要添加新的订单状态。在一个庞大的枚举类型中添加新状态不仅繁琐，而且容易出错。此外，如果枚举类型被多个模块或子系统共享，修改它可能会影响到其他部分。</p>
 </li>
 <li>
  <p><strong>难以维护</strong>：当枚举类型变得庞大时，理解和维护它变得困难。团队成员可能不清楚每个状态的具体含义和用途，导致在编写代码时出错。</p>
 </li>
</ol>
<p><strong>解决方案</strong>：</p>
<p>考虑使用状态模式（State Pattern）或策略模式（Strategy Pattern）来管理订单状态。这些模式允许将每个状态封装为一个单独的类，并通过接口或基类来定义共同的行为。这样，每个状态都可以独立地扩展和维护，而不会影响到其他状态。</p>
<h5>五、错误的组件划分</h5>
<p><strong>案例描述</strong>：</p>
<p>在一个大型的企业资源规划（ERP）系统中，设计人员错误地将所有与订单相关的功能都放在一个单一的组件（OrderComponent）中，而没有根据功能的不同进行细分。</p>
<p><strong>UML图示</strong>：</p>
<pre><code class="prism language-plaintext">+---------------+
| OrderComponent|
+---------------+
|-createOrder() |
|-updateOrder() |
|-cancelOrder() |
|-shipOrder()   |
|-...           |
+---------------+
</code></pre>
<p><strong>误用分析</strong>：</p>
<ol>
 <li>
  <p><strong>高耦合度</strong>：将所有与订单相关的功能都放在一个组件中，会导致该组件与其他组件之间的耦合度增加。这不利于系统的模块化和可重用性。</p>
 </li>
 <li>
  <p><strong>难以维护</strong>：随着功能的增加，OrderComponent会变得越来越大，越来越难以维护。此外，由于所有功能都紧密耦合在一起，修改一个功能可能会影响到其他功能。</p>
 </li>
</ol>
<p><strong>解决方案</strong>：</p>
<p>根据功能的不同，将OrderComponent拆分成多个更小的组件，如OrderCreationComponent、OrderUpdateComponent、OrderShippingComponent等。每个组件负责一组相关的功能，并通过接口或事件与其他组件进行交互。这样可以降低组件之间的耦合度，提高系统的模块化和可维护性。</p>
<h5>总结</h5>
<p>UML图示是软件开发过程中重要的沟通工具，但如果不当使用，可能会导致设计模式反模式的出现。通过避免类关系混淆、过度复杂的继承关系、接口与实现关系模糊、滥用枚举类型以及错误的组件划分等常见误用，可以确保UML图示的准确性和有效性，从而提升软件系统的质量和可维护性。在实际应用中，开发人员应深入理解UML和设计模式的相关知识，并结合具体项目的需求来合理使用它们。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141550709</p>]]></description><guid isPermaLink="false">/archives/5a91277d-589c-4789-b0a8-bcfff2104f62</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 07:31:40 GMT</pubDate></item><item><title><![CDATA[突破编程：C++中的组合模式（Composite Pattern）]]></title><link>https://wiki.dgfygk.com/archives/8ec4fce0-70a7-4078-9ac3-a0c0b5a9a31b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%AA%81%E7%A0%B4%E7%BC%96%E7%A8%8B%EF%BC%9AC%2B%2B%E4%B8%AD%E7%9A%84%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F%EF%BC%88Composite%20Pattern%EF%BC%89&amp;url=/archives/8ec4fce0-70a7-4078-9ac3-a0c0b5a9a31b" width="1" height="1" alt="" style="opacity:0;">
<h4>突破编程：C++中的组合模式（Composite Pattern）</h4>
<p>在软件设计领域，组合模式（Composite Pattern）是一种结构型设计模式，它允许你将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式让客户端代码可以一致地处理单个对象和组合对象，无需关心对象的具体类型，从而简化了客户端代码。在C++中实现组合模式，可以充分利用C++的面向对象特性和模板等高级功能，以优雅地解决复杂对象结构的构建和管理问题。</p>
<h5>一、组合模式的基本概念</h5>
<p>组合模式的关键在于定义了一个统一的接口用于访问组件对象（无论是叶子对象还是容器对象）。容器对象可以包含其他组件对象（包括容器对象和叶子对象），而叶子对象则不包含其他组件对象。这种结构使得客户端可以透明地处理不同类型的组件对象，无需关心它们的具体类型。</p>
<p>组合模式通常包含以下三个主要角色：</p>
<ol>
 <li>
  <p><strong>组件接口（Component）</strong>：为组合中的对象声明一个接口，在适当的情况下，声明一个接口用于访问和管理子组件。这个接口可以是一个抽象类或接口，它定义了所有对象共有的操作。</p>
 </li>
 <li>
  <p><strong>叶子节点（Leaf）</strong>：是组合中的叶对象，它没有子对象。在组合结构的表示中，它代表最基本的对象，用于被组合到树形结构中，但不再包含其他对象。</p>
 </li>
 <li>
  <p><strong>容器节点（Composite）</strong>：是组合中的容器对象，它可以包含子对象，其子对象可以是叶子节点，也可以是容器节点，从而形成了树形结构。容器节点提供一个集合来管理其子对象，并实现组件接口中定义的方法，包括添加、删除子对象等。</p>
 </li>
</ol>
<h5>二、C++中实现组合模式</h5>
<p>在C++中，我们可以使用类继承和虚函数来实现组合模式。以下是一个简单的示例，展示了如何在C++中构建组合模式。</p>
<h6>1. 定义组件接口</h6>
<p>首先，我们定义一个组件接口（Component），它包含一个纯虚函数，用于执行某些操作（如显示）。</p>
<pre><code class="prism language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;memory&gt;

class Component {

public:
    virtual ~Component() {
   }
    virtual void operation() const = 0; // 纯虚函数，定义操作接口

    // 可以添加其他公共接口，如添加、删除子组件等（这里为了简化省略）
};
</code></pre>
<h6>2. 实现叶子节点</h6>
<p>接着，我们实现叶子节点（Leaf），它继承自Component，并实现operation方法。</p>
<pre><code class="prism language-cpp">class Leaf : public Component {

public:
    Leaf(const std::string&amp; name) : name_(name) {
   }

    void operation() const override {

        std::cout &lt;&lt; "Leaf: " &lt;&lt; name_ &lt;&lt; std::endl;
    }

private:
    std::string name_;
};
</code></pre>
<h6>3. 实现容器节点</h6>
<p>然后，我们实现容器节点（Composite），它也继承自Component，并包含一个Component对象的列表来管理子对象。</p>
<pre><code class="prism language-cpp">class Composite : public Component {

public:
    Composite(const std::string&amp; name) : name_(name) {
   }

    void operation() const override {

        std::cout &lt;&lt; "Composite: " &lt;&lt; name_ &lt;&lt; std::endl;
        for (const auto&amp; child : children_) {

            child-&gt;operation();
        }
    }

    void add(std::shared_ptr&lt;Component&gt; component) {

        children_.push_back(component);
    }

    void remove(std::shared_ptr&lt;Component&gt; component) {

        auto it = std::find(children_.begin(), children_.end(), component);
        if (it != children_.end()) {

            children_.erase(it);
        }
    }

private:
    std::string name_;
    std::vector&lt;std::shared_ptr&lt;Component&gt;&gt; children_;
};
</code></pre>
<h6>4. 客户端代码</h6>
<p>最后，我们编写客户端代码来演示如何使用组合模式。</p>
<pre><code class="prism language-cpp">int main() {

    // 创建组件
    auto leaf1 = std::make_shared&lt;Leaf&gt;("Leaf 1");
    auto leaf2 = std::make_shared&lt;Leaf&gt;("Leaf 2");

    // 创建组合
    auto composite1 = std::make_shared&lt;Composite&gt;("Composite 1");
    composite1-&gt;add(leaf1);
    composite1-&gt;add(leaf2);

    // 创建另一个组合，并将之前的组合作为子组件
    auto composite2 = std::make_shared&lt;Composite&gt;("Composite 2");
    composite2-&gt;add(composite1);

    // 执行操作
    composite2-&gt;operation();
</code></pre>
<h5>五、组合模式的变种与扩展</h5>
<p>虽然组合模式本身已经非常强大和灵活，但在实际应用中，我们可能会遇到一些需要对其进行扩展或变种的情况。以下是一些常见的变种和扩展方式：</p>
<ol>
 <li>
  <p><strong>透明式与安全式</strong>：组合模式可以分为透明式（也称为统一接口模式）和安全式（也称为安全组合模式）。在透明式中，所有组件都实现了相同的接口，包括叶子节点和容器节点。这简化了客户端代码，但可能会增加一些不必要的操作（如叶子节点上的添加和删除操作）。在安全式中，容器节点和叶子节点有不同的接口，客户端代码需要知道它正在操作的是哪种类型的组件。这增加了代码的复杂度，但提高了安全性。</p>
 </li>
 <li>
  <p><strong>增加访问者模式</strong>：有时，我们需要在组合结构上执行更复杂的操作，而不仅仅是简单地遍历和执行操作。在这种情况下，我们可以将访问者模式与组合模式结合使用。访问者模式允许我们为组合结构中的每个节点定义一个访问者类，并在节点上执行所需的复杂操作。</p>
 </li>
 <li>
  <p><strong>动态组合</strong>：在某些情况下，我们可能需要在运行时动态地构建组合结构。这可以通过使用工厂模式或原型模式等设计模式来实现，以根据需要创建和组合组件。</p>
 </li>
 <li>
  <p><strong>缓存和优化</strong>：在处理大型组合结构时，为了提高性能，我们可以考虑实现缓存机制或优化算法来减少不必要的计算和内存使用。例如，我们可以缓存已计算的子树结果，以避免重复计算。</p>
 </li>
</ol>
<p>总之，组合模式是一种非常强大和灵活的设计模式，它允许我们以树形结构来表示和操作复杂对象。在C++中实现组合模式时，我们可以充分利用C++的面向对象特性和模板等高级功能来构建高效、可维护的代码结构。通过合理应用组合模式及其变种和扩展方式，我们可以设计出更加灵活、可扩展和易于维护的软件系统。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141526692</p>]]></description><guid isPermaLink="false">/archives/8ec4fce0-70a7-4078-9ac3-a0c0b5a9a31b</guid><dc:creator>黄健</dc:creator><category>C++</category><pubDate>Sat, 28 Sep 2024 07:31:02 GMT</pubDate></item><item><title><![CDATA[在Kubernetes中etcd作为存储集群]]></title><link>https://wiki.dgfygk.com/archives/75313048-e63f-45c4-a635-b701c4bb108f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8Kubernetes%E4%B8%ADetcd%E4%BD%9C%E4%B8%BA%E5%AD%98%E5%82%A8%E9%9B%86%E7%BE%A4&amp;url=/archives/75313048-e63f-45c4-a635-b701c4bb108f" width="1" height="1" alt="" style="opacity:0;">
<p>在Kubernetes中，etcd作为存储集群所有关键数据的分布式键值存储系统，其数据的备份与还原对于确保集群的可靠性和数据完整性至关重要。以下是关于如何在Kubernetes中对etcd进行备份和还原的详细步骤，内容不少于2000字。</p>
<h4>一、备份etcd数据</h4>
<h5>1. 确定备份策略</h5>
<p>在进行etcd数据备份之前，首先需要确定备份策略。这包括确定备份的频率（如每天、每周或每月备份一次）、备份的存储位置（如本地磁盘、远程云存储服务等）以及备份的保留周期（即保留多少天的备份数据）。建议定期进行etcd数据备份，并在多个安全的位置进行存储，以防止数据丢失。</p>
<h5>2. 安装etcdctl</h5>
<p>etcdctl是etcd的命令行客户端工具，用于与etcd集群进行交互。在进行备份之前，需要确保已经安装了etcdctl。etcdctl可以通过从etcd的官方GitHub仓库下载相应版本的二进制文件，并将其放置在系统的PATH路径下来实现安装。</p>
<h5>3. 执行备份命令</h5>
<p>使用etcdctl命令执行快照备份。命令的基本语法如下：</p>
<pre><code class="prism language-bash">ETCDCTL_API=3 etcdctl --endpoints="&lt;etcd-endpoints&gt;" --cacert=&lt;ca-file&gt; --cert=&lt;cert-file&gt; --key=&lt;key-file&gt; snapshot save &lt;snapshot-file-name&gt;
</code></pre>
<p>其中，<code>&lt;etcd-endpoints&gt;</code>是etcd集群的访问地址，<code>&lt;ca-file&gt;</code>、<code>&lt;cert-file&gt;</code>和<code>&lt;key-file&gt;</code>分别是客户端证书、私钥和CA证书的路径（如果etcd启用了TLS加密），<code>&lt;snapshot-file-name&gt;</code>是备份文件的名称和路径。</p>
<p>示例命令（假设etcd运行在本地，且证书和密钥文件位于<code>/etc/kubernetes/pki/etcd/</code>目录下）：</p>
<pre><code class="prism language-bash">ETCDCTL_API=3 etcdctl --endpoints="https://127.0.0.1:2379" --cacert="/etc/kubernetes/pki/etcd/ca.crt" --cert="/etc/kubernetes/pki/etcd/server.crt" --key="/etc/kubernetes/pki/etcd/server.key" snapshot save /opt/etcd/backup/etcdbackup.db
</code></pre>
<h5>4. 验证备份文件</h5>
<p>备份完成后，应验证备份文件的完整性和可用性。可以使用etcdctl的<code>snapshot status</code>命令来检查备份文件的状态。命令示例：</p>
<pre><code class="prism language-bash">ETCDCTL_API=3 etcdctl snapshot status /opt/etcd/backup/etcdbackup.db
</code></pre>
<p>该命令会输出备份文件的一些详细信息，如哈希值、修订号、总键数和总大小等，这些信息可以帮助验证备份文件是否正确无误。</p>
<h5>5. 存储备份文件</h5>
<p>验证备份文件无误后，应将其复制到安全的位置进行存储。这可以是远程云存储服务（如Amazon S3、Google Cloud Storage等），也可以是本地磁盘的其他分区或外部硬盘。确保备份文件的安全性和可访问性，以便在需要时能够快速恢复数据。</p>
<h4>二、还原etcd数据</h4>
<h5>1. 停止etcd服务</h5>
<p>在进行还原操作之前，需要停止etcd服务。这可以通过系统管理工具（如<code>systemctl</code>）来实现。命令示例：</p>
<pre><code class="prism language-bash">systemctl stop etcd
</code></pre>
<p>如果是在新的集群上还原，需要确保新集群的etcd节点数量与旧集群相同，并配置好相应的网络和安全设置。如果是在原集群上还原，需要删除现有的etcd数据目录（注意，这将导致数据丢失，请确保已有备份）。</p>
<h5>2. 复制备份文件</h5>
<p>将备份文件复制到需要还原的etcd节点上，并确保备份文件的路径和名称正确。如果备份文件存储在远程云存储服务上，则需要先下载到本地。</p>
<h5>3. 执行还原命令</h5>
<p>使用etcdctl命令执行快照还原。命令的基本语法如下：</p>
<pre><code class="prism language-bash">ETCDCTL_API=3 etcdctl snapshot restore &lt;snapshot-file-name&gt; --data-dir=&lt;data-directory&gt;
</code></pre>
<p>其中，<code>&lt;snapshot-file-name&gt;</code>是备份文件的路径和名称，<code>&lt;data-directory&gt;</code>是etcd数据存储目录的路径。确保指定的数据目录为空，否则会导致数据冲突。</p>
<p>示例命令（假设备份文件位于<code>/opt/etcd/backup/etcdbackup.db</code>，且希望将数据还原到<code>/var/lib/etcd</code>目录下）：</p>
<pre><code class="prism language-bash">ETCDCTL_API=3 etcdctl snapshot restore /opt/etcd/backup/etcdbackup.db --data-dir=/var/lib/etcd
</code></pre>
<h5>4. 启动etcd服务</h5>
<p>还原完成后，启动etcd服务。这同样可以通过系统管理工具（如<code>systemctl</code>）来实现。命令示例：</p>
<pre><code class="prism language-bash">systemctl start etcd
</code></pre>
<h5>5. 验证etcd服务状态</h5>
<p>启动etcd服务后，应验证etcd服务的状态以确保其正常运行。可以使用etcdctl或其他工具检查etcd集群的状态和数据，以确保还原操作成功完成。</p>
<p>例如，使用etcdctl的<code>endpoint health</code>命令来检查etcd集群的健康状态：</p>
<pre><code class="prism language-bash">ETCDCTL_API=3 etcdctl --endpoints="https://&lt;etcd-endpoints&gt;" --cacert=&lt;ca-file&gt; --cert=&lt;cert-file&gt; --key=&lt;key-file&gt; endpoint health
</code></pre>
<p>如果所有etcd节点都返回健康状态，则表示etcd集群已成功恢复。</p>
<h4>三、注意事项和最佳实践</h4>
<h5>1. 备份和还原的时机</h5>
<ul>
 <li><strong>备份时机</strong>：建议在低负载时段进行etcd数据备份，以减少对集群性能的影响。</li>
 <li><strong>还原时机</strong>：还原操作可能会导致etcd集群暂时不可用，因此应确保在合适的时机进行，并提前通知相关团队。</li>
</ul>
<h5>2. 自动化备份和还原</h5>
<p>在生产环境中，建议配置自动化备份和还原机制。可以使用脚本或第三方工具（如Kubernetes的备份和恢复工具Velero）来定期执行备份和还原操作，以确保数据的持续保护和快速恢复能力。</p>
<h5>3. 备份数据的加密和传输安全</h5>
<ul>
 <li>如果etcd启用了TLS加密，则备份数据在传输过程中应使用安全的连接。</li>
 <li>可以考虑对备份数据进行加密存储，以提高数据的安全性。</li>
</ul>
<h5>4. 版本兼容性</h5>
<p>在进行etcd数据的备份和还原时，需要确保备份和还原时使用的etcd版本兼容。不同版本的etcd可能在数据存储格式和API上存在差异，因此应仔细阅读etcd的官方文档，以了解版本兼容性方面的要求和限制。</p>
<h5>5. 监控和告警</h5>
<p>建议对etcd集群进行监控，并设置相应的告警规则。监控可以帮助及时发现etcd集群的异常情况，而告警则可以在异常情况发生时及时通知相关人员进行处理。</p>
<h4>四、结论</h4>
<p>在Kubernetes中，etcd作为存储集群所有关键数据的核心组件，其数据的备份与还原对于确保集群的可靠性和数据完整性至关重要。通过制定合理的备份策略、安装etcdctl工具、执行备份和还原命令、验证备份文件的完整性和可用性、存储备份文件以及监控etcd集群的状态，可以有效地保护etcd数据的安全性和可用性。同时，还应注意备份和还原的时机、自动化备份和还原机制、备份数据的加密和传输安全、版本兼容性以及监控和告警等方面的问题，以确保etcd集群的稳定运行和数据的安全保护。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141413564</p>]]></description><guid isPermaLink="false">/archives/75313048-e63f-45c4-a635-b701c4bb108f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 07:29:59 GMT</pubDate></item><item><title><![CDATA[在Spring Boot项目中，对密码进行加密]]></title><link>https://wiki.dgfygk.com/archives/3716cbcc-e803-4505-931b-41a56970219a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8Spring%20Boot%E9%A1%B9%E7%9B%AE%E4%B8%AD%EF%BC%8C%E5%AF%B9%E5%AF%86%E7%A0%81%E8%BF%9B%E8%A1%8C%E5%8A%A0%E5%AF%86&amp;url=/archives/3716cbcc-e803-4505-931b-41a56970219a" width="1" height="1" alt="" style="opacity:0;">
<p>在Spring Boot项目中，对密码进行加密是一个重要的安全措施，特别是在处理敏感信息如数据库连接密码时。下面将详细介绍Spring Boot密码加密的步骤，包括引入依赖、配置加密工具、生成加密密钥、加密密码、配置解密以及在应用程序中使用加密后的密码等过程。</p>
<h4>一、引入加密依赖</h4>
<p>首先，你需要在Spring Boot项目的<code>pom.xml</code>文件中引入用于加密的依赖库。常用的加密库有Jasypt和Spring Security等。以Jasypt为例，可以添加如下依赖：</p>
<pre><code class="prism language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.jasypt&lt;/groupId&gt;
    &lt;artifactId&gt;jasypt-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;最新版本&lt;/version&gt; &lt;!-- 请替换为发布时的最新版本 --&gt;
&lt;/dependency&gt;
</code></pre>
<p>请注意，版本号应替换为发布时的最新版本，以确保使用最新的功能和安全修复。</p>
<h4>二、配置加密工具</h4>
<p>在引入依赖后，你需要在Spring Boot的配置文件中配置加密工具的相关参数。对于Jasypt，你需要在<code>application.properties</code>或<code>application.yml</code>文件中设置加密密钥（encryptor password）和加密算法（algorithm）等。</p>
<p>例如，在<code>application.properties</code>中配置如下：</p>
<pre><code class="prism language-properties">jasypt.encryptor.password=your_encryption_password
jasypt.encryptor.algorithm=PBEWithMD5AndDES
</code></pre>
<p>这里的<code>your_encryption_password</code>是你设置的加密密钥，用于解密配置文件中的加密信息。<code>PBEWithMD5AndDES</code>是加密算法，你也可以根据需要选择其他算法。</p>
<h4>三、生成加密密钥和加密密码</h4>
<p>在配置好加密工具后，你需要生成加密密钥（如果尚未生成）并使用该密钥来加密敏感信息（如数据库密码）。对于Jasypt，你可以使用其提供的命令行工具或在线加密工具来生成加密后的密码。</p>
<h5>1. 生成加密密钥（如果尚未生成）</h5>
<p>加密密钥的生成通常是一个一次性过程，你可以将其保存在安全的地方，以便在需要时使用。对于Jasypt，密钥通常是通过配置文件或环境变量等方式提供给应用程序的。</p>
<h5>2. 加密密码</h5>
<p>使用生成的加密密钥和选定的加密算法，你可以对敏感信息进行加密。对于数据库密码，你可以使用Jasypt提供的命令行工具或在线服务来加密它。加密后的密码将是一个看起来像乱码的字符串。</p>
<h4>四、配置解密</h4>
<p>在Spring Boot应用程序中，你不需要显式编写解密代码，因为Jasypt等加密库会在应用程序启动时自动解密配置文件中的加密信息。但是，你需要确保加密密钥已正确配置，并且加密算法与加密时使用的算法相匹配。</p>
<h4>五、在配置文件中使用加密后的密码</h4>
<p>将加密后的密码替换掉原始配置文件中的敏感信息。在Jasypt中，你需要在加密后的密码前加上<code>ENC(</code>前缀和<code>)</code>后缀，以指示这是一个加密的字符串。</p>
<p>例如，对于数据库密码，你可以这样配置：</p>
<pre><code class="prism language-properties">spring.datasource.password=ENC(加密后的密码)
</code></pre>
<h4>六、启动应用程序</h4>
<p>在完成以上步骤后，你可以启动Spring Boot应用程序。在应用程序启动时，Jasypt等加密库会自动解密配置文件中的加密信息，并将解密后的信息注入到相应的配置类中。这样，你的应用程序就可以使用解密后的敏感信息（如数据库密码）来建立数据库连接等操作了。</p>
<h4>七、注意事项</h4>
<ol>
 <li><strong>安全存储加密密钥</strong>：加密密钥是解密敏感信息的关键，因此必须妥善保管。不要将加密密钥硬编码在代码中或存储在可公开访问的地方。</li>
 <li><strong>定期更换加密密钥</strong>：为了提高安全性，建议定期更换加密密钥，并重新加密所有敏感信息。</li>
 <li><strong>备份加密信息</strong>：在更换加密密钥或升级加密算法之前，请务必备份所有加密信息，以便在需要时能够恢复。</li>
 <li><strong>测试加密解密功能</strong>：在将加密后的密码部署到生产环境之前，请务必在开发或测试环境中测试加密解密功能，以确保一切正常工作。</li>
</ol>
<h4>八、总结</h4>
<p>Spring Boot密码加密是一个涉及多个步骤的过程，包括引入加密依赖、配置加密工具、生成加密密钥和加密密码、配置解密以及在配置文件中使用加密后的密码等。通过遵循这些步骤，你可以有效地保护Spring Boot应用程序中的敏感信息，提高应用程序的安全性。同时，也需要注意安全存储加密密钥、定期更换加密密钥、备份加密信息以及测试加密解密功能等安全措施。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141412882</p>]]></description><guid isPermaLink="false">/archives/3716cbcc-e803-4505-931b-41a56970219a</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:29:16 GMT</pubDate></item><item><title><![CDATA[在Ubuntu 22.04上安装微信]]></title><link>https://wiki.dgfygk.com/archives/22ff34f1-bacf-454c-86f5-4462d3133c52</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8Ubuntu%2022.04%E4%B8%8A%E5%AE%89%E8%A3%85%E5%BE%AE%E4%BF%A1&amp;url=/archives/22ff34f1-bacf-454c-86f5-4462d3133c52" width="1" height="1" alt="" style="opacity:0;">
<p>在Ubuntu 22.04上安装微信，有多种方法可供选择，每种方法都有其特点和适用场景。以下将详细介绍几种常见的安装方法，包括使用Wine环境、Docker镜像以及安装原生Linux版本（如果可用）。请注意，由于微信官方可能不断更新其Linux版本，以下信息可能随时间而变化，但我会尽量提供最新和准确的信息。</p>
<h4>一、使用Wine环境安装微信</h4>
<p>Wine是一个允许Linux和macOS系统上运行Windows应用程序的兼容层。通过Wine，你可以在Ubuntu上安装和运行微信Windows版。</p>
<h5>1. 安装Wine</h5>
<p>首先，你需要安装Wine。可以通过Ubuntu的软件包管理器apt来安装。打开终端，输入以下命令：</p>
<pre><code class="prism language-bash">sudo apt update
sudo apt install wine64
</code></pre>
<p>注意：这里使用的是<code>wine64</code>，因为大多数现代Windows应用程序都是64位的。如果你的系统或需求不同，可能需要安装<code>wine32</code>或<code>wine</code>（不带版本号，通常会自动安装最新版本）。</p>
<h5>2. 下载微信Windows版安装包</h5>
<p>从微信官网或其他可信来源下载微信Windows版的安装包（.exe文件）。</p>
<h5>3. 使用Wine安装微信</h5>
<p>下载完成后，在终端中导航到安装包所在的目录，并使用Wine来运行安装包：</p>
<pre><code class="prism language-bash">wine 微信Windows安装包.exe
</code></pre>
<p>按照屏幕上的指示完成安装过程。安装完成后，你可以在Ubuntu的应用菜单中找到微信，并像运行其他Linux应用程序一样运行它。</p>
<h4>二、使用Docker镜像安装微信</h4>
<p>Docker是一个开源的应用容器引擎，允许你打包、分发和运行任何应用程序，作为一个轻量级、可移植的容器。通过Docker，你可以轻松地在Ubuntu上运行微信。</p>
<h5>1. 安装Docker</h5>
<p>首先，你需要在Ubuntu上安装Docker。打开终端，输入以下命令来安装Docker：</p>
<pre><code class="prism language-bash">sudo apt update
sudo apt install docker.io
sudo systemctl start docker
sudo systemctl enable docker
</code></pre>
<h5>2. 查找并拉取微信Docker镜像</h5>
<p>在Docker Hub或其他Docker镜像仓库中查找微信的Docker镜像。找到后，使用<code>docker pull</code>命令拉取镜像到本地。</p>
<p>例如，如果镜像名为<code>bestwu/wechat</code>，则拉取命令为：</p>
<pre><code class="prism language-bash">docker pull bestwu/wechat
</code></pre>
<h5>3. 运行微信Docker容器</h5>
<p>拉取镜像后，你可以使用<code>docker run</code>命令来运行微信容器。根据你的需求，可能需要指定一些额外的参数，如端口映射、设备访问等。</p>
<pre><code class="prism language-bash">docker run -d --name wechat -p 端口号:端口号 --device /dev/snd bestwu/wechat
</code></pre>
<p>注意：上面的命令是一个示例，你需要根据实际情况替换<code>端口号</code>和镜像名。</p>
<h4>三、安装原生Linux版微信（如果可用）</h4>
<p>如果微信官方或第三方开发者提供了针对Linux的原生版本，那么安装过程将更加简单和直接。</p>
<h5>1. 查找并下载微信Linux版安装包</h5>
<p>首先，你需要在微信官网、GitHub或其他可信来源查找并下载微信Linux版的安装包（通常是.deb、.rpm或其他Linux软件包格式）。</p>
<h5>2. 安装微信Linux版</h5>
<p>下载完成后，使用Ubuntu的软件包管理器apt（对于.deb包）或相应的命令来安装微信。</p>
<p>例如，如果下载的是.deb包，则可以使用以下命令安装：</p>
<pre><code class="prism language-bash">sudo dpkg -i 微信Linux版安装包.deb
</code></pre>
<p>如果安装过程中提示缺少依赖项，可以使用<code>apt-get install -f</code>命令来自动安装缺失的依赖项。</p>
<h5>3. 运行微信</h5>
<p>安装完成后，你可以在Ubuntu的应用菜单中找到微信，并像运行其他Linux应用程序一样运行它。</p>
<h4>四、注意事项</h4>
<ul>
 <li>在安装过程中，请确保从可信来源下载微信安装包和Wine、Docker等软件，以避免安全风险。</li>
 <li>如果遇到安装或运行问题，请检查Ubuntu的版本、Wine或Docker的版本是否兼容，并查看相关的错误日志或社区论坛以获取帮助。</li>
 <li>考虑到微信官方可能不断更新其Linux版本，建议定期检查并更新你的微信安装。</li>
</ul>
<p>以上就是在Ubuntu 22.04上安装微信的几种常见方法。希望这些信息对你有所帮助！</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141451521</p>]]></description><guid isPermaLink="false">/archives/22ff34f1-bacf-454c-86f5-4462d3133c52</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:28:51 GMT</pubDate></item><item><title><![CDATA[Redis的线程模型]]></title><link>https://wiki.dgfygk.com/archives/205aaed1-b515-440f-8ff3-c693953f6cba</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B&amp;url=/archives/205aaed1-b515-440f-8ff3-c693953f6cba" width="1" height="1" alt="" style="opacity:0;">
<p>Redis作为一种基于内存的高性能键值对数据库，其线程模型和IO模型是实现高性能的关键因素。以下将详细探讨Redis的线程与IO模型，内容不少于2000字。</p>
<h4>一、Redis的线程模型</h4>
<p>Redis的线程模型是理解其高性能的重要基础。在Redis的发展过程中，其线程模型经历了从单一线程到多线程的演变，但即使在多线程版本中，Redis的核心操作（如命令执行）仍然保持单线程处理。</p>
<h5>1. 单线程模型</h5>
<p>在Redis的早期版本中，其所有的操作，包括获取数据、解析请求、执行命令以及返回结果等，都是由一个主线程顺序完成的。这种单线程模型的设计，使得Redis能够避免多线程带来的上下文切换开销和锁竞争问题，从而保持高效的数据处理能力。</p>
<ul>
 <li>
  <p><strong>优点</strong>：</p>
 </li>
 <li>
  <p><strong>简单性</strong>：单线程模型使得Redis的内部实现相对简单，降低了系统复杂度。</p>
 </li>
 <li>
  <p><strong>无锁竞争</strong>：避免了多线程下的锁竞争和死锁问题，提高了系统的稳定性和可预测性。</p>
 </li>
 <li>
  <p><strong>高效性</strong>：减少了上下文切换的开销，使得CPU能够更专注于执行实际的数据处理任务。</p>
 </li>
 <li>
  <p><strong>缺点</strong>：</p>
 </li>
 <li>
  <p><strong>CPU利用率不高</strong>：在单线程模型中，单个CPU核心只能被一个Redis实例利用，无法充分利用多核CPU的性能。</p>
 </li>
 <li>
  <p><strong>I/O性能瓶颈</strong>：尽管Redis的数据处理速度快，但网络I/O和磁盘I/O可能成为性能瓶颈。</p>
 </li>
</ul>
<h5>2. 多线程模型（Redis 6.0及以后）</h5>
<p>从Redis 6.0版本开始，Redis引入了多线程模型，但这一模型并不是全面替代单线程模型，而是对单线程模型的一种补充和优化。在Redis 6.0中，多线程主要用于处理网络I/O和磁盘I/O等较为耗时的操作，而命令的执行仍然由主线程完成。</p>
<ul>
 <li>
  <p><strong>优点</strong>：</p>
 </li>
 <li>
  <p><strong>提高I/O性能</strong>：多线程可以并行处理多个网络连接和磁盘I/O请求，减少了单个线程的等待时间，提高了整体性能。</p>
 </li>
 <li>
  <p><strong>充分利用多核CPU</strong>：通过多线程的方式，Redis能够充分利用多核CPU的性能，提高CPU的利用率。</p>
 </li>
 <li>
  <p><strong>缺点</strong>：</p>
 </li>
 <li>
  <p><strong>实现复杂度增加</strong>：多线程模型增加了系统的实现复杂度，需要处理线程间的同步和通信问题。</p>
 </li>
 <li>
  <p><strong>可能引入新的问题</strong>：如线程竞争、死锁等并发问题，需要仔细设计和测试以确保系统的稳定性和性能。</p>
 </li>
</ul>
<h4>二、Redis的IO模型</h4>
<p>Redis的IO模型是其高性能的另一个重要支撑。Redis采用了基于事件的IO多路复用机制，能够高效地处理大量的并发连接和请求。</p>
<h5>1. IO多路复用</h5>
<p>IO多路复用是一种可以同时监听多个IO事件的技术，当某个IO事件发生时，系统能够通知相应的程序进行处理。Redis采用了IO多路复用机制，如epoll（在Linux环境下）或kqueue（在BSD环境下）等，来同时监听多个Socket连接上的读写事件。</p>
<ul>
 <li>
  <p><strong>工作流程</strong>：</p>
 </li>
 <li>
  <p>Redis启动后，会创建一个或多个Socket来监听客户端的连接请求。</p>
 </li>
 <li>
  <p>使用IO多路复用机制监听这些Socket上的读写事件。</p>
 </li>
 <li>
  <p>当某个Socket上有数据可读或可写时，IO多路复用机制会通知Redis的主线程。</p>
 </li>
 <li>
  <p>主线程根据事件类型调用相应的处理器来处理数据读写等操作。</p>
 </li>
 <li>
  <p><strong>优点</strong>：</p>
 </li>
 <li>
  <p><strong>高效性</strong>：能够同时处理多个Socket连接上的IO事件，减少了线程的等待时间。</p>
 </li>
 <li>
  <p><strong>灵活性</strong>：可以根据系统负载和可用资源动态调整监听的Socket数量。</p>
 </li>
</ul>
<h5>2. 文件事件处理器</h5>
<p>Redis基于Reactor模式开发了自己的网络事件处理器，这个处理器被称为文件事件处理器（File Event Handler）。文件事件处理器是单线程的，但它能够高效地处理多个客户端的并发请求。</p>
<ul>
 <li>
  <p><strong>结构</strong>：</p>
 </li>
 <li>
  <p><strong>多个Socket</strong>：Redis与多个客户端建立连接，每个连接对应一个Socket。</p>
 </li>
 <li>
  <p><strong>IO多路复用程序</strong>：负责监听这些Socket上的读写事件。</p>
 </li>
 <li>
  <p><strong>文件事件分派器</strong>：从IO多路复用程序的事件队列中取出事件，并交给相应的事件处理器处理。</p>
 </li>
 <li>
  <p><strong>事件处理器</strong>：包括命令请求处理器、命令回复处理器和连接应答处理器等，负责处理具体的网络事件。</p>
 </li>
 <li>
  <p><strong>工作流程</strong>：</p>
 </li>
 <li>
  <p>客户端发起连接请求，Redis的监听Socket产生一个连接事件。</p>
 </li>
 <li>
  <p>IO多路复用程序监听到这个事件，并将其放入事件队列中。</p>
 </li>
 <li>
  <p>文件事件分派器从队列中取出事件，并交给连接应答处理器处理。</p>
 </li>
 <li>
  <p>连接应答处理器接受连接请求，并与客户端建立连接。</p>
 </li>
 <li>
  <p>客户端发送命令请求，对应的Socket产生可读事件。</p>
 </li>
 <li>
  <p><strong>IO多路复用程序</strong>再次监听到可读事件，并将其放入事件队列。</p>
 </li>
 <li>
  <p><strong>文件事件分派器</strong>取出可读事件，并交给命令请求处理器处理。</p>
 </li>
 <li>
  <p><strong>命令请求处理器</strong>读取客户端发送的命令，解析命令内容，并将解析后的命令参数传递给Redis的核心处理模块（仍然是单线程执行）。</p>
 </li>
 <li>
  <p><strong>Redis核心处理模块</strong>执行命令，并生成命令的响应结果。</p>
 </li>
 <li>
  <p><strong>命令回复处理器</strong>将响应结果写回到对应的Socket，准备发送给客户端。</p>
 </li>
 <li>
  <p>如果响应结果已经准备好并且Socket可写，<strong>IO多路复用程序</strong>会立即触发一个可写事件。</p>
 </li>
 <li>
  <p><strong>文件事件分派器</strong>取出可写事件，并交给命令回复处理器处理。</p>
 </li>
 <li>
  <p><strong>命令回复处理器</strong>将响应结果发送给客户端。</p>
 </li>
</ul>
<h5>3. 非阻塞IO与事件驱动</h5>
<p>Redis的IO模型还体现了非阻塞IO和事件驱动的特点。在Redis中，主线程不会直接进行IO操作（如读写Socket），而是将IO操作交给IO多路复用程序处理。这样，主线程可以在等待IO操作完成的同时，继续执行其他任务（如处理其他客户端的请求），从而实现了非阻塞IO。</p>
<p>同时，Redis的事件处理机制也是基于事件驱动的。当IO多路复用程序监听到某个Socket上有事件发生时（如可读、可写、连接关闭等），它会将事件放入事件队列中。文件事件分派器会不断从队列中取出事件，并根据事件类型调用相应的事件处理器进行处理。这种事件驱动的方式使得Redis能够高效地处理大量的并发请求。</p>
<h5>4. 持久化与IO性能</h5>
<p>虽然Redis主要将数据存储在内存中，但它也提供了持久化机制（如RDB和AOF）来确保数据的可靠性。在持久化过程中，Redis需要将内存中的数据写入到磁盘上。这个过程可能会涉及到大量的磁盘IO操作，从而影响Redis的整体性能。</p>
<p>为了减少对主线程的影响，Redis在持久化过程中采用了异步写入的策略。具体来说，当需要执行持久化操作时，Redis会创建一个子线程或进程来负责将数据写入磁盘。这样，主线程就可以继续处理客户端的请求，而不需要等待磁盘IO操作的完成。</p>
<p>在Redis 6.0及以后的版本中，为了进一步提高IO性能，Redis引入了多线程来处理磁盘I/O操作（如AOF的写入和RDB的生成）。这些线程专门负责将数据从内存缓冲区写入到磁盘上，从而减轻了主线程的负担，并提高了持久化的效率。</p>
<h4>三、总结</h4>
<p>Redis的线程模型和IO模型是其高性能的关键所在。通过采用单线程模型（核心操作）结合多线程模型（I/O操作）的方式，Redis既避免了多线程带来的上下文切换开销和锁竞争问题，又能够充分利用多核CPU的性能和IO多路复用机制来提高IO性能。同时，Redis还通过非阻塞IO和事件驱动的方式来处理大量的并发请求，确保了系统的高可用性和可扩展性。这些设计使得Redis成为了一个非常受欢迎的高性能键值对数据库。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141381708</p>]]></description><guid isPermaLink="false">/archives/205aaed1-b515-440f-8ff3-c693953f6cba</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 07:28:36 GMT</pubDate></item><item><title><![CDATA[机器学习在旅游业的革新之旅]]></title><link>https://wiki.dgfygk.com/archives/657348e8-b62d-4718-bf57-37835579844e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9C%A8%E6%97%85%E6%B8%B8%E4%B8%9A%E7%9A%84%E9%9D%A9%E6%96%B0%E4%B9%8B%E6%97%85&amp;url=/archives/657348e8-b62d-4718-bf57-37835579844e" width="1" height="1" alt="" style="opacity:0;">
<h4>机器学习在旅游业的革新之旅</h4>
<p>随着科技的飞速发展，尤其是人工智能（AI）技术的广泛应用，各个行业都迎来了前所未有的变革。其中，旅游业作为全球经济的重要支柱之一，更是受益匪浅。机器学习（Machine Learning, ML）作为AI的核心技术之一，正在逐步重塑旅游业的各个方面，从需求分析、行程规划、服务体验到营销策略，无一不展现出其巨大的潜力和价值。本文将深入探讨机器学习在旅游业的革新之旅，揭示其如何推动旅游业迈向智能化、个性化的新时代。</p>
<h5>一、机器学习在旅游需求分析中的应用</h5>
<p>在旅游业中，精准把握游客需求是提升竞争力和服务质量的关键。传统的市场调研方法往往依赖于问卷调查、访谈等手段，不仅成本高、耗时长，而且难以全面覆盖所有潜在用户。而机器学习技术的出现，为旅游需求分析提供了全新的解决方案。</p>
<p>通过对用户在旅游预订平台、社交媒体、搜索引擎等渠道的行为数据进行收集和分析，机器学习算法能够揭示出用户的潜在需求和偏好。这些数据包括但不限于用户的搜索记录、点击行为、浏览时长、购买历史等。通过深度学习等复杂算法，机器学习能够从中提取出有价值的模式和特征，为后续的旅游推荐和服务优化提供数据支持。</p>
<p>基于用户行为数据，机器学习可以构建旅客偏好预测模型。这些模型通过训练和优化，能够预测用户在未来一段时间内的旅游偏好和需求。例如，根据用户的历史旅游记录和当前的行为数据，模型可以预测用户可能对哪些类型的旅游产品感兴趣，如海滨度假、文化旅游、探险旅行等。此外，模型还可以预测用户在价格、服务质量、行程安排等方面的具体需求，为旅游企业提供定制化的服务方案。</p>
<h5>二、机器学习在智能行程规划中的应用</h5>
<p>智能行程规划是机器学习在旅游业中的另一重要应用。传统的行程规划往往依赖于人工查询和规划，不仅耗时费力，而且难以做到个性化。而基于机器学习的智能行程规划系统，能够根据用户的偏好和需求，自动生成最优化的行程安排。</p>
<p>智能行程规划系统通常会整合实时交通、天气、景点信息等多源数据，利用机器学习算法进行智能分析和推荐。例如，系统可以根据用户的旅行时间和目的地，推荐最合适的交通方式和路线；根据天气情况，提醒用户携带相应的衣物和装备；根据景点的热门程度和游客密度，提供避峰游览的建议。这种个性化的行程规划方式，不仅能够提升用户的旅行体验，还能够有效避免不必要的麻烦和浪费。</p>
<h5>三、机器学习在旅游服务与体验提升中的应用</h5>
<p>旅游服务与体验是旅游业的核心竞争力之一。随着旅游市场的日益竞争，提供高质量的服务和体验成为了旅游企业的重要任务。机器学习技术在这方面也展现出了巨大的潜力。</p>
<p>一方面，机器学习可以应用于客户服务自动化与智能客服。通过自然语言处理（NLP）技术，智能客服能够理解用户的查询意图，并给出准确的回答和建议。这种智能客服不仅能够提升服务效率，还能够降低人力成本。例如，Expedia推出的Facebook Messenger机器人，就能够帮助旅行者选择合适的酒店并进行预订。</p>
<p>另一方面，机器学习还可以应用于情感分析与用户反馈处理。通过情感分析技术，旅游企业可以快速有效地分析用户的评论和反馈，了解用户对产品和服务的看法。这种分析不仅能够帮助企业及时发现和解决问题，还能够为产品的改进和优化提供数据支持。例如，豪华酒店运营商Dorchester Collection就利用人工智能平台对来自不同地区28家不同酒店的7454条评论进行情感分析，以进行品牌研究。</p>
<h5>四、机器学习在旅游营销与策略制定中的应用</h5>
<p>旅游营销与策略制定是旅游企业获取市场份额和提升品牌影响力的重要手段。机器学习技术在这方面也发挥着重要作用。</p>
<p>一方面，机器学习可以应用于精准营销策略制定。通过对用户数据的深度挖掘和分析，机器学习能够揭示出用户的消费习惯和偏好，从而为企业制定个性化的营销策略提供依据。例如，根据用户的购买历史和浏览行为，企业可以向其推送符合其兴趣的旅游产品和优惠信息。</p>
<p>另一方面，机器学习还可以应用于社交媒体分析与用户互动。社交媒体已成为旅游营销的重要渠道之一。通过机器学习技术，企业可以实时监测和分析社交媒体上的用户讨论和反馈，了解用户对产品和服务的态度和需求。这种分析不仅能够帮助企业及时调整营销策略，还能够提升用户参与度和忠诚度。</p>
<h5>五、机器学习在旅游安全与风险管理中的应用</h5>
<p>旅游安全与风险管理是旅游业不可忽视的重要环节。机器学习技术在这方面也展现出了其独特的优势。</p>
<p>一方面，机器学习可以应用于游客行为监控与预警系统。通过对游客行为的实时监测和分析，系统能够及时发现异常情况并发出预警。例如，在景区内安装监控摄像头，并利用机器学习算法对游客的行为进行识别和分析，可以及时发现拥挤、推搡等潜在的安全隐患。</p>
<p>另一方面，机器学习还可以应用于旅游目的地风险评估。通过对历史数据和实时数据的分析，系统能够评估旅游目的地的安全风险等级，并为游客提供安全提示和建议。例如，在自然灾害频发的地区，系统可以实时监测天气和地质情况，并根据预测结果调整旅游路线和行程安排。</p>
<h5>六、总结与展望</h5>
<p>随着技术的不断进步和应用的不断深化，机器学习在旅游业中的应用前景越来越广阔。它不仅提升了旅游服务的智能化水平，还极大地增强了旅客的满意度和忠诚度。然而，我们也应认识到，机器学习在旅游业的广泛应用仍面临一些挑战和限制，如数据隐私保护、算法透明度、技术普及率等问题。因此，在未来的发展中，需要平衡技术创新与伦理规范，确保技术的健康发展。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141382048</p>]]></description><guid isPermaLink="false">/archives/657348e8-b62d-4718-bf57-37835579844e</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 07:28:18 GMT</pubDate></item><item><title><![CDATA[AI编程工具合集]]></title><link>https://wiki.dgfygk.com/archives/7f685904-3f0e-4301-8962-ae26a85f78af</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AI%E7%BC%96%E7%A8%8B%E5%B7%A5%E5%85%B7%E5%90%88%E9%9B%86&amp;url=/archives/7f685904-3f0e-4301-8962-ae26a85f78af" width="1" height="1" alt="" style="opacity:0;">
<p><strong>AI编程工具合集</strong></p>
<p>在人工智能（AI）技术日新月异的今天，AI编程工具成为了开发者们提升效率、优化代码质量的重要利器。这些工具通过集成先进的人工智能技术，如自然语言处理（NLP）、机器学习（ML）和深度学习（DL），为开发者们提供了代码补全、错误检测、代码优化、智能问答等一系列强大功能。以下是一系列备受推崇的AI编程工具合集，它们将在不同方面助力开发者们更高效地完成编程任务。</p>
<h4>1. TensorFlow</h4>
<p><strong>简介</strong>：TensorFlow是由Google开发的一个开源的机器学习和深度学习框架。它提供了一个灵活且可扩展的平台，用于构建和训练各种机器学习模型。TensorFlow支持多种编程语言，包括Python、C++、Java等，并提供了丰富的API接口，使得开发者能够轻松地在不同平台上部署和扩展模型。</p>
<p><strong>特点</strong>：</p>
<ul>
 <li><strong>灵活性</strong>：支持多种编程语言和平台，便于开发者根据自身需求选择最适合的工具。</li>
 <li><strong>可扩展性</strong>：通过分布式计算支持大规模模型训练，提升训练效率和准确性。</li>
 <li><strong>可视化工具</strong>：提供TensorBoard等可视化工具，帮助开发者理解和调试模型。</li>
</ul>
<h4>2. PyTorch</h4>
<p><strong>简介</strong>：PyTorch是另一个流行的AI编程软件，由Facebook开发。与TensorFlow类似，PyTorch也提供了灵活的张量计算和动态图机制，使得构建和训练深度学习模型更加容易。PyTorch在主要云平台上得到了良好的支持，提供无摩擦的开发和轻松的扩展。</p>
<p><strong>特点</strong>：</p>
<ul>
 <li><strong>动态图机制</strong>：与TensorFlow的静态图不同，PyTorch使用动态图，使得模型调试和修改更加直观和方便。</li>
 <li><strong>易用性</strong>：简洁的API设计和直观的编程风格，使得PyTorch深受开发者喜爱。</li>
 <li><strong>社区支持</strong>：拥有庞大的社区支持，提供了丰富的教程和文档资源。</li>
</ul>
<h4>3. GitHub Copilot</h4>
<p><strong>简介</strong>：GitHub Copilot是由微软和OpenAI共同开发的一款基于GPT-3模型的AI编程软件。它可以在VS Code等集成开发环境（IDE）中提供代码自动补全、生成和翻译等功能，支持多种编程语言和框架。</p>
<p><strong>特点</strong>：</p>
<ul>
 <li><strong>智能代码补全</strong>：根据上下文自动补全代码，提高编写效率。</li>
 <li><strong>代码生成</strong>：能够生成完整的函数、类和模块，减少重复劳动。</li>
 <li><strong>代码解释</strong>：对复杂的代码片段进行解释，帮助开发者理解代码逻辑。</li>
</ul>
<h4>4. Tabnine</h4>
<p><strong>简介</strong>：Tabnine是由Codota公司开发的一款基于深度学习的代码自动补全软件。它可以在多种IDE中提供智能、快速、准确的代码建议，支持多种编程语言和框架。</p>
<p><strong>特点</strong>：</p>
<ul>
 <li><strong>深度学习能力</strong>：利用深度学习算法分析大量代码库，提供精准的代码补全建议。</li>
 <li><strong>多语言支持</strong>：支持多种主流编程语言，满足开发者在不同项目中的需求。</li>
 <li><strong>个性化定制</strong>：支持根据开发者的编程习惯和项目需求进行个性化定制。</li>
</ul>
<h4>5. CodeGeeX</h4>
<p><strong>简介</strong>：CodeGeeX是一款基于智谱AI大模型GLM的智能编程助手。它提供代码生成/完成、注释生成、代码翻译和基于人工智能的聊天等功能，帮助开发人员显著提高工作效率。</p>
<p><strong>特点</strong>：</p>
<ul>
 <li><strong>强大代码生成能力</strong>：能够生成高质量的代码片段和注释，减少开发者的编写负担。</li>
 <li><strong>多语言支持</strong>：支持多种编程语言，满足跨语言开发的需求。</li>
 <li><strong>免费使用</strong>：CodeGeeX插件对个人用户完全免费，降低了使用门槛。</li>
</ul>
<h4>6. TONGYI Lingma</h4>
<p><strong>简介</strong>：通义灵码（TONGYI Lingma）是一款基于阿里通义大模型的智能编码辅助工具。它提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码注释生成等一系列功能，为开发者带来高效、流畅的编码体验。</p>
<p><strong>特点</strong>：</p>
<ul>
 <li><strong>实时续写</strong>：支持行级和函数级代码实时续写，提高编码速度。</li>
 <li><strong>自然语言生成代码</strong>：通过自然语言描述生成代码片段，降低编码难度。</li>
 <li><strong>阿里云SDK/API支持</strong>：针对阿里云SDK/API的使用场景进行优化，提升开发效率。</li>
</ul>
<h4>7. Raccoon</h4>
<p><strong>简介</strong>：Raccoon是商汤科技发布的基于商汤自研大语言模型的智能编程助手——代码小浣熊。它提供编辑器内代码补全、侧边栏代码助手以及代码生成、测试代码生成、代码翻译等功能。</p>
<p><strong>特点</strong>：</p>
<ul>
 <li><strong>高效代码补全</strong>：提供智能的代码补全功能，减少编码时间。</li>
 <li><strong>代码翻译</strong>：支持多种编程语言之间的代码翻译，方便跨语言开发。</li>
 <li><strong>代码修正</strong>：自动检测和修正代码中的错误和潜在问题。</li>
</ul>
<h3>“立刻使用"这个短语通常意味着要立即采取行动或开始使用某个工具、资源或技术。然而，在涉及到AI编程工具时，虽然"立刻使用"的意愿很强，但实际操作可能还需要一些准备工作和步骤。以下是一些建议，帮助你"立刻"开始使用AI编程工具：</h3>
<h4>1. 选择合适的工具</h4>
<p>首先，根据你的具体需求和项目类型，从上述介绍的AI编程工具中选择一个或几个合适的工具。考虑工具的功能、易用性、社区支持以及是否与你现有的开发环境兼容。</p>
<h4>2. 安装和配置</h4>
<ul>
 <li><strong>下载并安装</strong>：访问工具的官方网站或软件商店，下载最新版本的安装包，并按照安装向导进行安装。</li>
 <li><strong>环境配置</strong>：根据工具的文档或教程，配置好开发环境，包括必要的依赖库、插件和权限设置。</li>
</ul>
<h4>3. 学习基础</h4>
<ul>
 <li><strong>阅读文档</strong>：仔细阅读工具的官方文档，了解其基本功能、使用方法和最佳实践。</li>
 <li><strong>观看教程</strong>：寻找并观看相关的视频教程或在线课程，以更快地掌握工具的使用技巧。</li>
 <li><strong>实践练习</strong>：通过完成一些简单的练习项目或任务，加深对工具的理解和掌握。</li>
</ul>
<h4>4. 开始项目</h4>
<ul>
 <li><strong>规划项目</strong>：明确你的项目目标、需求和预期成果，制定详细的项目计划。</li>
 <li><strong>创建项目</strong>：在工具中创建新的项目或工作区，并设置必要的项目结构和配置。</li>
 <li><strong>编写代码</strong>：使用AI编程工具提供的智能功能，如代码补全、错误检测等，编写并调试你的代码。</li>
 <li><strong>运行和测试</strong>：在开发过程中不断运行和测试你的代码，确保它按预期工作。</li>
</ul>
<h4>5. 持续优化</h4>
<ul>
 <li><strong>性能优化</strong>：分析代码的性能瓶颈，并进行优化以提高运行效率。</li>
 <li><strong>代码重构</strong>：定期重构代码以提高可读性和可维护性。</li>
 <li><strong>学习反馈</strong>：关注用户反馈和社区讨论，不断改进和优化你的项目。</li>
</ul>
<h4>6. 社区支持</h4>
<ul>
 <li><strong>加入社区</strong>：加入工具的官方社区或相关论坛，与其他开发者交流经验和解决问题。</li>
 <li><strong>寻求帮助</strong>：当你遇到问题时，不要犹豫向社区寻求帮助或查阅官方文档。</li>
</ul>
<h4>注意事项</h4>
<ul>
 <li><strong>安全性</strong>：确保你的代码和数据在使用AI编程工具时保持安全。</li>
 <li><strong>兼容性</strong>：注意工具与你当前使用的其他软件或框架的兼容性。</li>
 <li><strong>备份</strong>：定期备份你的代码和数据以防丢失。</li>
</ul>
<p>通过遵循以上步骤和建议，你可以"立刻"开始使用AI编程工具，并逐步提高你的开发效率和项目质量。记住，持续学习和实践是掌握任何新工具的关键。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141327724</p>]]></description><guid isPermaLink="false">/archives/7f685904-3f0e-4301-8962-ae26a85f78af</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 07:27:48 GMT</pubDate></item><item><title><![CDATA[Linux网络中的NAT（Network Address Translation，网络地址转换）]]></title><link>https://wiki.dgfygk.com/archives/01b3dc34-5f41-43a8-9bb6-1f7512db072a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E7%BD%91%E7%BB%9C%E4%B8%AD%E7%9A%84NAT%EF%BC%88Network%20Address%20Translation%EF%BC%8C%E7%BD%91%E7%BB%9C%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2%EF%BC%89&amp;url=/archives/01b3dc34-5f41-43a8-9bb6-1f7512db072a" width="1" height="1" alt="" style="opacity:0;">
<p>Linux网络中的NAT（Network Address Translation，网络地址转换）技术是一种在本地网络中使用私有地址，在连接互联网时转而使用全局IP地址的关键技术。NAT技术主要用于解决IPv4地址短缺的问题，同时也带来了其他网络管理和安全方面的优势。以下是对Linux网络NAT技术的详细阐述。</p>
<h4>一、NAT的定义与工作机制</h4>
<p>NAT技术允许一个网络内的多台设备共享一个或少数几个公网IP地址进行互联网访问。这通过在网络边界设置NAT设备（如路由器或防火墙）来实现，该设备负责在内部网络（私网）和外部网络（公网）之间转换IP地址。</p>
<p>NAT的工作机制可以概括为以下几个步骤：</p>
<ol>
 <li>
  <p><strong>源地址转换（Source NAT, SNAT）</strong>：当内部网络中的设备（如私有IP地址为10.0.0.10的主机）向外部网络发送数据包时，NAT设备会将数据包的源IP地址从私有地址转换为公网IP地址（如202.244.174.37），然后发送出去。</p>
 </li>
 <li>
  <p><strong>目的地址转换（Destination NAT, DNAT）</strong>：当外部网络的数据包到达NAT设备时，设备会根据转换规则（如通过端口号区分不同的内部会话）将数据包的目的IP地址从公网IP地址转换回相应的私有IP地址，然后将数据包转发给内部网络中的目标设备。</p>
 </li>
</ol>
<h4>二、NAT的类型</h4>
<p>NAT技术主要有以下几种类型：</p>
<ol>
 <li>
  <p><strong>静态NAT</strong>：内部本地地址一对一转换成内部全局地址，即内部网络的每一台设备都绑定了一个固定的公网IP地址。这种类型常用于需要对外提供服务的服务器。</p>
 </li>
 <li>
  <p><strong>动态NAT</strong>：内部本地地址在需要时从地址池中动态选择一个空闲的公网IP地址进行转换，当数据传输完成后，该地址会被放回地址池中供其他设备使用。这种方式比静态NAT更灵活，但管理起来可能更复杂。</p>
 </li>
 <li>
  <p><strong>端口复用NAPT（Network Address and Port Translation）</strong>：也称为PAT（Port Address Translation）或IP伪装。它允许多个内部设备共享同一个公网IP地址，并通过不同的端口号来区分不同的会话。这种方式极大地节省了公网IP地址资源，是NAT技术中最常用的模式。</p>
 </li>
</ol>
<h4>三、NAT技术的优缺点</h4>
<h5>优点</h5>
<ol>
 <li>
  <p><strong>节省合法的公有IP地址</strong>：通过NAT技术，内部网络中的多台设备可以共享一个或少数几个公网IP地址，从而大大节省了公网IP地址资源。</p>
 </li>
 <li>
  <p><strong>增加网络安全性</strong>：NAT技术可以隐藏内部网络的IP地址，使得外部网络无法直接访问内部网络中的设备，从而提高了网络的安全性。</p>
 </li>
 <li>
  <p><strong>避免重新编址</strong>：当内部网络结构发生变化时，如增加或减少设备，只需在NAT设备上进行相应的配置调整，而无需对内部网络中的设备进行重新编址。</p>
 </li>
</ol>
<h5>缺点</h5>
<ol>
 <li>
  <p><strong>NAT穿越问题</strong>：由于NAT会改变数据包的IP地址和端口号，这可能导致一些需要直接通信的应用（如P2P应用）无法正常工作。为了解决这个问题，需要使用NAT穿越技术（如STUN、TURN等）。</p>
 </li>
 <li>
  <p><strong>TCP连接重置问题</strong>：如果NAT设备在通信过程中重启或出现故障，可能会导致所有的TCP连接被重置。这可能会影响到正在进行的网络应用和服务。</p>
 </li>
</ol>
<h4>四、Linux中的NAT配置</h4>
<p>在Linux系统中，NAT配置通常通过iptables命令和netfilter框架来实现。iptables是Linux内核中用于设置、维护和检查IPv4数据包过滤规则的工具。通过配置iptables，可以实现NAT的各种功能。</p>
<h5>NAT配置步骤</h5>
<ol>
 <li>
  <p><strong>启用IP转发</strong> ：首先需要在Linux系统中启用IP转发功能，允许系统转发数据包。这可以通过修改系统配置文件（如<code>/etc/sysctl.conf</code>）或使用<code>sysctl</code>命令来实现。</p>
 </li>
 <li>
  <p><strong>配置NAT规则</strong>：使用iptables命令配置NAT规则。例如，可以配置SNAT规则来修改数据包的源IP地址，配置DNAT规则来修改数据包的目的IP地址。</p>
 </li>
 <li>
  <p><strong>保存和应用规则</strong> ：配置完NAT规则后，需要将其保存到文件中（如<code>/etc/iptables/rules.v4</code>），以便在系统重启后自动加载。然后，使用iptables-restore命令将规则应用到系统中。</p>
 </li>
 <li>
  <p><strong>验证和调试</strong>：配置完成后，可以使用iptables命令查看当前的NAT规则列表，并使用网络分析工具（如Wireshark）监视网络流量，以确保NAT配置正确无误。</p>
 </li>
</ol>
<h4>五、总结</h4>
<p>Linux网络中的NAT技术是一种重要的网络地址转换技术，它允许内部网络中的多台设备共享公网IP地址进行互联网访问。通过NAT技术，可以节省公网IP地址资源、提高网络安全性并避免重新编址。然而，NAT技术也存在一些缺点，如NAT穿越问题和TCP连接重置问题等。在Linux系统中</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141356088</p>]]></description><guid isPermaLink="false">/archives/01b3dc34-5f41-43a8-9bb6-1f7512db072a</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:27:32 GMT</pubDate></item><item><title><![CDATA[LinuX下ETCD安装、配置、命令]]></title><link>https://wiki.dgfygk.com/archives/cfd42fd5-be5f-4568-bda4-5a7c3caec31c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=LinuX%E4%B8%8BETCD%E5%AE%89%E8%A3%85%E3%80%81%E9%85%8D%E7%BD%AE%E3%80%81%E5%91%BD%E4%BB%A4&amp;url=/archives/cfd42fd5-be5f-4568-bda4-5a7c3caec31c" width="1" height="1" alt="" style="opacity:0;">
<p>在Linux系统下安装、配置和使用ETCD涉及多个步骤，包括环境准备、安装包下载、配置文件设置、服务启动以及常用命令的使用。以下是对这些步骤的详细阐述，旨在帮助用户全面了解并成功部署ETCD。</p>
<h4>一、ETCD简介</h4>
<p>ETCD是一个高可用的键值存储系统，由CoreOS开发，采用Raft算法来保证数据的强一致性。它广泛应用于服务发现、配置共享、分布式锁以及任何需要一致性和高可用性的场景中。ETCD以其简单、可靠和强大的特性成为分布式系统的核心组件之一。</p>
<h4>二、安装ETCD</h4>
<h5>2.1 环境准备</h5>
<p>在安装ETCD之前，需要确保Linux系统已安装必要的工具，如curl、tar、wget等，用于下载和解压ETCD的安装包。可以使用如下命令安装这些工具（以Ubuntu为例）：</p>
<pre><code class="prism language-bash">sudo apt-get update
sudo apt-get install -y curl tar wget
</code></pre>
<h5>2.2 下载ETCD</h5>
<p>ETCD的安装包可以从其GitHub仓库下载，或者在一些Linux发行版的软件仓库中直接获取。以下是从GitHub下载最新版本的ETCD的示例命令：</p>
<pre><code class="prism language-bash">wget https://github.com/etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz
# 或者
curl -L https://github.com/etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz -o etcd-v3.5.0-linux-amd64.tar.gz
</code></pre>
<h5>2.3 解压和移动文件</h5>
<p>下载完成后，解压ETCD安装包，并将解压后的可执行文件移动到系统的<code>/usr/local/bin</code>目录下，以便全局访问：</p>
<pre><code class="prism language-bash">tar xzvf etcd-v3.5.0-linux-amd64.tar.gz
sudo mv etcd-v3.5.0-linux-amd64/etcd* /usr/local/bin/
</code></pre>
<h5>2.4 验证安装</h5>
<p>通过检查ETCD的版本信息来验证安装是否成功：</p>
<pre><code class="prism language-bash">etcd --version
etcdctl version
</code></pre>
<p>如果输出了版本信息，则说明ETCD已正确安装。</p>
<h4>三、配置ETCD</h4>
<p>ETCD的配置可以通过命令行参数或配置文件来完成。以下是一个基本的配置文件示例，用于启动一个单节点ETCD实例：</p>
<pre><code class="prism language-bash">sudo nano /etc/etcd/etcd.conf
</code></pre>
<p>配置文件内容示例：</p>
<pre><code class="prism language-ini"># ETCD 数据存储目录
data-dir="/var/lib/etcd"
# 节点名称
name="etcd-node-1"
# 启动时绑定的 IP 和端口
listen-peer-urls="http://0.0.0.0:2380"
listen-client-urls="http://0.0.0.0:2379"
# 集群配置
initial-advertise-peer-urls="http://127.0.0.1:2380"
advertise-client-urls="http://127.0.0.1:2379"
# 集群状态
initial-cluster-state="new"
initial-cluster-token="etcd-cluster-1"
# 集群节点（单节点启动时只包含自己）
initial-cluster="etcd-node-1=http://127.0.0.1:2380"
</code></pre>
<h4>四、启动ETCD</h4>
<h5>4.1 使用配置文件启动</h5>
<p>通过指定的配置文件启动ETCD服务：</p>
<pre><code class="prism language-bash">etcd --config-file=/etc/etcd/etcd.conf
</code></pre>
<h5>4.2 创建Systemd服务</h5>
<p>为了方便管理ETCD服务，可以将其设置为Systemd服务。首先，创建一个Systemd服务文件：</p>
<pre><code class="prism language-bash">sudo nano /etc/systemd/system/etcd.service
</code></pre>
<p>服务文件内容示例：</p>
<pre><code class="prism language-ini">[Unit]
Description=etcd key-value store
Documentation=https://github.com/coreos/etcd
After=network.target

[Service]
Type=notify
ExecStart=/usr/local/bin/etcd --config-file=/etc/etcd/etcd.conf
Restart=always
RestartSec=5s
LimitNOFILE=40000

[Install]
WantedBy=multi-user.target
</code></pre>
<p>然后，启用并启动ETCD服务：</p>
<pre><code class="prism language-bash">sudo systemctl daemon-reload
sudo systemctl enable
etcd.service
sudo systemctl start etcd

#### 4.3 检查ETCD服务状态

启动ETCD服务后，可以使用以下命令检查服务的状态：
</code></pre>
<p>sudo systemctl status etcd</p>
<pre><code>如果服务正在运行，输出将显示服务为`active (running)`状态，并可能包含其他有用的信息，如进程ID和日志输出。

#### 五、ETCD命令使用

ETCD提供了丰富的命令行工具`etcdctl`，用于与ETCD集群进行交互。以下是一些常用的`etcdctl`命令示例。

##### 5.1 设置键值对

使用`etcdctl put`命令设置键值对：
</code></pre>
<p>etcdctl put mykey “Hello, etcd!”</p>
<pre><code>这条命令会在ETCD中创建一个名为`mykey`的键，并将其值设置为`Hello, etcd!`。

##### 5.2 获取键值对

使用`etcdctl get`命令获取键值对：
</code></pre>
<p>etcdctl get mykey</p>
<pre><code>这条命令将输出`mykey`键的值，即`Hello, etcd!`。

##### 5.3 列出所有键

ETCD没有直接列出所有键的命令，但你可以通过`etcdctl endpoint status --write-out=table`查看集群状态，或者使用范围查询（`etcdctl get --keys-only --from-key "" --limit=0`）来尝试获取所有键（注意，这可能在大型数据集中非常低效）。

##### 5.4 删除键值对

使用`etcdctl del`命令删除键值对：
</code></pre>
<p>etcdctl del mykey</p>
<pre><code>这条命令将删除名为`mykey`的键及其值。

##### 5.5 监视键的变化

使用`etcdctl watch`命令可以监视一个或多个键的变化。例如，要监视`mykey`键的变化，可以使用：
</code></pre>
<p>etcdctl watch mykey</p>
<pre><code>此命令将保持运行，并输出`mykey`键的每次更改。

#### 六、配置ETCD集群

要配置ETCD集群，你需要确保每个节点上的`etcd.conf`配置文件都正确设置了集群成员信息。这包括每个成员的`name`、`listen-peer-urls`、`initial-advertise-peer-urls`以及`initial-cluster`。

##### 6.1 集群成员配置示例

假设你有一个包含三个节点的ETCD集群，每个节点的配置文件可能如下所示：

**节点1（etcd-node-1）**:
</code></pre>
<p>name=“etcd-node-1”
 <br>
 listen-peer-urls=“http://192.168.1.10:2380”
 <br>
 initial-advertise-peer-urls=“http://192.168.1.10:2380”
 <br>
 listen-client-urls=“http://192.168.1.10:2379”
 <br>
 advertise-client-urls=“http://192.168.1.10:2379”
 <br>
 initial-cluster=“etcd-node-1=http://192.168.1.10:2380,etcd-node-2=http://192.168.1.11:2380,etcd-node-3=http://192.168.1.12:2380”
 <br>
 initial-cluster-state=“new”
 <br>
 initial-cluster-token=“etcd-cluster-token”</p>
<pre><code>**节点2（etcd-node-2）和节点3（etcd-node-3）的配置文件类似，但`name`、`listen-peer-urls`、`initial-advertise-peer-urls`和IP地址将不同。**

##### 6.2 启动集群

确保所有节点的防火墙设置允许ETCD端口（默认为2379和2380）的通信，然后在每个节点上启动ETCD服务。

#### 七、安全配置

为了增强ETCD集群的安全性，你可以启用TLS加密、身份验证和授权控制。这包括生成TLS证书和密钥，并在ETCD配置文件中设置相应的参数。

##### 7.1 生成TLS证书

你可以使用OpenSSL等工具生成自签名证书，或者从受信任的证书颁发机构获取证书。

##### 7.2 配置ETCD以使用TLS

在ETCD的配置文件中，设置`--cert-file`、`--key-file`、`--trusted-ca-file`等参数以指定TLS证书、密钥和受信任的CA文件。

##### 7.3 启用身份验证和授权

ETCD支持多种身份验证和授权机制，包括基于简单令牌的认证（simple token-based authentication）、基于角色的访问控制（RBAC）等。为了增强安全性，建议启用RBAC来管理对ETCD资源的访问。

###### 7.3.1 启用RBAC

首先，你需要在ETCD中启用RBAC。这通常通过设置`--enable-v3`（ETCD v3默认启用）和`--enable-rbac`标志来完成。在配置文件或启动命令中指定这些标志。

###### 7.3.2 创建角色和角色绑定

使用`etcdctl`工具，你可以创建角色（role）和角色绑定（role binding），以定义哪些用户（或用户组）有权访问哪些资源。

* **创建角色**：定义角色可以执行的操作（如读取、写入等）和这些操作可以应用于哪些键空间。
</code></pre>
<p>etcdctl role add myrole
 <br>
 etcdctl role grant-permission myrole readwrite /mykeys/</p>
<pre><code>* **创建用户**：在ETCD中创建用户（注意：ETCD不直接存储用户密码，而是使用JWT令牌或外部身份验证系统）。对于简单的测试，你可以使用JWT令牌。

* **创建角色绑定**：将用户与角色绑定，以授予用户相应的权限。
</code></pre>
<p>etcdctl user add myuser
 <br>
 etcdctl user grant-role myuser myrole</p>
<pre><code>注意：上述命令是简化的示例，用于说明如何创建角色、用户和角色绑定。在实际应用中，你可能需要使用更复杂的认证和授权策略。

###### 7.3.3 配置ETCD以使用身份验证

确保在ETCD配置文件中或通过命令行参数指定了身份验证相关的设置，如`--auth-token`（如果使用JWT令牌）或配置ETCD以与外部身份验证系统集成。

##### 7.4 客户端身份验证

当客户端连接到ETCD集群时，它们也需要进行身份验证。这通常通过在客户端请求中包含JWT令牌或使用客户端证书来完成。

* **使用JWT令牌**：如果ETCD配置了JWT令牌身份验证，客户端需要在请求头中包含有效的JWT令牌。

* **使用客户端证书**：如果启用了TLS客户端身份验证，客户端需要使用由受信任的CA签发的有效客户端证书进行连接。

#### 八、维护和监控

ETCD集群的维护和监控是确保其稳定运行和高可用性的关键部分。

##### 8.1 备份和恢复

定期备份ETCD数据以防止数据丢失是非常重要的。你可以使用`etcdctl snapshot save`命令来创建ETCD的快照，并在需要时使用`etcdctl snapshot restore`命令来恢复数据。

##### 8.2 监控

监控ETCD集群的性能和健康状况可以帮助你及时发现并解决问题。你可以使用Prometheus、Grafana等工具来收集ETCD的度量指标，并通过可视化界面来监控集群状态。

##### 8.3 维护和升级

随着ETCD版本的更新，你可能需要定期维护和升级你的ETCD集群。在升级之前，请务必阅读升级指南，并按照指南中的步骤进行操作，以确保平滑升级并避免数据丢失。

#### 结论

ETCD是一个强大且灵活的键值存储系统，它提供了高可用性、强一致性和丰富的功能，非常适合用于分布式系统的核心组件。通过本文的介绍，你应该能够了解如何在Linux系统下安装、配置和使用ETCD，包括基本的安装步骤、配置文件的设置、常用命令的使用、集群的配置、安全性的增强以及维护和监控的方法。希望这些信息能够帮助你成功部署和管理ETCD集群。


原文链接: https://blog.csdn.net/hai40587/article/details/141246024
</code></pre>]]></description><guid isPermaLink="false">/archives/cfd42fd5-be5f-4568-bda4-5a7c3caec31c</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:27:17 GMT</pubDate></item><item><title><![CDATA[网络协议一般分为几类？如何划分]]></title><link>https://wiki.dgfygk.com/archives/dc0090a6-f5f2-4d74-8875-91dcc47cf20a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E4%B8%80%E8%88%AC%E5%88%86%E4%B8%BA%E5%87%A0%E7%B1%BB%EF%BC%9F%E5%A6%82%E4%BD%95%E5%88%92%E5%88%86&amp;url=/archives/dc0090a6-f5f2-4d74-8875-91dcc47cf20a" width="1" height="1" alt="" style="opacity:0;">
<p>网络协议是计算机网络中用于通信的规则和标准，确保不同设备之间能够有效地传输数据。网络协议可以根据不同的标准和功能进行分类，主要分为以下几类：</p>
<h4>一、按功能划分</h4>
<ol>
 <li>
  <p><strong>应用层协议</strong>
   <br>
   应用层协议负责特定应用程序之间的通信，它们定义了数据的格式和处理方式。常见的应用层协议包括：</p>
 </li>
 <li>
  <p><strong>HTTP/HTTPS</strong>：用于网页浏览。</p>
 </li>
 <li>
  <p><strong>FTP</strong>：用于文件传输。</p>
 </li>
 <li>
  <p><strong>SMTP/IMAP</strong>：用于电子邮件发送和接收。</p>
 </li>
 <li>
  <p><strong>DNS</strong>：用于域名解析。</p>
 </li>
 <li>
  <p><strong>传输层协议</strong>
   <br>
   传输层协议负责在主机之间传输数据，确保数据的完整性和顺序。常见的传输层协议包括：</p>
 </li>
 <li>
  <p><strong>TCP（传输控制协议）</strong>：提供可靠的、面向连接的传输服务，确保数据包按顺序到达。</p>
 </li>
 <li>
  <p><strong>UDP（用户数据报协议）</strong>：提供无连接、不可靠的传输服务，适合实时应用。</p>
 </li>
 <li>
  <p><strong>网络层协议</strong>
   <br>
   网络层协议负责在不同网络之间传输数据包，并为数据包选择最佳路径。常见的网络层协议包括：</p>
 </li>
 <li>
  <p><strong>IP（互联网协议）</strong>：负责数据包的寻址和路由。</p>
 </li>
 <li>
  <p><strong>ICMP（互联网控制消息协议）</strong>：用于发送错误消息和网络状态信息。</p>
 </li>
 <li>
  <p><strong>链路层协议</strong>
   <br>
   链路层协议负责在局域网或特定物理链路上传输数据帧，确保数据帧的正确传输。常见的链路层协议包括：</p>
 </li>
 <li>
  <p><strong>Ethernet</strong>：用于局域网中数据帧的传输。</p>
 </li>
 <li>
  <p><strong>PPP（点对点协议）</strong>：用于串行连接中的数据传输。</p>
 </li>
</ol>
<h4>二、按连接方式划分</h4>
<ol>
 <li>
  <p><strong>面向连接协议</strong>
   <br>
   面向连接的协议在发送数据之前建立连接，确保数据传输的可靠性。典型的面向连接协议是TCP。</p>
 </li>
 <li>
  <p><strong>无连接协议</strong>
   <br>
   无连接的协议在发送数据时不需要建立连接，适合对实时性要求高但不严格要求可靠性的应用。典型的无连接协议是UDP。</p>
 </li>
</ol>
<h4>三、按服务类型划分</h4>
<ol>
 <li>
  <p><strong>可靠协议</strong>
   <br>
   可靠协议确保数据的完整性和顺序，通常会进行重传和校验。例如，TCP就是一个可靠的传输层协议。</p>
 </li>
 <li>
  <p><strong>不可靠协议</strong>
   <br>
   不可靠协议不保证数据的完整性和顺序，数据包可能会丢失或乱序。UDP就是不可靠的协议。</p>
 </li>
</ol>
<h4>四、按作用层次划分</h4>
<ol>
 <li>
  <p><strong>基础协议</strong>
   <br>
   基础协议提供网络通信的基础功能，如IP协议、ARP（地址解析协议）等。</p>
 </li>
 <li>
  <p><strong>高层协议</strong>
   <br>
   高层协议建立在基础协议之上，提供更高级的应用功能，如HTTP、SMTP等。</p>
 </li>
</ol>
<h4>总结</h4>
<p>网络协议在计算机网络中起着至关重要的作用，不同类型的协议满足了不同的需求。了解网络协议的分类有助于深入理解网络通信的原理，提升网络设计和管理的能力。通过合理选择和应用这些协议，能够实现高效、安全的网络通信。</p>
<p>原文链接: https://blog.csdn.net/tlkqx37449/article/details/142599892</p>]]></description><guid isPermaLink="false">/archives/dc0090a6-f5f2-4d74-8875-91dcc47cf20a</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:25:39 GMT</pubDate></item><item><title><![CDATA[maven-web项目配置打包插件]]></title><link>https://wiki.dgfygk.com/archives/0ac4d616-9143-461e-b526-de72dcc4a703</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=maven-web%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE%E6%89%93%E5%8C%85%E6%8F%92%E4%BB%B6&amp;url=/archives/0ac4d616-9143-461e-b526-de72dcc4a703" width="1" height="1" alt="" style="opacity:0;">
<h2>maven-web项目配置打包</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F08070c8d57b94d279a6c54d2ef569755.jpeg&amp;size=m" alt=""></p>
<p>配置maven 打包插件</p>
<pre><code class="prism language-xml">&lt;!-- 打包 --&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;
                &lt;version&gt;2.4&lt;/version&gt;
                &lt;!-- &lt;configuration&gt;
                    &lt;webResources&gt;
                        &lt;resource&gt;
                            &lt;directory&gt;webapp&lt;/directory&gt;
                        &lt;/resource&gt;
                    &lt;/webResources&gt;
                &lt;/configuration&gt; --&gt;
            &lt;/plugin&gt;
</code></pre>
<p>完整示例</p>
<pre><code class="prism language-xml">&lt;build&gt;
        &lt;finalName&gt;filemanager&lt;/finalName&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
                &lt;version&gt;2.3.2&lt;/version&gt;
                &lt;configuration&gt;
                    &lt;source&gt;8&lt;/source&gt;
                    &lt;target&gt;8&lt;/target&gt;
                &lt;/configuration&gt;
            &lt;/plugin&gt;

            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.tomcat.maven&lt;/groupId&gt;
                &lt;artifactId&gt;tomcat7-maven-plugin&lt;/artifactId&gt;
                &lt;configuration&gt;
                    &lt;!-- 指定端口 --&gt;
                    &lt;port&gt;8000&lt;/port&gt;
                    &lt;!-- 请求路径 --&gt;
                    &lt;path&gt;/filemanager&lt;/path&gt;
                &lt;/configuration&gt;
            &lt;/plugin&gt;
            &lt;!-- 打包 --&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;
                &lt;version&gt;2.4&lt;/version&gt;
                &lt;!-- &lt;configuration&gt;
                    &lt;webResources&gt;
                        &lt;resource&gt;
                            &lt;directory&gt;webapp&lt;/directory&gt;
                        &lt;/resource&gt;
                    &lt;/webResources&gt;
                &lt;/configuration&gt; --&gt;
            &lt;/plugin&gt;

        &lt;/plugins&gt;
    &lt;/build&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/u011628753/article/details/142586768</p>]]></description><guid isPermaLink="false">/archives/0ac4d616-9143-461e-b526-de72dcc4a703</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:25:26 GMT</pubDate></item><item><title><![CDATA[如何创建一个微信公众号（手把手、超详细）]]></title><link>https://wiki.dgfygk.com/archives/8bdd3ad6-d945-4166-96c0-74078d74d7ed</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%EF%BC%88%E6%89%8B%E6%8A%8A%E6%89%8B%E3%80%81%E8%B6%85%E8%AF%A6%E7%BB%86%EF%BC%89&amp;url=/archives/8bdd3ad6-d945-4166-96c0-74078d74d7ed" width="1" height="1" alt="" style="opacity:0;">
<h2>一、网址</h2>
<p><a href="https://mp.weixin.qq.com/">https://mp.weixin.qq.com/</a>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffbf57f96f9b3e5abe5af207675fe7922.png&amp;size=m" alt=""></p>
<h2>二、账号类型</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F101bc829f014803510106eb3bc44b3b9.png&amp;size=m" alt="">
 <br>
 <em>详见后文 “三、创建订阅号 ——&gt; 2、选择类型 “</em></p>
<h2>三、创建"订阅号”</h2>
<h3>1、基本信息</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0ab2ad3e7aa889eab2a9984c66d6e50e.png&amp;size=m" alt=""></p>
<h3>2、选择类型</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8921961460df7f2a3a533eb73126b6f6.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9d684040c9d99bb2a1985cc7238611f2.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb1d0ef7ef29506bafd295b84ccd0df44.png&amp;size=m" alt=""></p>
<p><strong>1、订阅号</strong> ：主要偏于为用户传达资讯（类似报纸杂志），认证前后都是每天只可以群发一条消息；
 <br>
 <strong>2、服务号</strong> ：主要偏于服务交互（类似银行，114，提供服务查询），认证前后都是每个月可群发4条消息；
 <br>
 <strong>3、企业微信</strong> ：是一个面向企业级市场的产品，是一个独立的APP好用的基础办公沟通工具，拥有最基础和最实用的功能服务，专门提供给企业使用的IM产品。（适用于企业、政府、事业单位或其他组织）；
 <br>
 <strong>4、小程序</strong>：是一种新的开放能力，开发者可以快速的开发一个小程序。小程序可以在微信内被便捷的获取和传播，同时拥有出色的使用体验。</p>
<p><strong>温馨提示：</strong>
 <br>
 1）如果想简单的发送消息，达到宣传效果，建议可选择订阅号；
 <br>
 2）如果想用公众号获得更多的功能，例如开通微信支付，建议可以选择服务号；
 <br>
 3）如果想用来管理内部企业员工、团队，对内使用，可申请企业微信；
 <br>
 4）订阅号不支持变更为服务号，同样，服务号也不可变更成订阅号。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb3546fcfa825d74ede081e4cba75f19c.png&amp;size=m" alt="">
 <br>
 图例说明：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb61ef357987e5c4820f348443a6ae487.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2b3934712d2d96a9723c0c6b258b94b6.png&amp;size=m" alt=""></p>
<h3>3、信息登记（以订阅号-个人为例）</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb5db2f0402fce9b2354a52dad18fdeef.png&amp;size=m" alt=""></p>
<h4>（1）主体信息登记</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5c116f8904b97604962a0ed9f0751b2a.png&amp;size=m" alt=""></p>
<h4>（2）管理员信息登记</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3588853b1cf7d7fe66138c35a43c6049.png&amp;size=m" alt=""></p>
<h4>（3）创作者信息（选填）</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff03450a692c0f7e387e6fc439b647b3a.png&amp;size=m" alt=""></p>
<h3>4、公众号信息</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff956029b26f9d7422b05715e30a64b5f.png&amp;size=m" alt=""></p>
<h2>四、发布文章</h2>
<p>That's all.
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F67408bef034394f7e737d03c4058d61b.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/weixin_44465904/article/details/128454492</p>]]></description><guid isPermaLink="false">/archives/8bdd3ad6-d945-4166-96c0-74078d74d7ed</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 07:25:06 GMT</pubDate></item><item><title><![CDATA[Mybatis(进阶部分)]]></title><link>https://wiki.dgfygk.com/archives/2266fb16-61cc-43ec-af13-78d053a80268</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Mybatis%28%E8%BF%9B%E9%98%B6%E9%83%A8%E5%88%86%29&amp;url=/archives/2266fb16-61cc-43ec-af13-78d053a80268" width="1" height="1" alt="" style="opacity:0;">
<h2>四 Mybatis完成CURD（二）</h2>
<h3>4.5 多条件CRUD</h3>
<p>之前的案例中，接口里方法的形参个数都是1个；<strong>如果方法形参是两个或者两个以上时，MyBatis又该如何获取获取参数呢？</strong></p>
<p>Mybatis提供了好几种方式，可以获取多个参数的值</p>
<p><strong>第一种：</strong> <strong>使用arg0，arg1…或者param1，param2…来按照参数顺序获取对应的值</strong></p>
<pre><code class="java"> /*
        通过部门编号，领导编号，员工姓名，查询员工信息
     */
    Employee findByDeptnoAndMgrAndEname(int deptno, int mgr, String ename);
</code></pre>
<p>接口里的方法与Sql映射文件中的语句进行映射后，并且在调用方法期间，Mybatis会默认将所有传入到方法中的实际参数封装到一个Map对象中，实际参数作为value，<strong>按照从左到右的顺序，分别绑定到key名为arg0，arg1…或者param1，param2…上。</strong></p>
<p>因此我们在获取参数时，可以这样写</p>
<pre><code class="XML">    &lt;!--Employee findByDeptnoAndMgrAndEname(int deptno, int mgr, String ename);--&gt;
&lt;!--    方法的参数有多个时，可以不用子标签里书写parameterType属性--&gt;
&lt;!--    没有指定参数类型时，会默认从自动封装的Map对象中，寻找具体的值给?赋上
        使用arg0,arg1,arg2...
        或者使用param1,param2,param3...
--&gt;
    &lt;select id="findByDeptnoAndMgrAndEname" resultType="employee" resultMap="resultMap1" &gt;
&lt;!--        select * from emp where deptno = #{arg0} and mgr =#{param2} and ename=#{arg2}--&gt;
&lt;!--        select * from emp where deptno = #{arg0} and mgr =#{arg1} and ename=#{arg2}--&gt;
        select * from emp where deptno = #{param1} and mgr =#{param2} and ename=#{param3}

    &lt;/select&gt;
</code></pre>
<p>测试：</p>
<pre><code class="java">@Test
    public void test1(){
        //获取代理对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        /*
            当对应的Sql映射文件中，没有指定参数类型时，Mybatis会将实际参数封装到一个Map对象中
                arg0,arg1...作为key,
                实际参数从左到右依次作为value与key对应。
                还会再存储一组键值对:
                param1,param2....作为key
                实际参数从左到右依次作为value与key对应。
         */
        Employee scott = mapper.findByDeptnoAndMgrAndEname(30, 7698, "TURNER");
        System.out.println(scott);
    }
</code></pre>
<p><strong>第二种：Map作为参数</strong></p>
<p>map 集合：只需要保证 SQL 中的参数名和 map 集合的键的名称对应上，即可设置成功</p>
<pre><code class="java">/*
        如果在设计方法时，需要使用多个形参。为了将值传入到Mybatis的映射文件中的sql语句中，我们可以换一个思路：
        就是在设计方法时，形参设计成Map类型，在调用时，将各种实际参数封装到Map对象中，然后只需要传入Map参数即可

        注意，映射文件中，要使用parameterType:来指定形参为Map类型,map/Map
     */
    Employee findByMap(Map&lt;String,Object&gt; map);
</code></pre>
<p>映射文件：</p>
<pre><code class="XML">&lt;!--    Employee findByMap(Map&lt;String,Object&gt; map);--&gt;
&lt;!--    如果形参是Map类型,占位符中的名称必须是map的key,所以在封装成map对象时，尽量见名知意--&gt;
    &lt;select id="findByMap" resultType="Employee" resultMap="resultMap1" parameterType="map"&gt;
        select * from emp where deptno = #{deptno} and mgr = #{mgr} and empno = #{empno} and job = #{job}
    &lt;/select&gt;
</code></pre>
<p>测试：</p>
<pre><code class="java"> @Test
    public void test2(){
        //获取代理对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        // 自定义一个map类型，用于封装浏览器传过来的各种值。
        Map&lt;String,Object&gt; map = new HashMap&lt;&gt;();
        map.put("deptno",30);
        map.put("mgr",7698);
        map.put("empno",7499);
        map.put("job","SALESMAN");
        //map在存储键值对时，key必须和占位符中的名字一样。因为占位符是通过key来从map中获取具体的value值，给?赋值。
        Employee byMap = mapper.findByMap(map);
        System.out.println(byMap);
    }
</code></pre>
<p><strong>第三种：实体类作为参数</strong></p>
<p>实体类封装参数：只需要保证 SQL 中的参数名和实体类属性名对应上，即可设置成功</p>
<p>实体类：</p>
<pre><code class="java">package com.mybatis.pojo;

public class ParamsType {
    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;

    public ParamsType() {}

    public ParamsType(Integer empno, String ename, String job, Integer mgr) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.mgr = mgr;
    }

    @Override
    public String toString() {
        return "ParamsType{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                '}';
    }
}
</code></pre>
<p>接口：</p>
<pre><code class="java">/*
        如果在设计方法时，需要使用多个形参。除了使用Map类型外，还可以将这多个形参设计到一个实体类对象上。
        比如： 如果这些形参都是表对应的那个实体类，那就可以直接使用表对应的实体类，比如Employee
            还可以单独封装到一个参数实体类，专门用于传参用的，比如定义一个类型ParamsType。
        select * from emp where empno = ? and ename = ? and job = ? and mgr = ?

     */
    Employee findByEntity(ParamsType params);
</code></pre>
<p>映射文件：</p>
<pre><code class="XML">&lt;!--     Employee findByEntity(ParamsType params);--&gt;
    &lt;select id="findByEntity" resultType="Employee" resultMap="resultMap1" parameterType="paramsType"&gt;
        select * from emp where empno=#{empno} and ename=#{ename} and job=#{job} and mgr=#{mgr}
    &lt;/select&gt;
</code></pre>
<p>测试代码</p>
<pre><code class="java">    @Test
    public void test3(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        ParamsType paramsType = new ParamsType(7499,"ALLEN","SALESMAN",7698);
        Employee byEntity = mapper.findByEntity(paramsType);
        System.out.println(byEntity);
    }
</code></pre>
<p><strong>第四种：使用@Param注解命名参数</strong></p>
<p>散装参数：需要使用 @Param （” SQL 中的参数占位符名称”）</p>
<pre><code class="java">    /*
        如果在设计方法时，需要使用多个形参。除了上述方法之外，还可以使用注解@Param("占位符的名字")。
        这种方式就可以使用对应的形参给对应的占位符赋值了。非常方便。
     */
    Employee findByAnnotation(@Param("empno") int empno,
                              @Param("ename") String ename,
                              @Param("job") String jobb,
                              @Param("mgr") int mgrno);
</code></pre>
<p>映射文件：</p>
<pre><code class="XML">&lt;!--    Employee findByAnnotation(@Param("empno") int empno,
                              @Param("ename") String ename,
                              @Param("job") String jobb,
                              @Param("mgr") int mgrno);--&gt;
    &lt;select id="findByAnnotation" resultType="Employee" resultMap="resultMap1"&gt;
        select * from emp where empno=#{empno} and ename=#{ename} and job=#{job} and mgr=#{mgr}

    &lt;/select&gt;
</code></pre>
<p>测试类：</p>
<pre><code class="java">    @Test
    public void test4(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee byEntity = mapper.findByAnnotation(7499,"ALLEN","SALESMAN",7698);
        System.out.println(byEntity);
    }
</code></pre>
<h3>4.6 动态SQL</h3>
<p>SQL语句<strong>会随着用户的输入和外部条件的变化而变化</strong> ，我们称之为<strong>动态SQL</strong>。MyBatis对动态SQL有很强大的支持。</p>
<h4>4.6.1 where/if标签</h4>
<p>if标签，是根据Test属性中的布尔表达式的值，从而决定是否执行包含在其中的SQL片段。如果判断结果为true，则执行其中的SQL片段；如果结果为false，则不执行其中的SQL片段</p>
<p>应用场景如图所示： <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F30f79bae13b542bb8c5adb18d3e4c8ed.png&amp;size=m" alt=""></p>
<p>存在的问题：第一个条件不需要逻辑运算符。</p>
<p><strong>案例演示：</strong></p>
<p>在接口EmployeeMapper.java里添加如下方法</p>
<pre><code class="java"> /* ---where条件可能会随着用户的输入而改变的我们称之为动态sql----
        mybatis支持动态sql的写法
        第一个动态SQL： 应用场景： 浏览器在查询操作时，从多个条件中进行任意个组合条件进行查询，条件个数未知
    * */
    Employee findByConditionNum(Map&lt;String,Object&gt; map);
</code></pre>
<p>在映射文件EmployeeMapper.xml里配置如下</p>
<p><strong>第一种方案：</strong>使用恒等式让所有条件格式都一样</p>
<pre><code class="XML">&lt;!--    动态sql的练习
    Employee findByConditionNum(Map&lt;String,Object&gt; map);
    用户有的时候，按照 员工编号查询....
        有的时候按照 员工姓名查询....
        有的时候按照 员工编号 和职位查询
        select * from emp where empno = ?
        select * from emp where ename = ?
        select * from emp where empno = ? and job = ?
--&gt;
&lt;!--    第一种写法：使用1=1恒成立的写法，来解决 可能多一个and关键字的问题
--&gt;
    &lt;select id="findByConditionNum" resultType="Employee" resultMap="resultMap1" parameterType="map"&gt;
        select * from emp
        where 1=1
        &lt;if test="empno != null and empno!='' "&gt;
            and empno = #{empno}
        &lt;/if&gt;
        &lt;if test="ename != null and ename!='' "&gt;
            and ename = #{ename}
        &lt;/if&gt;
        &lt;if test="job != null and ename!='' "&gt;
            and job = #{job}
        &lt;/if&gt;

    &lt;/select&gt;
</code></pre>
<p><strong>第二种方案：</strong> 使用<code>&lt;where&gt;</code>标签替换where关键字。 注意：<strong>where标签会将第一个条件之前的连接符自动去掉</strong></p>
<pre><code class="XML">&lt;-- 第二种写法：配合where标签， where标签的作用，用来连接条件，如果在第一个条件前有多余连接符号(and 、or)。会自动去掉 --&gt;
        &lt;select id="findByConditionNum" resultType="Employee" resultMap="resultMap1" parameterType="map"&gt;  
        select * from emp
        &lt;where&gt;
            &lt;if test="empno!=null and empno!='' "&gt;
                empno = #{empno}
            &lt;/if&gt;
            &lt;if test="ename!=null and ename!='' "&gt;
                and ename = #{ename}
            &lt;/if&gt;
            &lt;if test="job!=null and job!='' "&gt;
                and job = #{job}
            &lt;/if&gt;
        &lt;/where&gt;
    &lt;/select&gt;
</code></pre>
<h4>4.6.2 choose/when标签</h4>
<p>应用场景：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8344f0d84743416ab1264e99a2156344.png&amp;size=m" alt=""></p>
<blockquote>
 <p>choose（when，otherwise）:类似于java的switch-case-default， 只要满足一个when，choose就结束了，如果都不满足，就会执行otherwise。</p>
</blockquote>
<p>1）在接口EmployeeMapper里添加如下方法</p>
<pre><code class="java">/*
        第二个动态sql：  应用场景： 浏览器在查询操作时，只能从多个条件中选择一个条件进行查询。条件个数只有一个
     */
    Employee findByConditionOne(Map&lt;String,Object&gt; map);
</code></pre>
<p>2）在sql映射文件EmployeeMapper.xml里添加如下内容</p>
<pre><code class="XML">&lt;!--    Employee findByConditionOne(Map&lt;String,Object&gt; map);--&gt;
&lt;!--    对应的标签：  choose-when-otherwise  与java的switch-case(break)-default功能一样，只能执行其中一个分支
                choose是父标签
                when是子标签，可以有多个，如果执行了某一个when，则结束choose
                otherwise是子标签，只能有一个，如果没有执行的when，则执行otherwise

    需求： 用户可能按照员工编号查询,也可能按照员工姓名查询,也可能按照奖金查询。
--&gt;

    &lt;select id="findByConditionOne" resultType="Employee" resultMap="resultMap1" parameterType="map"&gt;
        select * from emp where
        &lt;choose&gt;
            &lt;when test="empno!=null and empno!='' "&gt;
                empno = #{empno}
            &lt;/when&gt;
            &lt;when test="ename!=null and ename!='' "&gt;
                ename = #{ename}
            &lt;/when&gt;
            &lt;when test="comm!=null and comm!='' "&gt;
                comm = #{comm}
            &lt;/when&gt;
            &lt;otherwise&gt;
                1=1
            &lt;/otherwise&gt;
        &lt;/choose&gt;
    &lt;/select&gt;
</code></pre>
<p>3）测试</p>
<pre><code class="java"> /*模拟场景浏览器端只传过来一个条件进行查询*/
    @Test
    public void test6(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Map&lt;String,Object&gt; map = new HashMap&lt;&gt;();
        map.put("comm",1400);
        Employee byConditionOne = mapper.findByConditionOne(map);
        System.out.println(byConditionOne);
    }
</code></pre>
<h4>4.6.3 set/if标签</h4>
<p>当进行修改时，有些字段可能有选择的进行修改，这时我们就可以使用<code>&lt;set&gt;</code>标签 配合<code>&lt;if&gt;</code>标签来完成操作。set标签会自动将最后一个条件的逗号去掉。</p>
<p>1）在接口EmployeeMapper里添加如下方法</p>
<pre><code class="java">/*
        第三个动态sql: 应用场景： 浏览器在修改信息时，修改的文本框的个数不定
     */
    void updateByCondition(Map&lt;String,Object&gt; map);
</code></pre>
<p>2）在sql映射文件EmployeeMapper.xml里添加如下内容</p>
<pre><code class="XML">&lt;!--      void updateByCondition(Map&lt;String,Object&gt; map);

        标签：
            set/if两个标签的应用
            Set标签可以自动去掉最后一个字段后面的逗号。

        应用场景：  浏览器在修改信息时，可能修改了员工姓名，职位
                  也可能修改了员工的工资和奖金，总之就是修改的内容不一定是什么。
--&gt;
    &lt;update id="updateByCondition" parameterType="map"&gt;
        update emps
        &lt;set&gt;
            &lt;if test="ename != null and ename != '' "&gt;
                ename = #{ename},
            &lt;/if&gt;
            &lt;if test="job != null and job != '' "&gt;
                job = #{job},
            &lt;/if&gt;
            &lt;if test="mgr != null and mgr&gt;0"&gt;
                mgr = #{mgr},
            &lt;/if&gt;
            &lt;if test="hiredate != null and hiredate !='' "&gt;
                hiredate = #{hiredate},
            &lt;/if&gt;
            &lt;if test="sal != null and sal &gt;0"&gt;
                sal = #{sal},
            &lt;/if&gt;
            &lt;if test="comm != null and comm&gt;=0"&gt;
                comm = #{comm},
            &lt;/if&gt;
            &lt;if test="deptId != null and deptId &gt;0"&gt;
                deptno = #{deptId},
            &lt;/if&gt;
        &lt;/set&gt;
        where empno = #{empno}

    &lt;/update&gt;
</code></pre>
<p>3）测试</p>
<pre><code class="java">/*
        浏览器传过来的要修改的字段不一定是哪些
     */
    @Test
    public void test7(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Map&lt;String,Object&gt; map = new HashMap&lt;&gt;();
        map.put("empno",7499);
        map.put("ename","TOM");
        map.put("job","ClEAR");
        map.put("sal",1500);
        mapper.updateByCondition(map);
        sqlSession.commit();
    }
</code></pre>
<h4>4.6.4 foreach标签</h4>
<p><code>&lt;foreach&gt;</code>标签的使用， 比如进行集合查询等操作</p>
<p>1）在接口EmployeeMapper里添加如下方法</p>
<p>两种写法：直接传入字符串(使用${})和封装到集合中(foreach方法)</p>
<pre><code class="java">/*
            第四个动态sql：
            应用场景： 浏览器端可能使用了复选框来勾选要查询的某一个字段的多个值的数据，底层sql应该是一个集合查询
            比如：
            select * from emp where job in ('SALESMAN','ANALYST')
            传过来的某一个字段值不一定有几个
            select * from emp where ename in('tom','lily','lucy')

            empnos: "'tom','lily','lucy'"
     */
    List&lt;Employee&gt; findByOneColumnMuitlValue1(String empnos);
    /* foreach标签的应用：
            对一个集合进行遍历。

     */
    List&lt;Employee&gt; findByOneColumnMuitlValue2(@Param("enames") List&lt;String&gt; enames);
</code></pre>
<p>2）在EmployeeMapper.xml里添加如下内容</p>
<pre><code class="XML">&lt;!--    List&lt;Employee&gt; findByOneColumnMuitlValue1(String empnos);

        一个字段的多个值的查询操作:  一种变相的写法。
--&gt;
    &lt;select id="findByOneColumnMuitlValue1" resultType="Employee" parameterType="string" resultMap="resultMap1"&gt;
        select * from emp where empno in (${empnos})
    &lt;/select&gt;
&lt;!--    List&lt;Employee&gt; findByOneColumnMuitlValue2(List&lt;String&gt; enames);

        foreach标签: 用于遍历集合参数的。  通常用于(not) in 这种sql语句。
        属性：
            -collection: 用于指定方法的形参
            -open :  指定集合的开始符号，比如"("
            -close :  指定集合的结束符号，比如")"
            -item: 遍历期间的元素的存储位置，即变量   注意： foreach标签中要使用变量
            -separator: 分隔符，比如","
--&gt;
    &lt;select id="findByOneColumnMuitlValue2" resultType="Employee" resultMap="resultMap1"&gt;
        select * from emp
        where ename in
        &lt;foreach collection="enames" open="(" close=")" item="ename" separator="," &gt;
            #{ename}
        &lt;/foreach&gt;
    &lt;/select&gt;
</code></pre>
<p>3）测试</p>
<pre><code class="java">    @Test
    public void test8(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        String empnos = "7499,7521,7566";
//        String enames = "'TOM','WARD'";
        List&lt;Employee&gt; emps = mapper.findByOneColumnMuitlValue1(empnos);
        emps.forEach(System.out::println);
    }

    @Test
    public void test9(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        List&lt;String&gt; names = new ArrayList&lt;&gt;();
        names.add("TOM");
        names.add("WARD");
        names.add("KING");
        List&lt;Employee&gt; list = mapper.findByOneColumnMuitlValue2(names);
        list.forEach(System.out::println);
    }
</code></pre>
<h3>4.7 不同返回值类型的查询</h3>
<h4>4.7.1 返回基本数据类型</h4>
<pre><code class="java">/*
        查询student表中的记录个数
     */
    int selectCount();
    /*
</code></pre>
<pre><code>```XML
&lt;select id="selectCount" resultType="_int"&gt;
        select count(*) from student
    &lt;/select&gt;
</code></pre>
<h4>4.7.2 返回引用类型(实体类)</h4>
<pre><code class="java">/*
        返回值为实体类的
     */

    Student findById(Integer id);
</code></pre>
<pre><code>```XML
&lt;!--    Student findById(Integer id);
        属性resultType: 书写方法的返回值类型  代理对象通过sql语句进行查询，然后将查询到的结果自动封装成返回值类型的实例--&gt;
    &lt;select id="findById" resultType="Student"&gt;
        select * from student where id=#{arg0022331kkkskk}
    &lt;/select&gt;
</code></pre>
<h4>4.7.3 返回List类型</h4>
<pre><code class="java"> /*
        返回值为List类型
     */
    List&lt;Student&gt; findByPage(@Param("offset") int offset,@Param("pageSize") int pageSize);
</code></pre>
<pre><code>```XML
&lt;!--    List&lt;Student&gt; findByPage(@Param("page") int page,@Param("pageSize") int pageSize);
        返回值类型: 如果方法的返回值类型时List, 那么在属性resultType上只需要指定集合当元素类型的名字即可
    --&gt;
    &lt;select id="findByPage" resultType="Student"&gt;
        select * from student limit #{offset},#{pageSize}
    &lt;/select&gt;
</code></pre>
<h4>4.7.4 返回Map类型</h4>
<pre><code class="java">/*
        返回值为Map类型
        应用场景： 将查询出来的信息封装成Map形式的样子。
            表的字段名作为key
            字段值作为value
     */
    Map&lt;String,Object&gt; findByName(String name);
</code></pre>
<pre><code>```XML
&lt;select id="findByName" resultType="Map"&gt;
        select * from student where name=#{name}
    &lt;/select&gt;
</code></pre>
<h4>4.7.5 返回Map实例的集合</h4>
<pre><code class="java"> /*
    应用场景： 将查询出来的多条记录，封装到Map集合中

    条件：通过性别，开头查询出来多条记录， 封装到List集合中，泛型为Map
     */
    List&lt;Map&lt;String,Object&gt;&gt; findByGender(@Param("gender") String gender);
</code></pre>
<pre><code>```XML
&lt;select id="findByGender" resultType="Map"&gt;
        select * from student where gender=#{gender}
    &lt;/select&gt;
</code></pre>
<h4>4.7.6 返回Map的另一种情况</h4>
<pre><code class="java">    /**
     * 1001  =&gt; {address=江南, gender=m, name=刘备, age=40, sid=1001}
     * 1002  =&gt; {address=上海, gender=m, name=关羽, age=35, sid=1002}
     * 1003  =&gt; {address=长春, gender=m, name=赵云, age=27, sid=1004}
     */
//    Map&lt;String,Map&lt;String,Object&gt;&gt;
    @MapKey("id")
    Map&lt;Integer,Object&gt; findByGender2(@Param("gender") String gender);
</code></pre>
<pre><code>```XML
    &lt;select id="findByGender2" resultType="Map"&gt;
        select * from student where gender=#{gender}
    &lt;/select&gt;
</code></pre>
<h4>4.7.7 返回Set集合</h4>
<pre><code class="java">/*
    * 应用场景：  数据库里的记录可能有重复数据，  然后再进行查询时，想要进行去重操作，我们就可以设计方法的
    * 返回值为Set,  泛型为该表的实体类   注意：  实体类必须重写equals方法和hashCode方法。
    * */
Set&lt;Student&gt; findAllByDistinct();
</code></pre>
<pre><code>```XML
&lt;select id="findAllByDistinct" resultType="student"&gt;
   select * from student
&lt;/select&gt;
</code></pre>
<h3>4.8 特殊SQL的查询</h3>
<h4>4.8.1 模糊查询</h4>
<p>在sql映射文件中的三种写法：</p>
<pre><code class="sql">like  '%${username}%'
like concat('%',#{username},'%')
like "%"#{username}"%"
</code></pre>
<p>案例演示：</p>
<pre><code class="java">/*
        模糊查询：select * from student where name like '%备%'
     */
    List&lt;Student&gt; findByNameLike(@Param("shortName") String shortName);
</code></pre>
<pre><code>```XML
&lt;!--    select * from student where name like '%${shortName}%'--&gt;
&lt;!--    select * from student where name like concat('%',#{shortName},"%")--&gt;

&lt;!--    模糊匹配。--&gt;
    &lt;select id="findByNameLike" resultType="Student"&gt;
         select * from student where name like "%"#{shortName}"%"
    &lt;/select&gt;
</code></pre>
<h4>4.8.2 批量删除</h4>
<p>两种方法：</p>
<blockquote>
 <p>批量删除： 一般都是通过唯一标识符来删除的，浏览器上应该使用的是复选框。</p>
 <p>传入服务端的应该是一个唯一标识符的集合。</p>
</blockquote>
<p>第一种处理方式 String ids = “1,2,3,4,5” 占位符应该使用<strong>${}</strong> delete from student where id in (${})</p>
<pre><code class="java">   void deleteBatch1(String ids);
</code></pre>
<pre><code>```XML
    &lt;delete id="deleteBatch1"&gt;
        delete from student where id in (${i})
    &lt;/delete&gt;
</code></pre>
<p>第二种处理方式： 使用foreach标签</p>
<pre><code class="java">void deleteBatch2(@Param("ids") List&lt;Integer&gt; ids);
</code></pre>
<pre><code>```XML
&lt;!--    foreach中的collection属性默认为list list就是你传入的集合的。 如果没有指定（用@param指定），就会是默认的list--&gt;
    &lt;delete id="deleteBatch2" parameterType="list"&gt;
        delete from student where id in
        &lt;foreach collection="ids" open="(" close=")" separator="," item="id" &gt;
            #{id}
        &lt;/foreach&gt;
    &lt;/delete&gt;
</code></pre>
<h4>4.8.3 动态指定表名</h4>
<p>动态指定表名应该就是根据不同的表名实现不同的查询结果。</p>
<pre><code class="java">/*动态指定表名*/
    List&lt;Student&gt; findAllByTableName(String tableName);
</code></pre>
<pre><code>```XML
&lt;!--    动态指定表名：  站位符使用${}
        List&lt;Student&gt; findAllByTableName(String tableName);--&gt;
    &lt;select id="findAllByTableName" resultType="com.mybatis.pojo.Student"&gt;
        select  * from ${tableName}
    &lt;/select&gt;
</code></pre>
<h4>4.8.4 主键返回</h4>
<p><strong>自增长类型主键</strong></p>
<blockquote>
 <p>主键返回：</p>
 <p>之前的写法： 想要向数据库中新增一条记录 insert into student (id,name,gender,age,address)values(null,'安其拉','f',40,'北京')</p>
 <p>自增主键会传值null，想要获取主键值还需要再去表中查询。</p>
</blockquote>
<p><strong>mybatis为我们提供了可以在添加时获取主键的方式。（useGenerateKeys，keyproperty）</strong></p>
<pre><code class="java"> void insertStudent(Student student);
</code></pre>
<pre><code>```XML
&lt;!--
    应用场景： 向数据库保存记录时，同时返回这条记录的主键值。应用于后续代码。
        useGeneratedKeys： 是否要使用数据库中生产的该条记录的主键值，true表示使用，false表示不使用
        keyproperty: 用于指定生成的主键值，存储的位置，一般指的是对应的实体类的属性值。
--&gt;
    &lt;insert id="insertStudent" useGeneratedKeys="true" keyProperty="id"&gt;
        insert into student values(null,#{name},#{gender},#{age},#{address})
    &lt;/insert&gt;
</code></pre>
<p>测试：</p>
<pre><code class="java">@Test
public void test12() throws IOException {
   InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
   SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
   SqlSession sqlSession = factory.openSession();
   StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

   Student student = new Student("张鲁一",40,"男","2222233333","北京");
   mapper.insertStudent(student);
   System.out.println(student.getId());

   sqlSession.commit();
   sqlSession.close();

}
</code></pre>
<p><strong>非自增长类型主键</strong></p>
<p>创建数据库的新表</p>
<pre><code class="sql">use mybatis_db;
drop table if exists computer;
create table if not exists computer(
    id varchar(100) primary key,
    brand varchar(20),
   model varchar(20),
    price double
);

insert into computer values (1, 'ThinkPad','X1',12000);
insert into computer values (2, 'Mi','p1',6500);
insert into computer values (3, 'lenove','s1',4000);

commit;

select * from computer;
</code></pre>
<p>而对于不支持自增型主键的数据库（例如 Oracle）或者字符串类型主键，则可以使用 selectKey 子元素：selectKey 元素将会首先运行，id 会被设置，然后插入语句会被调用！</p>
<p>使用 selectKey 帮助插入UUID作为字符串类型主键示例：</p>
<pre><code class="XML">&lt;insert id="insertUser" parameterType="User"&gt;
    &lt;selectKey keyProperty="id" resultType="java.lang.String"
        order="BEFORE"&gt;
        SELECT UUID() as id
    &lt;/selectKey&gt;
    INSERT INTO user (id, username, password) 
    VALUES (
        #{id},
        #{username},
        #{password}
    )
&lt;/insert&gt;
</code></pre>
<p>在上例中，我们定义了一个 insertUser 的插入语句来将 User 对象插入到 user 表中。我们使用 selectKey 来查询 UUID 并设置到 id 字段中。</p>
<p>通过 keyProperty 属性来指定查询到的 UUID 赋值给对象中的 id 属性，而 resultType 属性指定了 UUID 的类型为 java.lang.String。</p>
<p>需要注意的是，我们将 selectKey 放在了插入语句的前面，这是因为 MySQL 在 insert 语句中只支持一个 select 子句，而 selectKey 中查询 UUID 的语句就是一个 select 子句，因此我们需要将其放在前面。</p>
<p>最后，在将 User 对象插入到 user 表中时，我们直接使用对象中的 id 属性来插入主键值。</p>
<p>使用这种方式，我们可以方便地插入 UUID 作为字符串类型主键。当然，还有其他插入方式可以使用，如使用Java代码生成UUID并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。</p>
<h3>4.9 级联查询</h3>
<h4>4.9.1 多对一查询</h4>
<p>多对一，指的是表与表之间的记录关系，比如学生信息表(`S_ID`，`S_NAME`，……，`T_ID`)与教师(班主任)信息表(`T_ID`，`T_NAME`，……)。多个学生是一个老师教的。通过学生信息表里的任意一条记录，都可以找到教师信息表里的对应的老师信息。</p>
<p>第一种写法：字段映射</p>
<p>第二种写法：association(关联，联合)</p>
<p>第三种写法：分步写法</p>
<pre><code class="java">/*通过员工id 查找员工信息和其所在部门信息
    * 两种写法在mapper文件中。
    * */
    Employee findById(Integer id);
</code></pre>
<pre><code>```XML
 &lt;!--    字段映射写法。column: 指定数据库表中的字段名，property: 指定实体类中的属性名，
        由于要给属性dept赋值，而dept是一个Dept类型，因此实际上是给dept的各个属性赋值。
        --&gt;
    &lt;resultMap id="result1" type="employee"&gt;
        &lt;id property="id" column="empno"/&gt;
        &lt;result property="name" column="ename"/&gt;
        &lt;result property="job" column="job"/&gt;
        &lt;result property="mgr" column="mgr"/&gt;
        &lt;result property="hireDate" column="hiredate"/&gt;
        &lt;result property="sal" column="sal"/&gt;
        &lt;result property="bonus" column="comm"/&gt;
        &lt;result property="deptId" column="deptno"/&gt;
        &lt;result property="dept.deptno" column="deptno"/&gt;
        &lt;result property="dept.dname" column="dname"/&gt;
        &lt;result property="dept.loc" column="loc"/&gt;
    &lt;/resultMap&gt;
    &lt;select id="findById" resultMap="result1"&gt;
        select * from emp e join dept d on e.deptno = d.deptno where e.empno = #{id}
    &lt;/select&gt;
</code></pre>
<p>第二种写法 association</p>
<pre><code class="XML"> &lt;resultMap id="result2" type="employee"&gt;
        &lt;!--        id子标签，专门用于主键映射
                    column: 指定数据库表中的字段名
                    property: 指定实体类中的属性名
         --&gt;
        &lt;id property="id" column="empno"/&gt;
        &lt;result property="name" column="ename"/&gt;
        &lt;result property="job" column="job"/&gt;
        &lt;result property="mgr" column="mgr"/&gt;
        &lt;result property="hireDate" column="hiredate"/&gt;
        &lt;result property="sal" column="sal"/&gt;
        &lt;result property="bonus" column="comm"/&gt;
        &lt;result property="deptId" column="deptno"/&gt;

&lt;!--       多对一的查询的第二种写法： 使用association(级联，关联，联合)标签。
            property： 用于指定给实体类的哪个属性做映射。
            javaType： 用于指定该属性的java类型。
            association的子标签: 用于将字段与实体类的属性的属性进行映射关系。
--&gt;
        &lt;association property="dept" javaType="Dept"&gt;
            &lt;result property="deptno" column="deptno"/&gt;
            &lt;result property="dname" column="dname"/&gt;
            &lt;result property="loc" column="loc"/&gt;
        &lt;/association&gt;
    &lt;/resultMap&gt;


&lt;!--    --&gt;
    &lt;select id="findById" resultMap="result2"&gt;
        select * from emp e join dept d on e.deptno = d.deptno where e.empno = #{id}
    &lt;/select&gt;
</code></pre>
<p>第三种 分步查询</p>
<pre><code class="java"> // 多对一的查询的第三种写法： 分步查询
    // 通过id 查询员工信息，同时查询出员工所对应的部门信息
    Employee findByIdFirstEmpSecondDept(Integer id);
</code></pre>
<pre><code>```java
/*
        多对一的第三种写法的第二步
     */
    Dept findByIdSecondStep(Integer deptno);
</code></pre>
<p>区别代码：</p>
<pre><code class="XML">&lt;association property="dept" select="com.mybatis.mapper.DeptMapper.findByIdSecondStep" column="deptno"/&gt;
</code></pre>
<pre><code>```XML
&lt;!--
    多对一的分步写法的第二步
--&gt;
    &lt;select id="findByIdSecondStep" resultType="Dept"&gt;
        select * from dept where deptno = #{id}
    &lt;/select&gt;
</code></pre>
<pre><code>```XML
&lt;resultMap id="result3" type="employee"&gt;
        &lt;id property="id" column="empno"/&gt;
        &lt;result property="name" column="ename"/&gt;
        &lt;result property="job" column="job"/&gt;
        &lt;result property="mgr" column="mgr"/&gt;
        &lt;result property="hireDate" column="hiredate"/&gt;
        &lt;result property="sal" column="sal"/&gt;
        &lt;result property="bonus" column="comm"/&gt;
        &lt;result property="deptId" column="deptno"/&gt;

        &lt;!--       多对一的查询的第三种写法： 也是使用association(级联，关联，联合)标签。
                    不同再写association里面的子标签了
                    只需配置下面几个属性即可
                    property： 用于指定给实体类的关联属性(Dept dept)
                    select： 对应的是第二步查询，语法结构：namespace.id(另一个映射的namespace地址值加上映射里的方法id值)
                    column： 第二步的sql需要一个条件，column用于指定第一步查询中要作为第二个sql语句的字段名。
        --&gt;
        &lt;association property="dept" select="com.mybatis.mapper.DeptMapper.findByIdSecondStep" column="deptno"/&gt;
    &lt;/resultMap&gt;

    &lt;select id="findByIdFirstEmpSecondDept" resultMap="result3"&gt;
        select * from emp where empno = #{id}
    &lt;/select&gt;
</code></pre>
<h4>4.9.2 一对多查询</h4>
<p>一对多，其实就是多对一的反向操作。教师信息表是主表，学生信息表是副表，通过教师信息表的任意一条记录，都可以在学生信息表里找到该教师的多个学生信息。</p>
<p>第一种写法：collection</p>
<pre><code class="java"> // 案例演示： 查询某一个部门的信息，及其所有员工的信息。
    Dept findByIdCollection(Integer deptno);
</code></pre>
<pre><code>```XML
&lt;!--    一对多的写法 collection--&gt;
    &lt;resultMap id="deptMap1" type="Dept"&gt;
        &lt;result property="deptno" column="deptno"/&gt;
        &lt;result property="dname" column="dname"/&gt;
        &lt;result property="loc" column="loc"/&gt;
        &lt;collection property="emps" ofType="Employee"&gt;
            &lt;id property="id" column="empno"/&gt;
            &lt;result property="name" column="ename"/&gt;
            &lt;result property="job" column="job"/&gt;
            &lt;result property="mgr" column="mgr"/&gt;
            &lt;result property="hireDate" column="hiredate"/&gt;
            &lt;result property="sal" column="sal"/&gt;
            &lt;result property="bonus" column="comm"/&gt;
            &lt;result property="deptId" column="deptno"/&gt;
        &lt;/collection&gt;
    &lt;/resultMap&gt;



    &lt;select id="findByIdCollection" resultMap="deptMap1"&gt;
        select * from dept d join emp e on d.deptno = e.deptno where d.deptno=#{deptno}
    &lt;/select&gt;
</code></pre>
<p>第二种写法：分步写法</p>
<pre><code class="java"> /*
        一对多的第二种写法： 分步查询
     */
    Dept findByIdFirstStep(Integer deptno);
</code></pre>
<pre><code>```java
 // 一对多查询的第二种写法分步查询
    // 第二步：通过第一步查询的结果中的deptno字段进行查询员工信息
    List&lt;Employee&gt; findByIdSecondStep(Integer deptno);
</code></pre>
<p>区别代码：</p>
<pre><code class="XML">&lt;collection property="emps" select="com.mybatis.mapper.EmployeeMapper.findByIdSecondStep" column="deptno"/&gt;
</code></pre>
<pre><code>```XML
    &lt;resultMap id="deptMap2" type="Dept"&gt;
        &lt;result property="deptno" column="deptno"/&gt;
        &lt;result property="dname" column="dname"/&gt;
        &lt;result property="loc" column="loc"/&gt;
        &lt;collection property="emps" select="com.mybatis.mapper.EmployeeMapper.findByIdSecondStep" column="deptno"&gt;
        &lt;/collection&gt;
    &lt;/resultMap&gt;


&lt;!--    一对多查询的分步第一步--&gt;
    &lt;select id="findByIdFirstStep" resultMap="deptMap2"&gt;
        select * from dept where deptno=#{deptno}
    &lt;/select&gt;
</code></pre>
<pre><code>```XML
&lt;resultMap id="result4" type="employee"&gt;
        &lt;id property="id" column="empno"/&gt;
        &lt;result property="name" column="ename"/&gt;
        &lt;result property="job" column="job"/&gt;
        &lt;result property="mgr" column="mgr"/&gt;
        &lt;result property="hireDate" column="hiredate"/&gt;
        &lt;result property="sal" column="sal"/&gt;
        &lt;result property="bonus" column="comm"/&gt;
        &lt;result property="deptId" column="deptno"/&gt;
    &lt;/resultMap&gt;

    &lt;select id="findByIdSecondStep" resultMap="result4"&gt;
        select * from emp where deptno = #{id}
    &lt;/select&gt;
</code></pre>
<h4>4.9.3 多对多查询</h4>
<p>一般多对多，都会涉及到第三张表。 学生信息表(每个学生的信息都是唯一的一条记录)， 课程信息表（每个科目也都是唯一的一条记录），学生课程表（一个学生可以选择多个科目进行学习，一个科目可以被多个学生选择学习）。学生信息表和课程信息表通过学生课程表进行的对应关系，就是<strong>多对多</strong>的关系。</p>
<p>建表sql：</p>
<pre><code class="sql">CREATE TABLE `course`  (
  `c_id` int(0) NOT NULL AUTO_INCREMENT COMMENT '课程ID',
  `c_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程名称',
  `t_id` int(0) NOT NULL COMMENT '授课教师ID',
  `c_academy` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '所属学院',
  `c_note` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程备注',
  PRIMARY KEY (`c_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of course
-- ----------------------------
INSERT INTO `course` VALUES (1, '高数', 1, '信息学院', '高等数学，微积分');
INSERT INTO `course` VALUES (2, '英语', 2, '工程学院', '英语选修');
INSERT INTO `course` VALUES (3, 'JAVA', 3, '信息学院', '面向对象的编程语言');
INSERT INTO `course` VALUES (4, '食品安全', 1, '食品学院', '民以食为天');
INSERT INTO `course` VALUES (5, '土木建筑', 2, '工程学院', '桥梁,观景房');
INSERT INTO `course` VALUES (6, '体育', 2, '工程学院', '健身强体...');



CREATE TABLE `score`  (
  `s_id` int(0) NOT NULL COMMENT '学生ID',
  `c_id` int(0) NOT NULL COMMENT '课程ID',
  `score` int(0) NULL DEFAULT NULL COMMENT '课程分数',
  PRIMARY KEY (`s_id`, `c_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of score
-- ----------------------------
INSERT INTO `score` VALUES (1001, 1, 80);
INSERT INTO `score` VALUES (1001, 2, 90);
INSERT INTO `score` VALUES (1001, 3, 99);
INSERT INTO `score` VALUES (1002, 1, 70);
INSERT INTO `score` VALUES (1002, 2, 60);
INSERT INTO `score` VALUES (1002, 3, 80);
INSERT INTO `score` VALUES (1003, 1, 80);
INSERT INTO `score` VALUES (1003, 2, 80);
INSERT INTO `score` VALUES (1003, 4, 80);
INSERT INTO `score` VALUES (1004, 3, 50);
INSERT INTO `score` VALUES (1004, 4, 30);
INSERT INTO `score` VALUES (1004, 5, 20);
INSERT INTO `score` VALUES (1005, 5, 76);
INSERT INTO `score` VALUES (1005, 6, 87);
INSERT INTO `score` VALUES (1006, 5, 31);
INSERT INTO `score` VALUES (1006, 6, 34);
INSERT INTO `score` VALUES (1007, 4, 89);
INSERT INTO `score` VALUES (1007, 6, 98);
</code></pre>
<p>StudentMapper接口</p>
<pre><code class="java">public interface StudentMapper {
    /*
        查询每个学生的基本信息，及其所学科目信息
     */
    List&lt;Student&gt; findAll();

    /*
        根据学生ID，查询学生的基本信息，及其所学科目信息(不包含成绩)
     */
    Student findById(Integer id);

    /*
        查询所有学生的学号，姓名，性别，年龄，课程号，选修科目名称，选修成绩
     */
    List&lt;StudentInfo&gt; findAllStudentInfo();
}
</code></pre>
<p>mapper映射文件</p>
<pre><code class="XML">&lt;mapper namespace="com.mybatis.mapper.StudentMapper"&gt;

    &lt;resultMap id="studentMap1" type="Student"&gt;
        &lt;id property="id" column="id"/&gt;
        &lt;result property="name" column="name"/&gt;
        &lt;result property="age" column="age"/&gt;
        &lt;result property="gender" column="gender"/&gt;
        &lt;result property="address" column="address"/&gt;
        &lt;collection property="courses" ofType="course"&gt;
            &lt;id property="cId" column="c_id"/&gt;
            &lt;result property="cName" column="c_name"/&gt;
            &lt;result property="tId" column="t_id"/&gt;
            &lt;result property="academy" column="c_academy"/&gt;
            &lt;result property="note" column="c_note"/&gt;
        &lt;/collection&gt;
    &lt;/resultMap&gt;


    &lt;select id="findAll" resultMap="studentMap1"&gt;
        select * from student s
        left join score sc on s.id = sc.s_id
        left join course c on sc.c_id = c.c_id
    &lt;/select&gt;

    &lt;select id="findById" resultMap="studentMap1"&gt;
        select * from student s
        left join score sc on s.id = sc.s_id
        left join course c on sc.c_id = c.c_id
        where s.id=#{id}
    &lt;/select&gt;


    &lt;resultMap id="studentMap2" type="studentInfo"&gt;
        &lt;id property="id" column="id"/&gt;
        &lt;result property="name" column="name"/&gt;
        &lt;result property="age" column="age"/&gt;
        &lt;result property="gender" column="gender"/&gt;
        &lt;result property="cid" column="c_id"/&gt;
        &lt;result property="cName" column="c_name"/&gt;
        &lt;result property="score" column="score"/&gt;
    &lt;/resultMap&gt;
    &lt;select id="findAllStudentInfo" resultMap="studentMap2"&gt;
        select s.id,s.name,s.age,s.gender,c.c_id,c.c_name,score
        from student s
        left join score sc on s.id = sc.s_id
        left join course c on sc.c_id = c.c_id
    &lt;/select&gt;
&lt;/mapper&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/zhaogodzero/article/details/142500262</p>]]></description><guid isPermaLink="false">/archives/2266fb16-61cc-43ec-af13-78d053a80268</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:23:22 GMT</pubDate></item><item><title><![CDATA[精通Maven：多模块项目中的依赖管理]]></title><link>https://wiki.dgfygk.com/archives/110fd7c0-dba7-4b44-b627-c22735eb7203</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%B2%BE%E9%80%9AMaven%EF%BC%9A%E5%A4%9A%E6%A8%A1%E5%9D%97%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%9A%84%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86&amp;url=/archives/110fd7c0-dba7-4b44-b627-c22735eb7203" width="1" height="1" alt="" style="opacity:0;">
<p>Maven是一个强大的项目管理和构建工具，但它的真正力量在于其依赖管理能力。在本文中，我将分享一些关键步骤和最佳实践，以及一些高级技巧，帮助你掌握Maven多模块项目中的依赖管理。</p>
<h3>Maven多模块项目基础</h3>
<p>在深入讨论之前，让我们先了解什么是Maven多模块项目。Maven项目可以被组织成多个模块，每个模块都是一个独立的Maven项目，它们共同构成一个更大的项目。这种结构有助于将大型项目分解为更小、更易于管理的部分。</p>
<h4>定义模块</h4>
<p>每个模块都在自己的子目录中，并且有自己的<code>pom.xml</code>文件。父POM定义了公共配置，而子模块继承这些配置。</p>
<pre><code>&lt;parent&gt;
    &lt;groupId&gt;com.example&lt;/groupId&gt;
    &lt;artifactId&gt;parent-project&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
&lt;/parent&gt;
&lt;modules&gt;
    &lt;module&gt;module-a&lt;/module&gt;
    &lt;module&gt;module-b&lt;/module&gt;
&lt;/modules&gt;
</code></pre>
<h3>管理模块间依赖</h3>
<h4>1. 使用<code>parent</code> POM</h4>
<p><code>parent</code> POM是多模块项目的核心，它包含了所有子模块共有的配置。通过在<code>parent</code> POM中使用<code>&lt;dependencyManagement&gt;</code>标签，你可以统一管理依赖版本。</p>
<h4>2. 模块间依赖</h4>
<p>在子模块的<code>pom.xml</code>中，你可以声明对其他子模块的依赖。由于所有模块都继承自同一个<code>parent</code> POM，它们共享依赖管理。</p>
<pre><code>&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.example&lt;/groupId&gt;
        &lt;artifactId&gt;module-a&lt;/artifactId&gt;
        &lt;version&gt;${project.version}&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<h4>3. 避免循环依赖</h4>
<p>循环依赖是一个常见的问题，它发生在两个或多个模块相互依赖对方时。这可能导致构建失败。设计模块时，要确保没有循环依赖。</p>
<h4>4. 使用<code>dependency:tree</code>命令</h4>
<p>Maven的<code>dependency:tree</code>目标是一个非常有用的工具，它可以帮助你理解项目的依赖结构，并识别潜在的冲突。</p>
<pre><code>mvn dependency:tree
</code></pre>
<h4>5. 限定依赖范围</h4>
<p>通过明确指定依赖的范围（如<code>compile</code>、<code>provided</code>、<code>test</code>等），你可以控制依赖的传递性和最终的构建输出。</p>
<h4>6. 排除不必要的依赖</h4>
<p>使用<code>&lt;exclusions&gt;</code>标签来排除不需要的传递性依赖。</p>
<h3>高级技巧</h3>
<h4>1. Profiles和依赖</h4>
<p>Maven Profiles提供了一种灵活的方式来根据不同的环境（如开发、测试和生产）管理依赖。</p>
<pre><code>&lt;profiles&gt;
    &lt;profile&gt;
        &lt;id&gt;production&lt;/id&gt;
        &lt;dependencies&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;com.example&lt;/groupId&gt;
                &lt;artifactId&gt;production-library&lt;/artifactId&gt;
                &lt;version&gt;1.0&lt;/version&gt;
            &lt;/dependency&gt;
        &lt;/dependencies&gt;
    &lt;/profile&gt;
&lt;/profiles&gt;
</code></pre>
<h4>2. 依赖归一化</h4>
<p>Maven 3.5.0+引入了依赖归一化，它可以帮助解决依赖冲突问题，确保项目中使用的是兼容的依赖版本。</p>
<h4>3. 反应式构建</h4>
<p>Maven 3.5.0+引入了反应式构建，它可以并行构建模块，从而加快构建速度。</p>
<h4>4. 依赖分析工具</h4>
<p>使用如OWASP Dependency Check等工具来分析依赖的安全性，及时发现潜在的安全问题。</p>
<h4>5. 私有依赖仓库</h4>
<p>对于需要在多个项目中共享的特定依赖，设置一个私有的Maven仓库（如Nexus或Artifactory）可以提高构建的效率和一致性。</p>
<h3>结论</h3>
<p>有效的依赖管理是任何Maven项目成功的关键。通过使用<code>parent</code> POM、避免循环依赖、使用<code>dependency:tree</code>命令、限定依赖范围和排除不必要的依赖，你可以确保你的多模块项目是健康的。此外，利用Maven的高级特性，如Profiles、依赖归一化、反应式构建和依赖分析工具，可以进一步提高你的项目的可维护性和稳定性。记住，良好的依赖管理不仅仅是技术问题，它也是项目管理的一部分。</p>
<p>原文链接: https://blog.csdn.net/apple_64847327/article/details/142468542</p>]]></description><guid isPermaLink="false">/archives/110fd7c0-dba7-4b44-b627-c22735eb7203</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:23:07 GMT</pubDate></item><item><title><![CDATA[如何用ChatGPT制作一款手机游戏应用]]></title><link>https://wiki.dgfygk.com/archives/ab4ccd0a-5da3-43d3-b357-45c9b9e3f504</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E7%94%A8ChatGPT%E5%88%B6%E4%BD%9C%E4%B8%80%E6%AC%BE%E6%89%8B%E6%9C%BA%E6%B8%B8%E6%88%8F%E5%BA%94%E7%94%A8&amp;url=/archives/ab4ccd0a-5da3-43d3-b357-45c9b9e3f504" width="1" height="1" alt="" style="opacity:0;">
<p>有没有想过自己做一款手机游戏，并生成apk手机应用呢？有了人工智能，这一切就成为可能。今天，我们就使用ChatGPT来创建一个简单的井字棋游戏（Tic-Tac-Toe），其实这个过程非常轻松且高效。</p>
<p>通过ChatGPT自动生成网页代码，并实现一些基本功能，比如更换背景颜色、显示谁是赢家、添加重新开始按钮等。下面就来跟大家分享一下详细步骤，教你如何在几分钟内打造一个属于自己的井字棋手机应用。</p>
<h3>一、利用ChatGPT生成井字棋代码</h3>
<h4><strong>1. 确定需求</strong></h4>
<p>我们的目标是创建一个井字棋游戏，并为它添加一些额外的功能，比如背景颜色渐变、胜利弹窗和重新开始按钮。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F5efc614f818c7d82e9f00e1e9585f0c3.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>井字棋游戏手机应用</p>
<h4><strong>2. 编写提示词</strong></h4>
<p>在使用ChatGPT时，我们可以输入明确的提示来生成代码。比如：</p>
<blockquote>
 <p>创建一个井字棋游戏，背景添加线性渐变颜色，弹窗显示谁赢了，并在游戏结束时提供重新开始按钮。</p>
</blockquote>
<p>ChatGPT会基于这个提示词生成完整的HTML、CSS和JavaScript代码。</p>
<p>这里index.html页是程序前端，以网页形式呈现，style.css是规定网页上各组件的排列样式， JavaScript的是游戏的内在逻辑，包括变色弹窗等。</p>
<h4><strong>3. 复制代码</strong></h4>
<p>一旦ChatGPT返回了生成的代码，我们可以直接复制这些代码，准备粘贴到开发工具CodeOpen里进行调试。</p>
<h3>二、测试井字棋代码</h3>
<h4><strong>1. 调试代码</strong></h4>
<p>打开一个代码编辑工具，比如CodeOpen，把刚刚从ChatGPT获得的代码按照html, CSS和JavaScript顺序，分别贴到三个不同的框里面。</p>
<p>然后点击运行按钮，或者拖动下方页面，看看你的井字棋游戏是否能正常显示和运作。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa14b5bf0b303ac25b4a4ed8eba4d4782.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>CodeOpen中粘贴代码</p>
<h4><strong>2. 功能添加</strong></h4>
<p>在运行代码后，还想继续对游戏的界面改进，添加新的功能，比如添加网页的渐变背景颜色，增加游戏结束时的弹窗提示，显示谁是赢家。</p>
<p>如果想实现这些功能，还可以返回ChatGPT的提示窗口，继续追加提示词，添加更多功能。</p>
<p>通过重新生成代码并替换旧代码，再次复制到CodeOpen中进行调试，就可以实现新代码的运行。如果报错，可以把错误的信息喂给ChatGPT，它会自动debug，并重新生成代码。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F80ca92a9bfbfea83bc27a5de25e3999a.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>添加新功能</p>
<h3>三、保存井字棋代码文件</h3>
<h4><strong>1. 保存HTML文件</strong></h4>
<p>如果调试代码发现无误后，就可以保存Html代码了。我们打开一个文本编辑器（如Notepad），将生成的HTML代码粘贴进去，然后将文件txt的扩展名改为index.html，这样操作是为了在访问网站域名时，会自动读取这个index命名的文件，并展示程序的界面。保存文件时，注意保存格式(html扩展名)正确，并保证这里文件编码为utf-8格式，以防止网页乱码。</p>
<h4><strong>2. 保存CSS和JavaScript文件</strong></h4>
<p>同样，你需要分别保存CSS和JavaScript代码，文件名分别为style.css和scripts.js，注意这三个文件要放在同一目录下面，这样不仅可以方便调用样式和js代码，而且可以确保井字棋游戏的样式和内在逻辑的正常运行。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F3906867fff99f66ca814da65403c2543.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>保存代码</p>
<h3>四、在tiny.host上测试程序</h3>
<p>我们打开这个tiny.host网站，新建一个域名mini2mini.tiny.host，然后把之前的三个文件打包成zip压缩包，并上传到网站上，这样一个在线游戏网站就出炉了。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ff4b5ddbe5f603a4034101e3097dfb23e.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>上传程序文件</p>
<p>接着我们就可以访问：mini2mini.tiny.host，看看我们的游戏网页是否能够运行成功了。注意验证前，需要输入你的谷歌账户。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fe95e10fa2500ad3d5ac24187f4d347b5.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>验证网页小游戏是否成功运行</p>
<h3>五、将井字棋代码转化为手机应用</h3>
<h4><strong>1. 压缩文件</strong></h4>
<p>将刚才保存的HTML，CSS和JavaScript等所有代码文件放在一个文件夹中，并将该文件夹压缩为.zip格式。接下来，我们将使用一个在线工具：<a href="https://link.zhihu.com/?target=http%3A//webintoapp.com" title="webintoapp.com">webintoapp.com</a> 来将这些在线网站打包成安卓设备可以安装运行的APK文件。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F7c7e4793980a8b52155e1f4d8a9d0b76.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>配置应用界面</p>
<h4><strong>2. 生成APK文件</strong></h4>
<p>我们通过注册<a href="https://link.zhihu.com/?target=http%3A//webintoapp.com" title="http://webintoapp.com">http://webintoapp.com</a>这个网站，输入刚才的游戏名称、网址地址，设置一个程序小图标，注意这里是icon文件。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9ebd513f9963ab3e83fed98e873142d3.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>生成apk文件</p>
<p>完成后，你就可以点击makeapp按钮，在线生成一个APK文件的zip压缩包。把它下载解压后，我们找到Android这个文件夹，把里面的app-release.apk这个文件传输到我们的安卓设备上进行安装。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F346add26b4cc808290639c0e67d42498.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>找到app-release文件</p>
<h3>五、安装并测试井字棋应用</h3>
<p>安装好我们生成的应用程序后，打开手机上的"井字棋"应用图标，进入游戏界面进行测试，检查背景颜色、输赢弹窗和重新开始按钮是否正常工作。</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fd17f1872b10978125dccfc39a1f0ac45.webp%3Fx-oss-process%3Dimage%2Fformat%2Cpng&amp;size=m" alt=""></p>
<p>手机测试程序</p>
<h3><strong>六、结语</strong></h3>
<p>通过ChatGPT生成代码，你可以在短短几分钟内创建出一个简单的井字棋网站，并把网站转化为一个手机应用，不用花一分钱就实现了一个全新手机应用的制作。</p>
<p>有了ChatGPT等人工智能的加持，从代码生成到实际运行的每一步都变得异常容易，无论你是编程新手还是有经验的开发者，都可以轻松上手。</p>
<p>现在，就利用ChatGPT制作属于自己的小游戏！如果不知道做什么游戏，就去问问ChatGPT吧。</p>
<p>原文链接: https://blog.csdn.net/henanlion/article/details/142442952</p>]]></description><guid isPermaLink="false">/archives/ab4ccd0a-5da3-43d3-b357-45c9b9e3f504</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 28 Sep 2024 07:22:49 GMT</pubDate></item><item><title><![CDATA[Java中的Junit、类加载时机与机制、反射、注解及枚举]]></title><link>https://wiki.dgfygk.com/archives/5b3505e5-2b4c-46c9-9ed9-8d026723ab88</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E7%9A%84Junit%E3%80%81%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%97%B6%E6%9C%BA%E4%B8%8E%E6%9C%BA%E5%88%B6%E3%80%81%E5%8F%8D%E5%B0%84%E3%80%81%E6%B3%A8%E8%A7%A3%E5%8F%8A%E6%9E%9A%E4%B8%BE&amp;url=/archives/5b3505e5-2b4c-46c9-9ed9-8d026723ab88" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p><a href="https://wiki.dgfygk.com#Java%E4%B8%AD%E7%9A%84Junit%E3%80%81%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%97%B6%E6%9C%BA%E4%B8%8E%E6%9C%BA%E5%88%B6%E3%80%81%E5%8F%8D%E5%B0%84%E3%80%81%E6%B3%A8%E8%A7%A3%E5%8F%8A%E6%9E%9A%E4%B8%BE">Java中的Junit、类加载时机与机制、反射、注解及枚举</a></p>
<p><a href="https://wiki.dgfygk.com#Junit">Junit</a></p>
<p><a href="https://wiki.dgfygk.com#Junit%E4%BB%8B%E7%BB%8D%E4%B8%8E%E4%BD%BF%E7%94%A8">Junit介绍与使用</a></p>
<p><a href="https://wiki.dgfygk.com#Junit%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9">Junit注意事项</a></p>
<p><a href="https://wiki.dgfygk.com#Junit%E5%85%B6%E4%BB%96%E6%B3%A8%E8%A7%A3">Junit其他注解</a></p>
<p><a href="https://wiki.dgfygk.com#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%97%B6%E6%9C%BA%E4%B8%8E%E6%9C%BA%E5%88%B6">类加载时机与机制</a></p>
<p><a href="https://wiki.dgfygk.com#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%97%B6%E6%9C%BA">类加载时机</a></p>
<p><a href="https://wiki.dgfygk.com#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E4%BB%8B%E7%BB%8D">类加载器介绍</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%8E%B7%E5%8F%96%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%AF%B9%E8%B1%A1">获取类加载器对象</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6%E5%92%8C%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6">双亲委派机制和缓存机制</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8F%8D%E5%B0%84">反射</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%8E%B7%E5%8F%96%E7%B1%BB%E5%AF%B9%E8%B1%A1">获取类对象</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%8E%B7%E5%8F%96%E7%B1%BB%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95">获取类对象的构造方法</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BD%BF%E7%94%A8%E5%8F%8D%E5%B0%84%E8%8E%B7%E5%8F%96%E7%9A%84%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1">使用反射获取的构造方法创建对象</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%8E%B7%E5%8F%96%E7%B1%BB%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%88%90%E5%91%98%E6%96%B9%E6%B3%95">获取类对象的成员方法</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BD%BF%E7%94%A8%E5%8F%8D%E5%B0%84%E8%8E%B7%E5%8F%96%E7%9A%84%E6%88%90%E5%91%98%E6%96%B9%E6%B3%95">使用反射获取的成员方法</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%8E%B7%E5%8F%96%E7%B1%BB%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%88%90%E5%91%98%E5%B1%9E%E6%80%A7">获取类对象的成员属性</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BD%BF%E7%94%A8%E5%8F%8D%E5%B0%84%E8%8E%B7%E5%8F%96%E7%9A%84%E6%88%90%E5%91%98%E5%B1%9E%E6%80%A7">使用反射获取的成员属性</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%8F%8D%E5%B0%84%E5%AE%9E%E7%94%A8%E6%A1%88%E4%BE%8B">反射实用案例</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%B3%A8%E8%A7%A3">注解</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BB%8B%E7%BB%8D">介绍</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3%E5%92%8C%E5%B1%9E%E6%80%A7">定义注解和属性</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%B3%A8%E8%A7%A3%E7%9A%84%E4%BD%BF%E7%94%A8">注解的使用</a></p>
<p><a href="https://wiki.dgfygk.com#%E8%A7%A3%E6%9E%90%E6%B3%A8%E8%A7%A3">解析注解</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%85%83%E6%B3%A8%E8%A7%A3">元注解</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%9E%9A%E4%B8%BE">枚举</a></p>
<p><a href="https://wiki.dgfygk.com#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8">基本使用</a></p>
<p><a href="https://wiki.dgfygk.com#%E6%9E%9A%E4%B8%BE%E4%B8%AD%E7%9A%84%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95">枚举中的常用方法</a></p>
<hr>
<p>
 <br>
</p>
<h2>Java中的Junit、类加载时机与机制、反射、注解及枚举</h2>
<h3>Junit</h3>
<h4>Junit介绍与使用</h4>
<p>在Java中，Junit是一个单元测试框架,可以代替<code>main</code>方法去执行其他的方法，其可以单独执行一个方法,测试该方法是否能跑通</p>
<p>因为Junit是一个第三方工具包，与Lombok一样需要先导入对应的jar包并进行解压，不同的是，Junit有两个包，并且相互依赖，所以都需要进行解压。这里可以使用一个文件夹名为<code>lib</code>，将两个解压包放在<code>lib</code>文件下，再对<code>lib</code>文件夹进行整体Add as Library</p>
<p>使用时，在需要进行测试的方法上方写上<code>@Test</code>注解，此时就会在对应方法所在行左侧显示运行按钮，点击即可运行对应的方法而不会执行其他方法</p>
<p>例如下面的代码：</p>
<pre><code class="java">public class Test01 {
    @Test
    public void method() {
        System.out.println("测试Junit");
    }
}
</code></pre>
<p>一个类中可以有多个有<code>@Test</code>方法：</p>
<pre><code class="java">public class Test01 {
    @Test
    public void method() {
        System.out.println("测试Junit");
    }

    @Test
    public void method01() {
        System.out.println("测试Junit-2");
    }
}
</code></pre>
<p>如果有多个<code>@Test</code>方法，想要运行所有的<code>@Test</code>方法，就可以点击类名所在行的运行按钮</p>
<h4>Junit注意事项</h4>
<ol>
 <li><code>@Test</code>不能修饰静态方法</li>
 <li><code>@Test</code>不能修饰带参数的方法</li>
 <li><code>@Test</code>不能修饰带返回值的方法</li>
</ol>
<p>例如：</p>
<pre><code class="java">public class Test01 {
    @Test
    public static void method02() {
        System.out.println("测试Junit-3");
    }
}

// 报错：Method 'method02' annotated with '@Test' should be non-static 
</code></pre>
<h4>Junit其他注解</h4>
<ol>
 <li><code>@Before</code>：被<code>@Before</code>修饰的方法会在每一个<code>@Test</code>方法执行前执行，但是本身不可以单独执行。特点：有多个<code>@Test</code>修饰的方法时会执行多次</li>
 <li><code>@After</code>：被<code>@Before</code>修饰的方法会在每一个<code>@Test</code>方法执行后执行，但是本身不可以单独执行。特点：有多个<code>@Test</code>修饰的方法时会执行多次</li>
 <li><code>@BeforeClass</code>：被<code>@BeforeClass</code>修饰的方法会在所有<code>@Test</code>方法执行前执行，但是本身不可以单独执行。特点：不论是否有多个<code>@Test</code>修饰的方法，该方法始终只会执行一次，并且被修饰的方法必须为静态方法</li>
 <li><code>@AfterClass</code>：被<code>@AfterClass</code>修饰的方法会在所有<code>@Test</code>方法执行后执行，但是本身不可以单独执行。特点：不论是否有多个<code>@Test</code>修饰的方法，该方法始终只会执行一次，并且被修饰的方法必须为静态方法</li>
</ol>
<p>例如：</p>
<pre><code class="java">public class Test01 {
    @Test
    public void method() {
        System.out.println("测试Junit");
    }

    @Test
    public void method01() {
        System.out.println("测试Junit-2");
    }


    @Before
    public void before() {
        System.out.println("before");
    }

    @After
    public void after() {
        System.out.println("after");
    }

    @BeforeClass
    public static void beforeClass() {
        System.out.println("beforeClass");
    }

    @AfterClass
    public static void afterClass() {
        System.out.println("afterClass");
    }
}

输出结果：
beforeClass
before
测试Junit
after
before
测试Junit-2
after
afterClass
</code></pre>
<h3>类加载时机与机制</h3>
<h4>类加载时机</h4>
<p>在Java中，以下五种情况中的一种情况出现，JVM就会将<code>class</code>文件加载进入内存：</p>
<ol>
 <li>实例化对象</li>
 <li>实例化子类对象或者接口实现类对象（此时创建子类对象会初始化父类）</li>
 <li>执行<code>main</code>方法</li>
 <li>直接使用类名调用静态成员</li>
 <li>使用反射创建<code>class</code>对象</li>
</ol>
<p>因为<code>class</code>也属于文件，所以JVM也需要使用IO流中的操作将<code>class</code>文件读取到内存，但并不是JVM直接加载，而是使用一个名为<code>ClassLoader</code>的类加载器进行加载</p>
<h4>类加载器介绍</h4>
<blockquote>
 <p>下面的类加载器将基于JDK8分析</p>
</blockquote>
<p>JVM会使用<code>ClassLoader</code>的类加载器加载类，在Java中，类加载器有以下三种：</p>
<ol>
 <li><code>BootStrapClassLoader</code>：根类加载器，也称为引导类加载器，主要加载Java中的核心类，例如<code>System</code>类、<code>String</code>类等（在路径<code>jre/lib/rt.jar</code>下）</li>
 <li><code>ExtClassLoader</code>：扩展类加载器，负责<code>jre</code>的扩展目录中的<code>jar</code>包的加载（在路径<code>jdk\jre\lib\ext</code>下）</li>
 <li><code>AppClassLoader</code>：系统类加载器，负责在JVM启动时加载来自Java命令的<code>class</code>文件（可以理解为自定义类），以及<code>classPath</code>环境变量所指定的<code>jar</code>包（可以理解为第三方<code>jar</code>包）</li>
</ol>
<p>在Java中，三种不同的类加载器会加载不同的类，而其三者的关系「从类加载机制层面」如下：</p>
<p><code>AppClassLoader</code>的父类加载器是<code>ExtClassLoader</code></p>
<p><code>ExtClassLoader</code>的父类加载器是<code>BootStrapClassLoader</code></p>
<blockquote>
 <p>需要注意，他们从代码级别上来看，没有子父类继承关系，但是他们都有一个共同的父类，即 <code>ClassLoader</code>
  <br>
  并且 <code>BootStrapClassLoader</code>是无法直接获取到的，因为其底层是C语言编写的</p>
</blockquote>
<pre><code class="java">// AppClassLoader类声明
public class AppletClassLoader extends URLClassLoader
// ExtClassLoader类声明
static class ExtClassLoader extends URLClassLoader

// 拥有共同的父类
public class URLClassLoader extends SecureClassLoader implements Closeable
public class SecureClassLoader extends ClassLoader
</code></pre>
<h4>获取类加载器对象</h4>
<p>在Java代码中，可以通过下面的两个方法获取类加载器对象：</p>
<ol>
 <li><code>Class</code>对象中的方法：<code>getClassLoader()</code>，使用时：<code>类名.class.getClassLoader()</code>，作用是获取某一个使用的类加载器</li>
 <li><code>ClassLoader</code>类中的方法：<code>getParent()</code>，使用时：<code>ClassLoader对象.getParent()</code>，作用是获取指定类加载器的父类加载器</li>
</ol>
<pre><code class="java">public class Test {
    public static void main(String[] args) {
        // 自定义类
        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println(classLoader);
        // 第三方类
        ClassLoader classLoader1 = IOUtil.class.getClassLoader();
        System.out.println(classLoader1);
        // 获取二者的父类
        System.out.println(classLoader.getParent());
        if (classLoader1 != null) {
            System.out.println(classLoader1.getParent());
        } else {
            System.out.println("父类加载器无法获取");
        }
        // 系统类
        ClassLoader classLoader2 = Scanner.class.getClassLoader();
        System.out.println(classLoader2);
    }
}

输出结果：
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@4554617c
父类加载器无法获取
null
</code></pre>
<h4>双亲委派机制和缓存机制</h4>
<p>双亲委派机制，也称为全盘负责委托机制，表示类加载器在加载类时，首先不会自己去尝试加载这个类，而是把请求交给父类加载器来完成，每一层的类加载器都会遵循这样的规则。</p>
<p>类加载器的缓存机制：一个类加载到内存之后，缓存中也会保存一份儿，后面如果再使用此类，如果缓存中保存了这个类，就直接返回他；如果没有才加载这个类，下一次如果有其他类在使用的时候就不会重新加载了，直接去缓存中拿</p>
<p>类加载器加载类的过程图如下：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F1fd5be48287e957408c7d85f9d5d248f.png&amp;size=m" alt=""></p>
<p>图解析：</p>
<p>一个类准备加载进内存时，首先遇到的类加载器就是<code>AppClassLoader</code>，如果<code>AppClassLoader</code>中的缓存已经存在该类，则直接返回该类，否则向上找类加载器<code>ExtClassLoader</code>，同样，如果<code>ExtClassLoader</code>的缓存已经存在该类，则直接返回，否则向上找类加载器<code>BootStrapClassLoader</code>，如果<code>BootStrapClassLoader</code>缓存依旧没有该类，则判断类是否是该类加载需要加载的，不是继续向下直到遇到属于加载该类的类加载器</p>
<p>所以，类加载器的双亲委派和缓存机制共同造就了加载类的特点:保证了类在内存中的唯一性</p>
<h3>反射</h3>
<p>在Java中，反射的作用是获取类对象，通过这个类对象获取对应类中的成员属性（重新赋值）、成员方法（调用方法）和构造方法（实例化对象）</p>
<p>类对象：在Java中，一切皆是对象，而<code>class</code>文件加载进内存就会生成对应的对象，该对象就被称为<code>class</code>对象</p>
<p><code>Class</code>类：描述<code>Class</code>对象就是<code>Class</code>类</p>
<p>同样，对于成员变量、成员方法和构造方法来说也有对应的对象和类：</p>
<ol>
 <li>成员变量：对应的成员变量对象为<code>Field</code>对象，描述<code>Field</code>对象的类称为<code>Field</code>类</li>
 <li>成员方法：对应的成员方法对象为<code>Method</code>对象，描述<code>Method</code>对象的类称为<code>Method</code>类</li>
 <li>构造方法：对应的构造方法对象为<code>Constructor</code>对象，描述<code>Constructor</code>对象的类称为<code>Constructor</code>类</li>
</ol>
<h4>获取类对象</h4>
<p>获取类对象是反射成立的第一步，而一共有三种方式获取类对象：</p>
<ol>
 <li>调用<code>Object</code>中的方法：<code>Class &lt;?&gt; getClass()</code>，该方法返回一个类对象</li>
 <li>通过<code>class</code>成员获取类对象：<code>基本数据类型/引用数据类型.class</code></li>
 <li><code>Class</code>类中的静态方法：<code>static Class&lt;?&gt; forName(String className)</code>，该方法的参数为类的全限定名，该方法返回一个类对象</li>
</ol>
<blockquote>
 <p>类的全限定名即为类所在的包名，即 <code>package</code>后面的内容+指定类名</p>
</blockquote>
<p>例如，下面的代码：</p>
<pre><code class="java">public class Test {
    public static void main(String[] args) throws Exception{
        // 1. 调用Object中的方法：Class &lt;?&gt; getClass()，该方法返回一个类对象
        Scanner scanner = new Scanner(System.in);
        Class&lt;? extends Scanner&gt; aClass = scanner.getClass();
        System.out.println(aClass);
        // 2. 通过class成员获取类对象：基本数据类型/引用数据类型.class
        Class&lt;Scanner&gt; scannerClass = Scanner.class;
        System.out.println(scannerClass);
        // 3. Class类中的静态方法：static Class&lt;?&gt; forName(String className)，该方法的参数为类的全限定名，该方法返回一个类对象
        Class&lt;?&gt; aClass1 = Class.forName("com.epsda.advanced.test_reflect.Test");
        System.out.println(aClass1);
    }
}

输出结果：
class java.util.Scanner
class java.util.Scanner
class com.epsda.advanced.test_reflect.Test
</code></pre>
<p>在IDEA中获取类的全限定名：</p>
<ol>
 <li>右键需要全限定名的类-&gt;选择<code>Copy Path/Reference...</code>-&gt;选择<code>Copy Reference</code></li>
 <li>在<code>forName</code>方法中输入需要全限定名的类名，按下<code>Tab</code>或者<code>Enter</code></li>
</ol>
<blockquote>
 <p>如果按住Ctrl+鼠标左键点击类的全限定名可以跳转到指定类时，说明全限定名正确</p>
</blockquote>
<p>在实际开发中最常用的获取类对象的方式是第二种，但是最通用的方式是第三种，因为第三种的参数是<code>String</code>类型，后面可以结合配置文件<code>xxx.properties</code>和<code>Properties</code>集合中的<code>load</code>方法加载类的全限定名</p>
<h4>获取类对象的构造方法</h4>
<p>获取类对象的构造方法一共有四种方法：</p>
<ol>
 <li>获取类对象中所有<code>public</code>构造方法：使用<code>Class</code>类中的方法：<code>Constructor&lt;?&gt;[] getConstructors()</code>，该方法返回一个构造方法类的对象</li>
 <li>获取类对象中指定的<code>public</code>构造方法：<code>Class</code>类中的方法：<code>Constructor&lt;T&gt; getConstructor (Class&lt;?&gt;... parameterTypes)</code>，该方法的参数为指定的<code>public</code>构造方法参数类型对应的类对象，返回一个构造方法类的对象。如果指定的<code>public</code>构造方法没有参数，则可以不传递任何内容</li>
 <li>获取类对象中所有构造方法（包括<code>public</code>和<code>private</code>）：<code>Constructor&lt;?&gt;[] getDeclaredConstructors()</code>，该方法返回一个构造方法类的对象</li>
 <li>获取类对象中指定的构造方法（包括<code>public</code>和<code>private</code>）：<code>Constructor&lt;T&gt; getDeclaredConstructor (Class&lt;?&gt;... parameterTypes)</code>，该方法的参数为指定的构造方法的参数，返回一个构造方法类的对象。如果指定的构造方法没有参数，则可以不传递任何内容</li>
</ol>
<p>下面示例使用的类：</p>
<pre><code class="java">@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    public Integer age;

    private Person(String name) {
        this.name = name;
    }
}
</code></pre>
<p>使用如下：</p>
<pre><code class="java">public class Test01 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;

        // 获取所有public构造方法
        Constructor&lt;?&gt;[] constructors = personClass.getConstructors();
        for (Constructor&lt;?&gt; constructor : constructors) {
            System.out.println(constructor);
        }

        // 获取指定public构造方法
        Constructor&lt;Person&gt; constructor = personClass.getConstructor(String.class, Integer.class);
        System.out.println(constructor);

        // 获取所有构造方法
        Constructor&lt;?&gt;[] declaredConstructors = personClass.getDeclaredConstructors();
        for (Constructor&lt;?&gt; declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

        // 获取指定的构造方法
        Constructor&lt;Person&gt; declaredConstructor = personClass.getDeclaredConstructor(String.class);
        System.out.println(declaredConstructor);
    }
}
</code></pre>
<h4>使用反射获取的构造方法创建对象</h4>
<p>使用<code>Constructor</code>类中的方法： <code>T newInstance(Object...initargs)</code>，参数传递对应对象初始值，如果获取到的是无参构造，则参数不传递，否则传递对应的值，该方法返回一个对应类的对象</p>
<pre><code class="java">public class Test02 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;
        Constructor&lt;Person&gt; declaredConstructor = personClass.getDeclaredConstructor(String.class, Integer.class);
        Person person = declaredConstructor.newInstance("张三", 16);
        System.out.println(person);
    }
}
</code></pre>
<p>如果获取到的是无参构造，则可以直接使用<code>类对象.newInstance()</code>，简写为如下的代码：</p>
<pre><code class="java">public class Test03 {
    public static void main(String[] args) throws Exception{
        // 无参构造默认反射方式
        Class&lt;Person&gt; personClass = Person.class;
        Constructor&lt;Person&gt; constructor = personClass.getConstructor();
        Person person = constructor.newInstance();
        System.out.println(person);

        // 简写为
        Person person1 = personClass.newInstance();
        System.out.println(person);
    }
}
</code></pre>
<blockquote>
 <p>但是，上面的简写形式已经被弃用（修饰为 <code>@Deprecated</code>），不过依旧可以使用</p>
</blockquote>
<p>如果获取到的是私有构造方法，则需要使用<code>Constructor</code>的父类<code>AccessibleObject</code>中的方法<code>void setAccessible(boolean flag)</code>将私有构造方法的权限修改为<code>public</code>，参数有两个值：<code>true</code>代表修改，<code>false</code>表示不修改</p>
<pre><code class="java">public class Test04 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;
        Constructor&lt;Person&gt; personConstructor = personClass.getDeclaredConstructor(String.class);
        // 修改权限
        personConstructor.setAccessible(true);
        Person person = personConstructor.newInstance("张三");
        System.out.println(person);
    }
}
</code></pre>
<p>上面获取私有构造方法并创建对象也被称为暴力反射</p>
<h4>获取类对象的成员方法</h4>
<p>获取类对象的成员方法一共有四种方法：</p>
<ol>
 <li>获取类对象中所有<code>public</code>成员方法：使用<code>Class</code>类中的方法：<code>Method[] getMethods()</code>，该方法返回一个成员方法类的对象</li>
 <li>获取类对象中指定的<code>public</code>成员方法：<code>Class</code>类中的方法：<code>Method getMethod (String name, Class&lt;?&gt;... parameterTypes)</code>，该方法的第一个参数为指定的<code>public</code>成员方法名，第二个参数为指定的<code>public</code>成员方法参数类型对应的类对象，返回一个成员方法类的对象。如果指定的<code>public</code>成员方法没有参数，则第二个参数可以不传递任何内容</li>
 <li>获取类对象中所有成员方法（包括<code>public</code>、<code>private</code>和<code>protected</code>）：<code>Method[] getDeclaredMethods()</code>，该方法返回一个成员方法类的对象</li>
 <li>获取类对象中指定的成员方法（包括<code>public</code>和<code>private</code>）：<code>Method getDeclaredMethod (String name, Class&lt;?&gt;... parameterTypes)</code>，该方法的参数为指定的成员方法的参数，返回一个成员方法类的对象。如果指定的<code>public</code>成员方法没有参数，则第二个参数可以不传递任何内容</li>
</ol>
<blockquote>
 <p>需要注意， <code>getMethods</code>方法会获取到本类和其父类的所有 <code>public</code>方法，但是 <code>getDeclaredMethods</code>方法只会获取到本类中的 <code>private</code>、 <code>public</code>和 <code>protected</code>方法</p>
</blockquote>
<p>例如下面的代码：</p>
<pre><code class="java">public class Test05 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;
        // 获取所有public方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        // 获取指定的public方法
        Method method = personClass.getMethod("setName", String.class);
        System.out.println(method);

        // 获取所有的方法
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }

        // 获取指定的方法
        Method walk = personClass.getDeclaredMethod("walk");
        System.out.println(walk);
    }
}
</code></pre>
<h4>使用反射获取的成员方法</h4>
<p>使用成员方法类对象调用<code>Object</code>类中的方法<code>Object invoke(Object obj, Object... args)</code>可以使用获取到的成员方法，第一个参数传递成员方法所在类的对象，第二个参数传递获取到的成员方法参数对应的值，该方法返回一个<code>Object</code>对象。该方法的返回值根据调用对象对应的方法是否有返回值决定，如果调用对象对应的方法有返回值，则与调用对象对应的方法的值相同，否则为<code>null</code></p>
<pre><code class="java">public class Test06 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;
        Person person = personClass.newInstance();
        Method setName = personClass.getDeclaredMethod("setName", String.class);
        Method getName = personClass.getDeclaredMethod("getName");
        Object set = setName.invoke(person, "张三");
        Object get = getName.invoke(person);
        System.out.println(get);
    }
}
</code></pre>
<p>如果需要操作类对象中的私有方法，与私有构造方法一样，需要使用<code>AccessibleObject</code>中的方法<code>void setAccessible(boolean flag)</code>将私有构造方法的权限修改为<code>public</code></p>
<pre><code class="java">public class Test06 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;
        Person person = personClass.newInstance();

        Method declaredMethod = personClass.getDeclaredMethod("walk");
        declaredMethod.setAccessible(true);
        declaredMethod.invoke(person);
    }
}
</code></pre>
<h4>获取类对象的成员属性</h4>
<p>获取类对象的构造方法一共有四种方法：</p>
<ol>
 <li>获取类对象中所有<code>public</code>成员属性：使用<code>Class</code>类中的方法：<code>Field[] getFields()</code>，该方法返回一个成员属性类的对象</li>
 <li>获取类对象中指定的<code>public</code>成员属性：<code>Class</code>类中的方法：<code>Field getField(String name)</code>，该方法的参数为指定的<code>public</code>成员属性名，返回一个成员属性类的对象</li>
 <li>获取类对象中所有成员属性（包括<code>public</code>和<code>private</code>）：<code>Field[] getDeclaredFields()</code>，该方法返回一个成员属性类的对象</li>
 <li>获取类对象中指定的成员属性（包括<code>public</code>、<code>private</code>和<code>protected</code>）：<code>Field getDeclaredField(String name)</code>，该方法的参数为指定的<code>public</code>成员属性名，返回一个成员属性类的对象</li>
</ol>
<p>例如：</p>
<pre><code class="java">public class Test07 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;

        // 获取所有public成员属性
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        // 获取指定的public成员属性
        Field age = personClass.getField("age");
        System.out.println(age);

        // 获取所有成员属性
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }

        // 获取指定成员属性
        Field declaredField = personClass.getDeclaredField("name");
        System.out.println(declaredField);
    }
}
</code></pre>
<h4>使用反射获取的成员属性</h4>
<p>使用成员属性类对象调用<code>Object</code>中的方法：<code>void set(Object obj, Object value)</code>为获取到的成员属性赋值，第一个参数传递成员属性所在类的对象，第二个参数传递属性值</p>
<p>使用成员属性类对象调用<code>Object</code>中的方法：<code>Object get(Object obj)</code>获取指定成员属性的值，参数传递成员属性所在类的对象，该方法返回一个<code>Object</code>对象，该对象中的值即为成员属性的值</p>
<pre><code class="java">public class Test08 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;
        Person person = personClass.newInstance();
        Field age = personClass.getField("age");
        age.set(person, 18);
        Object o = age.get(person);
        System.out.println(o);
    }
}
</code></pre>
<p>同样，如果是私有成员，则需要使用<code>AccessibleObject</code>中的方法<code>void setAccessible(boolean flag)</code>将私有构造方法的权限修改为<code>public</code></p>
<pre><code class="java">public class Test08 {
    public static void main(String[] args) throws Exception{
        Class&lt;Person&gt; personClass = Person.class;
        Person person = personClass.newInstance();

        Field declaredField = personClass.getDeclaredField("name");
        declaredField.setAccessible(true);
        declaredField.set(person, "张三");
        Object o1 = declaredField.get(person);
        System.out.println(o1);
    }
}
</code></pre>
<h4>反射实用案例</h4>
<p>在配置文件中，配置类的全限定名，以及配置一个方法名，通过解析配置文件，让配置好的方法执行起来，配置文件的内容如下：</p>
<pre><code class="java">className=包名.Person
methodName=walk
</code></pre>
<p>步骤:</p>
<ol>
 <li>创建<code>properties</code>配置文件，配置信息，需要注意，这个配置文件不能直接放到模块或者项目下，否则在<code>out</code>文件夹中不存在该文件，最常见的做法是在指定目录下创建一个名为<code>resources</code>文件夹，并将这个文件夹标记为<code>Resources Root</code>，然后将配置文件放在这个文件夹内部</li>
 <li>读取配置文件，解析配置文件。读取配置文件可以使用<code>properties</code>集合中的<code>load</code>方法，解析配置文件时不建议直接在创建IO流对象时传递<code>properties</code>文件的地址，这样导致该地址为死地址，而且out文件夹下不会存在<code>resources</code>文件夹。推荐方法：因为配置文件也属于文件，在Java中加载该文件时会产生对应的对象，使用<code>ClassLoader</code>获取当前类的类加载器对象，再使用该对象调用<code>getResourceAsStream ("配置文件名")</code>方法获取<code>InputStream</code>对象。这种方式会自动扫描<code>resources</code>下的文件，可以简单理解为扫描<code>out</code>路径下的配置文件</li>
 <li>根据解析出来的<code>className</code>，创建<code>Class</code>对象</li>
 <li>根据解析出来的<code>methodName</code>，获取对应的方法</li>
 <li>执行方法</li>
</ol>
<p>IDEA中「将这个文件夹标记为<code>Resources Root</code>」的步骤图：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F998becb007c62d2d2fb126389a1107d0.png&amp;size=m" alt=""></p>
<p>如果上面的方式中没有显示<code>Resources Root</code>，则可以考虑下面的步骤：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F88de1ddad2889ac6904087e9d54cd47e.png&amp;size=m" alt=""></p>
<p>参考代码如下：</p>
<pre><code class="java">// 配置文件
className=com.epsda.advanced.test_reflect_exercise.Person
methodName=walk

// 自定义类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;

    public void walk() {
        System.out.println("人在行走");
    }
}

// 测试
package com.epsda.advanced.test_reflect_exercise;

import org.junit.Test;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * ClassName: Test09
 * Description: 测试
 *
 * @author 憨八嘎
 * @version 1.0
 */
public class Test09 {
    @Test
    public void method () throws Exception{
        ClassLoader classLoader = Test09.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream("test.properties");
        // 读取配置文件
        Properties properties = new Properties();
        properties.load(resourceAsStream);
        // 根据key取出其中的值
        String methodName = properties.getProperty("methodName");
        String className = properties.getProperty("className");
        // 创建Class对象
        Class&lt;?&gt; aClass = Class.forName(className);
        Object o = aClass.newInstance();
        Method declaredMethod = aClass.getDeclaredMethod(methodName);
        declaredMethod.invoke((Person)o);
    }
}

输出结果：
人在行走
</code></pre>
<p>目录结构：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fdb6b429520ae2a5e8b50d392c1322596.png&amp;size=m" alt=""></p>
<h3>注解</h3>
<h4>介绍</h4>
<p>在Java中，注解也是一种引用数据类型，与类、接口、枚举和<code>Record</code>类同层次</p>
<p>注解常见的作用如下：</p>
<ol>
 <li>说明:对代码进行说明，生成doc文档(API文档)</li>
 <li>检查:检查代码是否符合条件，例如：<code>@Override</code>、<code>@FunctionalInterface</code></li>
 <li>分析:对代码进行分析，起到了代替配置文件的作用</li>
</ol>
<p>JDK中常见的注解：</p>
<ol>
 <li><code>@Override</code>：检测此方法是否为重写方法</li>
 <li>JDK5版本,支持父类的方法重写</li>
 <li>JDK6版本,支持接口的方法重写</li>
 <li><code>@Deprecated</code>：表示方法已经过时，不推荐使用，但是依旧可以使用</li>
 <li><code>@SuppressWarnings</code>：消除警告，例如消除所有警告：<code>@SuppressWarnings("all")</code></li>
</ol>
<p>在IDEA中，一般被警告的方法默认会有黄色底色，例如下图：</p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F99b67cdf710e3a9f5f9edbd4cf47d42a.png&amp;size=m" alt=""></p>
<h4>定义注解和属性</h4>
<p>在Java中，可以使用下面的格式定义注解：</p>
<pre><code class="java">public @Interface 注解名 {
    // 属性
}
</code></pre>
<p>在注解体内的属性，本质是抽象方法，但是在使用时，与成员属性相同，使用<code>成员属性名=值</code>的方式</p>
<p>属性的定义有两种方式：</p>
<ol>
 <li><code>数据类型 属性名()</code>：定义一个没有默认值的属性，使用注解时就必须赋值</li>
 <li><code>数据类型 属性名() default 值</code>：定义一个有默认值的属性，使用注解时可以不需要赋值</li>
</ol>
<p>可以作为属性的类型：</p>
<ol>
 <li>所有基本数据类型</li>
 <li><code>String</code>类型</li>
 <li>枚举类型</li>
 <li>注解类型</li>
 <li><code>Class</code>类型</li>
 <li>上面所有类型的一维数组（不可以是二维数组）</li>
</ol>
<p>例如：</p>
<pre><code class="java">public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}
</code></pre>
<h4>注解的使用</h4>
<p>使用注解本质就是为每一个属性（抽象方法）赋值，一般使用位置有下面几种：</p>
<ol>
 <li>类名上</li>
 <li>方法上</li>
 <li>成员变量上</li>
 <li>局部变量上</li>
 <li>参数位置</li>
</ol>
<p>使用格式如下：</p>
<ol>
 <li>普通属性：<code>@注解名(属性名 = 值, 属性名 = 值...)</code></li>
 <li>属性中有数组：<code>@注解名(属性名 = {元素1,元素2...})</code></li>
</ol>
<p>注解使用时需要注意：</p>
<ol>
 <li>空注解（注解中没有任何的属性）可以直接使用</li>
 <li>不同的位置可以使用一样的注解，但是同样的位置不能使用一样的注解</li>
 <li>使用注解时，如果此注解中有属性没有默认值，则注解中对应的属性一定要赋值。如果有多个属性，用<code>,</code>隔开；如果注解中的属性值有默认值，那么不用显示写，也不用重新赋值</li>
 <li>如果注解中的属性有数组，使用<code>{}</code></li>
 <li>如果注解中只有一个属性，并且属性名叫<code>value</code>，那么使用注解的时候，属性名不用写，直接写值(包括普通类型和数组)</li>
</ol>
<p>例如：</p>
<pre><code class="java">// 自定义注解
public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}

// 测试
@Book(bookName = "寓言故事",author = {"张三","李四"},price = 10,count = 20)
public class BookShelf {
}
</code></pre>
<h4>解析注解</h4>
<p>解析注解即为取出注解对应属性的值，注解涉及的接口是：<code>AnnotatedElement</code>接口，实现类有： <code>AccessibleObject</code>、<code>Class</code>、<code>Constructor</code>、<code>Executable</code>、<code>Field</code>、<code>Method</code>、<code>Package</code>、<code>Parameter</code></p>
<p>解析思路如下：</p>
<ol>
 <li>判断指定位置上有没有使用指定的注解：使用方法：<code>boolean isAnnotationPresent(Class&lt;? extends Annotation&gt; annotationClass)</code>，如果存在注解则返回为<code>true</code>，否则返回<code>false</code></li>
 <li>如果有，则获取指定的注解：使用方法：<code>getAnnotation(Class&lt;T&gt; annotationClass)</code></li>
 <li>通过注解获取指定的值</li>
</ol>
<p>例如：</p>
<pre><code class="java">// 自定义注解
public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}

// 自定义类
@Book(bookName = "寓言故事",author = {"张三","李四"},price = 10,count = 20)
public class BookShelf {
}

// 测试
public class Test01 {
    public static void main(String[] args) {
        //1.获取BookShelf的class对象
        Class&lt;BookShelf&gt; bookShelfClass = BookShelf.class;
        //2.判断bookShelf上有没有Book注解
        boolean b = bookShelfClass.isAnnotationPresent(Book.class);
        //3.判断，如果b为true就获取
        if (b) {
            Book book = bookShelfClass.getAnnotation(Book.class);
            System.out.println(book.bookName());
            System.out.println(Arrays.toString(book.author()));
            System.out.println(book.price());
            System.out.println(book.count());
        }
    }
}
</code></pre>
<p>上面的代码没有在控制台中打印运行结果，原因是注解并没有在内存中出现，而<code>class</code>文件在内存中运行，所以导致方法无法获取到对应的注解</p>
<h4>元注解</h4>
<p>元注解也是注解，这个注解用来管理其他注解，一般管理下面的方面：</p>
<ol>
 <li>控制注解的使用位置</li>
 <li>控制注解是否能在类上使用</li>
 <li>控制注解是否能在方法上使用</li>
 <li>控制注解是否能在构造上使用等</li>
 <li>控制注解的生命周期（加载位置）</li>
 <li>控制注解是否能在源码中出现</li>
 <li>控制注解是否能在<code>class</code>文件中出现</li>
 <li>控制注解是否能在内存中出现</li>
</ol>
<p>使用元注解：</p>
<ol>
 <li>注解<code>@Target</code>：控制注解的使用位置，其属性是个枚举数组<code>ElementType[] value();</code>，枚举的成员可以类名直接调用。常见的成员有：</li>
 <li><code>TYPE</code>：控制注解能使用在类上</li>
 <li><code>FIELD</code>：控制注解能使用在属性上</li>
 <li><code>METHOD</code>：控制注解能使用在方法上</li>
 <li><code>PARAMETER</code>：控制注解能使用在参数上</li>
 <li><code>CONSTRUCTOR</code>：控制注解能使用在构造上</li>
 <li><code>LOCAL_VARIABLE</code>：控制注解能使用在局部变量上</li>
 <li>注解<code>@Retention</code>：控制注解的生命周期（加载位置），其属性是一个枚举对象<code>RetentionPolicy</code>，常见的成员有：</li>
 <li><code>SOURCE</code>:控制注解能在源码中出现（默认）</li>
 <li><code>CLASS</code>:控制注解能在<code>class</code>文件中出现</li>
 <li><code>RUNTIME</code>:控制注解能在内存中出现</li>
</ol>
<blockquote>
 <p>需要注意， <code>@Target</code>如果不指定属性，默认是全部可用</p>
</blockquote>
<p>使用元注解就可以解决前面解析注解部分无法读取到注解的问题：</p>
<pre><code class="java">@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}

// 测试
public class Test01 {
    public static void main(String[] args) {
        //1.获取BookShelf的class对象
        Class&lt;BookShelf&gt; bookShelfClass = BookShelf.class;
        //2.判断bookShelf上有没有Book注解
        boolean b = bookShelfClass.isAnnotationPresent(Book.class);
        //3.判断,如果b为true,就获取
        if (b){
            Book book = bookShelfClass.getAnnotation(Book.class);
            System.out.println(book.bookName());
            System.out.println(Arrays.toString(book.author()));
            System.out.println(book.price());
            System.out.println(book.count());
        }
    }
}

输出结果：
寓言故事
[张三, 李四]
10
20
</code></pre>
<h3><strong></strong><strong>枚举</strong><strong></strong></h3>
<h4>基本使用</h4>
<p>枚举属于五大引用数据类型中的一种：类、数组、接口、注解、枚举</p>
<p>定义枚举格式如下：</p>
<pre><code class="java">public enum 枚举类名{

}
</code></pre>
<p>在Java中，所有枚举的父类都是<code>Enum</code></p>
<p>枚举的特点如下：</p>
<ol>
 <li>每一个枚举都是<code>static final</code>，但是定义枚举是不能显示写出</li>
 <li>每一个枚举由逗号分隔</li>
 <li>写完所有的枚举值之后，最后一个枚举后方需要加<code>;</code></li>
 <li>枚举值名字最好大写</li>
 <li>使用时使用枚举类名直接调用枚举成员</li>
 <li>枚举中的成员都是当前枚举类类型的对象</li>
 <li>枚举类中的构造方法都是<code>private</code>修饰</li>
</ol>
<p>基本使用如下：</p>
<pre><code class="java">public enum Status {
    RUNNING,
    WAITING,
    STOPPED;
}
</code></pre>
<p>如果想为每一个枚举赋值，可以在枚举类中定义成员和构造方法，并对外提供获取方法就可以获取到每一个枚举值对应的值</p>
<pre><code class="java">public enum Status {
    RUNNING("运行"),
    WAITING("等待"),
    STOPPED("暂停");

    private String name;

    private Status(String name) {
        this.name = name;
    }
}
</code></pre>
<p>测试如下：</p>
<pre><code class="java">public class Test {
    public static void main(String[] args) {
        System.out.println(Status.RUNNING);
        System.out.println(Status.RUNNING.getName());
    }
}

输出结果：
RUNNING
运行
</code></pre>
<h4>枚举中的常用方法</h4>
<p>|———————–|————–|
 <br>
 | 方法名 | 说明 |
 <br>
 | <code>String toString()</code> | 返回枚举值的名字 |
 <br>
 | <code>values()</code> | 返回所有与的枚举值 |
 <br>
 | <code>valueOf(String str)</code> | 将一个字符串转成枚举类型 |</p>
<p>例如下面的代码：</p>
<pre><code class="java">// 枚举
public enum Status {
    RUNNING("运行"),
    WAITING("等待"),
    STOPPED("暂停");

    private String name;

    private Status(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// 测试
public class Test01 {
    public static void main(String[] args) {
        Status status = Status.RUNNING;

        System.out.println(status.toString());
        System.out.println(status);

        Status[] values = Status.values();
        for (Status value : values) {
            System.out.println(value);
        }

        Status status1 = Status.valueOf("RUNNING");
        System.out.println(status1);
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/m0_73281594/article/details/142602594</p>]]></description><guid isPermaLink="false">/archives/5b3505e5-2b4c-46c9-9ed9-8d026723ab88</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:22:33 GMT</pubDate></item><item><title><![CDATA[Spring Boot 调用外部接口的常用方式！]]></title><link>https://wiki.dgfygk.com/archives/e306ecd4-a5c7-46f0-a0c7-52bc5affacb3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%20%E8%B0%83%E7%94%A8%E5%A4%96%E9%83%A8%E6%8E%A5%E5%8F%A3%E7%9A%84%E5%B8%B8%E7%94%A8%E6%96%B9%E5%BC%8F%EF%BC%81&amp;url=/archives/e306ecd4-a5c7-46f0-a0c7-52bc5affacb3" width="1" height="1" alt="" style="opacity:0;">
<p>使用Feign进行服务消费是一种简化HTTP调用的方式，可以通过声明式的接口定义来实现。下面是一个使用Feign的示例，包括设置Feign客户端和调用服务的方法。</p>
<p>添加依赖
 <br>
 首先，请确保你的项目中已经添加了Feign的依赖。如果你使用的是Maven，可以在pom.xml中添加以下依赖（如果使用Spring Boot，通常已经包含了这些依赖）：</p>
<pre><code class="prism language-bash">&lt;dependency&gt;  
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;  
    &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;  
&lt;/dependency&gt;  
</code></pre>
<p>以下是完整示例的结构：</p>
<p>主应用类（YourApplication.java）：</p>
<pre><code class="prism language-bash">import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.openfeign.EnableFeignClients;  

@SpringBootApplication  
@EnableFeignClients  
public class YourApplication {

    public static void main(String[] args) {

        SpringApplication.run(YourApplication.class, args);  
    }  
}  
</code></pre>
<p>Feign客户端接口（UserServiceClient.java）：</p>
<pre><code class="prism language-bash">import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RequestParam;  

@FeignClient(name = "user-service", url = "http://USER-SERVICE")  
public interface UserServiceClient {

    @GetMapping("/user")  
    String getUserByName(@RequestParam("name") String name);  
}  
</code></pre>
<p>服务类（UserService.java）：</p>
<pre><code class="prism language-bash">import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  

@Service  
public class UserService {

    private final UserServiceClient userServiceClient;  

    @Autowired  
    public UserService(UserServiceClient userServiceClient) {

        this.userServiceClient = userServiceClient;  
    }  

    public String fetchUserByName(String name) {

        return userServiceClient.getUserByName(name);  
    }  
}  
</code></pre>
<p>注意事项
 <br>
 Feign的配置：可以通过application.yml或application.properties配置Feign的超时、编码等。
 <br>
 服务发现：如果使用服务发现工具（如Eureka），可以将url参数省略，程序会自动根据服务名称进行调用。
 <br>
 错误处理：请考虑使用Feign提供的错误解码器或自定义的异常处理机制。</p>
<p>WebClient
 <br>
 WebClient是Spring WebFlux提供的非阻塞式HTTP客户端，适用于异步调用。</p>
<p>示例代码：</p>
<pre><code class="prism language-bash">import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import org.springframework.web.reactive.function.client.WebClient;  
import reactor.core.publisher.Mono;  

@Service  
public class WebClientService {


    private final WebClient webClient;  

    @Autowired  
    public WebClientService(WebClient.Builder webClientBuilder) {

        this.webClient = webClientBuilder.baseUrl("http://USER-SERVICE").build();  
    }  

    public Mono&lt;String&gt; getUser(String username) {

        return webClient.get()  
                .uri("/user?name={username}", username)  
                .retrieve()  
                .bodyToMono(String.class);  
    }  
}  
</code></pre>
<p>配置：
 <br>
 在@Configuration类中配置WebClient bean：</p>
<pre><code class="prism language-bash">import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.reactive.function.client.WebClient;  

@Configuration  
public class AppConfig {


    @Bean  
    public WebClient.Builder webClientBuilder() {

        return WebClient.builder();  
    }  
}  
</code></pre>
<p>使用hutool</p>
<pre><code class="prism language-bash">import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.net.URLEncoder;  
import java.nio.charset.StandardCharsets;  
import java.util.Map;  

public class ApiClient {


    public String sendPostRequest(String code, String appAccessToken, SocialDetails socialDetails) {

        String url = formatUrl(socialDetails.getUrl(), appAccessToken);  
        String jsonBody = createRequestBody(code);  

        return executePost(url, jsonBody);  
    }  

    private String formatUrl(String baseUrl, String token) {

        try {

            return String.format(baseUrl, URLEncoder.encode(token, StandardCharsets.UTF_8.toString()));  
        } catch (Exception e) {

            throw new RuntimeException("Error encoding URL", e);  
        }  
    }  

    private String createRequestBody(String code) {

        Map&lt;String, String&gt; requestBody = Map.of("code", code);  
        return JSONUtil.toJsonStr(requestBody);  
    }  

    private String executePost(String url, String jsonBody) {

        try {

            return HttpUtil.post(url, jsonBody);  
        } catch (Exception e) {

            throw new RuntimeException("Failed to execute POST request", e);  
        }  
    }  
}
</code></pre>
<ol>
 <li>创建一个 RestTemplate Bean
  <br>
  在你的 Spring Boot 应用中创建一个 RestTemplate 的 Bean，通常在主类或配置类中：</li>
</ol>
<pre><code class="prism language-bash">import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.client.RestTemplate;  

@Configuration  
public class AppConfig {


    @Bean  
    public RestTemplate restTemplate() {

        return new RestTemplate();  
    }  
}  
</code></pre>
<p>创建 RestTemplate 示例
 <br>
 以下是一个简单的服务类，展示如何使用 RestTemplate 发送 GET 和 POST 请求：</p>
<pre><code class="prism language-bash">import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import org.springframework.web.client.RestTemplate;  

@Service  
public class ApiService {


    @Autowired  
    private RestTemplate restTemplate;  

    // 发送 GET 请求  
    public String getExample() {

        String url = "https://baidu.com/posts/1";  
        return restTemplate.getForObject(url, String.class);  
    }  

    // 发送 POST 请求  
    public String postExample() {

        String url = "https://baidu.com/posts";  
        Post post = new Post("foo", "bar");  
        return restTemplate.postForObject(url, post, String.class);  
    }  

    static class Post {

        private String title;  
        private String body;  

        public Post(String title, String body) {

            this.title = title;  
            this.body = body;  
        }  

        // Getters and Setters (如果需要)  
    }  
}  
</code></pre>
<p>调用示例
 <br>
 通常在一个控制器中调用这个服务：</p>
<pre><code class="prism language-bash">import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RestController;  

@RestController  
public class ApiController {


    @Autowired  
    private ApiService apiService;  

    @GetMapping("/get")  
    public String get() {

        return apiService.getExample();  
    }  

    @PostMapping("/post")  
    public String post() {

        return apiService.postExample();  
    }  
}  
</code></pre>
<p>原文链接: https://blog.csdn.net/weimeilayer/article/details/142588955</p>]]></description><guid isPermaLink="false">/archives/e306ecd4-a5c7-46f0-a0c7-52bc5affacb3</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:22:15 GMT</pubDate></item><item><title><![CDATA[Docker配置阿里云镜像加速器，拉取Tomcat，使用云服务器访问Tomcat容器]]></title><link>https://wiki.dgfygk.com/archives/5b4e28cf-fb97-4edf-9833-5ef5ab11dc03</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Docker%E9%85%8D%E7%BD%AE%E9%98%BF%E9%87%8C%E4%BA%91%E9%95%9C%E5%83%8F%E5%8A%A0%E9%80%9F%E5%99%A8%EF%BC%8C%E6%8B%89%E5%8F%96Tomcat%EF%BC%8C%E4%BD%BF%E7%94%A8%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AE%BF%E9%97%AETomcat%E5%AE%B9%E5%99%A8&amp;url=/archives/5b4e28cf-fb97-4edf-9833-5ef5ab11dc03" width="1" height="1" alt="" style="opacity:0;">
<h2>一、简介与概述</h2>
<p>1.Docker 是一个开源的应用容器引擎，基于 Go 语言 并遵从 Apache2.0 协议开源。</p>
<p>Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的 Linux 机器上，也可以实现虚拟化。</p>
<ol>
 <li>Docker的主要目标是'build ，ship and run any app，anywhere'，也就是说通过对应用程序组件的封装，分发，部署，运行等生命周期的管理。使用户的app（可以是一个web应用程序或者数据库应用等）<strong>及其运行环境能够做到'一次封装，到处运行'。</strong></li>
</ol>
<h2>二、配置阿里云镜像加速器</h2>
<p>阿里云镜像获取地址：https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors，登陆后，左侧菜单选中镜像加速器就可以看到你的专属地址了：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe5bd56606b914d4ab03bc141aa9d35ed.png&amp;size=m" alt=""></p>
<p>1、通过修改daemon配置文件/etc/docker/daemon.json来使用加速器</p>
<blockquote>
 <p>sudo mkdir -p /etc/docker</p>
 <p>sudo tee /etc/docker/daemon.json &lt;&lt;-'EOF'</p>
 <p>{</p>
 <p>“registry-mirrors”: [“https://euf11uji.mirror.aliyuncs.com”]</p>
 <p>}</p>
 <p>EOF</p>
 <p>sudo systemctl daemon-reload</p>
 <p>sudo systemctl restart docker</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcb1f76b9d3bd47949b8ff61b071e8179.png&amp;size=m" alt="">2、查看加速镜像是否安装成功<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe3b3585687594fb0ace44276f4ae5520.png&amp;size=m" alt=""></p>
<p>三、Docker常用命令</p>
<blockquote>
 <ol>
  <li>
   <p>docker –help：帮助命令</p>
  </li>
  <li>
   <p>docker –version：查看docker版本信息</p>
  </li>
  <li>
   <p>systemctl status docker：查看docker状态</p>
  </li>
  <li>
   <p>systemctl stop docker：停止docker</p>
  </li>
  <li>
   <p>systemctl restart docker：重启docker</p>
  </li>
  <li>
   <p>docker images：查看本地镜像文件</p>
  </li>
  <li>
   <p>docker rmi -f kibana:5.6.9：删除镜像文件</p>
  </li>
  <li>
   <p>REPOSITORY 存储库名称</p>
   <p>Tag 镜像的标签 不写版本号码 默认下载最新latest镜像</p>
   <p>IMAGE ID 镜像id</p>
   <p>CREATED 创建时间</p>
   <p>SIZE 大小</p>
  </li>
  <li>
   <p>docker images -a：显示所有镜像信息</p>
  </li>
  <li>
   <p>docker images -q：只显示镜像的id</p>
  </li>
  <li>
   <p>docker images –digests：显示镜像的摘要信息</p>
  </li>
  <li>
   <p>docker images –no-trunc：显示完整镜像信息</p>
  </li>
  <li>
   <p>docker rmi 镜像名称：删除镜像
    <br>
    lsof -i:端口号：查看该端口被谁占用</p>
  </li>
 </ol>
 <p>kill -9 PID：杀死被占用的进程</p>
</blockquote>
<h2>三、Docker拉取Tomcat</h2>
<p>1、找到/etc/docker下的路径daemon.json文件，将镜像修改为Docker镜像</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa80f39a2b7d64fab8cd59665932c8054.png&amp;size=m" alt="">2、在/etc/docker路径下输入</p>
<blockquote>
 <p>docker pull tomcat:8.5 #tomcat要拉取的容器名称，8.5为容器版本号</p>
</blockquote>
<p>3、查看镜像<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd41a8591bb6745c98ce82aa92f64fab6.png&amp;size=m" alt=""></p>
<p>4、运行容器<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Feacb1e6df8244b2797caef7d30a6db1f.png&amp;size=m" alt=""></p>
<p>5、Docker容器相关命令</p>
<blockquote>
 <ol>
  <li>docker ps：查看正在运行的容器</li>
  <li>docker ps -a：查看运行和已经运行关闭的容器</li>
  <li>docker stop 容器ID：关闭容器</li>
  <li>docker start 容器ID：启动容器</li>
  <li>docker rm 容器ID：删除容器</li>
  <li>docker inspect 容器ID：查看容器信息</li>
  <li>docker exec -it 容器ID bash：进入容器</li>
 </ol>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe3a6a1ae037f4606851b901a89ce01f8.png&amp;size=m" alt=""></p>
<p>6、进入容器，使用bash命令浏览容器的文件<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2e5b29403e2c48058790b7b5bef16b60.png&amp;size=m" alt=""></p>
<p>7、使容器连接本机<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F29451edb07944eb2b0eae3d6c492b500.png&amp;size=m" alt=""></p>
<p>8、进入bin目录，停掉容器<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7633f86a4b75431fa541a0fe4655b8b3.png&amp;size=m" alt=""></p>
<p>9、停掉容器<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F33481fee71f941108ce6bfa45b4e592d.png&amp;size=m" alt=""></p>
<p>10、当执行docker rm 容器ID ，删除容器时，容器会删掉，镜像中还会存在<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F76f918c4fcd74c50842b9103b0742410.png&amp;size=m" alt=""></p>
<h2>四、使用云服务器连接Tomcat容器</h2>
<p>1、运行容器，8083为外部端口<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fad6f12824cc945cc9e4ad6867bf99516.png&amp;size=m" alt=""></p>
<p>2、进入容器： docker exec -it 容器ID bash，本机连接容器是否成功：curl 本机IP:8080<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5cfd3496a60c48d29c671aba117deca9.png&amp;size=m" alt=""></p>
<p>3、开启防火墙，查看防火墙对外开放的端口<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fde133aa00af54502bf5678b94294296c.png&amp;size=m" alt=""></p>
<p>4、使防火墙对外部端口开放（开放后需要重启防火墙，并查看对外开放的端口）<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F56e8919104c34be99b202f121e52c7e6.png&amp;size=m" alt=""></p>
<p>5、如果在浏览器中访问外部端口报404，需要进行以下操作</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8325b31eb5a64960bbce1f62c898ed74.png&amp;size=m" alt=""></p>
<p>此时再去浏览器访问即可访问成功！<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F02e3cb1bac0a491fae18c7ca9fe95d53.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/weixin_63908159/article/details/142439450</p>]]></description><guid isPermaLink="false">/archives/5b4e28cf-fb97-4edf-9833-5ef5ab11dc03</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 07:22:00 GMT</pubDate></item><item><title><![CDATA[Nginx+Tomcat负载均衡、反向代理]]></title><link>https://wiki.dgfygk.com/archives/45c2b6d6-14f8-42ab-87aa-163d780a603d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Nginx%2BTomcat%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E3%80%81%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86&amp;url=/archives/45c2b6d6-14f8-42ab-87aa-163d780a603d" width="1" height="1" alt="" style="opacity:0;">
<p><strong>目录</strong></p>
<p>
 <br>
</p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%80%E3%80%81%E4%BB%A3%E7%90%86">一、代理</a></p>
<p><a href="https://wiki.dgfygk.com#1.%E6%A6%82%E8%BF%B0">1.概述</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%BA%8C%E3%80%81Nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86">二、Nginx反向代理</a></p>
<p><a href="https://wiki.dgfygk.com#1.%E4%B8%83%E5%B1%82%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%20-%20%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB">1.七层反向代理 - 动静分离</a></p>
<p><a href="https://wiki.dgfygk.com#2.%E5%9B%9B%E5%B1%82%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%20-%20%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1">2.四层反向代理 - 负载均衡</a></p>
<p><a href="https://wiki.dgfygk.com#%E4%B8%89%E3%80%81Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E8%B0%83%E5%BA%A6%E7%AD%96%E7%95%A5">三、Nginx负载均衡调度策略</a></p>
<hr>
<p>
 <br>
</p>
<h2>一、代理</h2>
<h3>1.概述</h3>
<p><strong>代理:</strong>
 <br>
 指服务端和客户端之间架设的一层服务器。代理负责接受客户端的请求并将它转发给服务端，然后再将服务端返回的响应转发给客户端。</p>
<p>代理又分为正向代理和反向代理。</p>
<p><strong>正向代理：</strong>
 <br>
 为客户端服务的，客户端可以根据正向代理访问到它本身无法访问到的服务器资源。</p>
<p><strong>反向代理：</strong>
 <br>
 为服务端服务的，反向代理可以帮助服务器接收来自客户端的请求，帮助服务器做请求转发，负载均衡等。</p>
<h3>二、Nginx反向代理</h3>
<h3>1.七层反向代理 - 动静分离</h3>
<p><strong>七层反向代理：</strong>
 <br>
 基于http、https等七层应用协议的代理转发，可以根据用户的请求访问的URL路径来转发请求，通常用于动静分离等应用场景。</p>
<p><strong>Nginx动静分离：</strong>
 <br>
 通过location匹配用户的访问路径来判断是动静态资源请求还是动态资源请求。静态资源请求就由Nginx自行处理并相应，动态资源请求则通过反向代理转发请求给后端应用服务器处理了。</p>
<p>配置：</p>
<pre><code class="hljs">1）在 http 配置块中使用 upstream 定义后端应用服务器池的名称和各个节点的参数
http {
    upstream 服务器池名称 {
        server IP1:PORT1 weight=1;
        server IP2:PORT2 weight=1;
        [调度策略;]
    }

2）在 server 配置块中使用 location 匹配用户请求的动态资源的URL路径，在 location 配置块里使用 proxy_pass 基于协议配置转发
    server {
        listen 
        server_name
        .....
        location ~ .*\.jsp$ {
            proxy_pass http://服务器池名称;

            #还可以添加一些转发参数，用于为后端服务器获取真实的客户端地址
            proxy_set_header HOST $host;        
            proxy_set_header X_Real_IP $remote_addr;        
            proxy_set_header X_Forwarded_For $proxy_add_x_forwarded_for;        
        }       
    }

}
</code></pre>
<h3>2.四层反向代理 - 负载均衡</h3>
<p><strong>四层反向代理：</strong>
 <br>
 基于IP端口实现的代理转发，可以根据用户请求的IP或端口来转发请求，通常用于做网络接入层的负载均衡器等应用场景。</p>
<p><strong>Nginx负载均衡：</strong>
 <br>
 通过反向代理实现的，可以将Nginx反向代理服务器接收到的请求根据调度策略转发给多个后端应用服务器处理。</p>
<p>配置：</p>
<pre><code class="hljs">1）编译安装nginx时需要添加 stream 四层代理模块  ./configure --with-stream

2）在 http 配置块上面同层级添加 stream 配置块，在 stream 配置块里添加 upstream 配置块，使用 upstream 定义后端应用服务器池的名称和各个节点的参数
stream {
    upstream 服务器池名称 {
        server IP1:PORT1 weight=1;
        server IP2:PORT2 weight=1;
        [调度策略;]
    }

3）在 stream 配置块里添加 server 配置块，在 server 配置块里使用 listen 定义监听地址和端口，使用 proxy_pass 配置转发
    server {
        listen  IP:PORT;
        proxy_pass 服务器池名称;
    }
}

http {....}
</code></pre>
<h2>三、Nginx负载均衡调度策略</h2>
<ul>
 <li>轮询：是nginx默认调度策略，按照时间顺序逐一分配请求</li>
 <li>加权轮询：使用weight参数设置权重，权重值越高的后端节点被分配请求的概率越大</li>
 <li>least_conn(最少连接)：优先将请求分配给当前连接数最少的后端节点</li>
 <li>ip_hash：根据客户端IP做hash缓存调度，会将来自同一个IP的请求都分配给固定的一个后端节点</li>
 <li>url_hash：根据客户端请求的URL路径做hash缓存调度，会将相同的URL路径的请求都分配给固定的一个后端节点。需要第三方插件模块支持</li>
 <li>fair：优先将请求给给响应时间最少的后端节点。需要第三方插件模块支持</li>
 <li>random：随机分配请求</li>
 <li>hash $remote_addr consistent：一致性hash算法，根据nginx全局变量的值来做hash缓存调度</li>
</ul>
<p>配置举例：</p>
<pre><code class="hljs">upstream 服务器池名称 {
    server IP1:PORT1   weight=权重  max_fails=最大的失败次数  fail_timeout=暂时服务的时间;
    .....
    least_conn;/ip_hash;/fair;/hash $request_uri;/random;
}
</code></pre>
<p>Nginx负载均衡如何实现会话保持</p>
<ol>
 <li>
  <p>ip_hash、url_hash、hash $remote_addr consistent
   <br>
   基于客户端IP或请求的URL路径将请求发送到同一个后端节点服务器来实现会话保持，但是此法容易出现负载失衡问题</p>
 </li>
 <li>
  <p>sticky_cookie_insert
   <br>
   基于cookie判断实现会话保持</p>
  <pre><code class="hljs">upstream backend {
   server IP1:PORT1;
   server IP2:PORT2;
   sticky_cookie_insert  srv_id   expires=浏览器中保持cookie的时间    domain=cookie的域名    path=cookie的路径;
}
</code></pre>
 </li>
 <li>
  <p>通过后端应用服务器自身机制，配合使用缓存数据库（redis、memcached等）为后端节点服务器做session同步复制实现会话保持</p>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/2401_85983616/article/details/142596560</p>]]></description><guid isPermaLink="false">/archives/45c2b6d6-14f8-42ab-87aa-163d780a603d</guid><dc:creator>黄健</dc:creator><category>Nginx专题</category><pubDate>Sat, 28 Sep 2024 07:21:42 GMT</pubDate></item><item><title><![CDATA[Spring MVC中实现一个文件上传和下载功能]]></title><link>https://wiki.dgfygk.com/archives/438dc5c9-bebf-4bb5-9971-51d687fb139b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20MVC%E4%B8%AD%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%92%8C%E4%B8%8B%E8%BD%BD%E5%8A%9F%E8%83%BD&amp;url=/archives/438dc5c9-bebf-4bb5-9971-51d687fb139b" width="1" height="1" alt="" style="opacity:0;">
<p>说到文件上传和下载，相信每个开发者都有或多或少的接触过文件上传的功能吧，文件上传和下载是我们在学习计算机网络应用常见的一个功能，主要涉及到用户和服务器之间的数据传输。</p>
<p>我们来对文件上传和下载功能的进行相关概述吧！</p>
<h3>文件上传</h3>
<p>定义：文件上传是指用户将本地计算机上的文件通过网络传输到服务器的过程。用户通常通过网页表单选择文件并提交，服务器接收并存储该文件。</p>
<p>过程：</p>
<ol>
 <li><strong>用户选择文件</strong> ：用户在网页上选择要上传的文件，通常通过一个文件输入框（<code>&lt;input type="file"&gt;</code>）。</li>
 <li><strong>表单提交</strong>：用户点击提交按钮，浏览器将文件和其他表单数据一起发送到服务器。</li>
 <li><strong>HTTP请求</strong>：浏览器发起一个HTTP POST请求，包含文件数据和其他表单字段。</li>
 <li><strong>服务器接收文件</strong> ：</li>
 <li>服务器接收到请求后，解析请求体中的文件数据。</li>
 <li>服务器将文件存储在指定的目录中。</li>
 <li><strong>反馈结果</strong>：服务器处理完上传后，通常会返回一个响应，告知用户上传是否成功。</li>
</ol>
<h3>文件下载</h3>
<p>定义：文件下载是指用户从服务器获取文件并保存到本地计算机的过程。用户通常通过点击链接或按钮来触发下载。</p>
<p>过程：</p>
<ol>
 <li><strong>用户请求下载</strong>：用户在网页上点击下载链接或按钮，触发下载请求。</li>
 <li><strong>HTTP请求</strong>：浏览器发起一个HTTP GET请求，请求特定的文件。</li>
 <li><strong>服务器处理请求</strong> ：</li>
 <li>服务器接收到请求后，查找请求的文件。</li>
 <li>服务器将文件内容写入HTTP响应体，并设置适当的响应头（如Content-Disposition）以指示浏览器下载文件而不是直接显示。</li>
 <li><strong>浏览器接收文件</strong>：浏览器接收到响应后，开始下载文件并提示用户选择保存位置。</li>
 <li><strong>文件保存</strong>：用户选择保存位置后，文件被下载到本地计算机。</li>
</ol>
<h3>文件上传和下载的关键步骤</h3>
<p>在文件上传和下载的过程中，涉及以下关键步骤：</p>
<ol>
 <li><strong>用户界面</strong>：提供文件选择和下载的用户界面（HTML表单）。</li>
 <li><strong>HTTP请求</strong>：使用适当的HTTP方法（POST用于上传，GET用于下载）发送请求。</li>
 <li><strong>请求处理</strong>：服务器端代码处理请求，解析文件数据（上传）或查找文件（下载）。</li>
 <li><strong>文件存储</strong>：在上传过程中，将文件存储在服务器的指定位置。</li>
 <li><strong>响应返回</strong>：服务器返回处理结果（上传成功/失败）或文件内容（下载）。</li>
 <li><strong>错误处理</strong>：处理可能出现的错误，如文件大小限制、文件类型验证、文件不存在等。</li>
</ol>
<h3>在SpringMVC中实现一个文件上传和下载的功能</h3>
<p>第一步创建我们的文件项目目录结构：</p>
<pre><code class="prism language-java">src
 └── main
     ├── java
     │   └── com
     │       └── example
     │           └── fileupload
     │               ├── FileUploadController.java
     │               └── WebConfig.java
     ├── resources
     │   └── templates
     │       └── upload.html
     └── web.xml
</code></pre>
<p>第二步：添加相关依赖</p>
<pre><code class="prism language-xml">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt;
        &lt;version&gt;5.3.10&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.thymeleaf&lt;/groupId&gt;
        &lt;artifactId&gt;thymeleaf-spring5&lt;/artifactId&gt;
        &lt;version&gt;3.0.12.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
        &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt;
        &lt;version&gt;4.0.1&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<p>第三步，创建我们的Web配置类，用来监听相关信息
 <br>
 在WebConfig.java中配置Spring MVC：</p>
<pre><code class="prism language-java">import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {


    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {

        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setOrder(1);
        return resolver;
    }

    @Bean
    public CommonsMultipartResolver multipartResolver() {

        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setMaxUploadSize(10485760); // 10MB
        return multipartResolver;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/uploads/**").addResourceLocations("file:uploads/");
    }
}
</code></pre>
<h3>创建控制器</h3>
<p>在<code>FileUploadController.java</code>中实现文件上传和下载的逻辑：</p>
<pre><code class="prism language-java">import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

@Controller
public class FileUploadController {


    private final String uploadDir = "uploads/";

    @GetMapping("/")
    public String index() {

        return "upload";
    }

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

        if (file.isEmpty()) {

            redirectAttributes.addFlashAttribute("message", "请选择一个文件上传！");
            return "redirect:/";
        }

        try {

            // 保存文件
            File dir = new File(uploadDir);
            if (!dir.exists()) {

                dir.mkdirs();
            }
            File uploadedFile = new File(uploadDir + file.getOriginalFilename());
            file.transferTo(uploadedFile);
            redirectAttributes.addFlashAttribute("message", "文件上传成功：" + file.getOriginalFilename());
        } catch (IOException e) {

            redirectAttributes.addFlashAttribute("message", "文件上传失败：" + e.getMessage());
        }

        return "redirect:/";
    }

    @GetMapping("/download")
    public void downloadFile(@RequestParam("filename") String filename, HttpServletResponse response) {

        File file = new File(uploadDir + filename);
        if (file.exists()) {

            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
            try (FileInputStream in = new FileInputStream(file);
                 OutputStream out = response.getOutputStream()) {

                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) != -1) {

                    out.write(buffer, 0, bytesRead);
                }
            } catch (IOException e) {

                e.printStackTrace();
            }
        } else {

            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}
</code></pre>
<p>第五步，创建HTML模板</p>
<pre><code class="prism language-html">&lt;!DOCTYPE html&gt;
&lt;html xmlns:th="http://www.thymeleaf.org"&gt;
&lt;head&gt;
    &lt;title&gt;文件上传&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;文件上传&lt;/h1&gt;

&lt;form method="post" enctype="multipart/form-data" th:action="@{/upload}"&gt;
    &lt;input type="file" name="file" required/&gt;
    &lt;button type="submit"&gt;上传&lt;/button&gt;
&lt;/form&gt;

&lt;div th:if="${message}" th:text="${message}"&gt;&lt;/div&gt;

&lt;h2&gt;下载文件&lt;/h2&gt;
&lt;form method="get" th:action="@{/download}"&gt;
    &lt;input type="text" name="filename" placeholder="输入文件名" required/&gt;
    &lt;button type="submit"&gt;下载&lt;/button&gt;
&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>最后一步，配置web.xml文件</p>
<pre><code class="prism language-xml">&lt;web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1"&gt;

    &lt;servlet&gt;
        &lt;servlet-name&gt;dispatcher&lt;/servlet-name&gt;
        &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
    &lt;/servlet&gt;

    &lt;servlet-mapping&gt;
        &lt;servlet-name&gt;dispatcher&lt;/servlet-name&gt;
        &lt;url-pattern&gt;/&lt;/url-pattern&gt;
    &lt;/servlet-mapping&gt;

    &lt;context-param&gt;
        &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
        &lt;param-value&gt;/WEB-INF/applicationContext.xml&lt;/param-value&gt;
    &lt;/context-param&gt;

    &lt;listener&gt;
        &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
    &lt;/listener&gt;

&lt;/web-app&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff9f00f944853434982d1c83cbfef62e7.png&amp;size=m" alt=""></p>
<pre><code class="prism language-properties">14:18:08.583 [http-nio-8008-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - POST "/SpringMVC/upload", parameters={}
14:18:08.595 [http-nio-8008-exec-6] DEBUG org.springframework.web.multipart.commons.CommonsMultipartResolver - Part 'file', size 719635 bytes, filename='白底logo.png'
14:18:08.596 [http-nio-8008-exec-6] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.miaow.controller.FileUploadController#uploadFile(MultipartFile, RedirectAttributes)
14:18:08.600 [http-nio-8008-exec-6] DEBUG org.springframework.web.multipart.commons.CommonsMultipartFile - Part 'file',  filename '白底logo.png': moved to [D:\IDEATomcat\apache-tomcat-9.0.89-windows-x64\apache-tomcat-9.0.89\bin\uploads\白底logo.png]
14:18:08.600 [http-nio-8008-exec-6] DEBUG org.springframework.web.servlet.view.RedirectView - View name 'redirect:', model {}
14:18:08.601 [http-nio-8008-exec-6] DEBUG org.springframework.web.multipart.commons.CommonsMultipartResolver - Cleaning up part 'file', filename '白底logo.png'
14:18:08.601 [http-nio-8008-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 302 FOUND
14:18:08.608 [http-nio-8008-exec-5] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringMVC/", parameters={}
14:18:08.608 [http-nio-8008-exec-5] DEBUG org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped to ParameterizableViewController [view="index"]
14:18:08.610 [http-nio-8008-exec-5] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK
14:18:12.924 [http-nio-8008-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringMVC/upload", parameters={}
14:18:12.924 [http-nio-8008-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.miaow.controller.FileUploadController#index()
14:18:12.925 [http-nio-8008-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK
</code></pre>
<p>上传成功日志。</p>
<p>下载成功日志：</p>
<pre><code class="prism language-properties">14:19:16.956 [http-nio-8008-exec-9] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringMVC/download?filename=violet.webp", parameters={masked}
14:19:16.956 [http-nio-8008-exec-9] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.miaow.controller.FileUploadController#downloadFile(String, HttpServletResponse)
14:19:16.958 [http-nio-8008-exec-9] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_45922256/article/details/142521854</p>]]></description><guid isPermaLink="false">/archives/438dc5c9-bebf-4bb5-9971-51d687fb139b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:21:10 GMT</pubDate></item><item><title><![CDATA[Spring Boot集成Redis向量数据库实现相似性搜索]]></title><link>https://wiki.dgfygk.com/archives/062dc92a-9e62-442a-a7c1-c1d5219f1d5f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%E9%9B%86%E6%88%90Redis%E5%90%91%E9%87%8F%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0%E7%9B%B8%E4%BC%BC%E6%80%A7%E6%90%9C%E7%B4%A2&amp;url=/archives/062dc92a-9e62-442a-a7c1-c1d5219f1d5f" width="1" height="1" alt="" style="opacity:0;">
<h3>1.什么是Redis向量数据库？</h3>
<p><a href="http://redis.ac.cn/" title="Redis">Redis</a> 是一个开源（BSD 许可）的内存数据结构存储，用作数据库、缓存、消息代理和流式处理引擎。Redis 提供数据结构，例如字符串、哈希、列表、集合、带范围查询的有序集合、位图、超对数日志、地理空间索引和流。</p>
<p><a href="http://redis.ac.cn/docs/interact/search-and-query/" title="Redis 搜索和查询">Redis 搜索和查询</a> 扩展了 Redis OSS 的核心功能，并允许您将 Redis 用作向量数据库</p>
<ul>
 <li>在哈希或 JSON 文档中存储向量和关联的元数据</li>
 <li>检索向量</li>
 <li>执行向量搜索</li>
</ul>
<h3>2.向量检索（Vector Search）的核心原理</h3>
<p>向量检索（Vector Search）的核心原理是通过将文本或数据表示为高维向量，并在查询时根据向量的相似度进行搜索。在你的代码中，向量检索过程涉及以下几步：</p>
<h4>匹配的原理：</h4>
<ul>
 <li>检索的核心是将文本或数据转换成向量，在高维向量空间中查找与查询最相似的向量。</li>
 <li>在存储数据时将指定的字段通过嵌入模型生成了向量。</li>
 <li>在检索时，查询文本被向量化，然后与 Redis 中存储的向量进行相似度比较，找到相似度最高的向量（即相关的文档）。</li>
</ul>
<h4>关键点：</h4>
<ul>
 <li><strong>嵌入模型</strong> 将文本转换成向量。</li>
 <li><strong>相似度计算</strong> 通过余弦相似度或欧几里得距离来度量相似性。</li>
 <li><strong>Top K</strong> 返回相似度最高的 K 个文档。</li>
</ul>
<h4>具体过程</h4>
<h5>1. <strong>向量化数据：</strong></h5>
<p>当你将 JSON 中的字段存入 Redis 时，向量化工具（例如 <code>vectorStore</code>）会将指定的字段转换为高维向量。每个字段的内容会通过某种嵌入模型（如 Word2Vec、BERT、OpenAI Embeddings 等）转换成向量表示。每个向量表示的是该字段内容的语义特征。</p>
<h5>2. <strong>搜索时的向量生成：</strong></h5>
<p>当执行 <code>SearchRequest.query(message)</code> 时，系统会将输入的 <code>message</code> 转换为一个查询向量。这一步是通过同样的嵌入模型，将查询文本转换为与存储在 Redis 中相同维度的向量。</p>
<h5>3. <strong>相似度匹配：</strong></h5>
<p><code>vectorStore.similaritySearch(request)</code> 函数使用了一个向量相似度计算方法来查找最相似的向量。这通常是通过 <strong>余弦相似度</strong> 或 <strong>欧几里得距离</strong> 来度量查询向量和存储向量之间的距离。然后返回与查询最相似的前 <code>K</code> 个文档，即 <code>withTopK(topK)</code> 所指定的 <code>K</code> 个最相关的结果。</p>
<h5>4. <strong>返回匹配的文档：</strong></h5>
<p>匹配的结果是根据相似度得分排序的 <code>List&lt;Document&gt;</code>。这些文档是你最初存储在 Redis 中的记录，包含了 JSON 中指定的字段。</p>
<h3>3.环境搭建</h3>
<pre><code>version: '3'
services:
  redis-stack:
    image: redis/redis-stack
    ports:
      - 6379:6379

  redis-insight:
    image: redislabs/redisinsight:latest
    ports:
      - 5540:5540
</code></pre>
<p>Run following <strong>command</strong>:</p>
<pre><code>docker-compose up -d
</code></pre>
<p>访问 http://localhost:5540</p>
<h3>4.代码工程</h3>
<h4>实验目标</h4>
<blockquote>
 <p>实现文件数据向量化到redis，并进行相似性搜索</p>
</blockquote>
<h4>pom.xml</h4>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;parent&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;3.2.1&lt;/version&gt;
        &lt;relativePath /&gt; &lt;!-- lookup parent from repository --&gt;
    &lt;/parent&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;artifactId&gt;RedisVectorStore&lt;/artifactId&gt;

    &lt;properties&gt;
        &lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
        &lt;spring-ai.version&gt;0.8.1&lt;/spring-ai.version&gt;
    &lt;/properties&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-autoconfigure&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
            &lt;artifactId&gt;spring-ai-transformers-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;${spring-ai.version}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
            &lt;artifactId&gt;spring-ai-redis-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;${spring-ai.version}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;redis.clients&lt;/groupId&gt;
            &lt;artifactId&gt;jedis&lt;/artifactId&gt;
            &lt;version&gt;5.1.0&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
    &lt;repositories&gt;
        &lt;repository&gt;
            &lt;id&gt;spring-milestones&lt;/id&gt;
            &lt;name&gt;Spring Milestones&lt;/name&gt;
            &lt;url&gt;https://repo.spring.io/milestone&lt;/url&gt;
            &lt;snapshots&gt;
                &lt;enabled&gt;false&lt;/enabled&gt;
            &lt;/snapshots&gt;
        &lt;/repository&gt;
        &lt;repository&gt;
            &lt;id&gt;spring-snapshots&lt;/id&gt;
            &lt;name&gt;Spring Snapshots&lt;/name&gt;
            &lt;url&gt;https://repo.spring.io/snapshot&lt;/url&gt;
            &lt;releases&gt;
                &lt;enabled&gt;false&lt;/enabled&gt;
            &lt;/releases&gt;
        &lt;/repository&gt;
    &lt;/repositories&gt;
    &lt;pluginRepositories&gt;
        &lt;pluginRepository&gt;
            &lt;id&gt;spring-milestones&lt;/id&gt;
            &lt;name&gt;Spring Milestones&lt;/name&gt;
            &lt;url&gt;https://repo.spring.io/milestone&lt;/url&gt;
            &lt;snapshots&gt;
                &lt;enabled&gt;false&lt;/enabled&gt;
            &lt;/snapshots&gt;
        &lt;/pluginRepository&gt;
        &lt;pluginRepository&gt;
            &lt;id&gt;spring-snapshots&lt;/id&gt;
            &lt;name&gt;Spring Snapshots&lt;/name&gt;
            &lt;url&gt;https://repo.spring.io/snapshot&lt;/url&gt;
            &lt;releases&gt;
                &lt;enabled&gt;false&lt;/enabled&gt;
            &lt;/releases&gt;
        &lt;/pluginRepository&gt;
    &lt;/pluginRepositories&gt;
&lt;/project&gt;
</code></pre>
<h4>controller</h4>
<pre><code>package com.et.controller;

import com.et.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.et.service.SearchService;
import java.util.HashMap;
import java.util.Map;

@RestController
public class HelloWorldController {
    @Autowired
    SearchService searchService;
    @RequestMapping("/hello")
    public Map&lt;String, Object&gt; showHelloWorld(){
        Map&lt;String, Object&gt; map = new HashMap&lt;&gt;();
        map.put("msg", searchService.retrieve("beer"));
        return map;
    }
}
</code></pre>
<h4>configuration</h4>
<p>加载文件数据到并将数据向量化到redis</p>
<pre><code>JsonReader loader = new JsonReader(file, KEYS);
</code></pre>
<p><code>JsonReader</code> 和 <code>VectorStore</code> 实现是将 <code>KEYS</code> 中指定的多个字段拼接在一起，生成一个统一的文本表示，然后通过嵌入模型将这些字段的组合文本转换为一个单一的向量，那么这里就是将多个字段组合成 <strong>一个综合向量</strong>。并将其处理后存入 Redis。</p>
<pre><code>package com.et.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
import org.springframework.ai.reader.JsonReader;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.zip.GZIPInputStream;

@Component
public class DataLoader implements ApplicationRunner {

   private static final Logger logger = LoggerFactory.getLogger(DataLoader.class);

   private static final String[] KEYS = { "name", "abv", "ibu", "description" };

   @Value("classpath:/data/beers.json.gz")
   private Resource data;

   private final RedisVectorStore vectorStore;

   private final RedisVectorStoreProperties properties;

   public DataLoader(RedisVectorStore vectorStore, RedisVectorStoreProperties properties) {
      this.vectorStore = vectorStore;
      this.properties = properties;
   }

   @Override
   public void run(ApplicationArguments args) throws Exception {
      Map&lt;String, Object&gt; indexInfo = vectorStore.getJedis().ftInfo(properties.getIndex());
      Long sss= (Long) indexInfo.getOrDefault("num_docs", "0");
      int numDocs=sss.intValue();
      if (numDocs &gt; 20000) {
         logger.info("Embeddings already loaded. Skipping");
         return;
      }
      Resource file = data;
      if (data.getFilename().endsWith(".gz")) {
         GZIPInputStream inputStream = new GZIPInputStream(data.getInputStream());
         file = new InputStreamResource(inputStream, "beers.json.gz");
      }
      logger.info("Creating Embeddings...");
      // tag::loader[]
      // Create a JSON reader with fields relevant to our use case
      JsonReader loader = new JsonReader(file, KEYS);
      // Use the autowired VectorStore to insert the documents into Redis
      vectorStore.add(loader.get());
      // end::loader[]
      logger.info("Embeddings created.");
   }

}
</code></pre>
<p>配置redis vectorStore</p>
<pre><code>package com.et.config;

import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.document.MetadataMode;
import org.springframework.ai.transformers.TransformersEmbeddingClient;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedisConfiguration {

    @Bean
    TransformersEmbeddingClient transformersEmbeddingClient() {
        return new TransformersEmbeddingClient(MetadataMode.EMBED);
    }

    @Bean
    VectorStore vectorStore(TransformersEmbeddingClient embeddingClient, RedisVectorStoreProperties properties) {
        var config = RedisVectorStoreConfig.builder().withURI(properties.getUri()).withIndexName(properties.getIndex())
                .withPrefix(properties.getPrefix()).build();
        RedisVectorStore vectorStore = new RedisVectorStore(config, embeddingClient);
        vectorStore.afterPropertiesSet();
        return vectorStore;
    }


}
</code></pre>
<h4>service</h4>
<p>查询时，查询文本也会生成一个整体向量，与存储的综合向量进行匹配。</p>
<pre><code>package com.et.service;


import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SearchService {


   @Value("${topk:10}")
   private int topK;

   @Autowired
   private  VectorStore vectorStore;



   public List&lt;Document&gt; retrieve(String message) {
      SearchRequest request = SearchRequest.query(message).withTopK(topK);
      // Query Redis for the top K documents most relevant to the input message
      List&lt;Document&gt; docs = vectorStore.similaritySearch(request);

      return docs;
   }

}
</code></pre>
<p>只是一些关键代码，所有代码请参见下面代码仓库</p>
<h4>代码仓库</h4>
<ul>
 <li><a href="https://github.com/Harries/springboot-demo" title="GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.">GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.</a>(RedisVectorStore)</li>
</ul>
<h3>5.测试</h3>
<p>启动Spring Boot应用程序，查看日志</p>
<pre><code> . ____ _ __ _ _
 /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/ ___)| |_)| | | | | || (_| | ) ) ) )
 ' |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot :: (v3.2.1)

2024-09-24T14:03:48.217+08:00 INFO 23996 --- [ main] com.et.DemoApplication : Starting DemoApplication using Java 17.0.9 with PID 23996 (D:\IdeaProjects\ETFramework\RedisVectorStore\target\classes started by Dell in D:\IdeaProjects\ETFramework)
2024-09-24T14:03:48.221+08:00 INFO 23996 --- [ main] com.et.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2024-09-24T14:03:49.186+08:00 INFO 23996 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8088 (http)
2024-09-24T14:03:49.199+08:00 INFO 23996 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-09-24T14:03:49.199+08:00 INFO 23996 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.17]
2024-09-24T14:03:49.289+08:00 INFO 23996 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-09-24T14:03:49.290+08:00 INFO 23996 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1033 ms
2024-09-24T14:03:49.406+08:00 WARN 23996 --- [ main] ai.djl.util.cuda.CudaUtils : Failed to detect GPU count: CUDA driver version is insufficient for CUDA runtime version (35)
2024-09-24T14:03:49.407+08:00 WARN 23996 --- [ main] ai.djl.util.cuda.CudaUtils : Failed to detect GPU count: CUDA driver version is insufficient for CUDA runtime version (35)
2024-09-24T14:03:49.408+08:00 INFO 23996 --- [ main] ai.djl.util.Platform : Found matching platform from: jar:file:/D:/jar_repository/ai/djl/huggingface/tokenizers/0.26.0/tokenizers-0.26.0.jar!/native/lib/tokenizers.properties
2024-09-24T14:03:49.867+08:00 INFO 23996 --- [ main] o.s.a.t.TransformersEmbeddingClient : Model input names: input_ids, attention_mask, token_type_ids
2024-09-24T14:03:49.867+08:00 INFO 23996 --- [ main] o.s.a.t.TransformersEmbeddingClient : Model output names: last_hidden_state
2024-09-24T14:03:50.346+08:00 INFO 23996 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8088 (http) with context path ''
2024-09-24T14:03:50.354+08:00 INFO 23996 --- [ main] com.et.DemoApplication : Started DemoApplication in 2.522 seconds (process running for 2.933)
2024-09-24T14:03:50.364+08:00 INFO 23996 --- [ main] com.et.config.DataLoader : Creating Embeddings...
2024-09-24T14:03:51.493+08:00 WARN 23996 --- [ main] ai.djl.util.cuda.CudaUtils : Failed to detect GPU count: CUDA driver version is insufficient for CUDA runtime version (35)
2024-09-24T14:03:51.800+08:00 INFO 23996 --- [ main] ai.djl.pytorch.engine.PtEngine : PyTorch graph executor optimizer is enabled, this may impact your inference latency and throughput. See: https://docs.djl.ai/docs/development/inference_performance_optimization.html#graph-executor-optimization
2024-09-24T14:03:51.802+08:00 INFO 23996 --- [ main] ai.djl.pytorch.engine.PtEngine : Number of inter-op threads is 6
2024-09-24T14:03:51.802+08:00 INFO 23996 --- [ main] ai.djl.pytorch.engine.PtEngine : Number of intra-op threads is 6
2024-09-24T14:04:26.212+08:00 INFO 23996 --- [nio-8088-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-09-24T14:04:26.213+08:00 INFO 23996 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-09-24T14:04:26.215+08:00 INFO 23996 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2024-09-24T14:09:48.846+08:00 INFO 23996 --- [ main] com.et.config.DataLoader : Embeddings created.
</code></pre>
<p>查看redis是否存在向量化的数据</p>
<p><a href="http://www.liuhaihua.cn/wp-content/uploads/2024/09/vector.png"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fe66b976a0b44da83077adc06adc99d8f.png&amp;size=m" alt=""></a></p>
<p>访问http://127.0.0.1:8088/hello 进行0 相似度搜索(top 10),返回得分前10的数据</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F6896b0722d23e0c2b84344e4e9ae808b.png&amp;size=m" alt=""></p>
<h3>6.引用</h3>
<ul>
 <li><a href="https://docs.springframework.org.cn/spring-ai/reference/api/vectordbs/redis.html" title="https://docs.springframework.org.cn/spring-ai/reference/api/vectordbs/redis.html">https://docs.springframework.org.cn/spring-ai/reference/api/vectordbs/redis.html</a></li>
 <li><a href="https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/vectors/" title="Vectors | Docs">Vectors \| Docs</a></li>
 <li><a href="http://www.liuhaihua.cn/archives/711448.html" title="Spring Boot集成Redis向量数据库实现相似性搜索 | Harries Blog™">Spring Boot集成Redis向量数据库实现相似性搜索 \| Harries Blog™</a></li>
</ul>
<p>原文链接: https://blog.csdn.net/dot_life/article/details/142497729</p>]]></description><guid isPermaLink="false">/archives/062dc92a-9e62-442a-a7c1-c1d5219f1d5f</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:20:55 GMT</pubDate></item><item><title><![CDATA[使用rust+javascript+html5写一个问题记录的页面]]></title><link>https://wiki.dgfygk.com/archives/8f20d487-ddd9-4324-adc2-61ea8fdb2d1d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8rust%2Bjavascript%2Bhtml5%E5%86%99%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95%E7%9A%84%E9%A1%B5%E9%9D%A2&amp;url=/archives/8f20d487-ddd9-4324-adc2-61ea8fdb2d1d" width="1" height="1" alt="" style="opacity:0;">
<p>为了实现一个简单的问题记录系统，我们可以使用Rust作为后端，JavaScript和HTML5作为前端。以下是一个简单的实现：</p>
<ol>
 <li>首先，我们需要创建一个Rust后端，用于处理问题记录的增删改查操作。这里我们使用<code>warp</code>库来创建一个简单的HTTP服务器。</li>
</ol>
<p>安装依赖：</p>
<pre><code class="prism language-sh">cargo install warp
</code></pre>
<p>创建一个名为<code>main.rs</code>的文件，内容如下：</p>
<pre><code class="prism language-rust">use warp::Filter;

#[tokio::main]
async fn main() {

    let questions = warp::path("questions")
        .and(warp::get())
        .map(|| {

            // 这里可以从数据库或其他存储中获取问题列表
            "[]" // 返回一个空的问题列表
        });

    warp::serve(questions)
        .run(([127, 0, 0, 1], 3030))
        .await;
}
</code></pre>
<ol>
 <li>接下来，我们需要创建一个HTML页面，用于显示问题列表和添加新问题。创建一个名为<code>index.html</code>的文件，内容如下：</li>
</ol>
<pre><code class="prism language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;问题记录系统&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;问题记录系统&lt;/h1&gt;
    &lt;ul id="question-list"&gt;&lt;/ul&gt;
    &lt;form id="add-question-form"&gt;
        &lt;input type="text" id="question-input" placeholder="输入问题"&gt;
        &lt;button type="submit"&gt;添加问题&lt;/button&gt;
    &lt;/form&gt;
    &lt;script src="app.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<ol>
 <li>然后，我们需要编写JavaScript代码来处理前端逻辑，包括获取问题列表、添加新问题等。创建一个名为<code>app.js</code>的文件，内容如下：</li>
</ol>
<pre><code class="prism language-javascript">document.getElementById('add-question-form').addEventListener('submit', async (event) =&gt; {

    event.preventDefault();
    const questionInput = document.getElementById('question-input');
    const question = questionInput.value;
    if (!question) return;

    // 发送请求到后端API，添加新问题
    const response = await fetch('http://localhost:3030/questions', {

        method: 'POST',
        headers: {

            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
    question })
    });

    if (response.ok) {

        // 清空输入框并刷新问题列表
        questionInput.value = '';
        updateQuestionList();
    } else {

        alert('添加问题失败');
    }
});

async function updateQuestionList() {

    const response = await fetch('http://localhost:3030/questions');
    const questions = await response.json();
    const questionList = document.getElementById('question-list');
    questionList.innerHTML = '';
    questions.forEach(question =&gt; {

        const li = document.createElement('li');
        li.textContent = question;
        questionList.appendChild(li);
    });
}

updateQuestionList();
</code></pre>
<p>现在，你可以运行Rust后端服务器（<code>cargo run</code>），然后在浏览器中打开<code>index.html</code>文件，即可看到一个简单的问题记录系统。请注意，这个示例仅用于演示目的，实际应用中需要考虑数据持久化、错误处理等问题。</p>
<p>原文链接: https://blog.csdn.net/ChailangCompany/article/details/142601797</p>]]></description><guid isPermaLink="false">/archives/8f20d487-ddd9-4324-adc2-61ea8fdb2d1d</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Sat, 28 Sep 2024 07:20:32 GMT</pubDate></item><item><title><![CDATA[进阶SpringBoot之服务注册]]></title><link>https://wiki.dgfygk.com/archives/8fe302a3-4de8-438f-81c0-abc298b0c0be</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%BF%9B%E9%98%B6SpringBoot%E4%B9%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C&amp;url=/archives/8fe302a3-4de8-438f-81c0-abc298b0c0be" width="1" height="1" alt="" style="opacity:0;">
<p>新建一个空项目</p>
<p>SpringBoot 创建 Web 项目 provider-server（提供者、服务端）</p>
<p>service 包下创建 TicketService 接口，写个方法</p>
<pre><code class="java">package com.demo.service;

public interface TicketService {
    public String getTicket();
}
</code></pre>
<p>service 包下创建 TicketServiceImpl 类，实现 TicketService 接口，重写方法</p>
<pre><code class="java">package com.demo.service;

import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Component;

@DubboService
@Component
public class TicketServiceImpl implements TicketService{
    @Override
    public String getTicket() {
        return "Java";
    }
}
</code></pre>
<p><a href="https://mvnrepository.com/" title="Maven 仓库">Maven 仓库</a></p>
<p>pom.xml 文件导入包，dubbo-spring-boot-starter、zkclient、zookeeper（排除 slf4j-log4j12）</p>
<pre><code class="XML">        &lt;!-- 导入依赖：Dubbo + Zookeeper --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.dubbo&lt;/groupId&gt;
            &lt;artifactId&gt;dubbo-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;3.3.0&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;com.101tec&lt;/groupId&gt;
            &lt;artifactId&gt;zkclient&lt;/artifactId&gt;
            &lt;version&gt;0.11&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;!-- 引入Zookeeper --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.curator&lt;/groupId&gt;
            &lt;artifactId&gt;curator-framework&lt;/artifactId&gt;
            &lt;version&gt;4.3.0&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.curator&lt;/groupId&gt;
            &lt;artifactId&gt;curator-recipes&lt;/artifactId&gt;
            &lt;version&gt;4.3.0&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.zookeeper&lt;/groupId&gt;
            &lt;artifactId&gt;zookeeper&lt;/artifactId&gt;
            &lt;version&gt;3.4.14&lt;/version&gt;
            &lt;!-- 排除slf4j-log4j12 --&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
                    &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
        &lt;/dependency&gt;
</code></pre>
<p>application.properties：</p>
<pre><code class="XML">server.port=8001

#服务应用名字
spring.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.demo.service
</code></pre>
<p>开启 Zookeeper 和 Dubbo-admin 服务</p>
<p>SpringBoot 创建 Web 项目 consumer-server（消费者、客户端）</p>
<p>同理，pom.xml 文件导入相同依赖</p>
<p>application.properties：</p>
<pre><code class="XML">server.port=8002

#消费者名字
spring.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
</code></pre>
<p>两个项目端口号不一致，就需要使用 HTTP 或者 RPC（远程过程调用）</p>
<p>把 TicketService 接口复制到 service 包下，保持和服务端相同的接口名（包名路径要相同）</p>
<p>service 包下创建 UserService 类，从注册中心拿到服务</p>
<pre><code class="java">package com.demo.service;

import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service //放到容器中
public class UserService {

    //去注册中心拿到服务
    @Reference //引用，Pom坐标，可以定义路径相同的接口名
    TicketService ticketService;

    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.out.println("注册中心拿到"+ticket);
    }
}
</code></pre>
<p>测试类：</p>
<pre><code class="java">package com.demo;

import com.demo.consumerserver.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ConsumerServerApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
        userService.buyTicket();
    }

}
</code></pre>
<p>步骤总结：</p>
<p>前提条件：Zookeeper 服务已开启</p>
<p><strong>1.提供者提供服务</strong></p>
<p>1）导入依赖</p>
<p>2）配置注册中心的地址、服务发现名和要扫描的包</p>
<p>3）在想要被注册的服务上面增加一个注解 @DubboService</p>
<p><strong>2.消费者如何消费</strong></p>
<p>1）导入依赖</p>
<p>2）配置注册中心的地址、配置自己服务名</p>
<p>3）从远程注入服务 @Reference</p>
<p>原文链接: https://blog.csdn.net/m0_58838332/article/details/142590077</p>]]></description><guid isPermaLink="false">/archives/8fe302a3-4de8-438f-81c0-abc298b0c0be</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:19:45 GMT</pubDate></item><item><title><![CDATA[zookeeper 服务搭建（集群）]]></title><link>https://wiki.dgfygk.com/archives/fe503545-99b0-4ae0-bd34-6bcdc182a17c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=zookeeper%20%E6%9C%8D%E5%8A%A1%E6%90%AD%E5%BB%BA%EF%BC%88%E9%9B%86%E7%BE%A4%EF%BC%89&amp;url=/archives/fe503545-99b0-4ae0-bd34-6bcdc182a17c" width="1" height="1" alt="" style="opacity:0;">
<ol>
 <li>
  <p>准备3台虚拟机，ip分别是：</p>
  <pre><code class="hljs">192.168.10.75
192.168.10.76
192.168.10.77
</code></pre>
 </li>
 <li>
  <p>准备3个节点</p>
  <pre><code class="hljs">mkdir /usr/local/cluster
cd /usr/local/cluster
git clone https://gitee.com/starplatinum111/apache-zookeeper-3.5.9-bin.git 
</code></pre>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F73b5ce5e58fa41fda5d2b39cabbbaea8.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>重命名文件夹</p>
  <pre><code class="hljs">mv apache-zookeeper-3.5.9-bin/ zk-node-1
mv apache-zookeeper-3.5.9-bin/ zk-node-2
mv apache-zookeeper-3.5.9-bin/ zk-node-3
</code></pre>
 </li>
 <li>
  <p>创建data目录</p>
  <pre><code class="hljs">mkdir zk-node-1/data
mkdir zk-node-2/data
mkdir zk-node-3/data
</code></pre>
 </li>
 <li>
  <p>创建myid文件，这个id不能重复</p>
  <pre><code class="hljs">echo 1 &gt; zk-node1/myid
echo 2 &gt; zk-node2/myid
echo 3 &gt; zk-node3/myid
</code></pre>
 </li>
 <li>
  <p>分别修改zoo.cfg文件，配置data所在目录，如：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7f84490d99e64ffcb8d19dd730a15eac.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>分别在zoo.cfg文件的末尾添加(server.后面的id一定要与myid文件里面的配置保持一致！)</p>
  <pre><code class="hljs"> server.1=192.168.10.75:2999:3999
 server.2=192.168.10.76:2999:3999
 server.3=192.168.10.77:2999:3999
</code></pre>
 </li>
 <li>
  <p>分别添加sh文件可执行权限</p>
  <pre><code class="hljs">chmod +x zk-node-1/bin/*.sh
chmod +x zk-node-2/bin/*.sh
chmod +x zk-node-3/bin/*.sh
</code></pre>
 </li>
 <li>
  <p>启动每一个zk节点</p>
  <pre><code class="hljs">./zkServer.sh start
</code></pre>
 </li>
 <li>
  <p>查看节点角色</p>
  <pre><code class="hljs">./zkServer.sh status
</code></pre>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbc34a3d01f8f48c88c6057f48acb2298.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p><a href="https://blog.csdn.net/u011236069/article/details/122242739?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_ecpm_v1~rank_v31_ecpm-1-122242739-null-null.pc_agg_new_rank&amp;utm_term=zookeeper%20%E6%9F%A5%E7%9C%8B%20%E7%89%88%E6%9C%AC&amp;spm=1000.2123.3001.4430" title="查看zk版本">查看zk版本</a></p>
  <pre><code class="hljs">yum -y install nc               #安装nc命令
4lw.commands.whitelist=*        #在zoo.cfg配置文件中添加该项配置
echo stat|nc 127.0.0.1 2181     #查看版本
</code></pre>
  <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb15c3ec543b245f9bb54ba8862fa0c8f.png&amp;size=m" alt=""></p>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/qq_27683317/article/details/142612240</p>]]></description><guid isPermaLink="false">/archives/fe503545-99b0-4ae0-bd34-6bcdc182a17c</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:19:28 GMT</pubDate></item><item><title><![CDATA[SpringBoot集成Redis及SpringCache缓存管理]]></title><link>https://wiki.dgfygk.com/archives/03b3fafe-8e0a-4259-b3e7-ad592625fc45</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E9%9B%86%E6%88%90Redis%E5%8F%8ASpringCache%E7%BC%93%E5%AD%98%E7%AE%A1%E7%90%86&amp;url=/archives/03b3fafe-8e0a-4259-b3e7-ad592625fc45" width="1" height="1" alt="" style="opacity:0;">
<h3>1.集成Redis</h3>
<h4>1.导入依赖</h4>
<pre><code class="XML">&lt;!--spirngboot springdata对redis支持--&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h4>2.配置信息</h4>
<pre><code>#数据源配置
spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password: 123456
    jedis:
      pool:
        max-wait: 2000ms
        max-active: 8
</code></pre>
<h4>3.注入redisTemplate使用</h4>
<pre><code class="java">@RunWith(SpringRunner.class)  //使用springboot2.6.13的版本可以省略这个注解
@SpringBootTest(classes = App.class) //如果测试类和启动不在同一个包下classes必须要写
public class SpringbootDataRedisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void test() throws Exception{
        redisTemplate.opsForValue().set("name","zs");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }
}
</code></pre>
<h4>4.自定义配置redis中key和value的序列化</h4>
<pre><code class="java">@Configuration
public class RedisConfig {

    // 注入Redis连接工厂
    @Resource
    private RedisConnectionFactory factory;

    /**
     * @Description: 自定义RedisTemplate对象注入Bean容器中
     * @Author: Neuronet
     * @Date: 2023/10/22 18:49
     **/
    @Bean
    public RedisTemplate&lt;Object, Object&gt; redisTemplate() {
        // 1.创建一个RedisTemplate对象
        RedisTemplate&lt;Object, Object&gt; redisTemplate = new RedisTemplate&lt;&gt;();
        // 2.指定Redis连接工厂
        redisTemplate.setConnectionFactory(factory);
        // 3.创建一个JSON格式序列化对象，此处使用的是Redis自己的的序列化器
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        // 4.指定除开hash数据类型之外的数据key和value使用什么序列化器
        redisTemplate.setKeySerializer(serializer);
        redisTemplate.setValueSerializer(serializer);
        // 5.指定hash数据类型的key和value序列化器
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        return redisTemplate;
    }
} 
</code></pre>
<h3>2.SpringCahe缓存管理</h3>
<h4>1. SpringCache概述</h4>
<p>Spring 3.1 引入了激动人心的基于注解（annotation）的缓存（cache）技术，它本质上不是一个具体的缓存实现方案（比如EHCache 或者 OSCache），而是一个对缓存使用的抽象，通过在既有代码中加入少量它定义的各种 annotation，即能够达到缓存方法的返回对象的效果</p>
<p>Spring 的缓存技术还具备相当的灵活性。不仅能够使用 SpEL（Spring Expression Language）来定义缓存的 key 和各种 condition，还提供开箱即用的缓存暂时存储方案，也支持和主流的专业缓存比如 EHCache 集成</p>
<p>说人话：SpringCahce对缓存流程进行了简化封装，提供了一些注解，我们通过简单的打注解就能实现缓存的添加，修改，删除等，注解如下：</p>
<ul>
 <li>
  <p>@Cacheable：触发缓存写入</p>
 </li>
 <li>
  <p>@CacheEvict：触发缓存清除</p>
 </li>
 <li>
  <p>@CachePut：更新缓存(不会影响到方法的运行)</p>
 </li>
 <li>
  <p>@Caching：重新组合要应用于方法的多个缓存操作</p>
 </li>
 <li>
  <p>@CacheConfig：设置类级别上共享的一些常见缓存设置</p>
 </li>
</ul>
<h4>2.SpringCache大致原理</h4>
<ul>
 <li>
  <p>1.Spring Cache利用了AOP，实现了基于注解的缓存功能，并且进行了合理的抽象，业务代码不用关心底层是使用了什么缓存框架，只需要简单地加一个注解，就能实现缓存功能了，做到了对代码侵入性做小。</p>
 </li>
 <li>
  <p>2.由于市面上的缓存工具实在太多，SpringCache框架还提供了CacheManager接口，可以实现降低对各种缓存框架的耦合。它不是具体的缓存实现，它只提供一整套的接口和代码规范、配置、注解等，用于整合各种缓存方案，比如Caffeine、Guava Cache、Ehcache。 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fc2836e9bed8d0793cc381f8e36375f91.png&amp;size=m" alt=""></p>
 </li>
</ul>
<h4>3.SpringCache注解</h4>
<h5>3.1. @Cacheable：写缓存</h5>
<ul>
 <li>方式一:@Cacheable可以用来进行缓存的写入，先也会根据缓存名和key去查询，<code>如果没有查询到，自动将方法的返回结果存储在缓存中，以便于在后续调用的时候可以直接返回缓存中的值，而不必再执行实际的方法</code></li>
</ul>
<pre><code class="java">@Cacheable(cacheNames="books",key="'book1'")   // books::book1
public Book findBook(ISBN isbn) {.
  //查询数据库
  return 数据；
}
</code></pre>
<ul>
 <li>方式二：一个方法可以对应多个缓存名称</li>
</ul>
<pre><code class="java">@Cacheable(cacheNames={"books", "isbns"},key="'book1'")
public Book findBook(ISBN isbn) {.....}
</code></pre>
<ul>
 <li>方式三@Cacheable的缓存名称是可以配置动态参数的，比如选择传入的参数,如下: (以下示例是使用SpEL声明,如果您不熟悉SpEL，可以阅读<a href="https://www.yuque.com/yuqueyonghu5ixaoq/ogzo0v/zfg45n9kw47qox9h#expressions" title="Spring Expression Language">Spring Expression Language</a>)</li>
</ul>
<pre><code class="java">@Cacheable(cacheNames="books", key="#isbn") //参数值作为Key
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)


@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
</code></pre>
<ul>
 <li>方式四</li>
</ul>
<p>@Cacheable还可以设置根据条件判断是否需要缓存</p>
<pre><code class="java">condition:取决于给定的参数是否满足条件
unless:取决于返回值是否满足条件(除非)
</code></pre>
<p>以下是一个简单的例子：key有默认的名字</p>
<pre><code class="java">@Cacheable(cacheNames="book", condition="#name.length() &lt; 32") 
public Book findBook(String name)

@Cacheable(cacheNames="book",condition="#name.length()&lt;32", unless="#result.hardback") 
public Book findBook(String name)
</code></pre>
<h5>3.2. @CachePut：修改缓存</h5>
<p>@CachePut:当需要更新缓存而不干扰方法的运行时 ，可以使用该注解。也就是说，<code>始终执行该方法，并将结果放入缓存</code>(已经有缓存就更新缓存)，注解参数与@Cacheable相同。 以下是一个简单的例子:</p>
<pre><code class="java">@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor) {

}
</code></pre>
<p>通常强烈建议不要对同一方法同时使用@CachePut和@Cacheable注解，因为它们具有不同的行为。可能会产生不可思议的BUG哦</p>
<h5>3.3. @CacheEvict：删除缓存</h5>
<p>@CacheEvict:删除缓存的注解,这对删除旧的数据和无用的数据是非常有用的。这里还多了一个参数(allEntries),设置allEntries=true时，可以对整个条目进行批量删除。 以下是个简单的例子:</p>
<pre><code class="java">@CacheEvict(cacheNames="books", key="'book1'") 
public void loadBooks(InputStream batch)

//对cacheNames进行批量删除
@CacheEvict(cacheNames="books", allEntries=true) 
public void loadBooks(InputStream batch)
</code></pre>
<h5>3.4. @Caching：组合注解</h5>
<p>@Caching在使用缓存的时候，有可能会同时进行更新和删除，会出现同时使用多个注解的情况。而@Caching可以实现。 以下是个简单的例子：</p>
<pre><code class="java">@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
</code></pre>
<h5>3.5. @CacheConfig: 统一配置</h5>
<p>@CacheConfig：缓存提供了许多的注解选项，但是有一些公用的操作，我们可以使用@CacheConfig在类上进行全局设置。 以下是个简单的例子：</p>
<pre><code class="java">@CacheConfig(cacheName="books") 
public class BookRepositoryImpl implements BookRepository {
    //注意：如果没有指定key的话，keyGenerator会自动生成key导致删除缓存失败，所以下面的缓存操作需要指定key
    @Cacheable(key="'book1'")
    public Book findBook(ISBN isbn) {...}
}
</code></pre>
<h4>4.SpringCache实战</h4>
<h5>1.导入依赖</h5>
<pre><code class="XML">&lt;!-- SpringCache依赖 --&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h5>2.配置SpringCache集成redis</h5>
<pre><code class="java">/**
 * @Description: SpringCache配置
 * @Author: Neuronet
 * @Date: 2023/10/22 19:32
 **/
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    @Resource
    private RedisConnectionFactory factory;

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
        return new SimpleCacheErrorHandler();
    }

    //缓存管理器
    @Bean
    @Override
    public CacheManager cacheManager() {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                        .disableCachingNullValues() //不允许空值
                        .serializeValuesWith(RedisSerializationContext.SerializationPair
                                             //值使用JSON序列化
                                             .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
    }
}
</code></pre>
<h5>3.开启springCache</h5>
<pre><code class="java">在启动类注解：@EnableCaching
</code></pre>
<h5>4.添加缓存</h5>
<p>缓存注解不能加在内部方法上，比如：方法A调用方法B，给方法B加上缓存注解会失效，因为内部方法调用代理会失效，在A方法上打注解即可</p>
<pre><code class="java">//会先根据articleType::typeTree从redis获取，如果有直接用缓存的。如果没有就执行方法体的代码查询数据库
@Cacheable(cacheNames = "articleType", key = "'typeTree'")
public List&lt;ArticleType&gt; getTypeTree() {
    Object obj = redisTemplate.opsForValue().get("typeTree");
        if(obj == null){//redis没有
            List&lt;ArticleType&gt; typeTree = typeTree();
            redisTemplate.opsForValue().set("typeTree",typeTree);
            return typeTree;
        }
    return typeTree();
}
</code></pre>
<p>原文链接: https://blog.csdn.net/LG_971124/article/details/142564610</p>]]></description><guid isPermaLink="false">/archives/03b3fafe-8e0a-4259-b3e7-ad592625fc45</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:19:15 GMT</pubDate></item><item><title><![CDATA[【JVM】双亲委派模型]]></title><link>https://wiki.dgfygk.com/archives/d7d5bb7b-5b0c-4135-97ff-332bfbae7269</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90JVM%E3%80%91%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B&amp;url=/archives/d7d5bb7b-5b0c-4135-97ff-332bfbae7269" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_2">双亲委派模型（面试高频）</a></li>
 <li><a href="https://wiki.dgfygk.com#_9">类加载器</a></li>
 <li><a href="https://wiki.dgfygk.com#_37">工作过程</a></li>
 <li><a href="https://wiki.dgfygk.com#_66">存在意义</a></li>
</ul>
<p>
 <br>
</p>
<h2>双亲委派模型（面试高频）</h2>
<p><em>之所以这个东西面试出场概率高高，最大的原因就是它起的名字很好听</em></p>
<hr>
<p>描述了查找 <code>.class</code> 文件的策略。</p>
<h2>类加载器</h2>
<p><code>JVM</code> 中进行类加载的操作，是有一个专门的模块，称为"类加载器”（<code>ClassLoader</code>）。<code>JVM</code> 中的类加载器默认是由三个（也可以自定义个数）</p>
<p>类加载器的作用，给它一个"权限定类名”（带有包的类名（<code>java. lang. String</code>）），给了之后，就找到对应的 <code>.class</code> 文件。这里的类加载器就是从不同的目录中进行查找</p>
<ol>
 <li>
  <p><code>BootstrapClassLoader</code></p>
 </li>
 <li>
  <p>负责查找标准库的目录</p>
 </li>
 <li>
  <p><code>ExtensionClassLoader</code></p>
 </li>
 <li>
  <p>负责查找扩展库的目录</p>
 </li>
 <li>
  <p><code>Java</code> 语法的规范里面描述了标准库中应该有哪些功能。实现 <code>JVM</code> 的厂商/组织会在标准库的基础上扩充一些额外的功能（<code>JVM</code> 内置的，不同的厂商扩展的可能不太一样）</p>
 </li>
 <li>
  <p>这块内容在上古时期用处比较多，但随着时代的发展，这里的内容很少会使用了</p>
 </li>
 <li>
  <p><code>ApplicationClassLoader</code></p>
 </li>
 <li>
  <p>负责查找当前项目的代码目录，以及第三方库的目录</p>
 </li>
</ol>
<hr>
<p>上述的三个类加载器，存在”<strong>父子关系</strong>” <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fc006a4e03fdb19fdc3b4bbfe3a8fd452.png&amp;size=m" alt=""></p>
<ul>
 <li>不是面向对象中的，父类子类继承关系</li>
 <li>而是类似于"二叉树”。有一个指针（引用）<code>parent</code>，指向自己的"父"类加载器</li>
</ul>
<p>双亲委派模型，就描述了上述类加载器之间是符合配合工作的</p>
<h2>工作过程</h2>
<p>双亲委派模型工作过程：</p>
<ol>
 <li>
  <p>从 <code>ApplicationClassLoader</code> 作为入口，先开始工作</p>
 </li>
 <li>
  <p><code>ApplicationClassLoader</code> 不会立即搜索自己负责的目录，会把搜索的任务交给自己的父亲</p>
 </li>
 <li>
  <p>代码就进入到 <code>ExtensionClassLoader</code> 范畴了，但它也不会立即搜索自己负责的目录，也要把搜索的任务交给自己的父亲</p>
 </li>
 <li>
  <p>代码就进入到 <code>BootstrapClassLoader</code> 范畴了，它也不想立即搜索自己负责的目录，也想把搜索的任务交给自己的父亲</p>
 </li>
 <li>
  <p><code>BooststracpClassLoader</code> 发现自己没有父亲，才会真正搜索负责的目录（标准库目录），通过全限定类名，尝试在标准库目录中找到符合要求的 <code>.class</code> 文件</p>
 </li>
 <li>
  <p>如果找到了，接下来就直接进入到打开文件/读文件等流程中</p>
 </li>
 <li>
  <p>如果没找到，就回到孩子这一辈的类加载器中，继续尝试加载</p>
 </li>
 <li>
  <p><code>ExtensionClassLoader</code> 收到父亲交回给他的任务之后，自己进行搜索负责目录（扩展库的目录）</p>
 </li>
 <li>
  <p>如果找到了，接下来就进入到后续流程</p>
 </li>
 <li>
  <p>如果没找到，也就再回到孩子这一辈的类加载器中继续尝试加载</p>
 </li>
 <li>
  <p><code>ApplicationClassLoader</code> 收到父亲交回给他的任务之后，自己进行搜索负责的目录（当前项目目录/第三方库目录）</p>
 </li>
 <li>
  <p>如果找到了，接下来就进入后续流程</p>
 </li>
 <li>
  <p>如果没找到，也是回到孩子这一辈的类加载器中尝试继续加载。但是由于默认情况下 <code>ApplicationClassLoader</code> 没有孩子了，此时说明类加载过程失败了，就会抛出 <code>ClassNotFoundException</code> 异常</p>
 </li>
</ol>
<hr>
<h2>存在意义</h2>
<p>上述设定的最主要的目的就是为了确保这几个类加载器之间的<strong>优先级</strong></p>
<p>按照上述的顺序，假定在代码中自己定义了一个 <code>java.lang.String</code> 这样的类。最终程序执行效果是：自定义的类不会被 <code>JVM</code> 加载</p>
<ul>
 <li>因为当我们拿着这个类给类加载器找的时候，先 <code>BootstrapClassLoader</code> 到标准库中找，就找到了这个类，直接就从标准库中加载了，就不会回到 <code>ApplicationClassLoader</code> （你自己写的代码中）这一层进行加载
  <br>
  <strong>设定就是为了避免你写的类的名字和标准库的重复了，导致标准库的类的功能失效</strong></li>
</ul>
<p>原文链接: https://blog.csdn.net/Yeeear/article/details/142486575</p>]]></description><guid isPermaLink="false">/archives/d7d5bb7b-5b0c-4135-97ff-332bfbae7269</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:18:42 GMT</pubDate></item><item><title><![CDATA[[Redis][哨兵][下]详细讲解]]></title><link>https://wiki.dgfygk.com/archives/6cecd066-236f-444f-9926-9a1c8d769abe</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%5BRedis%5D%5B%E5%93%A8%E5%85%B5%5D%5B%E4%B8%8B%5D%E8%AF%A6%E7%BB%86%E8%AE%B2%E8%A7%A3&amp;url=/archives/6cecd066-236f-444f-9926-9a1c8d769abe" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#1Docker_3">1.安装部署(基于Docker)</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1Redis_5">1.编排Redis主从节点</a></li>
 <li><a href="https://wiki.dgfygk.com#2RedisSentinel_39">2.编排Redis-Sentinel节点</a></li>
 <li><a href="https://wiki.dgfygk.com#2_112">2.重新选举</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#1redismaster_114">1.redis-master宕机之后</a></li>
 <li><a href="https://wiki.dgfygk.com#2redismaster_127">2.redis-master重启之后</a></li>
 <li><a href="https://wiki.dgfygk.com#3_134">3.总结</a></li>
 <li><a href="https://wiki.dgfygk.com#3_140">3.选举原理</a></li>
 <li><a href="https://wiki.dgfygk.com#4_182">4.总结</a></li>
</ul>
<p>
 <br>
</p>
<hr>
<h2>1.安装部署(基于Docker)</h2>
<h3>1.编排Redis主从节点</h3>
<ul>
 <li>
  <p><strong>编写<code>docker-compose.yml</code></strong></p>
 </li>
 <li>
  <p>创建<code>/root/redis/docker-compose.yml</code>，同时<code>cd</code>到<code>yml</code>所在⽬录中</p>
 </li>
 <li>
  <p><strong>说明</strong>：docker中可以通过容器名字作为ip地址，进⾏相互之间的访问</p>
  <pre><code class="prism language-plaintext">version: '3.7'
services:
master:
    image: 'redis:5.0.9'
    container_name: redis-master
    restart: always
    command: redis-server --appendonly yes
    ports: 
        - 6379:6379
slave1:
    image: 'redis:5.0.9'
    container_name: redis-slave1
    restart: always
    command: redis-server --appendonly yes --slaveof redis-master 6379
    ports:
        - 6380:6379
slave2:
    image: 'redis:5.0.9'
    container_name: redis-slave2
    restart: always
    command: redis-server --appendonly yes --slaveof redis-master 6379
    ports:
        - 6381:6379
</code></pre>
 </li>
 <li>
  <p><strong>启动所有容器</strong> ：<code>docker-compose up -d</code></p>
 </li>
 <li>
  <p><strong>查看运行日志</strong> ：<code>docker-compose logs</code></p>
 </li>
</ul>
<hr>
<h3>2.编排Redis-Sentinel节点</h3>
<ul>
 <li>
  <p><strong>说明</strong> ：可以把<code>redis-sentinel</code>放到和上⾯的Redis的同⼀个<code>yml</code>中进⾏容器编排，<strong>但此处分成两组，主要是为了两⽅⾯</strong> ：</p>
 </li>
 <li>
  <p>观察⽇志⽅便</p>
 </li>
 <li>
  <p>确保Redis主从节点启动之后才启动<code>redis-sentinel</code></p>
  <ul>
   <li>如果先启动<code>redis-sentinel</code>的话，可能触发额外的选举过程，混淆视听</li>
   <li>不是说先启动哨兵不⾏，⽽是观察的结果可能存在⼀定随机性</li>
  </ul>
 </li>
 <li>
  <p><strong>编写<code>docker-compose.yml</code></strong></p>
 </li>
 <li>
  <p>创建<code>/root/redis-sentinel/docker-compose.yml</code> ,同时<code>cd</code>到<code>yml</code>所在⽬录中</p>
  <pre><code class="prism language-plaintext">version: '3.7'
services:
sentinel1:
    image: 'redis:5.0.9'
    container_name: redis-sentinel-1
    restart: always
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
        - ./sentinel1.conf:/etc/redis/sentinel.conf
    ports:
        - 26379:26379
sentinel2:
    image: 'redis:5.0.9'
    container_name: redis-sentinel-2
    restart: always
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
        - ./sentinel2.conf:/etc/redis/sentinel.conf
    ports:
        - 26380:26379
sentinel3:
    image: 'redis:5.0.9'
    container_name: redis-sentinel-3
    restart: always
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
        - ./sentinel3.conf:/etc/redis/sentinel.conf
    ports:
        - 26381:26379
networks:
default:
external:
name: redis-data_default
</code></pre>
 </li>
 <li>
  <p><strong>创建配置文件</strong> ：创建<code>sentinel1.conf</code>、<code>sentinel2.conf</code>、<code>sentinel3.conf</code>，三分文件的内容是完全相同的，都放在<code>/root/redis-sentinel/</code>⽬录中</p>
  <pre><code class="prism language-plaintext">bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2
sentinel down-after-milliseconds redis-master 1000
</code></pre>
 </li>
 <li>
  <p><strong>理解<code>sentinel monitor</code></strong> ：</p>
  <ul>
   <li><strong>主节点名</strong>：这个是哨兵内部⾃⼰起的名字</li>
   <li><strong>主节点ip</strong> ：部署<code>redis-master</code>的设备ip</li>
   <li>此处由于是使⽤docker，可以直接写docker的容器名，会被⾃动DNS成对应的容器ip</li>
   <li><strong>法定票数</strong> ：哨兵需要判定主节点是否挂了，但是有的时候可能因为特殊情况</li>
   <li>⽐如主节点仍然⼯作正常，但是哨兵节点⾃⼰⽹络出问题了，⽆法访问到主节点了</li>
   <li>此时就可能会使该哨兵节点认为主节点下线，出现误判</li>
   <li><strong>使⽤投票的⽅式来确定主节点是否真的挂了是更稳妥的做法</strong></li>
   <li>需要多个哨兵都认为主节点挂了，<strong>票数 &gt;= 法定票数之后，才会真的认为主节点是挂了</strong></li>
  </ul>
  <pre><code class="prism language-plaintext">sentinel monitor 主节点名 主节点 ip 主节点端⼝ 法定票数
</code></pre>
 </li>
 <li>
  <p><strong>理解<code>sentinel down-after-milliseconds</code></strong> ：</p>
  <ul>
   <li>主节点和哨兵之间通过⼼跳包来进⾏沟通</li>
   <li>如果⼼跳包在指定的时间内还没回来，就视为是节点出现故障</li>
  </ul>
 </li>
 <li>
  <p><strong>既然内容相同，为啥要创建多份配置⽂件?</strong></p>
  <ul>
   <li><code>redis-sentinel</code>在运⾏中可能会对配置进⾏<code>rewrite</code>，修改⽂件内容</li>
   <li>如果⽤⼀份⽂件，就可能出现修改混乱的情况</li>
  </ul>
 </li>
 <li>
  <p><strong>启动所有容器</strong> ：<code>docker-compose up -d</code></p>
 </li>
 <li>
  <p><strong>查看运行日志</strong> ：<code>docker-compose logs</code></p>
 </li>
</ul>
<hr>
<h2>2.重新选举</h2>
<h3>1.redis-master宕机之后</h3>
<ul>
 <li>
  <p><strong>哨兵发现了主节点<code>sdown</code>，进⼀步的由于主节点宕机得票达到 2 / 3 2/3 2/3，达到法定得票，于是<code>master</code>被判定为<code>odown</code></strong></p>
 </li>
 <li>
  <p><strong>主观下线(Subjectively Down，SDown)</strong>：哨兵感知到主节点没⼼跳了，判定为主观下线</p>
 </li>
 <li>
  <p><strong>客观下线(Objectively Down，ODown)</strong> ：多个哨兵达成⼀致意⻅，才能认为<code>master</code>确实下线了
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9b21ef23d0ed46a9b22a4bab6868c76c.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p><strong>接下来，哨兵们挑选出了⼀个新的<code>master</code>，上图中，是<code>172.22.04:6379</code>这个节点</strong>
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3402d80957a34d2c97303bd6cd1e8072.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>此时，对于Redis来说仍然是可以继续使用的</p>
 </li>
</ul>
<hr>
<h3>2.redis-master重启之后</h3>
<ul>
 <li><strong>哨兵日志</strong> ：刚才新启动的<code>redis-master</code>被当成了<code>slave</code>
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6e79cff9f6cf443aba3f44a8e44e41b7.png&amp;size=m" alt=""></li>
</ul>
<hr>
<h3>3.总结</h3>
<ul>
 <li>Redis主节点如果宕机，哨兵会把其中的⼀个从节点，提拔成主节点</li>
 <li>当之前的Redis主节点重启之后，这个主节点被加⼊到哨兵的监控中，但是只会被作为从节点使⽤</li>
</ul>
<hr>
<h2>3.选举原理</h2>
<ul>
 <li>
  <p>假定生产环境如下
   <br>
   <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2d406f662b6c448e8ab37cd9c233808e.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p><strong>主观下线</strong> ：</p>
 </li>
 <li>
  <p>当<code>redis-master</code>宕机，此时<code>redis-master</code>和三个哨兵之间的⼼跳包就没有了</p>
 </li>
 <li>
  <p>此时，站在三个哨兵的⻆度来看，<code>redis-master</code>出现严重故障，因此三个哨兵均会把<code>redis-master</code>判定为主观下线(<code>SDown</code>)</p>
 </li>
 <li>
  <p><strong>客观下线</strong> ：</p>
 </li>
 <li>
  <p>此时，哨兵<code>sentenal1, sentenal2, sentenal3</code>均会对主节点故障这件事情进⾏投票</p>
 </li>
 <li>
  <p>当故障得票数 &gt;= 配置的法定票数之后，意味着<code>redid-master</code>故障这个事情被坐实，此时触发客观下线(<code>ODown</code>)</p>
  <pre><code class="prism language-plaintext"> sentinel monitor redis-master 172.22.0.4 6379 2
</code></pre>
 </li>
 <li>
  <p><strong>选举出哨兵的<code>leader</code></strong></p>
 </li>
 <li>
  <p>接下来需要哨兵把剩余的<code>slave</code>中挑选出⼀个新的<code>master</code>，<strong>这个⼯作不需要所有的哨兵都参与，只需要选出个代表(称为leader)，由leader负责进⾏slave升级到master的提拔过程</strong></p>
 </li>
 <li>
  <p>这个选举过程涉及到<code>Raft</code>算法，假定一共三个哨兵节点S1，S2，S3</p>
  <ul>
   <li>每个哨兵节点都给其他所有哨兵节点，发起⼀个”<strong>拉票请求</strong> "</li>
   <li>S1 -&gt; S2, S1 -&gt; S3, S2 -&gt; S1, S2 -&gt; S3，S3 -&gt; S1, S3 -&gt; S2</li>
   <li>收到拉票请求的节点，会回复⼀个 “投票响应”，响应的结果有两种可能, 投or不投</li>
   <li><strong>例如</strong>：S1给S2发了个投票请求，S2就会给S1返回投票相应</li>
   <li>S2是否要投S1，取决于S2是否给别人投过票了(每个哨兵只有一票)</li>
   <li>如果没投过(S1是第一个向S2拉票的)，S2就会投给S1，否则则不投</li>
   <li>一轮投票完成之后，发现得票超过半数的节点，自动成为<code>leader</code></li>
   <li>如果出现平票，则重新再投一次即可</li>
   <li><strong>这也是为啥建议哨兵节点设置成奇数个的原因，如果为偶数个，则增大了平票的概率，带来了不必要的开销</strong></li>
   <li><code>leader</code>节点负责挑选⼀个<code>slave</code>成为新的<code>master</code>，当其他的<code>sentenal</code>发现新的<code>master</code>出现了，就说明选举结束了</li>
  </ul>
 </li>
 <li>
  <p><strong>综上</strong> ，<code>Raft</code>算法的核心就是"先下手为强”，谁率先发出了拉票请求，谁就有更大的概率成为<code>leader</code></p>
  <ul>
   <li><strong>决定因素</strong>：网络延时，其本身就带有一定的随机性</li>
  </ul>
 </li>
 <li>
  <p>具体选出的哪个节点是<code>leader</code>不重要，重要的是能选出一个节点</p>
 </li>
 <li>
  <p><strong><code>leader</code>挑选出合适的<code>slave</code>成为新的<code>master</code></strong></p>
 </li>
 <li>
  <p><strong>挑选规则</strong> ：</p>
  <ol>
   <li><strong>比较优先级</strong> ，优先级高(数值小的)的上位
    <ul>
     <li>优先级是配置文件中的配置项<code>slave-priority</code>或者<code>replica-priority</code></li>
    </ul></li>
   <li><strong>比较<code>replication offset</code></strong>，谁复制的数据多，高的上位</li>
   <li>**比较<code>run id**，谁的</code>id`小，谁上位
    <ul>
     <li><strong>即</strong>：大小全凭缘分，选谁都可以，随便挑一个</li>
    </ul></li>
  </ol>
 </li>
 <li>
  <p>当某个<code>slave</code>节点被指定为<code>master</code>之后</p>
  <ul>
   <li><code>leader</code>指定该节点执行<code>slave no one，成为</code>master`</li>
   <li><code>leader</code>指定剩余的<code>slave</code>节点，都依附于这个新<code>master</code></li>
  </ul>
 </li>
</ul>
<hr>
<h2>4.总结</h2>
<ul>
 <li>上述过程，都是”⽆⼈值守”，Redis ⾃动完成的，这样做就解决了主节点宕机之后需要⼈⼯⼲预的问题，提⾼了系统的稳定性和可⽤性</li>
 <li><strong>注意事项</strong> ：</li>
 <li>哨兵节点不能只有一个，否则哨兵节点挂了也会影响系统可用性
  <ul>
   <li><strong>分布式系统中，应该避免使用"单点”，应该有冗余</strong></li>
  </ul></li>
 <li>哨兵节点最好是奇数个，方便选举<code>leader</code>，得票更容易超过半数
  <ul>
   <li>大部分情况下，3个就足够了</li>
  </ul></li>
 <li>哨兵节点不负责存储数据，仍然是Redis主从节点负责存储
  <ul>
   <li>哨兵节点可以使用一些配置不高的机器来部署</li>
  </ul></li>
 <li>哨兵 + 主从复制解决的问题是"提高可用性”，不能解决"数据极端情况下写丢失"的问题</li>
 <li>哨兵 + 主从复制不能提高数据的存储容量，当需要存的数据接近或者超过机器的物理内存，这样的结构就不能胜任了
  <ul>
   <li><strong>为了能存储更多的数据，就引入了集群</strong></li>
  </ul></li>
</ul>
<p>原文链接: https://blog.csdn.net/qq_37281656/article/details/142552417</p>]]></description><guid isPermaLink="false">/archives/6cecd066-236f-444f-9926-9a1c8d769abe</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Sat, 28 Sep 2024 07:18:32 GMT</pubDate></item><item><title><![CDATA[jdk & tomcat 镜像制作]]></title><link>https://wiki.dgfygk.com/archives/2a30fbb7-eb61-4890-9557-a5df9ba0a5bb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=jdk%20%26%20tomcat%20%E9%95%9C%E5%83%8F%E5%88%B6%E4%BD%9C&amp;url=/archives/2a30fbb7-eb61-4890-9557-a5df9ba0a5bb" width="1" height="1" alt="" style="opacity:0;">
<h2>镜像加速地址</h2>
<pre><code>https://docker.aityp.com/manage/add
</code></pre>
<h2>下载jdk</h2>
<pre><code>https://repo.huaweicloud.com/java/jdk/8u172-b11/
</code></pre>
<h2>下载镜像</h2>
<pre><code>docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/centos:7.4.1708
</code></pre>
<h2>jdk的Dockerfile文件</h2>
<pre><code>FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/centos:7.4.1708

LABEL maintainer="huang.jinjin"

ADD jdk-8u172-linux-x64.tar.gz /usr/local/

ENV JAVA_HOME="/usr/local/jdk1.8.0_172"
ENV JAVA_HOME="/usr/local/jdk1.8.0_172/jre"
ENV CLASSPATH="$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar"
ENV PATH="$JAVA_HOME/bin:$PATH"
</code></pre>
<h2>构建镜像</h2>
<pre><code>docker build -t jdk-1.8.0_172:v1 .
</code></pre>
<h2>运行jdk镜像</h2>
<pre><code>docker images | grep jdk

docker run -it  jdk-1.8.0_172:v1 /bin/sh
</code></pre>
<h2>下载tomcat</h2>
<pre><code>wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.95/bin/apache-tomcat-9.0.95.tar.gz
</code></pre>
<h2>tomcat的Dockerfile</h2>
<pre><code>FROM jdk-1.8.0_172:v1
LABEL maintainer="huangjinjin"

ENV WORKDIR="/usr/local"

ADD apache-tomcat-9.0.95.tar.gz $WORKDIR

RUN mv $WORKDIR/apache-tomcat-9.0.95 $WORKDIR/tomcat9 &amp;&amp; chmod +x $WORKDIR/tomcat9/bin/*.sh

ENV CATALINA_HOME="$WORKDIR/tomcat9"
ENV PATH="$CATALINA_HOME/bin:$PATH"

EXPOSE 8080

CMD ["$WORKDIR/tomcat9/bin/catalina.sh", "run"]
</code></pre>
<h2>构建镜像</h2>
<pre><code>docker build -t tomcat9.0.95:v1 .
</code></pre>
<h2>运行镜像</h2>
<pre><code>docker run -d -p 8080:8080 --name tomcat9_test tomcat9.0.95:v1
</code></pre>
<p>原文链接: https://blog.csdn.net/huangjinjin520/article/details/142595022</p>]]></description><guid isPermaLink="false">/archives/2a30fbb7-eb61-4890-9557-a5df9ba0a5bb</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:18:16 GMT</pubDate></item><item><title><![CDATA[Spring Security 详解：保护Java应用的强大盾牌]]></title><link>https://wiki.dgfygk.com/archives/c784abe7-a845-4ac1-9cd6-2e5d8f709eba</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Security%20%E8%AF%A6%E8%A7%A3%EF%BC%9A%E4%BF%9D%E6%8A%A4Java%E5%BA%94%E7%94%A8%E7%9A%84%E5%BC%BA%E5%A4%A7%E7%9B%BE%E7%89%8C&amp;url=/archives/c784abe7-a845-4ac1-9cd6-2e5d8f709eba" width="1" height="1" alt="" style="opacity:0;">
<p>在Java企业级开发中，安全性始终是一个至关重要的考虑因素。Spring Security作为Spring框架的一个重要子项目，提供了一个功能强大、可高度定制的身份验证和访问控制框架，帮助开发者轻松实现应用程序的安全性管理。本文将详细介绍Spring Security的基本概念、核心功能、配置方法以及应用场景。</p>
<h3>一、Spring Security概述</h3>
<p>Spring Security是一个面向Java应用程序的安全框架，它提供了全面的安全性解决方案，包括用户认证、授权、攻击防护（如防止跨站请求伪造CSRF、点击劫持等）和会话管理等。Spring Security能够轻松与Spring框架的其他部分集成，如Spring Boot和Spring Cloud，是Java企业级应用的首选安全框架。</p>
<h3>二、Spring Security的核心概念</h3>
<h4>1. 认证（Authentication）</h4>
<p>认证是指验证用户身份的过程。Spring Security通过多种方式实现认证，包括用户名/密码、OAuth2、JWT等。其核心接口是<code>Authentication</code>，而<code>AuthenticationManager</code>负责验证身份。</p>
<h4>2. 授权（Authorization）</h4>
<p>授权是指用户被允许访问特定资源或执行某些操作的过程。Spring Security通过配置访问控制列表(ACL)、角色(Roles)和权限(Authorities)来管理授权。其核心在于一系列的过滤器(Filters)，这些过滤器构成了一个过滤器链，用于拦截HTTP请求并根据配置的安全规则进行处理。</p>
<h4>3. SecurityContext与SecurityContextHolder</h4>
<p><code>SecurityContext</code>用于存储用户的安全信息（如认证后的用户详情），而<code>SecurityContextHolder</code>是一个存储这些信息的容器。在请求处理的任何地方，都可以通过<code>SecurityContextHolder.getContext()</code>来获取当前用户的安全信息。</p>
<h3>三、Spring Security的核心功能</h3>
<h4>1. 身份验证</h4>
<p>Spring Security支持多种身份验证方式，如用户名/密码、OAuth2.0、JWT等。这些认证方式可以通过配置轻松集成到应用程序中。</p>
<h4>2. 授权控制</h4>
<p>通过配置访问控制列表，Spring Security可以控制哪些用户或角色可以访问特定的资源。它还提供了强大的表达式语言(SpEL)，可以在配置中使用这些表达式来定义访问规则和权限控制。</p>
<h4>3. 攻击防护</h4>
<p>Spring Security提供了防止CSRF、会话固定、点击劫持、跨站点请求伪造等攻击的机制。这些机制可以显著提高应用程序的安全性。</p>
<h4>4. 会话管理</h4>
<p>Spring Security允许控制同一用户同时可以有多少个活动会话。通过配置，可以限制会话数量，并在达到最大会话数时采取相应措施。</p>
<h3>四、Spring Security的配置方法</h3>
<h4>1. 基本配置</h4>
<p>在Spring Boot应用中，首先需要在<code>pom.xml</code>中添加Spring Security的依赖。然后，可以通过创建一个继承自<code>WebSecurityConfigurerAdapter</code>的类来定制Spring Security的行为。</p>
<h4>2. 自定义用户服务</h4>
<p>为了实现自定义的用户信息存储和验证，可以实现<code>UserDetailsService</code>接口并配置到<code>AuthenticationManagerBuilder</code>中。这样，Spring Security就可以使用自定义的用户信息来进行身份验证。</p>
<h4>3. 配置访问控制</h4>
<p>通过重写<code>configure(HttpSecurity http)</code>方法，可以配置哪些URL需要保护，以及这些URL的访问权限。使用SpEL表达式，可以灵活地定义访问规则。</p>
<h3>五、Spring Security的应用场景</h3>
<p>Spring Security广泛应用于各种Java企业级应用中，特别是在需要高度安全性的场景中，如银行系统、电商网站、在线支付平台等。通过Spring Security，开发者可以轻松地实现用户身份验证、授权控制、攻击防护等功能，从而确保应用程序的安全性。</p>
<h3>六、总结</h3>
<p>Spring Security是一个功能强大、可高度定制的身份验证和访问控制框架，它提供了全面的安全性解决方案，帮助开发者简化应用程序的安全性管理。通过简单的配置和扩展，Spring Security可以轻松地满足各种安全需求，成为Java企业级应用的首选安全框架。希望本文能够帮助读者更好地理解和使用Spring Security。</p>
<p>原文链接: https://blog.csdn.net/qq_51321722/article/details/142450146</p>]]></description><guid isPermaLink="false">/archives/c784abe7-a845-4ac1-9cd6-2e5d8f709eba</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:18:02 GMT</pubDate></item><item><title><![CDATA[springboot+阿里云物联网教程]]></title><link>https://wiki.dgfygk.com/archives/3d36ccd0-d369-48aa-8e56-21c3c01de3f9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=springboot%2B%E9%98%BF%E9%87%8C%E4%BA%91%E7%89%A9%E8%81%94%E7%BD%91%E6%95%99%E7%A8%8B&amp;url=/archives/3d36ccd0-d369-48aa-8e56-21c3c01de3f9" width="1" height="1" alt="" style="opacity:0;">
<h2>需求背景</h2>
<p>最近有一个项目，需要用到阿里云物联网，不是MQ。发现使用原来EMQX的代码去连接阿里云MQTT直接报错，试了很多种方案都不行。最终还是把错误分析和教程都整理一下。</p>
<p>需要注意的是，阿里云物联网平台和MQ不一样。方向别走偏了。</p>
<p>
 <br>
</p>
<h2>概念描述</h2>
<p>EMQX和阿里云MQTT有什么区别呢？看下面两张图就行。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbabc66152562403e903fad0d4f7f4167.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F756ccdf9dbd846ff8ffb48d5da374760.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd271fd2dc96d4e23b56234d579f232d8.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fea041e563f754c4390b1b44df451323c.png&amp;size=m" alt=""></p>
<p>—————特别重要———-</p>
<p>—————特别重</p>
<p>原文链接: https://blog.csdn.net/renkai721/article/details/142489429</p>]]></description><guid isPermaLink="false">/archives/3d36ccd0-d369-48aa-8e56-21c3c01de3f9</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:17:47 GMT</pubDate></item><item><title><![CDATA[nginx反向代理tomcat多实例]]></title><link>https://wiki.dgfygk.com/archives/8e97bbe5-eca2-42c2-821a-9f6c9322599f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86tomcat%E5%A4%9A%E5%AE%9E%E4%BE%8B&amp;url=/archives/8e97bbe5-eca2-42c2-821a-9f6c9322599f" width="1" height="1" alt="" style="opacity:0;">
<p>1.tomcat多实例配置_基于端口号
 <br>
 前期需要准备java 环境 这里不作赘述</p>
<pre><code class="prism language-bash"># 下载tomcat源码包
# 清华站点下载
https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.95/bin/apache-tomcat-8.5.95.tar.gz
https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-9/v9.0.82/bin/apache-tomcat-9.0.82.tar.gz
https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-10/v10.1.15/bin/apache-tomcat-10.1.15.tar.gz


[root@localhost tar]# tar xf apache-tomcat-8.5.96.tar.gz 
[root@localhost tar]# ls
apache-tomcat-10.1.24.tar.gz  apache-tomcat-8.5.96.tar.gz       jdk-8u251-linux-x64.tar.gz  jvmtop-0.8.0.tar.gz
apache-tomcat-8.5.96          jdk-11.0.23_linux-x64_bin.tar.gz  jpress-web-newest.war       visualvm_202.zip

[root@localhost tar]# cp -a apache-tomcat-8.5.96 /usr/local/tomcat8.5_01
[root@localhost tar]# cp -a apache-tomcat-8.5.96 /usr/local/tomcat8.5_02

[root@localhost tar]# ll /usr/local/ | grep tomcat
drwxr-xr-x. 9 root root 220 9月  23 11:39 tomcat8.5_01
drwxr-xr-x. 9 root root 220 9月  23 11:39 tomcat8.5_02

[root@localhost tar]# sed -i 's#8005#8011#;s#8080#8081#' /usr/local/tomcat8.5_01/conf/server.xml
[root@localhost tar]# sed -i 's#8005#8012#;s#8080#8082#' /usr/local/tomcat8.5_02/conf/server.xml

[root@localhost tar]# /usr/local/tomcat8.5_01/bin/startup.sh 
[root@localhost tar]# /usr/local/tomcat8.5_02/bin/startup.sh

[root@localhost tar]# ss -tuln | grep 80                                         
tcp    LISTEN     0      100      :::8081                 :::*                  
tcp    LISTEN     0      100      :::8082                 :::*  
</code></pre>
<p>2.nginx 反向代理tomcat</p>
<pre><code class="prism language-bash"># 安装
[root@localhost tar]# yum -y install nginx

[root@localhost tar]# systemctl start nginx

[root@localhost tar]# netstat -lntup | grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      29873/nginx: master 
tcp6       0      0 :::80                   :::*                    LISTEN      29873/nginx: master 

[root@localhost tar]# vim /etc/nginx/conf.d/tomcat.conf 

 upstream tomcatWeb {

        server 192.168.29.200:8081;
        server 192.168.29.200:8082;
    }

    server {

        listen       80;
        server_name  192.168.29.200;
        location / {

            proxy_pass http://tomcatWeb;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {

            root   html;
        }
    }

[root@localhost tar]# echo 8081 &gt; /usr/local/tomcat8.5_01/webapps/ROOT/index.jsp 
[root@localhost tar]# echo 8082 &gt; /usr/local/tomcat8.5_02/webapps/ROOT/index.jsp
[root@localhost tar]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost tar]# systemctl restart nginx

[root@localhost tar]# curl 192.168.29.200
8081
[root@localhost tar]# curl 192.168.29.200
8081
[root@localhost tar]# curl 192.168.29.200
8082
[root@localhost tar]# curl 192.168.29.200
8082
[root@localhost tar]# curl 192.168.29.200
8081
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_43527128/article/details/142454447</p>]]></description><guid isPermaLink="false">/archives/8e97bbe5-eca2-42c2-821a-9f6c9322599f</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:17:30 GMT</pubDate></item><item><title><![CDATA[【网络原理】❤️Tcp 连接管理机制❤️ “三次握手” “四次挥手”的深度理解， 面试最热门的话题，没有之一， 保姆式教学 ！！！]]></title><link>https://wiki.dgfygk.com/archives/7f371a8b-fa31-47a5-88c4-ff8e5e609690</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86%E3%80%91%E2%9D%A4%EF%B8%8FTcp%20%E8%BF%9E%E6%8E%A5%E7%AE%A1%E7%90%86%E6%9C%BA%E5%88%B6%E2%9D%A4%EF%B8%8F%20%E2%80%9C%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E2%80%9D%20%E2%80%9C%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%E2%80%9D%E7%9A%84%E6%B7%B1%E5%BA%A6%E7%90%86%E8%A7%A3%EF%BC%8C%20%E9%9D%A2%E8%AF%95%E6%9C%80%E7%83%AD%E9%97%A8%E7%9A%84%E8%AF%9D%E9%A2%98%EF%BC%8C%E6%B2%A1%E6%9C%89%E4%B9%8B%E4%B8%80%EF%BC%8C%20%E4%BF%9D%E5%A7%86%E5%BC%8F%E6%95%99%E5%AD%A6%20%EF%BC%81%EF%BC%81%EF%BC%81&amp;url=/archives/7f371a8b-fa31-47a5-88c4-ff8e5e609690" width="1" height="1" alt="" style="opacity:0;">
<p>本篇会加入个人的所谓鱼式疯言</p>
<p>❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言</p>
<p>而是理解过并总结出来通俗易懂的大白话,</p>
<p>小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.</p>
<p>🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 ！！！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F23d4544919a241468b10730afd505d71.png&amp;size=m" alt=""></p>
<h2>引言</h2>
<p><em>在数字化时代，我们几乎无时无刻不在与网络进行着无声的对话。无论是在线购物、视频会议，还是简单的网页浏览，背后都离不开一种看似简单却又极其复杂的技术—— 传输控制协议（TCP)。</em></p>
<p><strong>它确保了数据的可靠传输，是网络通信的基石。然而，TCP连接的建立和终止过程，即所谓的 “三次握手"和"四次挥手”，却充满了技术细节和策略考量。本文将深入探讨这一过程，揭示其背后的原理和重要性，为读者提供一个全面的视角，以理解网络通信的这一核心机制。</strong></p>
<h2>目录</h2>
<ol>
 <li>
  <p>三次握手和四次握手的初识</p>
 </li>
 <li>
  <p>三次握手</p>
 </li>
 <li>
  <p>四次挥手</p>
 </li>
</ol>
<h2>一. 三次握手和四次挥手的初识</h2>
<p>在Tcp 中有一种特殊的连接管理机制, 我们称之为 <code>"三次握手， 四次挥手"</code></p>
<p>在Tcp中， 握手和挥手只是代表 <strong>连接</strong> 的含义。</p>
<p>上回我们说到， 对于网络上的连接来说：</p>
<p>连接并不是物理上的用绳子来连接。</p>
<blockquote>
 <p>而是 双方都保存对方 的 <code>信息</code> 称之为 <code>"连接"</code>。
  <br>
  双方都不保存对方 的 <code>信息</code> 称之为 <code>"断开连接"</code> 。
  <br>
  而 <code>三次握手</code> 就是 建立连接 的过程， <code>四次挥手</code> 就是 断开连接 的过程。</p>
</blockquote>
<p>下面跟着小编来深度学习面试常考内容——三次握手，四次挥手吧\~ 💖 💖 💖 💖</p>
<h2>二. 三次握手</h2>
<h3>1. 三次握手的流程步骤</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe14b516c9c324cb0a2ca176da404f4e2.png&amp;size=m" alt=""></p>
<blockquote>
 <p>首先，客户端向服务器发送请求： 会发送一个 <code>Syn</code> 的标志位 的数据报， 并设置 <code>syn = 1</code> ，就是告诉服务器 这个客户端想和它进行连接。
  <br>
  于是， 当服务器接收到了 客户端发来是 <code>Syn</code> 的数据报， 就会开始 <strong>建立连接</strong> ， 并且返回一个 <code>应答报文 Ack</code> 和 一个 <code>Syn</code> 的数据报。 发送 <code>Ack</code> 是代表反馈给 客户端已经接收你需要连接的请求了， 发送 <code>Syn</code> 是告诉客户端： <strong>服务器也想和该客户端建立连接</strong> 。
  <br>
  由于这两次 <code>Syn</code> 和 <code>Ack</code> 是接收到 <strong>客户端发来的 Syn</strong> 时就会发送的， 所以 <code>Tcp</code> 就会把 这<strong>两次发送合并成一次发送</strong> ， 以此提高 连接效率 。
  <br>
  最后当接收到对方的 <code>Ack</code> 就感知到了 <strong>对方已经连接好了</strong> ，同时接收到了 <code>Syn</code> 并且自己也连接好就返回 <code>Ack</code> 给对方， 让对方也感受到 两端都连接好了 。</p>
</blockquote>
<h3>鱼式疯言</h3>
<p><strong>补充细节</strong>：</p>
<ol>
 <li>
  <p>上面的栗子是客户端作为发送方， 但是在一些情况下， 服务器也可以作为发送方。</p>
 </li>
 <li>
  <p><code>syn</code> 全英文： <code>synchronized</code> 翻译： <strong>同步化</strong> ，<strong>锁</strong> 。</p>
 </li>
</ol>
<blockquote>
 <p>在 多线程 中， 是 <code>锁</code> 含义， 用于解决 <strong>线程安全问题</strong> 。
  <br>
  在 Tcp 中， 是 <code>同步化</code> 的含义， 小编的理解就是 ; 用于 同步连接 的消息</p>
 <ol>
  <li>有小伙伴问了， 如果是在 三次握手 的期间， 出现了 <code>丢包情况</code> , 依然还是会进行 <strong>超时重传的</strong> ， 仍然是可以保证三次握手的 <strong>正常进行</strong> 的。</li>
 </ol>
</blockquote>
<h3>2. 三次握手的 “三次”</h3>
<p>有小伙伴就问了？</p>
<blockquote>
 <p>为啥 <strong>三次握手，不是两次握手</strong> 呢？</p>
</blockquote>
<p>其实从上面的流程中， 可以看出 <code>三次握手</code></p>
<blockquote>
 <p>第一次: 客户端 发送 <code>Syn</code> 的数据包来表示希望服务器进行连接, 可以认为是 连接的开始 。
  <br>
  第二次： 服务器返回 <code>Ack</code> 并且发送 <code>Syn</code> 希望对端连接么，可以认为是 连接的进行 。
  <br>
  第三次： 客户端确定接受了上述的 <strong>所有需求并完成</strong> ，所以返回 <code>Ack</code> 表示 ， 可以认为的是 连接的结束 。</p>
</blockquote>
<p>三者缺一不可， 一旦 缺少一步 ， 连接很有 <code>可能会失败</code>。</p>
<p>为啥是 <strong>三次握手， 不是四次握手</strong> 呢？</p>
<p>主要原因还是因为第二次的发送过程， 系统内核为了提升效率，就把需要反馈给 客户端的 <code>Ack</code> 和 发送给 客户端请求连接的 <code>Syn</code> 。</p>
<p>就好比小爱同学很懒， 有一天她在看电视， 突然觉的很渴， 但是自己又不想起来， 就等下次。 等他想上厕所了， 于是就 <strong>上完厕所顺便喝水</strong> ， 从而 <strong>提高效率</strong> 。</p>
<p>就像上面
 <br>
 这里就可以多 <code>减少一次发送的次数</code> ， 从而 加快两个端口的连接 。</p>
<h3>3. 三次握手的优势</h3>
<blockquote>
 <ol>
  <li><strong>投石问路</strong> ， 用于保证通信链路的 完整传输</li>
 </ol>
</blockquote>
<p><strong>栗子说明</strong> ：</p>
<p>不知道小伙伴们有没有坐过早班的 <strong>第一趟地铁</strong> ， 是不是都是没什么人呢！</p>
<p>但是早班的第一趟地铁并不是第一趟的， 在第一趟之前， 还有一趟 <strong>提前开一趟空车</strong>。</p>
<p>开空车的原因： 用于 检查通信链路的完整 ，<strong>保证链路不出意外</strong> ， 如果是空车还能 <strong>及时的调整</strong> ， 如果承载有人的车就会调整就很不方便了， 以此提前的空车就起到 投石问路 的效果</p>
<blockquote>
 <ol>
  <li>确定通信双方的 <code>发送能力</code> 和 <code>接收能力</code></li>
 </ol>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F960390bec538455681d4c2dea1457625.png&amp;size=m" alt=""></p>
<p>假设有一天我和女神 联机打游戏</p>
<p>首先小编作为发送方， 女神作为接收方</p>
<p>我： 喂，听得到吗？</p>
<blockquote>
 <p>当女神听到这句话，就知道了，我的麦克风和女神的耳机是没有问题的 。
  <br>
  然后女神就要回了， 在回我的同时也可以顺便<strong>检查女神的麦克风和我的耳机是否正常。</strong></p>
</blockquote>
<p>女神： 听的到， 你听得到我吗？</p>
<blockquote>
 <p>当我听到这句话了，前面的我问的是， 听得到吗？ 当她回我听得到， 说明<strong>我的麦克风和她的耳机都是没有问题的</strong> ， 然后她再问我听得到吗？ 于是我听到了， 说明 她的麦克风和我的耳机都是没有问题的。</p>
</blockquote>
<p>我说： 听得到，我们可以说话了。</p>
<blockquote>
 <p>最后都说明 <strong>两边的耳机和麦克风都没有问题</strong> ， 最后我要把这个消息传送回女神， 就可以让两边都感知到 <code>连接没有问题</code>， 正常通信 了。</p>
</blockquote>
<p>这里的 <strong>耳机</strong> 就代表 <code>接收能力</code>， 这里的 <strong>麦克风</strong> 就代表 <code>发送能力</code>。</p>
<p>通过上面的 <code>三次握手</code> 的方式， 就能形象的体现确认 发送能力和接收能力 是否 <code>良好</code>。</p>
<blockquote>
 <ol>
  <li><strong>防止前朝的剑斩当朝的官</strong> 即： 防止 过时数据 的传输。</li>
 </ol>
</blockquote>
<p>这句话就是说， 防止因为中间数据包传输 停滞了很久才返回 。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F037f0d9d7e4c43c5814e6717d45e71d5.png&amp;size=m" alt=""></p>
<blockquote>
 <p>上述过程就是，防止有一段 <code>数据包</code> 在 中途迷路 了， 当经过一段期间，
  <br>
  在此的期间，就会出现了 <strong>两台主机的断连</strong> ， 又出现了 两台主机重新连接 ， 但是很有 <code>可能在不同的应用程序</code> 上运行了。
  <br>
  而在三次握手期间，每一次连接都会更新不同的 <code>数据序号包</code> ， 只需要比对 发送过来的数据序号包 和 <code>当前端口的序号</code> 差异是不是很大， <strong>如果很大的话</strong> ， 就说明是 <code>前朝</code> 的数据， 就进行 <strong>直接丢弃</strong>。</p>
</blockquote>
<h3>鱼式疯言</h3>
<p><strong>补充说明</strong>：</p>
<blockquote>
 <p>对于上面提及的 序列号， 并不是说都是从 <code>1</code> 开始的, 而是 <strong>每次连接</strong> 都是会设置 <strong>不同的序列号</strong> ， 以此来 <code>防止过时数据</code> 的发送。</p>
</blockquote>
<h2>三. 四次挥手</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb22b35d709ae4a49b9de00904961a1f6.png&amp;size=m" alt=""></p>
<h3>1. 四次挥手的流程步骤</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd9dcb3a42e3c41709c9f4ddaa52c84cd.png&amp;size=m" alt=""></p>
<p>我们知道四次挥手的过程其实就是 关闭连接 的过程</p>
<p>我们假设主动断开连接的一方为 <code>客户端</code> , 被动断开连接的一方为 <code>服务器</code>。</p>
<p>首先： 由客户端 先设置 <code>fin</code> 的标志位为 <code>1</code> ， <strong>并发送</strong> 给服务器， 希望服务器<strong>断开连接。</strong></p>
<p>然后服务器接受到了 客户端 <code>fin</code> 的标志位为 1 的报文， 于是就触发了 <code>ack</code> 返回给客户端以确认 fin 的发送。</p>
<p>随后服务器也发送一个 <code>fin</code> 以此来表示 服务器也 <strong>希望客户端同时也关闭连接</strong>。</p>
<p>最后 <strong>客户端接收到了</strong> ， 也返回一个带有 <code>ack</code> 的报文。</p>
<h3>鱼式疯言</h3>
<blockquote>
 <p>服务器和客户端都有可能成为 主动发送到一方 或者 <strong>被动发送的一方</strong> 。</p>
</blockquote>
<h3>2. 四次挥手的 “四次”</h3>
<p>有小伙伴问了， 为啥这次偏偏是四次挥手呢？</p>
<p>其实在断开连接的过程中， <code>fin</code> 与 <code>ack</code> 不同的是</p>
<blockquote>
 <p><strong>ack</strong> 是 系统内核 去调度的， 当有一端 <strong>有报文发送时</strong> ， 就会触发 ack 报文的返回给发送的那一方。
  <br>
  而 <code>fin</code> 是 应用程序 调度的， 只有当 程序执行到 close（） 方法/ 进程结束时 ， 才会发送 <code>fin</code> 。
  <br>
  上次的调用过程是不一样的， 所以触发的时间是很有可能不相同， 很有可能被动关闭的一方先接收到 fin 就直接返回ack ， 而程序需要执行多行代码， 才会执行到 close（） ， 才会发送 <code>fin</code> 。</p>
</blockquote>
<p>所以两者一般情况下是<strong>不能合并的</strong> ， 所以才有 中间两次的发送 。</p>
<h3>鱼式疯言</h3>
<p>一般情况下不能合并， 并不是说一定不能合并 ， 在一些特殊的协议中就更有可能出现 <strong>三次挥手</strong> 的情况。</p>
<h3>3. 连接的不同状态</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb22b35d709ae4a49b9de00904961a1f6.png&amp;size=m" alt=""></p>
<p>在不同连接的状态下， Tcp也会呈现的不同状态。 用于确保 建立连接 和 关闭连接 的可靠。</p>
<p>下面小编重要是介绍以下几个状态： <code>ESTABLISHED</code> , <code>CLOSE_WAIT</code> , <code>TIME_WAIT</code> 。</p>
<h4>&lt;1&gt;. ESTABLISHED</h4>
<p>这是两端都 <strong>都建立好了连接</strong> ， 可以进行业务上的 <code>正常通信</code> 的状态。</p>
<p>也就相当于双方都<strong>接通好了电话</strong> ， 可以 <code>正常打电话</code> 的状态。</p>
<h4>&lt;2&gt;. CLOSE_WAIT</h4>
<blockquote>
 <p>这是 <strong>被动关闭连接的一端</strong> ， 就会进入这个状态， 接收到 fin 之后， 等待代码执行到 <code>close</code> 方法。</p>
</blockquote>
<h3>鱼式疯言</h3>
<blockquote>
 <p>如果发送 服务器这端 出现了大量的 <strong>CLOSE_WAIT</strong> 的状态， 就表示代码 <strong>执行不到 close 方法</strong> ， 就说明服务器这端代码出现了 <code>BUG</code> 了。</p>
</blockquote>
<h4>&lt;3&gt;. TIME_WAIT</h4>
<blockquote>
 <p>这是 <strong>主动关闭连接的一端</strong> ， 就会进入这个状态， 就进入 <code>等待系统设置好的一段时间</code> , 延迟过了 系统设置好的时间 才执行 <code>close</code> 方法 。</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fadb7c9b1f6ae491991bdd0d9eb104dfa.png&amp;size=m" alt=""></p>
<blockquote>
 <p>之所以设置一个等待的时间， 就是为了防止返回到 <code>ack 丢包</code> , 如果一旦 <strong>ack 丢包</strong> ， 当 <strong>主动断开连接的一方</strong> 就会在系统设置好的一定时间里等待 ， 对端就会进行 <strong>重传操作</strong> ，重新发送 fin ， 就需要主动断开连接的一方 <strong>重新发送 ack</strong> 。
  <br>
  对于能不能重新发送ack 的前提是， 这端是还没断开连接的， 所以主动连接的一方就需要在一定时间内等待， 保证 连接断开的完整性 。</p>
</blockquote>
<h3>鱼式疯言</h3>
<p><strong>一般而言</strong> ：
 <br>
 设置的等待时间为： 2*MSL</p>
<p><code>MSL</code> 即为 数据包在网络通路传递过程中<strong>所需要消耗的最大时间</strong> ， 所以 2 倍的MSL 是可以检查 <strong>ack 是否被接收的</strong>。</p>
<h2>总结</h2>
<ul>
 <li>
  <p>三次握手和四次挥手的初识： 三次握手是 建立连接 的过程， 四次挥手是 <code>断开连接</code> 的过程, 连接就是通信双方保存对方的信息的过程的理解。</p>
 </li>
 <li>
  <p>三次握手： 三次握手的过程， 以及三次握手为何是三次， 和它的优势： <code>投石问路</code> ， 确定通信双方接收和发送的能力 ， <strong>防止前朝的剑斩当朝的官</strong>。</p>
 </li>
 <li>
  <p>四次挥手： 四次挥手的过程， 已经四次挥手为何是四次， 最后就是 断开连接前后过程中 的<strong>不同的状态表示。</strong></p>
 </li>
</ul>
<blockquote>
 <p>如果觉得小编写的还不错的咱可支持 <strong>三连</strong> 下 (定有回访哦) , 不妥当的咱请评论区 <strong>指正</strong>
  <br>
  希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 <strong>动力</strong> 💖 💖 💖</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff7c58703097442608a2fa36b96464e76.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/mgzdwm/article/details/141710826</p>]]></description><guid isPermaLink="false">/archives/7f371a8b-fa31-47a5-88c4-ff8e5e609690</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:17:12 GMT</pubDate></item><item><title><![CDATA[【HTTPS】中间人攻击和证书的验证]]></title><link>https://wiki.dgfygk.com/archives/08058957-107d-48ad-9dfd-d08e5dc67387</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90HTTPS%E3%80%91%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB%E5%92%8C%E8%AF%81%E4%B9%A6%E7%9A%84%E9%AA%8C%E8%AF%81&amp;url=/archives/08058957-107d-48ad-9dfd-d08e5dc67387" width="1" height="1" alt="" style="opacity:0;">
<h2>中间人攻击</h2>
<p>服务器可以创建出一堆公钥和私钥，黑客也可以按照同样的方式，创建一对公钥和私钥，冒充自己是服务器（<strong>搅屎棍</strong>）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fc50689bfb6a96de471eaaaf6a2ffb869.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F57f627bc03fc993689e16657b70dac47.png&amp;size=m" alt=""></p>
<ul>
 <li>黑客自己也能生成一对公钥和私钥。生成公钥和私钥的算法是开放的，服务器能生产，黑客也能生成</li>
 <li>客户端拿到 <code>pub2</code> 之后，无法区分 <code>pub2</code> 是否是服务器的，于是就拿着 <code>pub2</code> 就对 <code>key</code> 进行加密了</li>
 <li>随后信息到达黑客之后，就使用 <code>pri2</code> 对上述数据进行解密，然后黑客就拿到了 <code>key</code></li>
 <li>黑客继续使用从服务器拿到的 <code>pub1</code> 对 <code>key</code> 进行加密，传给服务器·</li>
 <li>服务器拿到数据之后，使用 <code>pri1</code> 来解密，于是知道了对称密钥是 <code>key</code>。后续的通信，服务器和客户端之间，仍会继续使用 <code>key</code> 作为加密的密钥。此时后续传输的各种数据就可以被黑客解密了</li>
</ul>
<hr>
<h2>引入证书</h2>
<p>针对中间人攻击，怎么解决？</p>
<hr>
<p>最关键的一点，客户端在拿到公钥的时候，要能有办法验证，这个公钥是否是真的，而不是黑客伪造的。这就要求服务器这边，要可以提供一个"证书”</p>
<ul>
 <li>证书是一个结构化的数据（里面包含很多属性，最终以字符串的形式提供）</li>
 <li>证书中会包含一系列的信息（服务器的主域名、公钥、证书有效期…）</li>
</ul>
<p>证书是搭建服务器的人，要从第三方的共振机构进行申请的
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F2c961a4f8bf34b7c6f20866ebde1f2fb.png&amp;size=m" alt=""></p>
<h3>证书的验证</h3>
<p>证书里面包含：</p>
<ul>
 <li>服务器的域名：…</li>
 <li>证书的有效时间：…</li>
 <li>服务器的公钥：…</li>
 <li>公正机构信息：…</li>
 <li>…</li>
 <li><strong>证书的签名：</strong> …（<strong>验证过程中最重要的</strong>）</li>
</ul>
<h4>证书签名</h4>
<p>此处的<strong>签名</strong> 本质上是一个经过加密的<strong>校验和</strong></p>
<ul>
 <li>把证书中其他的字段通过一系列的算法（CRC、MD5 等），得到一个较短的字符串，就是校验和</li>
 <li>如果两份数据内容一样，此时校验和就一定是相同的；如果校验和不同，则两份数据一定不同</li>
</ul>
<p>颁布证书的公证机构，会在颁布证书的时候，给这个证书计算出校验和。然后公正机构会使用自己的私钥（和服务器的私钥无关），针对校验和进行加密，此时就得到了证书的签名</p>
<h4>验证过程</h4>
<p>客户端拿到证书之后，主要做两件事：</p>
<ol>
 <li>按照同样的校验和算法，把证书的其他字段都重新算一遍，得到 <strong>校验和1</strong></li>
 <li>使用<strong>系统中内置</strong> 的公正机构的公钥，对证书中的签名进行解密，得到 <strong>校验和2</strong></li>
</ol>
<p>此时，就可以对比，这两个校验和是否一致。若一致，就说明证书是没有被修改过的，就是原版证书；若不一致，就说明证书被人篡改过了（比如黑客如果替换了自己的公钥，此时出来的校验和一定发生变化），此时客户端就能识别出来了</p>
<ul>
 <li>此时浏览器这边就会弹出一个告警页面，告诉用户，你访问的网站有风险，请谨慎访问！！！（可能是黑客进行了中间人攻击，也可能是证书过期了）</li>
</ul>
<blockquote>
 <p>市面上的公正机构一共也没多少，这些公正机构都有自己的私钥，对应的公钥都包含在常见的系统中。Windows 里面就内置了大量的公钥（如果没有，也可以额外安装）</p>
 <ul>
  <li>前面安装 fiddler 的时候，有一步操作就是在安装证书（主要就是在安装 fiddler 这边提供的公钥）</li>
 </ul>
</blockquote>
<p>证书是为了防止黑客篡改，而不是为了避免黑客知道。黑客的系统也内置了公正机构的公钥，黑客也能进行解密</p>
<ol>
 <li>
  <p>如果黑客直接修改公钥，不修改签名。此时，客户端验证的校验和是一定不一样的，直接就识别出来了</p>
 </li>
 <li>
  <p>如果黑客修改公钥，也尝试重新生成签名，由于黑客不知道公正机构的密钥，所以黑客无法重新生成加密的签名。
   <br>
   如果黑客拿自己的私钥加密呢？客户端这边拿着公证机构的公钥也会解密失败</p>
 </li>
 <li>
  <p>黑客能不能自己也去公证机构申请个证书？然后把自己的整数替换掉服务器的证书呢？
   <br>
   证书中还需要包含服务器的域名，域名是唯一的，黑客申请的证书的域名，和原服务器的域名肯定是不同的。
   <br>
   客户端拿到证书之后，一看域名都不一样，直接就知道证书是假的了，都不用进行校验和验证了</p>
 </li>
</ol>
<hr>
<p>当然，上述的过程，所谓的安全，也不是绝对的安全。上述的安全本质上都是基于非对称加密体系。非对称加密体系也不是无懈可击的，只不过破解这样的加密体系，需要的计算量非常大，超出了现有计算机的算力上限。</p>
<p>随着算力的提升，尤其是量子计算机崛起，我们的算力又会大幅度提升，对现有的密码学体系就会造成重大冲击</p>
<p>原文链接: https://blog.csdn.net/Yeeear/article/details/142438540</p>]]></description><guid isPermaLink="false">/archives/08058957-107d-48ad-9dfd-d08e5dc67387</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:17:03 GMT</pubDate></item><item><title><![CDATA[如何通过内网穿透实现外部网络对Spring Boot服务端接口的HTTP监听和调试？]]></title><link>https://wiki.dgfygk.com/archives/5cdfe506-ce79-47ac-8a01-fd21ea5df7d2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E5%AE%9E%E7%8E%B0%E5%A4%96%E9%83%A8%E7%BD%91%E7%BB%9C%E5%AF%B9Spring%20Boot%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8E%A5%E5%8F%A3%E7%9A%84HTTP%E7%9B%91%E5%90%AC%E5%92%8C%E8%B0%83%E8%AF%95%EF%BC%9F&amp;url=/archives/5cdfe506-ce79-47ac-8a01-fd21ea5df7d2" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_4">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#1__8">1. 本地环境搭建</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#11__10">1.1 环境参数</a></li>
 <li><a href="https://wiki.dgfygk.com#12_springboot_19">1.2 搭建springboot服务项目</a></li>
 <li><a href="https://wiki.dgfygk.com#2__47">2. 内网穿透</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#21_cpolar_51">2.1 安装配置cpolar内网穿透</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#211_windows_55">2.1.1 windows系统</a></li>
   <li><a href="https://wiki.dgfygk.com#212_linux_59">2.1.2 linux系统</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#22__102">2.2 创建隧道映射本地端口</a></li>
 <li><a href="https://wiki.dgfygk.com#23__124">2.3 测试公网地址</a></li>
 <li><a href="https://wiki.dgfygk.com#3__134">3. 固定公网地址</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#31__140">3.1 保留一个二级子域名</a></li>
 <li><a href="https://wiki.dgfygk.com#32__146">3.2 配置二级子域名</a></li>
 <li><a href="https://wiki.dgfygk.com#32__165">3.2 测试使用固定公网地址</a></li>
 <li><a href="https://wiki.dgfygk.com#4_Cpolar_175">4. Cpolar监听器</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#41__179">4.1 开启侦听功能</a></li>
 <li><a href="https://wiki.dgfygk.com#42__189">4.2 请求侦听</a></li>
</ul>
<p>
 <br>
</p>
<h2>前言</h2>
<p>前后端分离项目中，在调用接口调试时候，我们可以通过cpolar内网穿透将本地服务端接口模拟公共网络环境远程调用调试，本次教程我们以Java服务端接口为例。</p>
<ol>
 <li>
  <h2>本地环境搭建</h2>
 </li>
</ol>
<h3>1.1 环境参数</h3>
<ul>
 <li>JDK1.8</li>
 <li>IDEA</li>
 <li>SpringBoot</li>
 <li>Maven</li>
 <li>Tomcat9.0</li>
 <li>Postman</li>
</ul>
<h3>1.2 搭建springboot服务项目</h3>
<p>搭建一个springboot服务的项目,编写一个接口,为了更好直观看到,这里创建一个pos请求的接口</p>
<pre><code class="prism language-shell">@RestController
@RequestMapping("/test")
public class InterfaceTest {


    /**
     * 测试接口
     * @param data
     * @return Map&lt;String,String&gt;
     */
    @PostMapping("/interTest")
    public Map&lt;String,String&gt;interTest(@RequestBody Map&lt;String,String&gt; data){

        System.out.println(data);

        if (data.size()&gt;0){


            return  data;
        }

        data.put("code","404");
        return data;
    }
}
</code></pre>
<ol>
 <li>
  <h2>内网穿透</h2>
 </li>
</ol>
<p>这里我们使用[cpolar](<a href="https://www.cpolar.com/blog">博客 - cpolar 极点云</a>)来进行内网穿透，支持http/https/tcp协议，不限制流量，无需公网IP，也不用设置路由器，使用简单。</p>
<h3>2.1 安装配置cpolar内网穿透</h3>
<blockquote>
 <p>cpolar官网：<a href="https://www.cpolar.com/">https://www.cpolar.com/</a></p>
</blockquote>
<h4>2.1.1 windows系统</h4>
<p>进入cpolar官网后，下载windows版本版本，双击安装包一路默认安装即可。</p>
<h4>2.1.2 linux系统</h4>
<ul>
 <li>cpolar 安装（国内使用）</li>
</ul>
<pre><code class="prism language-shell">curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
</code></pre>
<ul>
 <li>或 cpolar短链接安装方式：(国外使用）</li>
</ul>
<pre><code class="prism language-shell">curl -sL https://git.io/cpolar | sudo bash
</code></pre>
<ul>
 <li>查看版本号，有正常显示版本号即为安装成功</li>
</ul>
<pre><code class="prism language-shell">cpolar version
</code></pre>
<ul>
 <li>token认证</li>
</ul>
<p>登录cpolar官网后台，点击左侧的验证，查看自己的认证token，之后将token贴在命令行里</p>
<pre><code class="prism language-shell">cpolar authtoken xxxxxxx
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fee1abe1aa3791ece168c630f2b0f7f53.png%23pic_center&amp;size=m" alt=""></p>
<ul>
 <li>简单穿透测试</li>
</ul>
<pre><code class="prism language-shell">cpolar http 8080
</code></pre>
<p>按ctrl+c退出</p>
<ul>
 <li>向系统添加服务</li>
</ul>
<pre><code class="prism language-shell">sudo systemctl enable cpolar
</code></pre>
<ul>
 <li>启动cpolar服务</li>
</ul>
<pre><code class="prism language-shell">sudo systemctl start cpolar
</code></pre>
<ul>
 <li>查看服务状态</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa7dc2a3bf5c517051697c61b655c5d1d.png%23pic_center&amp;size=m" alt=""></p>
<h3>2.2 创建隧道映射本地端口</h3>
<p>cpolar安装成功后，在浏览器上访问本地9200端口【<a href="http://localhost:9200">http://localhost:9200</a>】，使用cpolar账号登录。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F59f801a81bb785bcad6031881a688fa3.png%23pic_center&amp;size=m" alt=""></p>
<p>点击左侧仪表盘的隧道管理——创建隧道，创建一个tomcat的8080端口 http隧道</p>
<ul>
 <li>隧道名称：可自定义命名，注意不要与已有的隧道名称重复</li>
 <li>协议：选择http</li>
 <li>本地地址：8080</li>
 <li>域名类型：免费选择随机域名</li>
 <li>地区：选择China vip</li>
</ul>
<p>点击<code>创建</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F611c1772953384a3d43c2ca24419103f.png%23pic_center&amp;size=m" alt=""></p>
<p>隧道创建成功后，点击左侧的状态——在线隧道列表,查看所生成的公网地址，然后复制地址</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff36baefc319276d77f8b3cb533e17ffb.png%23pic_center&amp;size=m" alt=""></p>
<h3>2.3 测试公网地址</h3>
<p>这里以Postman接口调试工具向接口发送请求,在postman创建一个post请求方式.输入复制的公网地址加上接口路径,参数使用JSON格式,设置好参数点击</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb06688954fa207b6da8aef900d09cf57.png%23pic_center&amp;size=m" alt=""></p>
<p>在服务接口端debug调试接口,查看请求是否进入接口,进入接口表示调用成功</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0469335876a29dcab31cf093551920c1.png%23pic_center&amp;size=m" alt=""></p>
<ol>
 <li>
  <h2>固定公网地址</h2>
 </li>
</ol>
<p>由于以上使用cpolar所创建的隧道使用的是随机公网地址，24小时内会随机变化，不利于长期远程访问。因此我们可以为其配置二级子域名，该地址为固定地址，不会随机变化</p>
<blockquote>
 <p>注意需要将cpolar套餐升级至基础套餐或以上，且每个套餐对应的带宽不一样。【cpolar.cn已备案】</p>
</blockquote>
<h3>3.1 保留一个二级子域名</h3>
<p>登录cpolar官网，点击左侧的预留，选择保留二级子域名，设置一个二级子域名名称，点击保留,保留成功后复制保留的二级子域名名称</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F25dd12b0cfc05ff616e9ddfa232fb215.png%23pic_center&amp;size=m" alt=""></p>
<h3>3.2 配置二级子域名</h3>
<p>访问<a href="http://127.0.0.1:9200/">http://127.0.0.1:9200/</a>，登录cpolar web UI管理界面，点击左侧仪表盘的隧道管理——隧道列表，找到所要配置的8080隧道，点击右侧的编辑</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb5de6e7730b29fe66870c042c8f8b18f.png%23pic_center&amp;size=m" alt=""></p>
<p>修改隧道信息，将保留成功的二级子域名配置到隧道中</p>
<ul>
 <li>域名类型：选择二级子域名</li>
 <li>Sub Domain：填写保留成功的二级子域名，本例为test01</li>
</ul>
<p>点击<code>更新</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F04b279b9fc4abd9a1499400898011a0e.png%23pic_center&amp;size=m" alt=""></p>
<p>更新完成后,打开在线隧道列表,此时可以看到公网地址已经发生变化,地址名称也变成了保留过的二级子域名名称,将其复制下来</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4e4189b7d510e1dc3e36b271e41b0bb1.png%23pic_center&amp;size=m" alt=""></p>
<h3>3.2 测试使用固定公网地址</h3>
<p>打开postman,使用固定http地址进行调用</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F18929e8f9b8240d45a1be14cb0028990.png%23pic_center&amp;size=m" alt=""></p>
<p>同样在服务端debug调试查看请求是否进入接口,进入接口表示成功</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdfa61fc4b46859a840f50c7a124892ba.png%23pic_center&amp;size=m" alt=""></p>
<ol>
 <li>
  <h2>Cpolar监听器</h2>
 </li>
</ol>
<p>我们还可以使用cpolar监听器（<a href="http://localhost:4040">http://localhost:4040</a>）查看接口请求日志，处理一个讨厌的bug。甚至可以重播请求消息包，加速测试的请求，单击重放（Replay）按钮，重新发送该HTTP信令请求，而不是手动重新触发操作。下面介绍使用cpolar监听器监听请求。</p>
<h3>4.1 开启侦听功能</h3>
<p>选择我们刚刚创建配置的http隧道，并点击右侧的<code>编辑</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F429c45d15a14a73e6a59599964df2ace.png%23pic_center&amp;size=m" alt=""></p>
<p>打开高级设置,开启侦听功能</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8c0690b52c60ce96615076855657245b.png%23pic_center&amp;size=m" alt=""></p>
<h3>4.2 请求侦听</h3>
<p>在浏览器访问本地4040端口，<a href="http://localhost:4040">http://localhost:4040</a></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffd3f12e35f6971f3cc636c7b2d76f640.png%23pic_center&amp;size=m" alt=""></p>
<p>向服务端发送请求后,此处就会显示相关的请求日志,可以看到请求的方式,请求的数据,接口路径,和返回状态及结果,极大提高了调试效率。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F17238a214e76e6d94e21d3f532f23fad.png%23pic_center&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/ks_wyf/article/details/132559479</p>]]></description><guid isPermaLink="false">/archives/5cdfe506-ce79-47ac-8a01-fd21ea5df7d2</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:16:54 GMT</pubDate></item><item><title><![CDATA[【网络编程通关之路】 Tcp 基础回显服务器(Java实现)及保姆式知识原理详解 ！ ！ ！]]></title><link>https://wiki.dgfygk.com/archives/b2a294dc-356b-448f-8a0e-2020f86cb66e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E9%80%9A%E5%85%B3%E4%B9%8B%E8%B7%AF%E3%80%91%20Tcp%20%E5%9F%BA%E7%A1%80%E5%9B%9E%E6%98%BE%E6%9C%8D%E5%8A%A1%E5%99%A8%28Java%E5%AE%9E%E7%8E%B0%29%E5%8F%8A%E4%BF%9D%E5%A7%86%E5%BC%8F%E7%9F%A5%E8%AF%86%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%20%EF%BC%81%20%EF%BC%81%20%EF%BC%81&amp;url=/archives/b2a294dc-356b-448f-8a0e-2020f86cb66e" width="1" height="1" alt="" style="opacity:0;">
<p>本篇会加入个人的所谓鱼式疯言</p>
<p>❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言</p>
<p>而是理解过并总结出来通俗易懂的大白话,</p>
<p>小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.</p>
<p>🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 ！！！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc08da7c5fe564bc19e70673ba8b8cdfa.png&amp;size=m" alt=""></p>
<h2>引言</h2>
<p><strong>在这个数字化时代，信息的快速流通已成为我们日常生活的一部分。想象一下，当你发送一封电子邮件或浏览网页时，背后是一系列复杂的网络协议在默默工作，确保数据的准确传输。</strong></p>
<p><strong>今天，我们将聚焦于这些协议中的一个核心成员——传输控制协议（TCP）。TCP协议是互联网通信的基石，它通过确保数据的可靠传输，支撑着我们每天使用的无数网络服务。</strong></p>
<h2>目录</h2>
<ol>
 <li>
  <p>Tcp回显服务器的普通实现</p>
 </li>
 <li>
  <p>Tcp 回显服务器的多线程实现</p>
 </li>
 <li>
  <p>Tcp 回显服务器的线程池实现</p>
 </li>
</ol>
<h2>一. Tcp 回显服务器的普通实现</h2>
<h3>1. Tcp 的特点</h3>
<p>在上一篇 实现Udp 的回显服务器的文章中 ， 我们提及Tcp 和 Udp 分别有4个主要的特点</p>
<ul>
 <li>[ ] Tcp 是 <code>可靠</code> 传输， Udp是 <code>不可靠</code> 传输。</li>
 <li>[ ] Tcp是面向 字节流， Udp是面向 数据报。</li>
 <li>[ ] Tcp 是 <code>有连接</code> ， Udp是 <code>无连接</code> 。</li>
 <li>[ ] Tcp 是 全双工 ， Udp也是 全双工 。</li>
</ul>
<p>关于以上这些 <strong>特点</strong> 还有 <code>回显服务器</code> 的详解全部都收录到了 【网络编程通关之路】 Udp 基础回响服务器(Java实现)及你不知道知识原理详解 ！ ！ ！ 这篇文章中 。</p>
<p><a href="https://blog.csdn.net/mgzdwm/article/details/141437775?spm=1001.2014.3001.5502">Tcp网络编程文章详解</a></p>
<p>还没学习过或者忘记的小伙伴记得去看看哦。</p>
<h3>2. Tcp使用的相关类</h3>
<p><code>Tcp</code> 与<code>Udp</code>一样， 在进行网络通信的过程中，需要调用系统 <code>Socket api</code> ， 但在 Java标准库 中已经封装好了相关的类， 通过这几个类， 在 <code>JVM</code> 中来调用系统原生的 <code>Socket api</code> 来 操作网卡 ， 进行网络通信。</p>
<p>关于Tcp 来实现回显</p>
<p><strong>ServerSocket 类</strong> :</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6fb04596195b4fdbbdd2c88acf81f775.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p><code>实例化ServerSocket</code> 对象时， <strong>只用于服务器一端</strong> ， 并且进行 <code>端口号</code> 连接 <strong>进程</strong> 的过程。</p>
 </li>
 <li>
  <p>用于建立 <code>服务器</code> 与 <code>客户端</code> 连接，并且返回 <code>Socket</code> 对象。</p>
  <p>accept() 方法</p>
 </li>
</ul>
<h3>鱼式疯言</h3>
<p>从上面可以确定 Tcp的 <code>有连接</code> 的特点之一。</p>
<p><em>ServerSocket的构造方法</em> 的连接是 <strong>端口号与进程的连接</strong> ， 相当于 <code>"接好了电话线"</code> 。</p>
<p><em>accept（）</em> 的连接是 服务器与客户端的连接 ， 相当于 客户端那边给服务器 <strong>“打电话”</strong> ， 而服务器这边就用 <code>accept（）</code> 来接电话。完成连接。</p>
<hr>
<p><strong>Socket 类</strong> ：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe705403f096f47948d383c3de6b9f1c2.png&amp;size=m" alt=""></p>
<ul>
 <li>
  <p>用于客户端与服务器连接：</p>
  <p><strong>Socket</strong> 的构造方法, 在 实例化Socket 对象 时， 添加 <code>IP地址</code> 和 <code>端口号</code>， 就意味着 <code>建立了连接</code> 。</p>
 </li>
 <li>
  <p>操作 Socket对象 来获取各种 <code>IP地址 和</code> <code>输入和输出流对象</code> 。</p>
  <p>获取IP地址</p>
  <p><strong>getInetAddress()</strong></p>
  <p>获取输入字节流方法
   <br>
   getInputStream()</p>
  <p>获取输出字节流方法</p>
  <p><code>getOutputStream()</code></p>
 </li>
</ul>
<h3>鱼式疯言</h3>
<blockquote>
 <ol>
  <li>由于我们 <code>Tcp 特点</code> 之一就是 面向字节流 ， 并且是 <code>全双工</code> 。</li>
 </ol>
 <p>所以我们既可以获取到 <strong>输入字节流对象</strong> ，也可以获取到 <strong>输出字节流对象</strong> 。</p>
 <ol>
  <li>
   <p><code>ServerSocket</code> 类一般只适用于 服务器一端的端口号和进程的连接 ， 而 <code>Socket</code> 一般只适用于 <code>客户端一端</code> 与 <strong>服务器</strong> 的连接， 但是都适用于 <strong><em>服务器和客户端</em></strong> 的 获取输入流与输出流 的操作。</p>
  </li>
  <li>
   <p>在 同一台主机 上，同一个协议 ， 一个 <code>端口号</code> 直接连接 <code>一个进程</code> 。</p>
   <p>但是在不同协议中， <strong>一个端口号</strong> 可以连接 <code>不同协议</code> 下的进程。</p>
  </li>
  <li>
   <p>无论是 <code>ServerSocket 类</code> 还是 <code>Socket 类</code> ， 都是属于 <code>java.net</code> 下的包的类。</p>
  </li>
 </ol>
</blockquote>
<h3>回显服务器的普通实现代码展示</h3>
<h4>&lt;1&gt;. 服务器实现</h4>
<pre><code class="prism language-java">package echoTCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


public class MyTcpEchoServer {


    ServerSocket serverSocket = null;

    // 进行连接
    public MyTcpEchoServer(int port) {

        try {

            // 进行连接
            serverSocket = new ServerSocket(port);
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }

    public  void start() {

        // 开始交互

        System.out.println("服务器开始运行...");
        while(true) {

        // 使用线程池





            Socket socket = null;

            try {

                socket = serverSocket.accept();
            } catch (IOException e) {

                throw new RuntimeException(e);
            }

            // 进行接收请求并响应
            ProcessServerConnect(socket);



        }







    }

    private void ProcessServerConnect(Socket socket) {

            try(InputStream inputSTream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()
            ) {



                while(true) {


                    System.out.printf("客户端上线 : [客户端ip = %s,
                    客户端端口号 = %d]\n"
                    ,socket.getInetAddress(),
                    socket.getPort());

                    // 得到请求
                    Scanner scanner = new Scanner(inputSTream);
                    if(!scanner.hasNext()) {

                        break;
                    }

                    // 得到请求
                    String request = scanner.nextLine();

                    // 计算响应
                    // 执行业务
                   String response =  process(request);

                   // 返回响应
                   // 让客户端得到业务的执行结果
                   PrintWriter printWriter = new 
                   PrintWriter(outputStream);

                   // 开始返回
                   printWriter.println(response);
                   // 进行刷新
                    printWriter.flush();


//                    打印日志
                    System.out.printf("客户端下线 :  [ip = %s , 
                    port = %d], 请求 = %s , 响应 = %s\n",
                            socket.getInetAddress(),
                            socket.getPort(),request,response);


                }



            } catch (IOException e) {

                throw new RuntimeException(e);
            } finally {

                System.out.printf("任务执行结束： [%s,%d]\n",
                socket.getInetAddress(),
                socket.getPort());
                try {

//                    关闭 socket 流
                    socket.close();
                } catch (IOException e) {

                    throw new RuntimeException(e);
                }
            }

    }

    private String process(String request) {

        return  request;
    }


    public static void main(String[] args) {

        MyTcpEchoServer myTcpEchoServer = new MyTcpEchoServer(9090);
        myTcpEchoServer.start();
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff52d8e0e8c4b45f1ad70b2366ce12773.png&amp;size=m" alt=""></p>
<p>服务器一端的实现的整体流程可分为以下几步：</p>
<blockquote>
 <ol>
  <li>创建 <code>ServerSocket</code> 对象 , 在构造方法中参数列表添加 端口号 ， 建立端口号与进程的 <code>连接</code> 。</li>
 </ol>
</blockquote>
<pre><code class="prism language-java">   // 进行连接
            serverSocket = new ServerSocket(port);
</code></pre>
<blockquote>
 <ol>
  <li>调用 <code>accept</code> 方法让服务器与客户端进行，并返回一个 <code>Socket</code> 对象。</li>
 </ol>
</blockquote>
<pre><code class="prism language-java">   Socket socket = null;

        try {

            socket = serverSocket.accept();
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
</code></pre>
<blockquote>
 <ol>
  <li>获取到输入流，利用 <code>Scanner</code>包装输入流 得到请求 。</li>
 </ol>
</blockquote>
<pre><code class="prism language-java">// 得到请求
Scanner scanner = new Scanner(inputSTream);
if(!scanner.hasNext()) {

    break;
}

// 得到请求
String request = scanner.nextLine();
</code></pre>
<blockquote>
 <ol>
  <li><code>计算响应</code> 并把 <strong>输出流对象</strong> 包装到 <code>printWriter</code> <strong>输出到客户端</strong></li>
 </ol>
</blockquote>
<pre><code class="prism language-java">// 计算响应
    // 执行业务
   String response =  process(request);

   // 返回响应
   // 让客户端得到业务的执行结果
   PrintWriter printWriter = new 
   PrintWriter(outputStream);

   // 开始返回
   printWriter.println(response);
// 进行刷新
    printWriter.flush();
</code></pre>
<blockquote>
 <ol>
  <li><strong>打印日志</strong> ，并关闭 <code>Socket</code> 对象</li>
 </ol>
</blockquote>
<pre><code class="prism language-java">//                    打印日志
                 System.out.printf("客户端下线 :  [ip = %s , 
                 port = %d], 请求 = %s , 响应 = %s\n",
                         socket.getInetAddress(),
                         socket.getPort(),request,response);


             }



         } catch (IOException e) {

             throw new RuntimeException(e);
         } finally {

             System.out.printf("任务执行结束： [%s,%d]\n",
             socket.getInetAddress(),
             socket.getPort());
             try {

//                    关闭 socket 流
                 socket.close();
             } catch (IOException e) {

                 throw new RuntimeException(e);
             }
         }
</code></pre>
<h3>鱼式疯言</h3>
<p><strong>补充细节</strong>：</p>
<blockquote>
 <ol>
  <li>
   <p>对于大部分 <code>IO流</code> 来说， 并不是 <strong>输入或输出一个/ 一段</strong> 就会加载到 <code>硬盘</code> , 而是会存放到一个叫 缓冲区的地方， 当数据积攒到 <code>一定的量</code> ， 才会 <strong>加载过去</strong> 。</p>
   <p>所以我们为了每次只加载 <strong>一个 / 一段数据</strong> ， 可以调用 <code>flush</code> 这个方法 来将 <strong>缓冲区</strong> 的数据全部
    <br>
    <code>刷新出去</code>。</p>
  </li>
 </ol>
</blockquote>
<pre><code class="prism language-java"> // 进行刷新
      printWriter.flush();
</code></pre>
<hr>
<blockquote>
 <ol>
  <li>小编在这里利用了 <code>Scanner</code> 和 <code>printWriter</code> 来包装输入和输出流。 但是小伙伴们也可以利用小编之前提及过的 IO流的操作方式 来进行哦， 效果是一样的 。</li>
 </ol>
</blockquote>
<p>关于IO的流的操作文章， 可以多学习学习并且回去哦 💖 💖 💖 💖</p>
<p><a href="https://blog.csdn.net/mgzdwm/article/details/141320164?spm=1001.2014.3001.5501">IO流理论篇文章详解</a></p>
<p><a href="https://blog.csdn.net/mgzdwm/article/details/141355440?spm=1001.2014.3001.5501">IO流Java实践篇文章详解</a></p>
<blockquote>
 <ol>
  <li>对于上述 <strong>回显服务器</strong> 来说， 服务端一端 <code>accept（）</code> 只有当服务器<strong>每次发来请求时</strong> ，才会进行 连接， 否则进行 <code>阻塞等待</code>。</li>
 </ol>
</blockquote>
<h4>&lt;2&gt;. 客户端实现</h4>
<pre><code class="prism language-java">package echoTCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;


public class MyTcpEchoClient {

    Socket clientSocket = null;

    // 进行连接
    public MyTcpEchoClient(String ip , int port) {

        try {

            // 进行连接
            clientSocket = new Socket(ip, port);
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }

    public  void start() {

        // 开始交互


            System.out.println("客户端开始运行...");


                // 发送请求并接受响应
                ProcessServerConnect(clientSocket);


    }

    private void ProcessServerConnect(Socket socket) {

        try(InputStream inputSTream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()
        ) {


            while(true) {




                Scanner in = new Scanner(System.in);
                System.out.print("请输入你的需求-&gt; ");


//                输入请求并发送
                String request = in.nextLine();

                PrintWriter printWriter = new 
                PrintWriter(outputStream);
                printWriter.println(request);
                // 进行刷新
                printWriter.flush();


                // 接收响应并输出
                Scanner scanner = new Scanner(inputSTream);
                if(!scanner.hasNext()) {

                    break;
                }

                String response = scanner.nextLine();

                System.out.println("response = "+ response);

            }



        } catch (IOException e) {

            throw new RuntimeException(e);
        } finally {

            System.out.printf("任务执行结束： 
            [%s,%d]\n",
            socket.getInetAddress(),
            socket.getPort());
            try {

//                    关闭 socket 流
                socket.close();
            } catch (IOException e) {

                throw new RuntimeException(e);
            }
        }

    }


    public static void main(String[] args) {

        MyTcpEchoClient myTcpEchoClient = new 
        MyTcpEchoClient("127.0.0.1",9090);
        myTcpEchoClient.start();
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F63bff3d5bdd44b99820099c913d95ec8.png&amp;size=m" alt=""></p>
<p><strong>具体客户端一端的整体实现流程梳理</strong>：</p>
<ol>
 <li>实例化Socket 对象， 并把 <code>IP地址</code> 和 <code>端口号</code> 作为 <code>Socket</code> 构造方法的参数进行 <strong>客户端与服务端</strong> 的连接。</li>
</ol>
<pre><code class="prism language-java">Socket clientSocket = null;

// 进行连接
public MyTcpEchoClient(String ip , int port) {

    try {

        // 进行连接
        clientSocket = new Socket(ip, port);
    } catch (IOException e) {

        throw new RuntimeException(e);
    }
}
</code></pre>
<ol>
 <li><strong>输入请求</strong> 并发送给 <code>客户端</code></li>
</ol>
<pre><code class="prism language-java">                Scanner in = new Scanner(System.in);
                System.out.print("请输入你的需求-&gt; ");


//                输入请求并发送
                String request = in.nextLine();

                PrintWriter printWriter = new 
                PrintWriter(outputStream);
                printWriter.println(request);
                // 进行刷新
                printWriter.flush();
</code></pre>
<ol>
 <li>接收服务器返回的 <strong>响应并输出</strong></li>
</ol>
<pre><code class="prism language-java"> // 接收响应并输出
    Scanner scanner = new Scanner(inputSTream);
    if(!scanner.hasNext()) {

        break;
    }

    String response = scanner.nextLine();

    System.out.println("response = "+ response);
</code></pre>
<ol>
 <li>宣布服务器<strong>执行结束</strong> ， 并 关闭Socket对象</li>
</ol>
<pre><code class="prism language-java"> finally {

            System.out.printf("任务执行结束： 
            [%s,%d]\n",
            socket.getInetAddress(),
            socket.getPort());
            try {

//                    关闭 socket 流
                socket.close();
            } catch (IOException e) {

                throw new RuntimeException(e);
            }
        }
</code></pre>
<h3>鱼式疯言</h3>
<p><strong>补充细节</strong> ：</p>
<blockquote>
 <ol>
  <li>对应上述代码中的 <code>hasNext()</code> 方法来说， 出现了 <code>换行</code> 才会 <strong>break 结束</strong> 出去， 而当 服务器/ 客户端 <code>没有</code> 发来数据时， 是会进入 <strong>阻塞等待</strong> 的， 不会break 直接跳出。</li>
 </ol>
</blockquote>
<pre><code class="prism language-java">   if(!scanner.hasNext()) {

                break;
            }
</code></pre>
<h2>二. Tcp 回显服务器的多线程实现</h2>
<h3>1. 代码展示</h3>
<p><strong>服务器源码</strong>：</p>
<pre><code class="prism language-java">package echoTCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


public class MyTcpEchoServer {


    ServerSocket serverSocket = null;

    // 进行连接
    public MyTcpEchoServer(int port) {

        try {

            // 进行连接
            serverSocket = new ServerSocket(port);
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }

    public  void start() {

        // 开始交互

        System.out.println("服务器开始运行...");



//        使用多线程

        while(true) {

            Thread t = new Thread(()-&gt;{


                Socket socket = null;
                try {

                    socket = serverSocket.accept();
                } catch (IOException e) {

                    throw new RuntimeException(e);
                }

                // 进行接收请求并响应
                ProcessServerConnect(socket);


            });
            t.start();
        }




    }

    private void ProcessServerConnect(Socket socket) {

            try(InputStream inputSTream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()
            ) {



                while(true) {


                    System.out.printf("客户端上线 : [客户端ip = %s,客户端端口号 = %d]\n",socket.getInetAddress(),socket.getPort());
                    // 得到请求
                    Scanner scanner = new Scanner(inputSTream);
                    if(!scanner.hasNext()) {

                        break;
                    }

                    // 得到请求
                    String request = scanner.nextLine();

                    // 计算响应
                    // 执行业务
                   String response =  process(request);

                   // 返回响应
                   // 让客户端得到业务的执行结果
                   PrintWriter printWriter = new PrintWriter(outputStream);

                   // 开始返回
                   printWriter.println(response);
                   // 进行刷新
                    printWriter.flush();


//                    打印日志
                    System.out.printf("客户端下线 :  [ip = %s , port = %d], 请求 = %s , 响应 = %s\n",
                            socket.getInetAddress(),socket.getPort(),request,response);


                }



            } catch (IOException e) {

                throw new RuntimeException(e);
            } finally {

                System.out.printf("任务执行结束： [%s,%d]\n",socket.getInetAddress(),socket.getPort());
                try {

//                    关闭 socket 流
                    socket.close();
                } catch (IOException e) {

                    throw new RuntimeException(e);
                }
            }

    }

    private String process(String request) {

        return  request;
    }


    public static void main(String[] args) {

        MyTcpEchoServer myTcpEchoServer = new MyTcpEchoServer(9090);
        myTcpEchoServer.start();
    }
}
</code></pre>
<p><strong>客户端源码</strong> ：</p>
<pre><code class="prism language-java">package echoTCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;


public class MyTcpEchoClient {

    Socket clientSocket = null;

    // 进行连接
    public MyTcpEchoClient(String ip , int port) {

        try {

            // 进行连接
            clientSocket = new Socket(ip, port);
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }

    public  void start() {

        // 开始交互
            System.out.println("客户端开始运行...");


                // 发送请求并接受响应
                ProcessServerConnect(clientSocket);


    }

    private void ProcessServerConnect(Socket socket) {

        try(InputStream inputSTream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()
        ) {


            while(true) {




                Scanner in = new Scanner(System.in);
                System.out.print("请输入你的需求-&gt; ");


//                输入请求并发送
                String request = in.nextLine();

                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                // 进行刷新
                printWriter.flush();


                // 接收响应并输出
                Scanner scanner = new Scanner(inputSTream);
                if(!scanner.hasNext()) {

                    break;
                }

                String response = scanner.nextLine();

                System.out.println("response = "+ response);



            }



        } catch (IOException e) {

            throw new RuntimeException(e);
        } finally {

            System.out.printf("任务执行结束： [%s,%d]\n",socket.getInetAddress(),socket.getPort());
            try {

//                    关闭 socket 流
                socket.close();
            } catch (IOException e) {

                throw new RuntimeException(e);
            }
        }

    }


    public static void main(String[] args) {

        MyTcpEchoClient myTcpEchoClient = new MyTcpEchoClient("127.0.0.1",9090);
        myTcpEchoClient.start();
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3945aaf0a2ff477a862ff7d31331cdb9.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F133bf6f21f244ca0b0326126a01ff1cf.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc8fce98c4f714df2af2cf9844ab991d5.png&amp;size=m" alt=""></p>
<h3>2. 原理剖析</h3>
<p>对于多线程的回显服务器的实现， 最主要是解决 <code>多个</code> 客户端同时给 <code>一个</code> 服务器 发送请求 的问题。</p>
<blockquote>
 <p>对于服务器来说 <strong>外边套着一个循环</strong> ， 里面又加了 <strong>一层循环</strong> ， 这样的话就会导致 <strong>两层循环</strong> 的加入
  <br>
  当 <strong>第一个客户端进入内循环</strong> 时， 它的任务还 <code>没结束</code>， 第二个客户端就向服务器 <strong>发送请求</strong> ， 这时由于内部的还 一直循环， 服务器就无法进入<code>内循环</code> ， 只能让第二个客户端一直停留在 <code>缓冲区</code>。
  <br>
  这时我们就引入 <strong>多线程</strong> 的方式， 将每一个服务器都 <code>分布同时</code> 执行循环， 这样就能保证不会让 <code>多个</code> 服务器一直阻塞到 <code>缓冲区</code> 。</p>
</blockquote>
<p>关于代码的实现实现流程， 只是在普通回显服务器的基础上加了 一行多线程 的代码。 其他都是一样的， 小编在这里就 <strong>不赘述了</strong> 。</p>
<h2>三. Tcp 回显服务器的线程池实现</h2>
<h3>1. 代码展示</h3>
<p><strong>服务器一端</strong>：</p>
<pre><code class="prism language-java">package echoTCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


public class MyTcpEchoServer {


    ServerSocket serverSocket = null;

    // 进行连接
    public MyTcpEchoServer(int port) {

        try {

            // 进行连接
            serverSocket = new ServerSocket(port);
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }

    public  void start() {

        // 开始交互

        System.out.println("服务器开始运行...");
        while(true) {

        // 使用线程池
        Executor executor = Executors.newCachedThreadPool();

        executor.execute(() -&gt; {



            Socket socket = null;

            try {

                socket = serverSocket.accept();
            } catch (IOException e) {

                throw new RuntimeException(e);
            }

            // 进行接收请求并响应
            ProcessServerConnect(socket);


        });
        }









    }

    private void ProcessServerConnect(Socket socket) {

            try(InputStream inputSTream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()
            ) {



                while(true) {


                    System.out.printf("客户端上线 : [客户端ip = %s,客户端端口号 = %d]\n",socket.getInetAddress(),socket.getPort());
                    // 得到请求
                    Scanner scanner = new Scanner(inputSTream);
                    if(!scanner.hasNext()) {

                        break;
                    }

                    // 得到请求
                    String request = scanner.nextLine();

                    // 计算响应
                    // 执行业务
                   String response =  process(request);

                   // 返回响应
                   // 让客户端得到业务的执行结果
                   PrintWriter printWriter = new PrintWriter(outputStream);

                   // 开始返回
                   printWriter.println(response);
                   // 进行刷新
                    printWriter.flush();


//                    打印日志
                    System.out.printf("客户端下线 :  [ip = %s , port = %d], 请求 = %s , 响应 = %s\n",
                            socket.getInetAddress(),socket.getPort(),request,response);


                }



            } catch (IOException e) {

                throw new RuntimeException(e);
            } finally {

                System.out.printf("任务执行结束： [%s,%d]\n",socket.getInetAddress(),socket.getPort());
                try {

//                    关闭 socket 流
                    socket.close();
                } catch (IOException e) {

                    throw new RuntimeException(e);
                }
            }

    }

    private String process(String request) {

        return  request;
    }


    public static void main(String[] args) {

        MyTcpEchoServer myTcpEchoServer = new MyTcpEchoServer(9090);
        myTcpEchoServer.start();
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F367e12418da3497b9577e109800a6a12.png&amp;size=m" alt="">
 <br>
 <strong>客户端一端</strong> ：</p>
<pre><code class="prism language-java">package echoTCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;


public class MyTcpEchoClient {

    Socket clientSocket = null;

    // 进行连接
    public MyTcpEchoClient(String ip , int port) {

        try {

            // 进行连接
            clientSocket = new Socket(ip, port);
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }

    public  void start() {

        // 开始交互
            System.out.println("客户端开始运行...");


                // 发送请求并接受响应
                ProcessServerConnect(clientSocket);


    }

    private void ProcessServerConnect(Socket socket) {

        try(InputStream inputSTream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()
        ) {


            while(true) {




                Scanner in = new Scanner(System.in);
                System.out.print("请输入你的需求-&gt; ");


//                输入请求并发送
                String request = in.nextLine();

                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                // 进行刷新
                printWriter.flush();


                // 接收响应并输出
                Scanner scanner = new Scanner(inputSTream);
                if(!scanner.hasNext()) {

                    break;
                }

                String response = scanner.nextLine();

                System.out.println("response = "+ response);



            }



        } catch (IOException e) {

            throw new RuntimeException(e);
        } finally {

            System.out.printf("任务执行结束： [%s,%d]\n",socket.getInetAddress(),socket.getPort());
            try {

//                    关闭 socket 流
                socket.close();
            } catch (IOException e) {

                throw new RuntimeException(e);
            }
        }

    }


    public static void main(String[] args) {

        MyTcpEchoClient myTcpEchoClient = new MyTcpEchoClient("127.0.0.1",9090);
        myTcpEchoClient.start();
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2cd64cda8a3240b2878c11d0ff26548a.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff1936d2dd60743448a979a95e6a0f29a.png&amp;size=m" alt=""></p>
<h3>代码说明</h3>
<blockquote>
 <p>关于线程池的代码实现： 主要还是在原来 普通 回显服务器的基础上， 加上了线程池的 <code>newCachedThreadPool</code> 对象 。
  <br>
  这个线程池对象在 <strong>任务繁忙</strong> 时， 最多可以创建出 <code>Integer.MAX_VALUES</code> 个 非核心线程。</p>
</blockquote>
<p>具体详情小伙伴们可以参考多线程的文章， 有重点讲解哦</p>
<p><a href="https://blog.csdn.net/mgzdwm/article/details/141135147?spm=1001.2014.3001.5501">多线程实践篇详解</a></p>
<p>非常适合 <strong>服务器开发使用</strong> 。</p>
<pre><code class="prism language-java">  // 使用线程池
        Executor executor = Executors.newCachedThreadPool();

        executor.execute(() -&gt; {



            Socket socket = null;

            try {

                socket = serverSocket.accept();
            } catch (IOException e) {

                throw new RuntimeException(e);
            }

            // 进行接收请求并响应
            ProcessServerConnect(socket);


        });
</code></pre>
<h3>鱼式疯言</h3>
<p>如果是<code>大量</code>的客户端， 我们可能会用到上面的 <code>线程池</code> 来使用。</p>
<p>如果是<code>海量</code>的客户端使用， 就会使用 <code>IO多路复用</code> （了解即可）。</p>
<h2>总结</h2>
<ol>
 <li>
  <p>Tcp回显服务器的普通实现： 从Tcp 的四大特点： 有连接， 面向字节流， 可靠传输， 全双工 的四个特点展开介绍 <code>Tcp</code> 的两个主要网络通信的类： <code>ServerSocket</code> 和 <code>Socket</code> 两个类 ，并在代码中充分的使用并结合理解流程。</p>
 </li>
 <li>
  <p>Tcp 回显服务器的 <strong>多线程实现</strong> ： 利用多线程解决了一个服务器只能解决一个客户端的问题。</p>
 </li>
 <li>
  <p>Tcp 回显服务器的线程池实现： 从另外一个线程池的角度更解决大量客户端的问题。</p>
 </li>
</ol>
<blockquote>
 <p>如果觉得小编写的还不错的咱可支持 <strong>三连</strong> 下 (定有回访哦) , 不妥当的咱请评论区 <strong>指正</strong>
  <br>
  希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 <strong>动力</strong> 💖 💖 💖</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff7c58703097442608a2fa36b96464e76.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/mgzdwm/article/details/141562116</p>]]></description><guid isPermaLink="false">/archives/b2a294dc-356b-448f-8a0e-2020f86cb66e</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:16:27 GMT</pubDate></item><item><title><![CDATA[Windows 10 如何设置静态 IP 地址？]]></title><link>https://wiki.dgfygk.com/archives/aa6f264e-67a6-4a6f-91cc-20007f262221</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Windows%2010%20%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E9%9D%99%E6%80%81%20IP%20%E5%9C%B0%E5%9D%80%EF%BC%9F&amp;url=/archives/aa6f264e-67a6-4a6f-91cc-20007f262221" width="1" height="1" alt="" style="opacity:0;">
<h3>如何设置静态 IP 地址</h3>
<p>在 Windows 电脑中如何去设置静态 IP 地址呢？下面就给大家总结了几种不同的方法。</p>
<h4>设置界面设置</h4>
<p>第一种方法，我们可以去设置界面中设置静态 IP 地址。具体的操作步骤如下：</p>
<p>首先打开设置面板之后，选择网络和 Internet。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe18ebd50a82f4a2f9120fb37a8845bba.png&amp;size=m" alt=""></p>
<p>然后点击左边框中的以太网选项，然后点击当前连接的网络。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffbac453825464489ae189395fcfc072d.png&amp;size=m" alt=""></p>
<p>接下来往下滑动，找到 IP 设置模块，点击下方的编辑按钮。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0291af817896463dbf4b6a31e06b4243.png&amp;size=m" alt=""></p>
<p>选择手动之后，输入静态 IP 地址即可，然后点击保存按钮。（子网前缀长度可以通过 IP子网计算器进行计算之后填写）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Febc5fd505fa445f79c87f3e4869b4102.png&amp;size=m" alt=""></p>
<h4>控制面板设置</h4>
<p>设置静态 IP 地址的方法第二种就是在控制面板中进行设置，下面是具体的操作步骤：</p>
<p>首先我们可以从搜索栏打开控制面板，然后点击网络和共享中心。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F997940a5398e4a4c9812820f0afdf425.png&amp;size=m" alt=""></p>
<p>接下来找到左侧的更改适配器设置选项，点击进入。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F840e33dd3bb74669a5600668c71e70ea.png&amp;size=m" alt=""></p>
<p>找到你现在所连接的网络，然后右键打开菜单之后选择属性。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F874e57b5abed45c3a314132ec2680a48.png&amp;size=m" alt=""></p>
<p>在此链接使用下列项目模块中，找到 Internet 协议版本4（TCP/IPv4），之后同样选择之后点击属性按钮。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffdea5b0e7ee44096b77d0dfa3c1ee16f.png&amp;size=m" alt=""></p>
<p>然后选择使用下面的 IP 地址，输入静态 IP 地址即可，子网掩码可以输入 255.255.255.0，然后下方的 DNS 服务器可以自行在网上找到适合的比如 8.8.8.8，最后点击确定退出。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb9c95c24155b41d794962c238906609f.png&amp;size=m" alt=""></p>
<p>完整文章：<a href="https://www.dians.net/thread-4000.htm" title="Windows 10 如何设置静态 IP 地址？3 种方法实现该操作">Windows 10 如何设置静态 IP 地址？3 种方法实现该操作</a></p>
<p>原文链接: https://blog.csdn.net/qq_57728300/article/details/140314326</p>]]></description><guid isPermaLink="false">/archives/aa6f264e-67a6-4a6f-91cc-20007f262221</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><pubDate>Sat, 28 Sep 2024 07:16:14 GMT</pubDate></item><item><title><![CDATA[Nginx 配置 SSL（HTTPS）详解]]></title><link>https://wiki.dgfygk.com/archives/d56f63fb-b9b9-446f-bd4c-216c4895593b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Nginx%20%E9%85%8D%E7%BD%AE%20SSL%EF%BC%88HTTPS%EF%BC%89%E8%AF%A6%E8%A7%A3&amp;url=/archives/d56f63fb-b9b9-446f-bd4c-216c4895593b" width="1" height="1" alt="" style="opacity:0;">
<p>Nginx作为一款高性能的HTTP和反向代理<a href="https://cloud.tencent.com/act/pro/promotion-cvm?from_column=20065&amp;from=20065" title="服务器">服务器</a>，自然支持SSL/TLS加密通信。本文将详细介绍如何在Nginx中配置SSL，实现HTTPS的访问。</p>
<p>随着互联网安全性的日益重要，HTTPS协议逐渐成为网站加密通信的标配。Nginx作为一款高性能的HTTP和反向代理服务器，自然支持SSL/TLS加密通信。本文将详细介绍如何在Nginx中配置SSL，实现HTTPS的访问。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fedaf9409ef6f4a484041eea0c9776c84.png&amp;size=m" alt=""></p>
<p>使用Nginx进行反向代理的时候，对于正常的http；流量使用 <strong><em>location</em></strong> 块并且配置 <strong><em>proxy_pass</em></strong> 转发即可，但是如果是https的流量，使用Nginx进行反向代理的时候，直接使用 <strong><em>proxy_pass</em></strong> 会出现各种报错的情况，包括不限于：</p>
<ul>
 <li>502 Bad Getway</li>
 <li>ERR_SSL_PROTOCOL_ERROR</li>
 <li>ERR_CONNECTION_RESET</li>
</ul>
<blockquote>
 <p>我本身遇到的ERR_SSL_PROTOCOL_ERROR错误比较多，这是因为在配置Nginx的时候，对于443端口本身的侦听没有带上 <strong><em>ssl</em></strong> 关键字，所以会报SSL协议错误，但是如果加上ssl，没有给证书位置，也是不行</p>
 <p>
  <br>
 </p>
 <p>对于转发到8080端口，如果配置不对(比如后端服务是http，结果配置了https)，会出现502 Bad Getway</p>
</blockquote>
<ul>
 <li>proxy_pass后边的要写IP地址，不能写域名，否则还要解决域名解析的问题，有据称如果写localhost的，访问速度会变慢，是因为需要访问host文件解析localhost</li>
 <li>响应8080端口的server块，也要注意，也要写成侦听ssl，配置好证书位置</li>
</ul>
<pre><code class="bash">upstream  plan-uat {
        server 10.202.159.74:8080 max_fails=3 fail_timeout=10 weight=1;
}

server {
        server_name   plan-uat.ztoky.cn;
        listen 443 ssl ;
        ssl_certificate ssl/ztoky.cn.crt;
        ssl_certificate_key ssl/ztoky.cn.key;
        access_log logs/plan-uat.ztoky.cn.access.log json;

        location /plan/ {
           proxy_pass      http://plan-uat/;
           include         proxy.conf;
        }
}
</code></pre>
<p><strong>检查 linux 服务器上的 nginx 是否安装 ssl 模块</strong>
 <br>
 nginx 安装目录，下面有个 sbin 文件夹，来到 sbin 目录下执行 ./nginx -V命令，注意是大写的 V，小写的 v 只会展示 nginx 的版本号信息。执行完出现下图圈红的这些信息说明 ssl 模块已经安装过了。可以忽略下文的 ssl 模块安装的相关内容，直接看 nginx 配置的内容。</p>
<pre><code class="bash">cd /usr/local/nginx/sbin
./nginx -V
</code></pre>
<p><strong>为 nginx 安装 ssl 模块</strong>
 <br>
 如果没有安装 ssl 模块，即使你成功的配置好 https 的相关配置，网站也依旧不能使用 https，会提示诸如：无法提供安全连接、TLS协议不支持等问题。ssl 模块的安装是为了能让后端接口得以使用 https 的方式调用。找到我们的 nginx 安装解压包，我这里是放在 /usr/local/software 目录下，然后执行如下命令进入到 nginx-1.18.0 目录下。</p>
<pre><code class="bash">cd /usr/local/nginx-1.18.0
</code></pre>
<p><strong>来到 ngixn-1.18.0 目录 然后依次执行如下这命令，配置了 ssl 模块，使用 make 命令重新编译</strong></p>
<pre><code class="bash">./configure --with-http_ssl_module
make
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb0b6060d9d7b9b6ca515905025815e8d.png&amp;size=m" alt=""></p>
<p>进入 objs 目录，会看到 nginx 的可执行文件</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9d758d6e242ededfe5861346492b80bf.png&amp;size=m" alt=""></p>
<p>注意，不能使用 make install 命令，这样会导致重新安装 nginx，此时当前目录就会出现 objs 目录</p>
<p>替换 nginx 可执行文件，我们使用这个新的 nginx 可执行文件 替换 sbin 目录下的 nginx 可执行文件</p>
<pre><code class="bash">cp ./nginx /usr/local/nginx/sbin/
</code></pre>
<p>系统会询问你是否覆盖，输入 y 即可，如果覆盖失败，多半是因为你的 nginx 还在启动中，先将 nginx 关闭即可，再次检验是否安装 ssl 模块，进入 nginx 目录</p>
<pre><code class="bash">cd /usr/local/nginx/sbin/
</code></pre>
<p>执行命令</p>
<pre><code class="bash">./nginx -V
</code></pre>
<p>此时就能看到 ssl 模块已经安装成功了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe5e1ac3c30d81ea73e9e552e48fc4433.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<h3>准备SSL证书</h3>
<hr>
<p>首先，我们需要准备<a href="https://cloud.tencent.com/product/ssl?from_column=20065&amp;from=20065" title="SSL证书">SSL证书</a>。你可以选择从证书颁发机构（CA）购买商业证书，也可以自己生成自签名证书。自签名证书虽然免费，但不会被浏览器信任，仅适用于测试环境。</p>
<p>如果你选择购买商业证书，通常会获得以下文件：</p>
<ul>
 <li>证书文件（例如：example.com.crt）</li>
 <li>私钥文件（例如：example.com.key）</li>
 <li>中间证书文件（如果有的话）</li>
</ul>
<p>
 <br>
</p>
<p>
 <br>
</p>
<h3>配置Nginx SSL 启用443端口</h3>
<hr>
<p>(1) 打开Nginx配置文件，通常位于/etc/nginx/nginx.conf或/etc/nginx/conf.d/default.conf。</p>
<p>(2) 在http块中，配置SSL相关参数。示例如下：</p>
<pre><code class="bash">http {
    ...

    server {
        listen 443 ssl;
        server_name example.com;

        ssl_certificate /path/to/example.com.crt;  # 证书文件路径
        ssl_certificate_key /path/to/example.com.key;  # 私钥文件路径

        # 如果有中间证书，也需要配置
        ssl_trusted_certificate /path/to/intermediate.crt;

        # 其他SSL配置参数
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        ssl_protocols TLSv1.2 TLSv1.3;  # 支持的协议版本
        ssl_ciphers ECDH:AESGCM:HIGH:!RC4:!DH:!MD5:!aNULL:!eNULL;  # 使用此加密套件
        ssl_prefer_server_ciphers on;  # 优先使用服务器端的加密套件

        # 其他server配置...
    }

    ...
}
</code></pre>
<h6>测试HTTPS访问</h6>
<p>现在，你的Nginx服务器已经配置了SSL，可以通过HTTPS协议访问了。在浏览器中输入https://example.com，检查是否能够成功访问并显示安全的连接标识（如绿色锁头）。</p>
<p>此外，你还可以使用<a href="https://cloud.tencent.com/product/cli?from_column=20065&amp;from=20065" title="命令行工具">命令行工具</a>（如openssl或curl）来测试HTTPS连接和证书的有效性。</p>
<p>
 <br>
</p>
<h3>配置代理</h3>
<hr>
<p>使用proxy_pass指令来配置代理。以下是一个示例配置，将HTTPS请求代理到另一个HTTPS服务器：(注意这里的后端是HTTPS的服务)</p>
<blockquote>
 <p>后端服务是https类型的 proxy_pass https://your.backend.server;</p>
 <p>后端服务是http类型的 proxy_pass http://your.backend.server;</p>
</blockquote>
<pre><code class="bash">server {

  # 监听443端口，处理所有HTTPS请求
  listen 443 ssl;

  server_name your.domain.com;

  # SSL配置 
  ssl_certificate /path/to/your/cert.pem;
  ssl_certificate_key /path/to/your/private.key;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers HIGH:!aNULL:!MD5;

  # 代理配置，将HTTPS请求代理到另一个HTTPS服务器
  location / {
    proxy_pass https://your.backend.server;
  }
}
</code></pre>
<p>以上配置假设您的域名是your.domain.com，代理目标是您的后端HTTPS服务器（例如https://your.backend.server）。请记住将路径和参数适当地配置为您的特定场景。</p>
<p>原文链接: https://blog.csdn.net/qq_34556414/article/details/138190241</p>]]></description><guid isPermaLink="false">/archives/d56f63fb-b9b9-446f-bd4c-216c4895593b</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:16:02 GMT</pubDate></item><item><title><![CDATA[【网络】HTTPS——HTTP的安全版本]]></title><link>https://wiki.dgfygk.com/archives/f4075d8b-924b-4460-b004-8707f093b763</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%BD%91%E7%BB%9C%E3%80%91HTTPS%E2%80%94%E2%80%94HTTP%E7%9A%84%E5%AE%89%E5%85%A8%E7%89%88%E6%9C%AC&amp;url=/archives/f4075d8b-924b-4460-b004-8707f093b763" width="1" height="1" alt="" style="opacity:0;">
<h2>1.HTTP的问题</h2>
<blockquote>
 <ul>
  <li><strong>1、使用明文通信</strong></li>
 </ul>
 <p><strong>HTTP协议不具备给通讯内容加密的功能，所有使用HTTP协议通信的请求和响应的内容无法进行加密,都是使用明文发送。</strong>由于HTTP属于TCP/IP协议族的协议，按照TCP/IP协议族的通讯机制，<strong>HTTP在整个通讯线路上都存在被窃听的可能</strong>。例如：wireshark等抓包工具、sniffer等嗅探工具都可以在通讯网络上收集数据包并进行解析。</p>
 <p>
  <br>
 </p>
 <ul>
  <li><strong>2、不验证通讯双方身份</strong></li>
 </ul>
 <p>HTTP协议不会对通信中的请求方和响应方进行验证。服务器只要接收到请求就会返回一个响应。因此存在会一些安全隐患：</p>
 <ul>
  <li>1、无法确定请求发送到的web服务器是按真实意图返回响应的web服务器，存在伪装服务器的可能。</li>
  <li>2、无法确定响应是不是返回到按照真实意图接受的客户端的情况，存在伪装客户端的可能。</li>
  <li>3、无法确定通信方是否有通讯的权限。对于一些保存有重要信息的服务器，只能将信息发送给部分有通讯权限的用户。</li>
  <li>4、对于无意义的请求，服务端也会响应。无法阻止DOS拒绝服务攻击，通过海量请求占用服务资源，无法正常提供服务。</li>
  <li>5、无法判断请求的来源和发起者。</li>
 </ul>
 <p>
  <br>
 </p>
 <ul>
  <li><strong>3、无法验证报文的完整性</strong></li>
 </ul>
 <p><strong>HTTP协议无法确认通信报文的完整、准确。</strong>在请求或响应送出之后直到接收方接收之前这段时间，如果请求或响应被篡改，也是无法知道的。即：发出的请求和响应和接收到的请求和响应无法确保相同。例如：中间人攻击</p>
</blockquote>
<h2>2.预备知识</h2>
<h3>2.1.HTTPS是什么</h3>
<p><strong>https(全称：Hypertext Transfer Protocol Secure)，是以安全为目标的 HTTP 通道，在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性，是 HTTP 的安全版本。</strong></p>
<ol>
 <li><strong>Https是身披SSL/TLS外壳的HTTP协议，它并非一个新的协议而是在HTTP的基础上新增了安全层(SSL/TLS),HTTPS先和安全层通信，然后安全层和TCP层通信。</strong></li>
 <li>HTTPS 经由 HTTP 进行通信，但是在 HTTP 的基础上引入了一个加密层，使用 SSL/TLS 来加密数据包。</li>
 <li>HTTPS 开发的主要目的，是提供对网站服务器的身份认证，保护交换数据的隐私与完整性。</li>
 <li>HTTPS 默认工作在 TCP 协议443端口(HTTP默认80端口)</li>
</ol>
<p><strong>既然要保证数据安全，就需要进行"加密”，即网络传输中不再直接传输明文，而是加密之后的"密文”。</strong></p>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F04be529a16544a17a9e587e9dd0c09b3.png&amp;size=m" alt=""></p>
<p>应用层就是我们自己啊，我们自己去加密吗？别开玩笑了，等下偷鸡不成蚀把米了。</p>
<p><strong>为了更好的加密，就在应用层和传输层之间加了一个安全层，使用 SSL/TLS 来加密数据包。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4cecc66bb24c410b8702b8cc8ed9059e.png&amp;size=m" alt=""></p>
<p><strong>以前HTTP是应用层将HTTP响应/请求直接传递给传输层，现在变成了HTTPS，先将应用层将HTTP响应/请求传递给SSL\&amp;TLS这个过渡层，然后再传递给传输层，这就是HTTPS和HTTP的区别。</strong></p>
<blockquote>
 <p><strong>也就是说，</strong></p>
 <ul>
  <li><strong>Https是身披SSL/TLS外壳的HTTP协议，它并非一个新的协议而是在HTTP的基础上新增了安全层(SSL/TLS),HTTPS先和安全层通信，然后安全层和TCP层通信。</strong></li>
 </ul>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffcb35384592140a79d14420319a85838.png&amp;size=m" alt="">
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F399c5f2cb7344cecbf2e72f0e7ba152d.png&amp;size=m" alt=""></p>
 <p><strong><code>SSL/TLS</code> 是用于加密和保护网络通信的协议。它们用于确保在互联网上传输的数据在传输过程中是安全的，并且不能被未经授权的人读取或篡改。<code>SSL</code> 是 <code>TLS</code> 的前身，<code>TLS</code> 实际上是 <code>SSL</code> 的后续版本</strong></p>
 <ul>
  <li><strong>使用SSL/TLS就不一定安全了 ，因为SSL/TLS会一直更新，新版本出来的时候，旧版本的漏洞也就公布出来了，这会导致使用旧版本的SSL/TLS的客户端不安全。</strong></li>
 </ul>
</blockquote>
<h3><strong>2.2.加密和解密</strong></h3>
<h4>2.2.1.什么是<strong>加密和解密</strong></h4>
<p>首先需要简单了解一下 加密与解密 的概念</p>
<ul>
 <li><strong>加密：把明文经过一系列变换，形成密文</strong></li>
 <li><strong>解密：把密文经过一系列变换，还原成明文</strong></li>
</ul>
<blockquote>
 <p><strong>明文就是我们能看懂的信息，密文就是我们看不懂的东西。</strong></p>
 <p>比如这篇博客现在就是以明文的形式显示的，但如果我将其变为火星文，所有人都看不懂，那它就变成密文了</p>
</blockquote>
<p><strong>在加密与解密的过程中需要一个或者多个辅助数据，我们将其称为 「密钥」</strong></p>
<blockquote>
 <p><strong>加密是上锁，解密就是开锁，无论是上锁还是解锁，都需要钥匙，也就是「密钥」</strong></p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5676224e1356415998959e534159905a.png&amp;size=m" alt=""></p>
</blockquote>
<p><strong>加密示例：假设我们要加密的消息（明文）是 “HELLO”，并且我们选择的密钥是3（意味着字母表中的每个字母向后移动3位）。</strong></p>
<ul>
 <li>H -&gt; K (因为H向后移3位是K)</li>
 <li>E -&gt; H (E向后移3位是H)</li>
 <li>L -&gt; O (L向后移3位是O，注意这里超过了Z则循环回A)</li>
 <li>L -&gt; O (同上)</li>
 <li>O -&gt; R (O向后移3位是R）</li>
</ul>
<p>所以，经过加密后的密文是 “KHOOR”。</p>
<p>解密示例：<strong>现在，如果我们收到密文 “KHOOR” 并知道密钥是3，我们可以通过相反的操作来解密，即将每个字母向前移3位来还原明文。</strong></p>
<ul>
 <li>K -&gt; H (K向前移3位是H)</li>
 <li>H -&gt; E (H向前移3位是E)</li>
 <li>O -&gt; L (O向前移3位是L)</li>
 <li>O -&gt; L (同上)</li>
 <li>R -&gt; O (R向前移3位是O)</li>
</ul>
<p>这样，我们就成功地将密文 “KHOOR” 还原成了明文 “HELLO”。<strong>在这个例子中，密钥就是那个固定的数字3，它在加密和解密过程中起到了至关重要的作用，确保只有拥有密钥的人才能正确解码信息。</strong></p>
<h4>2.2.2.为什么要加密解码</h4>
<ul>
 <li><strong>为什么需要加密？</strong></li>
</ul>
<p><strong>首先是因为信息都是以 </strong>明文<strong> 的形式传输的，难免会遇到坏人收集个人信息，其次是可能存在运营商劫持等安全问题，个人信息容易泄漏，所以才需要对信息加密，保护隐私</strong></p>
<p><strong>我们举个例子来</strong></p>
<blockquote>
 <p><strong>由于我们通过网络传输的任何的数据包都会经过运营商的网络设备(路由器,交换机等)，那么运营商的网络设备就可以解析出你传输的数据内容，并进行篡改。</strong></p>
 <p>
  <br>
 </p>
 <p>比如说我们下载天天动听，我们在浏览器点击"下载按钮”，其实就是在给服务器发送了⼀个HTTP请求，<strong>我们通过网络传输的任何的数据包都会经过运营商的网络设备(路由器,交换机等)，所以我们发出的HTTP请求会先会经过运营商的网络设备(路由器,交换机等)，然后再到天天动听的服务端，原路返回HTTP响应，</strong>获取到的HTTP响应其实就包含了该 APP的下载链接。</p>
 <p>
  <br>
 </p>
 <p><strong>原路返回HTTP响应时，还是会经过运营商的网络设备，这个时候运营商可以选择劫持这个HTTP请求。</strong> <strong>运营商劫持之后，就发现这个请求是要下载天天动听，</strong><strong>它不想让我们下载天天动听</strong><strong>，于是把服务器交给用户的HTTP响应给偷偷篡改成"QQ浏览器"的下载地址了，再把这个HTTP响应返回给浏览器。</strong></p>
 <p>
  <br>
 </p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fba533f75d1104144a1b66558ce3f9354.png&amp;size=m" alt=""></p>
 <p>这个时候，用户本来想下载天天动听，却变成了下载QQ浏览器。</p>
 <p>
  <br>
 </p>
 <p><strong>所以：因为http的内容是明文传输的，明⽂数据会经过路由器、wifi热点、通信服务运营商、代理服务 器等多个物理节点，如果信息在传输过程中被劫持，传输的内容就完全暴露了。</strong></p>
 <p>
  <br>
 </p>
 <p><strong>劫持者还可以篡改传输的信息且不被双方察觉，这就是中间人攻击，所以我们才需要对信息进行加密。</strong></p>
 <p>
  <br>
 </p>
 <p><strong>在互联⽹上,明文传输是比较危险的事情!!! HTTPS就是在HTTP的基础上进行了加密,进⼀步的来保证用户的信息安全\~</strong>
  <br>
  <strong>我们上网的时候一定会处于一个局域网，局域网一定会有路由器，如果我们是明文传送的，那么我们的HTTP请求就会被局域网捕捉到了，别人就可以充当中间人，篡改我们的HTTP请求和响应。</strong></p>
</blockquote>
<h4>2.2.3.常用的加密方式</h4>
<p>常见的加密方式有以下两种：</p>
<blockquote>
 <ul>
  <li><strong>1.对称加密</strong></li>
 </ul>
 <p><strong>采用单钥密码系统的加密方法，同一个密钥可以同时用作信息的加密和解密，这种加密方法称为对称加密，也称为单密钥加密。</strong></p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F08c77532e3e74434bc82b21fde837791.png&amp;size=m" alt=""></p>
 <ul>
  <li><strong>特征:加密和解密所用的密钥是相同的</strong></li>
  <li><strong>常见对称加密算法(了解):DES、3DES、AES、TDEA、Blowfish、RC2等</strong></li>
  <li><strong>特点:算法公开、计算量小、加密速度快、加密效率高对称加密其实就是通过同一个"密钥”,把明文加密成密文,并且也能把密文解密成明文.</strong></li>
 </ul>
 <p>一个简单的对称加密,按位异或</p>
 <p>
  <br>
 </p>
 <p>假设 明文a=1234,密钥 key=8888</p>
 <p>
  <br>
 </p>
 <p>则加密 a^key 得到的密文 b为 9834，然后针对密文 9834 再次进行运算 b^key，得到的就是原来的明文 1234.(对于字符串的对称加密也是同理,每一个字符都可以表示成一个数字)</p>
 <p>
  <br>
 </p>
 <p>当然,按位异或只是最简单的对称加密.HTTPS 中并不是使用按位异或.</p>
 <ul>
  <li>
   <p><strong>2.非对称加密</strong></p>
  </li>
  <li>
   <p><strong>需要两个密钥来进行加密和解密，这两个密钥是公开密钥(publickey，简称公钥)和私有密钥(private key，简称私钥)。</strong></p>
  </li>
  <li>
   <p><strong>常见非对称加密算法(了解):RSA，DSA，ECDSA</strong></p>
  </li>
  <li>
   <p><strong>特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂，而使得加密解密速度没有对称加密解密的速度快。</strong></p>
  </li>
 </ul>
 <p><strong>非对称加密要用到两个密钥,一个叫做"公钥”,一个叫做"私钥”.公钥和私钥是配对的.</strong></p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F580671281fd849b88bc1d6dee1fdf461.png&amp;size=m" alt=""></p>
 <p>
  <br>
 </p>
 <p>最大的缺点就是运算速度非常慢，比对称加密要慢很多</p>
 <ol>
  <li>通过公钥对明文加密,变成密文</li>
  <li>通过私钥对密文解密,变成明文</li>
 </ol>
 <p>也可以反着用</p>
 <ol>
  <li>通过私钥对明文加密,变成密文</li>
  <li>通过公钥对密文解密,变成明文</li>
 </ol>
 <p>非对称加密的数学原理比较复杂，涉及到一些 数论 相关的知识。</p>
 <p>
  <br>
 </p>
 <p>这里举一个简单的生活上的例子.</p>
 <p>
  <br>
 </p>
 <p>A 要给 B 一些重要的文件，但是 B 可能不在。</p>
 <p>
  <br>
 </p>
 <p>于是A和 B提前做出约定：<strong>B 说:我桌子上有个盒子，然后我给你一把锁，你把文件放盒子里用锁锁上，然后我回头拿着钥匙来开锁取文件.</strong></p>
 <p>
  <br>
 </p>
 <p>在这个场景中,这把锁就相当于公钥,钥匙就是私钥。</p>
 <p>
  <br>
 </p>
 <p><strong>公钥给谁都行(不怕泄露)，但是私钥只有 B自己持有，持有私钥的人才能解密。</strong></p>
</blockquote>
<ol>
 <li><strong>对称加密算法在加密和解密过程中使用相同的密钥，速度较快，但需要事先共享密钥，导致密钥管理的复杂性。</strong></li>
 <li><strong>非对称加密算法使用不同的密钥进行加密和解密，提供更好的安全性和身份验证，但速度较慢。</strong></li>
</ol>
<p>
 <br>
</p>
<p>一种常见的方式是在通信开始时使用非对称加密算法来安全地交换对称密钥，然后使用对称加密算法进行实际的数据传输。 我们下面再讲。</p>
<h2>3.<strong>HTTPS的工作过程探究</strong></h2>
<p>我们自己给HTTP逐步设计一套安全方案，设计完一套，逐个分析各个方案的不足，最后得出最完美的，那个就和我们的HTTPS差不多了</p>
<p><strong>最开始的问题是HTTP对于报文是明文传递的。我们需要对其进行加密</strong></p>
<h3>3.1.加密方案的选定</h3>
<blockquote>
 <ul>
  <li>方案一：<strong>只使⽤对称加密</strong></li>
 </ul>
 <p><strong>如果通信双⽅都各⾃持有同⼀个密钥X，且没有别⼈知道，这两⽅的通信安全当然是可以被保证的（除非密钥被破解）</strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbf3a630fcbb14340b37c9d53fbf9658d.png&amp;size=m" alt=""></p>
 <ul>
  <li><strong>我们先看看这个方案的结果</strong></li>
 </ul>
 <p><strong>引⼊对称加密之后，即使数据被截获，由于⿊客不知道密钥是啥，因此就⽆法进⾏解密，也就不知道请求 的真实内容是啥了。这确实保证了数据的安全传输。</strong></p>
 <p><strong>但事情没这么简单，服务器同⼀时刻其实是给很多客户端提供服务的，这么多客户端，每个⼈⽤的秘钥都必须是不同的(如果是相同那密钥就太容易扩散了,⿊客就也能拿到了)， 因此服务器就需要维护每个客户端和每个密钥之间的关联关系，这也是个很⿇烦的事情\~</strong></p>
 <p>
  <br>
 </p>
 <ul>
  <li><strong>但是我们怎么实现呢？</strong></li>
 </ul>
 <p><strong>有人想说下面两种</strong></p>
 <ul>
  <li>方案A：<strong>服务器生成密钥后传递给客户端</strong></li>
  <li>方案B：<strong>客户端生成密钥后传递给服务器</strong></li>
 </ul>
 <p><strong>但是大家有没有想过，这两种方案都是在传递密钥（明文状态），这样子一定会被中间人截获。</strong></p>
 <p><strong>但是如果直接把密钥明⽂传输,那么⿊客也就能获得密钥了</strong>\~\~此时后续的加密操作就形同虚设了. <strong>因此密钥的传输也必须加密传输!</strong>但是要想对密钥进⾏对称加密,就仍然需要先协商确定⼀个"密钥的密钥”.这就成了"先有鸡还是先有 蛋"的问题了.此时密钥的传输再⽤对称加密就⾏不通了</p>
 <p>
  <br>
 </p>
 <ul>
  <li><strong>由于结果不好和不可能实现，我们就不考虑单独使用对称加密</strong></li>
  <li><strong>方案2-只使用非对称加密</strong></li>
  <li><strong>我们要知道，不是只有公钥才能加密，私钥也可以加密，同理公钥也能解密，私钥也能解密</strong></li>
 </ul>
 <p>
  <br>
 </p>
 <ul>
  <li><strong>我们先看浏览器到服务器这段路怎么保障安全？</strong></li>
 </ul>
 <p>鉴于⾮对称加密的机制，<strong>如果服务器先把公钥以明⽂⽅式传输给浏览器，之后浏览器向服务器传数据前都先⽤这个公钥加密好再传，从客户端到服务器信道似乎是安全的(有安全问题)，因为只有服务器有相应的私钥能解开公钥加密的数据。</strong></p>
 <p>
  <br>
 </p>
 <ul>
  <li><strong>但是服务器到浏览器的这条路怎么保障安全？</strong></li>
 </ul>
 <p><strong>如果服务器⽤它的私钥来对数据进行加密，然后传给浏览器，那么浏览器⽤公钥可以解密它，⽽这个公钥是⼀开始通过明⽂传输给浏览器的，若这个公钥被中间⼈劫持到了，那他也能⽤该公钥解密服务器传来的信息了。</strong></p>
 <p>
  <br>
 </p>
 <ul>
  <li><strong>这个也不安全</strong></li>
  <li><strong>方案三——双方都使用非对称加密</strong></li>
 </ul>
 <p>我们看了方案2，会不会想到，让服务器和浏览器双方各持有自己的密钥和公钥。</p>
 <ul>
  <li>
   <ol>
    <li>服务端拥有公钥A1与对应的私钥A11，客户端拥有公钥B1与对应的私钥B11</li>
   </ol>
  </li>
  <li>
   <ol>
    <li><strong>服务器先把公钥A1以明⽂⽅式传输给浏览器，浏览器将公钥B1传递给服务器</strong></li>
   </ol>
  </li>
  <li>
   <ol>
    <li>客户端给服务端发信息：先⽤A1对数据加密，再发送，只能由服务器解密，因为只有服务器有私钥 A11</li>
   </ol>
  </li>
  <li>
   <ol>
    <li>服务端给客户端发信息：先⽤B1对数据加密，再发送，只能由客户端解密，因为只有客户端有私钥 B11</li>
   </ol>
  </li>
 </ul>
 <p><strong>这样貌似也行啊，但是效率太低 依旧有安全问题。</strong></p>
 <ul>
  <li><strong>方案四——非对称加密+对称加密</strong></li>
 </ul>
 <p>这个方案来弥补第一个方案的遗憾</p>
 <ul>
  <li><strong>服务端具有非对称公钥S1和私钥S11</strong></li>
  <li><strong>客户端发起https请求，获取服务端公钥S1</strong></li>
  <li><strong>服务端以明文方式发送公钥S1给客户端</strong></li>
  <li><strong>客户端在本地生成对称密钥C，通过公钥S1加密，发送给服务器.</strong></li>
  <li><strong>由于中间的网络设备没有私钥，即使截获了数据，也无法还原出内部的原文,也就无法获取到对称密钥(真的吗?)</strong></li>
  <li><strong>服务器通过私钥S11解密，还原出客户端发送的对称密钥C，并且使用这个对称密钥C加密给客户端返回的响应数据.</strong></li>
  <li>后续客户端和服务器的通信都只用对称加密即可。<strong>由于该密钥只有客户端和服务器两个主机知道,其他主机/设备不知道密钥即使截获数据也没有意义.</strong></li>
 </ul>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffb474d27a61b4667ada0de9b9f6087cc.png&amp;size=m" alt=""></p>
 <p>这种解决方案首先使用非对称式加密保证了 <strong>密钥传输时的安全性</strong> ，确保只有客户端和服务器拥有 <strong>密钥</strong> ，因为 <strong>密钥</strong> 加密与解密的特点就是 <strong>效率高</strong>，所以后续在传输数据时，整体效率就会高很多</p>
 <ul>
  <li><strong>由于对称加密的效率⽐⾮对称加密⾼很多,因此只是在开始阶段协商密钥的时候使⽤⾮对称加密,后 续的传输仍然使⽤对称加密. 虽然上⾯已经⽐较接近答案了，但是依旧有安全问题 。</strong></li>
 </ul>
</blockquote>
<h3>3.2.加密方案的缺陷</h3>
<p><strong>我们上面得到了最接近答案的方案四，</strong>在方案4中，客户端获取到公钥S之后，对客⼾端形成的对称秘钥X⽤服务端给客⼾端的公钥 S进行加密，中间⼈即使窃取到了数据，此时中间⼈确实⽆法解出客户端形成的密钥X，因为只有服务器有私钥S' 。</p>
<p><strong>但是中间人的攻击，如果在最开始握⼿协商的时候就进⾏了，方案四那就不⼀定安全了.</strong></p>
<blockquote>
 <ul>
  <li>
   <p><strong>中间人攻击-针对上面的场景</strong></p>
  </li>
  <li>
   <p>服务器具有非对称加密算法的公钥S，私钥S'</p>
  </li>
  <li>
   <p>中间人具有非对称加密算法的公钥M，私钥M'</p>
  </li>
  <li>
   <p><strong>客户端向服务器发起请求，服务器明文传送公钥S给客户端</strong></p>
  </li>
  <li>
   <p>中间人劫持数据报文，提取公钥S并保存好，然后<strong>将被劫持报文中的公钥S替换成为自己的公钥M，并将伪造报文发给客户端</strong></p>
  </li>
  <li>
   <p><strong>客户端收到报文，提取公钥M(自己当然不知道公钥被更换过了)，自己形成对称密钥X，用公钥M加密X，形成报文发送给服务器</strong></p>
  </li>
  <li>
   <p><strong>中间人劫持后，直接用自己私钥M'进行解密，得到通信秘钥X，再用曾经保存的服务端公钥S加密后，将报文推送给服务器</strong></p>
  </li>
  <li>
   <p>服务器拿到报文，用自己的私钥S'解密，得到通信秘钥X</p>
  </li>
  <li>
   <p>双方开始采用X进行对称加密，进行通信。但是一切都在中间人的掌握中，劫持数据，进行窃听甚至修改，都是可以的</p>
  </li>
 </ul>
</blockquote>
<ul>
 <li><strong>上⾯的攻击方案，同样适⽤于方案2，方案3 问题本质出在哪⾥了呢？</strong></li>
</ul>
<p><strong>客户端⽆法确定收到的含有公钥的数据报⽂，就是目标服务器发送过来的！</strong></p>
<p>被攻击的核心原因：<strong>客户端无法验证公钥的合法性（客户端不认识服务器）</strong>，此时就需要一个公平公正的第三方机构来进行认证，确保客户端所连接的服务器是合法的</p>
<h3>3.3.引入证书</h3>
<h4>3.3.1.摘要算法</h4>
<p><strong>摘要算法（Digest Algorithm），也就是常说的散列函数、哈希函数（Hash Function）。</strong></p>
<p><strong>你可以把摘要算法近似地理解成一种特殊的压缩算法，它能够把任意长度的数据"压缩"成固定长度、而且独一无二的"摘要"字符串，就好像是给这段数据生成了一个数字"指纹”。</strong></p>
<p>换一个角度，也可以把摘要算法理解成特殊的"单向"加密算法，它只有算法，没有密钥，加密后的数据无法解密，不能从摘要逆推出原文。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F715bfe289055451aab9b4aea59e51715.png&amp;size=m" alt=""></p>
<p><strong>摘要算法实际上是把数据从一个"大空间"映射到了"小空间”，所以就存在"冲突”（collision，也叫碰撞）的可能性，就如同现实中的指纹一样，可能会有两份不同的原文对应相同的摘要。</strong>好的摘要算法必须能够"抵抗冲突”，让这种可能性尽量地小。</p>
<h4>3.3.2.数字摘要</h4>
<p><strong>数字摘要</strong> 又称为 <strong>数字指纹</strong> ，指通过哈希函数对信息进行运算后生成的<strong>一串定长字符串，具有很强的唯一性</strong> ，<strong>数字摘要</strong> 并不能加密，而是<strong>用于快速判断原始内容是否被修改</strong></p>
<ol>
 <li>⭐数字指纹(数据摘要),其基本原理是利用单向散列函数(Hash函数)对信息进行运算生成一串固定长度的数字摘要。<strong>数字指纹并不是一种加密机制，但可以用来判断数据有没有被窜改。</strong></li>
 <li>⭐摘要常见算法:有MD5、SHA1、 SHA256、SHA512等， 算法把无限的映射成有限，因此可能会有碰撞(两个不同的信息，算出的摘要相同，但是概率非常低)</li>
 <li>⭐摘要特征:和加密算法的区别是，摘要严格意义不是加密,因为没有解密,只不过从摘要很难反推原信息，通常用来进行数据对比。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F745934800f394288bb69a3c46d66774c.png&amp;size=m" alt=""></p>
<p>摘要算法保证<strong>了"数字摘要"和原文是完全等价的。所以，我们只要在原文后附上它的摘要，就能够保证数据的完整性。</strong></p>
<p><strong>比如，你发了条消息：“转账 1000 元”，然后再加上一个 SHA-2 的摘要。网站收到后也计算一下消息的摘要，把这两份"指纹"做个对比，如果一致，就说明消息是完整可信的，没有被修改。</strong></p>
<p><strong>如果黑客在中间哪怕改动了一个标点符号，摘要也会完全不同，网站计算比对就会发现消息被窜改，是不可信的。</strong></p>
<blockquote>
 <p>这种技术还可以用于网盘存储中，比如xxx网盘中就有一个 <strong>“秒传功能</strong> “，可以快速上传目标文件，其实就是根据<strong>用户上传的文件</strong> 生成了<strong>数字摘要</strong> ，将该摘要与服务器中<strong>已经记录</strong>好的摘要信息进行对比，如果发现该摘要，就证明服务器中已经存在这个文件了，无需再重复上传，只需要在用户的上传目录中建立目标文件的软链接或者硬链接，就可以实现 “秒传”。</p>
 <p>
  <br>
 </p>
 <p>就比如我上传了一部《飞驰人生2》，网盘根据<strong>用户上传的文件</strong> 生成了<strong>数字摘要，发现这个服务器里已经有人上传过</strong>《飞驰人生2》了，这个时候服务器就可以不用再上传了，直接给我返回了一个链接，这样子，我一点那个链接，就去到别人上传的《飞驰人生2》里面了。</p>
 <h4>3.3.3.<strong>数字签名</strong></h4>
 <p><strong>数字签名</strong> 是指将 <strong>数字摘要</strong> 通过加密后得到的字符串，计算过程需要借助 <strong>密钥</strong></p>
 <p>
  <br>
 </p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0b6f5f05ae7149998a7af35b6fee0704.png&amp;size=m" alt=""></p>
 <p>数字签名（又称<a href="https://so.csdn.net/so/search?q=%E5%85%AC%E9%92%A5&amp;spm=1001.2101.3001.7020" title="公钥">公钥</a>数字签名）是<strong>只有信息的发送者才能产生的别人无法伪造的一段数字串</strong>，这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名，但是使用了公钥领域的技术来实现的，用于鉴别数字信息的方法。<strong>一套数字签名通常定义两种互补的运算，一个用于签名，另一个用于验证。</strong>数字签名是非对称密钥加密技术与数字摘要技术的应用。</p>
 <p>
  <br>
 </p>
 <p><strong>数字签名的验证过程如下：</strong></p>
 <ul>
  <li>发送方使用哈希函数对消息A进行摘要处理，生成消息摘要A1。</li>
  <li>发送方使用密钥对消息摘要A1进行加密，生成数字签名A2。</li>
  <li>发送方将消息A和数字签名A2一起发送给接收方。</li>
  <li>接收方使用发送方的公钥对数字签名A2进行解密，得到消息摘要B1。</li>
  <li>接收方使用哈希函数对收到的消息A进行摘要处理，得到消息摘要B2。</li>
  <li>接收方比对解密得到的消息摘要和计算得到的消息摘要，如果一致，则证明消息的完整性和真实性得到了保证。</li>
 </ul>
</blockquote>
<p>为什么不直接对原始内容进行加密后得到 <strong>数字签名</strong>？</p>
<ul>
 <li><strong>因为在经过哈希函数后，会得到一串定长且唯一的字符串，更适合进行加密计算</strong></li>
</ul>
<h4>3.3.4.CA证书</h4>
<blockquote>
 <ul>
  <li><strong>数字证书是什么？</strong></li>
 </ul>
 <p>我们都知道，<strong>互联网是虚拟的，通过互联网我们无法正确获取对方真实身份。</strong></p>
 <p><strong>而通过数字证书可以明确对方身份</strong></p>
 <p>比如：</p>
 <p>小美通过网络发送一份电子版合同（邮件或电子版的纸质合同）给你，你是无法确定这份文档是不是小美发送的，有没有被篡改。通过<strong>数字签名</strong>的电子合同可以防篡改，可以确定这份电子合同是不是小美发送给你的。</p>
 <p><strong>数字证书就是互联网通讯中标志通讯各方身份信息的一串数字，提供了一种在互联网上验证通信实体身份的方式。</strong></p>
 <p><strong>数字证书不是数字身份证，而是身份认证机构盖在数字身份证上的一个章或印（或者说加在数字身份证上的一个签名）。其作用类似于现实生活中司机的驾驶执照或日常生活中的身份证。</strong></p>
 <ul>
  <li><strong>CA是什么？</strong></li>
 </ul>
 <p><strong>上面提到的数字证书就是CA发行的。</strong></p>
 <p>
  <br>
 </p>
 <p><strong>CA是证书的签发机构，它是公钥基础设施（Public Key Infrastructure，PKI）的核心。CA是负责签发证书、认证证书、管理已颁发证书的机关。</strong></p>
 <p>
  <br>
 </p>
 <p><strong>CA 拥有一个证书（内含公钥和私钥）。</strong></p>
 <p>
  <br>
 </p>
 <p><strong>网上的公众用户通过验证 CA 的签字从而信任 CA ，任何人都可以得到 CA 的证书（含公钥），用以验证它所签发的证书。如果用户想得到一份属于自己的证书，他应先向 CA 提出申请。</strong></p>
 <p><strong>在 CA 判明申请者的身份后，便为他分配一个公钥，并且 CA 将该公钥与申请者的身份信息绑在一起，并为之签字后，便形成证书发给申请者。</strong></p>
 <ul>
  <li><strong>CA证书是什么？</strong></li>
 </ul>
 <p>顾名思义，<strong>CA 证书就是CA颁发的证书。</strong></p>
 <p>
  <br>
 </p>
 <p>CA证书也就我们常说的数字证书，包含证书拥有者的身份信息，CA机构的签名，公钥和私钥。身份信息用于证明证书持有者的身份；CA签名用于保证身份的真实性；公钥和私钥用于通信过程中加解密，从而保证通讯信息的安全性。</p>
 <p>以上看完了还是很晕咋办？没关系，只要记得以下两点就可以了：<strong>CA是权威可信的第三方机构，是"发证机关”。CA证书是CA发的"证件”，用于证明自身身份，就像身份证和驾驶证。</strong>
  <br>
  大家在网上肯定看过下面这张图片吧</p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F92af51c4eded44f0901c5c796ba490fd.png&amp;size=m" alt=""> 这个证书就是CA证书
  <br>
  在今天，如果想使用 <code>HTTPS</code> 协议，服务器就需要向 <strong><code>CA</code>机构</strong> 申请一份 <strong><code>CA</code>证书</strong> ，证书就如同该服务的身份证，其中包含了 <strong>证书申请者信息、公钥信息</strong> 等重要信息，服务器在成功申请到证书后，就会将证书交给客户端（也就是浏览器），以进行证书认证和获取 <strong>公钥</strong></p>
</blockquote>
<p><strong>下面是申请CA证书的业务流程：</strong></p>
<ol>
 <li><strong>申请公开密钥</strong>：服务器运营人员向数字证书认证机构提出公开密钥的申请。</li>
 <li><strong>验证身份</strong>：数字证书认证机构会验证申请者的身份，确保他们是他们声称的那个人或组织。</li>
 <li><strong>数字签名：</strong>一旦身份得到确认，数字证书认证机构就会对申请的公钥进行数字签名。这个签名保证了公钥的真实性和完整性。</li>
 <li><strong>颁发证书：</strong>数字证书认证机构将签名的公钥放入一个公钥证书中，并将这个证书颁发给服务器。</li>
 <li><strong>发送证书：</strong>服务器将这份由数字证书认证机构颁发的公钥证书发送给客户端，以便进行公开密钥加密的通信。</li>
</ol>
<blockquote>
 <p><strong>注意这个证书不是挂起来的证书，而是安装在服务器的证书。</strong></p>
</blockquote>
<p><strong>我们看看数字证书通常包含了：</strong></p>
<ol>
 <li><strong>服务器的非对称密钥中的公钥；</strong></li>
 <li><strong>持有者信息；</strong></li>
 <li><strong>证书认证机构（CA）的信息；</strong></li>
 <li><strong>CA 对这份文件的数字签名及使用的算法；</strong></li>
 <li><strong>证书有效期；</strong></li>
 <li><strong>还有一些其他额外信息；</strong></li>
</ol>
<blockquote>
 <p>首先我们要了解一些预备知识</p>
 <p><strong>1.浏览器内置有CA证书：</strong></p>
 <ul>
  <li><strong>关于认证机关的公钥的安全转交问题，由于直接通过通信方式转交存在困难，</strong> <strong>大多数浏览器开发商在发布浏览器版本时，会预先在浏览器内部植入常用认证机关的CA证书。</strong></li>
  <li>客户端（如浏览器）内置了很多受信任的CA证书。<strong>这些CA证书中的公钥是用来验证服务器证书的签名的。</strong></li>
  <li>这些内置的CA证书是由浏览器厂商和操作系统开发商精心管理和更新的，它们确保客户端可以信任来自这些CA的证书。</li>
  <li>这样，当客户端访问一个使用HTTPS的网站时，浏览器可以自动验证网站证书的有效性，确保用户的安全。</li>
 </ul>
 <p>
  <br>
 </p>
 <p><strong>我们可以在浏览器设置里面看到这些内置的证书</strong></p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6807d5adbb3c413592877d0cfea61ecf.png&amp;size=m" alt=""></p>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fef74d1b8e9614e5b8758e3a0de453483.png&amp;size=m" alt=""></p>
 <p><strong>2.客户端接收到证书后，会进行以下操作：</strong></p>
 <ol>
  <li><strong>验证数字签名</strong>：客户端使用数字证书认证机构的公开密钥来验证证书上的数字签名。</li>
  <li><strong>确认证书有效性</strong>：如果签名验证通过，客户端就可以确认两件事情：一是证书是由一个真实有效的数字证书认证机构签发的；二是服务器的公开密钥是可以信赖的。</li>
 </ol>
 <p>
  <br>
 </p>
 <p><strong>3.数字签名的验证过程如下：</strong></p>
 <ul>
  <li>发送方使用哈希函数对消息A进行摘要处理，生成消息摘要A1。</li>
  <li>发送方使用密钥对消息摘要A1进行加密，生成数字签名A2。</li>
  <li>发送方将消息A和数字签名A2一起发送给接收方。</li>
  <li>接收方使用发送方的公钥对数字签名A2进行解密，得到消息摘要B1。</li>
  <li>接收方使用哈希函数对收到的消息A进行摘要处理，得到消息摘要B2。</li>
  <li>接收方比对解密得到的消息摘要和计算得到的消息摘要，如果一致，则证明消息的完整性和真实性得到了保证。</li>
 </ul>
 <p><strong>4.CA证书数字签名的验证过程：</strong></p>
 <ol>
  <li><strong>CA证书的签发</strong> ：当客户端向服务器发起请求时，服务器会发送它的数字证书。<strong>这份证书中包含了服务器用于加密的公钥和其他信息（如服务器的身份信息）。</strong><strong>服务器的数字证书是由CA机构签署的，数字签名是由CA使用其私钥生成的。</strong>服务器在向CA机构申请生成CA证书时，CA机构会对证书中的数据（如服务器的公钥和身份信息）进行哈希运算得到数字摘要，最后CA机构使用CA的私钥对这个哈希值进行加密，生成数字签名。<strong>服务器将申请到的这个数字签名和证书一起发送给客户端。</strong></li>
  <li><strong>CA证书的验证</strong> ：<strong>当客户端收到服务器发送的证书后，它会使用内置的CA公钥来解密证书中的数字签名。</strong>如果解密成功，并且解密后的哈希值与客户端自己计算的哈希值一致，那么客户端就可以确认这个证书确实是由受信任的CA签发的，且在传输过程中没有被篡改。</li>
 </ol>
</blockquote>
<p>也就是说，证书的工作过程如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F17c9eecc816d4cebb6fcdd8e3164230e.png&amp;size=m" alt=""></p>
<blockquote>
 <ul>
  <li><strong>现在有一个问题，存在中间人攻击并对数字证书进行了篡改，我们是如何保证客户端收到的服务器发送的数字签名的正确性？</strong></li>
 </ul>
 <p>存在以下情况：</p>
 <ol>
  <li><strong>证书篡改</strong>：攻击者可能会修改证书的内容，比如更改公钥为攻击者自己的公钥，或者修改证书中的其他信息。</li>
  <li><strong>证书替换</strong>：攻击者可能会用自己的证书（可能是自签名的或者由不可信的CA签发的）替换掉原始的证书。</li>
 </ol>
 <p>如果收到了中间人攻击：</p>
 <p>
  <br>
 </p>
 <p><strong>1.中间人无法伪造签名：</strong></p>
 <ul>
  <li>即使中间人截获了服务器发送的证书，并试图篡改证书内容（例如，替换服务器的公钥），他也无法生成一个有效的数字签名，因为他没有CA的私钥。</li>
  <li>只有拥有CA私钥的机构（即真正的CA）才能生成匹配的签名。中间人即使知道生成签名的算法，也无法在没有CA私钥的情况下生成合法的签名。</li>
 </ul>
 <p><strong>2.篡改后的证书会导致验证失败：</strong></p>
 <ul>
  <li>如果中间人篡改了证书内容（如替换了服务器公钥），客户端在解密数字签名后会发现，解密得到的哈希值与客户端计算出的哈希值不匹配。</li>
  <li>这意味着证书已经被篡改，客户端会拒绝信任这个证书，并且中止与服务器的通信。</li>
 </ul>
 <p>也就是说<strong>中间人如果强行篡改服务器发向客户端的证书，客户会发现证书中的明文和签名解密后的值不一致，则说明证书已被篡改，证书不可信，从而终止向服务器传输信息，防止信息泄露给中间人。</strong></p>
</blockquote>
<h3>3.4.方案五——HTTPS真容</h3>
<p>学习完上面的知识，我们终于可以得到最终方案了。</p>
<p>我们的方案5（<strong>「非对称式加密」+「对称式加密」+「CA证书」）</strong>是在方案4的基础上面增加的CA证书这个环节。我们的方案5也刚好就是HTTPS锁采取的方案。</p>
<blockquote>
 <p>HTTPS的整体过程：</p>
 <ul>
  <li><strong>服务端具有非对称公钥A1和私钥A2</strong></li>
  <li>客户端向服务器发送HTTPS请求，要求建立安全连接.</li>
  <li><strong>在服务器收到客户端请求的时候，服务器先把自己生成的一个非对称加密的公钥A1通过CA证书传递给客户端。（注意这个过程服务端使用了CA私钥对我们的证书进行了加密）</strong></li>
  <li><strong>客户端通过CA证书的公钥对证书进行验证（浏览器/客户端那边内置了CA公钥，可以解密CA私钥加密的数据）。</strong> <strong>验证成功后，客户端端根据得到服务器发送过来的公钥A1，对自己生成的对称加密的密钥B进行加密。然后将密钥B发送给服务端</strong></li>
  <li><strong>服务器得到由客户端使用服务器公钥A1加密过的数据（这个数据是客户端生成的对称加密的密钥B），最后使用服务器的私钥A2进行解密 得到对称密钥B。</strong></li>
  <li><strong>后续客户端和服务器的通信都只用对称加密即可。</strong> <strong>由于该密钥只有客户端和服务器两个主机知道,其他主机/设备不知道密钥即使截获数据也没有意义.</strong></li>
 </ul>
 <p>整个过程确保了数据在传输过程中是加密的，并且只有通信的双方能够解密，防止中途被窃听或篡改。操作系统和浏览器会定期更新信任的CA证书列表，确保新增的信任根证书和已撤销的CA证书的变化能及时反映在用户设备上。
  <br>
  也就是说<strong>HTTPS工作过程设计到的密钥有三组</strong> ：<strong>CA的非对称密钥：CA公钥，CA私钥、服务器的非对称密钥：公钥A1，私钥A2，以及客户端和服务器之间的对称密钥B。</strong></p>
</blockquote>
<p><strong>HTTPS共作过程中涉及到的密钥有三组：</strong></p>
<ol>
 <li>第⼀组（非对称加密）：用于校验证书是否被篡改。服务器持有私钥（私钥在形成CSR文件与申请证书时获得），客户端持有公钥（操作系统包含了可信任的CA认证机构有哪些，同时持有对应的公钥）。服务器在客⼾端请求时，发送携带签名的证书。客户端通过这个公钥进行证书验证，保证证书的合法性，进⼀步保证证书中携带的服务端公钥权威性。</li>
 <li>第二组（非对称加密）：用于协商生成对称加密的密钥。客户端用收到的CA证书中的公钥(是可被信任的)给随机生成的对称加密的密钥加密，传输给服务器,服务器通过私钥解密获取到对称加密密钥。</li>
 <li>第三组（对称加密）：客户端和服务器后续传输的数据都通过这个对称密钥加密解密。其实⼀切的关键都是围绕这个对称加密的密钥。其他的机制都是辅助这个密钥工作的。</li>
</ol>
<p><strong>其实⼀切的关键都是围绕这个对称加密的密钥B，其他的机制都是辅助这个密钥工作的。</strong></p>
<ol>
 <li><strong>第⼆组非对称加密的密钥A2是为了让客户端把这个对称密钥B传给服务器</strong></li>
 <li><strong>第⼀组非对称加密的CA密钥是为了让客户端拿到第⼆组非对称加密的公钥——CA机构的公钥。</strong></li>
</ol>
<blockquote>
 <p>如果对HTTPS还是一头雾水，就看看下面这个</p>
 <ol>
  <li><strong>客户端发起请求</strong>：客户端向服务器发送HTTPS请求，要求建立安全连接。</li>
  <li><strong>服务器响应并发送证书</strong>：服务器收到请求后，会发送自己的数字证书（包含服务器自己生成的非对称密钥中的公钥、数字签名以及其他的信息）给客户端。这个证书由可信的证书颁发机构（CA）签名。</li>
  <li><strong>验证证书</strong>：客户端收到证书后，会使用内置的CA公钥来验证服务器证书的真实性。如果证书有效且可信，客户端会继续，否则会中断连接并向用户发出警告。</li>
  <li><strong>生成对称加密密钥</strong>：客户端验证证书成功后，会生成一个随机的对称加密密钥，并使用服务器的公钥对客户端自己生成的密钥进行加密。</li>
  <li><strong>发送加密的对称加密密钥</strong>：客户端将加密后的对称加密密钥发送给服务器。</li>
  <li>服务器解密对称加密密钥：服务器使用自己的私钥解密收到的对称加密密钥，从而获得客户端生成的对称加密密钥。</li>
  <li><strong>建立安全通信</strong>：从此刻起，客户端和服务器都使用这个对称加密密钥进行通信。这种对称加密方式可以保证数据传输的保密性和完整性，因为对称加密在速度和效率上优于非对称加密。</li>
 </ol>
 <p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F697de574f29a4daca8c28d415f2cdded.png&amp;size=m" alt=""></p>
</blockquote>
<p>上面就是HTTPS的做法</p>
<h3>3.5.HTTPS和HTTP的区别</h3>
<p><strong>Https是身披SSL/TLS外壳的HTTP协议，它并非一个新的协议而是在HTTP的基础上新增了安全层(SSL/TLS),HTTPS先和安全层通信，然后安全层和TCP层通信。</strong></p>
<p><strong>HTTPS和HTTP的区别主要在于安全性和加密方式：</strong></p>
<ol>
 <li>建立连接时：HTTPS比HTTP多了TLS的握手过程。当使用HTTPS时，客户端和服务器在建立连接之前，会先进行一个叫做TLS握手的过程。这个过程包括客户端向服务器索要并验证服务器的公钥，确保服务器是它声称的那一个。然后，双方会协商产生一个会话秘钥，这个秘钥用来加密双方之间的通信内容。</li>
 <li>传输内容时：HTTPS会把数据进行加密，通常是使用对称加密。这意味着在传输过程中，数据是以加密的形式传输的，这样即使数据在传输过程中被截获，攻击者也无法理解数据的内容。</li>
 <li>HTTP协议是一种以字符串明文传输的简单的请求-响应协议，在传输层基于TCP协议实现。HTTP协议默认使用80端口。HTTPS协议是一种对HTTP加密后的协议，主要是多了身份验证以及加密传输等功能，HTTPS协议默认使用443端口。</li>
</ol>
<p>总结来说，<strong>HTTPS通过TLS协议提供了一种加密的通信方式，可以防止数据在传输过程中被截获和窃取，而HTTP则是明文传输，没有加密。因此，HTTPS比HTTP更加安全。</strong></p>
<p>原文链接: https://blog.csdn.net/2301_80224556/article/details/141384753</p>]]></description><guid isPermaLink="false">/archives/f4075d8b-924b-4460-b004-8707f093b763</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:15:48 GMT</pubDate></item><item><title><![CDATA[如何通过内网穿透实现Pycharm远程服务器编译项目与服务器代码同步]]></title><link>https://wiki.dgfygk.com/archives/84088a19-a86e-485a-9a21-f370456671de</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E5%AE%9E%E7%8E%B0Pycharm%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E8%AF%91%E9%A1%B9%E7%9B%AE%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BB%A3%E7%A0%81%E5%90%8C%E6%AD%A5&amp;url=/archives/84088a19-a86e-485a-9a21-f370456671de" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#_2">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#_14">一、前期准备</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1_IDE_16">1. 检查IDE版本是否支持</a></li>
   <li><a href="https://wiki.dgfygk.com#2_SSH_28">2. 服务器需要开通SSH服务</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#Pycharm_40">二、Pycharm本地链接服务器测试</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1_python_42">1. 配置服务器python解释器</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_94">三、使用内网穿透实现异地链接服务器开发</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#1_Cpolar_96">1. 服务器安装Cpolar</a></li>
   <li><a href="https://wiki.dgfygk.com#2__128">2. 创建远程连接公网地址</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#TCP_210">四、使用固定TCP地址远程开发</a></li>
</ul>
<p>
 <br>
</p>
<h3>前言</h3>
<p>本文主要介绍如何使用Pycharm进行远程开发，并结合cpolar内网穿透工具配置公网地址，轻松实现异地连接服务器编译代码与项目同步。</p>
<p>新版本<strong>Jetbrains</strong> 系列开发IDE（<strong>IntelliJ IDEA</strong> ，<strong>PyCharm</strong> ，<strong>GoLand</strong> ）等都支持远程使用服务器编译，并且可以<strong>通过SFTP同步本地与服务器项目代码</strong>。</p>
<p>这样做的好处是**我们只要连接上服务器就能开始干活儿，不用折腾环境，**不占用个人笔记本资源，最重要的是不用忍受笔记本的烂风扇噪音。</p>
<p>接下来我来教大家<strong>如何配置实现在家远程利用公司服务器资源开发</strong>：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fd68df34e7933cac7020c01163dbea970.png&amp;size=m" alt=""></p>
<h3>一、前期准备</h3>
<h4>1. 检查IDE版本是否支持</h4>
<p>本文以<strong>Pycharm</strong> 举例，<strong>IntelliJ IDEA</strong>操作方式相同。</p>
<p>确认您安装的PyCharm是社区版还是<strong>专业版</strong> ，只有<strong>专业版</strong>才支持远程开发功能。</p>
<p>在这里查看软件版本：工具栏help-&gt;about</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fae592e87d5e68c07c7169c698a15c230.png&amp;size=m" alt=""></p>
<p>显示 <strong>Professional Edition</strong>，即为专业版。</p>
<h4>2. 服务器需要开通SSH服务</h4>
<p>执行：</p>
<pre><code class="prism language-shell">sudo systemctl status sshd 
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F2d3c0d58979e2b691531e1a2daf077a5.png&amp;size=m" alt=""></p>
<p><strong>active</strong>表示已开通</p>
<h3>二、Pycharm本地链接服务器测试</h3>
<h4>1. 配置服务器python解释器</h4>
<p>找到工具栏的 [File] -&gt; [Settings]，打开以下设置弹窗，搜索interpreter找到[Python interpreter]设置项</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F0468ef0f2ed0ecce3d0b6db9b0fac601.png&amp;size=m" alt=""></p>
<p>点击Add Interpreter，选择On SSH</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9f8d860672287193f1247902fe6fd6df.png&amp;size=m" alt=""></p>
<p>将实例SSH指令中的Host、Port与Username进行匹配和填写（Username均为root，Host和Port查看自己实例的SSH指令)</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fc9e949045c748954dee895bcd91171ef.png&amp;size=m" alt=""></p>
<p>输入服务器登陆密码：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F8b526598f6ef43a6dab83df956cf08bd.png&amp;size=m" alt=""></p>
<p>继续下一步，直到看到 <strong>completed</strong> 字样，再点下一步。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ff978988c4f68a78eff583192b41a89c1.png&amp;size=m" alt=""></p>
<p>选择System Interpreter（选择服务器的代码解释器），选择服务器的远程Python解释器路径（如果您安装了其他Python或虚拟环境则根据实际情况填写）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ff8e584c8bef513f0e313b749729ff1c5.png&amp;size=m" alt=""></p>
<p>配置项目路径，意思是本地项目和远程实例中的哪个目录进行关联，这里设置为我演示项目的目录（不建议使用默认的/tmp目录）</p>
<p>选择好自定义同步项目文件夹，点击文件夹图标：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F404545a78b1a25e33e86ff8d4159140c.png&amp;size=m" alt=""></p>
<p>弹窗内左侧为本地项目路径，右侧为服务器项目路径，可以自行修改：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ff1a94bfc3a0720eedba16e4229585447.png&amp;size=m" alt=""></p>
<p>我修改的远程路径为：/root/projects/pythonlearn，点 ok</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ff346aba490cd8967ce1a43884d04cbb3.png&amp;size=m" alt=""></p>
<p>检查一下远程服务器的解释器和同步路径，点 Create</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9e22bc42db473198f2c73fc220a6cb2f.png&amp;size=m" alt=""></p>
<p>如果配置均无误PyCharm会配置一下，之后我们在本机项目最下边可以看到 SFTP 字样和服务器信息，说明我们的配置成功，此时我们不光使用服务器编译代码，项目还能与服务器同步。</p>
<p>我链接服务器后同步出的数据如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fff5ae60dd62f71934806c3125713e522.png&amp;size=m" alt=""></p>
<p>此处我们在本地测试成功，但是这样配置的意义就是可以实现异地使用服务器的资源开发，这里我们**需要将SSH时使用的本地IP映射为公网TCP地址，才能实现异地开发。**这样我们只需本地编写代码，远程到服务器编译并同步代码。</p>
<h3>三、使用内网穿透实现异地链接服务器开发</h3>
<h4>1. 服务器安装Cpolar</h4>
<p>本地测试完成后,我们需要在服务器上安装cpolar内网穿透工具,通过cpolar 转发本地端口映射的公网地址,我们可以很容易实现远程访问。下面是安装cpolar步骤：</p>
<blockquote>
 <p>cpolar官网地址: <a href="https://www.cpolar.com">https://www.cpolar.com</a></p>
</blockquote>
<ul>
 <li>使用一键脚本安装命令</li>
</ul>
<pre><code class="prism language-shell">curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
</code></pre>
<ul>
 <li>向系统添加服务</li>
</ul>
<pre><code class="prism language-shell">sudo systemctl enable cpolar
</code></pre>
<ul>
 <li>启动cpolar服务</li>
</ul>
<pre><code class="prism language-shell">sudo systemctl start cpolar
</code></pre>
<p>cpolar安装成功后，在外部浏览器上访问Linux 的9200端口即:【http://服务器的局域网ip:9200】，使用cpolar账号登录,登录后即可看到cpolar web 配置界面,结下来在web 管理界面配置即可</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F367660702b479a1cfe6aea2aacf8dda8.png&amp;size=m" alt=""></p>
<p><strong>Ubuntu18及Centos7之前的系统，需要使用手动安装</strong>Cpolar，详见：</p>
<p><a href="https://www.cpolar.com/blog/linux-manual-installation-of-cpolar-method">Linux手动安装Cpolar方法 - cpolar 极点云</a></p>
<h4>2. 创建远程连接公网地址</h4>
<p>登录cpolar web UI管理界面后,点击左侧仪表盘的隧道管理——创建隧道：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ffac9f55371c4a810f7547a2a126c998b.png&amp;size=m" alt=""></p>
<ul>
 <li>隧道名称：可自定义，注意不要与已有的隧道名称重复</li>
 <li>协议：tcp</li>
 <li>本地地址：22 (本地ssh默认端口)</li>
 <li>域名类型：临时随机TCP端口</li>
 <li>地区：选择China</li>
</ul>
<p>点击<code>创建</code></p>
<p>创建成功后,打开左侧在线隧道列表,查看刚刚创建隧道后生成的tcp地址,这个地址就是公网连接地址,接下来使用这个地址：<strong><em>1.tcp.cpolar.cn:20747</em></strong> ，替换我们的本地地址。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F8f27e1357204ac5ec2dec8bdda44fedd.png&amp;size=m" alt=""></p>
<p>我们再次回到编译器设置界面， 点击Add Interpreter，选择On SSH</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9f8d860672287193f1247902fe6fd6df.png&amp;size=m" alt=""></p>
<p>找到[Python interpreter]设置项，选择Existing，修改Python编译器地址：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa8d276cb8b3cd374bbef61b6408fd96c.png&amp;size=m" alt=""></p>
<p>选择后边是 agent 这个 sever选项</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9f0b92e41cdc4a1dd5207d410ddd962c.png&amp;size=m" alt=""></p>
<p>点 管理</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F816f4e9dd199bb7a4c78263978ae9738.png&amp;size=m" alt=""></p>
<p>修改本地地址为公网TCP地址，修改端口号。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F550ec4cba45dffba127bae997244e71c.png&amp;size=m" alt=""></p>
<p>修改为：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F9dd35c0ad5adefdee2c5ba42c8365d38.png&amp;size=m" alt=""></p>
<p>点击Apply部署，点 ok，自动跳转为：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F240970a3849ed25f81eb8ae1ade7db8f.png&amp;size=m" alt=""></p>
<p>点 ok</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F64491dc8d349d93eec6d8afc2a4b2d8c.png&amp;size=m" alt=""></p>
<p>输入服务器登陆密码：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa7d05d391bb83c7dadd7180a59bbc53c.png&amp;size=m" alt=""></p>
<p>配置完毕，点Next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ffe43be559e0e10594fc42f4553ad823a.png&amp;size=m" alt=""></p>
<p>我们可以看到已经链接成功，可以看到编译器路径和项目保存路径，点 create：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F4c548b5ee63aadd056b33632ef004d37.png&amp;size=m" alt=""></p>
<p>等待一会加载：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fda080bf4eaffe1beec306a4fd0888067.png&amp;size=m" alt=""></p>
<p>此时Python编译器处已经显示我们配置的公网地址，点Apply，就配置完成。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F6c827a7e019dff0a6284617e92cebe1f.png&amp;size=m" alt=""></p>
<p>项目页面左下角已经显示我们编译器的路径，此时我们编译代码使用的就是服务器编译器，运行完毕后结果返回至Pycharm。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F59feba304153062b9ac5f6bb79858e8f.png&amp;size=m" alt=""></p>
<p><strong>小结</strong></p>
<p>为了方便演示，我们在前述过程中使用了Cpolar生成的TCP地址隧道，其公网地址是随机生成的。</p>
<p>这种随机地址的优势在于建立速度快，可以立即使用。然而，它的缺点是网址是随机生成，这个地址在24小时内会发生随机变化，更适合于临时使用。</p>
<p>我一般会使用固定TCP域名，原因是我希望每天打开开发工具直接上手工作，不用这配置一下那配置一下，接下来我教大家如何固定TCP地址：</p>
<h3>四、使用固定TCP地址远程开发</h3>
<p><strong>访问官网控制面板，保留TCP：</strong></p>
<p><a href="https://dashboard.Cpolar.com/reserved">Cpolar - secure introspectable tunnels to localhost</a></p>
<p>保留一个固定tcp地址,登录<a href="https://www.cpolar.com">cpolar官网</a>，点击左侧的预留，找到保留的tcp地址，为远程服务器连接保留一个固定tcp地址：</p>
<ul>
 <li>地区：选择<strong>China VIP</strong> / <strong>China VIP Top</strong>。</li>
 <li>描述：即备注，可自定义</li>
</ul>
<p>点击<code>保留</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F77a1bdf00bfe03fae4971393b7f9a54c.png&amp;size=m" alt=""></p>
<p>地址保留成功后，系统会生成相应的固定公网地址，将其复制下来:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F828bdacb06b7faa734b825599bbff7aa.png&amp;size=m" alt=""></p>
<p>打开cpolar web ui管理界面，点击左侧仪表盘的隧道管理——隧道列表，找到我们上面创建的TCP隧道，点击<code>编辑</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F10b7ebb4688b0f3d4bc7fe519d6925bf.png&amp;size=m" alt=""></p>
<p>选择 固定TCP端口：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa040e9be5d462142e7a389d1ea2838c8.png&amp;size=m" alt=""></p>
<p>修改隧道信息，将保留成功的固定tcp地址配置到隧道中</p>
<ul>
 <li>端口类型：修改为固定TCP端口</li>
 <li>预留的tcp地址：复制粘贴官网保留成功的地址</li>
</ul>
<p>点击<code>更新</code></p>
<p>隧道更新成功后，点击左侧仪表盘的状态——在线隧道列表，可以看到公网地址已经更新成为了<strong>固定TCP地址。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ff951f76b5ffe70d2dd6db20b86b00ff2.png&amp;size=m" alt=""></p>
<p>我们仍然重复之前的操作，将原来的随机TCP地址替换为固定TCP地址：</p>
<p>找到工具栏的 [File] -&gt; [Settings]，选择Existing，修改Python编译器地址：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F2d901c68e582aaf97da0937470361731.png&amp;size=m" alt=""></p>
<p>按照如下方法填写我们固定好的TCP地址和端口号：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fe038782cec7b8fd119a22228e536b94f.png&amp;size=m" alt=""></p>
<p>点击Apply后，点 ok</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fe61269fec67c55bf79bb52f164c72386.png&amp;size=m" alt=""></p>
<p>点 Next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Ffe8ba45c2696825171f3bd68e72e0f46.png&amp;size=m" alt=""></p>
<p>点 ok</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Faff3d0fac3f84e98a4bbcc8c9adb3909.png&amp;size=m" alt=""></p>
<p>输入服务器登陆密码</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F36bd9d6c6b3db58bcdde8e3046ee022c.png&amp;size=m" alt=""></p>
<p>配置完毕，点 Next。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F3e8f3a02d76da37e6129d74e58ca214a.png&amp;size=m" alt=""></p>
<p>这里我们看到，已经自动查到我们服务器中编译器路径和项目保存路径；点 create。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fa415a416301ca702a23c02775871b0da.png&amp;size=m" alt=""></p>
<p>点Apply部署，点 ok</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2F918d044397ce203e90538f0f8e5e006f.png&amp;size=m" alt=""></p>
<p>此时我们可以看到左下角已经显示使用我们固定地址的Python编译器：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fimg-blog.csdnimg.cn%2Fimg_convert%2Fe68d5ecbdc0fbd06849c87e3928bf55a.png&amp;size=m" alt=""></p>
<p>本教程最终效果已经达成。我们已经能够使用永久固定地址来远程操作服务器中的Python编译器并将运行结果返回到Pycharm，代码修改还能通过这个TCP地址同步，这样我们就可在任意地点远程使用服务器的强大算力来编译各种复杂的项目而不占用本地资源！</p>
<p>原文链接: https://blog.csdn.net/bushibrnxiaohaij/article/details/142106962</p>]]></description><guid isPermaLink="false">/archives/84088a19-a86e-485a-9a21-f370456671de</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:15:31 GMT</pubDate></item><item><title><![CDATA[如何用个人电脑搭建一台本地服务器，并部署云原生开发工具TitanIDE到服务器详细教程]]></title><link>https://wiki.dgfygk.com/archives/a4655183-7992-478c-9780-8ff3990c5aa6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E7%94%A8%E4%B8%AA%E4%BA%BA%E7%94%B5%E8%84%91%E6%90%AD%E5%BB%BA%E4%B8%80%E5%8F%B0%E6%9C%AC%E5%9C%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%8C%E5%B9%B6%E9%83%A8%E7%BD%B2%E4%BA%91%E5%8E%9F%E7%94%9F%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7TitanIDE%E5%88%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B&amp;url=/archives/a4655183-7992-478c-9780-8ff3990c5aa6" width="1" height="1" alt="" style="opacity:0;">
<p>服务器是一种高性能计算机，作为网络的节点，它存储、处理网络上80%的数据、信息，因此也被称为网络的灵魂。与普通计算机相比，服务器具有高速CPU运算能力、长时间可靠运行、强大I/O外部数据吞吐能力以及更好的扩展性。</p>
<p>服务器的主要用途广泛，包括：</p>
<ol>
 <li><strong>网站托管</strong>：服务器可以托管各种网站，包括个人博客、企业官网、电商平台等。通过服务器，网站的内容可以存储并对外提供访问服务。</li>
 <li><strong>数据存储与备份</strong>：服务器可以作为数据中心，存储大量的数据和文件。这对于企业、机构或个人来说都非常重要，尤其是需要长期保存或共享的数据。</li>
 <li><strong>应用程序运行</strong>：各种在线应用程序，如社交媒体、在线游戏、办公软件等，都需要在服务器上运行。服务器为这些应用程序提供计算资源和存储空间。</li>
 <li><strong>数据库管理</strong>：服务器可以用来存储和管理数据库，如关系型数据库或非关系型数据库。这对于需要处理大量数据或提供实时数据查询的应用来说至关重要。</li>
 <li><strong>视频流和媒体服务</strong>：服务器可以处理视频流和媒体文件，如在线视频平台、直播平台等，确保用户能够流畅地观看和下载媒体内容。</li>
 <li><strong>云计算服务</strong>：云计算服务提供商使用大型服务器集群来提供计算、存储和网络服务，满足用户的各种需求。</li>
</ol>
<p>总的来说，服务器是现代社会数字化、网络化发展中不可或缺的基础设施，为各种在线服务和应用提供了强大的支持。</p>
<h4>一、搭建服务器</h4>
<h5>1.下载VMware并安装，下载镜像文件，后缀名为iso，常见的镜像有Centos和Ubuntu,本文以Centos 7镜像进行搭建。</h5>
<p>VMware下载地址：<a href="https://softwareupdate.vmware.com/cds/vmw-desktop/ws/17.6.0/24238078/windows/core/VMware-workstation-17.6.0-24238078.exe.tar" title="Download VMware Workstation Pro">Download VMware Workstation Pro</a></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff0560f38933f495bacaf9ce2af9058db.png&amp;size=m" alt=""></p>
<p>Centos 7和Ubuntu下载地址：<a href="https://www.cloudtogo.cn/product-TitanIDE" title="TITANIDE 快速安装">TITANIDE 快速安装</a></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1fd3f52933b1bf08e1283b2280b36d77.png&amp;size=m" alt=""></p>
<h5>2.打开VMware,创建新的虚拟机</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F642bf9984f442b9a1691007c07371c1d.png&amp;size=m" alt=""></p>
<p>3.选择自定义，下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F462d89fa7b5f5704a8679429727b2051.png&amp;size=m" alt=""></p>
<p>下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff58f24bce13a5dcbb82d8163c4f65880.png&amp;size=m" alt=""></p>
<h5>3.选择稍后安装操作系统，下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F010b52aef8ff70829f1ca410e629bfc7.png&amp;size=m" alt=""></p>
<h5>4.选择Linux系统以及版本就是您下载的镜像版本，如果您是Ubuntu就选Ubuntu，您是Centos 7就选Centos 7 ,下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F63908bc04ec3b3ad775f2c4b607b6bb6.png&amp;size=m" alt=""></p>
<h5>5.虚拟机名称自定义，虚拟机位置尽量选容量大的盘进行存放，下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe4dd6a94aca63e295d44408cd36a872c.png&amp;size=m" alt=""></p>
<h5>6.处理器数量和核数根据个人需求选择，下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7a393e872f5f69d1e4d85bd4d8cac733.png&amp;size=m" alt=""></p>
<p>7.虚拟机内存也根据个人需求而定</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe5b1f1a0884bcacc43e4e2e3b8151ce7.png&amp;size=m" alt=""></p>
<h5>7.网络连接类型默认即可，下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0c1175afdd6fd5bbc3a1f03ef6969d4b.png&amp;size=m" alt=""></p>
<h5>8.镜像控制器类型和磁盘类型默认即可，下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdae3b6baaddc7e11326f6e19d8aa0ca4.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb355eedc77a77c8cfae04fc3a392be26.png&amp;size=m" alt=""></p>
<h5>9.磁盘选择创建新的虚拟磁盘，如果之前有创建过虚拟磁盘可以选择使用现有的，下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9bd9368d9b9c6c351a215c331bee32a3.png&amp;size=m" alt=""></p>
<h5>10.磁盘容量根据个人需求而定，不要太低，不然什么都做不了，磁盘分区在配置完成以后可以进行详细的分配，这里先默认，下一步</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8ef2e885f1d2b6d7be7710e01063efcc.png&amp;size=m" alt=""></p>
<p>下一步</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff88d0241eb9d4bb3a6fe429fbcd9d327.png&amp;size=m" alt=""></p>
<p>点击完成</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcf9d472f3f85a030c3d37799ff7f00ae.png&amp;size=m" alt=""></p>
<h5>11.配置镜像</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F02861866bcde9213a14fb905e66256ff.png&amp;size=m" alt=""></p>
<p>把下载好的镜像文件上传到指定位置，文件后缀名为iso，然后点击确定</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb0b0761817b11d5c30f5e65fdf877697.png&amp;size=m" alt=""></p>
<h5>12.开启此虚拟机，静静的等待就行</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F911aa37f04ea5e5e6906596d6a3ff1a5.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F39fc659f39e3b049af21a02e785f0b62.png&amp;size=m" alt=""></p>
<h5>13.配置虚拟机镜像相关内容，选择中文，继续</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6f1db0863e877d19fe9938032d698ce7.png&amp;size=m" alt=""></p>
<h5>14.软件选择，我这里默认最小安装，当然也有其他环境选择，点击完成</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5997a412435257d53d3b4385b34952f9.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F78b234acd6176d25918e1cdc7cf2dea0.png&amp;size=m" alt=""></p>
<h5>15.安装源默认即可，点击完成</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc3c53c290927e864cfd68283eb4ea2dc.png&amp;size=m" alt=""></p>
<h5>16.安装位置，除了自动配置分区，还可以根据个人需求去配置自己想要的分区，我这里选择自动配置分区，点击完成</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9f1b55b24edf92ce7940d3f478c3a12e.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe9fb7524136a0e1a7d0cb69f8fa602f5.png&amp;size=m" alt=""></p>
<h5>17.关闭KDUMP，因为KDUMP会占用内存</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffde4042ef40c38b7d5e92ad1f434de29.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F48675d791653f0606e7bb89081364515.png&amp;size=m" alt=""></p>
<h5>18.启用以太网，启用以后会显示您的服务器ip，也就是在浏览器输入这个ip就可以访问到您服务器的内容，点击完成</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd0bcd9aec3eb0a1debb74bc255e05e7b.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe5d09703c8d219bffec42d0dd0901112.png&amp;size=m" alt=""></p>
<h5>19.开启安装</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb7848693aa5b1d5aa2e8fb91dcde5fc3.png&amp;size=m" alt=""></p>
<h5>20.在等待安装的过程中，我们可以设置root用户密码</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F93ee6bcb30d8420b39e8677a7172f681.png&amp;size=m" alt=""></p>
<p>密码越简单越好，点击完成，静待安装即可</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe784d1929fce31c10aaa0180034958e9.png&amp;size=m" alt=""></p>
<h5>21.安装完成以后，重启</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7b97b05f533798682e5eca2df3fb3572.png&amp;size=m" alt=""></p>
<p>来到这一步的时候，你得服务器已经搭建完成了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6c0d52f550660a1119bdfae82fdb7518.png&amp;size=m" alt=""></p>
<p>登录root用户</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff5717b31b92ca1a515a8ddf7bb737e6f.png&amp;size=m" alt=""></p>
<h4>二、部署项目到服务器</h4>
<p>完成以上操作，你的服务器是完成搭建了，但是里面并没有任何东西，我们去浏览器访问是不会有任何数据展示的，这时候就需要部署点项目到服务器发挥他的最大用处，部署项目的过程也很简单，只需要一行命令。</p>
<h5>1.首先访问服务器的root权限，除了上述的访问方式还可以用本地PC的命令行工具进行访问，win+R快捷键访问</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F97dfa85e68ada318f4e189d48a535e71.png&amp;size=m" alt=""></p>
<p>用ssh命令访问，然后输入您在配置虚拟机时设置的root用户密码,切换为您服务器的ip地址</p>
<pre><code>ssh root@192.10.10.10
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F767ae435104bc996db657303a311db5e.png&amp;size=m" alt=""></p>
<h5>2.接下来部署项目到服务器只需要一行命</h5>
<p>只需打开<a href="https://www.cloudtogo.cn/product-TitanIDE?csdn" title="TitanIDE">TitanIDE<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.3.7%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DO83A&amp;size=m" alt="">https://www.cloudtogo.cn/product-TitanIDE?csdn</a>的官网进行查看快速安装命令</p>
<p>输入命令以后，等待几分钟即可</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5f03f16d59a946a1b0c2b2398a8ebbd2.png&amp;size=m" alt=""></p>
<p>当显示以下信息，说明您成功在服务器上部署了TitanIDE，我们只需要去到浏览器输入ip地址访问服务器，便可以看到TitanIDE的登录页面，输入TitanIDE提供的测试账号和密码，便可看到您TitanIDE的完整内容</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa88ab9bdb3c3f473dfc844062309be6c.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F644a1d6c4ce3d1723464856c9de2da41.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F78af9a294608252aaab734dc3bafb664.png&amp;size=m" alt=""></p>
<p>以上就是搭建服务器以及部署项目到服务器的完整过程，如果您觉得这个教程不够详细的话可以去博主的视频网站观看详细的视频教程</p>
<p><a href="https://www.bilibili.com/video/BV1xevkeVE5Y/?spm_id_from=333.999.0.0&amp;vd_source=92c7aa456e98cc66a84fbb3a4b4703d2" title="全程干货，4分钟学会从Linux虚拟机搭建→磁盘分配→静态ip配置→部署云原生开发工具→信任证书导入全流程_哔哩哔哩_bilibili">全程干货，4分钟学会从Linux虚拟机搭建→磁盘分配→静态ip配置→部署云原生开发工具→信任证书导入全流程_哔哩哔哩_bilibili</a></p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/weixin_53937255/article/details/137667492</p>]]></description><guid isPermaLink="false">/archives/a4655183-7992-478c-9780-8ff3990c5aa6</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:15:11 GMT</pubDate></item><item><title><![CDATA[Linux安装宝塔，并实现公网远程登录宝塔面板【内网穿透】]]></title><link>https://wiki.dgfygk.com/archives/5f53d9ea-a236-4203-879c-f7ecc3dab444</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E5%AE%89%E8%A3%85%E5%AE%9D%E5%A1%94%EF%BC%8C%E5%B9%B6%E5%AE%9E%E7%8E%B0%E5%85%AC%E7%BD%91%E8%BF%9C%E7%A8%8B%E7%99%BB%E5%BD%95%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E3%80%90%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E3%80%91&amp;url=/archives/5f53d9ea-a236-4203-879c-f7ecc3dab444" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>文章目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_1">前言</a>
  <br>
  *</li>
 <li><a href="https://wiki.dgfygk.com#_8">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#1__15">1. 安装宝塔</a></li>
 <li><a href="https://wiki.dgfygk.com#2_cpolar_48">2. 安装cpolar内网穿透</a></li>
 <li><a href="https://wiki.dgfygk.com#3__96">3. 远程访问宝塔</a></li>
 <li><a href="https://wiki.dgfygk.com#4_http_134">4. 固定http地址</a></li>
 <li><a href="https://wiki.dgfygk.com#5__152">5. 配置二级子域名</a></li>
 <li><a href="https://wiki.dgfygk.com#6__172">6. 测试访问二级子域名</a></li>
</ul>
<p>
 <br>
</p>
<h2>前言</h2>
<ol>
 <li>安装宝塔</li>
 <li>安装cpolar内网穿透</li>
 <li>远程访问宝塔</li>
 <li>固定http地址</li>
 <li>配置二级子域名</li>
 <li>测试访问二级子域名</li>
</ol>
<h3>前言</h3>
<p>宝塔面板作为建站运维工具，它支持一键LAMP/LNMP/集群/监控/网站/FTP/数据库/JAVA等100多项服务器管理功能，可提高运维效率。对于新手来说，简单好用，基本不需要操心。而当我们在家里/公司搭建了宝塔，没有公网IP，但是想要在外也可以访问内网的宝塔面板应该如何实现呢？</p>
<p>内网穿透，它可以使用一台有公网IP的服务器作为 “中间人” 来与没有公网 IP 的电脑建立连接并转发数据。因此，想要实现公网的远程访问，内网穿透是很好的选择。</p>
<p>下面简单几步实现在linux上宝塔面板，并做内网穿透实现在公网环境下远程登录宝塔面板:</p>
<h3>1. 安装宝塔</h3>
<p>进入官网查看命令,这里以linux为准,以下是linux安装命令:</p>
<pre><code class="prism language-php">yum install -y wget &amp;&amp; wget -O install.sh https://download.bt.cn/install/install_6.0.sh &amp;&amp; sh install.sh ed8484bec
</code></pre>
<p>等待安装完成
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6b4a2a8792f0a2d72ff7e63719ba17a2.png&amp;size=m" alt=""></p>
<p>安装完成后记录这些访问地址,和账号信息.</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb67dc0b6bb347065f6a28090a2192b15.png&amp;size=m" alt=""></p>
<p>然后我们打开浏览器,访问一下上面的内网地址,出现界面表示成功,如果没有界面,可能是防火墙问题,需配置防火墙放开14550端口</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe26e18f8c5412a3a75be11c9284782ce.png&amp;size=m" alt=""></p>
<p>使用上面的账号密码登陆后,会提示绑定宝塔账号.所以我们需要注册一个宝塔账号</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb62b87edf8885d1ceb5a883232f20561.png&amp;size=m" alt=""></p>
<p>在官网注册账号登陆后,我们先开放一个宝塔端口14550</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6a7bb58ebb33598e8b398bdc508a27d3.png&amp;size=m" alt=""></p>
<h3>2. 安装cpolar内网穿透</h3>
<ul>
 <li>安装cpolar</li>
</ul>
<p>打开宝塔终端命令窗口,使用cpolar一件安装脚本:</p>
<pre><code class="prism language-cpp">curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F263e86efaca1d1fdbff0e211f581b4f6.png&amp;size=m" alt=""></p>
<ul>
 <li>token认证</li>
</ul>
<p>登录<a href="https://www.cpolar.com/?channel=0&amp;invite=4jCo">cpolar官网</a>,点击左侧的<strong>验证</strong>，查看自己的认证token，之后将token贴在命令行里</p>
<pre><code class="prism language-cpp">cpolar authtoken xxxxxxx
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F023307f30628c346c4b307091ba230c2.png&amp;size=m" alt=""></p>
<ul>
 <li>向系统添加服务</li>
</ul>
<pre><code class="prism language-cpp">sudo systemctl enable cpolar
</code></pre>
<ul>
 <li>启动cpolar服务</li>
</ul>
<pre><code class="prism language-cpp">sudo systemctl start cpolar
</code></pre>
<p>在宝塔面板中选择安全，然后开放9200端口
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F78934ff59364e5d4920a9d3b5ab7ff16.png&amp;size=m" alt=""></p>
<ul>
 <li>登录cpolar web UI管理界面</li>
</ul>
<p>在浏览器上访问<strong>局域网ip:9200</strong>端口即可出现cpolar管理界面</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbc5613d351386451270fbb113f61351f.png&amp;size=m" alt=""></p>
<p>输入官网注册的账号登陆即可对隧道进行操作.</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdd1c128b8cf342f92df37c00782a53dc.png&amp;size=m" alt=""></p>
<h3>3. 远程访问宝塔</h3>
<p>接下来创建一条隧道将本地端口映射到公网上，实现公网远程访问宝塔</p>
<p>点击左侧的隧道管理——创建隧道，创建一个随机的http隧道，指向14550端口:</p>
<ul>
 <li>隧道名称：可自定义，注意不要重复</li>
 <li>协议：http</li>
 <li>本地地址：14550</li>
 <li>端口类型：随机域名</li>
 <li>地区：China vip</li>
</ul>
<p>点击<strong>创建</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3489c8291494dc708a66d287fc6347eb.png&amp;size=m" alt=""></p>
<p>创建成功后我们打开在线隧道列表,查看公网地址
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe60e1644109a8073a783cec9ac06b5d9.png&amp;size=m" alt=""></p>
<p>然后我们使用公网地址访问宝塔面板,此时会出现如下问题,这个问题是访问宝塔面板的时候,需要在地址后面加一个8位数的字符安全入口名称
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5274a8fb68cfe8e2f2dbede389cd17bd.png&amp;size=m" alt=""></p>
<p>为了使得连接更加流畅,我们可以关闭这个验证,打开宝塔终端,输入以下命令</p>
<pre><code class="prism language-cpp">rm -f /www/server/panel/data/admin_path.pl
</code></pre>
<p>再重新访问公网地址，即可访问成功
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff96d4e2562322cae7b125edd82752002.png&amp;size=m" alt=""></p>
<h3>4. 固定http地址</h3>
<p>由于刚刚创建隧道使用的是随机临时地址，该地址会在24小时内发生变化，为了长期远程访问，我们接下来将这个公网地址配置为固定的。</p>
<ul>
 <li><strong>保留二级子域名</strong></li>
</ul>
<p>需升级至基础套餐或以上才支持配置二级子域名</p>
<p>登录<a href="https://i.cpolar.com/m/4jCo">cpolar官网后台</a>，点击左侧仪表盘的<strong>预留</strong> ，<strong>找到保留二级子域名</strong>，为http隧道保留一个二级子域名。</p>
<ul>
 <li>地区：选择服务器地区</li>
 <li>名称：填写您想要保留的二级子域名（可自定义）</li>
 <li>描述：即备注，可自定义填写
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F280b07a8dfdb8ee2a36f53a484b09756.png&amp;size=m" alt=""></li>
</ul>
<p>本例保留一个名称为<strong>baotatest</strong>的二级子域名。子域名保留成功后，我们将子域名复制下来，接下来需要将其配置到隧道中去。</p>
<h3>5. 配置二级子域名</h3>
<p>登录cpolar web ui管理界面。点击左侧仪表盘的<strong>隧道管理——隧道列表</strong> ，找到需要配置二级子域名的隧道（本例中为baota隧道），点击右侧的<strong>编辑</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa2a70b72e6e1cbee2efc5f62d47e3e4f.png&amp;size=m" alt=""></p>
<p>修改隧道信息，将二级子域名配置到隧道中：</p>
<ul>
 <li>域名类型：改为选择<strong>二级子域名</strong></li>
 <li>Sub Domain：填写我们刚刚所保留的二级子域名（本例为<strong>baotatest</strong>）</li>
</ul>
<p>修改完成后，点击<strong>更新</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe2f41a2c834d8d1d6423f9577ff6b994.png&amp;size=m" alt=""></p>
<p>隧道更新成功后，点击左侧仪表盘的状态——在线隧道列表，可以看到baotaTest隧道的公网地址，已经更新为二级子域名了，将公网地址复制下来。</p>
<h3>6. 测试访问二级子域名</h3>
<p>打开浏览器，我们来测试一下访问配置成功的二级子域名,测试成功，可以正常访问。现在，我们全网唯一的私有二级子域名，就创建好了。 而且这个地址也不会再随机变化，他是固定不变的，只要保持隧道在线，我们在外就可以通过这个公网地址，随时随地远程访问，无需公网IP，也不用设置路由器。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff35574e15d343915b372197f05acfc9d.png&amp;size=m" alt=""></p>
<hr>
<p>原文链接: https://blog.csdn.net/m0_63100066/article/details/129941662</p>]]></description><guid isPermaLink="false">/archives/5f53d9ea-a236-4203-879c-f7ecc3dab444</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:14:54 GMT</pubDate></item><item><title><![CDATA[宝塔面板安装教程（2024年更新版）]]></title><link>https://wiki.dgfygk.com/archives/e4283b76-530f-48b6-affb-55fee77cc99b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B%EF%BC%882024%E5%B9%B4%E6%9B%B4%E6%96%B0%E7%89%88%EF%BC%89&amp;url=/archives/e4283b76-530f-48b6-affb-55fee77cc99b" width="1" height="1" alt="" style="opacity:0;">
<p>随着互联网的飞速发展，网站的建设与维护已经成为许多企业和个人的必备技能。而宝塔面板作为一款功能强大、操作简便的服务器运维软件，深受广大用户的喜爱。本教程将详细介绍如何在自己的VPS上安装<a href="https://www.buildwebsite.top/index.php/2024/04/25/bt-install-2024/">宝塔面板</a>，并帮助用户轻松搭建网站。</p>
<p>一、安装前准备</p>
<p>在安装宝塔面板之前，我们需要做好以下准备工作：</p>
<p>购买并设置VPS
 <br>
 首先，我们需要购买一台VPS服务器。在选择VPS时，建议考虑服务器的性能、稳定性以及价格等因素。购买完成后，我们需要根据服务商的指引进行VPS的基本设置，如设置密码、安装操作系统等。</p>
<p>下载Xshell软件
 <br>
 为了方便管理VPS，我们需要下载并安装Xshell软件。Xshell是一款强大的终端模拟软件，支持SSH连接，可以让我们通过命令行的方式远程操作VPS。</p>
<p>二、安装宝塔面板</p>
<p>接下来，我们将开始安装宝塔面板。请按照以下步骤进行操作：</p>
<p>打开Xshell并连接VPS
 <br>
 打开Xshell软件，点击"新建"按钮创建一个新的连接配置。在配置中填写VPS的IP地址、端口号（默认为22）、用户名和密码等信息。然后点击"连接"按钮，等待连接成功。</p>
<p>登录VPS
 <br>
 连接成功后，我们将看到命令行界面。此时，我们需要输入VPS的用户名和密码进行登录。通常情况下，用户名默认为"root”。</p>
<p>复制并粘贴安装命令
 <br>
 登录成功后，我们需要复制<a href="https://www.buildwebsite.top/index.php/2024/04/25/bt-install-2024/">宝塔面板</a>的安装命令并粘贴到命令行中执行。请根据您的VPS操作系统类型选择相应的命令进行复制和粘贴：</p>
<p>对于CentOS系统：</p>
<pre><code class="prism language-bash">yum install -y wget &amp;&amp; wget -O install.sh http://download.bt.cn/install/install_6.0.sh &amp;&amp; bash install.sh
</code></pre>
<p>对于Ubuntu/Deepin系统：</p>
<pre><code class="prism language-bash">wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh &amp;&amp; sudo bash install.sh
</code></pre>
<p>对于Debian系统：</p>
<pre><code class="prism language-bash">wget -O install.sh http://download.bt.cn/install/install-debian_6.0.sh &amp;&amp; sudo bash install.sh
</code></pre>
<p>粘贴命令后，按回车键执行。在执行过程中，您可能需要输入"yes"以确认安装，或者根据提示输入其他相关信息。</p>
<p>等待安装完成
 <br>
 安装过程可能需要一些时间，具体时间取决于您的VPS性能和网络速度。请耐心等待安装完成。在安装过程中，您可以看到命令行界面上的安装进度提示。</p>
<p>获取登录信息
 <br>
 安装完成后，您将看到宝塔面板的登录地址、账号和密码等信息。请务必将这些信息保存好，以便日后登录管理。</p>
<p>三、登录宝塔面板并配置环境</p>
<p>打开浏览器登录宝塔面板
 <br>
 使用保存的登录地址在浏览器中打开宝塔面板的登录页面。输入之前获取的账号和密码进行登录。</p>
<p>配置服务器环境
 <br>
 登录成功后，您将看到宝塔面板的管理界面。在界面中，您可以进行各种服务器配置和管理操作。首先，我们需要配置网站环境。</p>
<p>宝塔面板提供了丰富的环境配置选项，包括Web服务器、数据库、FTP服务器等。根据您的需求选择相应的环境进行配置。通常情况下，我们可以选择安装Nginx作为Web服务器，MySQL或MariaDB作为数据库，以及安装FTP服务器以方便文件上传和下载。</p>
<p>在配置过程中，您需要根据提示进行相应的设置，如设置数据库的用户名和密码、指定FTP服务器的端口号等。请确保按照您的实际需求进行配置，并妥善保存相关配置信息。</p>
<p>添加网站并绑定域名
 <br>
 配置好环境后，我们可以开始添加网站并绑定域名。在宝塔面板中，选择"网站"菜单，然后点击"添加网站"按钮。在弹出的窗口中，填写网站的域名、根目录等信息，并点击"提交"按钮。</p>
<p>如果您还没有购买域名，可以先使用临时域名进行测试。在添加网站后，您可以在宝塔面板中管理网站的各种设置，如修改网站配置、上传文件、安装网站程序等。</p>
<p>四、注意事项</p>
<p>在安装和配置宝塔面板的过程中，有几点需要注意的事项：</p>
<p>防火墙设置
 <br>
 如果您的VPS使用的是腾讯云、阿里云或华为云等云服务提供商，请确保在云服务商的管理后台中开放了宝塔面板所需的端口。这样可以确保您能够正常访问和管理宝塔面板。</p>
<p>安全设置
 <br>
 在安装和配置过程中，请务必注意安全问题。建议定期更新宝<a href="https://www.buildwebsite.top/index.php/2024/04/25/bt-install-2024/">塔面板和服务器</a>上的软件版本，以修复潜在的安全漏洞。同时，设置复杂的密码并定期更换密码也是保障安全的重要措施。</p>
<p>原文链接: https://blog.csdn.net/Y1258429182/article/details/138198582</p>]]></description><guid isPermaLink="false">/archives/e4283b76-530f-48b6-affb-55fee77cc99b</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:14:38 GMT</pubDate></item><item><title><![CDATA[宝塔部署 Vue + Spring Boot + MySQL + Redis 项目保姆级教程]]></title><link>https://wiki.dgfygk.com/archives/a26501a2-9732-44e9-9f0b-a105775856c7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AE%9D%E5%A1%94%E9%83%A8%E7%BD%B2%20Vue%20%2B%20Spring%20Boot%20%2B%20MySQL%20%2B%20Redis%20%E9%A1%B9%E7%9B%AE%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%95%99%E7%A8%8B&amp;url=/archives/a26501a2-9732-44e9-9f0b-a105775856c7" width="1" height="1" alt="" style="opacity:0;">
<p>最近做了一个厚米匹配的项目并且成功用宝塔部署上线了。因此写了这篇文章和大家分享、记录一下，希望对大家有所帮助，如果觉得有用的话，能给我点个赞嘛，多谢！</p>
<h3>服务器安装宝塔</h3>
<p>不同的服务器操作系统对应着不同的安装命令。这里我用的是centos8.6，所以安装命令是</p>
<pre><code>yum install -y wget &amp;&amp; wget -O install.sh http://download.bt.cn/install/install_6.0.sh &amp;&amp; sh install.sh
</code></pre>
<p>详情请看这篇博客：<a href="http://t.csdnimg.cn/NlPXj" title="http://t.csdnimg.cn/NlPXj">http://t.csdnimg.cn/NlPXj</a> 注意安装宝塔面板时，服务器不要部署任何项目，最好是纯净状态。</p>
<h3>部署项目前的宝塔前置配置</h3>
<h4>安装tomcat再卸载tomcat</h4>
<p>安装tomcat是为了安装jdk1.8，卸载是因为Spring Boot内置了tomcat，所以它是一个安装jdk的跳板。如果你的项目不是jdk1.8可以点击Java环境管理选择你想要的jdk版本。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd0017bc7cfbd6198419f3708ed7504ea.png&amp;size=m" alt=""></p>
<p>进入软件商店搜索tomcat，版本选择9.x，直接极速安装。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7830712689d1eda52cce914b0990aa49.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F43b40020b6034e4f7e4fb576da3ca72c.png&amp;size=m" alt=""></p>
<h4>安装和配置MySQL</h4>
<p>还是一样在软件商店搜索MySQL，点击安装。选择你需要的版本，我选的是8.x，再点击极速安装。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5e52b451fb6803a55dd5f67cb0d30f6a.png&amp;size=m" alt=""></p>
<p>安装完成后，点击侧边栏中的数据库，再点击添加数据库。选一个数据库名字要和yml文件中连接MySQL数据库的库名一致，用户名和密码也对应yml中的MySQL用户名和密码。访问权限，暂时选择所有人，方便后续使用navicat或者IDEA往线上数据库添加数据，当然后续可以在设置中修改。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc1be0723b3cdc620018a1357ae5fa0e2.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F519de59f0b4aaddd5a81c12f8bc1258e.png&amp;size=m" alt=""></p>
<p>想连接服务器上的MySQL把localhost改为服务器IP地址，端口是3306，库名、账号和密码就上面的。（前提访问权限改为所有人，或者只指定你的电脑的IP地址）。这样就可以往线上数据库添加数据了。</p>
<h4>安装和配置Redis</h4>
<p>安装Redis也是一样，在软件商店里面搜索，点击安装。我安装的是6.x版本，因为我自己电脑安装的是5.x版本，安装过高害怕有问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5b1bc4bc2302a8643d78aee6e5ea7f7f.png&amp;size=m" alt=""></p>
<p>点击设置，再点击性能调整，这里可以设置Redis数据库数量，密码和端口啥的。（建议设个密码，要和yml文件Redis的密码一致。）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb4e7d40028bf689ecb246cc19b9c1492.png&amp;size=m" alt=""></p>
<p>对了如果Redis设置了密码，那么后端RedissonConfig配置类一定要改，改为这样子。多一个password变量和setPassword。</p>
<pre><code class="java">@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
    private String host;
    private String port;
    private String password;

    @Bean
    public RedissonClient redissonClient(){
        // 1. 创建配置
        Config config = new Config();
        String redisAddress = String.format("redis://%s:%s", host, port);
        config.useSingleServer().setAddress(redisAddress).setDatabase(3).setPassword(password);//设置单个服务器，设置地址，选择数据库
        // 2. 创建实例
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}
</code></pre>
<h4>安装nginx</h4>
<p>在软件商店里面搜索并安装nginx</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3eba60149df2fa3fa77a46f3360dc8ec.png&amp;size=m" alt=""></p>
<p>对了为了防止项目部署后登录出现nginx 404的问题，我们可以在nginx的配置文件中，添加以下配置。</p>
<pre><code class="java">  location / {
  try_files $uri $uri/ @router;#需要指向下面的@router否则会出现vue的路由在
      index index.html index.htm;
    }
  location @router {
    rewrite ^.*$ /index.html last;
  }
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd89073b8994ca544f5fdc774fe2ded8e.png&amp;size=m" alt=""></p>
<h4>安装Java项目一键部署</h4>
<p>在软件商店里面搜索并安装Java项目一键部署</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb6d390964163fdb5903cc8bf578b21f9.png&amp;size=m" alt=""></p>
<h4>在服务器和宝塔面板中放行对应端口</h4>
<p>最后，在服务器中添加安全组，放行对应的端口。（6379：Redis端口，3306：MySQL，80：前端，8080：后端，21528：宝塔面板端口）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F489939acf79eeb501348424105b004c4.png&amp;size=m" alt=""></p>
<p>宝塔亦是如此点击侧边栏的安全，再点击添加端口规则就可以放行了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc63299a71693d66e29bd9f5a13453a20.png&amp;size=m" alt=""></p>
<h4>后端配置跨域</h4>
<p>如果前端和后端的协议、域名和端口有一个不同就会涉及跨域问题，所以我们可以在前端或者后端配置跨域策略，以防止请求跨域。本次，我们在后端配置跨域，在config目录下添加跨域配置类就好了。</p>
<pre><code class="java">@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                //是否发送Cookie
                .allowCredentials(true)
                //放行哪些原始域
                .allowedOriginPatterns("*")
                .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}
</code></pre>
<p>到这里部署项目的前置配置就结束了。接下来，就正式开始打包前端和后端项目了，并将打包文件上传到宝塔面板中，它会帮我们把文件部署在服务器中。</p>
<h3>前端部署</h3>
<h4>配置生产和开发环境</h4>
<p>在项目打包之前，先配置myAxios.ts文件（这个文件就是你自定义axios的文件，并且在里面区分开发环境和生产环境）的线上环境和生产环境。（注意生产环境的url后面要加上后端端口号，但有时又不要加，视情况而定。）</p>
<pre><code class="javascript">import axios from 'axios';

const isDev = process.env.NODE_ENV === 'development';

const myAxios = axios.create({
    baseURL: isDev ? 'http://localhost:8080/api' : 'http://你的服务器IP地址或者域名/api',
})

myAxios.defaults.withCredentials = true; //设置为true

// Add a request interceptor
myAxios.interceptors.request.use(function (config) {
    console.log('我要发请求啦')
    return config;
}, function (error) {
    return Promise.reject(error);
});

myAxios.interceptors.response.use(function (response) {
    console.log('我收到你的响应啦')
    // 未登录则跳转登录页
    if (response?.data?.code === 40100) {
        const redirectUrl = window.location.href;
        window.location.href = `/user/login?=redirect=${redirectUrl}`;
    }
    return response.data;
}, function (error) {
    // Do something with response error
    return Promise.reject(error);
});

export default myAxios;
</code></pre>
<h4>前端项目打包</h4>
<p>在宝塔部署前端项目之前，先将前端项目打包好。在package.json文件中执行buid命令。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4a214f12a818408f79d27ff5d02f7d8a.png&amp;size=m" alt=""></p>
<p>打包后会多出一个dist目录，点击右键打开于终端，在终端中执行serve命令，判断打包好的项目是否能够运行成功。（注意一定要切换至dist目录下执行serve命令，不然点击链接显示不出来项目）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F286e495a32d18f8599f41f62138f738a.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4846a01304f6e5768ad98ef8f37f8400.png&amp;size=m" alt=""></p>
<p>点击Local后面的地址，看看项目打包是否有问题。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F177da7bbc1320bce6a71a36fc710339f.png&amp;size=m" alt=""></p>
<p>如果是这样子的话就没啥问题。（我这个显示了当前登录用户"ikun”，是因为我已经上线了，你们是不会显示的）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff02029dff31e9e72c51d001442ddccb4.png&amp;size=m" alt=""></p>
<h4>前端打包后的项目拖到宝塔</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb33e1c96e118e55c738125788d99cdc6.png&amp;size=m" alt=""></p>
<p>连接Linux宝塔面板后点击PHP项目，点击添加站点。域名最好就填你的服务器IP地址，或者你自己想一个域名（前提域名是你买的，不是随便起的）吧。随后直接提交。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff9cbd03296cb1426c413dab3634f2c3e.png&amp;size=m" alt=""></p>
<p>接着点击根目录下面的绿色链接。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc97d9c723a1d0814cb36ff47c3024fa7.png&amp;size=m" alt=""></p>
<p>把打包后的dist目录下的东西全部拖进去。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa5165768757a9c7c1bbf2eec91b9feb1.png&amp;size=m" alt="">至此前端部署已经结束，开始后端部署。</p>
<p><strong>如果前端部署的时候出现 404 not found 的话，请在站点的配置文件中加上一下配置，如果还是不行点击侧边栏的软件商店找到 nginx 查看其监听的端口号，把这个端口在宝塔和云服务器中放行。</strong></p>
<pre><code class="XML">    location / {
    try_files $uri $uri/ @router;#需要指向下面的@router否则会出现vue的路由在nginx中刷新出现404
      index  index.html index.htm;
    }
    #对应上面的@router，主要原因是路由的路径资源并不是一个真实的路径，所以无法找到具体的文件
    #因此需要rewrite到index.html中，然后交给路由在处理请求资源
    location @router {
        rewrite ^.*$ /index.html last;
    }
</code></pre>
<h3>后端部署</h3>
<h4>配置生产和开发环境</h4>
<p>在后端打成jar包之前，先把后端的application-prod.yml文件给配置好。这里直接放我的哪个文件了。</p>
<pre><code class="javascript">spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  application:
    name: homieMatching
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://服务器IP地址:3306/MySQL库名?characterEncoding=UTF-8
    username: 服务器MySQL账号
    password: 服务器MySQL密码
  session:
    timeout: 86400
    store-type: redis
  redis:
    port: 6379
    host: 127.0.0.1
    database: 0
    password: 服务器redis密码
server:
  port: 8080
  servlet:
    context-path: /api
    session:
      cookie:
        domain: 服务器IP地址或者域名（其实就是前端添加站点时的网站名，我这里还是建议全部设为服务器IP地址吧，但注意别把IP地址告诉任何人）
        same-site: lax
        secure: false
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: isDelete
      logic-delete-value: 1
      logic-not-delete-value: 0
</code></pre>
<h4>后端项目打包</h4>
<p>点击Maven，点击禁止标志（这是为了跳过Test测试用例的打包，如果某些Test类有问题就会打包失败），再点击package准备打包，等待打包完成。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fce5aa0320df8f1135d216c28b4e8a502.png&amp;size=m" alt=""></p>
<p>打包完成之后，target目录下就会出现一个homieMatching-0.0.1-SNAPSHOT.jar文件，这个就是后端打包后的jar包。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd8e944faf23fe2a8d007a385b40fdbb8.png&amp;size=m" alt=""></p>
<h4>后端项目拖到宝塔</h4>
<p>点击侧边栏中的文件在/www/wwwroot目录下新建一个文件，将打包后的jar放进新建的目录下。我新建的目录名是homieMatching-backend。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2095a7a642cde39c7056e05a0ac54894.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F100ad53fe9f29a87e2d663e0dcfa8a5b.png&amp;size=m" alt=""></p>
<p>随后进入你新建的目录下面，把后端打包好的jar包拖进去。此时复制一下路径名，为后面做准备。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F91a6ac409535ceb5114876caac326099.png&amp;size=m" alt=""></p>
<p>复制路径名。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8ee2cd18709013deb8c0bb871c104cbe.png&amp;size=m" alt=""></p>
<p>随后点击侧边栏的网站，再点击Java项目，再点击添加Java项目。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F96b06a309a94378e3b8f439b585ccc9e.png&amp;size=m" alt=""></p>
<p>点击项目jar路径后的文件夹标志，选择刚才拖进目录的jar包。项目端后就是yml文件中的port，即你后端的端口，同时勾选放行该端口。如果你是8081你就放行8081端口，同时项目执行命令中的–server.port的端口也要修改，得与上面项目端口一致。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd7a6b2381034d7b39ca6ea3eba88f43b.png&amp;size=m" alt=""></p>
<p>点击保存项目配置，Spring Boot项目就会开始运行了。（如果项目启动失败，请看看左侧边栏的项目日志，看看项目启动时抛了什么异常或者有什么问题，随后逐一排查）</p>
<p>这时候你就可以输入网站名，看看项目前后端联调存不存在问题了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9d2cbea495882a774a6c7dbee950143c.png&amp;size=m" alt=""></p>
<h3>总结</h3>
<p>如果在部署过程中有问题可以私信我。另外觉得文章有用的话能否给我点个赞呢，谢谢。</p>
<p>原文链接: https://blog.csdn.net/xyendjsj/article/details/135921460</p>]]></description><guid isPermaLink="false">/archives/a26501a2-9732-44e9-9f0b-a105775856c7</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Sat, 28 Sep 2024 07:14:25 GMT</pubDate></item><item><title><![CDATA[Postman 测试 POST 请求：JSON 类型参数实例深度解析]]></title><link>https://wiki.dgfygk.com/archives/1ba13a96-d74f-42b7-aa64-c498d4a319f4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Postman%20%E6%B5%8B%E8%AF%95%20POST%20%E8%AF%B7%E6%B1%82%EF%BC%9AJSON%20%E7%B1%BB%E5%9E%8B%E5%8F%82%E6%95%B0%E5%AE%9E%E4%BE%8B%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90&amp;url=/archives/1ba13a96-d74f-42b7-aa64-c498d4a319f4" width="1" height="1" alt="" style="opacity:0;">
<h5>引言</h5>
<p>在现代 Web 开发中，API 测试扮演着至关重要的角色，尤其是在微服务架构日益普及的当下。Postman，作为一款功能强大的 API 开发工具，深受开发者们的喜爱。它不仅能够帮助你构建、发送和调试 HTTP 请求，还能进行响应分析、自动化测试、环境配置等一系列操作。本文将通过一个详细的实例，指导你如何使用 Postman 发送一个 POST 请求，其中请求体为 JSON 类型，帮助你全面掌握这一过程，以应对实际开发和测试中的需求。</p>
<h5>一、Postman 简介与环境准备</h5>
<h6>1. Postman 的功能与优势</h6>
<p>Postman 是一款用于构建和测试 API 的工具，它提供了一个直观的用户界面，支持发送各种类型的 HTTP 请求，包括 GET、POST、PUT、DELETE 等。Postman 的主要优势包括：</p>
<ul>
 <li><strong>请求构建</strong>：轻松构建复杂的 HTTP 请求。</li>
 <li><strong>响应分析</strong>：详细查看和分析服务器返回的响应。</li>
 <li><strong>自动化测试</strong>：使用内置测试脚本来验证响应。</li>
 <li><strong>环境管理</strong>：管理不同的环境变量，轻松切换测试和生产环境。</li>
 <li><strong>集合管理</strong>：组织和保存请求序列，方便重复使用和分享。</li>
 <li><strong>文档生成</strong>：自动生成 API 文档。</li>
</ul>
<h6>2. 环境准备</h6>
<ul>
 <li><strong>安装 Postman</strong> ：访问 Postman 官方网站（<a href="https://www.postman.com/%EF%BC%89%E4%B8%8B%E8%BD%BD%E5%B9%B6%E5%AE%89%E8%A3%85%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC%E7%9A%84" title="https://www.postman.com/）下载并安装最新版本的">https://www.postman.com/）下载并安装最新版本的</a> Postman。</li>
 <li><strong>创建账号</strong>：注册一个 Postman 账户，以便保存和同步你的请求、集合和环境。</li>
</ul>
<h5>二、构建 JSON 类型的 POST 请求</h5>
<h6>1. 创建新的 POST 请求</h6>
<ul>
 <li><strong>打开 Postman</strong>：启动 Postman 应用程序。</li>
 <li><strong>新建请求</strong> ：点击左上角的 <code>+</code> 符号或 <code>New</code> 按钮，创建一个新的请求。</li>
 <li><strong>选择 POST 方法</strong> ：在请求方法下拉菜单中选择 <code>POST</code>。</li>
</ul>
<h6>2. 设置请求 URL</h6>
<ul>
 <li><strong>输入 URL</strong> ：在 URL 输入框中，输入你想要发送请求的目标服务器地址，例如 <code>https://api.example.com/register</code>。</li>
</ul>
<h6>3. 设置请求头</h6>
<ul>
 <li><strong>添加 Content-Type</strong> ：点击 <code>Headers</code> 标签页，在表格中添加一个键值对，键为 <code>Content-Type</code>，值为 <code>application/json</code>。这告诉服务器你发送的数据格式是 JSON。</li>
</ul>
<h6>4. 构建 JSON 请求体</h6>
<ul>
 <li>
  <p><strong>选择 Body 类型</strong> ：点击 <code>Body</code> 标签页，选择 <code>raw</code> 单选按钮。</p>
 </li>
 <li>
  <p><strong>设置 JSON 格式</strong> ：在 <code>raw</code> 模式下，从下拉菜单中选择 <code>JSON (application/json)</code>，这会自动填充 <code>Content-Type</code> 头部（如果之前未设置，则需要手动添加）。</p>
 </li>
 <li>
  <p><strong>填写 JSON 数据</strong>：在文本区域中输入 JSON 格式的数据。例如，假设我们要发送一个用户注册请求，请求体可能是这样的：</p>
  <p>1{
   <br>
   2 “username”: “john_doe”,
   <br>
   3 “email”: “john.doe@example.com”,
   <br>
   4 “password”: “securePassword123”
   <br>
   5}</p>
 </li>
</ul>
<h5>三、发送请求与分析响应</h5>
<h6>1. 发送请求</h6>
<ul>
 <li><strong>点击 Send</strong> ：确认所有字段都已正确设置后，点击 <code>Send</code> 按钮发送请求。</li>
</ul>
<h6>2. 分析响应</h6>
<ul>
 <li><strong>查看状态码</strong> ：在响应区域顶部，查看 HTTP 状态码，如 <code>200 OK</code> 表示请求成功。</li>
 <li><strong>检查响应体</strong>：查看响应体中的数据，确保它符合预期。响应体通常也是 JSON 格式，便于解析和分析。</li>
 <li><strong>分析响应头</strong> ：在 <code>Headers</code> 标签页下，查看响应头，检查是否有额外的信息，如 <code>Content-Length</code>、<code>Date</code> 等。</li>
</ul>
<h5>四、调试与问题解决</h5>
<h6>1. 错误处理</h6>
<ul>
 <li><strong>检查错误消息</strong>：如果收到错误响应，仔细阅读错误消息，通常会提供错误代码和描述，帮助你定位问题。</li>
 <li><strong>验证 JSON 格式</strong>：使用在线 JSON 格式校验工具，确保请求体中的 JSON 数据格式正确，没有语法错误。</li>
</ul>
<h6>2. 调整请求参数</h6>
<ul>
 <li><strong>修改 JSON 数据</strong> ：回到 <code>Body</code> 标签页，修改 JSON 数据中的字段，重新发送请求，直至得到预期的响应。</li>
</ul>
<h5>五、保存与分享请求</h5>
<h6>1. 将请求保存到集合</h6>
<ul>
 <li><strong>创建或选择集合</strong> ：右键点击请求，选择 <code>Add to Collection</code>，然后选择或创建一个集合。</li>
 <li><strong>添加请求到集合</strong>：将请求添加到选定的集合中，以便日后重复使用或分享给团队成员。</li>
</ul>
<h6>2. 导出与导入请求</h6>
<ul>
 <li><strong>导出请求</strong> ：点击 <code>Export</code> 按钮，选择导出格式（如 <code>Collection v2.1</code>），保存到本地或云端。</li>
 <li><strong>导入请求</strong> ：点击 <code>Import</code> 按钮，选择之前导出的文件，将请求导入到 Postman 中。</li>
</ul>
<h5>六、高级功能探索</h5>
<h6>1. 预请求脚本</h6>
<ul>
 <li><strong>编写预请求脚本</strong> ：在 <code>Pre-request Script</code> 标签页下，使用 JavaScript 编写脚本来执行请求前的准备工作，如生成随机数、设置动态参数等。</li>
</ul>
<h6>2. 测试脚本</h6>
<ul>
 <li><strong>编写测试脚本</strong> ：在 <code>Tests</code> 标签页下，使用 JavaScript 编写测试脚本来验证响应是否符合预期，例如检查状态码、响应体中的特定字段等。</li>
</ul>
<h6>3. 环境变量</h6>
<ul>
 <li><strong>设置环境变量</strong> ：在 <code>Environment</code> 标签页下，定义变量，如 <code>baseUrl</code>，并在请求 URL 中引用该变量，以便在不同环境中轻松切换。</li>
</ul>
<h5>结语</h5>
<p>通过本文的详细指南，你已经学会了如何使用 Postman 构建和发送一个 POST 请求，其中请求体为 JSON 类型。Postman 的强大功能远不止于此，从自动化测试到环境管理，从集合共享到文档生成，它几乎覆盖了 API 开发和测试的方方面面。随着你对 Postman 的深入使用，你会发现它能极大地提升你的工作效率，帮助你更高效地进行 API 相关的工作。如果你在使用 Postman 的过程中遇到任何问题，或有更多使用技巧想要分享，欢迎在评论区留言，让我们一起交流，共同提升技能。</p>
<hr>
<p>Postman 不仅仅是一个工具，它更是一个开发者社区的聚集地，你在这里不仅可以学习到最新的 API 技术和最佳实践，还可以结识来自世界各地的同行，共同探讨和解决问题。无论你是初学者，还是经验丰富的开发者，Postman 都将是你进行 API 测试和开发的得力助手。愿你在 API 的世界里，借助 Postman 的力量，不断探索，不断进步，创造属于自己的精彩！</p>
<h5>(End of Article)</h5>
<p>原文链接: https://blog.csdn.net/Huang020101/article/details/140300382</p>]]></description><guid isPermaLink="false">/archives/1ba13a96-d74f-42b7-aa64-c498d4a319f4</guid><dc:creator>黄健</dc:creator><category>测试</category><pubDate>Sat, 28 Sep 2024 07:13:58 GMT</pubDate></item><item><title><![CDATA[MySQL数据库主从搭建（详细图文）]]></title><link>https://wiki.dgfygk.com/archives/67c953e9-d643-4443-a406-088de7bb83a7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%BB%E4%BB%8E%E6%90%AD%E5%BB%BA%EF%BC%88%E8%AF%A6%E7%BB%86%E5%9B%BE%E6%96%87%EF%BC%89&amp;url=/archives/67c953e9-d643-4443-a406-088de7bb83a7" width="1" height="1" alt="" style="opacity:0;">
<h3>MySQL数据库主从搭建（详细图文）</h3>
<p><strong>前言</strong>
 <br>
 主从原理：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F45fcdef7b3fb4a29b8d23da9825b2e55.png&amp;size=m" alt="">
 <br>
 MySQL之间数据复制的基础是二进制日志文件（binary log file）。一台MySQL数据库一旦启用二进制日志后，其作为master，它的数据库中所有操作都会以"事件"的方式记录在二进制日志中，其他数据库作为slave通过一个I/O线程与主服务器保持通信，并监控master的二进制日志文件的变化，如果发现master二进制日志文件发生变化，则会把变化复制到自己的中继日志中，然后slave的一个SQL线程会把相关的"事件"执行到自己的数据库中，以此实现从数据库和主数据库的一致性，也就实现了主从复制。</p>
<h3>一、主从搭建准备</h3>
<p>1.主从数据库版本一致
 <br>
 2.主从数据库内数据保持一致
 <br>
 3.克隆虚拟机，只需在一个虚拟机搭建环境，然后克隆两台虚拟机当做从机（我搭建的是一主两从）</p>
<h3>二、Master</h3>
<p><strong>1.修改mysql配置文件</strong></p>
<pre><code class="prism language-javascript">cd /etc 
vim my.cnf
或者 vim /etc/my.cnf
./etc/my.cnf

##master 中my.cnf需要添加的内容
server-id=188    //master  192.168.88.188  去最后的188作为id
log-bin=mysql-bin
binlog_format=MIXED
sync_binlog=1
expire_logs_days=0

binlog-do-db=news    //news 为数据库的名称
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffbe86979b1324e40bb7c37dd0c63f4c5.png&amp;size=m" alt="">
 <br>
 <strong>2.创建授权用户</strong>
 <br>
 //开启mysql服务</p>
<pre><code class="prism language-javascript">service mysqld start
</code></pre>
<p>//查看mysql运行状态</p>
<pre><code class="prism language-javascript">service mysqld status
</code></pre>
<p>//进入mysql</p>
<pre><code class="prism language-javascript">mysql -u root -p
</code></pre>
<p>注意复制粘贴后将标点符号更改为西文符号!!</p>
<p>//创建一个从机用户 并指定可以从本机外部进行访问
 <br>
 //MySQL5.7clone为密码，设置一个自己能记住的密码</p>
<pre><code class="prism language-javascript">create user 'slave'@'%' identified by 'MySQL5.7clone'
</code></pre>
<p>//设置slave远程从机账户拥有一个可以复制的权限</p>
<pre><code class="prism language-javascript">grant replication slave,replication client on *.* to 'slave'@'%'
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd03176a8f09242f39241220684b6ba3d.png&amp;size=m" alt=""></p>
<h3>3.查看日志和宿主机master的状态</h3>
<p>代码如下（示例）：</p>
<p>查看log_bin日志是否属于开启状态</p>
<pre><code class="prism language-javascript">show variables like 'log_bin';
</code></pre>
<p>查看当前操作的宿主机是否为master状态</p>
<pre><code class="prism language-javascript">show master status;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F44058c6944da4740ae6167c39ac3524c.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd9e1f9dc1d53441b8d08dd0cc5d69ab2.png&amp;size=m" alt="">
 <br>
 这里要记住 file和position，后面配置slave会用到</p>
<p>该处使用的url网络请求的数据。</p>
<h3>三、slave操作</h3>
<p><strong>1.修改宿主机的server-id</strong>
 <br>
 进入从库宿主机后修改my.cnf配置文件加入server-id即可
 <br>
 同master操作
 <br>
 但是只需要 添加server-id即可</p>
<p><strong>2.进入mysql执行相关命令</strong>
 <br>
 //首先要关掉slave</p>
<pre><code class="prism language-javascript">stop slave
</code></pre>
<p>//重点
 <br>
 //执行下面的语句 将下面的语句复制到记事本上，保证是一行数据，然后复制到mysql中
 <br>
 //master_host为master的ip
 <br>
 //master_password 为授权时设置的密码
 <br>
 //master_log_file 分别为上文中需要你记住的file和position</p>
<pre><code class="prism language-javascript">change master to master_host='192.168.88.188', master_port=3306,master_user='slave',master_password='MySQL5.7clone',master_log_file='mysql-bin.000001',master_log_file=617
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F97d82157043c416dbb434586130c3e77.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4a98fe4720f243a8b4ca7a3cadbe08bf.png&amp;size=m" alt="">
 <br>
 //执行完后开启slave
 <br>
 start slave;
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F548faae2ac7941a4b7e5be34fdcec773.png&amp;size=m" alt="">
 <br>
 查看同步状态</p>
<pre><code class="prism language-javascript">show slave status \G
</code></pre>
<p>若下面两个状态都是yes，则搭建成功，若是Slave_IO_Running为NO，则去查看master和slave的uuid是否一致</p>
<pre><code class="prism language-javascript">  Slave_IO_Running: No
  Slave_SQL_Running: Yes
</code></pre>
<pre><code>```prism language-powershell
*************************** 1. row ***************************
               Slave_IO_State: 
                  Master_Host: 192.168.25.136
                  Master_User: slave
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 617
               Relay_Log_File: localhost-relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: No
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 617
              Relay_Log_Space: 154
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 1593
                Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 136
                  Master_UUID: 
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 201114 11:40:14
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.00 sec)
</code></pre>
<h3>3.UUID的相关配置</h3>
<p>//查看主机与从机的uuid</p>
<pre><code class="prism language-powershell">Show variables like '%uuid%'
</code></pre>
<p>//如果两边一致就需要将从库的auto.cnf文件删除
 <br>
 //可以选择备份 或者删除
 <br>
 //备份</p>
<pre><code class="prism language-powershell">mv auto.cnf  ./auto.cnf.back
</code></pre>
<p>//删除</p>
<pre><code class="prism language-powershell">rm -rf auto.cnf
</code></pre>
<p>删除后在启动从机mysql服务进入客户端执行salve状态</p>
<pre><code class="prism language-powershell">show slave status \G
</code></pre>
<p>//状态会变为</p>
<pre><code class="prism language-powershell">Slave_IO_Running: Yes
Slave_SQL_Running: Yes
</code></pre>
<h3>四、测试</h3>
<p>在数据库工具新建连接，一个为master，将master的数据导出
 <br>
 新建两个slave连接，将数据导入
 <br>
 修改master连接中的数据，查看从库是否改变，改变则MySql数据库主从搭建成功</p>
<p>原文链接: https://blog.csdn.net/weixin_43735086/article/details/141127282</p>]]></description><guid isPermaLink="false">/archives/67c953e9-d643-4443-a406-088de7bb83a7</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Sat, 28 Sep 2024 07:13:30 GMT</pubDate></item><item><title><![CDATA[纯技术手段实现内网穿透，免注册免收费]]></title><link>https://wiki.dgfygk.com/archives/28427861-88a7-4aed-ab44-1b6b739568e3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BA%AF%E6%8A%80%E6%9C%AF%E6%89%8B%E6%AE%B5%E5%AE%9E%E7%8E%B0%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%EF%BC%8C%E5%85%8D%E6%B3%A8%E5%86%8C%E5%85%8D%E6%94%B6%E8%B4%B9&amp;url=/archives/28427861-88a7-4aed-ab44-1b6b739568e3" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<h4>纯技术手段实现内网穿透，免注册免收费</h4>
<p>*</p>
<ul>
 <li><a href="https://wiki.dgfygk.com#_3">一、内网穿透</a></li>
 <li><a href="https://wiki.dgfygk.com#_6">二、方法分类</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#21__9">2.1 基于隧道协议的内网穿透</a></li>
   <li><a href="https://wiki.dgfygk.com#22__18">2.2 基于反向代理的内网穿透</a></li>
   <li><a href="https://wiki.dgfygk.com#23_SSH_24">2.3 基于SSH的内网穿透</a></li>
   <li><a href="https://wiki.dgfygk.com#_30">具体工具的分类如下：</a>
    <br>
    *</li>
   <li><a href="https://wiki.dgfygk.com#_32">基于隧道协议</a></li>
   <li><a href="https://wiki.dgfygk.com#_39">基于反向代理</a></li>
   <li><a href="https://wiki.dgfygk.com#SSH_43">基于SSH</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_49">三、本文方法</a></li>
 <li><a href="https://wiki.dgfygk.com#_75">四、具体操作</a>
  <br>
  *
  <ul>
   <li><a href="https://wiki.dgfygk.com#41__79">4.1 安装服务端</a></li>
   <li><a href="https://wiki.dgfygk.com#42__117">4.2 安装客户端</a></li>
  </ul></li>
 <li><a href="https://wiki.dgfygk.com#_150">五、完结</a></li>
</ul>
<p>
 <br>
</p>
<p>大家好，我是比特桃。互联网时代大家都需要一个身份标识自己，对于普通用户来说，创作的内容是基础，所以一个平台的 UID 就可以代表了用户本身。但对于开发者来说，所开发的服务是基础，为了让自己的服务能够更好的进行测试或提供给其他人，就需要因特网能够唯一识别的域名或 IP 来定位自己提供的服务。</p>
<h3>一、内网穿透</h3>
<p>平常我们上网会用到域名，虽然现在移动互联网，大家都很少直接接触具体的域名了。但实际软件背后都会通过具体的域名来定位到具体的 IP 地址，从而和软件平台进行通讯。由于 IPv4 的地址已经接近枯竭，所以现在大家都会转战到 IPv6 的地址上来。IPv6 是下一代互联网协议，拥有128位地址空间，可以提供约340个十亿亿亿个地址，几乎无限的地址空间。这也是物联网的基础，这个话题我们就不再展开。重点是对于普通人来说，我们如何获取因特网能够唯一识别的 IP。</p>
<blockquote>
 <p>免责声明：很多公司禁止通过私自搭建内网穿透的方式入侵公司内网，有的公司有正规的手续办理内部的 VPN 使用。在进行该操作之前请确定方式及提供的服务合法合规。</p>
</blockquote>
<h3>二、方法分类</h3>
<p>实现内网穿透的方法有很多种，可以根据技术原理大致分为以下几类：</p>
<h4>2.1 基于隧道协议的内网穿透</h4>
<p>这些工具通过建立隧道协议，将内网服务映射到外网，达到穿透防火墙的效果。</p>
<ul>
 <li><strong>Ngrok</strong>：通过建立加密的隧道，将本地服务映射到公共URL，从而实现内网穿透。</li>
 <li><strong>Natapp</strong>：类似于Ngrok，通过隧道协议将本地服务映射到外网。</li>
 <li><strong>Frp</strong>：使用TCP、UDP等协议建立隧道，实现内网穿透。支持多种协议和复杂配置。</li>
 <li><strong>Spike</strong>：也是一种隧道协议工具，用于将本地服务暴露到公网。</li>
 <li><strong>Cpolar</strong>：通过隧道协议将本地服务映射到外网，支持HTTP、HTTPS、TCP等多种协议。</li>
</ul>
<h4>2.2 基于反向代理的内网穿透</h4>
<p>这些工具通过反向代理技术，将外部请求转发到内网服务，从而实现内网穿透。</p>
<ul>
 <li><strong>花生壳</strong>：通过反向代理技术，将公网请求代理到内网设备上。</li>
 <li><strong>Lanproxy</strong>：使用反向代理的方式，将内网服务通过代理服务器暴露到外网。</li>
</ul>
<h4>2.3 基于SSH的内网穿透</h4>
<p>这些工具利用SSH协议的端口转发功能，实现内网穿透。</p>
<ul>
 <li><strong>SSH</strong>：通过SSH的本地端口转发和远程端口转发功能，可以将内网服务映射到外网。</li>
 <li><strong>autossh</strong>：在SSH的基础上增加了自动重连功能，确保隧道的稳定性。</li>
</ul>
<h4>具体工具的分类如下：</h4>
<h5>基于隧道协议</h5>
<ul>
 <li>Ngrok</li>
 <li>Natapp</li>
 <li>Frp</li>
 <li>Spike</li>
 <li>Cpolar</li>
</ul>
<h5>基于反向代理</h5>
<ul>
 <li>花生壳</li>
 <li>Lanproxy</li>
</ul>
<h5>基于SSH</h5>
<ul>
 <li>SSH</li>
 <li>autossh</li>
</ul>
<p>这些工具在使用场景和实现细节上各有不同，可以根据具体需求选择合适的工具。例如，Ngrok和Natapp适合快速临时的内网服务暴露，Frp适合更复杂和多样化的需求，SSH和autossh适合熟悉命令行和需要稳定性的用户。</p>
<h3>三、本文方法</h3>
<p>由于开发者通常需要暴露的是一个本机计算机上的端口，其安全性还是值得重视的。所以关于需要注册的跳板机，出于安全和性价比多方面考虑都不会使用。本文重点推荐的是基于开源项目：<a href="https://github.com/fatedier/frp">frp</a>实现的一种内网穿透。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe40803be2ed8497cb30c0e8013cbe2ef.png&amp;size=m" alt=""></p>
<p>这里是一张FRP服务技术原理图：</p>
<p>• FRP Server：位于图的顶部，负责在外部网络中接收来自FRP客户端的连接请求，并将这些请求转发给内部网络中的实际服务。</p>
<p>• Client with Service：左下角表示内部网络中的实际服务客户端，它通过FRP客户端连接到FRP服务器。</p>
<p>• FRP Client：中央位置，负责在内部网络中建立隧道并将流量转发给FRP服务器。</p>
<p>• Client with Browser：右下角表示外部网络中的浏览器客户端，它通过HTTP/HTTPS请求与FRP服务器通信。</p>
<p><strong>具体的连接方式</strong></p>
<p>• 服务流量（Service Traffic） ：从实际服务客户端到FRP客户端的连接。</p>
<p>• 隧道（Tunnel） ：从FRP客户端到FRP服务器的连接，形成一个隧道以传输流量。</p>
<p>• HTTP/HTTPS请求和响应（HTTP/HTTPS Request \&amp; Response） ：从外部浏览器客户端到FRP服务器的请求和响应。</p>
<p>FRP通过隧道将内部网络中的服务暴露给外部网络，从而实现内网穿透的功能。</p>
<h3>四、具体操作</h3>
<p>由于 frp 的服务端和客户端都是开源的，所以我们只需要将拥有公网 IP 的电脑安装好服务端；将准备内网穿透的电脑安装好客户端。就可以搭配进行内网穿透，实现被因特网上所有机器进行访问。这里推荐直接跟着 <a href="https://gofrp.org/zh-cn/docs/overview/">frp 的中文文档</a>进行。</p>
<p>作者本人的环境是有一台公网 IP 的 NAS 作为服务端，异地有一台可以访问互联网的电脑作为客户端。</p>
<h4>4.1 安装服务端</h4>
<p>NAS 是群晖的系统，熟悉群晖的小伙伴应该知道，可以直接像 Linux 一样使用它。先准备好配置文件，现在 frp 的配置文件是<code>toml</code>格式的。作者使用的服务端配置文件如下所示：</p>
<pre><code class="prism language-toml">[common]
bind_addr = "0.0.0.0"
bind_port = 7000
bind_udp_port = 7001
kcp_bind_port = 7000
vhost_http_port = 10080
vhost_https_port = 10443
dashboard_addr = "0.0.0.0"
dashboard_port = 7500
dashboard_user = "admin"
dashboard_pwd = "admin"
log_file = "./frps.log"
log_level = "info"
log_max_days = 3
disable_log_color = false
token = "12345678"
allow_ports = "2000-3000,3001,3003,4000-50000"
max_pool_count = 5
max_ports_per_client = 0
subdomain_host = "frps.com"
tcp_mux = true
</code></pre>
<p>将上面的这个配置文件保存成<code>frps.toml</code>，然后通过 Docker 启动 frp 容器：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F68074c9718d04760a0852b7e4ec0034f.png&amp;size=m" alt=""></p>
<p>双击安装 frps 的服务端，将配置文件及相关端口映射出来。配置文件映射路径为：<code>/你本机的路径/frps.toml:/etc/frp/frps.toml</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7c04860eb6484eb29a282a8c1d255e41.png&amp;size=m" alt=""></p>
<p>看一下日志窗口，确定启动成功后即可。</p>
<h4>4.2 安装客户端</h4>
<p>其实 frp 可以安装在诸多平台上，Windows、Linux都可以。我这里客户端是一台 Ubuntu：</p>
<pre><code class="prism language-shell">#下载
wget https://github.com/fatedier/frp/releases/download/v0.59.0/frp_0.59.0_linux_amd64.tar.gz
#解压
tar -zxvf frp_0.59.0_linux_amd64.tar.gz
#进入目录
cd frp_0.59.0_linux_amd64/
</code></pre>
<p>进入目录后可以发现既有 Server 又有 Client,只用关注客户端这俩文件即可：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff61d7f023aa8427986253b2a6a2ca926.png&amp;size=m" alt=""></p>
<p>先修改一下 Client的配置文件，改成如下：</p>
<pre><code class="prism language-toml">serverAddr = "你的公网ip"
serverPort = 7000
auth.token = "12345678"

[[proxies]]
name = "test-tcp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 2001
remotePort = 2001
</code></pre>
<p>其中<code>localPort</code>就是本机想要公开出去的端口，<code>remotePort</code>就是服务端的端口，这俩做了一个映射。可以依次写很多个映射，这里就举个例子。
 <br>
 修改完配置文件后，直接脚本启动：<code>./frps -c ./frps.toml</code>。然后就可以通过服务端对应的IP:端口进行访问了。</p>
<p>最后，一般推荐大家通过 systemd 来进行 frp 服务的管理，包括启动、停止、配置后台运行和设置开机自启动。具体教程<a href="https://gofrp.org/zh-cn/docs/setup/systemd/">在这里</a>，就不赘述了。</p>
<h3>五、完结</h3>
<p>至此，我们就完成了内网穿透，实现了无公网的电脑对外提供服务，希望大家一切顺利。</p>
<p>原文链接: https://blog.csdn.net/u012558210/article/details/140849783</p>]]></description><guid isPermaLink="false">/archives/28427861-88a7-4aed-ab44-1b6b739568e3</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:13:12 GMT</pubDate></item><item><title><![CDATA[【网络编程】——基于TCP协议实现回显服务器及客户端]]></title><link>https://wiki.dgfygk.com/archives/38737fc8-def6-4f8a-bb7b-efe80dbe3ee8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E3%80%91%E2%80%94%E2%80%94%E5%9F%BA%E4%BA%8ETCP%E5%8D%8F%E8%AE%AE%E5%AE%9E%E7%8E%B0%E5%9B%9E%E6%98%BE%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8F%8A%E5%AE%A2%E6%88%B7%E7%AB%AF&amp;url=/archives/38737fc8-def6-4f8a-bb7b-efe80dbe3ee8" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>个人主页：<a href="https://xiaofeizhu.blog.csdn.net/">兜里有颗棉花糖</a>
  <br>
  欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 <a href="https://xiaofeizhu.blog.csdn.net/">兜里有颗棉花糖</a> 原创
  <br>
  收录于专栏【<a href="https://blog.csdn.net/m0_74352571/category_12535230.html?spm=1001.2014.3001.5482">网络编程</a>】【<a href="https://blog.csdn.net/m0_74352571/category_12520599.html?spm=1001.2014.3001.5482">Java系列</a>】
  <br>
  本专栏旨在分享学习网络编程的一点学习心得，欢迎大家在评论区交流讨论💌</p>
</blockquote>
<p>
 <br>
</p>
<h4>目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#TCP_9">一、TCP实现回显服务器</a></li>
 <li><a href="https://wiki.dgfygk.com#_14">二、服务器端</a></li>
 <li><a href="https://wiki.dgfygk.com#_136">三、客户端</a></li>
 <li><a href="https://wiki.dgfygk.com#_190">四、运行结果</a></li>
</ul>
<p>
 <br>
</p>
<h2>一、TCP实现回显服务器</h2>
<p>TCP提供的API主要有两个类<code>Socket</code>（<code>既会给服务器使用也会给客户端使用</code>）和<code>ServerSocket</code>（一般为服务器使用）。TCP将数据分割成<code>以字节为单位</code>的小数据块进行传输（一个TCP数据报就是一个字节数组byte[]）。</p>
<h2>二、服务器端</h2>
<blockquote>
 <p><strong>在服务器端创建一个ServerSocket对象，并绑定一个端口号。</strong>
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F64083ae2d5b96177612bc9eaf351256e.png&amp;size=m" alt="">
  <br>
  <strong>进入while循环：</strong>
  <br>
  <strong>注意这里和Udp实现回显服务器不同的是,Tcp进入循环之后并不是读取客户端请求,而是先处理客户端的连接（<code>Tcp是一个有连接的协议(Udp是没有连接的协议),所以有连接的话会优先处理连接，连接可以理解为客户端和服务器彼此之间保留对方的信息</code>）。一个服务器要应对很多客户端,在服务器内核中有很多客户端的连接,在应用程序层面我们需要对这些连接进行一一处理，这里服务器内核中的连接就像一个一个的待办事项一样,这些待办事项在队列这样的数据结构中，所以应用程序需要一一完成这样的任务（这个过程类似于生产者消费者模型）。</strong>
  <br>
  <strong><code>serverSocket.accept()方法可以将服务器内核中的连接获取到应用程序中</code></strong>
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F840a7da86bf06baf936e74a88caf614d.png&amp;size=m" alt=""></p>
 <p><strong>当程序启动之后就会立刻执行到accept方法，在调用<code>serverSocket.accept()</code>方法之后，如果此时没有客户端连接请求到达，该方法会一直阻塞，直到有客户端与服务器成功建立连接才会继续执行。</strong>
  <br>
  <strong>现在来解释这里的返回值问题，<code>serverSocket.accept()方法会返回一个Socket对象</code>：我们已经直到accept方法会把服务器内核已经建立好的连接拿到应用程序中，但是accpet方法的返回值并非是一个<code>Connection这样的对象</code>，而是<code>Socket这样的对象</code>。</strong>
  <br>
  <strong>这里返回的<code>Socket对象</code>相当于一个耳麦，既可以发出自己的声音也可以听到对方的声音（我们不需要管对方是谁，我们只需要对着这个耳麦说话就行）。然后我们通过<code>Socket对象与对方进行网络通信</code></strong>
  <br>
  <strong><code>Socket</code>和<code>ServerSocket</code>：</strong>
  <br>
  <strong>ServerSocket类用于在服务器端创建一个服务器套接字，它监听指定的端口，等待客户端的连接请求：通过调用ServerSocket的accept()方法，服务器监听指定端口，直到有客户端发起连接请求，accept()方法才返回一个表示客户端连接的Socket对象。通过这个Socket对象，可以进行与客户端的通信。</strong>
  <br>
  <strong>Socket类用于创建一个客户端套接字，它是客户端与服务器进行通信的终点：通过调用Socket的构造方法，指定服务器的主机名和端口号，可以与服务器建立连接。</strong>
  <br>
  <strong>总的来说：<code>ServerSocket用于监听端口，接受客户端的连接请求并返回一个Socket对象，而Socket用于与服务器建立连接，并进行数据的读写操作</code>。通过这两个类的配合，实现了TCP协议下的客户端与服务器之间的通信。</strong></p>
</blockquote>
<p><strong>服务器端代码如下</strong></p>
<pre><code class="prism language-java">import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {

    private ServerSocket serverSocket = null;
    private ExecutorService service = Executors.newCachedThreadPool();
    // 绑定端口号
    public  TcpEchoServer(int port) throws IOException {

        serverSocket = new ServerSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {

        System.out.println("服务器启动!!!");
        while(true) {

            // 注意这里和Udp不同的是,Tcp进入循环之后并不是读取客户端请求,而是先处理客户端的连接
            // Tcp是一个有连接的协议(Udp是没有连接的协议),所以有连接的话会优先处理连接
            // 连接可以理解为客户端和服务器彼此之间保留对方的信息
            // 一个服务器要应对很多客户端,在服务器内核中有很多客户端的连接,在应用程序层面我们需要对这些连接进行一一处理
            // 这里服务器内核中的连接就像一个一个的待办事项一样,这些待办事项在队列这样的数据结构中
            // 所以应用程序需要一一完成这样的任务
            Socket clientSocket = serverSocket.accept();
//            Thread t = new Thread(() -&gt; {

//                processConnection(clientSocket);
//            });
//            t.start();
            service.submit(new Runnable() {

                @Override
                public void run() {

                    processConnection(clientSocket);
                }
            });
        }
    }

    // 通过这个方法来处理连接的逻辑
    private void processConnection(Socket clientSocket) {

        System.out.printf("[%s:%d] 客户端上线啦!!!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        // 接下来读取请求，根据请求计算响应，最后再返回响应
        // Socket对象内部包含了两个字节流对象，我们需要先获取到这个字节流对象，然后再完成后续的读写操作
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {

            // 一次连接中可以涉及到多次请求和响应
            while( true ) {

                // 第一步:读取请求并进行解析
                // 这里为了读取方便,我们直接使用Scanner
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()) {

                    // 读取完毕客户端下线

                    System.out.printf("[%s:%d] 客户端下线!!!\n]",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                // 这里我们约定客户端输入来的请求是文本数据，同时以空白符作为分割
                String request = scanner.next();

                // 第二步:根据请求计算响应
                String response = process(request);

                // 第三步:把响应写回给客户端,把OutputStream使用PrinterWriter进行包装，方法进行数据的传输
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(response);

                // 刷新缓冲区
                writer.flush();

                // 打印当前的请求详情
                System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress().toString(),
                                                                clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {

            e.printStackTrace();
        } finally {

            try {

                clientSocket.close();
            } catch (IOException e) {

                // 这里必须确保Socket能够被关闭
                e.printStackTrace();
            }
        }
    }

    private String process(String request) {

        return request;
    }

    public static void main(String[] args) throws IOException {

        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}
</code></pre>
<h2>三、客户端</h2>
<p>关于回显服务器这里我们来看客户端的代码是如何进行编写的，主要有4个步骤，如下：</p>
<ul>
 <li><strong>第一步：从控制台上读取用户的输入。</strong></li>
 <li><strong>第二部：把输入的内容构造成请求，并将其发送给服务器。</strong></li>
 <li><strong>第三步：从服务器读取响应</strong></li>
 <li><strong>最后一步：把响应显示到控制台上。</strong></li>
</ul>
<p><strong>客户端代码如下：</strong></p>
<pre><code class="prism language-java">import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {

    private Socket socket = null;

    // 要和服务器进行通信的话我们就需要先知道服务器的位置外哪里。
    public TcpEchoClient(String serverIp,int serverPort) throws IOException {

        socket = new Socket(serverIp,serverPort);
    }

    public void start() {

        System.out.println("客户端启动!!!");
        Scanner scannerConsole = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {

            while(true) {

                // 从控制台输入一个字符串
                System.out.print("-&gt; ");
                String request = scannerConsole.next();
                // 把请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                // 使用println加上换行,后续服务器读取请求的时候就可以使用scanner.next来获取了
                printWriter.println(request);
                // 调用flush确保数据发送出去了
                printWriter.flush();
                // 从服务器中读取响应
                Scanner scannerNetwort = new Scanner(inputStream);
                String response = scannerNetwort.next();
                // 把响应打印出来
                System.out.println(response);
            }
        } catch (IOException e) {

            e.printStackTrace();
        }

    }
    public static void main(String[] args) throws IOException {

        TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
        client.start();
    }
}
</code></pre>
<h2>四、运行结果</h2>
<blockquote>
 <p>运行结果如下：
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8ab5b87ca242e0b989352bb7d2087885.png&amp;size=m" alt="">
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9423aa7879bae70bdb25175fce0bcd46.png&amp;size=m" alt="">
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0fe6fe7f7bfb8f4eb893e4950ce23dfe.png&amp;size=m" alt=""></p>
</blockquote>
<p><strong></strong>本文到这里就结束了，希望友友们可以支持一下一键三连哈。嗯，就到这里吧，再见啦！！！<strong></strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1626b768045268dccc6bef6db2d4aaf0.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/m0_74352571/article/details/135209036</p>]]></description><guid isPermaLink="false">/archives/38737fc8-def6-4f8a-bb7b-efe80dbe3ee8</guid><dc:creator>黄健</dc:creator><category>网络与通信</category><pubDate>Sat, 28 Sep 2024 07:13:00 GMT</pubDate></item><item><title><![CDATA[Java如何读取resources目录下的文件路径（九种代码示例教程）]]></title><link>https://wiki.dgfygk.com/archives/a37663dd-4638-4255-858b-ecb43d30e63f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%A6%82%E4%BD%95%E8%AF%BB%E5%8F%96resources%E7%9B%AE%E5%BD%95%E4%B8%8B%E7%9A%84%E6%96%87%E4%BB%B6%E8%B7%AF%E5%BE%84%EF%BC%88%E4%B9%9D%E7%A7%8D%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B%E6%95%99%E7%A8%8B%EF%BC%89&amp;url=/archives/a37663dd-4638-4255-858b-ecb43d30e63f" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>本文摘要：Java如何读取resources目录下的文件路径</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff2455270220f4e1d95e398625528bd23.png&amp;size=m" alt=""></p>
<blockquote>
 <p>😎 作者介绍：我是程序员洲洲，一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。公粽号：洲与AI。
  <br>
  🤓 欢迎大家关注！我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
  <br>
  🌼 同时洲洲已经建立了程序员技术交流群，如果您感兴趣，可以私信我加入我的社群\~社群中将不定时分享各类福利
  <br>
  🖥 随时欢迎您跟我沟通，一起交流，一起成长、进步！<a href="https://bbs.csdn.net/topics/614780338">点此即可获得联系方式\~</a></p>
</blockquote>
<p>
 <br>
</p>
<h4>本文目录</h4>
<ul>
 <li><a href="https://wiki.dgfygk.com#_13">前言</a></li>
 <li><a href="https://wiki.dgfygk.com#_17">代码一：根据文件路径读取文件内容</a></li>
 <li><a href="https://wiki.dgfygk.com#getResourcegetPath_46">代码二：使用getResource和getPath方法</a></li>
 <li><a href="https://wiki.dgfygk.com#getResourcegetPath_62">代码三：使用getResource和getPath方法</a></li>
 <li><a href="https://wiki.dgfygk.com#getFile_81">代码四：通过文件名+getFile()来获取文件</a></li>
 <li><a href="https://wiki.dgfygk.com#getResourceAsStream_108">代码五：使用getResourceAsStream方法获取流（无需文件路径）</a></li>
 <li><a href="https://wiki.dgfygk.com#ClassPathResource_139">代码六：通过ClassPathResource类获取文件流</a></li>
 <li><a href="https://wiki.dgfygk.com#_155">代码七：通过绝对路径获取项目中文件的位置</a></li>
 <li><a href="https://wiki.dgfygk.com#new_File_173">代码八：new File(““)获取当前的绝对路径</a></li>
 <li><a href="https://wiki.dgfygk.com#_197">代码九：设置环境变量</a></li>
 <li><a href="https://wiki.dgfygk.com#_226">总结</a></li>
</ul>
<p>
 <br>
</p>
<h2>前言</h2>
<p>资源管理是开发过程中不可或缺的一部分。资源文件，如配置文件、图片和文本文件，通常被放置在项目的resources目录下，以便于管理和访问。然而，对于初学者来说，如何正确地读取这些文件路径可能会遇到一些困惑。本文将深入探讨Java中读取resources目录下文件路径的几种常见方法，帮助开发者更有效地管理项目资源，并确保应用程序的健壮性和可维护性。</p>
<h2>代码一：根据文件路径读取文件内容</h2>
<pre><code class="prism language-java">    /**
     * 根据文件路径读取文件内容
     * @param fileInPath
     * @throws IOException
     */
    public static void getFileContent(Object fileInPath) throws IOException {

        BufferedReader br = null;
        if (fileInPath == null) {

            return;
        }
        if (fileInPath instanceof String) {

            br = new BufferedReader(new FileReader(new File((String) fileInPath)));
        } else if (fileInPath instanceof InputStream) {

            br = new BufferedReader(new InputStreamReader((InputStream) fileInPath));
        }
        String line;
        while ((line = br.readLine()) != null) {

            System.out.println(line);
        }
        br.close();
    }
</code></pre>
<h2>代码二：使用getResource和getPath方法</h2>
<p>这里的getResource(““)里面是空字符串</p>
<pre><code class="prism language-java">    public void function1(String fileName) throws IOException {

        String path = this.getClass().getClassLoader().getResource("").getPath();
        //注意getResource("")里面是空字符串
        System.out.println(path);
        String filePath = path + fileName;
        System.out.println(filePath);
        getFileContent(filePath);
    }
</code></pre>
<h2>代码三：使用getResource和getPath方法</h2>
<p>直接通过getResource(fileName)方法获取文件路径，如果路径中带有中文要使用URLDecoder.decode进行解码。</p>
<pre><code class="prism language-java">    /**
     * 直接通过文件名getPath来获取路径
     * @param fileName
     * @throws IOException
     */
    public void function2(String fileName) throws IOException {

        String path = this.getClass().getClassLoader().getResource(fileName).getPath();//注意getResource("")里面是空字符串
        System.out.println(path);
        String filePath = URLDecoder.decode(path, "UTF-8");//如果路径中带有中文会被URLEncoder,因此这里需要解码
        System.out.println(filePath);
        getFileContent(filePath);
    }
</code></pre>
<h2>代码四：通过文件名+getFile()来获取文件</h2>
<p>文件路径的话getFile和getPath效果是一样的，如果是URL路径的话getPath是带有参数的路径。如下所示：</p>
<pre><code class="prism language-java">url.getFile()=/pub/files/foobar.txt?id=zhouzhou
url.getPath()=/pub/files/foobar.txt
</code></pre>
<p>使用getFile()方式获取文件的代码如下：</p>
<pre><code class="prism language-java">    /**
     * 直接通过文件名+getFile()来获取
     * @param fileName
     * @throws IOException
     */
    public void function3(String fileName) throws IOException {

        String path = this.getClass().getClassLoader().getResource(fileName).getFile();//注意getResource("")里面是空字符串
        System.out.println(path);
        String filePath = URLDecoder.decode(path, "UTF-8");//如果路径中带有中文会被URLEncoder,因此这里需要解码
        System.out.println(filePath);
        getFileContent(filePath);
    }
</code></pre>
<h2>代码五：使用getResourceAsStream方法获取流（无需文件路径）</h2>
<p>SpringBoot中所有文件都在jar包中，没有一个实际的路径，因此可以使用以下方式。</p>
<pre><code class="prism language-java">    /**
     * 直接使用getResourceAsStream方法获取流
     * springboot项目中需要使用此种方法，因为jar包中没有一个实际的路径存放文件
     * @param fileName
     * @throws IOException
     */
    public void function4(String fileName) throws IOException {

        InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);
        getFileContent(in);
    }
</code></pre>
<p>使用getResourceAsStream方法获取流，不使用getClassLoader可以使用getResourceAsStream(“/配置测试.txt”)直接从resources根路径下获取，SpringBoot中所有文件都在jar包中，没有一个实际的路径，因此可以使用以下方式。</p>
<pre><code class="prism language-java">    /**
     * 直接使用getResourceAsStream方法获取流
     * 如果不使用getClassLoader，可以使用getResourceAsStream("/配置测试.txt")直接从resources根路径下获取
     * @param fileName
     * @throws IOException
     */
    public void function5(String fileName) throws IOException {

        InputStream in = this.getClass().getResourceAsStream("/" + fileName);
        getFileContent(in);
    }
</code></pre>
<h2>代码六：通过ClassPathResource类获取文件流</h2>
<pre><code class="prism language-java">    /**
     * 通过ClassPathResource类获取，建议SpringBoot中使用
     * springboot项目中需要使用此种方法，因为jar包中没有一个实际的路径存放文件
     * @param fileName
     * @throws IOException
     */
    public void function6(String fileName) throws IOException {

        ClassPathResource classPathResource = new ClassPathResource(fileName);
        InputStream inputStream = classPathResource.getInputStream();
        getFileContent(inputStream);
    }
</code></pre>
<h2>代码七：通过绝对路径获取项目中文件的位置</h2>
<pre><code class="prism language-java">    /**
     * 通过绝对路径获取项目中文件的位置（不能用于服务器）
     * @param fileName
     * @throws IOException
     */
    public void function7(String fileName) throws IOException {

        String rootPath = System.getProperty("user.dir");//E:\WorkSpace\Git\spring-framework-learning-example
        String filePath = rootPath + "\\chapter-2-springmvc-quickstart\\src\\main\\resources\\"+fileName;
        getFileContent(filePath);
    }
</code></pre>
<h2>代码八：new File(““)获取当前的绝对路径</h2>
<pre><code class="prism language-java">    /**
     * 通过绝对路径获取项目中文件的位置（不能用于服务器）
     * @param fileName
     * @throws IOException
     */
    public void function8(String fileName) throws IOException {

        //参数为空
        File directory = new File("");
        //规范路径：getCanonicalPath() 方法返回绝对路径，会把 ..\ 、.\ 这样的符号解析掉
        String rootCanonicalPath = directory.getCanonicalPath();
        //绝对路径：getAbsolutePath() 方法返回文件的绝对路径，如果构造的时候是全路径就直接返回全路径，如果构造时是相对路径，就返回当前目录的路径 + 构造 File 对象时的路径
        String rootAbsolutePath =directory.getAbsolutePath();
        System.out.println(rootCanonicalPath);
        System.out.println(rootAbsolutePath);
        String filePath = rootCanonicalPath + "\\chapter-2-springmvc-quickstart\\src\\main\\resources\\"+fileName;
        getFileContent(filePath);
    }
</code></pre>
<h2>代码九：设置环境变量</h2>
<p>主要是通过设置环境变量，将文件放在环境变量中，原理也是通过绝对路径获取。</p>
<p>设置一个环境变量：TEST_ROOT=D:\WorkSpace\Git\spring-framework-learning-example</p>
<pre><code class="prism language-java"> System.getenv("TEST_ROOT");
 System.getProperty("TEST_ROOT")
</code></pre>
<p>通过设置环境变量的方式，然后通过绝对路径获取文件</p>
<pre><code class="prism language-java">    /**
     * 通过绝对路径获取项目中文件的位置
     * @param fileName
     * @throws IOException
     */
    public void function9(String fileName) throws IOException {

        System.setProperty("TEST_ROOT","E:\\WorkSpace\\Git\\spring-framework-learning-example");
        //参数为空
        String rootPath = System.getProperty("TEST_ROOT");
        System.out.println(rootPath);
        String filePath = rootPath + "\\chapter-2-springmvc-quickstart\\src\\main\\resources\\"+fileName;
        getFileContent(filePath);
    }
</code></pre>
<h2>总结</h2>
<p>Hello，各位看官老爷们好，洲洲已经建立了CSDN技术交流群，如果你很感兴趣，可以私信我加入我的社群。</p>
<p>📝社群中不定时会有很多活动，例如每周都会包邮免费送一些技术书籍及精美礼品、学习资料分享、大厂面经分享、技术讨论、行业大佬创业杂谈等等。</p>
<p>📝社群方向很多，相关领域有Web全栈（前后端）、人工智能、机器学习、自媒体变现、前沿科技文章分享、论文精读等等。</p>
<p>📝不管你是多新手的小白，都欢迎你加入社群中讨论、聊天、分享，加速助力你成为下一个技术大佬！也随时欢迎您跟我沟通，一起交流，一起成长。变现、进步、技术、资料、项目、你想要的这里都会有</p>
<p>📝欢迎您加入社群\~一个人可以或许可以走的很快，但一群人将走的更远！</p>
<p>📝关注我的公众号（与CSDN同ID：洲与AI）可以获得一份Java 10万字面试宝典及相关资料！\~</p>
<p>📝想都是问题，做都是答案！行动起来吧！欢迎评论区or后台与我沟通交流，也欢迎您点击下方的链接直接加入到我的交流社群！\~ <a href="https://bbs.csdn.net/topics/614780338">跳转链接社区\~</a></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe1caf81c3270b046409b9959bcfc7475.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/weixin_51484460/article/details/139393766</p>]]></description><guid isPermaLink="false">/archives/a37663dd-4638-4255-858b-ecb43d30e63f</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Sat, 28 Sep 2024 07:11:28 GMT</pubDate></item><item><title><![CDATA[秒开WebView Android性能优化全攻略]]></title><link>https://wiki.dgfygk.com/archives/6ead1434-7c8f-42db-8e57-f46d505882c8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%A7%92%E5%BC%80WebView%20Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%85%A8%E6%94%BB%E7%95%A5&amp;url=/archives/6ead1434-7c8f-42db-8e57-f46d505882c8" width="1" height="1" alt="" style="opacity:0;">
<p><strong>秒开WebView Android性能优化全攻略</strong></p>
<p>在Android开发中，WebView作为一个用于展示网页内容的组件，其性能优化对于提升用户体验至关重要。以下是一份详尽的WebView性能优化全攻略，旨在通过一系列策略和技巧实现WebView的秒开体验。</p>
<h4>一、加载优化</h4>
<h5>1. 预加载WebView</h5>
<p>预加载是在应用启动时提前初始化WebView并进行初步加载的过程，可以有效减少WebView首次加载页面的时间。具体做法包括：</p>
<ul>
 <li><strong>应用启动时初始化</strong>：在应用启动过程中，将WebView加入到IdleHandler中，等到主线程空闲时进行加载。可以加载一个空白页（“about:blank”），以减少首次加载时的白屏时间。</li>
 <li><strong>WebView配置</strong>：设置WebView的相关配置，如启用JavaScript等，确保WebView处于最佳状态。</li>
</ul>
<h5>2. 延迟加载非首屏资源</h5>
<p>将一些非首屏必需的操作推迟到首屏显示后再执行，如后台网络请求、埋点上报等，以减少首屏加载时间。例如，可以使用Handler的postDelayed方法延迟执行非首屏操作。</p>
<h5>3. 并行请求</h5>
<p>在加载H5页面时，同时由Native端发起请求获取模板文件和动态数据。一旦数据获取成功，通过JavaScript将数据传递给H5页面进行填充，实现并行请求，减少总耗时。</p>
<h4>二、请求优化</h4>
<h5>1. 拦截请求</h5>
<p>通过自定义WebViewClient并重写shouldInterceptRequest方法，拦截WebView的请求并进行相应的处理。例如，可以拦截请求并加载本地缓存资源，或者重定向请求到新的URL。</p>
<h5>2. 缓存机制</h5>
<ul>
 <li><strong>WebView缓存池</strong>：使用WebView缓存池来存储预先创建的WebView实例，减少初始化WebView的时间和资源消耗。当需要加载网页时，从缓存池中获取可用的WebView实例，而不是每次都创建新的WebView。</li>
 <li><strong>HTTP缓存</strong>：通过添加Cache-Control、Expires、Etag、Last-Modified等HTTP头信息定义缓存策略，减少重复加载相同页面的时间。</li>
 <li><strong>本地缓存</strong>：将频繁访问的公共资源（如CSS、JavaScript文件）缓存到应用的本地存储中，并在WebView中加载这些本地资源，减少网络请求。</li>
</ul>
<h4>三、渲染优化</h4>
<h5>1. 启用硬件加速</h5>
<p>启用硬件加速可以提高WebView的渲染速度，但在低端设备上可能会造成性能问题，因此需要根据实际情况进行选择。可以在AndroidManifest.xml中设置<code>&lt;application android:hardwareAccelerated="true"&gt;</code>来全局启用硬件加速，或者针对特定Activity启用。</p>
<h5>2. 优化CSS与JS</h5>
<ul>
 <li><strong>CSS优化</strong>：将CSS标签尽量放在HTML文件的头部，以确保在解析HTML时尽早加载CSS，避免白屏时间。</li>
 <li><strong>JS优化</strong>：将JS代码放在HTML文件的底部，或使用defer属性，以避免阻塞DOM解析。</li>
</ul>
<h4>四、进程优化</h4>
<p>将WebView放置在单独的进程中运行，以减轻对主进程的影响，避免主线程阻塞、内存泄漏、异常crash等问题。可以在AndroidManifest.xml中为WebView所在的Activity指定不同的进程名，如<code>&lt;activity android:name=".WebViewActivity" android:process=":webview_process"&gt;</code>。</p>
<h4>五、DNS优化</h4>
<p>确保WebView加载的网页域名与App中其他网络请求的域名一致，以减少域名解析的耗时。相同的域名解析成IP系统会进行缓存，从而减少解析时间。</p>
<h4>六、静态页面直出</h4>
<p>后端对正文数据和前端代码进行整合，直接生成HTML文件，减少前端渲染时间。这样，WebView可以直接加载包含所需内容和样式的HTML文件，无需进行二次加工，提高加载速度。</p>
<h4>七、监控与调优</h4>
<h5>1. 加载过程监控</h5>
<p>使用WebViewClient的回调方法（如onPageFinished）来监控页面加载的完整过程，并记录加载时间。根据加载时间调整优化策略，如增加缓存、优化网络请求等。</p>
<h5>2. 用户数据分析</h5>
<p>收集用户使用数据，分析加载时长分布，针对高耗时用户进行优化。通过数据分析，可以识别出性能瓶颈所在，并针对性地进行优化。</p>
<h4>八、其他注意事项</h4>
<ul>
 <li><strong>避免大图片和复杂WebGL</strong>：避免在WebView中使用过大的图片或复杂的WebGL操作，以免导致性能问题。</li>
 <li><strong>安全保护</strong>：使用CSP（Content Security Policy）和HTTPS来保护WebView不被运营商劫持或注入恶意代码。</li>
 <li><strong>定期更新</strong>：确保WebView和Android系统保持最新，以享受最新的性能改进和安全修复。</li>
</ul>
<p>综上所述，通过加载优化、请求优化、渲染优化、进程优化、DNS优化、静态页面直出、监控与调优以及其他注意事项等一系列策略，可以显著提升Android WebView的加载速度和用户体验，实现秒开WebView的目标。在实际开发中，应根据具体需求和设备性能情况灵活选择和应用这些优化策略。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141280758</p>]]></description><guid isPermaLink="false">/archives/6ead1434-7c8f-42db-8e57-f46d505882c8</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 05:51:12 GMT</pubDate></item><item><title><![CDATA[设计模式六大原则中的里氏替换原则]]></title><link>https://wiki.dgfygk.com/archives/c16dce48-8632-4d91-bb59-5a414a61f4c5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%85%AD%E5%A4%A7%E5%8E%9F%E5%88%99%E4%B8%AD%E7%9A%84%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99&amp;url=/archives/c16dce48-8632-4d91-bb59-5a414a61f4c5" width="1" height="1" alt="" style="opacity:0;">
<p>设计模式六大原则中的里氏替换原则（Liskov Substitution Principle, LSP）是面向对象设计中一个至关重要的原则，它定义了继承的基本原则和约束，确保子类能够透明地替换父类，而不会破坏系统的正确性和稳定性。以下是对里氏替换原则的详细阐述，包括其定义、应用、重要性、以及在实际开发中的具体体现。</p>
<h4>一、里氏替换原则的定义</h4>
<p>里氏替换原则是由芭芭拉·里斯科夫（Barbara Liskov）在1987年提出的，它强调了在继承关系中，子类应当能够替换掉父类而不影响程序的正确性。具体来说，里氏替换原则要求子类必须能够完全继承父类的所有功能，并且在不改变原有功能的前提下，可以添加新的功能。这意味着，当使用父类的地方，使用子类进行替换后，程序的行为应当保持不变。</p>
<h4>二、里氏替换原则的应用</h4>
<p>里氏替换原则在面向对象设计中的应用非常广泛，它主要通过以下几个方面来体现：</p>
<ol>
 <li>
  <p><strong>继承的正确使用</strong>：</p>
 </li>
 <li>
  <p>子类必须能够继承父类的所有属性和方法，除非这些方法在子类中不再适用。</p>
 </li>
 <li>
  <p>子类在继承父类的基础上，可以添加新的属性和方法，但不能改变父类原有的行为。</p>
 </li>
 <li>
  <p>子类在重写父类的方法时，必须保持方法的行为一致性，即子类方法的行为结果应当与父类方法相同或更严格。</p>
 </li>
 <li>
  <p><strong>方法的重写与重载</strong>：</p>
 </li>
 <li>
  <p>当子类重写父类的方法时，方法的输入参数应当比父类方法的输入参数更宽松（协变），或者方法的返回类型应当比父类方法的返回类型更严格（逆变）。</p>
 </li>
 <li>
  <p>子类不能重写父类的静态方法，因为静态方法是与类关联的，而不是与对象关联的。</p>
 </li>
 <li>
  <p>子类可以重载父类的方法，但重载的方法必须具有不同的参数列表。</p>
 </li>
 <li>
  <p><strong>多态的使用</strong>：</p>
 </li>
 <li>
  <p>多态是面向对象编程的一个重要特性，它允许以统一的接口调用不同的对象。里氏替换原则确保了在使用多态时，子类对象可以透明地替换父类对象。</p>
 </li>
 <li>
  <p>通过接口或抽象类实现多态时，子类必须完全实现接口或抽象类中定义的所有方法。</p>
 </li>
</ol>
<h4>三、里氏替换原则的重要性</h4>
<p>里氏替换原则在面向对象设计中具有重要的意义，它主要体现在以下几个方面：</p>
<ol>
 <li>
  <p><strong>提高代码的可维护性</strong>：</p>
 </li>
 <li>
  <p>通过确保子类能够替换父类而不影响程序的正确性，里氏替换原则降低了代码修改和扩展的难度。</p>
 </li>
 <li>
  <p>当需要修改或扩展系统时，可以在不破坏原有代码的基础上，通过添加新的子类来实现新的功能。</p>
 </li>
 <li>
  <p><strong>增强代码的复用性</strong>：</p>
 </li>
 <li>
  <p>子类继承了父类的所有属性和方法，因此可以重用父类中已经定义好的代码。</p>
 </li>
 <li>
  <p>通过扩展父类的功能，子类可以在不修改父类代码的情况下，实现更加丰富的功能。</p>
 </li>
 <li>
  <p><strong>促进良好的继承体系</strong>：</p>
 </li>
 <li>
  <p>里氏替换原则要求子类在继承父类时，必须遵守一定的规则和约束，这有助于构建更加合理和清晰的继承体系。</p>
 </li>
 <li>
  <p>良好的继承体系可以降低代码的复杂度，提高代码的可读性和可理解性。</p>
 </li>
</ol>
<h4>四、里氏替换原则在实际开发中的具体体现</h4>
<p>在实际开发中，里氏替换原则通常通过以下几个方面来体现：</p>
<ol>
 <li>
  <p><strong>接口和抽象类的设计</strong>：</p>
 </li>
 <li>
  <p>设计接口或抽象类时，需要明确其功能和职责，确保子类能够完全实现这些功能和职责。</p>
 </li>
 <li>
  <p>接口或抽象类中的方法应当具有明确的行为规范和约束条件，子类在实现这些方法时必须遵守这些规范和条件。</p>
 </li>
 <li>
  <p><strong>子类的设计</strong>：</p>
 </li>
 <li>
  <p>子类在继承父类时，应当避免重写父类的非抽象方法，除非这些方法在子类中的行为与父类中的行为不一致。</p>
 </li>
 <li>
  <p>子类在添加新的属性和方法时，应当注意不要破坏父类的原有功能。</p>
 </li>
 <li>
  <p>子类在重写父类的方法时，应当保持方法的行为一致性，确保子类对象可以透明地替换父类对象。</p>
 </li>
 <li>
  <p><strong>测试和维护</strong>：</p>
 </li>
 <li>
  <p>在进行单元测试时，可以使用子类对象来替换父类对象进行测试，以验证里氏替换原则是否得到遵守。</p>
 </li>
 <li>
  <p>在系统维护和升级过程中，需要时刻关注继承关系的正确性和合理性，确保子类能够正确地替换父类而不影响系统的稳定性和正确性。</p>
 </li>
</ol>
<h4>五、结论</h4>
<p>里氏替换原则是面向对象设计中一个非常重要的原则，它确保了子类能够透明地替换父类而不影响程序的正确性。通过遵守里氏替换原则，我们可以构建更加稳定、可靠、可维护和可扩展的软件系统。在实际开发中，我们应当注重接口和抽象类的设计、子类的设计以及测试和维护等方面的工作，以确保里氏替换原则得到充分的体现和应用。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141356010</p>]]></description><guid isPermaLink="false">/archives/c16dce48-8632-4d91-bb59-5a414a61f4c5</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 05:50:45 GMT</pubDate></item><item><title><![CDATA[最佳的iPhone解锁软件和应用程序]]></title><link>https://wiki.dgfygk.com/archives/55bec7f9-e2b1-493a-9aff-a64b4be4fd62</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%80%E4%BD%B3%E7%9A%84iPhone%E8%A7%A3%E9%94%81%E8%BD%AF%E4%BB%B6%E5%92%8C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F&amp;url=/archives/55bec7f9-e2b1-493a-9aff-a64b4be4fd62" width="1" height="1" alt="" style="opacity:0;">
<p>在探讨最佳的iPhone解锁软件和应用程序时，我们需要考虑多个方面，包括软件的解锁能力、易用性、安全性、兼容性以及用户评价等。以下是对当前市场上几款优秀iPhone解锁软件和应用程序的详细分析，旨在为用户提供全面而深入的指导。</p>
<h4>一、奇客iOS解锁器</h4>
<p><strong>解锁能力</strong> ：
 <br>
 奇客iOS解锁器以其强大的解锁能力著称，能够绕过iOS的MDM（移动设备管理），让用户在没有管理员权限的情况下删除个人资料，并完全访问他们的iPhone。此外，它还支持使用iCloud激活锁删除功能，让用户能够轻松地将音乐、视频和照片与iTunes同步。同时，奇客iOS解锁器支持多种解锁方式，包括4/6位密码、Touch ID、Face ID、自定义数字和字母数字代码等，满足不同用户的需求。</p>
<p><strong>易用性</strong> ：
 <br>
 奇客iOS解锁器拥有简洁的界面设计，使得初学者也能轻松导航。其操作步骤简单明了，用户只需按照屏幕上的提示进行操作，即可在几分钟内完成解锁过程。此外，奇客iOS解锁器还提供了详细的操作指南和客户支持，帮助用户解决在使用过程中遇到的问题。</p>
<p><strong>安全性</strong> ：
 <br>
 在解锁过程中，奇客iOS解锁器注重用户数据的安全，确保不会丢失重要数据。同时，该软件还经过严格的安全测试，确保无病毒或恶意软件，保障用户的设备安全。</p>
<p><strong>兼容性</strong> ：
 <br>
 奇客iOS解锁器支持几乎所有平台，如Windows、iOS和macOS，同时与最新的iPhone型号和iOS版本保持兼容，确保用户能够随时随地进行解锁操作。</p>
<h4>二、LockWiper</h4>
<p><strong>解锁能力</strong> ：
 <br>
 LockWiper是另一款功能强大的iPhone解锁工具，能够解锁任何iPhone，即使忘记了密码、禁用了系统或多次尝试错误。它可以绕过Face ID和其他块，如二手iPhone和破碎的屏幕。此外，LockWiper还支持绕过MDM激活屏幕并永久删除配置文件，为用户提供全面的解锁服务。</p>
<p><strong>易用性</strong> ：
 <br>
 LockWiper拥有简单直观的用户界面，使得用户能够轻松上手。其操作步骤简洁明了，用户只需按照屏幕上的提示进行操作即可完成解锁过程。同时，LockWiper还提供了详细的使用教程和客户支持，帮助用户解决在使用过程中遇到的问题。</p>
<p><strong>安全性</strong> ：
 <br>
 LockWiper在解锁过程中注重用户数据的安全，确保不会丢失重要数据。同时，该软件还经过严格的安全测试，确保无病毒或恶意软件，保障用户的设备安全。</p>
<p><strong>兼容性</strong> ：
 <br>
 LockWiper支持所有iOS版本和设备，包括最新的iOS 16和iPhone 14机型。这使得用户无需担心设备兼容性问题，可以放心使用LockWiper进行解锁操作。</p>
<h4>三、FonesGo iPhone解锁器</h4>
<p><strong>解锁能力</strong> ：
 <br>
 FonesGo iPhone解锁器是一款功能全面的iPhone解锁软件，可以从iPhone/iPad/iPod touch中删除所有锁屏密码，并绕过iCloud激活锁。它支持删除没有密码的Apple ID，并在不丢失数据的情况下重置屏幕时间密码。此外，FonesGo iPhone解锁器还提供了高达99%的解锁成功率，确保用户能够成功解锁设备。</p>
<p><strong>易用性</strong> ：
 <br>
 FonesGo iPhone解锁器操作简单易懂，用户只需在3个简单的步骤中即可完成解锁操作。同时，该软件还提供了详细的操作指南和客户支持，帮助用户解决在使用过程中遇到的问题。</p>
<p><strong>安全性</strong> ：
 <br>
 FonesGo iPhone解锁器注重用户数据的安全，确保在解锁过程中不会丢失重要数据。同时，该软件还经过严格的安全测试，确保无病毒或恶意软件，保障用户的设备安全。</p>
<p><strong>兼容性</strong> ：
 <br>
 FonesGo iPhone解锁器支持最新的iPhone型号和iOS版本，包括iPhone 15和iOS 17。这使得用户能够随时随地进行解锁操作，无需担心设备兼容性问题。</p>
<h4>四、AnyUnlock</h4>
<p><strong>解锁能力</strong> ：
 <br>
 AnyUnlock是一款全面的iPhone解锁应用，支持解锁所有类型的屏幕锁定（如4/6位数字代码、Touch ID、Face ID等）以及Apple ID和iCloud账户。此外，它还可以绕过远程MDM（移动设备管理），解决"不支持SIM卡"等问题。AnyUnlock的高兼容性使得它几乎可以解锁所有iOS设备和版本。</p>
<p><strong>易用性</strong> ：
 <br>
 AnyUnlock拥有简洁直观的用户界面和操作流程，用户无需具备复杂的技术知识即可轻松上手。其提供的详细操作指南和客户支持也进一步提升了用户的使用体验。</p>
<p><strong>安全性</strong> ：
 <br>
 AnyUnlock在解锁过程中注重用户数据的安全，确保不会丢失重要数据。同时，该软件还经过严格的安全测试，确保无病毒或恶意软件，保障用户的设备安全。</p>
<p><strong>兼容性</strong> ：
 <br>
 AnyUnlock支持广泛的iOS设备和版本，包括最新的iPhone型号和iOS版本。这使得用户能够随时随地进行解锁操作，无需担心设备兼容性问题。</p>
<h4>五、Dr.Fone - Screen Unlock</h4>
<p><strong>解锁能力</strong> ：
 <br>
 Dr.Fone -Dr.Fone - Screen Unlock 是另一款备受欢迎的iPhone解锁软件，它在解锁领域有着良好的口碑和广泛的用户基础。</p>
<p><strong>解锁能力</strong> ：
 <br>
 Dr.Fone - Screen Unlock 提供了强大的解锁功能，能够处理各种iPhone屏幕锁定问题，包括忘记锁屏密码、Touch ID或Face ID无法识别等。它能够绕过这些限制，让用户重新获得对设备的访问权限。此外，Dr.Fone - Screen Unlock 还支持解锁由于多次输入错误密码而导致的iPhone停用状态，让用户无需恢复设备即可重新使用。</p>
<p><strong>易用性</strong> ：
 <br>
 Dr.Fone - Screen Unlock 的用户界面设计得既简洁又直观，使得即便是初学者也能轻松上手。软件的操作流程被简化到几个关键步骤，用户只需按照屏幕上的提示进行操作，即可在短时间内完成解锁过程。此外，Dr.Fone - Screen Unlock 还提供了详尽的帮助文档和在线支持，以便用户在遇到问题时能够迅速获得解答。</p>
<p><strong>安全性</strong> ：
 <br>
 在安全性方面，Dr.Fone - Screen Unlock 始终将用户数据的安全放在首位。它采用先进的加密技术来保护用户的数据传输和存储过程，确保在解锁过程中不会泄露用户的敏感信息。同时，Dr.Fone - Screen Unlock 还经过严格的安全测试，以确保其无病毒、无恶意软件，从而保障用户的设备安全。</p>
<p><strong>兼容性</strong> ：
 <br>
 Dr.Fone - Screen Unlock 支持广泛的iOS设备和版本，包括最新的iPhone型号和iOS系统。这意味着无论用户使用的是哪款iPhone或处于哪个iOS版本，都可以尝试使用Dr.Fone - Screen Unlock 进行解锁。这种广泛的兼容性使得Dr.Fone - Screen Unlock 成为许多用户的首选解锁工具。</p>
<h4>总结</h4>
<p>在选择最佳的iPhone解锁软件和应用程序时，用户需要根据自己的具体需求和设备情况来进行选择。以上提到的奇客iOS解锁器、LockWiper、FonesGo iPhone解锁器、AnyUnlock 和 Dr.Fone - Screen Unlock 都是市场上优秀的解锁工具，它们各自具有独特的优势和特点。用户可以根据解锁能力、易用性、安全性和兼容性等方面进行综合评估，选择最适合自己的解锁软件。同时，用户还应该注意保护自己的设备安全和个人隐私，避免在解锁过程中泄露敏感信息。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141280863</p>]]></description><guid isPermaLink="false">/archives/55bec7f9-e2b1-493a-9aff-a64b4be4fd62</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 05:50:16 GMT</pubDate></item><item><title><![CDATA[Ubuntu系统]]></title><link>https://wiki.dgfygk.com/archives/3387922c-fa4b-4683-a03e-980e2672dcab</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Ubuntu%E7%B3%BB%E7%BB%9F&amp;url=/archives/3387922c-fa4b-4683-a03e-980e2672dcab" width="1" height="1" alt="" style="opacity:0;">
<p>Ubuntu系统作为一款流行的开源操作系统，以其易用性、稳定性和丰富的软件资源而受到广泛欢迎。本文将从基础操作、文件管理、用户管理、软件管理、系统监控与维护等多个方面详细介绍Ubuntu系统的基础操作和使用。</p>
<h4>一、基础操作</h4>
<h5>1. 图形界面与命令行界面切换</h5>
<p>Ubuntu系统提供了图形用户界面（GUI）和命令行界面（CLI）两种操作方式。图形界面直观易用，适合大多数用户；而命令行界面则提供了更强大的功能和灵活性，适合高级用户和开发人员。</p>
<ul>
 <li><strong>图形界面到命令行界面</strong> ：在图形界面下，可以通过快捷键<code>Ctrl + Alt + T</code>打开终端（Terminal），也可以在桌面空白处右键选择"在终端中打开"来启动终端。此外，还可以通过关闭图形界面来完全进入命令行模式，使用<code>systemctl set-default multi-user.target</code>命令并重启系统即可。</li>
 <li><strong>命令行界面到图形界面</strong> ：在命令行界面下，可以通过<code>startx</code>命令尝试启动图形界面（但这通常不是标准做法）。更常见的是，使用<code>systemctl set-default graphical.target</code>命令并重启系统来恢复图形界面。</li>
</ul>
<h5>2. 关机与重启</h5>
<ul>
 <li><strong>关机</strong> ：在终端中输入<code>poweroff</code>命令或<code>shutdown -h now</code>命令可以立即关闭系统。</li>
 <li><strong>重启</strong> ：在终端中输入<code>reboot</code>命令或<code>shutdown -r now</code>命令可以立即重启系统。</li>
</ul>
<h5>3. 文件与目录操作</h5>
<p>Ubuntu系统中的文件和目录操作是日常使用中最为频繁的操作之一。</p>
<ul>
 <li><strong>查看当前目录内容</strong> ：使用<code>ls</code>命令可以查看当前目录下的文件和子目录。<code>ls -l</code>会以列表形式显示详细信息，<code>ls -a</code>会显示包括隐藏文件在内的所有文件。</li>
 <li><strong>切换目录</strong> ：使用<code>cd</code>命令可以切换当前工作目录。例如，<code>cd /home/username</code>会切换到指定用户的家目录。</li>
 <li><strong>创建文件与目录</strong> ：使用<code>touch</code>命令可以创建空文件，例如<code>touch newfile.txt</code>。使用<code>mkdir</code>命令可以创建新目录，例如<code>mkdir newdir</code>。</li>
 <li><strong>复制、移动与删除</strong> ：<code>cp</code>命令用于复制文件或目录，<code>mv</code>命令用于移动文件或目录（也可用于重命名），<code>rm</code>命令用于删除文件或目录（<code>rm -r</code>用于递归删除目录）。</li>
 <li><strong>编辑文件</strong> ：Ubuntu提供了多种文本编辑器，如<code>nano</code>、<code>gedit</code>（图形界面下）、<code>vim</code>等。<code>nano</code>和<code>vim</code>是命令行下的编辑器，而<code>gedit</code>则提供了图形界面。</li>
</ul>
<h4>二、用户管理</h4>
<p>Ubuntu系统支持多用户环境，每个用户都有自己的家目录和权限设置。</p>
<ul>
 <li><strong>查看当前用户</strong> ：使用<code>whoami</code>命令可以查看当前登录的用户名。</li>
 <li><strong>切换用户</strong> ：使用<code>su</code>命令可以切换到另一个用户（需要输入该用户的密码）。如果要以root用户身份登录，可以使用<code>sudo su</code>命令。</li>
 <li><strong>用户管理</strong> ：Ubuntu使用<code>useradd</code>、<code>usermod</code>、<code>userdel</code>等命令来管理用户账户。这些命令通常需要root权限才能执行。</li>
 <li><strong>组管理</strong> ：与用户管理类似，Ubuntu也提供了<code>groupadd</code>、<code>groupmod</code>、<code>groupdel</code>等命令来管理用户组。</li>
</ul>
<h4>三、软件管理</h4>
<p>Ubuntu系统提供了强大的包管理工具，如APT（Advanced Package Tool）和DPKG，用于安装、更新、卸载软件包。</p>
<ul>
 <li><strong>更新软件包列表</strong> ：使用<code>sudo apt update</code>命令可以更新APT的软件包列表。</li>
 <li><strong>安装软件包</strong> ：使用<code>sudo apt install package_name</code>命令可以安装指定的软件包。对于<code>.deb</code>格式的软件包，可以使用<code>sudo dpkg -i package_file.deb</code>命令进行安装。</li>
 <li><strong>卸载软件包</strong> ：使用<code>sudo apt remove package_name</code>命令可以卸载软件包，但保留配置文件。如果希望完全卸载软件包及其配置文件，可以使用<code>sudo apt purge package_name</code>命令。</li>
 <li><strong>升级系统</strong> ：使用<code>sudo apt upgrade</code>命令可以升级所有已安装的软件包到最新版本。使用<code>sudo apt dist-upgrade</code>命令则会进行更智能的升级处理，包括处理依赖关系的变化。</li>
</ul>
<h4>四、系统监控与维护</h4>
<p>Ubuntu系统提供了多种工具来监控系统性能和维护系统健康。</p>
<ul>
 <li><strong>查看系统信息</strong> ：使用<code>uname -a</code>命令可以查看内核版本和架构信息，使用<code>lscpu</code>、<code>lshw</code>等命令可以查看CPU、硬件等信息。</li>
 <li><strong>查看内存和CPU使用情况</strong> ：使用<code>free -h</code>命令可以查看内存使用情况（包括物理内存、交换空间等），使用<code>top</code>、<code>htop</code>（当然，我们继续深入探讨Ubuntu系统的基础操作和使用，特别是在系统监控与维护、网络配置、安全设置以及定制和个性化方面的内容。</li>
</ul>
<h4>五、系统监控与维护</h4>
<h5>5.1 系统监控工具</h5>
<p>Ubuntu提供了多种系统监控工具，帮助用户实时了解系统运行状态。</p>
<ul>
 <li><strong><code>top</code> 命令</strong> ：<code>top</code> 命令是Linux下常用的性能分析工具，它可以显示系统中各个进程的资源占用状况（如CPU、内存等）。</li>
 <li><strong><code>htop</code> 工具</strong> ：<code>htop</code> 是 <code>top</code> 命令的一个增强版本，它提供了一个彩色界面和更多的功能，如支持鼠标操作、进程树显示等。</li>
 <li><strong><code>vmstat</code> 命令</strong> ：<code>vmstat</code>（Virtual Memory Statistics）命令用于显示关于系统内存、进程、中断、CPU活动等的信息。</li>
 <li><strong><code>iostat</code> 命令</strong> ：<code>iostat</code> 主要用于监控系统输入输出设备和CPU的使用情况。这个命令是sysstat软件包的一部分，可能需要额外安装。</li>
 <li><strong><code>glances</code> 工具</strong> ：<code>glances</code> 是一个跨平台的监控工具，它通过一个统一的界面展示系统的多种信息，包括CPU、内存、磁盘、网络等。</li>
</ul>
<h5>5.2 系统维护</h5>
<p>系统维护是确保Ubuntu系统长期稳定运行的关键。</p>
<ul>
 <li><strong>定期更新</strong> ：使用<code>sudo apt update &amp;&amp; sudo apt upgrade</code>命令定期更新系统和已安装的软件包，以获取最新的安全补丁和功能改进。</li>
 <li><strong>清理无用文件</strong> ：使用<code>sudo apt autoremove</code>命令删除自动安装但现在不再需要的软件包，使用<code>sudo apt clean</code>命令清理本地仓库中下载的软件包文件。</li>
 <li><strong>检查磁盘错误</strong> ：虽然Ubuntu通常使用文件系统（如ext4）具有自我修复能力，但偶尔使用<code>fsck</code>命令检查磁盘错误仍然是一个好习惯。注意，<code>fsck</code>最好在单用户模式下运行。</li>
 <li><strong>备份重要数据</strong> ：定期备份重要数据以防止数据丢失。可以使用<code>rsync</code>、<code>tar</code>等工具手动备份，也可以使用专业的备份软件。</li>
</ul>
<h4>六、网络配置</h4>
<p>Ubuntu系统提供了灵活的网络配置选项，满足各种网络环境的需求。</p>
<ul>
 <li><strong>有线网络</strong> ：大多数情况下，Ubuntu能够自动识别并连接到有线网络。如果需要手动配置，可以通过<code>NetworkManager</code>的图形界面或编辑<code>/etc/network/interfaces</code>文件（在某些版本中可能是<code>netplan</code>配置）来实现。</li>
 <li><strong>无线网络</strong> ：Ubuntu同样支持无线网络，可以通过桌面环境的网络设置菜单或<code>nmcli</code>（NetworkManager的命令行接口）来配置无线网络连接。</li>
 <li><strong>静态IP地址</strong> ：如果需要为网络接口配置静态IP地址，可以通过编辑网络配置文件（如<code>/etc/network/interfaces</code>或Netplan配置）来实现。</li>
 <li><strong>防火墙</strong> ：Ubuntu使用<code>ufw</code>（Uncomplicated Firewall）作为默认的防火墙管理工具。可以使用<code>sudo ufw enable</code>命令启用防火墙，并通过<code>sudo ufw allow</code>和<code>sudo ufw deny</code>命令来设置规则。</li>
</ul>
<h4>七、安全设置</h4>
<p>安全是任何操作系统不可忽视的方面。Ubuntu提供了一系列的安全特性和工具来帮助用户保护系统安全。</p>
<ul>
 <li><strong>用户权限管理</strong> ：合理使用用户权限，避免使用root账户进行日常操作。可以通过<code>sudo</code>命令来以root权限执行命令。</li>
 <li><strong>密码策略</strong> ：设置强密码策略，包括密码长度、复杂度要求以及密码过期时间等。可以使用<code>pam_pwquality</code>模块来配置密码策略。</li>
 <li><strong>SSH安全</strong>：如果通过SSH远程管理Ubuntu系统，应确保SSH服务的安全。可以通过禁用root登录、限制SSH访问的IP地址、设置SSH密钥认证等方式来提高SSH的安全性。</li>
 <li><strong>安全更新</strong>：及时安装系统和软件包的安全更新，以修复已知的安全漏洞。</li>
 <li><strong>防病毒软件</strong>：虽然Linux系统相对于Windows和macOS来说较少受到病毒和恶意软件的攻击，但安装防病毒软件仍然是一个好习惯。Ubuntu社区提供了多种开源的防病毒软件供用户选择。</li>
</ul>
<h4>八、定制和个性化</h4>
<p>Ubuntu系统提供了丰富的定制和个性化选项，允许用户根据自己的喜好来定制系统界面和功能。</p>
<ul>
 <li><strong>主题和图标</strong>：Ubuntu允许用户更改桌面主题、窗口装饰、图标集等，以创建独特的桌面环境。用户可以从Ubuntu官方仓库或第三方源下载并安装主题和图标包。</li>
 <li><strong>桌面小工具</strong>：Ubuntu支持多种桌面小工具（如天气、日历、便签等），用户可以将它们添加到桌面上以快速访问常用功能。</li>
 <li><strong>键盘快捷键</strong>：Ubuntu允许用户自定义键盘快捷键，以提高工作效率。用户可以通过系统设置中的"键盘"选项来设置和修改快捷键。</li>
 <li><strong>终端自定义</strong>：对于喜欢在终端中工作的用户来说，Ubuntu允许他们自定义终端的外观和行为。例如，用户可以更改终端的颜色方案、字体大小、滚动行为等。</li>
</ul>
<p>结论
 <br>
 Ubuntu系统以其易用性、稳定性和丰富的功能而广受欢迎。从基础操作到高级配置，从桌面环境到服务器和虚拟化应用，Ubuntu都提供了全面的支持。通过不断学习和实践，用户可以充分发挥Ubuntu的潜力，构建出符合自己需求的个性化系统和解决方案。无论是对于个人用户还是企业和组织来说，Ubuntu都是一个值得推荐的操作系统选择。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141105984</p>]]></description><guid isPermaLink="false">/archives/3387922c-fa4b-4683-a03e-980e2672dcab</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Fri, 27 Sep 2024 05:48:52 GMT</pubDate></item><item><title><![CDATA[ECMAScript性能优化技巧]]></title><link>https://wiki.dgfygk.com/archives/c4e96790-b230-482d-b0b7-f3cc33b6cf23</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ECMAScript%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7&amp;url=/archives/c4e96790-b230-482d-b0b7-f3cc33b6cf23" width="1" height="1" alt="" style="opacity:0;">
<p>ECMAScript，作为JavaScript的标准化形式，其性能优化是前端开发中的重要环节。随着Web应用的日益复杂，优化ECMAScript代码的性能变得尤为重要。以下将详细探讨ECMAScript性能优化的技巧与陷阱，旨在帮助开发者编写更高效、更快速的代码。</p>
<h4>ECMAScript性能优化技巧</h4>
<h5>1. 减少DOM操作</h5>
<p>DOM操作是Web应用中常见的性能瓶颈之一。频繁的DOM操作会导致浏览器重绘和回流，从而消耗大量计算资源。优化DOM操作的方法包括：</p>
<ul>
 <li><strong>合并DOM操作</strong> ：将多个DOM操作合并为一个操作，或者使用<code>DocumentFragment</code>来批量插入DOM元素。这样可以减少重绘和回流的次数。</li>
 <li><strong>使用CSS类切换</strong>：当需要修改多个样式属性时，使用CSS类切换而不是直接修改样式属性。这样可以利用浏览器的优化机制，减少重绘和回流的开销。</li>
 <li><strong>避免在循环中修改样式</strong>：尽量在循环外部修改样式，或者在循环结束后一次性修改。</li>
</ul>
<h5>2. 使用事件委托</h5>
<p>事件委托是一种将事件监听器绑定到父元素上，利用事件冒泡机制来处理子元素事件的技术。这种方法可以减少事件处理函数的数量，提高性能。特别是在处理大量子元素时，事件委托的优势尤为明显。</p>
<h5>3. 节流与防抖</h5>
<p>在处理高频触发的事件（如<code>resize</code>、<code>scroll</code>）时，节流（Throttling）和防抖（Debouncing）是两种常用的优化技术。</p>
<ul>
 <li><strong>节流</strong>：限制事件处理函数的执行频率，确保在指定时间内只执行一次。</li>
 <li><strong>防抖</strong>：在事件被触发n秒后再执行回调，如果在这n秒内又被触发，则重新计时。</li>
</ul>
<p>这两种技术都可以有效减少事件处理函数的执行次数，提高页面性能。</p>
<h5>4. 减少HTTP请求</h5>
<p>HTTP请求是Web应用中常见的性能瓶颈之一。减少HTTP请求次数可以显著提高页面加载速度。优化方法包括：</p>
<ul>
 <li><strong>合并脚本和样式表</strong>：将多个脚本或样式表合并为单个文件，减少请求次数。</li>
 <li><strong>使用CSS Sprites</strong>：将多个小图标合并为一个图片文件，通过CSS背景定位来显示不同的图标，减少图片请求次数。</li>
 <li><strong>使用CDN</strong>：利用CDN（内容分发网络）加速资源加载，减少用户到服务器的距离和延迟。</li>
</ul>
<h5>5. 使用Web Workers</h5>
<p>对于涉及大量计算或耗时操作的任务，可以将其放入Web Workers中，在后台线程中运行，避免阻塞主线程，提高页面响应性能。Web Workers可以执行复杂的计算任务、处理大量数据或进行文件读写操作，而不会干扰UI的渲染和交互。</p>
<h5>6. 延迟加载非关键资源</h5>
<p>对于非关键的资源（如图片、脚本等），可以采用延迟加载的方式，在用户需要时再进行加载，减少页面初始加载时间，提高用户体验。</p>
<h5>7. 压缩和合并JavaScript文件</h5>
<p>压缩JavaScript文件可以减小文件体积，加快加载速度。同时，将多个JavaScript文件合并为一个文件也可以减少HTTP请求次数。现代构建工具（如Webpack、Rollup等）都支持JavaScript文件的压缩和合并。</p>
<h5>8. 利用ECMAScript SIMD</h5>
<p>SIMD（单指令多数据）是一种可以在单个指令中处理多个数据点的技术。ECMAScript SIMD API允许开发者在JavaScript中利用SIMD指令集进行高性能计算。虽然目前并非所有主流浏览器都支持SIMD，但对于需要高性能计算的项目来说，SIMD是一个值得关注的技术方向。</p>
<h4>ECMAScript性能陷阱</h4>
<h5>1. 使用eval或Function构造函数</h5>
<p><code>eval</code>函数和<code>Function</code>构造函数都可以动态执行JavaScript代码，但它们的代价是非常昂贵的。每次调用时，都需要脚本引擎将源代码转换为可执行代码，这会导致性能下降。此外，<code>eval</code>还可以执行任何传递给它的代码，存在安全风险。因此，应尽量避免使用<code>eval</code>和<code>Function</code>构造函数。</p>
<h5>2. 使用with语句</h5>
<p><code>with</code>语句可以改变代码块中的作用域链，使得在编译时无法确定变量的作用域。这会导致脚本引擎在运行时进行额外的查找操作，降低性能。因此，应尽量避免使用<code>with</code>语句。</p>
<h5>3. 在性能关键函数中使用try-catch-finally</h5>
<p><code>try-catch-finally</code>语句在运行时每次都会在当前作用域创建一个新的变量，用于分配语句执行的异常。这会增加额外的性能开销。在性能要求较高的函数中，应尽量避免使用<code>try-catch-finally</code>语句，或者将其放在循环体外部。</p>
<h5>4. 过度使用全局变量</h5>
<p>全局变量在脚本的生命周期内都存在，且其作用域链较长。在函数或其他作用域中频繁使用全局变量会导致脚本引擎在查找变量时遍历较长的作用域链，降低性能。因此，应尽量减少全局变量的使用，并通过模块化、闭包等技术来封装和保护变量。</p>
<h5>5. 忽视数据类型和算法复杂度</h5>
<p>JavaScript是动态类型语言，但在性能敏感的应用中，仍应注意数据类型的选择。例如，在处理大量数据时，使用<code>Number</code>而非<code>String</code>进行数学计算会更高效。此外，算法的复杂度直接影响性能。一个时间复杂度为O(n^2)的算法在处理大数据集时可能会非常慢，而一个O(n log n)或O(n)的算法则可能快得多。因此，在选择算法时应尽量采用时间复杂度较低的方案。</p>
<h5>6. 滥用闭包</h5>
<p>闭包是JavaScript中一个强大的特性，它允许函数访问并操作函数外部的变量。然而，闭包也会带来额外的性能开销，因为它需要维护一个额外的词法环境。如果闭包被大量使用或长时间保留，它们可能会占用大量内存并导致内存泄漏。因此，在不需要保持外部变量状态的情况下，应避免使用闭包。</p>
<h5>7. 忽略垃圾回收机制</h5>
<p>JavaScript使用自动垃圾回收机制来管理内存。然而，开发者仍需要了解垃圾回收的工作原理，以避免创建过多的临时对象或引用循环，这些都会增加垃圾回收的负担并降低性能。例如，使用<code>setTimeout</code>或<code>setInterval</code>时，如果回调函数引用了外部变量并且这些变量在回调执行完毕后不再需要，应确保这些变量在回调外部被释放或置为<code>null</code>，以避免不必要的内存占用。</p>
<h5>8. 忽视缓存机制</h5>
<p>合理利用缓存可以显著提高性能。在JavaScript中，可以通过多种方式实现缓存，如使用对象或Map来存储计算结果、使用localStorage或sessionStorage来持久化数据等。然而，滥用缓存也可能导致性能问题。例如，如果缓存的数据过于庞大或更新频率过高，可能会导致内存占用过高或缓存失效问题。因此，在使用缓存时应权衡其利弊，并根据实际需求进行调整。</p>
<h5>9. 忽视浏览器优化</h5>
<p>现代浏览器提供了许多内置的优化机制，如JIT（即时编译）、V8引擎的隐藏类优化等。然而，这些优化机制并不是万能的，它们也有自己的局限性和适用场景。因此，开发者应了解并充分利用这些优化机制，同时也要关注浏览器的发展动态和最佳实践，以便及时调整和优化自己的代码。</p>
<h5>10. 忽视网络性能</h5>
<p>ECMAScript性能优化不仅仅局限于代码本身，还包括与服务器之间的数据传输。网络延迟和带宽限制都可能影响Web应用的性能。因此，在优化ECMAScript代码时，也应考虑网络性能的优化。例如，通过减少HTTP请求次数、使用CDN加速资源加载、启用HTTP/2和HTTP/3等新技术来提高网络传输效率。</p>
<h4>总结</h4>
<p>ECMAScript性能优化是一个复杂而细致的过程，需要开发者从多个角度进行考虑和优化。通过减少DOM操作、使用事件委托、节流与防抖、减少HTTP请求、使用Web Workers等技巧，可以显著提高JavaScript代码的性能。同时，也要注意避免一些常见的性能陷阱，如滥用eval、with语句、全局变量等。此外，了解并充分利用浏览器的优化机制和最佳实践也是提高性能的关键。总之，只有不断学习和实践，才能编写出既高效又可靠的JavaScript代码。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141205791</p>]]></description><guid isPermaLink="false">/archives/c4e96790-b230-482d-b0b7-f3cc33b6cf23</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 05:48:23 GMT</pubDate></item><item><title><![CDATA[Linux系统性能调优技巧]]></title><link>https://wiki.dgfygk.com/archives/38a1e702-7483-4635-a840-05551ebe8204</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E7%B3%BB%E7%BB%9F%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E6%8A%80%E5%B7%A7&amp;url=/archives/38a1e702-7483-4635-a840-05551ebe8204" width="1" height="1" alt="" style="opacity:0;">
<p>Linux系统性能调优是一个复杂而细致的过程，它涉及到硬件、软件、配置、监控和调优策略等多个方面。以下将详细阐述Linux系统性能调优的技巧，分为几个关键部分进行说明。</p>
<h4>一、硬件层面的优化</h4>
<ol>
 <li>
  <p><strong>CPU优化</strong></p>
 </li>
 <li>
  <p><strong>选择适合的CPU</strong>：根据应用需求选择多核、高频的CPU，以满足高并发和计算密集型任务的需求。</p>
 </li>
 <li>
  <p><strong>CPU缓存优化</strong>：确保CPU缓存（L1、L2、L3）被有效利用，减少CPU访问内存的次数，提高计算效率。</p>
 </li>
 <li>
  <p><strong>CPU频率调整</strong> ：在需要时，可以使用如<code>cpufreq</code>等工具动态调整CPU频率，以平衡功耗和性能。</p>
 </li>
 <li>
  <p><strong>内存优化</strong></p>
 </li>
 <li>
  <p><strong>增加内存容量</strong>：根据系统负载和应用需求增加物理内存，减少交换（swap）的使用，提高系统响应速度。</p>
 </li>
 <li>
  <p><strong>内存压缩</strong>：使用内存压缩技术（如KSM、zRAM等），提高内存利用率，减少内存占用的空间。</p>
 </li>
 <li>
  <p><strong>内存清理</strong>：定期清理不再使用的内存页面，减少内存碎片，提高内存分配效率。</p>
 </li>
 <li>
  <p><strong>存储优化</strong></p>
 </li>
 <li>
  <p><strong>使用SSD</strong>：将系统盘和数据盘替换为SSD，显著提高I/O性能。</p>
 </li>
 <li>
  <p><strong>RAID配置</strong>：使用RAID技术（如RAID 5、RAID 10）提高磁盘的可靠性和读写性能。</p>
 </li>
 <li>
  <p><strong>文件系统优化</strong>：选择合适的文件系统（如Ext4、XFS），并配置合适的挂载选项（如noatime、nodiratime），减少不必要的文件系统操作。</p>
 </li>
 <li>
  <p><strong>网络优化</strong></p>
 </li>
 <li>
  <p><strong>高性能网卡</strong>：选用千兆或万兆网卡，提高网络带宽和吞吐量。</p>
 </li>
 <li>
  <p><strong>网络QoS</strong>：配置网络QoS策略，合理分配带宽资源，确保关键应用的网络性能。</p>
 </li>
 <li>
  <p><strong>网络协议优化</strong>：优化TCP/IP协议栈参数，如调整TCP缓冲区大小、TCP窗口缩放等，提高网络传输效率。</p>
 </li>
</ol>
<h4>二、软件层面的优化</h4>
<ol>
 <li>
  <p><strong>内核参数调整</strong></p>
 </li>
 <li>
  <p><strong>禁用不必要的模块</strong>：去除不需要的内核模块，减少内核体积，提高启动速度和占用内存的效率。</p>
 </li>
 <li>
  <p><strong>启用适当的调度器</strong>：根据系统特点选择合适的进程调度器（如CFS），优化任务分配和响应速度。</p>
 </li>
 <li>
  <p><strong>调整内核参数</strong> ：通过修改<code>/sys</code>、<code>/proc</code>等目录下的内核参数，如文件系统缓冲区大小、内核共享内存等，提高系统IO性能和内存利用率。</p>
 </li>
 <li>
  <p><strong>文件系统优化</strong></p>
 </li>
 <li>
  <p><strong>合理的挂载选项</strong>：通过调整文件系统的挂载选项（如noatime、nodiratime），减少不必要的文件系统操作。</p>
 </li>
 <li>
  <p><strong>碎片整理</strong>：定期使用工具进行文件系统碎片整理，提高文件读写效率。</p>
 </li>
 <li>
  <p><strong>使用合适的文件系统</strong>：根据应用场景选择最合适的文件系统（如XFS适用于小文件数量较多的场景，Ext4适用于大文件场景）。</p>
 </li>
 <li>
  <p><strong>内存管理优化</strong></p>
 </li>
 <li>
  <p><strong>动态分配内存</strong>：合理分配物理内存和交换空间，避免过度使用交换分区。</p>
 </li>
 <li>
  <p><strong>使用LRU算法</strong>：利用LRU（最近最少使用）算法管理内存，保留最常用的数据在内存中，减少磁盘读写。</p>
 </li>
 <li>
  <p><strong>网络优化</strong></p>
 </li>
 <li>
  <p><strong>调整网卡缓冲区大小</strong>：根据网络流量和延迟情况，适当调整网卡缓冲区大小，提高网络吞吐量和响应速度。</p>
 </li>
 <li>
  <p><strong>网络协议栈优化</strong>：优化TCP/IP协议栈的参数设置，如TCP窗口大小、TCP连接超时时间等，提高网络传输效率。</p>
 </li>
 <li>
  <p><strong>进程和线程优化</strong></p>
 </li>
 <li>
  <p><strong>合理调整进程优先级</strong>：通过调整进程优先级，确保关键任务得到及时响应。</p>
 </li>
 <li>
  <p><strong>使用多线程编程</strong>：利用多线程模型充分利用多核CPU的性能，提高系统的并发处理能力。</p>
 </li>
 <li>
  <p><strong>减少上下文切换</strong>：通过优化代码和配置，减少不必要的进程和线程上下文切换，降低系统开销。</p>
 </li>
</ol>
<h4>三、监控与调优策略</h4>
<ol>
 <li>
  <p><strong>系统监控</strong></p>
 </li>
 <li>
  <p><strong>使用监控工具</strong> ：利用<code>top</code>、<code>vmstat</code>、<code>iostat</code>、<code>sar</code>等系统监控工具收集系统性能数据和指标。</p>
 </li>
 <li>
  <p><strong>查看系统日志</strong> ：定期检查系统日志文件（如<code>/var/log/messages</code>、<code>/var/log/syslog</code>），寻找异常或错误信息。</p>
 </li>
 <li>
  <p><strong>性能分析</strong> ：使用<code>perf</code>、<code>strace</code>等工具进行进程级别的性能分析，定位性能瓶颈。</p>
 </li>
 <li>
  <p><strong>调优策略</strong></p>
 </li>
 <li>
  <p><strong>定期评估</strong>：定期对系统性能进行评估，识别性能瓶颈和优化空间。</p>
 </li>
 <li>
  <p><strong>逐步调优</strong>：根据监控结果和分析结果，逐步调整系统配置和参数设置，避免一次性做出大量更改导致系统不稳定。</p>
 </li>
 <li>
  <p>**持续### 调优策略</p>
 </li>
 <li>
  <p><strong>性能基准测试</strong></p>
 </li>
 <li>
  <p><strong>建立基准</strong>：在系统调优前后进行基准测试，以量化性能改进的效果。可以使用专门的性能测试工具（如Sysbench、JMeter、LoadRunner等）来模拟真实负载并收集性能指标。</p>
 </li>
 <li>
  <p><strong>对比分析</strong>：对比调优前后的性能指标，如响应时间、吞吐量、CPU使用率、内存占用等，评估调优效果。</p>
 </li>
 <li>
  <p><strong>资源限制与隔离</strong></p>
 </li>
 <li>
  <p><strong>设置cgroups</strong>：利用Linux的cgroups（控制组）功能，对系统资源进行细粒度的管理和限制。可以针对特定的进程或用户组设置CPU、内存、IO等资源的使用上限，以防止某个进程或用户占用过多资源导致系统整体性能下降。</p>
 </li>
 <li>
  <p><strong>使用namespaces</strong>：通过Linux的namespaces功能，实现进程间资源的隔离。这有助于在多租户环境下保护各个租户的资源不被其他租户干扰。</p>
 </li>
 <li>
  <p><strong>缓存与缓冲策略</strong></p>
 </li>
 <li>
  <p><strong>优化页面缓存</strong>：Linux内核使用页面缓存来缓存文件系统的读写操作，以减少对磁盘的访问。通过调整内核参数（如vm.dirty_ratio、vm.dirty_background_ratio等），可以优化页面缓存的使用，提高文件系统的性能。</p>
 </li>
 <li>
  <p><strong>应用级缓存</strong>：在应用程序层面实现缓存机制，如使用Redis、Memcached等内存数据库来缓存热点数据，减少对数据库的访问压力。</p>
 </li>
 <li>
  <p><strong>并发与异步处理</strong></p>
 </li>
 <li>
  <p><strong>多线程与多进程</strong>：根据应用的特点和需求，合理设计多线程或多进程架构，以充分利用多核CPU的性能。同时，注意线程或进程间的同步与通信机制，避免死锁和竞态条件。</p>
 </li>
 <li>
  <p><strong>异步编程</strong>：采用异步编程模型，如使用Node.js的异步IO、Java的Future/CompletableFuture等，减少阻塞操作，提高系统的并发处理能力和响应速度。</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>：定期更新系统和软件的安全补丁，关闭不必要的服务和端口，配置防火墙和SELinux等安全机制，以提高系统的安全性。</p>
 </li>
 <li>
  <p><strong>性能与安全平衡</strong>：在优化系统性能的同时，注意保持系统的安全性。避免因为追求性能而牺牲安全性，如禁用关键的安全特性或配置不安全的参数。</p>
 </li>
</ol>
<h4>四、自动化与持续调优</h4>
<ol>
 <li>
  <p><strong>自动化监控与告警</strong></p>
 </li>
 <li>
  <p><strong>使用监控工具</strong>：部署自动化监控工具（如Prometheus、Grafana、Zabbix等），实时监控系统的各项性能指标。</p>
 </li>
 <li>
  <p><strong>设置告警规则</strong>：根据业务需求设置性能告警规则，当系统性能低于预设阈值时自动触发告警通知，以便及时响应和处理。</p>
 </li>
 <li>
  <p><strong>持续集成与持续部署（CI/CD）</strong></p>
 </li>
 <li>
  <p><strong>自动化测试</strong>：将性能测试纳入CI/CD流程中，确保每次代码提交或版本更新后都进行性能测试，以及时发现和修复性能问题。</p>
 </li>
 <li>
  <p><strong>自动化部署</strong>：使用自动化部署工具（如Jenkins、GitLab CI/CD等）实现代码的自动化部署，减少人为错误并提高部署效率。</p>
 </li>
 <li>
  <p><strong>性能趋势分析</strong></p>
 </li>
 <li>
  <p><strong>收集历史数据</strong>：长期收集系统的性能数据，并存储到数据库中以便后续分析。</p>
 </li>
 <li>
  <p><strong>趋势分析</strong>：利用数据分析工具（如Pandas、R语言等）对性能数据进行趋势分析，识别性能变化的规律和原因。</p>
 </li>
</ol>
<h4>五、总结</h4>
<p>Linux系统性能调优是一个复杂而持续的过程，需要综合考虑硬件、软件、配置、监控和调优策略等多个方面。通过优化硬件资源、调整系统配置、监控系统性能、实施代码优化和自动化部署等措施，可以显著提高Linux系统的性能和稳定性。然而，需要注意的是，性能调优并非一劳永逸的过程，随着业务的发展和系统的变化，需要定期评估和调整优化策略，以确保系统始终保持在最佳状态。同时，保持对新技术和新工具的关注和学习，也是提高Linux系统性能调优能力的重要途径。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141205704</p>]]></description><guid isPermaLink="false">/archives/38a1e702-7483-4635-a840-05551ebe8204</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Fri, 27 Sep 2024 05:47:53 GMT</pubDate></item><item><title><![CDATA[Sublime Text 文本编辑器]]></title><link>https://wiki.dgfygk.com/archives/7eb5e89a-3f79-4920-8004-2caf2d51fe30</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Sublime%20Text%20%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E5%99%A8&amp;url=/archives/7eb5e89a-3f79-4920-8004-2caf2d51fe30" width="1" height="1" alt="" style="opacity:0;">
<p>Sublime Text作为一款功能强大且高度可定制的文本编辑器，其丰富的快捷键设置极大地提高了编码和文本编辑的效率。在撰写一篇关于Sublime Text常用快捷键的文章时，我们可以从文件操作、编辑操作、导航操作、代码格式化、多光标与多行编辑、视图与窗口管理、以及插件与扩展等多个方面来详细阐述。以下是一篇不少于2000字的文章概要：</p>
<hr>
<h4>Sublime Text常用快捷键全解析</h4>
<h5>一、引言</h5>
<p>Sublime Text以其流畅的界面、强大的功能和丰富的插件生态，赢得了众多开发者和写作者的青睐。在众多提升编辑效率的工具中，快捷键无疑是最为直接和有效的手段之一。本文将详细介绍Sublime Text中的常用快捷键，帮助读者更好地掌握这款编辑器，提高工作效率。</p>
<h5>二、文件操作快捷键</h5>
<p>文件操作是任何文本编辑器的基础功能，Sublime Text也不例外。以下是一些常用的文件操作快捷键：</p>
<ul>
 <li><strong>新建文件</strong> ：<code>Ctrl + N</code>（Windows/Linux）或<code>Cmd + N</code>（Mac）</li>
 <li><strong>打开文件</strong> ：<code>Ctrl + O</code>（Windows/Linux）或<code>Cmd + O</code>（Mac）</li>
 <li><strong>保存文件</strong> ：<code>Ctrl + S</code>（Windows/Linux）或<code>Cmd + S</code>（Mac）</li>
 <li><strong>另存为</strong> ：<code>Ctrl + Shift + S</code>（Windows/Linux）或<code>Cmd + Shift + S</code>（Mac）</li>
 <li><strong>关闭文件</strong> ：<code>Ctrl + W</code>（Windows/Linux）或<code>Cmd + W</code>（Mac）</li>
 <li><strong>关闭所有文件</strong> ：<code>Ctrl + Shift + W</code>（Windows/Linux）或<code>Cmd + Option + W</code>（Mac）</li>
</ul>
<p>这些快捷键帮助用户快速进行文件的新建、打开、保存和关闭等操作，极大地方便了日常编辑工作。</p>
<h5>三、编辑操作快捷键</h5>
<p>编辑操作是文本编辑器的核心功能，Sublime Text提供了众多编辑快捷键，以满足用户的各种需求：</p>
<ul>
 <li><strong>撤销</strong> ：<code>Ctrl + Z</code>（Windows/Linux）或<code>Cmd + Z</code>（Mac）</li>
 <li><strong>重做</strong> ：<code>Ctrl + Y</code>（Windows/Linux）或<code>Cmd + Shift + Z</code>（Mac）</li>
 <li><strong>复制</strong> ：<code>Ctrl + C</code>（Windows/Linux）或<code>Cmd + C</code>（Mac）</li>
 <li><strong>剪切</strong> ：<code>Ctrl + X</code>（Windows/Linux）或<code>Cmd + X</code>（Mac）</li>
 <li><strong>粘贴</strong> ：<code>Ctrl + V</code>（Windows/Linux）或<code>Cmd + V</code>（Mac）</li>
 <li><strong>删除整行</strong> ：<code>Ctrl + Shift + K</code>（Windows/Linux）或<code>Cmd + Shift + K</code>（Mac）</li>
 <li><strong>选中整行</strong> ：<code>Ctrl + L</code>（Windows/Linux）或<code>Cmd + L</code>（Mac）</li>
 <li><strong>选中单词</strong> ：<code>Ctrl + D</code>（Windows/Linux）或<code>Cmd + D</code>（Mac），连续按可选中相同单词</li>
 <li><strong>注释/取消注释</strong> ：<code>Ctrl + /</code>（Windows/Linux）或<code>Cmd + /</code>（Mac）</li>
</ul>
<p>此外，Sublime Text还支持多光标编辑，通过<code>Ctrl + Click</code>（Windows/Linux）或<code>Cmd + Click</code>（Mac）可以在多个位置同时插入光标，进行同时编辑。这一功能在批量修改代码或文本时尤为有用。</p>
<h5>四、导航操作快捷键</h5>
<p>在编写长文档或复杂代码时，快速导航到指定位置是提高效率的关键。Sublime Text提供了多种导航快捷键：</p>
<ul>
 <li><strong>跳转到指定行</strong> ：<code>Ctrl + G</code>（Windows/Linux）或<code>Cmd + G</code>（Mac）</li>
 <li><strong>跳转到指定字符</strong>（注：此功能可能需要结合其他工具或插件实现）</li>
 <li><strong>快速查找</strong> ：<code>Ctrl + F</code>（Windows/Linux）或<code>Cmd + F</code>（Mac）</li>
 <li><strong>查找并替换</strong> ：<code>Ctrl + H</code>（Windows/Linux）或<code>Cmd + Option + F</code>（Mac）</li>
 <li><strong>在项目中查找</strong> ：<code>Ctrl + Shift + F</code>（Windows/Linux）或<code>Cmd + Shift + F</code>（Mac）</li>
 <li><strong>跳转到函数或定义</strong> （注：此功能可能需要安装特定插件，如<code>SublimeLinter</code>、<code>EasyMotion</code>等）</li>
</ul>
<h5>五、代码格式化快捷键</h5>
<p>代码格式化是保持代码整洁、易读的重要手段。Sublime Text虽然内置了一定的代码格式化功能，但更多时候需要依赖插件来实现。不过，用户可以通过<code>Ctrl + Shift + P</code>（Windows/Linux）或<code>Cmd + Shift + P</code>（Mac）打开命令面板，然后输入"Format"来查找并执行相关的格式化命令。</p>
<h5>六、多光标与多行编辑快捷键</h5>
<p>Sublime Text的多光标与多行编辑功能是其一大亮点，这些功能极大地增强了编辑器的灵活性和效率。以下是一些关于多光标与多行编辑的常用快捷键和技巧：</p>
<ul>
 <li>
  <p><strong>通过鼠标</strong> ：如上所述，使用<code>Ctrl + Click</code>（Windows/Linux）或<code>Cmd + Click</code>（Mac）可以在多个位置同时插入光标。</p>
 </li>
 <li>
  <p><strong>通过键盘</strong> ：<code>Alt</code>键（Windows/Linux）或<code>Option</code>键（Mac）结合方向键可以在文本中上下左右移动并插入多个光标。例如，按下<code>Alt</code>（或<code>Option</code>）和向下箭头键，可以在每行的同一列位置插入光标。</p>
 </li>
 <li>
  <p><strong>多行编辑（列模式）</strong>：</p>
 </li>
 <li>
  <p>激活列模式：按下<code>Alt + Shift + 方向键</code>（Windows/Linux）或<code>Option + Shift + 方向键</code>（Mac）可以选择多列文本，进入列编辑模式。这时，你可以像编辑一行文本一样编辑多列文本。</p>
 </li>
 <li>
  <p>退出列模式：按下<code>Esc</code>键即可退出列编辑模式。</p>
 </li>
 <li>
  <p><strong>多光标复制粘贴</strong>：</p>
 </li>
 <li>
  <p>在多光标模式下，你可以对每个光标位置进行独立的复制、剪切和粘贴操作。这意味着你可以一次性在不同位置粘贴相同或不同的内容。</p>
 </li>
 <li>
  <p><strong>多光标文本输入</strong>：</p>
 </li>
 <li>
  <p>一旦激活了多光标，你就可以开始输入文本了。输入的文本会同时出现在所有光标所在的位置。</p>
 </li>
</ul>
<h5>七、视图与窗口管理快捷键</h5>
<p>Sublime Text的视图和窗口管理功能允许你灵活地控制编辑器界面，提高工作效率。</p>
<ul>
 <li>
  <p><strong>分屏显示</strong>：</p>
 </li>
 <li>
  <p>垂直分屏：<code>Alt + Shift + 2</code>（Windows/Linux）或<code>Cmd + Option + 2</code>（Mac）</p>
 </li>
 <li>
  <p>水平分屏：<code>Alt + Shift + 1</code>（Windows/Linux）或<code>Cmd + Option + 1</code>（Mac）</p>
 </li>
 <li>
  <p>切换分屏中的文件：<code>Ctrl + 数字键</code>（Windows/Linux）或<code>Cmd + 数字键</code>（Mac），其中数字键代表分屏中的文件顺序。</p>
 </li>
 <li>
  <p><strong>视图控制</strong>：</p>
 </li>
 <li>
  <p>放大/缩小字体：<code>Ctrl + +</code>（放大）/<code>Ctrl + -</code>（缩小）（Windows/Linux）或<code>Cmd + +</code>（放大）/<code>Cmd + -</code>（缩小）（Mac）</p>
 </li>
 <li>
  <p>切换全屏模式：<code>F11</code>（Windows/Linux）或<code>Cmd + Ctrl + F</code>（Mac）</p>
 </li>
 <li>
  <p>显示/隐藏侧边栏：<code>Ctrl + K, Ctrl + B</code>（Windows/Linux）或<code>Cmd + K, Cmd + B</code>（Mac）</p>
 </li>
 <li>
  <p><strong>窗口管理</strong>：</p>
 </li>
 <li>
  <p>新建窗口：<code>Ctrl + Shift + N</code>（Windows/Linux）或<code>Cmd + Shift + N</code>（Mac）</p>
 </li>
 <li>
  <p>关闭当前窗口：<code>Ctrl + Shift + W</code>（Windows/Linux）或<code>Cmd + W</code>（如果当前窗口是唯一的，则需要<code>Cmd + Q</code>来退出程序）（Mac）</p>
 </li>
</ul>
<h5>八、插件与扩展</h5>
<p>Sublime Text的强大之处不仅在于其内置的功能，更在于其丰富的插件生态系统。通过安装插件，你可以为Sublime Text添加几乎任何你想要的功能。</p>
<ul>
 <li>
  <p><strong>安装插件</strong>：</p>
 </li>
 <li>
  <p>使用Package Control是安装插件的最简单方法。首先，通过快捷键<code>Ctrl + Shift + P</code>（Windows/Linux）或<code>Cmd + Shift + P</code>（Mac）打开命令面板，然后输入"Install Package"并选择"Package Control: Install Package"选项。接着，在搜索框中输入你想要的插件名称并安装即可。</p>
 </li>
 <li>
  <p><strong>推荐插件</strong>：</p>
 </li>
 <li>
  <p><strong>SublimeLinter</strong>：一个代码检查插件，可以帮助你实时发现代码中的错误和潜在问题。</p>
 </li>
 <li>
  <p><strong>Emmet</strong>：一个快速编写HTML/CSS代码的插件，通过简短的缩写来生成复杂的代码结构。</p>
 </li>
 <li>
  <p><strong>GitGutter</strong>：一个显示Git变更差异的插件，帮助你在编辑器中直接查看代码的提交历史、差异等信息。</p>
 </li>
 <li>
  <p><strong>SideBarEnhancements</strong>：增强了侧边栏的功能，包括文件预览、批量重命名等。</p>
 </li>
</ul>
<h5>九、结语</h5>
<p>Sublime Text的快捷键和插件生态系统为用户提供了极大的灵活性和效率提升。通过熟练掌握常用快捷键和安装合适的插件，你可以将Sublime Text打造成一个完全符合自己需求的强大编辑器。希望本文的介绍能够帮助你更好地利用Sublime Text，提高你的工作和学习效率。</p>
<p>在探索Sublime Text的过程中，不妨多尝试一些新的快捷键和插件，也许你会发现一些之前未曾注意到的强大功能。同时，也欢迎你分享自己的使用经验和技巧，与更多Sublime Text用户交流学习。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141129561</p>]]></description><guid isPermaLink="false">/archives/7eb5e89a-3f79-4920-8004-2caf2d51fe30</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 04:00:03 GMT</pubDate></item><item><title><![CDATA[Ubuntu系统的基础操作和使用]]></title><link>https://wiki.dgfygk.com/archives/abae45da-075e-4bad-944f-5f3da8fd4bf2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Ubuntu%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%9F%BA%E7%A1%80%E6%93%8D%E4%BD%9C%E5%92%8C%E4%BD%BF%E7%94%A8&amp;url=/archives/abae45da-075e-4bad-944f-5f3da8fd4bf2" width="1" height="1" alt="" style="opacity:0;">
<p>Ubuntu系统是一种基于Linux内核的开源操作系统，由南非的商业人士马克·舍特尔沃斯创立，其目标是提供一个易于使用、免费且开放的操作系统。Ubuntu系统因其安全性、稳定性和丰富的应用程序而广受欢迎，尤其适合从Windows系统过渡到Linux系统的用户。以下将详细介绍Ubuntu系统的基础操作和使用，内容涵盖界面介绍、文件管理、系统设置、软件管理以及高级功能等方面。</p>
<h4>一、界面介绍</h4>
<p>Ubuntu系统的桌面环境通常是基于GNOME桌面环境，界面直观且易于操作。桌面环境主要包括以下几个元素：</p>
<ul>
 <li><strong>桌面</strong>：用户可以在桌面上放置文件和文件夹，类似于Windows系统的桌面。</li>
 <li><strong>任务栏</strong>：位于屏幕顶部的横条，通常包含应用程序启动器、系统菜单、通知区域等。</li>
 <li><strong>应用程序启动器</strong>：位于任务栏左侧，包含常用的应用程序图标，用户可以从这里启动应用程序。</li>
 <li><strong>通知区域</strong>：位于任务栏右侧，包含系统通知、网络连接、音量控制等快捷操作。</li>
 <li><strong>应用程序菜单</strong>：在桌面的左上角，通常是一个"Activities"按钮，点击后会弹出应用程序菜单，用户可以在这里搜索并启动安装的应用程序。</li>
</ul>
<h4>二、文件管理</h4>
<p>在Ubuntu系统中，文件管理主要通过文件管理器（如Nautilus）进行。用户可以使用文件管理器浏览、创建、复制、删除文件和文件夹。</p>
<ul>
 <li><strong>浏览文件和文件夹</strong>：打开文件管理器，可以通过点击左侧的目录树或右侧显示的文件夹来浏览文件和文件夹。</li>
 <li><strong>创建文件和文件夹</strong>：在文件管理器中右键点击空白处，选择"创建文件"或"创建文件夹"来创建新文件或文件夹。也可以通过菜单栏中的相应选项来创建。</li>
 <li><strong>复制文件和文件夹</strong>：选择要复制的文件或文件夹，右键点击选择"复制”，然后移动到目标位置右键点击选择"粘贴”。也可以使用快捷键Ctrl+C和Ctrl+V来复制和粘贴。</li>
 <li><strong>删除文件和文件夹</strong>：选择要删除的文件或文件夹，右键点击选择"删除”，或者直接按下Delete键。系统会确认是否要永久删除文件或将其移至垃圾箱。注意，删除文件或文件夹后无法恢复，所以请谨慎操作。</li>
</ul>
<p>此外，Ubuntu系统还支持通过命令行进行文件管理，常用的命令包括<code>ls</code>（列出目录内容）、<code>cd</code>（切换目录）、<code>pwd</code>（显示当前目录路径）、<code>mkdir</code>（创建目录）、<code>rm</code>（删除文件或目录）等。</p>
<h4>三、系统设置</h4>
<p>Ubuntu系统提供了丰富的系统设置选项，用户可以根据自己的需求进行个性化设置。</p>
<ul>
 <li><strong>显示设置</strong>：可以调整屏幕分辨率、亮度、色彩等显示参数。</li>
 <li><strong>声音设置</strong>：可以调整音量大小、选择输出设备等声音参数。</li>
 <li><strong>电源管理</strong>：可以设置电源计划、屏幕超时等电源管理选项。</li>
 <li><strong>网络设置</strong>：可以配置网络连接，包括有线网络、无线网络和VPN等。</li>
 <li><strong>用户账户</strong>：可以管理用户账户，包括添加、删除用户账户以及设置用户权限等。</li>
</ul>
<h4>四、软件管理</h4>
<p>Ubuntu系统拥有丰富的应用程序和工具，用户可以通过软件中心或命令行来安装和卸载应用程序。</p>
<ul>
 <li><strong>软件中心</strong>：打开"Ubuntu 软件"应用程序，用户可以搜索想要安装的应用程序，然后点击安装按钮即可。软件中心还提供了已安装应用程序的卸载功能，用户可以在"已安装"选项卡中找到想要卸载的应用程序，然后点击"删除"按钮即可。</li>
 <li><strong>命令行安装</strong> ：用户也可以通过命令行使用<code>apt</code>命令来安装应用程序。首先，需要更新软件包列表，使用命令<code>sudo apt update</code>。然后，使用命令<code>sudo apt install 软件包名</code>来安装应用程序。例如，要安装Firefox浏览器，可以使用命令<code>sudo apt install firefox</code>。</li>
 <li><strong>卸载应用程序</strong> ：同样地，用户可以通过命令行使用<code>apt</code>命令来卸载应用程序。使用命令<code>sudo apt remove 软件包名</code>来卸载应用程序，如果想要同时删除应用程序及其配置文件，可以使用命令<code>sudo apt purge 软件包名</code>。</li>
</ul>
<h4>五、高级功能</h4>
<p>Ubuntu系统还提供了许多高级功能，以满足用户的不同需求。</p>
<ul>
 <li><strong>终端的使用</strong>：Ubuntu系统内置了终端（Terminal），用户可以通过终端执行各种命令来管理系统和应用程序。终端的使用需要一定的Linux命令基础，但掌握后可以大大提高工作效率。</li>
 <li><strong>系统更新和升级</strong> ：Ubuntu系统支持在线更新和升级，用户可以通过软件中心或命令行来更新系统和应用程序。使用命令<code>sudo apt update</code>来更新软件包列表，使用命令<code>sudo apt upgrade</code>来升级已安装的软件包。</li>
 <li><strong>软件源的添加和管理</strong>：Ubuntu系统使用软件源（Software Sources）来管理应用程序的安装包。用户可以根据自己的需求添加或删除软件源，以便安装更多的应用程序。</li>
 <li><strong>用户管理和权限设置</strong>：Ubuntu系统支持多用户管理，用户可以根据自己的需求添加、删除用户账户，并设置用户权限。通过命令行或系统设置界面，用户可以轻松管理用户账户和权限。</li>
</ul>
<h4>六、总结</h4>
<p>Ubuntu系统的基础操作和使用不仅限于上述提到的内容，它还拥有强大的社区支持、灵活的自定义选项以及与其他开源软件和服务的无缝集成能力。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141180622</p>]]></description><guid isPermaLink="false">/archives/abae45da-075e-4bad-944f-5f3da8fd4bf2</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Fri, 27 Sep 2024 03:59:44 GMT</pubDate></item><item><title><![CDATA[XMind在软件需求分析中的应用技巧]]></title><link>https://wiki.dgfygk.com/archives/2f10a89d-c877-412a-8042-dc99088db1ee</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=XMind%E5%9C%A8%E8%BD%AF%E4%BB%B6%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%E6%8A%80%E5%B7%A7&amp;url=/archives/2f10a89d-c877-412a-8042-dc99088db1ee" width="1" height="1" alt="" style="opacity:0;">
<h2>XMind在软件需求分析中的应用技巧</h2>
<h3>引言</h3>
<p>在软件开发过程中，需求分析是至关重要的一环。它不仅决定了软件最终的功能和性能，还直接影响到开发周期、成本以及用户体验。因此，如何高效、准确地完成需求分析成为软件开发团队面临的重要挑战。XMind作为一款功能强大的思维导图和头脑风暴软件，凭借其灵活、自由、多样化的设计特点，在软件需求分析中发挥着重要作用。本文将详细探讨XMind在软件需求分析中的应用技巧，帮助读者更好地利用这一工具提升需求分析的效率和质量。</p>
<h3>1. 需求分析的基本流程</h3>
<p>在进行具体的XMind应用技巧探讨之前，先简要概述一下需求分析的基本流程。需求分析通常包括以下几个步骤：</p>
<ol>
 <li><strong>需求收集</strong>：通过访谈、问卷、会议等方式，从用户、利益相关者等渠道收集需求信息。</li>
 <li><strong>需求整理</strong>：对收集到的需求进行整理、分类和优先级排序，形成需求清单。</li>
 <li><strong>需求分析</strong>：对需求进行深入分析，理解其背后的业务逻辑、用户场景等，确保需求的准确性和可行性。</li>
 <li><strong>需求建模</strong>：通过UML图、数据流图、状态图等方式对需求进行建模，以便更直观地展示和理解需求。</li>
 <li><strong>需求评审</strong>：组织相关人员进行需求评审，确保需求的一致性和完整性。</li>
 <li><strong>需求文档编写</strong>：将需求分析的结果编写成详细的需求文档，作为后续开发和测试的依据。</li>
</ol>
<h3>2. XMind在需求分析中的应用技巧</h3>
<h4>2.1 快速构建需求框架</h4>
<p>在需求收集阶段，可以使用XMind快速构建需求框架。通过中心主题代表整个系统或项目，然后逐层添加子主题来细分需求。这种方式不仅有助于梳理需求层次，还能让团队成员快速了解项目的整体需求和各个部分之间的关联。</p>
<h5>技巧示例：</h5>
<ul>
 <li>打开XMind，创建一个新的思维导图文件。</li>
 <li>将中心主题命名为"XX系统需求分析”。</li>
 <li>逐层添加子主题，如"用户管理”、“商品管理”、“订单管理"等，代表不同的功能模块。</li>
 <li>在每个功能模块下，继续添加子主题，细化到具体的功能点或需求项。</li>
</ul>
<h4>2.2 需求分类与优先级排序</h4>
<p>在需求整理阶段，可以利用XMind的图标、颜色和标签功能对需求进行分类和优先级排序。通过为不同类型的需求设置不同的图标和颜色，可以直观地区分出哪些是核心需求、哪些是可选需求或待评估需求。同时，使用标签功能为需求打上优先级标签（如P1、P2、P3等），以便在后续的开发过程中优先处理重要需求。</p>
<h5>技巧示例：</h5>
<ul>
 <li>选中某个需求项，点击工具栏上的图标按钮，为其选择一个合适的图标。</li>
 <li>点击工具栏上的颜色按钮，为需求项设置不同的颜色。</li>
 <li>使用标签功能为需求项添加优先级标签，如"P1-核心需求”、“P2-重要需求"等。</li>
</ul>
<h4>2.3 深入分析与建模</h4>
<p>在需求分析阶段，可以利用XMind的多种结构和样式来深入分析和建模需求。例如，可以使用鱼骨图（因果图）来分析问题的根本原因；使用矩阵图来展示二维数据的关系和对比；使用逻辑图或树状图来展示层次结构和逻辑关系等。这些图形化工具能够帮助团队成员更直观地理解需求背后的业务逻辑和用户场景。</p>
<h5>技巧示例：</h5>
<ul>
 <li>使用鱼骨图分析用户投诉问题的原因：在XMind中创建一个新的画布，选择鱼骨图结构，然后逐层添加"人”、“机”、“料”、“法”、“环"等子分支，并在每个子分支下添加具体的原因项。</li>
 <li>使用矩阵图进行产品功能对比：在XMind中创建一个新的画布，选择矩阵图结构，然后设置行和列代表不同的产品功能和评价指标（如易用性、性能、价格等），并在每个单元格中填写具体的评价分数或描述。</li>
</ul>
<h4>2.4 高效沟通与评审</h4>
<p>在需求评审阶段，可以利用XMind的共享和协作功能来提高沟通效率。通过将思维导图共享给团队成员或利益相关者，他们可以直接在XMind中进行查看、编辑和评论，从而实现实时的沟通和反馈。此外，还可以利用XMind的导出功能将思维导图导出为PPT、PDF等格式，以便在评审会议中进行展示和讨论。</p>
<h5>技巧示例：</h5>
<ul>
 <li>在XMind中完成需求分析后，点击工具栏上的"分享"按钮，将思维导图共享给团队成员或利益相关者。</li>
 <li>团队成员可以在XMind中进行查看、编辑和评论，实时反馈意见和建议。</li>
 <li>导出思维导图为PPT或PDF格式，以便在评审会议中进行展示和讨论。</li>
</ul>
<h4>2.5 辅助需求文档编写</h4>
<p>在需求文档编写阶段，可以利用XMind的导出功能将思维导图转换为需求文档的一部分。通过将思维导图中的关键内容和结构导出为Word、Excel等格式的文件，可以大大减轻文档编写的负担。</p>
<h4>2.6 细化需求场景与用例</h4>
<p>在软件需求分析中，理解并细化用户场景与用例是非常重要的。XMind可以帮助你构建场景图和用例图，这些图形化表示有助于团队成员更深入地理解用户如何与系统交互，以及系统在不同情境下的行为。</p>
<h5>技巧示例：</h5>
<ul>
 <li>
  <p><strong>场景图</strong>：你可以使用XMind的自由布局或组织结构图来创建场景图。首先，定义一个中心主题作为场景的开始点，然后添加子主题表示用户的动作、系统的响应以及可能的分支路径。使用连接线或箭头来表示流程的走向，并可以添加注释或图标来丰富场景描述。</p>
 </li>
 <li>
  <p><strong>用例图</strong>：用例图是一种描述系统功能的视图，它展示了参与者（用户或其他系统）与用例（系统功能）之间的关系。在XMind中，你可以将中心主题设置为系统名称，然后添加子主题代表不同的参与者。在每个参与者下，进一步添加子主题来定义他们与系统交互的用例。你可以使用不同的颜色或图标来区分不同的参与者或用例，以增强可读性。</p>
 </li>
</ul>
<h4>2.7 追踪需求变更</h4>
<p>在软件开发过程中，需求变更几乎是不可避免的。XMind可以帮助你有效地追踪和管理这些变更。你可以使用XMind的注释、链接或历史记录功能来记录每次变更的详细信息，包括变更的原因、影响范围、解决方案以及相关的决策记录。</p>
<h5>技巧示例：</h5>
<ul>
 <li>
  <p><strong>使用注释</strong>：在发生需求变更时，直接在受影响的节点上添加注释，说明变更的具体内容和时间。这样，其他团队成员在查看思维导图时就能快速了解变更的背景和细节。</p>
 </li>
 <li>
  <p><strong>添加链接</strong>：如果变更涉及到外部文档或讨论记录，你可以在XMind中使用链接功能将这些资源关联到相应的节点上。这样，团队成员就可以通过点击链接直接访问到相关的资料。</p>
 </li>
 <li>
  <p><strong>利用历史记录</strong>：虽然XMind本身不提供详细的历史记录功能（这通常需要借助专业的版本控制系统），但你可以通过定期保存不同的版本或使用XMind的导出功能来手动创建历史记录。每次需求变更后，导出当前版本的思维导图并保存为不同的文件名或版本号，以便将来进行回溯和比较。</p>
 </li>
</ul>
<h4>2.8 结合其他工具使用</h4>
<p>虽然XMind在软件需求分析中非常有用，但有时候你可能需要与其他工具结合使用以满足更复杂的需求。例如，你可以将XMind与UML建模工具（如StarUML、Visio等）结合使用来创建更详细的系统模型；或者将XMind与项目管理工具（如Jira、Trello等）结合使用来追踪和管理需求实现进度。</p>
<h5>技巧示例：</h5>
<ul>
 <li>
  <p><strong>与UML建模工具结合</strong>：在XMind中完成初步的需求分析后，你可以将思维导图中的关键元素（如类、接口、用例等）导入到UML建模工具中，以创建更详细的系统模型。这有助于进一步验证和细化需求，并为后续的设计和开发工作提供指导。</p>
 </li>
 <li>
  <p><strong>与项目管理工具结合</strong>：在XMind中完成需求分析并确定需求优先级后，你可以将这些信息导入到项目管理工具中，以创建任务、分配资源和设置截止日期。这样，你就可以在整个开发过程中跟踪和管理需求的实现进度，并确保项目按时交付。</p>
 </li>
</ul>
<h4>结语</h4>
<p>综上所述，XMind在软件需求分析中发挥着重要作用。通过利用其强大的思维导图功能、灵活的布局选项以及丰富的自定义选项，你可以高效地收集、整理、分析和沟通需求信息。同时，结合其他工具的使用可以进一步提升你的工作效率和需求分析的质量。希望本文提供的技巧能够帮助你更好地利用XMind进行软件需求分析工作。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141242506</p>]]></description><guid isPermaLink="false">/archives/2f10a89d-c877-412a-8042-dc99088db1ee</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:38:35 GMT</pubDate></item><item><title><![CDATA[编程小白如何成为大神？大学新生的最佳入门攻略]]></title><link>https://wiki.dgfygk.com/archives/809c267d-f594-4f5d-aaa8-acb0b6ab31ae</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BC%96%E7%A8%8B%E5%B0%8F%E7%99%BD%E5%A6%82%E4%BD%95%E6%88%90%E4%B8%BA%E5%A4%A7%E7%A5%9E%EF%BC%9F%E5%A4%A7%E5%AD%A6%E6%96%B0%E7%94%9F%E7%9A%84%E6%9C%80%E4%BD%B3%E5%85%A5%E9%97%A8%E6%94%BB%E7%95%A5&amp;url=/archives/809c267d-f594-4f5d-aaa8-acb0b6ab31ae" width="1" height="1" alt="" style="opacity:0;">
<h3>编程小白如何成为大神？大学新生的最佳入门攻略</h3>
<p>编程小白成长为编程大神，是一个既充满挑战又极具成就感的过程。对于大学新生而言，这更是一个全新的起点，一个可以系统学习、深入探索并逐步精通编程技能的黄金时期。以下是一份详尽的入门攻略，旨在帮助大学新生从零开始，逐步迈向编程大神的行列。</p>
<h4>一、明确目标与动机</h4>
<h5>1. 设定明确目标</h5>
<p>在开始学习之前，首先要明确自己的学习目标。是希望成为一名全栈开发者，专注于前端开发、后端开发，还是对数据科学、人工智能等领域感兴趣？明确的目标将帮助你更有针对性地选择学习路径和资源。</p>
<h5>2. 激发内在动机</h5>
<p>编程之路不会一帆风顺，遇到困难时，强大的内在动机是坚持下去的关键。思考为何选择编程，是出于兴趣爱好、职业规划，还是对未来技术的憧憬？找到那个让你热血沸腾的理由，让它成为你不断前进的动力。</p>
<h4>二、打牢基础，循序渐进</h4>
<h5>1. 学习编程语言</h5>
<p>作为编程小白，选择一门入门级的编程语言至关重要。Python因其简洁易读、应用领域广泛而成为许多人的首选。通过学习Python，你可以掌握编程的基本概念，如变量、数据类型、控制结构（循环、条件判断）、函数等。同时，利用Python的丰富库和框架（如Django、Flask用于Web开发，NumPy、Pandas用于数据处理等），你可以快速进入实际项目开发。</p>
<h5>2. 理解计算机科学基础</h5>
<p>编程不仅仅是编写代码，更是对计算机科学原理的应用。因此，掌握计算机科学的基础知识，如算法与数据结构、计算机网络、操作系统、数据库等，对于提升编程能力和解决复杂问题至关重要。这些课程在大学里通常都有开设，务必认真对待。</p>
<h4>三、实践为主，理论为辅</h4>
<h5>1. 动手实践</h5>
<p>“纸上得来终觉浅，绝知此事要躬行”。编程是一门实践性极强的学科，理论学习只是基础，真正的能力提升来自于不断的实践。从编写简单的Hello World程序开始，逐步尝试实现更复杂的功能和项目。可以通过完成在线编程习题、参与开源项目、自己设计并实现小项目等方式来积累实践经验。</p>
<h5>2. 参与项目</h5>
<p>参与实际项目能够极大地提升你的编程能力和团队协作能力。在大学里，可以加入学生社团或技术团队，与志同道合的同学一起完成项目。此外，还可以利用GitHub等平台寻找并参与开源项目，这不仅能锻炼你的编程技能，还能让你接触到更广泛的技术栈和社区文化。</p>
<h4>四、持续学习，紧跟技术前沿</h4>
<h5>1. 养成学习习惯</h5>
<p>编程技术日新月异，持续学习是保持竞争力的关键。养成每天学习一点新知识的习惯，无论是通过阅读技术博客、观看教学视频，还是参加线上课程、技术研讨会等。同时，保持好奇心和求知欲，勇于尝试新技术和新工具。</p>
<h5>2. 关注技术动态</h5>
<p>关注行业动态和技术趋势对于编程人员来说至关重要。可以通过订阅技术博客、关注技术大咖的社交媒体账号、参与技术论坛等方式来了解最新的技术动态和热点话题。这有助于你把握技术发展的脉搏，及时调整学习方向。</p>
<h4>五、培养软技能，提升综合素质</h4>
<h5>1. 沟通能力</h5>
<p>良好的沟通能力是编程人员必备的技能之一。无论是与团队成员协作完成项目，还是向非技术人员解释技术方案，都需要良好的沟通能力。因此，在大学期间要积极参与团队活动，锻炼自己的表达能力和倾听能力。</p>
<h5>2. 解决问题的能力</h5>
<p>编程过程中难免会遇到各种问题和挑战。培养解决问题的能力对于编程人员来说至关重要。要学会独立思考、分析问题、提出解决方案并付诸实践。同时，也要学会从失败中汲取教训，不断总结经验教训以提高自己的解决问题的能力。</p>
<h5>3. 团队协作能力</h5>
<p>在软件开发领域，团队协作能力同样重要。要学会与不同背景、不同专长的人合作共事，共同完成项目目标。在大学期间可以积极参与团队项目、技术竞赛等活动来锻炼自己的团队协作能力。</p>
<h4>六、保持积极心态，勇于面对挑战</h4>
<h5>1. 保持积极心态</h5>
<p>编程之路并非一帆风顺，遇到困难和挫折是在所难免的。保持积极的心态对于克服这些困难至关重要。要学会调整自己的情绪和心态，以乐观的态度面对挑战和失败。同时，也要学会从失败中汲取力量和经验教训以更好地前行。</p>
<h5>2. 勇于面对挑战</h5>
<p>编程领域充满了未知和挑战，只有勇于面对并克服这些挑战才能不断成长和进步。要敢于尝试新技术和新方法，不断挑战自己的极限和舒适区。只有这样才能不断突破自我限制并实现更高的目标。</p>
<h4>七、总结与展望</h4>
<p>成为编程大神并非一蹴而就的事情，需要长期的努力和坚持。大学新生作为编程领域的新手更应该珍惜这段宝贵的时光并充分利用各种资源来提升自己的编程能力和综合素质。通过明确目标与动机、打牢基础并循序渐进地学习、注重实践并积极参与项目、持续学习并紧跟技术前沿以及培养软技能并提升综合素质等方面的努力和实践，你一定能够逐步</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141154463</p>]]></description><guid isPermaLink="false">/archives/809c267d-f594-4f5d-aaa8-acb0b6ab31ae</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:31:11 GMT</pubDate></item><item><title><![CDATA[提升前端性能的JavaScript技巧]]></title><link>https://wiki.dgfygk.com/archives/356f278d-9f03-4550-aa85-3d720d55ea4c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8F%90%E5%8D%87%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E7%9A%84JavaScript%E6%8A%80%E5%B7%A7&amp;url=/archives/356f278d-9f03-4550-aa85-3d720d55ea4c" width="1" height="1" alt="" style="opacity:0;">
<p>在前端开发中，JavaScript 扮演着至关重要的角色，负责处理用户交互、动态内容加载以及复杂的业务逻辑等。然而，不当的 JavaScript 使用往往会成为前端性能的瓶颈。为了提升前端性能，以下是一些关键的 JavaScript 技巧，涵盖了代码优化、DOM 操作、资源加载、缓存策略等多个方面。</p>
<p>一、代码优化</p>
<ol>
 <li>变量作用域与局部变量
  <br>
  避免全局变量：全局变量会增加作用域链的长度，从而影响性能。尽可能使用局部变量，并将变量定义在最近的函数作用域内。
  <br>
  使用 let 和 const：ES6 引入的 let 和 const 提供了块级作用域，比 var 更具限制性，有助于减少变量冲突和提升性能。</li>
 <li>最小化代码
  <br>
  使用代码压缩工具：如 UglifyJS、Terser 等，可以自动移除 JavaScript 代码中的空格、注释、换行符等不必要的字符，同时缩短变量名和函数名，从而减少文件大小和提高加载速度。
  <br>
  避免复杂的逻辑：尽量简化代码逻辑，减少不必要的判断和循环，特别是在性能敏感的函数中。</li>
 <li>避免重复代码
  <br>
  使用函数封装：将重复的代码块封装成函数，以减少代码冗余和提高代码复用性。
  <br>
  利用模块化和组件化：现代前端框架（如 React、Vue）支持模块化和组件化开发，可以有效避免代码重复并提升开发效率。
  <br>
  二、DOM 操作优化</li>
 <li>批量操作 DOM
  <br>
  使用 DocumentFragment：将多个 DOM 节点先添加到 DocumentFragment 中，然后再一次性将 DocumentFragment 添加到文档中。这样可以减少页面的重绘和重排次数，从而提升性能。
  <br>
  避免频繁操作 DOM：尽量减少在循环或频繁调用的函数中操作 DOM，因为每次 DOM 操作都可能触发浏览器的重绘或重排。</li>
 <li>使用虚拟 DOM
  <br>
  现代前端框架：React、Vue 等框架使用虚拟 DOM 技术来最小化实际的 DOM 操作。虚拟 DOM 是一种轻量级的 JavaScript 对象表示，它在内存中模拟 DOM 树。通过比较新旧虚拟 DOM 的差异，并只更新必要的部分到真实 DOM，从而提高性能。</li>
 <li>事件委托
  <br>
  利用事件冒泡：将事件监听器绑定到父元素上，而不是直接绑定到多个子元素上。当子元素触发事件时，事件会冒泡到父元素，并在那里被处理。这样可以减少事件监听器的数量，并提升性能。
  <br>
  三、资源加载优化</li>
 <li>异步加载脚本
  <br>
  使用</li>
</ol>
<h4>七、避免内存泄漏</h4>
<p>内存泄漏是前端开发中常见的问题之一，它会导致应用运行缓慢，甚至崩溃。以下是避免内存泄漏的一些关键技巧：</p>
<h5>1. 清理定时器与事件监听器</h5>
<ul>
 <li>在组件卸载或不再需要时，确保清除所有通过 <code>setInterval</code>、<code>setTimeout</code> 设置的定时器，以及通过 <code>addEventListener</code> 添加的事件监听器。这可以通过在相应的位置调用 <code>clearInterval</code>、<code>clearTimeout</code> 和 <code>removeEventListener</code> 来实现。</li>
</ul>
<h5>2. 避免全局变量和闭包中的循环引用</h5>
<ul>
 <li>全局变量在页面的整个生命周期内都存在，如果它们引用了大量的数据或 DOM 元素，可能会导致内存占用过高。使用局部变量和适当的作用域来限制变量的生命周期。</li>
 <li>在闭包中，如果对象 A 引用了对象 B，而对象 B 又通过闭包引用了对象 A，这会导致循环引用。JavaScript 的垃圾回收机制通常能够处理简单的循环引用，但在某些情况下（如涉及 DOM 元素的循环引用），可能需要手动断开引用链。</li>
</ul>
<h5>3. 合理使用 Web Workers</h5>
<ul>
 <li>对于计算密集型任务，考虑使用 Web Workers 在后台线程中执行，以避免阻塞主线程并影响页面性能。Web Workers 允许你在后台运行脚本而不会干扰页面的性能。</li>
</ul>
<h4>八、优化数据结构与算法</h4>
<p>虽然前端开发中直接使用复杂数据结构和算法的场景相对较少，但在处理大量数据或复杂逻辑时，优化数据结构和算法仍然非常重要。</p>
<h5>1. 选择合适的数据结构</h5>
<ul>
 <li>根据数据的特性和使用场景选择最适合的数据结构。例如，如果需要频繁地进行插入和删除操作，可以考虑使用链表；如果需要快速查找元素，则可以使用哈希表或二叉搜索树。</li>
</ul>
<h5>2. 优化算法</h5>
<ul>
 <li>对于复杂的计算任务，尝试使用更高效的算法。例如，使用快速排序而不是冒泡排序来对大量数据进行排序。</li>
 <li>使用时间复杂度和空间复杂度来分析算法的性能，并尽可能选择较低的复杂度。</li>
</ul>
<h4>九、利用现代前端框架和库</h4>
<p>现代前端框架和库（如 React、Vue、Angular）提供了丰富的功能和优化手段，可以帮助开发者更高效地开发高性能的 Web 应用。</p>
<h5>1. 组件化开发</h5>
<ul>
 <li>利用组件化开发模式，将页面拆分成多个可复用的组件。这不仅可以提高开发效率，还可以减少不必要的 DOM 操作和重绘/重排。</li>
</ul>
<h5>2. 响应式更新</h5>
<ul>
 <li>现代前端框架通常实现了响应式系统，可以自动跟踪数据的变化并只更新受影响的组件。这有助于减少不必要的更新和渲染，提高页面性能。</li>
</ul>
<h5>3. 虚拟滚动与无限滚动</h5>
<ul>
 <li>对于包含大量列表项的应用，可以使用虚拟滚动或无限滚动技术来优化性能。虚拟滚动通过只渲染可视区域内的列表项来减少 DOM 节点的数量；无限滚动则通过按需加载数据来避免一次性加载过多数据。</li>
</ul>
<h4>十、关注浏览器兼容性和性能瓶颈</h4>
<ul>
 <li><strong>浏览器兼容性</strong>：确保你的应用能够在目标用户群体常用的浏览器中正常运行。使用像 Babel 这样的转译器可以帮助你将现代 JavaScript 代码转换为旧版浏览器也能理解的代码。</li>
 <li><strong>性能瓶颈分析</strong>：定期使用性能分析工具（如 Chrome DevTools 的 Performance 面板）来分析和优化你的应用。注意查找瓶颈，如长时间运行的任务、高CPU使用率、频繁的内存分配和释放等，并尝试通过优化代码、使用更高效的数据结构和算法或改进资源加载策略来解决这些问题。</li>
</ul>
<p>综上所述，提升前端性能的 JavaScript 技巧涉及多个方面，包括代码优化、DOM 操作优化、资源加载优化、缓存策略、图片与资源优化、避免内存泄漏、优化数据结构与算法、利用现代前端框架和库以及关注浏览器兼容性和性能瓶颈等。通过综合运用这些技巧，你可以显著提升前端应用的性能，为用户带来更加流畅和愉快的体验。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141154913</p>]]></description><guid isPermaLink="false">/archives/356f278d-9f03-4550-aa85-3d720d55ea4c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 03:31:02 GMT</pubDate></item><item><title><![CDATA[提升效率的印象笔记(Evernote)]]></title><link>https://wiki.dgfygk.com/archives/c1491ca5-4db8-455b-8907-efaacd3d5876</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8F%90%E5%8D%87%E6%95%88%E7%8E%87%E7%9A%84%E5%8D%B0%E8%B1%A1%E7%AC%94%E8%AE%B0%28Evernote%29&amp;url=/archives/c1491ca5-4db8-455b-8907-efaacd3d5876" width="1" height="1" alt="" style="opacity:0;">
<p>提升效率的印象笔记(Evernote)使用技巧涉及多个方面，从快速记录、高效整理到智能搜索、跨平台同步以及与其他工具的集成等。以下将详细阐述这些技巧，帮助用户更高效地使用印象笔记。</p>
<h4>一、快速记录与创建</h4>
<h5>1. 快捷键与快速记录功能</h5>
<ul>
 <li><strong>快捷键操作</strong>：印象笔记支持多种快捷键，如新建笔记（Ctrl+N/Command+N）、保存（Ctrl+S/Command+S）、全选（Ctrl+A/Command+A）等，这些快捷键可以大大提高操作效率。用户还可以根据自己的习惯自定义快捷键，进一步加速操作流程。</li>
 <li><strong>快速记录功能</strong>：印象笔记的电脑和手机客户端都具备快速创建笔记的功能。在电脑端，用户可以通过向下滑动通知栏或设置全局快捷键来快速启动笔记；在手机端，则可以直接在主界面点击新建笔记按钮或利用语音输入功能快速记录想法。</li>
</ul>
<h5>2. 模板使用</h5>
<p>印象笔记提供了多种预设模板，如会议记录、项目计划、读书笔记等，用户可以根据需要选择合适的模板来快速创建笔记。这些模板不仅包含了常见的结构和内容，还能够帮助用户保持笔记的一致性和规范性，提高记录效率。</p>
<h4>二、高效整理与分类</h4>
<h5>1. 笔记本与笔记本组</h5>
<ul>
 <li><strong>创建笔记本</strong>：用户可以根据不同的主题或场景创建多个笔记本，如"工作”、“学习”、“旅行"等，以便对笔记进行分类管理。</li>
 <li><strong>使用笔记本组</strong>：当笔记本数量较多时，可以使用笔记本组对它们进行进一步分类，形成层级结构。例如，可以将"项目A”、“项目B"等笔记本放入"工作项目"组中，将"家庭预算”、“购物清单"等笔记本放入"个人事务"组中。</li>
</ul>
<h5>2. 标签系统</h5>
<p>标签是印象笔记中一个非常灵活且强大的分类工具。与笔记本相比，标签可以对笔记进行多维度的标记，使用户能够更快速地找到所需内容。例如，在工作笔记中，可以使用"重要”、“待办”、“会议纪要"等标签；在旅行笔记中，则可以使用"目的地”、“行程安排”、“餐饮推荐"等标签。</p>
<h4>三、智能搜索与查找</h4>
<h5>1. 强大的搜索功能</h5>
<p>印象笔记的搜索功能非常强大，支持关键词、标签、笔记本名称等多种搜索方式。用户可以在搜索框中输入相关信息，快速定位到目标笔记。此外，印象笔记还支持高级搜索语法，如使用"tag:重要"可以查找所有带有"重要"标签的笔记，使用"notebook:工作"则可以查找所有在"工作"笔记本中的笔记。</p>
<h5>2. 笔记链接与快捷链接</h5>
<p>用户可以为每个笔记创建快捷链接，并将这些链接插入到其他笔记、任务管理工具或日历中，实现快速跳转。例如，在待办事项列表中添加相关笔记的快捷链接，可以方便用户随时查阅详细信息。</p>
<h4>四、跨平台同步与备份</h4>
<h5>1. 实时同步</h5>
<p>印象笔记支持跨平台同步功能，用户可以在电脑、手机、平板等不同设备之间无缝切换，随时随地查看和编辑笔记。这一功能极大地提高了用户的工作效率，使用户能够在任何时间、任何地点保持工作状态的连续性。</p>
<h5>2. 定期备份</h5>
<p>尽管印象笔记提供了强大的同步功能，但定期备份仍然是一个好习惯。用户可以将重要的笔记导出为PDF或其他格式的文件，以便在特殊情况下能够恢复数据。此外，用户还可以考虑购买印象笔记的订阅服务，以获得更大的存储空间、更高的同步频率等高级功能。</p>
<h4>五、与其他工具的集成与扩展</h4>
<h5>1. 第三方插件与应用</h5>
<p>印象笔记支持多种第三方插件和应用，这些插件可以进一步扩展其功能。例如，用户可以通过插件将网页、图片、语音等内容快速保存到印象笔记中；还可以使用第三方应用（如Microsoft Office、Google Chrome等）直接保存内容到印象笔记中。这些工具和应用的集成使得印象笔记在信息收集、整理和分享方面更加高效便捷。</p>
<h5>2. 自动化工具</h5>
<p>用户还可以利用IFTTT（If This Then That）或Zapier等自动化工具将印象笔记与其他应用进行自动化连接。例如，可以设置在日历中添加新事件时自动创建笔记，或在邮箱中收到特定邮件时自动保存到印象笔记中。这些自动化操作可以大大减少手动操作的时间和精力，提高工作效率。</p>
<h4>六、其他实用技巧</h4>
<h5>1. Markdown语言支持</h5>
<p>印象笔记支持Markdown语言输入，这使得用户可以通过简单的语法快速创建数字列表、项目符号列表、复选框以及表格等内容。Markdown语言的引入不仅提高了输入效率，还使得笔记内容更加清晰、美观。</p>
<h5>2. OCR文字识别</h5>
<p>印象笔记支持对图片和PDF文件中的文字进行OCR（光学字符识别）处理。这意味着用户可以拍摄纸质文件、白板内容或截图，并通过搜索功能找到其中的文字信息，极大地提高了信息获取和整理的效率。无论是会议记录、书籍摘录还是收据管理，OCR功能都能让这些信息变得可搜索和可编辑。</p>
<h5>3. 笔记协作与共享</h5>
<p>印象笔记的协作功能允许用户邀请团队成员或朋友共同编辑和查看笔记。通过共享笔记本或特定笔记，团队成员可以实时协作，共同完成任务或项目。此外，印象笔记还支持评论和提及功能，使得团队成员之间的沟通和反馈更加便捷。这种协作模式不仅提高了工作效率，还促进了团队之间的沟通和协作精神。</p>
<h5>4. 笔记提醒与任务管理</h5>
<p>印象笔记内置了提醒功能，用户可以为笔记设置提醒时间，确保不会错过任何重要事项。此外，用户还可以将笔记转换为待办事项，利用印象笔记的任务管理功能来跟踪和管理自己的日程安排。通过将笔记与任务管理相结合，用户可以更加清晰地了解自己的工作重点和优先级，从而提高工作效率和生产力。</p>
<h5>5. 笔记整理与归档</h5>
<p>随着时间的推移，笔记数量会逐渐增多。为了保持笔记的整洁和有序，用户需要定期进行整理和归档。印象笔记提供了多种整理工具，如移动、复制、删除、合并笔记本等，帮助用户轻松管理笔记。此外，用户还可以利用标签和搜索功能来快速找到需要的笔记，避免在大量笔记中浪费时间。</p>
<h5>6. 笔记安全与隐私</h5>
<p>印象笔记非常重视用户数据的安全和隐私保护。它采用了多种安全措施来保护用户数据的安全，如加密传输、存储加密、双重认证等。此外，用户还可以设置密码保护、指纹解锁等隐私保护功能，确保自己的笔记不会被未经授权的人员访问。这些安全措施让用户可以更加放心地使用印象笔记来记录和管理自己的重要信息。</p>
<h4>七、总结与展望</h4>
<p>通过以上技巧的介绍，我们可以看到印象笔记不仅是一款功能强大的笔记应用，更是一个高效的工作和生活助手。通过合理利用这些技巧，用户可以大大提高自己的工作效率和生产力，更好地管理自己的时间和信息。</p>
<p>然而，随着技术的不断发展和用户需求的不断变化，印象笔记也在不断更新和完善自己的功能。未来，我们可以期待印象笔记在以下几个方面进行更多的创新和优化：</p>
<ol>
 <li><strong>更强大的AI技术</strong>：利用AI技术来进一步提升笔记的搜索、分类和整理能力，让用户能够更快地找到需要的信息。</li>
 <li><strong>更丰富的插件和扩展</strong>：不断引入新的插件和扩展，以满足用户多样化的需求，如语音识别、手写识别、PDF编辑等。</li>
 <li><strong>更紧密的团队协作</strong>：加强团队协作功能，提供更加便捷的沟通和协作工具，让团队成员能够更加高效地协同工作。</li>
 <li><strong>更智能的提醒和任务管理</strong>：结合用户的日程安排和工作习惯，提供更加智能化的提醒和任务管理功能，帮助用户更好地规划和管理自己的时间。</li>
</ol>
<p>总之，印象笔记作为一款优秀的笔记应用，已经为用户提供了丰富的功能和技巧来提高工作效率。随着技术的不断进步和用户需求的不断变化，我们有理由相信印象笔记将会在未来变得更加智能、高效和便捷。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141129421</p>]]></description><guid isPermaLink="false">/archives/c1491ca5-4db8-455b-8907-efaacd3d5876</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:30:48 GMT</pubDate></item><item><title><![CDATA[【网络】协议与网络版计算器]]></title><link>https://wiki.dgfygk.com/archives/37901544-cccd-4deb-9a64-722f31fddb30</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%BD%91%E7%BB%9C%E3%80%91%E5%8D%8F%E8%AE%AE%E4%B8%8E%E7%BD%91%E7%BB%9C%E7%89%88%E8%AE%A1%E7%AE%97%E5%99%A8&amp;url=/archives/37901544-cccd-4deb-9a64-722f31fddb30" width="1" height="1" alt="" style="opacity:0;">
<p>在网络通信的广阔领域中，协议（Protocol）是构建信息交换基础的基石，它们定义了数据如何在网络中的不同设备之间传输、识别、解释和响应。而网络版计算器，作为网络应用的一个简单实例，不仅展示了网络服务的实现方式，也体现了协议在其中的关键作用。以下将深入探讨网络协议的基本概念、类型、作用，并结合网络版计算器的设计与实现，阐述协议在实际应用中的体现。</p>
<h4>一、网络协议概述</h4>
<h5>1. 定义与重要性</h5>
<p>网络协议是网络通信中双方或多方必须遵循的一系列规则和标准，它们规定了数据交换的格式、顺序、速率以及控制信息的结构和含义。没有统一的协议，不同设备之间就无法进行有效的通信。网络协议的存在，使得全球范围内的计算机能够相互连接，共享资源，实现信息的快速传递。</p>
<h5>2. 协议层次结构</h5>
<p>网络协议通常被组织成层次结构，最著名的模型是OSI（开放系统互连）模型和TCP/IP（传输控制协议/互联网协议）模型。OSI模型将网络通信划分为七层，从下到上依次为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而TCP/IP模型则简化为四层：网络接口层、网络层、传输层和应用层。这两种模型虽然层次划分略有不同，但基本思想是一致的，即分层处理网络通信中的复杂问题。</p>
<h4>二、网络协议的类型与作用</h4>
<h5>1. 物理层协议</h5>
<p>物理层协议定义了数据传输的物理介质（如双绞线、光纤）、信号类型（如模拟信号、数字信号）、传输速率等。它确保了比特流在物理介质上的正确传输，是网络通信的基础。</p>
<h5>2. 数据链路层协议</h5>
<p>数据链路层协议负责在相邻节点间无差错地传输数据帧。常见的协议有以太网（Ethernet）、令牌环（Token Ring）等。它们通过帧的封装、差错控制、流量控制等机制，确保数据在物理层之上的可靠传输。</p>
<h5>3. 网络层协议</h5>
<p>网络层协议负责将数据包从源地址路由到目的地址。IP（互联网协议）是网络层的核心协议，它定义了数据包的格式、寻址方式和路由选择算法。此外，ICMP（互联网控制消息协议）、ARP（地址解析协议）等也是网络层的重要协议。</p>
<h5>4. 传输层协议</h5>
<p>传输层协议为应用层提供端到端的通信服务。TCP（传输控制协议）和UDP（用户数据报协议）是传输层的两个主要协议。TCP提供面向连接的、可靠的、基于字节流的服务，而UDP则提供无连接的、不可靠的、基于数据报的服务。</p>
<h5>5. 应用层协议</h5>
<p>应用层协议直接面向用户应用，如HTTP（超文本传输协议）、FTP（文件传输协议）、SMTP（简单邮件传输协议）等。这些协议定义了应用层数据的格式和交换方式，是用户与网络服务交互的接口。</p>
<h4>三、网络版计算器的设计与实现</h4>
<h5>1. 设计思路</h5>
<p>网络版计算器是一个基于Web的应用，用户可以通过浏览器访问服务器上的计算器服务，进行数学运算。其设计思路主要包括前端界面设计、后端逻辑处理和网络通信协议的选择。</p>
<h5>2. 前端界面设计</h5>
<p>前端界面是用户与计算器交互的窗口，通常使用HTML、CSS和JavaScript等技术实现。HTML用于构建页面的基本结构，CSS用于美化页面，JavaScript则用于处理用户的输入和显示计算结果。界面设计应简洁明了，便于用户操作。</p>
<h5>3. 后端逻辑处理</h5>
<p>后端逻辑处理是计算器服务的核心部分，负责接收用户的输入、执行数学运算并返回结果。后端可以使用多种编程语言和技术实现，如Python、Java、Node.js等。在实现时，需要定义接收输入、处理运算和返回结果的逻辑流程，并确保运算的准确性和高效性。</p>
<h5>4. 网络通信协议的选择</h5>
<p>对于网络版计算器来说，选择合适的网络通信协议至关重要。由于计算器服务主要涉及到用户与服务器之间的数据交换，且数据量较小、实时性要求较高，因此可以选择HTTP协议作为通信协议。HTTP是一种应用层协议，它支持客户端和服务器之间的请求-响应模型，非常适合于Web应用的网络通信。</p>
<h5>5. 实现过程</h5>
<ul>
 <li><strong>前端实现</strong>：使用HTML构建计算器界面，包括输入框、运算符按钮和结果显示区域。使用CSS进行样式设计，使界面美观易用。使用JavaScript监听用户的输入和点击事件，并将数据发送到服务器。</li>
 <li><strong>后端实现</strong>：在后端服务器上部署一个Web应用，该应用能够接收前端发送的数据（如用户输入的数值和运算符），执行相应的数学运算，并将结果返回给前端。后端可以使用任何支持HTTP协议的服务器软件（如Apache、Nginx）和编程语言（如Python的Flask、Django框架）。</li>
 <li><strong>网络通信</strong>：前端通过AJAX（Asynchronous JavaScriptand XMLHttpRequest）或Fetch API等现代JavaScript技术向服务器发送HTTP请求，并异步接收响应。这些技术允许网页在不重新加载整个页面的情况下，与服务器交换数据并更新部分网页内容。</li>
</ul>
<h5>6. 安全性考虑</h5>
<p>虽然网络版计算器看似简单，但在实现过程中仍需考虑安全性问题。首先，应确保服务器能够正确处理用户输入，防止SQL注入等安全漏洞。其次，对于敏感数据（尽管在计算器应用中可能不常见），应使用HTTPS协议来加密传输，保护数据不被窃听或篡改。此外，还应考虑跨站脚本（XSS）和跨站请求伪造（CSRF）等常见Web安全威胁，并采取相应的防护措施。</p>
<h5>7. 性能优化</h5>
<p>为了提高网络版计算器的响应速度和用户体验，可以进行一系列性能优化。例如，优化前端代码以减少加载时间；使用缓存技术来存储常用数据和运算结果；对后端逻辑进行算法优化以提高计算效率；以及合理配置服务器和网络资源以支持高并发访问等。</p>
<h5>8. 扩展性与可维护性</h5>
<p>在设计网络版计算器时，还应考虑其扩展性和可维护性。随着用户需求的不断变化和技术的不断发展，计算器可能需要添加新的功能或改进现有功能。因此，在设计时应采用模块化、可复用的代码结构，并遵循良好的编程规范和文档编写习惯。同时，应建立有效的版本控制和代码审查机制，以确保代码质量和可维护性。</p>
<h4>四、总结</h4>
<p>网络版计算器作为网络应用的一个简单实例，不仅展示了网络服务的实现方式，也深刻体现了网络协议在其中的关键作用。从物理层到应用层，每一层协议都为实现计算器服务提供了必要的支持和保障。同时，在设计和实现过程中，还需要考虑安全性、性能优化、扩展性和可维护性等多个方面。通过综合运用各种技术和方法，可以开发出既满足用户需求又具有良好性能和可维护性的网络版计算器。</p>
<p>此外，随着Web技术的不断发展和创新，网络版计算器的实现方式也将不断演进。例如，可以利用WebAssembly等新技术来提高计算性能；利用WebSockets等实时通信技术来实现更加流畅的交互体验；以及利用容器化、微服务化等架构思想来构建更加灵活和可扩展的系统等。这些新技术和新方法的应用将进一步推动网络版计算器的发展和完善。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141051152</p>]]></description><guid isPermaLink="false">/archives/37901544-cccd-4deb-9a64-722f31fddb30</guid><dc:creator>黄健</dc:creator><category>网络运维</category><category>计算机基础</category><pubDate>Fri, 27 Sep 2024 03:30:39 GMT</pubDate></item><item><title><![CDATA[Ant-Design-Vue快速上手指南与排坑指南]]></title><link>https://wiki.dgfygk.com/archives/e4800b74-5549-4bb0-a53d-28efc509ad2c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Ant-Design-Vue%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E6%8C%87%E5%8D%97%E4%B8%8E%E6%8E%92%E5%9D%91%E6%8C%87%E5%8D%97&amp;url=/archives/e4800b74-5549-4bb0-a53d-28efc509ad2c" width="1" height="1" alt="" style="opacity:0;">
<h4>Ant-Design-Vue快速上手指南与排坑指南</h4>
<h5>一、安装与配置</h5>
<h6>1. 安装Ant Design Vue</h6>
<p>通过npm或yarn来安装Ant Design Vue：</p>
<pre><code class="prism language-bash">npm install ant-design-vue --save
# 或者
yarn add ant-design-vue
</code></pre>
<h6>2. 引入样式文件</h6>
<p>在项目的入口文件（如<code>main.js</code>或<code>main.ts</code>）中引入Ant Design Vue的CSS文件：</p>
<pre><code class="prism language-javascript">import 'ant-design-vue/dist/antd.css';
</code></pre>
<h6>3. 引入组件</h6>
<ul>
 <li>
  <p><strong>全局引入</strong> ：在<code>main.js</code>或<code>main.ts</code>中全局引入Ant Design Vue的所有组件。</p>
  <pre><code class="prism language-javascript">import Vue from 'vue';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';

Vue.use(Antd);
</code></pre>
 </li>
 <li>
  <p><strong>局部引入</strong>：在需要的组件中局部引入Ant Design Vue的组件，以减少项目体积。</p>
  <pre><code class="prism language-javascript">import {
    Button, Icon } from 'ant-design-vue';

export default {

components: {

  'a-button': Button,
  'a-icon': Icon,
},
};
</code></pre>
 </li>
</ul>
<h5>二、基本使用</h5>
<h6>1. 按钮（Button）</h6>
<pre><code class="prism language-html">&lt;template&gt;
  &lt;a-button type="primary"&gt;主要按钮&lt;/a-button&gt;
  &lt;a-button&gt;默认按钮&lt;/a-button&gt;
  &lt;!-- 更多按钮类型 --&gt;
&lt;/template&gt;
</code></pre>
<h6>2. 表单（Form）</h6>
<p>表单是Web应用中常用的数据收集工具。</p>
<pre><code class="prism language-html">&lt;template&gt;
  &lt;a-form ref="form" :model="formData" @submit.prevent="handleSubmit"&gt;
    &lt;a-form-item label="用户名" required&gt;
      &lt;a-input v-model="formData.username" placeholder="请输入用户名" /&gt;
    &lt;/a-form-item&gt;
    &lt;a-form-item label="密码" required&gt;
      &lt;a-input v-model="formData.password" placeholder="请输入密码" type="password" /&gt;
    &lt;/a-form-item&gt;
    &lt;a-form-item&gt;
      &lt;a-button type="primary" html-type="submit"&gt;提交&lt;/a-button&gt;
    &lt;/a-form-item&gt;
  &lt;/a-form&gt;
&lt;/template&gt;

&lt;script&gt;
export default {

  data() {

    return {

      formData: {

        username: '',
        password: '',
      },
    };
  },
  methods: {

    handleSubmit() {

      // 表单验证与提交逻辑
    },
  },
};
&lt;/script&gt;
</code></pre>
<h6>3. 表格（Table）</h6>
<p>表格用于展示数据列表。</p>
<pre><code class="prism language-html">&lt;template&gt;
  &lt;a-table :columns="columns" :dataSource="data" rowKey="id"&gt;
    &lt;!-- 表格内容将根据columns和dataSource自动生成 --&gt;
  &lt;/a-table&gt;
&lt;/template&gt;

&lt;script&gt;
export default {

  data() {

    return {

      columns: [
        // 列定义
      ],
      data: [
        // 数据源
      ],
    };
  },
};
&lt;/script&gt;
</code></pre>
<h5>三、快速上手技巧</h5>
<ol>
 <li><strong>阅读文档</strong>：深入阅读Ant Design Vue的官方文档，了解每个组件的API和使用方法。</li>
 <li><strong>实践项目</strong>：通过实际项目来巩固学习成果，将理论知识应用到实践中。</li>
 <li><strong>利用社区资源</strong>：遇到问题时，可以在GitHub、Stack Overflow等社区中寻求帮助。</li>
 <li><strong>关注更新</strong>：及时关注Ant Design Vue的更新日志，了解新功能和修复的问题。</li>
</ol>
<h5>四、排坑指南</h5>
<ol>
 <li>
  <p><strong>版本兼容性问题</strong>：确保Vue和Ant Design Vue的版本兼容。</p>
 </li>
 <li>
  <p><strong>样式冲突</strong>：使用CSS命名空间和优先级来避免样式冲突。</p>
 </li>
 <li>
  <p><strong>按需加载</strong>：为了减小项目体积，可以使用插件进行组件的按需加载。</p>
 </li>
 <li>
  <p><strong>国际化</strong>：如果你的项目需要支持多语言，可以利用Ant Design Vue的国际化功能。</p>
 </li>
 <li>
  <p><strong>性能优化</strong>：避免不必要的组件渲染，使用Vue的性能优化技巧来提升应用性能。</p>
 </li>
 <li>
  <p><strong>错误处理</strong>：在开发过程中添加适当的错误处理逻辑，以便及时发现并修复潜在的问题。</p>
 </li>
 <li>
  <p><strong>自定义主题</strong>：Ant Design Vue 提供了丰富的样式定制选项，包括通过 Less 变量来自定义主题。你可以根据自己的品牌需求，调整颜色、字体等样式，使 UI 组件更好地融入你的项目中。</p>
 </li>
 <li>
  <p><strong>响应式设计</strong>：确保你的页面和组件在不同屏幕尺寸下都能良好地显示。Ant Design Vue 的组件大多数都支持响应式设计，但你可能还需要对特定的布局或功能进行额外的适配。</p>
 </li>
 <li>
  <p><strong>组件通信</strong>：在 Vue 项目中，组件之间的通信是常见的需求。Ant Design Vue 的组件作为 Vue 组件，同样支持 Vue 的组件通信方式，如 props、events、Vuex、provide/inject 等。确保你熟悉这些通信方式，以便在需要时能够高效地实现组件间的数据交互。</p>
 </li>
 <li>
  <p><strong>避免过度依赖</strong>：虽然 Ant Design Vue 提供了丰富的组件库，但过度依赖第三方库可能会限制你的项目的灵活性和可维护性。在引入新组件时，请仔细考虑是否真的需要它，以及是否有更简单的实现方式。</p>
 </li>
 <li>
  <p><strong>学习最佳实践</strong>：关注 Vue 和 Ant Design Vue 的最佳实践，如组件的复用、代码的组织结构、状态管理等。这些最佳实践可以帮助你编写出更高效、更可维护的代码。</p>
 </li>
 <li>
  <p><strong>参与社区</strong>：加入 Vue 和 Ant Design Vue 的社区，与其他开发者交流心得、分享经验。这不仅可以帮助你解决遇到的问题，还可以让你了解到最新的技术和趋势。</p>
 </li>
 <li>
  <p><strong>持续学习</strong>：技术日新月异，保持学习的态度对于开发者来说至关重要。不断关注 Vue 和前端技术的最新动态，学习新的框架、库和工具，以提升自己的竞争力。</p>
 </li>
</ol>
<p>通过以上指南，你应该能够更加高效地使用 Ant Design Vue，并在项目中充分发挥其优势。记住，实践是检验真理的唯一标准，只有不断尝试和积累经验，你才能成为一名优秀的前端开发者。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141065215</p>]]></description><guid isPermaLink="false">/archives/e4800b74-5549-4bb0-a53d-28efc509ad2c</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Fri, 27 Sep 2024 03:30:22 GMT</pubDate></item><item><title><![CDATA[CentOS 7 的安装流程]]></title><link>https://wiki.dgfygk.com/archives/f76b0985-d6d7-451b-a839-da97927da624</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=CentOS%207%20%E7%9A%84%E5%AE%89%E8%A3%85%E6%B5%81%E7%A8%8B&amp;url=/archives/f76b0985-d6d7-451b-a839-da97927da624" width="1" height="1" alt="" style="opacity:0;">
<p>CentOS 7 的安装流程是一个相对详细且需要细致操作的过程，涉及到从虚拟机配置到系统配置完成的多个步骤。以下是一个详细的 CentOS 7 安装流程教程，旨在帮助用户顺利完成安装。</p>
<h4>一、准备工作</h4>
<h5>1.1 下载 CentOS 7 镜像</h5>
<p>在开始安装之前，首先需要下载 CentOS 7 的安装镜像。由于官网下载速度可能较慢，推荐从其他可靠的镜像源下载，如清华大学开源软件镜像站、北京外国语大学开源软件镜像站等。选择合适的版本进行下载，例如 CentOS-7-x86_64-bin-DVD1.iso。</p>
<h5>1.2 配置虚拟机</h5>
<p>使用 VMware Workstation 或 VirtualBox 等虚拟化软件配置虚拟机。以下以 VMware Workstation 为例说明配置过程：</p>
<ol>
 <li><strong>打开 VMware Workstation</strong>，点击"文件"菜单下的"新建虚拟机”。</li>
 <li>选择"自定义（高级）“安装类型，点击"下一步”。</li>
 <li>选择"稍后安装操作系统”，因为此时还没有加载 CentOS 镜像，点击"下一步”。</li>
 <li>在客户机操作系统部分，选择"Linux”，版本选择"CentOS 7 64位”，确保与下载的镜像版本一致，点击"下一步”。</li>
 <li>设置虚拟机名称和安装位置，点击"下一步”。</li>
 <li>设置处理器数量和核心数，根据实际需求配置，一般建议至少分配2个核心。</li>
 <li>配置虚拟机内存，建议至少分配4GB（4096MB），以保证系统运行的流畅性。</li>
 <li>选择网络类型，通常选择"桥接网络"以便虚拟机与主机在同一网络下，也可以选择"NAT模式"或其他模式根据需要。</li>
 <li>选择I/O控制器和磁盘类型，通常保持默认设置即可。</li>
 <li>选择"创建新的虚拟磁盘”，并设置磁盘容量，建议至少分配50GB，以确保有足够的空间用于系统安装和后续的应用部署。</li>
 <li>指定磁盘文件存放位置，并完成虚拟机的配置。</li>
</ol>
<h5>1.3 加载 CentOS 镜像</h5>
<p>在虚拟机配置完成后，需要加载 CentOS 7 的安装镜像。在虚拟机设置中，找到"CD/DVD (IDE)“设备，选择"使用ISO映像文件”，并浏览选择之前下载的 CentOS-7-x86_64-bin-DVD1.iso 文件。</p>
<h4>二、安装 CentOS 7</h4>
<h5>2.1 启动虚拟机</h5>
<p>加载镜像后，启动虚拟机。启动过程中，会出现 CentOS 的安装界面，通常会有一个倒计时，可以选择直接按回车（Enter）键跳过倒计时，进入安装界面。</p>
<h5>2.2 选择安装选项</h5>
<p>在安装界面上，选择第一项"Install CentOS 7"进行安装。</p>
<h5>2.3 设置语言</h5>
<p>进入安装界面后，首先设置语言。建议选择"English (United States)“以避免潜在的字符显示问题，但也可以根据个人需求选择中文或其他语言。</p>
<h5>2.4 配置安装选项</h5>
<p>接下来进入"INSTALLATION SUMMARY"安装总览界面，这里包含了 CentOS 7 安装的所有配置选项。</p>
<ul>
 <li><strong>DATE \&amp; TIME</strong>：设置时区和时间，选择"Asia/Shanghai"作为时区。</li>
 <li><strong>KEYBOARD</strong>：保持默认设置"English (US)“即可。</li>
 <li><strong>LANGUAGE SUPPORT</strong>：选择需要的语言支持，可以是默认的 English，也可以添加中文支持。</li>
 <li><strong>INSTALLATION SOURCE</strong>：安装资源通常选择"Local media"即本地媒体文件，因为已经通过 ISO 镜像加载了安装文件。</li>
 <li><strong>SOFTWARE SELECTION</strong>：选择需要安装的软件包。对于服务器，通常选择"Minimal install"或"Server with GUI"以最小化安装或带图形界面的服务器安装。如果需要桌面环境，可以选择"GNOME Desktop”。</li>
 <li><strong>INSTALLATION DESTINATION</strong>：选择安装位置并进行分区。可以选择自动分区（Automatically configure partitioning），也可以手动进行分区。手动分区时，建议至少创建三个分区：/boot（启动分区，大小约200MB）、swap（交换分区，大小通常是物理内存的1\~2倍）、/（根分区，剩余空间全部分配）。</li>
</ul>
<h5>2.5 设置网络和主机名</h5>
<p>在"NETWORK \&amp; HOST NAME"部分，设置网络连接和主机名。确保网络连接正常，并根据需要设置主机名。</p>
<h5>2.6 开始安装</h5>
<p>配置完成后，点击"Begin Installation"开始安装过程。在安装过程中，需要设置 root 用户的密码，并可以选择性地创建其他用户。</p>
<h4>三、安装完成后的配置</h4>
<h5>3.1 重启虚拟机</h5>
<p>安装完成后，点击"Reboot"重启虚拟机。重启后，根据安装时选择的界面类型（字符界面或图形界面），进行相应的登录和配置。</p>
<h5>3.2 登录系统</h5>
<p>在重启后的登录界面，输入 root 用户的用户名和密码登录系统### 3.3 登录系统后的基本配置</p>
<h5>3.3.1 更新系统</h5>
<p>登录系统后，首先推荐更新系统和所有已安装的包到最新版本，以确保系统的安全性和稳定性。可以通过以下命令进行：</p>
<pre><code class="prism language-bash">sudo yum update
</code></pre>
<p>该命令会列出所有可用的更新，并询问是否继续。输入<code>y</code>并回车以确认更新。</p>
<h5>3.3.2 配置防火墙</h5>
<p>CentOS 7 默认使用 firewalld 作为防火墙管理工具。根据服务器的用途，可能需要调整防火墙规则。例如，如果服务器将用作 Web 服务器，则需要允许 HTTP 和 HTTPS 流量。</p>
<pre><code class="prism language-bash">sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload
</code></pre>
<p>以上命令将永久性地为公共区域（public zone）添加 HTTP 和 HTTPS 服务的访问权限，并重新加载防火墙配置以应用更改。</p>
<h5>3.3.3 配置 SELinux</h5>
<p>SELinux（Security-Enhanced Linux）是 CentOS 7 中的一项安全功能，用于增强系统的安全性。SELinux 有三种模式：Enforcing、Permissive 和 Disabled。默认情况下，SELinux 设置为 Enforcing 模式，这意味着它会强制执行安全策略。</p>
<p>如果你发现 SELinux 导致某些服务无法正常运行，可以临时将其设置为 Permissive 模式来诊断问题，而不完全禁用它。</p>
<pre><code class="prism language-bash">sudo setenforce 0  # 设置为 Permissive 模式
# 或者，要永久更改，需要编辑 /etc/selinux/config 文件并重启系统
</code></pre>
<p>但请注意，在生产环境中，通常建议保持 SELinux 在 Enforcing 模式，以提供最佳的安全保护。</p>
<h5>3.3.4 安装额外的软件包</h5>
<p>根据需要，可能还需要安装额外的软件包。例如，如果计划使用 SSH 远程管理服务器，确保 openssh-server 已安装并运行。</p>
<pre><code class="prism language-bash">sudo yum install openssh-server
sudo systemctl start sshd
sudo systemctl enable sshd
</code></pre>
<h5>3.3.5 配置 SSH 密钥认证（可选）</h5>
<p>为了提高 SSH 访问的安全性，建议使用密钥认证代替密码认证。你可以生成 SSH 密钥对，并将公钥添加到服务器的 <code>~/.ssh/authorized_keys</code> 文件中。</p>
<p>在本地计算机上生成密钥对（如果尚未生成）：</p>
<pre><code class="prism language-bash">ssh-keygen -t rsa -b 4096
</code></pre>
<p>将公钥复制到服务器的 <code>~/.ssh/authorized_keys</code> 文件中：</p>
<pre><code class="prism language-bash">ssh-copy-id username@server_ip
</code></pre>
<p>替换 <code>username</code> 和 <code>server_ip</code> 为实际的用户名和服务器 IP 地址。</p>
<h5>3.3.6 配置系统时区</h5>
<p>虽然安装过程中已经设置了时区，但有时可能需要根据实际情况进行调整。可以使用 timedatectl 命令来查看和设置时区。</p>
<p>查看当前时区：</p>
<pre><code class="prism language-bash">timedatectl
</code></pre>
<p>设置新的时区（例如，设置为上海时区）：</p>
<pre><code class="prism language-bash">sudo timedatectl set-timezone Asia/Shanghai
</code></pre>
<h5>3.3.7 配置系统启动项</h5>
<p>使用 systemctl 命令可以管理系统服务（systemd 管理的服务）。你可以查看所有服务的状态、启动、停止或启用/禁用服务在系统启动时自动运行。</p>
<p>查看所有服务的状态：</p>
<pre><code class="prism language-bash">sudo systemctl list-units --type=service --all
</code></pre>
<p>启用/禁用服务（例如，禁用 httpd 服务）：</p>
<pre><code class="prism language-bash">sudo systemctl disable httpd
</code></pre>
<p>启动/停止服务（例如，启动 httpd 服务）：</p>
<pre><code class="prism language-bash">sudo systemctl start httpd
sudo systemctl stop httpd
</code></pre>
<h4>3.4 监控和维护</h4>
<p>安装和配置完成后，重要的是要定期监控系统的性能和安全性，并进行必要的维护。</p>
<ul>
 <li><strong>监控工具</strong>：安装和配置如 nagios、zabbix 或 Prometheus 等监控工具，以跟踪系统性能、网络流量、磁盘使用情况等。</li>
 <li><strong>日志管理</strong> ：定期查看和分析系统日志文件（如 <code>/var/log/messages</code>、<code>/var/log/secure</code> 等），以便及时发现潜在的问题。</li>
 <li><strong>安全审计</strong>：使用工具如 auditd 进行安全审计，记录系统上的安全相关事件。</li>
 <li><strong>备份和恢复</strong>：制定备份策略，定期备份重要数据，并测试恢复过程以确保在数据丢失或系统故障时能够迅速恢复。</li>
</ul>
<h4>3.5 结论</h4>
<p>CentOS 7 的安装和配置过程虽然相对复杂，但通过仔细规划和执行，可以确保系统的稳定性和安全性。以上步骤涵盖了从准备工作到系统配置完成的主要环节，但请注意，根据具体的服务器用途和需求，可能还需要进行其他额外的配置和优化。始终关注最新的安全更新和最佳实践，以确保你的服务器保持</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141105890</p>]]></description><guid isPermaLink="false">/archives/f76b0985-d6d7-451b-a839-da97927da624</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Fri, 27 Sep 2024 03:30:09 GMT</pubDate></item><item><title><![CDATA[Spring Boot整合SA-Token的使用详解]]></title><link>https://wiki.dgfygk.com/archives/2e3114ad-f8f7-4594-af55-bd1b13189939</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%E6%95%B4%E5%90%88SA-Token%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3&amp;url=/archives/2e3114ad-f8f7-4594-af55-bd1b13189939" width="1" height="1" alt="" style="opacity:0;">
<p>Spring Boot整合SA-Token的使用详解，涉及到SA-Token的基本介绍、整合步骤、配置、常用API以及实际使用场景等多个方面。以下将详细阐述这一过程，确保内容不少于2000字。</p>
<h4>一、SA-Token简介</h4>
<p>SA-Token是一个轻量级的Java权限认证框架，由国人开发，主要解决登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权等一系列权限相关问题。SA-Token以简单、优雅的方式完成系统的权限认证部分，让开发者能够专注于业务逻辑的开发，而无需过多关注权限控制的实现细节。</p>
<h4>二、Spring Boot整合SA-Token的步骤</h4>
<h5>1. 引入SA-Token依赖</h5>
<p>首先，你需要在Spring Boot项目的<code>pom.xml</code>文件中引入SA-Token的依赖。如果你使用的是Spring Boot 3.x版本，则需要引入<code>sa-token-spring-boot3-starter</code>；对于Spring Boot 2.x版本，则引入<code>sa-token-spring-boot-starter</code>。以Spring Boot 3.x为例，依赖配置如下：</p>
<pre><code class="prism language-xml">&lt;dependency&gt;
    &lt;groupId&gt;cn.dev33&lt;/groupId&gt;
    &lt;artifactId&gt;sa-token-spring-boot3-starter&lt;/artifactId&gt;
    &lt;version&gt;最新版本号&lt;/version&gt; &lt;!-- 请替换为最新的版本号 --&gt;
&lt;/dependency&gt;
</code></pre>
<p>注意：在编写本文时，SA-Token的最新版本可能已经更新，请访问<a href="https://github.com/dromara/sa-token">SA-Token的官方GitHub仓库</a>或<a href="https://sa-token.dev33.cn/">SA-Token的官方网站</a>获取最新的版本号。</p>
<h5>2. 配置SA-Token</h5>
<p>SA-Token的配置可以在<code>application.yml</code>或<code>application.properties</code>文件中进行。以下是一个<code>application.yml</code>的配置示例：</p>
<pre><code class="prism language-yaml">sa-token:
  token-name: satoken  # token名称(同时也是cookie名称)
  timeout: 2592000      # token有效期(单位:秒), 默认30天, -1代表永久有效
  active-timeout: 7200  # token临时有效期(单位:秒), 指定时间内无操作就视为token过期
  is-concurrent: true   # 是否允许同一账号多地同时登录
  is-share: true        # 在多人登录同一账号时, 是否共用一个token
  token-style: uuid     # token风格
  is-log: false         # 是否输出操作日志
  # 其他配置...
</code></pre>
<h5>3. 编写登录认证逻辑</h5>
<p>在Spring Boot项目中，你通常需要编写登录接口来处理用户的登录请求。SA-Token提供了<code>StpUtil.login(Object id)</code>方法来标记当前会话登录的账号ID。以下是一个简单的登录接口示例：</p>
<pre><code class="prism language-java">@RestController
@RequestMapping("/user")
public class UserController {


    @PostMapping("/login")
    public SaResult login(@RequestParam String username, @RequestParam String password) {

        // 假设这里通过数据库查询用户信息并验证密码
        // ...

        // 验证成功后，使用StpUtil.login(Object id)标记当前会话登录
        StpUtil.login(userId); // userId为验证成功后的用户ID

        return SaResult.ok("登录成功");
    }
}
</code></pre>
<p>注意：在实际项目中，你需要根据业务逻辑从数据库中查询用户信息并验证密码。上述代码中的<code>userId</code>是一个假设的变量，代表验证成功后的用户ID。</p>
<h5>4. 权限认证</h5>
<p>SA-Token提供了丰富的API来进行权限认证。首先，你需要实现<code>StpInterface</code>接口来定义如何获取一个账号所拥有的权限码集合和角色标识集合。然后，你可以使用<code>StpUtil</code>类中的方法来进行权限校验。</p>
<p>当然，让我们继续并清晰地完成<code>getRoleList</code>方法的实现，并简要介绍如何使用SA-Token进行权限校验。</p>
<h6>实现StpInterface接口</h6>
<pre><code class="prism language-java">@Component
public class StpInterfaceImpl implements StpInterface {


    @Override
    public List&lt;String&gt; getPermissionList(Object loginId, String loginType) {

        // 假设这里通过loginId（用户ID）从数据库或其他数据源查询用户的权限码集合
        // ...

        // 示例：返回一个包含权限码的列表
        List&lt;String&gt; permissions = new ArrayList&lt;&gt;();
        permissions.add("user.add");
        permissions.add("user.update");
        permissions.add("user.delete");
        // 根据实际情况添加更多权限码

        return permissions;
    }

    @Override
    public List&lt;String&gt; getRoleList(Object loginId, String loginType) {

        // 假设这里通过loginId（用户ID）从数据库或其他数据源查询用户的角色标识集合
        // ...

        // 示例：返回一个包含角色标识的列表
        List&lt;String&gt; roles = new ArrayList&lt;&gt;();
        roles.add("admin");
        roles.add("user");
        // 根据实际情况添加更多角色标识

        return roles;
    }
}
</code></pre>
<h5>使用SA-Token进行权限校验</h5>
<p>在Spring Boot项目中，你可以使用SA-Token提供的<code>StpUtil</code>类中的方法来进行权限校验。以下是一些常用的权限校验方法：</p>
<ul>
 <li><code>checkPermission(String permission)</code>: 校验当前账号是否拥有指定权限码</li>
 <li><code>checkRole(String role)</code>: 校验当前账号是否拥有指定角色标识</li>
 <li><code>hasPermission(String permission)</code>: 判断当前账号是否拥有指定权限码（不抛出异常）</li>
 <li><code>hasRole(String role)</code>: 判断当前账号是否拥有指定角色标识（不抛出异常）</li>
</ul>
<h6>示例：在Controller中使用权限校验</h6>
<pre><code class="prism language-java">@RestController
@RequestMapping("/user")
public class UserController {


    // ... 其他方法 ...

    @PostMapping("/add")
    public SaResult addUser(@RequestBody User user) {

        // 校验当前账号是否拥有"user.add"权限
        StpUtil.checkPermission("user.add");

        // 如果校验通过，则执行添加用户的逻辑
        // ...

        return SaResult.ok("用户添加成功");
    }

    @PutMapping("/update/{id}")
    public SaResult updateUser(@PathVariable Long id, @RequestBody User user) {

        // 校验当前账号是否拥有"user.update"权限
        StpUtil.checkPermission("user.update");

        // 如果校验通过，则执行更新用户的逻辑
        // ...

        return SaResult.ok("用户更新成功");
    }

    // ... 其他需要权限校验的方法 ...
}
</code></pre>
<p>在上述示例中，<code>addUser</code>方法通过<code>StpUtil.checkPermission("user.add")</code>来校验当前登录的账号是否拥有"user.add"权限。如果当前账号没有该权限，则SA-Token会抛出异常，并默认由Spring Boot的全局异常处理器（如果你配置了的话）来处理这个异常，通常返回给前端一个错误响应。</p>
<p>同样地，<code>updateUser</code>方法通过<code>StpUtil.checkPermission("user.update")</code>来校验当前账号是否拥有"user.update"权限。</p>
<h4>三、其他常用功能</h4>
<p>SA-Token还提供了许多其他功能，如：</p>
<ul>
 <li><strong>会话管理</strong>：支持踢出其他设备登录、获取当前会话信息等功能。</li>
 <li><strong>OAuth2.0</strong>：支持OAuth2.0第三方登录。</li>
 <li><strong>分布式Session</strong>：支持基于Redis的分布式Session会话共享。</li>
 <li><strong>注解鉴权</strong> ：通过<code>@SaCheckLogin</code>、<code>@SaCheckPermission</code>等注解实现方法级别的权限校验。</li>
 <li><strong>自动续签</strong>：支持Token的自动续签功能，避免用户频繁登录。</li>
</ul>
<h4>四、总结</h4>
<p>通过整合SA-Token到Spring Boot项目中，你可以轻松实现登录认证、权限认证、会话管理等一系列权限控制功能。SA-Token以其简单、优雅的设计理念和丰富的功能，为开发者提供了极大的便利。希望本文的详细介绍能够帮助你更好地理解和使用SA-Token。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141018741</p>]]></description><guid isPermaLink="false">/archives/2e3114ad-f8f7-4594-af55-bd1b13189939</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 03:29:53 GMT</pubDate></item><item><title><![CDATA[AI大模型中的RAG：检索增强生成技术深度解析]]></title><link>https://wiki.dgfygk.com/archives/4e16cb65-26e8-48ee-89a4-6701224bfb53</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AI%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%B8%AD%E7%9A%84RAG%EF%BC%9A%E6%A3%80%E7%B4%A2%E5%A2%9E%E5%BC%BA%E7%94%9F%E6%88%90%E6%8A%80%E6%9C%AF%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90&amp;url=/archives/4e16cb65-26e8-48ee-89a4-6701224bfb53" width="1" height="1" alt="" style="opacity:0;">
<h4>AI大模型中的RAG：检索增强生成技术深度解析</h4>
<p>在人工智能（AI）的浩瀚领域中，大型语言模型（Large Language Models, LLMs）如GPT系列、BERT等，以其强大的文本生成与理解能力，引领着自然语言处理（NLP）的新纪元。然而，这些模型尽管在知识广度和深度上取得了显著进展，但在处理特定事实性信息或知识密集型任务时，仍面临一定挑战。为了弥补这一不足，检索增强生成（Retrieval-Augmented Generation, RAG）技术应运而生，成为连接LLMs与大规模知识库的桥梁，显著提升了模型在生成过程中的准确性和丰富性。本文将深入探讨RAG技术的原理、架构、应用、挑战及未来展望，力求全面展现这一前沿技术的魅力。</p>
<h5>一、RAG技术背景与动机</h5>
<p>随着互联网的飞速发展，人类社会的知识总量呈爆炸式增长，如何有效地从海量信息中检索并整合所需知识，成为AI领域亟待解决的问题。传统的LLMs虽然能够生成流畅、连贯的文本，但在涉及具体事实、最新数据或专业领域知识时，往往缺乏准确性和时效性。这是因为LLMs的训练数据虽然庞大，但终究受限于数据集的静态性和有限性，难以实时反映世界的变化。</p>
<p>为了克服这一局限性，RAG技术通过将检索机制与生成模型相结合，为LLMs引入了外部知识源，如互联网、数据库或专业知识图谱。这种结合使得模型在生成文本时，能够先根据输入查询检索相关信息，然后基于检索结果进行生成，从而极大地提高了生成的准确性和信息丰富度。</p>
<h5>二、RAG技术原理与架构</h5>
<h6>2.1 技术原理</h6>
<p>RAG技术的核心在于将检索过程无缝集成到生成模型的流程中。具体来说，当用户输入一个查询或任务时，RAG系统首先利用一个检索器（Retriever）从大规模知识库中检索出与查询最相关的文档或片段。然后，这些检索结果作为输入被送入一个生成器（Generator），通常是基于Transformer结构的LLM，用于生成最终的文本输出。生成器在生成过程中会综合考虑查询、检索结果以及自身的语言模型知识，以产生既符合语言规范又富含相关信息的文本。</p>
<h6>2.2 系统架构</h6>
<p>RAG系统通常包含以下几个关键组件：</p>
<ul>
 <li><strong>检索器</strong>：负责从知识库中检索与查询相关的文档或信息。检索器可以是基于关键词的搜索引擎，也可以是更复杂的语义搜索系统，如基于向量相似度的检索方法。</li>
 <li><strong>知识库</strong>：存储海量文本、图像、视频等多种类型的知识资源，是检索器搜索的目标对象。知识库的选择直接影响RAG系统的性能，需要根据应用场景和需求进行定制。</li>
 <li><strong>生成器</strong>：基于LLM的文本生成模型，负责根据查询和检索结果生成最终的文本输出。生成器需要具备良好的语言建模能力和知识整合能力，以确保生成文本的准确性和流畅性。</li>
 <li><strong>融合机制</strong>：将检索结果与生成模型有效融合的机制。这包括如何将检索结果编码为生成模型可理解的格式、如何控制检索结果对生成过程的影响程度等。</li>
</ul>
<h5>三、RAG技术的应用场景</h5>
<p>RAG技术凭借其独特的优势，在多个领域展现出了广泛的应用前景。</p>
<ul>
 <li><strong>问答系统</strong>：在问答系统中，RAG技术能够显著提升回答的准确性和深度。通过检索相关文档或知识图谱，生成器能够基于丰富的信息源生成更加准确、全面的回答。</li>
 <li><strong>创意写作</strong>：在文学创作、新闻报道等领域，RAG技术可以辅助作者快速获取相关素材和灵感，提高创作效率和质量。通过检索相关领域的资料，生成器能够生成具有创意和深度的文本内容。</li>
 <li><strong>知识推理与解释</strong>：在知识图谱构建、逻辑推理等任务中，RAG技术能够结合检索到的知识片段进行推理和解释，提高模型的解释性和可信赖性。</li>
 <li><strong>个性化推荐</strong>：在电商、社交媒体等领域，RAG技术可以根据用户的查询和历史行为检索相关信息，生成个性化的推荐内容或建议。</li>
</ul>
<h5>四、面临的挑战与解决方案</h5>
<p>尽管RAG技术在多个方面展现出了巨大的潜力，但在实际应用中仍面临诸多挑战。</p>
<ul>
 <li><strong>检索效率与准确性</strong>：如何在海量知识库中快速准确地检索到相关信息是RAG技术面临的首要挑战。解决方案包括优化检索算法、提高索引效率、引入多模态检索等。</li>
 <li><strong>生成质量</strong>：如何确保生成文本既符合语言规范又富含相关信息是另一个重要挑战。这需要不断提升生成模型的性能和知识整合能力，同时加强对生成文本的质量评估和反馈机制。</li>
 <li><strong>知识源的选择与更新</strong>：知识库的选择和更新直接影响RAG系统的性能。如何构建一个全面、准确、及时更新的知识库是亟待解决的问题。这需要综合考虑数据来源的可靠性、更新频率以及数据处理的复杂度等因素。</li>
 <li><strong>隐私与伦理问题</strong>：在处理涉及个人隐私或敏感信息的任务时，RAG技术还需面临隐私保护和伦理道德的考量。检索过程中可能涉及到用户数据的收集、处理和分析，这要求系统必须严格遵守相关法律法规，确保用户数据的隐私性和安全性。同时，生成的文本内容也需要遵循社会伦理规范，避免产生误导性、歧视性或有害的信息。</li>
</ul>
<p>为了解决这些问题，可以从以下几个方面入手：</p>
<ol>
 <li>
  <p><strong>加强数据加密与隐私保护</strong>：在数据收集、存储和传输过程中，采用先进的加密技术，确保用户数据不被非法获取或滥用。同时，明确告知用户数据的使用方式和目的，获取用户的明确授权。</p>
 </li>
 <li>
  <p><strong>建立严格的伦理审查机制</strong>：对于生成的文本内容，建立多层次的伦理审查机制，包括自动审核和人工审核相结合的方式。自动审核可以通过预设的伦理规则库来过滤掉可能违反伦理的内容，而人工审核则可以提供更深入、更细致的判断。</p>
 </li>
 <li>
  <p><strong>促进透明度和可解释性</strong>：提高RAG系统的透明度和可解释性，让用户和监管机构能够清晰地了解系统的运作方式和决策依据。这有助于建立用户对系统的信任，并促进对系统行为的监督和管理。</p>
 </li>
 <li>
  <p><strong>持续学习与更新</strong>：RAG系统需要不断学习和更新，以适应不断变化的知识库和用户需求。通过引入在线学习机制，系统可以实时地根据用户的反馈和新的数据来优化检索和生成策略，提高系统的性能和准确性。</p>
 </li>
 <li>
  <p><strong>跨领域合作与标准制定</strong>：RAG技术的发展需要跨领域的合作与交流，包括计算机科学、法律、伦理学等多个学科。通过制定统一的标准和规范，可以促进不同系统之间的互操作性和可比性，推动RAG技术的健康发展。</p>
 </li>
</ol>
<h5>五、未来展望</h5>
<p>随着技术的不断进步和应用场景的不断拓展，RAG技术有望在更多领域发挥重要作用。未来，我们可以期待以下几个方面的发展：</p>
<ol>
 <li>
  <p><strong>深度融合与协同工作</strong>：RAG技术将与其他AI技术（如机器翻译、语音识别、图像识别等）进行更深入的融合和协同工作，形成更加智能、全面的解决方案。</p>
 </li>
 <li>
  <p><strong>知识库的智能化与个性化</strong>：知识库将逐渐实现智能化和个性化，根据用户的兴趣、偏好和历史行为等信息，动态地调整和优化检索结果和生成内容。</p>
 </li>
 <li>
  <p><strong>实时性与动态性</strong>：RAG系统将更加注重实时性和动态性，能够实时地反映世界的变化和用户的需求，为用户提供更加及时、准确的信息服务。</p>
 </li>
 <li>
  <p><strong>多模态生成</strong>：除了文本生成外，RAG技术还将逐渐扩展到图像、视频等多模态生成领域，实现更加丰富、多样的信息表达方式。</p>
 </li>
 <li>
  <p><strong>跨语言与跨文化</strong>：随着全球化的不断深入，RAG技术将更加注重跨语言和跨文化的能力建设，为不同语言和文化的用户提供更加便捷、准确的信息服务。</p>
 </li>
</ol>
<p>总之，RAG技术作为连接LLMs与大规模知识库的桥梁，在提升AI系统生成文本的准确性和丰富性方面展现出了巨大的潜力。随着技术的不断发展和完善，我们有理由相信RAG技术将在未来发挥更加重要的作用，为人类社会的进步和发展贡献更多的智慧和力量。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140986522</p>]]></description><guid isPermaLink="false">/archives/4e16cb65-26e8-48ee-89a4-6701224bfb53</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:29:29 GMT</pubDate></item><item><title><![CDATA[Apache Flink开发时选择Java还是Scala作为编程语言]]></title><link>https://wiki.dgfygk.com/archives/7126a600-dc66-4442-8bcb-08b4ac01ec7a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Apache%20Flink%E5%BC%80%E5%8F%91%E6%97%B6%E9%80%89%E6%8B%A9Java%E8%BF%98%E6%98%AFScala%E4%BD%9C%E4%B8%BA%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80&amp;url=/archives/7126a600-dc66-4442-8bcb-08b4ac01ec7a" width="1" height="1" alt="" style="opacity:0;">
<p>在Apache Flink的开发过程中，选择Java还是Scala作为编程语言是一个重要的决策点。这两种语言各有其独特的优势和特点，适合不同的开发场景和需求。以下是对这一选择的详细探讨，旨在帮助开发者更好地理解并做出合理的选择。</p>
<h4>一、Apache Flink简介</h4>
<p>Apache Flink是一个开源的分布式流处理框架，用于处理无界和有界数据流。它以其高吞吐量、低延迟、精确的状态管理和强大的容错能力而闻名。Flink支持多种编程语言，其中Java和Scala是最常用的两种。</p>
<h4>二、Java作为开发语言的优势</h4>
<ol>
 <li>
  <p><strong>广泛的社区支持和丰富的资源</strong></p>
  <p>Java作为一门成熟且广泛使用的编程语言，拥有庞大的开发者社区和丰富的资源。这包括大量的教程、文档、库和框架，以及广泛的社区支持。对于已经熟悉Java的开发者来说，这意味着更容易找到相关的资料和解决方案，降低了学习成本和开发难度。</p>
 </li>
 <li>
  <p><strong>企业级支持和稳定性</strong></p>
  <p>许多大型企业都使用Java进行软件开发，这使得Java在企业级应用中具有极高的稳定性和可靠性。在Flink项目中，Java作为最初支持的语言之一，已经得到了广泛的验证和应用。选择Java作为开发语言，可以确保项目的稳定性和可维护性。</p>
 </li>
 <li>
  <p><strong>与Flink内部组件的紧密集成</strong></p>
  <p>由于Flink本身是用Java编写的，因此Java与Flink内部组件的交互更为便捷。这意味着使用Java开发Flink应用程序时，可以更容易地利用Flink的底层优化和特性，提高开发效率和性能。</p>
 </li>
 <li>
  <p><strong>面向对象编程的优势</strong></p>
  <p>Java是一种面向对象的编程语言，具有强大的类和对象支持。这使得Java在复杂的面向对象设计中表现良好，适合构建大型企业级应用。在Flink开发中，Java的这种特性可以帮助开发者更好地组织和管理代码，提高代码的可读性和可维护性。</p>
 </li>
</ol>
<h4>三、Scala作为开发语言的优势</h4>
<ol>
 <li>
  <p><strong>简洁性和函数式编程特性</strong></p>
  <p>Scala是一种多范式编程语言，既支持面向对象编程也支持函数式编程。Scala的语法更加简洁，通常可以用更少的代码完成同样的任务。此外，Scala支持高阶函数、模式匹配等函数式编程特性，这些特性非常适合处理流式数据。在Flink开发中，Scala的这些特性可以帮助开发者编写更加高效和简洁的代码。</p>
 </li>
 <li>
  <p><strong>与大数据生态的兼容性</strong></p>
  <p>Scala与Java兼容，可以直接调用Java类和库。这使得Scala能够充分利用Java生态系统的丰富资源。在大数据领域，Scala与Spark等框架有着紧密的集成，这使得Scala成为处理大数据流的理想选择。在Flink开发中，Scala的这种兼容性也为其带来了额外的优势。</p>
 </li>
 <li>
  <p><strong>类型推断和强大的类型系统</strong></p>
  <p>Scala具有强大的类型推断能力，可以自动推断变量和函数的类型，减少了代码中显式类型声明的冗余。此外，Scala的静态类型系统还支持类型类、隐式转换等高级特性，这些特性有助于编写更加安全和可靠的代码。在Flink开发中，Scala的这些特性可以帮助开发者减少代码中的错误和漏洞。</p>
 </li>
 <li>
  <p><strong>适合函数式编程和数据流处理</strong></p>
  <p>Scala的函数式编程特性使得它非常适合处理数据流。在Flink中，数据流处理是一个核心功能，而Scala的函数式编程特性可以使得数据流处理逻辑更加清晰和简洁。此外，Scala还支持并发编程和异步操作，这些特性对于处理高并发数据流非常有用。</p>
 </li>
</ol>
<h4>四、选择Java还是Scala的考虑因素</h4>
<ol>
 <li>
  <p><strong>团队背景和技能</strong></p>
  <p>团队中成员的技能和背景是选择开发语言的重要因素。如果团队中大部分成员都熟悉Java，那么选择Java作为开发语言将更容易上手，减少学习成本。相反，如果团队中有对Scala有深入了解的成员，那么选择Scala可能会带来更高的开发效率。</p>
 </li>
 <li>
  <p><strong>项目需求和特性</strong></p>
  <p>项目的具体需求和特性也是选择开发语言的关键因素。如果项目需要处理大量的数据流，并且需要利用函数式编程的特性来提高开发效率，那么Scala可能是更好的选择。如果项目更注重稳定性和企业级支持，那么Java可能更合适。</p>
 </li>
 <li>
  <p><strong>长期维护和支持</strong></p>
  <p>在选择开发语言时，还需要考虑长期维护和支持的问题。Java作为一门成熟且广泛使用的编程语言，具有长期的维护和支持保障。而Scala虽然在一些特定领域（如大数据和函数式编程）具有优势，但其长期趋势和稳定性可能相对不确定。</p>
 </li>
 <li>
  <p><strong>性能和效率</strong></p>
  <p>虽然Java和Scala在性能上相差不大（因为它们都编译成JVM字节码），但Scala的一些高级特性（如模式匹配和隐式转换）可能会带来额外的运行时开销。然而，这些差异通常不会对整体性能产生显著影响。因此，在选择开发语言时，性能和效率并不是决定性的因素，但也需要考虑在内。</p>
 </li>
</ol>
<h4>五、结论</h4>
<p>综上所述，选择Java还是Scala作为Apache Flink的开发语言取决于多个因素，包括团队背景、项目需求、长期维护和支持以及性能和效率等。并且对于已经熟悉Java并且注重企业级支持、稳定性和广泛社区资源的团队来说，Java可能是更自然和更安全的选择。Java的面向对象特性和与Flink内部组件的紧密集成，使得在开发复杂数据流处理应用时能够更加得心应手。</p>
<p>然而，如果团队对Scala有深入的了解，或者项目特别需要利用Scala的函数式编程特性和简洁性来优化数据流处理逻辑，那么Scala可能是一个更有吸引力的选项。Scala的语法简洁性、类型推断能力以及与大数据生态的兼容性，都为处理大规模数据流提供了强大的支持。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140944696</p>]]></description><guid isPermaLink="false">/archives/7126a600-dc66-4442-8bcb-08b4ac01ec7a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 03:29:10 GMT</pubDate></item><item><title><![CDATA[C++中的错误处理机制：异常]]></title><link>https://wiki.dgfygk.com/archives/0e16877a-d146-4b53-a489-8378b31c151c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=C%2B%2B%E4%B8%AD%E7%9A%84%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6%EF%BC%9A%E5%BC%82%E5%B8%B8&amp;url=/archives/0e16877a-d146-4b53-a489-8378b31c151c" width="1" height="1" alt="" style="opacity:0;">
<h4>C++中的错误处理机制：异常</h4>
<p>在软件开发中，错误处理是确保程序稳定性和健壮性的关键环节。C++作为一种高级编程语言，提供了比C语言更为灵活和强大的错误处理机制——异常处理。异常处理机制允许程序在运行时检测到错误或异常情况时，能够以一种结构化和可预测的方式作出响应，从而提高代码的可读性、可维护性和异常安全性。</p>
<h5>异常处理的基本概念</h5>
<p>异常处理是一种编程范式，它通过抛出和捕获异常对象来处理运行时错误。在C++中，异常处理主要依赖于几个关键概念：异常抛出（Throw）、异常捕获（Catch）、异常传递（Exception Propagation）以及异常规范（Exception Specification，尽管自C++11起已不再推荐使用）。</p>
<h6>异常抛出（Throw）</h6>
<p>当程序执行过程中遇到错误或异常情况时，可以使用<code>throw</code>关键字来抛出一个异常对象。这个对象通常是派生自<code>std::exception</code>类的异常类对象，用于表示具体的错误状态。抛出的异常对象可以是任意类型的对象，但通常建议使用继承自<code>std::exception</code>的类，以便利用标准异常类的特性。</p>
<p>例如，自定义一个异常类<code>MyException</code>：</p>
<pre><code class="prism language-cpp">#include &lt;iostream&gt;
#include &lt;exception&gt;

class MyException : public std::exception {

private:
    std::string message;

public:
    MyException(const std::string&amp; msg) : message(msg) {
   }

    const char* what() const noexcept override {

        return message.c_str();
    }
};

void myFunction() {

    throw MyException("Something went wrong!");
}
</code></pre>
<h6>异常捕获（Catch）</h6>
<p>异常捕获通过<code>try-catch</code>语句块实现。<code>try</code>块用于包裹可能抛出异常的代码片段，而<code>catch</code>块则用于捕获并处理异常。可以根据需要在<code>try</code>块中添加多个<code>catch</code>块，以捕获并处理不同类型的异常。</p>
<pre><code class="prism language-cpp">try {

    myFunction();
} catch (const MyException&amp; e) {

    std::cerr &lt;&lt; "Caught MyException: " &lt;&lt; e.what() &lt;&lt; std::endl;
} catch (const std::exception&amp; e) {

    std::cerr &lt;&lt; "Caught std::exception: " &lt;&lt; e.what() &lt;&lt; std::endl;
} catch (...) {

    std::cerr &lt;&lt; "Caught unknown exception" &lt;&lt; std::endl;
}
</code></pre>
<p>在上面的例子中，<code>try</code>块中调用了<code>myFunction</code>，该函数可能抛出<code>MyException</code>类型的异常。第一个<code>catch</code>块尝试捕获<code>MyException</code>类型的异常，第二个<code>catch</code>块捕获所有继承自<code>std::exception</code>的异常（但非<code>MyException</code>），而最后一个<code>catch(...)</code>块则捕获所有类型的异常，作为一个兜底处理。</p>
<h6>异常传递（Exception Propagation）</h6>
<p>当异常在函数内部没有被捕获时，它会被传递给调用该函数的地方，并继续向上层函数传递，直到找到匹配的<code>catch</code>块或程序终止。这个过程称为异常传递或栈展开（Stack Unwinding）。</p>
<h5>C++标准异常类</h5>
<p>C++标准库提供了一系列标准异常类，用于表示各种常见的错误或异常情况。这些异常类都是从<code>std::exception</code>类继承而来的，提供了一种标准化的方式来处理异常情况。</p>
<ul>
 <li><code>std::logic_error</code>：表示逻辑错误，即程序员编程错误导致的异常情况。</li>
 <li><code>std::invalid_argument</code>：表示传递给函数的参数无效。</li>
 <li><code>std::length_error</code>：表示容器超出了其最大允许长度。</li>
 <li><code>std::out_of_range</code>：表示访问容器元素时超出了有效范围。</li>
 <li><code>std::runtime_error</code>：表示运行时错误，通常是由于程序运行环境导致的异常情况。</li>
 <li><code>std::overflow_error</code>：表示算术运算溢出。</li>
 <li><code>std::underflow_error</code>：表示算术运算下溢出。</li>
 <li><code>std::range_error</code>：表示数值超出了可表示的范围。</li>
 <li><code>std::bad_alloc</code>：表示内存分配失败，通常是由于内存耗尽导致的异常情况。</li>
 <li><code>std::bad_cast</code>：表示类型转换失败，通常是由于动态类型转换失败导致的异常情况。</li>
 <li><code>std::bad_typeid</code>：表示类型标识符操作失败，通常是由于<code>typeid</code>运算符无法识别类型导致的异常情况。</li>
</ul>
<h5>异常处理的最佳实践</h5>
<ol>
 <li>
  <p><strong>只在必要的情况下使用异常</strong>：异常处理是有开销的，因此在性能敏感的代码或经常执行的代码中，应避免过度使用异常。</p>
 </li>
 <li>
  <p><strong>使用具体的异常类</strong>：为不同类型的异常定义具体的异常类，并根据需要捕获和处理这些异常。这样可以提高异常处理的粒度，使代码更具可读性和可维护性。</p>
 </li>
 <li>
  <p><strong>在异常处理器中进行适当的清理和资源释放</strong> ：在<code>catch</code>块中，不仅要处理异常本身，还要确保进行必要的资源清理和释放工作，比如关闭文件句柄、释放内存等。这有助于防止资源泄露和其他潜在问题。</p>
 </li>
 <li>
  <p><strong>避免使用异常规范（自C++11起已弃用）</strong> ：C++早期版本中引入了异常规范，用于指定函数可能抛出的异常类型。然而，由于实践中这些规范往往被忽略或错误使用，且编译器很难进行有效检查，因此C++11起废除了异常规范的语法，并引入了<code>noexcept</code>关键字作为更现代的替代方案。<code>noexcept</code>用于指明函数是否抛出异常，对于优化和异常安全性都有重要意义。</p>
 </li>
 <li>
  <p><strong>使用<code>noexcept</code></strong> ：对于不会抛出异常的函数，应使用<code>noexcept</code>进行标记。这不仅可以提高程序的性能（因为编译器可以据此进行更高效的优化），还可以使异常处理逻辑更清晰。同时，如果<code>noexcept</code>函数确实抛出了异常，程序会立即调用<code>std::terminate()</code>终止，这有助于快速定位问题。</p>
 </li>
 <li>
  <p><strong>避免在析构函数中抛出异常</strong> ：析构函数在对象生命周期结束时自动调用，用于释放资源。如果在析构函数中抛出异常，并且该异常在析构函数的调用过程中未被捕获，那么会导致程序调用<code>std::terminate()</code>终止。因此，析构函数应该设计为不抛出异常，或者至少能够安全地处理自己抛出的异常。</p>
 </li>
 <li>
  <p><strong>考虑使用RAII（Resource Acquisition Is Initialization）</strong>：RAII是一种在C++中管理资源（如动态内存、文件句柄、互斥锁等）的惯用法则。通过将对象的生命周期与资源的获取和释放绑定在一起，可以确保在对象被销毁时自动释放资源，从而避免资源泄露。RAII还可以与异常处理无缝结合，因为即使发生异常，对象的析构函数也会自动被调用，从而释放资源。</p>
 </li>
 <li>
  <p><strong>记录异常信息</strong>：在捕获异常时，除了立即处理异常外，还应考虑将异常信息记录下来，以便进行后续的问题分析和调试。这可以通过将异常信息写入日志文件、发送警报通知给相关人员或将异常信息显示在用户界面上等方式实现。</p>
 </li>
 <li>
  <p><strong>设计可测试的异常处理代码</strong>：为了确保异常处理代码的正确性和健壮性，应设计可测试的场景来验证异常处理逻辑。这包括模拟各种可能导致异常的情况，并验证异常是否被正确捕获和处理。</p>
 </li>
 <li>
  <p><strong>遵循异常安全保证</strong> ：在编写涉及异常的代码时，应考虑代码的异常安全保证级别。通常有三种级别的异常安全保证：基本保证（不泄露资源，但状态可能不一致）、强保证（不泄露资源，且保持状态一致）和不抛出保证（不泄露资源，保持状态一致，且不抛出异常，这通常通过<code>noexcept</code>实现）。在设计函数和类时，应明确其异常安全保证级别，并据此编写代码。</p>
 </li>
</ol>
<p>综上所述，C++中的异常处理机制是一种强大而灵活的错误处理手段。通过合理使用异常处理机制，并结合上述最佳实践，可以编写出更健壮、更易于维护的C++程序。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140986297</p>]]></description><guid isPermaLink="false">/archives/0e16877a-d146-4b53-a489-8378b31c151c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 03:28:53 GMT</pubDate></item><item><title><![CDATA[GPT-5：未来已来，你准备好了吗]]></title><link>https://wiki.dgfygk.com/archives/a848d73a-ba00-4bc5-98d6-1c0b8b640a7f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=GPT-5%EF%BC%9A%E6%9C%AA%E6%9D%A5%E5%B7%B2%E6%9D%A5%EF%BC%8C%E4%BD%A0%E5%87%86%E5%A4%87%E5%A5%BD%E4%BA%86%E5%90%97&amp;url=/archives/a848d73a-ba00-4bc5-98d6-1c0b8b640a7f" width="1" height="1" alt="" style="opacity:0;">
<p><strong>GPT-5：未来已来，你准备好了吗？</strong></p>
<p>在人工智能的浩瀚星空中，自然语言处理（NLP）技术如同璀璨星辰，不断引领着技术革新的浪潮。而在这股浪潮中，OpenAI的GPT（Generative Pre-trained Transformer）系列模型无疑是最为耀眼的明星之一。随着GPT-3的横空出世，其强大的文本生成能力和广泛的应用前景震撼了整个科技界。如今，当我们的目光聚焦于即将到来的GPT-5时，不禁要问：未来已来，我们真的准备好了吗？</p>
<h4>一、GPT系列的前世今生</h4>
<h5>GPT-1：初探生成式预训练</h5>
<p>GPT-1是GPT系列的开山之作，于2018年由OpenAI推出。它采用了Transformer架构中的解码器部分，通过大规模无监督语料库的预训练，学会了丰富的语言知识和上下文理解能力。GPT-1的出现，标志着生成式预训练语言模型（Generative Pre-trained Language Model）时代的开启，为后续的模型发展奠定了坚实基础。</p>
<h5>GPT-2：规模与能力的飞跃</h5>
<p>GPT-2在GPT-1的基础上进行了显著扩展，不仅增大了模型规模，还改进了训练策略，使得其生成文本的质量和多样性有了质的飞跃。GPT-2的出现，让人们对生成式语言模型的能力有了全新的认识，同时也引发了关于模型安全、伦理和社会影响的广泛讨论。</p>
<h5>GPT-3：颠覆性的存在</h5>
<p>如果说GPT-1和GPT-2是探索与尝试，那么GPT-3无疑是颠覆性的存在。GPT-3拥有惊人的1750亿参数，是迄今为止最大的语言模型之一。其强大的文本生成能力、零样本学习和少样本学习能力，让GPT-3能够在各种NLP任务中展现出卓越的性能，从文本创作到知识问答，从代码编写到聊天对话，几乎无所不能。GPT-3的出现，不仅推动了NLP技术的快速发展，也激发了人们对未来AI应用的无限遐想。</p>
<h4>二、GPT-5：未来的展望与挑战</h4>
<h5>规模与能力的极限探索</h5>
<p>随着计算能力和数据资源的不断提升，GPT-5在模型规模上无疑将再次实现飞跃。更大的模型意味着更强的表达能力和更丰富的知识库，这将使GPT-5在文本生成、知识推理、情感分析等方面的能力得到进一步提升。然而，模型规模的扩大也带来了诸多挑战，如训练成本高昂、能耗巨大、计算资源稀缺等。如何在保证模型性能的同时，降低训练成本和环境影响，是GPT-5需要面对的重要问题。</p>
<h5>技术创新的持续推动</h5>
<p>GPT-5不仅在规模上会有所突破，更可能在技术创新上带来新的飞跃。例如，通过引入更先进的算法和架构，如注意力机制的改进、多模态融合、知识图谱的整合等，进一步提升模型的智能化水平和泛化能力。此外，GPT-5还可能在可解释性、鲁棒性、隐私保护等方面取得重要进展，以更好地满足实际应用的需求和法规要求。</p>
<h5>应用场景的广泛拓展</h5>
<p>随着GPT-5能力的不断提升，其应用场景也将得到广泛拓展。在教育领域，GPT-5可以为学生提供个性化的学习资源和辅导服务；在医疗领域，它可以帮助医生进行病例分析、药物推荐等工作；在金融领域，GPT-5可以辅助进行风险评估、投资策略制定等决策支持。此外，GPT-5还可能在创意产业、法律服务、科研支持等多个领域发挥重要作用，推动各行各业的智能化转型。</p>
<h5>伦理与社会的深刻影响</h5>
<p>GPT-5的出现，无疑将对伦理、法律、社会等方面产生深远影响。一方面，GPT-5的强大能力将为社会带来巨大便利和价值；另一方面，其潜在的滥用风险也不容忽视。例如，恶意用户可能利用GPT-5生成虚假信息、进行网络诈骗等行为；同时，GPT-5的广泛应用也可能加剧就业市场的竞争和不平等问题。因此，在推动GPT-5发展的同时，我们也需要加强监管和引导，确保其健康、可持续地发展。</p>
<h4>三、我们准备好了吗？</h4>
<p>面对即将到来的GPT-5，我们是否已经做好了充分的准备？这不仅是一个技术问题，更是一个涉及伦理、法律、社会等多个层面的复杂问题。</p>
<h5>技术层面</h5>
<p>从技术层面来看，我们需要不断提升计算能力和数据存储能力，以支持GPT-5等大规模模型的训练和推理。同时，我们还需要加强算法和架构的创新研究，以应对模型规模扩大带来的挑战。此外，我们还需要加强模型的可解释性、鲁棒性和隐私保护等方面的研究，以确保模型的可靠性和安全性。</p>
<h5>伦理与法律层面</h5>
<p>在伦理与法律层面，我们需要加强对GPT-5等AI技术的监管和引导。一方面，我们需要制定和完善相关法律法规和政策标准，明确AI技术的使用范围和限制条件；另一方面，我们还需要加强伦理教育和宣传引导工作，提高公众对AI技术的认识和理解水平。同时，我们还需要建立健全AI技术的评估机制和问责机制，以确保其健康、可持续地发展。</p>
<h4>社会层面</h4>
<p>在社会层面，GPT-5的到来将深刻影响我们的工作、学习和生活方式，要求我们做出相应的调整和准备。</p>
<h5>教育与培训</h5>
<p>随着GPT-5等AI技术的普及，许多传统行业和职业将面临转型或消失的风险。这意味着我们需要不断更新自己的知识和技能，以适应新的就业市场需求。教育系统需要更加灵活地调整课程设置和教学内容，注重培养学生的创新思维、批判性思维和解决问题的能力，而非仅仅传授书本知识。同时，职业培训将成为更加重要的领域，帮助人们快速掌握新技能，适应职场变化。</p>
<h5>就业市场</h5>
<p>GPT-5的广泛应用将可能引发就业市场的结构性变革。一方面，它可能会取代一些低技能、重复性高的工作岗位，导致部分人群失业；另一方面，它也将创造新的就业机会，特别是在AI技术、数据分析、创意设计等领域。因此，我们需要关注就业市场的动态变化，及时调整自己的职业规划和发展方向。政府和企业也需要采取措施，为受影响的劳动者提供转岗培训和再就业支持。</p>
<h5>社会结构与人际关系</h5>
<p>GPT-5等AI技术的发展还可能对社会结构和人际关系产生影响。随着智能机器人和虚拟助手的普及，人们与机器的交流将越来越多，而与人的面对面交流可能会减少。这可能导致社交技能的退化和社会关系的疏离。因此，我们需要重视社交技能的培养和人际交往的重要性，保持与他人的良好沟通和互动。</p>
<h5>文化与价值观</h5>
<p>GPT-5等AI技术的发展还将对文化和价值观产生深远影响。随着信息传播的速度和广度的增加，不同文化之间的交流和融合将更加频繁。这有助于促进文化的多样性和包容性，但也可能引发文化冲突和价值观的碰撞。因此，我们需要加强文化教育和价值观引导工作，培养人们的文化自觉和文化自信，尊重不同文化的差异和多样性。</p>
<h4>结论</h4>
<p>GPT-5作为未来AI技术的代表之一，其出现将带来一系列深刻的变化和挑战。我们需要从技术、伦理、法律和社会等多个层面做好准备，以应对这些变化和挑战。同时，我们也需要保持开放的心态和积极的态度，充分利用GPT-5等AI技术带来的机遇和优势，推动社会的进步和发展。未来已来，让我们携手共进，迎接更加美好的明天。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141051813</p>]]></description><guid isPermaLink="false">/archives/a848d73a-ba00-4bc5-98d6-1c0b8b640a7f</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:24:39 GMT</pubDate></item><item><title><![CDATA[Vue3的福音框架Arco.Design]]></title><link>https://wiki.dgfygk.com/archives/1de8850d-06e7-4428-bcac-8fced9cb82d0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue3%E7%9A%84%E7%A6%8F%E9%9F%B3%E6%A1%86%E6%9E%B6Arco.Design&amp;url=/archives/1de8850d-06e7-4428-bcac-8fced9cb82d0" width="1" height="1" alt="" style="opacity:0;">
<p>Vue3的福音框架Arco.Design，作为字节跳动公司研发并开源的企业级设计体系和Vue组件库，自问世以来便以其强大的功能、良好的性能以及年轻化的设计风格赢得了众多开发者的青睐。以下是对Arco.Design的详细解析，包括其特点、优势、应用场景、使用指南以及未来展望，旨在帮助读者全面了解这一Vue3时代的优秀UI框架。</p>
<h4>一、Arco.Design概述</h4>
<p>Arco.Design是字节跳动针对内部UI定制化需求而研发的一套企业级设计体系和Vue组件库。它不仅继承了字节跳动在产品设计上的深厚积累，还充分考虑了开发者的实际需求，旨在通过提供高质量、高度可定制化的前端解决方案，帮助开发者构建出美观且功能强大的企业级应用。</p>
<h4>二、Arco.Design的特点与优势</h4>
<h5>1. 强大的功能与组件库</h5>
<p>Arco.Design提供了一系列预先设计和精心实现的组件，包括布局、按钮、表单、表格等常见元素，以及通知、对话框等复杂交互组件。这些组件经过精心打磨，不仅功能强大，而且易于使用，能够满足开发者在构建企业级应用时的各种需求。</p>
<h5>2. Vue 3的完美融合</h5>
<p>作为Vue 3的UI框架，Arco.Design充分利用了Vue 3的最新特性，如Composition API，使代码组织更加模块化，提高了代码复用性和维护性。同时，它也支持TypeScript，为开发者提供了更强的类型检查和代码提示功能，进一步提升了开发效率。</p>
<h5>3. 高度可定制化的主题系统</h5>
<p>Arco.Design内置了强大的主题系统，支持CSS变量自定义。开发者可以通过修改主题变量来轻松打造出符合公司品牌风格的应用界面，实现个性化的UI设计。</p>
<h5>4. 响应式设计</h5>
<p>遵循移动优先的设计理念，Arco.Design的每个组件都优化了不同屏幕尺寸下的显示效果。无论是手机、平板还是桌面浏览器，都能为用户提供流畅、舒适的浏览体验。</p>
<h5>5. 详尽的文档和丰富的示例</h5>
<p>Arco.Design提供了详尽的文档和丰富的示例代码，帮助开发者快速上手并解决开发中遇到的问题。同时，其官方社区也非常活跃，开发者可以在其中交流心得、分享经验，并获得来自社区的帮助和支持。</p>
<h4>三、Arco.Design的应用场景</h4>
<p>Arco.Design适用于多种企业级应用场景，包括但不限于：</p>
<ul>
 <li><strong>企业管理后台</strong>：提供丰富的组件和布局选项，帮助开发者快速搭建专业、高效的后台管理系统。</li>
 <li><strong>数据展示平台</strong>：具有良好的图表集成能力，适合用于数据分析、监控等场景。</li>
 <li><strong>B2B/B2C电商平台</strong>：提供大量交互组件和布局模板，满足电商平台对用户体验的高要求。</li>
 <li><strong>内部工具</strong>：简化开发流程，提升工具的用户体验和易用性。</li>
</ul>
<h4>四、Arco.Design的使用指南</h4>
<h5>1. 安装与引入</h5>
<p>开发者可以通过npm或yarn来安装Arco.Design的Vue组件库：</p>
<pre><code class="prism language-bash">npm install @arco-design/web-vue --save
# 或者
yarn add @arco-design/web-vue
</code></pre>
<p>然后，在项目的入口文件中引入Arco.Design的CSS文件和Vue组件库：</p>
<pre><code class="prism language-javascript">import {
    createApp } from 'vue';
import ArcoVue from '@arco-design/web-vue';
import App from './App.vue';
import '@arco-design/web-vue/dist/arco.css';

const app = createApp(App);
app.use(ArcoVue);
app.mount('#app');
</code></pre>
<h5>2. 组件使用</h5>
<p>在Vue组件中，可以直接使用Arco.Design提供的组件。例如，使用按钮组件：</p>
<pre><code class="prism language-html">&lt;template&gt;
  &lt;a-button type="primary"&gt;主要按钮&lt;/a-button&gt;
&lt;/template&gt;
</code></pre>
<h5>3. 主题定制</h5>
<p>开发者可以通过修改Arco.Design的主题变量来自定义应用的主题。这通常需要在项目的全局样式文件中进行设置。</p>
<h5>4. 国际化</h5>
<p>Arco.Design支持国际化，开发者可以根据需要为应用添加多语言支持。这通常涉及到在Vue组件中使用<code>&lt;a-config-provider&gt;</code>组件来包裹需要国际化的内容，并通过<code>:locale</code>属性指定当前的语言环境。</p>
<h4>五、Arco.Design的未来展望</h4>
<p>随着Vue 3的普及和前端技术的不断发展，Arco.Design作为Vue 3的福音框架，其未来发展前景广阔。可以预见的是，Arco.Design将继续优化其组件库和主题系统，提升开发者的使用体验；同时，它也将加强与Vue 3生态系统的融合，为开发者提供更多高质量的解决方案。此外，随着国际化需求的增加，Arco.Design也将进一步完善其国际化支持，为全球开发者提供更好的服务。</p>
<h4>六、总结</h4>
<p>Arco.Design作为Vue 3时代的佼佼者，不仅以其强大的组件库、灵活的定制能力以及卓越的性能表现吸引了众多开发者的目光，更以其前瞻性的设计理念和对企业级应用的深刻理解，为前端开发者构建高效、美观、易于维护的应用提供了有力支持。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141065430</p>]]></description><guid isPermaLink="false">/archives/1de8850d-06e7-4428-bcac-8fced9cb82d0</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Fri, 27 Sep 2024 03:24:24 GMT</pubDate></item><item><title><![CDATA[在MySQL数据库环境中线上查询之前进行性能调优]]></title><link>https://wiki.dgfygk.com/archives/fa889840-d45c-4914-a912-b2405ffb625f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E7%8E%AF%E5%A2%83%E4%B8%AD%E7%BA%BF%E4%B8%8A%E6%9F%A5%E8%AF%A2%E4%B9%8B%E5%89%8D%E8%BF%9B%E8%A1%8C%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98&amp;url=/archives/fa889840-d45c-4914-a912-b2405ffb625f" width="1" height="1" alt="" style="opacity:0;">
<p>在MySQL数据库环境中，进行线上查询之前进行性能调优是一项至关重要的任务，它直接关系到系统的响应速度、稳定性以及用户体验。性能调优是一个复杂且持续的过程，涉及多个层面，包括硬件、网络、数据库配置、查询优化、索引设计、表结构优化等。以下将详细探讨在MySQL线上查询前进行性能调优的多个方面，内容将尽可能详尽，以满足不少于2000字的要求。</p>
<h4>一、理解性能瓶颈</h4>
<p>在进行任何性能调优之前，首先需要明确性能瓶颈所在。常见的性能瓶颈包括CPU使用率过高、内存不足、磁盘I/O性能低下、网络延迟等。使用工具如<code>top</code>、<code>vmstat</code>、<code>iostat</code>、<code>netstat</code>、<code>mysqltuner.pl</code>以及MySQL自带的<code>SHOW PROCESSLIST</code>、<code>EXPLAIN</code>、<code>SHOW PROFILE</code>等命令可以帮助识别问题所在。</p>
<h4>二、硬件优化</h4>
<ol>
 <li>
  <p><strong>CPU</strong>：</p>
 </li>
 <li>
  <p>确保CPU性能满足当前及未来一段时间的数据库负载需求。</p>
 </li>
 <li>
  <p>对于高并发的数据库，考虑使用多核CPU。</p>
 </li>
 <li>
  <p>监控CPU使用率，避免长时间处于高负载状态。</p>
 </li>
 <li>
  <p><strong>内存</strong>：</p>
 </li>
 <li>
  <p>增加物理内存可以减少磁盘I/O操作，提高查询速度。</p>
 </li>
 <li>
  <p>调整MySQL的<code>innodb_buffer_pool_size</code>参数，使其尽可能接近但不超过物理内存的80%，以优化InnoDB存储引擎的性能。</p>
 </li>
 <li>
  <p><strong>存储</strong>：</p>
 </li>
 <li>
  <p>使用SSD代替HDD可以显著提升I/O性能。</p>
 </li>
 <li>
  <p>合理的RAID配置可以提高数据读写速度和安全性。</p>
 </li>
 <li>
  <p>考虑使用更快的网络接口和存储设备，如SAN或NAS。</p>
 </li>
 <li>
  <p><strong>网络</strong>：</p>
 </li>
 <li>
  <p>确保网络带宽和延迟满足数据库访问需求。</p>
 </li>
 <li>
  <p>优化网络配置，如使用TCP/IP参数调优减少网络延迟。</p>
 </li>
</ol>
<h4>三、数据库配置优化</h4>
<ol>
 <li>
  <p><strong>MySQL配置文件（my.cnf/my.ini）</strong>：</p>
 </li>
 <li>
  <p>调整<code>innodb_log_file_size</code>和<code>innodb_log_buffer_size</code>，以优化事务日志性能。</p>
 </li>
 <li>
  <p>设置合适的<code>max_connections</code>，避免过多连接导致资源耗尽。</p>
 </li>
 <li>
  <p>调整<code>query_cache_size</code>，但在MySQL 5.7及更高版本中，查询缓存已被弃用，因为其在高并发环境下可能导致性能下降。</p>
 </li>
 <li>
  <p>启用<code>slow_query_log</code>和<code>long_query_time</code>，以捕获并优化慢查询。</p>
 </li>
 <li>
  <p><strong>InnoDB存储引擎配置</strong>：</p>
 </li>
 <li>
  <p>调整<code>innodb_flush_log_at_trx_commit</code>、<code>innodb_flush_method</code>等参数，以平衡数据一致性和性能。</p>
 </li>
 <li>
  <p>监控<code>InnoDB Buffer Pool</code>的命中率，调整<code>innodb_buffer_pool_size</code>以提高缓存效率。</p>
 </li>
</ol>
<h4>四、查询优化</h4>
<ol>
 <li>
  <p><strong>使用EXPLAIN分析查询</strong>：</p>
 </li>
 <li>
  <p>使用<code>EXPLAIN</code>命令查看查询的执行计划，了解MySQL如何执行SQL语句。</p>
 </li>
 <li>
  <p>关注<code>type</code>、<code>key</code>、<code>rows</code>、<code>Extra</code>等列，识别全表扫描、索引未使用等问题。</p>
 </li>
 <li>
  <p><strong>优化查询语句</strong>：</p>
 </li>
 <li>
  <p>避免在<code>WHERE</code>子句中使用函数或计算，这会导致索引失效。</p>
 </li>
 <li>
  <p>使用<code>JOIN</code>代替子查询，减少查询嵌套。</p>
 </li>
 <li>
  <p>确保<code>WHERE</code>子句中的条件能够利用索引。</p>
 </li>
 <li>
  <p>使用<code>LIMIT</code>限制返回的行数，减少数据传输和处理时间。</p>
 </li>
 <li>
  <p><strong>索引优化</strong>：</p>
 </li>
 <li>
  <p>根据查询模式创建合适的索引，包括单列索引、复合索引等。</p>
 </li>
 <li>
  <p>定期审查并删除未使用的索引，减少维护成本。</p>
 </li>
 <li>
  <p>使用<code>FORCE INDEX</code>强制MySQL使用特定索引，但需谨慎使用。</p>
 </li>
</ol>
<h4>五、表结构优化</h4>
<ol>
 <li>
  <p><strong>分区表</strong>：</p>
 </li>
 <li>
  <p>对于大表，考虑使用分区表技术，将表数据分散到不同的物理位置，提高查询效率。</p>
 </li>
 <li>
  <p>根据业务需求选择合适的分区键，如时间、地区等。</p>
 </li>
 <li>
  <p><strong>归档旧数据</strong>：</p>
 </li>
 <li>
  <p>定期将不常用的旧数据归档到历史表中，减少主表的大小和查询负担。</p>
 </li>
 <li>
  <p><strong>规范化与反规范化</strong>：</p>
 </li>
 <li>
  <p>适度规范化可以减少数据冗余，但可能增加查询的复杂性。</p>
 </li>
 <li>
  <p>在某些情况下，通过反规范化（如添加冗余字段、创建汇总表等）可以提高查询性能。</p>
 </li>
</ol>
<h4>六、其他优化策略</h4>
<ol>
 <li>
  <p><strong>并发控制</strong>：</p>
 </li>
 <li>
  <p>使用适当的锁策略（如行锁、表锁、乐观锁、悲观锁）来管理并发访问，减少锁争用和死锁的发生。</p>
 </li>
 <li>
  <p>调整<code>innodb_lock_wait_timeout</code>等参数，以控制锁等待的超时时间。</p>
 </li>
 <li>
  <p><strong>读写分离</strong>：</p>
 </li>
 <li>
  <p>在高并发场景下，通过读写分离来分担主库的压力。将读操作分发到从库，写操作保持在主库。</p>
 </li>
 <li>
  <p>使用MySQL自带的复制功能或第三方中间件（如ProxySQL、MaxScale）来实现读写分离。</p>
 </li>
 <li>
  <p><strong>定期维护</strong>：</p>
 </li>
 <li>
  <p>定期进行数据库维护操作，如<code>OPTIMIZE TABLE</code>、<code>ANALYZE TABLE</code>、<code>REPAIR TABLE</code>等，以优化表性能和结构。</p>
 </li>
 <li>
  <p>清理无用的数据和日志文件，释放磁盘空间。</p>
 </li>
 <li>
  <p><strong>监控与告警</strong>：</p>
 </li>
 <li>
  <p>实施全面的监控策略，包括数据库性能、硬件资源、网络状况等。</p>
 </li>
 <li>
  <p>设置告警阈值，当性能指标超过阈值时自动触发告警，以便及时响应和处理。</p>
 </li>
 <li>
  <p><strong>版本升级</strong>：</p>
 </li>
 <li>
  <p>定期关注MySQL的更新和版本迭代，了解新版本的特性和优化点。</p>
 </li>
 <li>
  <p>在评估风险和收益后，适时升级数据库版本，以获取性能提升和新功能。</p>
 </li>
 <li>
  <p><strong>使用外部工具和服务</strong>：</p>
 </li>
 <li>
  <p>利用外部的性能分析和调优工具，如Percona Toolkit、Oracle Enterprise Manager for MySQL等，来辅助进行性能调优。</p>
 </li>
 <li>
  <p>考虑使用云服务提供商的数据库服务（如Amazon RDS、Google Cloud SQL等），这些服务通常提供了自动调优、备份恢复、高可用性等高级功能。</p>
 </li>
</ol>
<h4>七、性能调优的迭代过程</h4>
<p>性能调优是一个持续的过程，需要不断地监测、分析和调整。在每次调整之后，都应该重新评估系统的性能表现，并根据实际情况进行进一步的优化。同时，需要记录每次调优的详细信息和结果，以便在未来的优化过程中参考和借鉴。</p>
<h4>结语</h4>
<p>MySQL线上查询之前的性能调优是一个复杂而细致的过程，涉及多个层面和多个环节。通过合理的硬件配置、优化数据库配置、优化查询语句、优化表结构、使用缓存技术以及其他优化策略，可以显著提升MySQL数据库的性能和稳定性。然而，需要注意的是，性能调优并非一劳永逸的过程，需要持续地进行监测和调整以适应不断变化的业务需求和环境变化。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/141019081</p>]]></description><guid isPermaLink="false">/archives/fa889840-d45c-4914-a912-b2405ffb625f</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Fri, 27 Sep 2024 03:24:11 GMT</pubDate></item><item><title><![CDATA[C#进阶：轻量级ORM框架Dapper详解]]></title><link>https://wiki.dgfygk.com/archives/fd43196f-c045-4f2d-95be-4694139b7f9a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=C%23%E8%BF%9B%E9%98%B6%EF%BC%9A%E8%BD%BB%E9%87%8F%E7%BA%A7ORM%E6%A1%86%E6%9E%B6Dapper%E8%AF%A6%E8%A7%A3&amp;url=/archives/fd43196f-c045-4f2d-95be-4694139b7f9a" width="1" height="1" alt="" style="opacity:0;">
<h4>C#进阶：轻量级ORM框架Dapper详解</h4>
<p>在C#开发中，ORM（对象关系映射）框架是处理数据库交互的重要工具。Dapper作为一个轻量级的ORM框架，专为.NET平台设计，因其高性能和易用性而备受开发者青睐。本文将详细介绍Dapper的基本概念、与其他持久层框架的比较、基本及高级语法的使用，并通过实例展示如何在项目中集成和使用Dapper。</p>
<h5>一、Dapper的基本概念</h5>
<p>Dapper是一个轻量级的ORM框架，由Stack Overflow团队开发并开源。它通过扩展IDbConnection接口，简化了数据库访问过程，允许开发者直接执行SQL查询，并将查询结果映射到C#对象模型中。Dapper的核心优势在于其轻量、高效和易用性，非常适合需要高效数据访问的场景。</p>
<h5>二、Dapper与其他持久层框架的比较</h5>
<p>为了更清晰地展示Dapper的优势，我们可以通过比较它与其他数据库操作框架来进行分析。以下是一些常见的数据库操作框架与Dapper的比较：</p>
<ul>
 <li><strong>Entity Framework (EF)</strong>：EF是微软官方推出的一个完整的ORM框架，提供了丰富的功能，如变更跟踪、延迟加载等。然而，EF的复杂性也带来了额外的性能开销。相比之下，Dapper更加轻量，专注于提供高效的数据库访问能力，没有复杂的上下文管理和变化跟踪机制。</li>
 <li><strong>ADO.NET</strong>：ADO.NET是.NET平台下用于数据库访问的一组类库，它提供了数据库连接、命令执行等底层功能。Dapper可以看作是ADO.NET的扩展，通过提供高级的抽象和简化的API，使开发者能够更高效地执行数据库操作。</li>
 <li><strong>Dapper.Contrib</strong>：Dapper.Contrib是Dapper的一个扩展库，提供了更丰富的功能，如自动映射、CRUD操作等。然而，Dapper本身已经足够轻量且强大，对于大多数场景来说，直接使用Dapper即可满足需求。</li>
</ul>
<h5>三、Dapper的基本使用</h5>
<h6>1. 安装Dapper</h6>
<p>首先，你需要在项目中安装Dapper。通过NuGet包管理器可以轻松添加Dapper到你的项目中：</p>
<pre><code class="prism language-bash">Install-Package Dapper
</code></pre>
<h6>2. 建立数据库连接</h6>
<p>使用ADO.NET的IDbConnection接口建立数据库连接。Dapper不限制你使用哪种数据库连接，只要它实现了IDbConnection接口即可。</p>
<pre><code class="prism language-csharp">string connectionString = "YourConnectionStringHere";
using (IDbConnection db = new SqlConnection(connectionString))
{

    // 数据库操作
}
</code></pre>
<h6>3. 执行SQL查询</h6>
<p>Dapper提供了多种扩展方法来执行SQL查询。例如，使用<code>Query</code>方法执行查询并返回结果集：</p>
<pre><code class="prism language-csharp">string sql = "SELECT * FROM Users";
var users = db.Query&lt;User&gt;(sql).ToList();
</code></pre>
<p>对于单条记录的查询，可以使用<code>QueryFirstOrDefault</code>方法：</p>
<pre><code class="prism language-csharp">var user = db.QueryFirstOrDefault&lt;User&gt;("SELECT * FROM Users WHERE Id = @Id", new {
    Id = 1 });
</code></pre>
<h6>4. 执行插入、更新和删除操作</h6>
<p>Dapper使用<code>Execute</code>方法执行插入、更新和删除操作。这些方法返回受影响的行数。</p>
<ul>
 <li><strong>插入操作</strong>：</li>
</ul>
<pre><code class="prism language-csharp">string insertSql = "INSERT INTO Users (Name, Email) VALUES (@Name, @Email)";
var newUserId = db.ExecuteScalar&lt;int&gt;(insertSql + "; SELECT CAST(SCOPE_IDENTITY AS INT)", new {
    Name = "NewUser", Email = "newuser@example.com" });
</code></pre>
<ul>
 <li><strong>更新操作</strong>：</li>
</ul>
<pre><code class="prism language-csharp">string updateSql = "UPDATE Users SET Name = @Name WHERE Id = @Id";
var updatedRows = db.Execute(updateSql, new {
    Name = "UpdatedUser", Id = 1 });
</code></pre>
<ul>
 <li><strong>删除操作</strong>：</li>
</ul>
<pre><code class="prism language-csharp">string deleteSql = "DELETE FROM Users WHERE Id = @Id";
var deletedRows = db.Execute(deleteSql, new {
    Id = 1 });
</code></pre>
<h6>5. 参数化查询</h6>
<p>Dapper支持参数化查询，这有助于防止SQL注入攻击。在SQL语句中使用参数占位符，并将参数作为匿名对象传递给<code>Execute</code>或<code>Query</code>方法：</p>
<pre><code class="prism language-csharp">string query = "SELECT * FROM Students WHERE LastName = @LastName";
var students = db.Query&lt;Student&gt;(query, new {
    LastName = "Doe" }).ToList();
</code></pre>
<h6>6. 缓存查询计划</h6>
<p>Dapper会缓存查询计划，减少SQL解析和执行的开销。这意味着对于相同的查询（包括参数化查询），Dapper可以更快地执行它们。</p>
<h5>四、Dapper的高级特性</h5>
<h6>1. 多表查询和结果映射</h6>
<p>Dapper支持多表查询，并将结果映射到多个对象。这通常通过<code>Query</code>方法的重载版本或自定义类型处理器（Type Handler）来实现。</p>
<p><strong>多表查询和结果映射示例</strong>：</p>
<p>假设我们有两个表，一个是<code>Users</code>（用户表），另一个是<code>UserDetails</code>（用户详情表），现在我们想要通过一个查询同时获取用户及其详细信息。</p>
<p>首先，定义两个C#类来表示这两个表：</p>
<pre><code class="prism language-csharp">public class User
{

    public int Id {
    get; set; }
    public string Name {
    get; set; }
    public UserDetail Detail {
    get; set; }
}

public class UserDetail
{

    public int UserId {
    get; set; }
    public string Address {
    get; set; }
    // 其他用户详情字段...
}
</code></pre>
<p>但是，直接映射到一个嵌套对象可能不太直接，因为Dapper默认不支持直接映射到嵌套对象。一种常见的解决方案是使用匿名类型或者DTO（数据传输对象）来接收查询结果，然后在应用层进行手动组装。不过，对于简单的嵌套关系，我们可以考虑使用<code>splitOn</code>参数来告诉Dapper如何分割结果到不同的对象。但请注意，这种方法更适合简单的嵌套关系，并且<code>splitOn</code>依赖于结果集中某个字段的值来分割数据。</p>
<p>然而，对于更复杂的场景，我们通常会使用一个DTO或视图模型（ViewModel）来接收查询结果，并在查询后手动组装对象。</p>
<p><strong>使用DTO和手动组装的示例</strong>：</p>
<pre><code class="prism language-csharp">public class UserDto
{

    public int Id {
    get; set; }
    public string Name {
    get; set; }
    public string Address {
    get; set; }
    // 其他需要的字段...
}

// 假设我们有一个联合查询，同时获取用户和详情
string sql = @"
SELECT u.Id, u.Name, ud.Address
FROM Users u
INNER JOIN UserDetails ud ON u.Id = ud.UserId
WHERE u.Name = @Name";

var userDtos = db.Query&lt;UserDto, UserDto, UserDto&gt;(sql, 
    (user, detail) =&gt; 
    {

        // 注意：这里我们实际上没有用到第二个和第三个UserDto参数，因为这是一个简化的例子
        // 在真实场景中，你可能需要处理更复杂的映射关系
        // 但由于Dapper不支持直接映射到嵌套对象，我们采用DTO来接收所有数据
        // 并在查询后手动处理（尽管在这个例子中没有展示）
        // 或者，你可能需要执行两次查询，分别获取用户和详情，然后在内存中进行组装
        return user; // 这里返回user作为示例，实际上你可能需要更复杂的逻辑
    }, 
    new {
    Name = "JohnDoe" }, 
    splitOn: "Id").ToList(); // 注意：splitOn在这里不适用，因为这里只是一个示例

// 实际上，上面的代码逻辑是不正确的，因为它试图用splitOn来处理一个本不需要分割的场景
// 正确的做法可能是执行两个查询或使用DTO和手动组装

// 正确的DTO使用方式示例（假设我们已经有了两个分开的查询结果）
// ...（此处省略具体的查询和组装逻辑）
</code></pre>
<p>请注意，上面的<code>Query</code>方法使用示例并不完全正确，因为它试图用<code>splitOn</code>来处理一个不需要分割的查询结果。在实际应用中，对于复杂的多表查询和结果映射，你可能需要：</p>
<ol>
 <li>执行多个单独的查询，并在内存中将结果组装成你需要的对象结构。</li>
 <li>使用DTO（数据传输对象）来接收查询结果，并在查询之后（可能是在服务层或应用层）进行手动组装。</li>
 <li>考虑使用更高级的ORM功能，如Entity Framework的导航属性，如果你的项目可以接受EF带来的额外复杂性和性能开销。</li>
</ol>
<h6>2. 异步查询</h6>
<p>Dapper支持异步数据库操作，这有助于提高应用程序的响应性和可伸缩性。你可以使用<code>QueryAsync</code>、<code>ExecuteAsync</code>等异步方法来执行数据库操作。</p>
<pre><code class="prism language-csharp">string sql = "SELECT * FROM Users";
var users = await db.QueryAsync&lt;User&gt;(sql).ToListAsync();
</code></pre>
<h5>五、Dapper的最佳实践</h5>
<ol>
 <li><strong>使用参数化查询</strong>：防止SQL注入攻击。</li>
 <li><strong>合理设计数据库查询</strong>：尽量优化SQL查询，减少不必要的数据传输和处理。</li>
 <li><strong>考虑使用DTO</strong>：对于复杂的数据传输场景，使用DTO可以减少数据传输量，并有助于解耦数据库模型和应用程序模型。</li>
 <li><strong>利用Dapper的缓存功能</strong>：Dapper会缓存查询计划，但对于频繁变化的数据，考虑使用更细粒度的缓存策略。</li>
 <li><strong>异步编程</strong>：在可能的情况下，使用Dapper的异步API来提高应用程序的响应性和可伸缩性。</li>
 <li><strong>错误处理</strong>：确保妥善处理数据库操作中的错误和异常，如连接失败、查询超时等。</li>
</ol>
<h5>六、Dapper的高级用法</h5>
<h6>1. 动态SQL构建</h6>
<p>在复杂的应用场景中，有时候SQL查询的具体结构（如表名、字段名或条件）在运行时才能确定。Dapper虽然不直接提供SQL构建器，但你可以结合C#的字符串拼接、表达式树或第三方库（如Dapper-Contrib、DapperExtensions或SqlKata）来动态构建SQL语句。</p>
<p>使用字符串拼接时，需要特别注意SQL注入的风险，并确保所有动态内容都通过参数化查询来传递。</p>
<h6>2. 批处理操作</h6>
<p>对于需要执行大量相似操作（如插入多行数据）的场景，批处理可以显著提高性能。Dapper没有内置的批处理API，但你可以通过<code>Execute</code>方法多次执行相同的SQL命令（尽管这不是真正的批处理），或者利用数据库特定的批处理语法（如SQL Server的表值参数或MySQL的批量插入语法）来手动实现。</p>
<p>另外，你也可以考虑使用ADO.NET的<code>SqlBulkCopy</code>（对于SQL Server）或类似的数据库特定功能来实现高效的批量数据操作。</p>
<h6>3. 事务管理</h6>
<p>Dapper本身不直接提供事务管理功能，但它可以与.NET的<code>TransactionScope</code>或数据库连接的事务对象结合使用来管理事务。</p>
<p>使用<code>TransactionScope</code>时，你可以轻松地将多个数据库操作包含在单个事务中，并确保它们要么全部成功，要么在遇到错误时全部回滚。</p>
<pre><code class="prism language-csharp">using (var scope = new TransactionScope())
{

    // 执行数据库操作...

    // 如果所有操作都成功，则自动提交事务
    scope.Complete();
}
</code></pre>
<p>如果你使用的是数据库连接的事务对象（如<code>SqlTransaction</code>），则需要手动控制事务的开始、提交和回滚。</p>
<h6>4. 插件和扩展</h6>
<p>Dapper社区提供了许多插件和扩展，以增强Dapper的功能。这些插件可能包括：</p>
<ul>
 <li><strong>Dapper-Contrib</strong>：提供了基于约定的CRUD操作，减少了样板代码。</li>
 <li><strong>DapperExtensions</strong>：提供了更高级的查询构建和映射功能。</li>
 <li><strong>SqlKata</strong>：一个强大的SQL查询构建器，可以与Dapper结合使用来构建复杂的SQL查询。</li>
</ul>
<p>使用这些插件和扩展时，请确保它们与你的项目需求和数据库环境兼容，并仔细阅读文档以了解如何正确集成和使用它们。</p>
<h5>七、Dapper的局限性</h5>
<p>尽管Dapper是一个轻量级且功能强大的ORM工具，但它也有一些局限性：</p>
<ol>
 <li>
  <p><strong>缺乏内置的高级功能</strong>：与EF Core等更全面的ORM相比，Dapper提供了更少的内置功能，如自动变更跟踪、复杂关系映射和延迟加载等。</p>
 </li>
 <li>
  <p><strong>SQL依赖</strong>：由于Dapper允许直接编写SQL语句，因此你的代码将紧密依赖于特定的数据库实现。这可能会增加在不同数据库之间迁移的难度。</p>
 </li>
 <li>
  <p><strong>错误处理和日志记录</strong>：Dapper本身不提供内置的错误处理和日志记录功能。你需要自己实现这些功能，或使用其他库（如log4net、Serilog等）来记录数据库操作的日志。</p>
 </li>
 <li>
  <p><strong>学习曲线</strong>：虽然Dapper的API相对简单，但编写高效的SQL查询和了解数据库的最佳实践仍然需要一定的学习和经验。</p>
 </li>
</ol>
<h5>八、总结</h5>
<p>Dapper是一个轻量级、高性能的.NET ORM工具，它允许开发者直接编写SQL语句并映射到.NET对象。通过提供简单的API和高效的性能，Dapper成为许多.NET开发者的首选ORM之一。然而，它也有一些局限性，如缺乏内置的高级功能和SQL依赖等。在使用Dapper时，建议结合项目需求、数据库环境和开发团队的经验来评估其适用性，并考虑使用插件和扩展来增强其功能。同时，也需要注意SQL注入的风险，并确保编写高效的SQL查询和合理的数据库操作。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140901905</p>]]></description><guid isPermaLink="false">/archives/fd43196f-c045-4f2d-95be-4694139b7f9a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 03:23:58 GMT</pubDate></item><item><title><![CDATA[Docker Image 详细讲解]]></title><link>https://wiki.dgfygk.com/archives/b7b8ca14-3376-416f-acc3-4b5d0dd18976</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Docker%20Image%20%E8%AF%A6%E7%BB%86%E8%AE%B2%E8%A7%A3&amp;url=/archives/b7b8ca14-3376-416f-acc3-4b5d0dd18976" width="1" height="1" alt="" style="opacity:0;">
<h4>Docker Image 详细讲解</h4>
<p>Docker Image 是 Docker 生态系统中的核心概念之一，它作为容器运行的基础，封装了应用运行所需的环境和依赖。本文将详细讲解 Docker Image 的定义、构建、存储、管理以及使用，帮助读者全面理解 Docker Image。</p>
<h5>一、Docker Image 概述</h5>
<p>Docker Image 是一个轻量级、可执行的独立软件包，包含了运行某个软件所需要的所有内容，包括代码、运行时、库、环境变量、配置文件等。Docker 利用这些镜像来创建容器，每个容器都可以从镜像中运行实例，并且这些容器之间互相隔离，互不干扰。</p>
<p>Docker Image 的设计目标是实现"一次构建，到处运行”（Build once, run anywhere），这意味着开发者可以在任何地方使用相同的镜像来运行他们的应用，无需担心环境差异。</p>
<h5>二、Docker Image 的构建</h5>
<p>Docker Image 的构建通常通过 Dockerfile 来实现。Dockerfile 是一个文本文件，包含了构建镜像所需的所有命令和参数。Docker 读取 Dockerfile 中的指令，按顺序执行这些指令，最终生成一个镜像。</p>
<p>Dockerfile 中常用的指令包括：</p>
<ul>
 <li><code>FROM</code>：指定基础镜像。</li>
 <li><code>RUN</code>：执行命令并创建新的镜像层。</li>
 <li><code>CMD</code>：容器启动时执行的命令，但可以被 docker run 命令行中的参数覆盖。</li>
 <li><code>ENTRYPOINT</code>：配置容器启动时运行的命令，让容器以类似于执行文件的方式运行。</li>
 <li><code>COPY</code>：将文件或目录从构建上下文复制到镜像中。</li>
 <li><code>ADD</code>：用法与 COPY 类似，但支持通过 URL 下载文件和解压压缩包。</li>
 <li><code>ENV</code>：设置环境变量。</li>
 <li><code>EXPOSE</code>：声明容器运行时监听的端口。</li>
 <li><code>LABEL</code>：为镜像添加元数据。</li>
</ul>
<p>构建镜像的基本步骤为：</p>
<ol>
 <li>编写 Dockerfile。</li>
 <li>使用 <code>docker build</code> 命令构建镜像。</li>
</ol>
<p>例如，构建一个基于 Alpine Linux 的 Node.js 环境镜像的 Dockerfile 如下：</p>
<pre><code class="prism language-Dockerfile">FROM alpine:latest
RUN apk update &amp;&amp; apk add --no-cache nodejs nodejs-npm
WORKDIR /app
COPY . /app
RUN npm install
CMD ["node", "app.js"]
</code></pre>
<p>然后使用以下命令构建镜像：</p>
<pre><code class="prism language-bash">docker build -t my-nodejs-app .
</code></pre>
<h5>三、Docker Image 的存储与管理</h5>
<p>Docker Image 存储在 Docker 镜像仓库（Image Registry）中。Docker 客户端的镜像仓库服务是可配置的，默认使用 Docker Hub。Docker Hub 提供了大量的官方镜像和社区贡献的镜像，方便用户直接使用。</p>
<p>镜像仓库服务包含多个镜像仓库（Image Repository），每个镜像仓库中可以包含多个镜像。镜像通过命名和标签（Tag）来区分不同的版本。例如，<code>alpine:latest</code> 表示 Docker Hub 上最新的 Alpine Linux 镜像。</p>
<p>Docker 镜像的存储采用了分层（Layered）结构，每个镜像都是由多个层组成的。这种设计允许不同的镜像共享相同的层，从而节省存储空间和提高构建效率。当从某个镜像启动容器时，Docker 会从该镜像的最底层开始，逐层叠加，直到达到指定的镜像层。</p>
<p>Docker 提供了丰富的命令来管理和操作镜像，包括：</p>
<ul>
 <li><code>docker image pull</code>：从远程仓库拉取镜像到本地。</li>
 <li><code>docker image push</code>：将本地镜像推送到远程仓库。</li>
 <li><code>docker image ls</code>：列出本地镜像。</li>
 <li><code>docker image inspect</code>：查看镜像的详细信息。</li>
 <li><code>docker image rm</code>：删除本地镜像。</li>
 <li><code>docker image save</code>：将镜像保存为压缩文件，用于共享或备份。</li>
 <li><code>docker image load</code>：加载压缩文件，创建镜像。</li>
</ul>
<h5>四、Docker Image 的使用</h5>
<p>Docker 镜像的主要用途是创建容器。一旦镜像被构建并存储在本地或远程仓库中，就可以使用 <code>docker run</code> 命令从镜像启动容器。</p>
<p><code>docker run</code> 命令的基本语法为：</p>
<pre><code class="prism language-bash">docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
</code></pre>
<p>其中，<code>OPTIONS</code> 是可选的 Docker 容器运行时参数，<code>IMAGE[:TAG|@DIGEST]</code> 指定了要使用的镜像及其标签或摘要，<code>COMMAND</code> 和 <code>ARG...</code> 是容器启动时执行的命令和传递给命令的参数。</p>
<p>例如，使用上面构建的 <code>my-nodejs-app</code> 镜像启动容器的命令如下：</p>
<pre><code class="prism language-bash">docker run -d -p 3000:3000 my-nodejs-app
</code></pre>
<p>这个命令会以后台模式（<code>-d</code>）启动一个容器，并将容器的 3000 端口映射到宿主机的 3000 端口上。</p>
<h5>五、Docker Image 的优化</h5>
<p>Docker 镜像的优化是提升 Docker 容器性能和减小镜像体积的重要一环。以下是一些常用的 Docker Image 优化策略：</p>
<h5>六、Docker Image 的优化</h5>
<ol>
 <li>
  <p><strong>选择合适的基础镜像</strong>：</p>
 </li>
 <li>
  <p>使用尽可能小的基础镜像。例如，对于不需要完整 Linux 系统的应用，可以选择 Alpine Linux 或 BusyBox 等轻量级 Linux 发行版作为基础镜像。</p>
 </li>
 <li>
  <p>尽量避免使用包含额外软件包和服务的官方镜像，如 <code>ubuntu:latest</code>，因为它们通常体积较大。</p>
 </li>
 <li>
  <p><strong>减少镜像层数</strong>：</p>
 </li>
 <li>
  <p>合并多个 <code>RUN</code> 指令。在 Dockerfile 中，每执行一个 <code>RUN</code> 指令都会创建一个新的镜像层。如果可能，尽量将多个命令合并到一个 <code>RUN</code> 指令中，使用 <code>&amp;&amp;</code> 连接，以减少镜像层数。</p>
 </li>
 <li>
  <p>使用 <code>.dockerignore</code> 文件排除不需要的文件和目录，减少构建上下文的大小，从而提高构建速度并减小最终镜像的体积。</p>
 </li>
 <li>
  <p><strong>清理无用文件和日志</strong>：</p>
 </li>
 <li>
  <p>在构建过程中，删除临时文件、编译缓存和不必要的日志，以减少镜像体积。</p>
 </li>
 <li>
  <p>使用 <code>apt-get clean</code> 和 <code>rm -rf /var/lib/apt/lists/*</code>（对于基于 Debian 的镜像）来清理 APT 缓存。</p>
 </li>
 <li>
  <p><strong>使用多阶段构建</strong>：</p>
 </li>
 <li>
  <p>Docker 17.05 引入了多阶段构建（Multi-stage Builds），允许在一个 Dockerfile 中使用多个基础镜像，每个阶段都可以使用前一个阶段的输出作为输入，并且最终镜像只包含最后一个阶段的文件。这对于编译应用并将编译结果复制到较小的运行环境中特别有用。</p>
 </li>
 <li>
  <p><strong>压缩和打包文件</strong>：</p>
 </li>
 <li>
  <p>如果需要在镜像中包含大量文件（如静态资源），考虑先对它们进行压缩，然后在运行时解压。</p>
 </li>
 <li>
  <p>使用 <code>tar</code> 或 <code>zip</code> 等工具压缩文件，并在 Dockerfile 中解压它们。</p>
 </li>
 <li>
  <p><strong>利用缓存</strong>：</p>
 </li>
 <li>
  <p>Docker 在构建镜像时会利用缓存来加速构建过程。确保 Dockerfile 中的指令顺序和参数在每次构建时尽可能保持不变，以便 Docker 能够重用缓存层。</p>
 </li>
 <li>
  <p>如果需要更新某些软件包或依赖项，考虑使用缓存失效策略（如更改版本号或添加时间戳）来触发新的构建过程。</p>
 </li>
 <li>
  <p><strong>使用专门的镜像构建工具</strong>：</p>
 </li>
 <li>
  <p>除了 Dockerfile 之外，还可以使用专门的镜像构建工具（如 Jib、Kaniko、Buildah 等）来优化镜像构建过程。这些工具提供了额外的功能和灵活性，有助于生成更小、更安全的镜像。</p>
 </li>
 <li>
  <p><strong>定期审查和更新镜像</strong>：</p>
 </li>
 <li>
  <p>定期检查并更新基础镜像和依赖项，以利用最新的安全补丁和性能改进。</p>
 </li>
 <li>
  <p>清理过时和不再使用的镜像版本，以节省存储空间。</p>
 </li>
</ol>
<h5>七、Docker Image 的安全性</h5>
<p>除了优化镜像以提高性能和减小体积外，还需要关注镜像的安全性。以下是一些提高 Docker 镜像安全性的策略：</p>
<ol>
 <li>
  <p><strong>使用官方或受信任的镜像</strong>：</p>
 </li>
 <li>
  <p>尽可能使用 Docker Hub 或其他受信任的镜像仓库中的官方镜像。这些镜像经过了严格的审查和测试，通常包含最新的安全补丁。</p>
 </li>
 <li>
  <p><strong>最小化权限</strong>：</p>
 </li>
 <li>
  <p>在 Dockerfile 中，使用非 root 用户运行容器内的进程。这可以通过在 Dockerfile 中添加 <code>USER</code> 指令来实现。</p>
 </li>
 <li>
  <p><strong>定期更新和修补</strong>：</p>
 </li>
 <li>
  <p>定期更新基础镜像和依赖项，以应用最新的安全补丁。</p>
 </li>
 <li>
  <p>使用自动化工具（如 Docker Hub 的自动化构建和通知功能）来跟踪镜像的更新和漏洞。</p>
 </li>
 <li>
  <p><strong>使用内容信任</strong>：</p>
 </li>
 <li>
  <p>Docker 提供了内容信任（Content Trust）功能，允许用户验证镜像的完整性和来源。启用内容信任可以确保从受信任的源下载镜像。</p>
 </li>
 <li>
  <p><strong>避免在镜像中存储敏感信息</strong>：</p>
 </li>
 <li>
  <p>不要在 Dockerfile 或镜像中硬编码敏感信息（如密码、密钥等）。使用环境变量、Docker secrets 或外部存储服务来管理敏感信息。</p>
 </li>
 <li>
  <p><strong>进行安全扫描</strong>：</p>
 </li>
 <li>
  <p>使用 Docker 扫描工具（如 Docker Scan、Clair、Trivy 等）对镜像进行安全扫描，以识别潜在的安全漏洞和恶意软件。</p>
 </li>
 <li>
  <p><strong>限制网络访问</strong>：</p>
 </li>
 <li>
  <p>在 Dockerfile 中使用 <code>EXPOSE</code> 指令仅暴露必要的端口。在运行时，使用 Docker 网络功能来限制容器之间的网络访问。</p>
 </li>
</ol>
<p>通过遵循上述优化和安全性策略，可以构建出更小、更快、更安全的 Docker 镜像，从而提高应用的性能和可靠性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140867506</p>]]></description><guid isPermaLink="false">/archives/b7b8ca14-3376-416f-acc3-4b5d0dd18976</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 03:19:30 GMT</pubDate></item><item><title><![CDATA[Zoom视频会议软件]]></title><link>https://wiki.dgfygk.com/archives/c244a2ce-566e-4f96-85f5-3c21ca501f77</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Zoom%E8%A7%86%E9%A2%91%E4%BC%9A%E8%AE%AE%E8%BD%AF%E4%BB%B6&amp;url=/archives/c244a2ce-566e-4f96-85f5-3c21ca501f77" width="1" height="1" alt="" style="opacity:0;">
<p>Zoom视频会议软件是一款由Zoom Video Communications公司开发的基于云计算的视频会议工具，自2011年推出以来，凭借其便捷的操作和强大的功能，迅速成为全球广泛使用的视频会议解决方案。以下是对Zoom视频会议软件使用的详细介绍，内容不少于2000字。</p>
<h4>一、Zoom视频会议软件的基本介绍</h4>
<p>Zoom视频会议软件是一款集视频、音频、聊天、屏幕共享、录制等多种功能于一体的在线会议工具。它支持跨平台使用，包括Windows、Mac、Linux、iOS和Android等操作系统，用户可以通过电脑、手机、平板等多种设备轻松加入会议。Zoom不仅适用于企业内部的日常会议和团队协作，还广泛应用于在线教育、远程医疗、线上活动等多个领域。</p>
<h4>二、Zoom视频会议软件的下载与安装</h4>
<h5>1. 下载Zoom客户端</h5>
<p>用户可以通过Zoom官方网站或各大应用商店（如App Store、Google Play）下载Zoom客户端。在下载时，用户应根据自己的操作系统选择相应的版本进行下载。下载完成后，双击安装文件，按照安装向导的提示完成安装过程。</p>
<h5>2. 注册与登录</h5>
<p>安装完成后，用户需要注册一个Zoom账号并登录。注册过程简单快捷，用户只需提供必要的信息（如邮箱地址、密码等）即可完成注册。如果已有Zoom账号，用户可以直接使用已有的账号登录。</p>
<h4>三、Zoom视频会议软件的基本功能</h4>
<h5>1. 视频会议</h5>
<p>Zoom支持高质量的视频会议，最多可容纳1000名视频参与者和49个视频屏幕视图，适用于各种规模的团队会议。在会议中，用户可以清晰地看到其他参与者的视频画面，并通过麦克风和扬声器进行语音交流。Zoom还提供了虚拟背景和滤镜功能，用户可以根据自己的需求选择合适的背景或滤镜，保护隐私或增加趣味性。</p>
<h5>2. 音频通话</h5>
<p>除了视频通话外，Zoom还支持纯音频通话功能。当用户网络环境不佳或出于隐私考虑不想开启摄像头时，可以选择仅使用音频通话功能进行会议。</p>
<h5>3. 屏幕共享</h5>
<p>Zoom的屏幕共享功能允许用户将自己的电脑屏幕或特定应用程序窗口共享给会议中的其他参与者。这一功能非常适合进行演示、协作和培训等活动。用户只需点击屏幕共享按钮，选择要共享的内容即可开始共享。</p>
<h5>4. 聊天功能</h5>
<p>Zoom内置了聊天功能，用户可以在会议中通过聊天栏发送文字消息，与其他参与者进行即时交流。聊天记录也会被保存下来，方便用户后续查看。</p>
<h5>5. 会议录制</h5>
<p>Zoom支持会议录制功能，用户可以选择录制整个会议或仅录制自己的屏幕和声音。录制完成后，会议视频将被保存到本地或云端存储中，用户可以随时查看和分享。</p>
<h5>6. 虚拟白板</h5>
<p>Zoom还提供了虚拟白板功能，支持多人同时书写和绘图。这一功能有助于头脑风暴和讨论等需要协作的活动。用户可以在白板上自由书写、画图或插入图片等多媒体元素。</p>
<h4>四、Zoom视频会议软件的高级功能</h4>
<h5>1. 会议安排与邀请</h5>
<p>Zoom支持预先安排会议功能，用户可以通过Zoom的日历集成功能直接从Google日历或Outlook安排和启动会议。在安排会议时，用户可以设置会议时间、日期、参与者等信息，并生成会议邀请链接发送给参与者。参与者只需点击链接即可加入会议。</p>
<h5>2. 会议密码与等候室</h5>
<p>为了保障会议的安全性，Zoom提供了会议密码和等候室功能。用户可以为会议设置密码，并开启等候室功能。这样，只有输入正确密码的参与者才能加入会议，而未经邀请的参与者则会被安排在等候室中等待主持人批准加入。</p>
<h5>3. 主持人控制</h5>
<p>在会议中，主持人拥有多种控制权限，如静音所有参与者、锁定会议、防止新参与者加入等。这些功能有助于主持人确保会议的有序进行和防止不必要的干扰。</p>
<h5>4. 分组讨论</h5>
<p>Zoom支持分组讨论功能，主持人可以将大会议分成小组讨论。每个小组都可以在自己的会议室中进行讨论和交流，而主持人则可以在主会议室中监控各个小组的讨论情况。这一功能有助于提高会议的互动性和参与度。</p>
<h5>5. 远程支持</h5>
<p>Zoom还提供了远程支持功能，允许技术支持人员通过远程桌面控制帮助用户解决问题。这一功能非常适合IT支持团队或远程客服团队使用。</p>
<h4>五、Zoom视频会议软件的使用技巧</h4>
<h5>1. 确保网络连接稳定</h5>
<p>稳定的网络连接是视频会议顺利进行的基础。用户在使用Zoom进行视频会议时，应确保自己的网络连接稳定可靠，避免出现卡顿、掉线等问题。</p>
<h5>2. 提前测试音视频设备</h5>
<p>在会议开始前，用户应提前测试自己的音视频设备（如摄像头、麦克风、扬声器等）是否正常工作。如果发现设备有问题或效果不佳，应及时进行调整或更换。</p>
<h5>3. 关闭不必要的应用程序</h5>
<p>在会议期间，用户应关闭不必要的应用程序和后台程序，以减少对系统资源的占用和干扰。</p>
<h4>六、Zoom视频会议软件的安全性与隐私保护</h4>
<p>随着远程办公和在线教育的普及，视频会议软件的安全性与隐私保护问题日益受到关注。Zoom在此方面投入了大量资源，采取了一系列措施来确保用户的数据安全和隐私保护。</p>
<h5>1. 端到端加密</h5>
<p>Zoom提供了端到端加密的选项，这意味着在会议过程中传输的所有数据都会被加密，只有会议的参与者才能解密并查看。这一功能大大增强了会议内容的安全性，防止了数据泄露的风险。</p>
<h5>2. 双重身份验证</h5>
<p>为了进一步提高账户安全性，Zoom支持双重身份验证功能。用户在登录时需要除了密码外，还需要通过短信验证码、邮箱验证码或身份验证应用等方式进行二次验证。这样即使密码被泄露，攻击者也难以轻易登录用户的账户。</p>
<h5>3. 隐私设置</h5>
<p>Zoom允许用户根据自己的需求调整隐私设置。用户可以选择是否允许其他人看到自己的视频、是否接收来自陌生人的会议邀请等。此外，Zoom还提供了虚拟背景和模糊背景功能，帮助用户在保护个人隐私的同时，保持会议的专业性。</p>
<h5>4. 遵守法律法规</h5>
<p>Zoom严格遵守各国和地区的法律法规，包括数据保护法和隐私法等。公司在全球范围内设有数据中心，并采用先进的技术手段确保用户数据的安全性和合规性。同时，Zoom还积极与监管机构合作，共同维护用户的合法权益。</p>
<h4>七、Zoom视频会议软件在教育领域的应用</h4>
<p>随着在线教育的兴起，Zoom成为了许多教育机构和教育工作者的首选工具。其强大的功能和灵活的使用方式，为远程教学提供了极大的便利。</p>
<h5>1. 实时互动教学</h5>
<p>通过Zoom的视频会议功能，教师可以与学生进行实时互动教学。教师可以展示PPT、播放视频、进行板书等教学活动，而学生则可以通过摄像头和麦克风与教师进行问答和讨论。这种教学方式打破了地域限制，使得优质教育资源得以广泛传播。</p>
<h5>2. 虚拟课堂管理</h5>
<p>Zoom提供了丰富的虚拟课堂管理功能，如分组讨论、举手发言、在线投票等。这些功能有助于教师更好地组织和管理课堂秩序，提高教学效果。同时，Zoom还支持会议录制和回放功能，学生可以在课后回顾课程内容，巩固所学知识。</p>
<h5>3. 远程监考与考试</h5>
<p>Zoom还可以用于远程监考和在线考试。教师可以利用Zoom的屏幕共享和监控功能，实时查看学生的答题情况，确保考试的公正性和准确性。此外，Zoom还支持多人协作编辑文档的功能，使得团队合作的在线考试和作业提交变得更加便捷。</p>
<h4>八、Zoom视频会议软件的未来展望</h4>
<p>随着技术的不断进步和市场需求的不断变化，Zoom视频会议软件也在不断迭代和升级。未来，Zoom有望在以下几个方面取得更大的突破和发展：</p>
<h5>1. 智能化升级</h5>
<p>Zoom将继续加强AI技术的应用，如自然语言处理、机器学习等。通过智能化升级，Zoom可以为用户提供更加个性化的会议体验和服务。例如，智能推荐会议议程、自动翻译和语音识别等功能将大大提升会议的效率和便捷性。</p>
<h5>2. 拓展应用场景</h5>
<p>除了现有的企业会议、在线教育等领域外，Zoom还将继续拓展其应用场景。例如，在医疗领域，Zoom可以与医疗设备集成，实现远程会诊和手术指导等功能；在娱乐领域，Zoom可以开发虚拟演唱会、在线游戏等新型娱乐形式。</p>
<h5>3. 强化数据安全与隐私保护</h5>
<p>随着用户对数据安全和隐私保护的需求日益增加，Zoom将继续加强其数据安全与隐私保护能力。公司将投入更多资源用于技术研发和合规建设，确保用户数据的安全性和合规性。同时，Zoom还将积极与监管机构合作，共同维护用户的合法权益。</p>
<h5>4. 全球化布局</h5>
<p>Zoom将继续加强其全球化布局和本地化服务。公司将在全球范围内建立更多的数据中心和服务机构，以提供更加稳定和高效的服务。同时，Zoom还将根据不同国家和地区的法律法规和文化习惯进行本地化调整和优化，以满足不同用户的需求。</p>
<p>综上所述，Zoom视频会议软件凭借其强大的功能、便捷的使用方式和出色的安全性与隐私保护能力，在全球范围内赢得了广泛的认可和应用。未来，随着技术的不断进步和市场需求的不断变化，Zoom有望在更多领域取得更大的突破和发展。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140917200</p>]]></description><guid isPermaLink="false">/archives/c244a2ce-566e-4f96-85f5-3c21ca501f77</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:18:38 GMT</pubDate></item><item><title><![CDATA[机器学习领域中选择使用Python还是R]]></title><link>https://wiki.dgfygk.com/archives/a01eb6a7-8d93-40ef-a9ae-956f922833ea</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E9%A2%86%E5%9F%9F%E4%B8%AD%E9%80%89%E6%8B%A9%E4%BD%BF%E7%94%A8Python%E8%BF%98%E6%98%AFR&amp;url=/archives/a01eb6a7-8d93-40ef-a9ae-956f922833ea" width="1" height="1" alt="" style="opacity:0;">
<p>在机器学习领域中，选择使用Python还是R，这主要取决于个人需求、项目特性、技能水平以及偏好。以下是对两种语言在机器学习方面的详细比较：</p>
<h4>一、社区支持与生态系统</h4>
<ul>
 <li><strong>Python</strong>：Python在数据科学和机器学习领域拥有庞大的社区支持，这意味着你可以轻松找到大量的教程、文档、库和框架。Python的生态系统非常活跃，不断有新的库和工具被开发出来，以满足不同领域的需求。例如，NumPy、Pandas、Scikit-learn、TensorFlow、PyTorch等都是Python在机器学习领域非常受欢迎的库和框架。</li>
 <li><strong>R</strong>：R语言在统计分析和数据可视化方面有着深厚的基础，其社区也相对活跃。R语言拥有一些强大的包，如ggplot2、dplyr等，这些包在数据分析和可视化方面表现出色。然而，与Python相比，R的数据科学生态系统可能稍小一些。</li>
</ul>
<h4>二、学习曲线与易用性</h4>
<ul>
 <li><strong>Python</strong>：Python的语法简洁明了，更接近自然语言，这使得它对于初学者来说更容易上手。Python的学习曲线相对平缓，即使是编程新手也能较快地掌握其基础知识，并开始编写机器学习代码。</li>
 <li><strong>R</strong>：R的语法相对复杂，需要更多的学习和实践才能掌握。尽管R在统计分析和数据可视化方面非常强大，但其学习曲线可能对于初学者来说较为陡峭。</li>
</ul>
<h4>三、性能与扩展性</h4>
<ul>
 <li><strong>Python</strong>：Python是一种解释型语言，但在处理大规模数据集时，通过使用NumPy、Pandas等库，它可以提供非常高效的计算性能。此外，Python的可扩展性也很强，可以轻松集成C、C++等语言的代码，以提高性能。</li>
 <li><strong>R</strong>：R是一种编译型语言（实际上，R是解释执行的，但在这里为了与Python对比，我们可以说它在编译阶段会进行优化以提高执行效率），在某些情况下，其执行效率可能高于Python。然而，随着Python性能的不断提升，这一差距正在逐渐缩小。此外，R也可以通过调用C、C++等语言的代码来扩展其功能。</li>
</ul>
<h4>四、应用领域</h4>
<ul>
 <li><strong>Python</strong>：Python在机器学习领域的应用非常广泛，尤其是在深度学习领域。Python拥有TensorFlow、Keras、PyTorch等深度学习库，这些库提供了丰富的功能和灵活的接口，使得Python成为了深度学习领域的首选语言之一。</li>
 <li><strong>R</strong>：R在机器学习领域的应用相对较少，主要集中在统计学和回归模型等方面。然而，R在数据分析和可视化方面的强大功能仍然使其在某些领域具有不可替代的地位。</li>
</ul>
<h4>五、可视化</h4>
<ul>
 <li><strong>R</strong>：R在数据可视化方面有着强大的功能，其绘图功能非常强大，可以绘制各种复杂的图表和图形。这使得R在需要高度定制化的数据可视化项目中非常受欢迎。</li>
 <li><strong>Python</strong>：Python也有一些优秀的可视化库，如Matplotlib、Seaborn等。虽然Python的可视化能力不如R那么强大，但对于大多数机器学习项目来说已经足够使用。</li>
</ul>
<p>综上所述，选择Python还是R进行机器学习取决于你的具体需求。如果你是一个初学者，想要一个简单易学的语言，并且希望在一个庞大的社区中找到支持，那么Python可能是更好的选择。如果你对统计学和数据可视化有较高的需求，并且愿意投入更多的时间来学习一门相对复杂的语言，那么R可能更适合你。无论你选择哪种语言，都需要掌握一些基本的机器学习算法和概念，以便能够有效地利用这些工具进行机器学习实践。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140944624</p>]]></description><guid isPermaLink="false">/archives/a01eb6a7-8d93-40ef-a9ae-956f922833ea</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:18:25 GMT</pubDate></item><item><title><![CDATA[C++11革新之旅：探索C++编程的无限可能]]></title><link>https://wiki.dgfygk.com/archives/4109f0a5-4a7a-4f0b-bbe2-79cf6bbbe2b4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=C%2B%2B11%E9%9D%A9%E6%96%B0%E4%B9%8B%E6%97%85%EF%BC%9A%E6%8E%A2%E7%B4%A2C%2B%2B%E7%BC%96%E7%A8%8B%E7%9A%84%E6%97%A0%E9%99%90%E5%8F%AF%E8%83%BD&amp;url=/archives/4109f0a5-4a7a-4f0b-bbe2-79cf6bbbe2b4" width="1" height="1" alt="" style="opacity:0;">
<h2>C++11革新之旅：探索C++编程的无限可能</h2>
<p>C++11，作为C++语言的一个重要标准，为C++编程带来了革命性的变革。它不仅引入了众多新特性和改进，还极大地增强了C++的表达能力、提高了程序的性能和资源利用率。本文将从多个方面深入探讨C++11的新特性，并解析这些特性如何改变C++编程的方式。</p>
<h3>一、右值引用与移动语义</h3>
<h4>1.1 右值引用</h4>
<p>在C++11之前，C++中的引用主要通过左值引用（通过符号<code>&amp;</code>表示）实现，用于为已存在的对象取别名。然而，随着C++11的发布，右值引用（通过符号<code>&amp;&amp;</code>表示）被引入，这一特性允许我们引用即将被销毁的对象，从而能够安全地"窃取"其资源。</p>
<p>右值通常表示临时数据，如字面量、函数返回值等，它们通常不具有可寻址性，并且只能出现在赋值语句的右侧。C++11进一步将右值细分为纯右值（如字面量）和将亡值（如<code>std::move</code>返回的引用）。通过右值引用，我们可以编写出更加高效的代码，特别是在处理大量数据或资源时。</p>
<h4>1.2 移动语义</h4>
<p>移动语义是C++11引入的另一项重要特性，它通过右值引用和移动构造函数、移动赋值运算符实现了资源的高效转移。在C++11之前，对象传递和赋值通常通过拷贝构造函数或赋值运算符完成，这可能导致大量资源的深拷贝。对于包含大量数据或资源的对象，深拷贝不仅效率低下，还可能浪费资源。</p>
<p>移动语义允许我们将一个对象（源对象）的资源（如动态分配的内存、文件句柄等）转移到另一个对象（目标对象）中，而不是复制这些资源。这通过移动构造函数和移动赋值运算符实现，它们分别用于在对象初始化时和赋值操作中转移资源。通过转移资源而非复制资源，我们可以显著减少不必要的资源分配和释放操作，从而提高程序的执行效率。</p>
<h3>二、自动类型推导与统一初始化</h3>
<h4>2.1 自动类型推导（auto）</h4>
<p>C++11引入了<code>auto</code>关键字，允许编译器自动推导变量的类型。这大大简化了代码编写，特别是当变量类型复杂或难以直接写出时。<code>auto</code>的引入使得我们可以根据初始化表达式的类型来自动确定变量的类型，从而减少了类型重复和潜在的错误。</p>
<h4>2.2 统一初始化（Uniform Initialization）</h4>
<p>C++11还引入了统一初始化语法，允许我们使用花括号<code>{}</code>来初始化对象。这一特性不仅简化了对象的初始化过程，还提高了代码的一致性和可读性。无论是基本类型、数组、类对象还是容器，都可以使用统一的初始化语法进行初始化。</p>
<h3>三、Lambda表达式与范围for循环</h3>
<h4>3.1 Lambda表达式</h4>
<p>Lambda表达式是C++11引入的另一个强大特性，它允许在代码中定义匿名函数。Lambda表达式可以捕获其所在作用域中的变量，并在需要时执行一段代码。这一特性简化了函数对象的创建和使用，使得代码更加简洁、灵活。</p>
<h4>3.2 范围for循环（Range-based for loop）</h4>
<p>范围for循环是C++11中用于遍历容器、数组等可迭代对象的一种简洁方式。它自动遍历容器中的每个元素，并将当前元素的值赋给循环变量，从而简化了循环的编写。范围for循环的引入使得遍历容器中的元素变得更加直观和方便。</p>
<h3>四、智能指针与内存管理</h3>
<p>C++11引入了三种主要的智能指针：<code>std::unique_ptr</code>、<code>std::shared_ptr</code>和<code>std::weak_ptr</code>，它们用于更安全、方便地管理动态分配的资源，避免内存泄漏和悬空指针等问题。</p>
<ul>
 <li><strong>std::unique_ptr</strong> ：一种独占式智能指针，用于管理唯一的对象，确保只有一个指针可以访问该对象。当<code>std::unique_ptr</code>超出作用域或被重置时，它会自动删除所管理的对象。</li>
 <li><strong>std::shared_ptr</strong> ：一种共享式智能指针，允许多个指针同时共享对同一对象的拥有权。<code>std::shared_ptr</code>使用引用计数技术来追踪所管理对象的引用数量，当引用计数变为零时，自动销毁所管理的对象。</li>
 <li><strong>std::weak_ptr</strong> ：一种弱引用智能指针，用于解决<code>std::shared_ptr</code>的循环引用问题。<code>std::weak_ptr</code>指向<code>std::shared_ptr</code>管理的对象，但不会增加引用计数。因此，当所有<code>std::shared_ptr</code>对象超出作用域后，即使还有<code>std::weak_ptr</code>对象存在，所管理的对象也会被销毁。</li>
</ul>
<h3>五、其他重要特性</h3>
<h4>5.1 线程支持与并发编程</h4>
<p>C++11首次将多线程编程的支持直接集成到标准库中，通过<code>&lt;thread&gt;</code>、<code>&lt;mutex&gt;</code>、<code>&lt;condition_variable&gt;</code>等头文件，C++程序员可以更加方便地编写并发和并行程序。</p>
<ul>
 <li>
  <p><strong>std::thread</strong> ：C++11引入了<code>std::thread</code>类，允许创建和管理线程。程序员可以定义线程执行的函数或可调用对象，并通过<code>std::thread</code>的构造函数传递给新线程。当<code>std::thread</code>对象被销毁时，如果其关联的线程仍在运行，则会调用<code>std::terminate()</code>终止程序。因此，需要确保线程在<code>std::thread</code>对象销毁之前正确结束。</p>
 </li>
 <li>
  <p><strong>同步原语</strong> ：为了在多线程环境中保护共享数据，C++11提供了多种同步原语，包括互斥锁（<code>std::mutex</code>）、递归互斥锁（<code>std::recursive_mutex</code>）、锁保护器（<code>std::lock_guard</code>、<code>std::unique_lock</code>）、条件变量（<code>std::condition_variable</code>）等。这些工具可以帮助程序员实现线程间的同步，避免数据竞争和死锁等问题。</p>
 </li>
</ul>
<h4>5.2 类型特征（Type Traits）</h4>
<p>C++11引入了一系列类型特征模板，用于在编译时查询类型信息。这些类型特征模板定义在<code>&lt;type_traits&gt;</code>头文件中，包括<code>std::is_integral</code>、<code>std::is_class</code>、<code>std::remove_const</code>、<code>std::enable_if</code>等。类型特征模板的引入使得模板元编程更加灵活和强大，程序员可以在编译时根据类型信息决定代码的行为。</p>
<h4>5.3 基于范围的for循环的增强</h4>
<p>虽然前面已经提到了范围for循环，但值得一提的是，C++17进一步增强了这一特性，允许在初始化列表和数组上进行迭代。这使得范围for循环的适用范围更加广泛，代码更加简洁。</p>
<h4>5.4 标准库的扩展</h4>
<p>C++11对标准库进行了大量扩展，增加了许多新的容器和算法，以及改进了现有容器和算法的性能。例如，<code>std::unordered_map</code>和<code>std::unordered_set</code>等基于哈希表的容器被引入，它们提供了平均常数时间复杂度的查找、插入和删除操作。此外，C++11还引入了正则表达式库（<code>&lt;regex&gt;</code>）、元组（<code>&lt;tuple&gt;</code>）、智能指针（如前所述）等，进一步丰富了C++的标准库。</p>
<h4>5.5 原子操作和内存模型</h4>
<p>C++11引入了<code>&lt;atomic&gt;</code>头文件，提供了一套原子操作类型和函数，用于实现无锁编程。原子操作是指在执行过程中不会被线程调度机制中断的操作，保证了多线程环境下的数据一致性和安全性。C++11还定义了内存模型，明确了数据在不同线程间的可见性和顺序性，为并发编程提供了更加严格的保障。</p>
<h4>5.6 变参模板和完美转发</h4>
<p>C++11引入了变参模板和完美转发机制，允许编写能够接收任意数量和类型参数的模板函数和模板类。变参模板通过模板参数包（template parameter packs）实现，而完美转发则通过<code>std::forward</code>函数和引用折叠规则（reference collapsing rules）实现。这些特性使得C++11的模板编程更加灵活和强大，能够编写出更加通用和高效的代码。</p>
<h4>总结</h4>
<p>C++11作为C++语言的一个重要标准，为C++编程带来了诸多新特性和改进。从右值引用和移动语义到自动类型推导和统一初始化，从Lambda表达式和范围for循环到智能指针和内存管理，再到线程支持、类型特征、标准库扩展等，这些特性共同构成了C++11的丰富内容。它们不仅提高了C++的表达能力、简化了代码编写，还增强了C++的性能和安全性。随着C++11的广泛应用和深入发展，我们有理由相信C++将继续在软件开发领域发挥重要作用。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140901975</p>]]></description><guid isPermaLink="false">/archives/4109f0a5-4a7a-4f0b-bbe2-79cf6bbbe2b4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 03:18:12 GMT</pubDate></item><item><title><![CDATA[JVM生态创新]]></title><link>https://wiki.dgfygk.com/archives/6f3c9e5b-4a6e-4f2b-9582-a0a92f549ff9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JVM%E7%94%9F%E6%80%81%E5%88%9B%E6%96%B0&amp;url=/archives/6f3c9e5b-4a6e-4f2b-9582-a0a92f549ff9" width="1" height="1" alt="" style="opacity:0;">
<p><strong>JVM生态创新</strong></p>
<p>Java虚拟机（JVM）作为Java技术的核心，不仅为Java语言提供了一个运行时环境，还构建了一个庞大而丰富的生态系统。这个生态系统涵盖了开发工具、框架、库和平台等多个方面，支持多种编程语言和应用场景，特别是在大数据处理、微服务架构、云计算等领域展现出了强大的生命力和创新力。本文将从多个角度探讨JVM生态的创新与发展。</p>
<h4>一、JVM生态的构成</h4>
<p>JVM生态的构成复杂而多样，主要包括以下几个方面：</p>
<ol>
 <li>
  <p><strong>开发工具</strong>：集成开发环境（IDE）如IntelliJ IDEA、Eclipse、NetBeans等，为开发者提供了强大的代码编辑、调试和项目管理功能。此外，Maven、Gradle等构建工具帮助开发者管理项目依赖、构建和发布。Git、SVN等版本控制系统则支持代码的版本管理和团队协作。</p>
 </li>
 <li>
  <p><strong>编程语言</strong>：Java是JVM的主要编程语言，但JVM并非仅限于Java。Kotlin、Scala、Groovy、Clojure等多种编程语言都能在JVM上运行，这些语言各有特色，为开发者提供了更多的选择和可能性。</p>
 </li>
 <li>
  <p><strong>框架与库</strong>：Spring、Hibernate、Struts等框架为Java企业级应用开发提供了强大的支持。Spring Boot更是简化了Spring应用的开发、配置和部署过程。此外，Spark、Kafka、Flink等大数据处理框架和流处理平台也基于JVM运行，为大数据处理提供了高效的计算和存储能力。</p>
 </li>
 <li>
  <p><strong>平台与服务</strong>：Java EE、Jakarta EE等企业级应用平台提供了丰富的服务，包括Web服务、事务管理、安全性等。而云服务提供商如Amazon Web Services、Microsoft Azure等也提供了基于JVM的云服务，使得Java应用能够轻松部署到云端。</p>
 </li>
</ol>
<h4>二、JVM生态的创新</h4>
<h5>1. 编程语言的创新</h5>
<p>JVM上的编程语言不断创新，为开发者提供了更多的选择和灵活性。例如：</p>
<ul>
 <li><strong>Kotlin</strong>：作为一种现代的静态类型编程语言，Kotlin与Java完全兼容，但语法更加简洁、安全。Kotlin的协程（Coroutine）特性使得异步编程变得更加简单和直观，广泛应用于Android开发和服务器端开发。</li>
 <li><strong>Scala</strong>：Scala融合了面向对象和函数式编程的特性，使得开发者能够以更加灵活和高效的方式编写代码。Scala在大数据处理和分布式计算领域有着广泛的应用，如Apache Spark就是基于Scala开发的。</li>
 <li><strong>Groovy</strong>：Groovy是一种动态类型编程语言，具有简洁的语法和强大的脚本能力。它广泛应用于构建脚本、测试等领域，同时也能够作为Java应用的一部分进行开发。</li>
</ul>
<h5>2. 框架与库的创新</h5>
<p>随着技术的不断发展，JVM上的框架与库也在不断创新和完善。例如：</p>
<ul>
 <li><strong>Spring Boot</strong>：Spring Boot通过自动配置、嵌入式服务器等功能，极大地简化了Spring应用的开发和部署过程。它使得开发者能够更加专注于业务逻辑的实现，而不是繁琐的配置和部署工作。</li>
 <li><strong>Apache Spark</strong>：Spark是一个快速、通用的大数据处理引擎，基于JVM运行。它提供了内存计算和批处理能力，能够显著提高大数据处理的效率。Spark还包含了Spark SQL、Spark Streaming、MLlib、GraphX等多个模块，支持结构化数据处理、实时数据处理、机器学习和图计算等多种场景。</li>
 <li><strong>Apache Kafka</strong>：Kafka是一个分布式流处理平台，基于JVM运行。它支持高吞吐量的消息发布和订阅，广泛应用于日志收集、消息系统、流处理等领域。Kafka的分布式架构和容错机制使得它能够处理大规模的数据流。</li>
</ul>
<h5>3. 平台与服务的创新</h5>
<p>随着云计算的兴起，JVM生态也在不断向云端延伸。各大云服务提供商纷纷推出了基于JVM的云服务，如Amazon Web Services的Elastic Beanstalk、Microsoft Azure的App Service等。这些云服务提供了丰富的功能和灵活的计费方式，使得Java应用能够轻松部署到云端，并享受云端带来的弹性伸缩、高可用性等优势。</p>
<h4>三、JVM生态的未来展望</h4>
<h5>1. 容器化技术的普及</h5>
<p>随着Docker等容器化技术的普及，JVM应用也将更加容易地实现容器化部署。容器化部署可以提高应用的可移植性、可伸缩性和安全性，使得JVM应用能够更加灵活地适应不同的运行环境。</p>
<h5>2. 云原生技术的融合</h5>
<p>云原生技术如Kubernetes、Serverless等正在逐渐改变应用的部署和管理方式。JVM生态也将积极融入云原生技术，提供更加高效、灵活的应用部署和管理解决方案。</p>
<h5>3. 多语言虚拟机的发展</h5>
<p>GraalVM等多语言虚拟机的发展为JVM生态带来了新的可能性。它们支持多种编程语言在JVM上高效运行，促进了不同编程语言之间的互操作和融合。这将使得JVM生态更加多元化和包容性，为开发者提供更多的选择和可能性。</p>
<h5>4. AI与大数据的深度融合</h5>
<p>随着AI技术的不断发展，大数据处理将成为AI应用的重要支撑。JVM生态中的大数据处理框架和流处理平台将不断优化和完善，以更好地支持AI应用的数据处理需求。同时，JVM生态也将积极融入AI技术，提供更加智能化的应用开发和管理解决方案。</p>
<h4>四、技术深度与广度拓展</h4>
<h5>1. 模块化与微服务的深化</h5>
<p>随着微服务架构的流行，JVM生态中的框架和库也在持续优化以支持更细粒度的服务划分和更高效的服务间通信。Java平台模块系统（JPMS，即Java Platform Module System，引入于Java 9）为Java应用带来了模块化的能力，使得开发者可以更容易地构建和管理大型、复杂的项目。这一特性与微服务架构相结合，将进一步推动应用的模块化和服务的独立部署，提高系统的可扩展性和可维护性。</p>
<h5>2. 性能与资源管理的优化</h5>
<p>JVM生态中的性能优化一直是一个重要议题。随着硬件技术的不断进步和应用的日益复杂，JVM和Java应用需要不断优化以充分利用现代硬件资源，如多核CPU、大内存和高速存储。这包括JVM内部的优化，如垃圾收集器的改进、即时编译器的优化等，以及应用层面的优化，如代码优化、资源管理等。此外，随着容器化和云原生技术的发展，JVM生态还需要更好地支持资源的动态分配和管理，以适应云环境的弹性伸缩和高效利用。</p>
<h5>3. 安全性的加强</h5>
<p>随着网络安全威胁的日益严峻，JVM生态也在不断加强安全性。这包括JVM层面的安全机制，如沙箱模型、访问控制等，以及应用层面的安全措施，如加密解密、身份验证、授权等。同时，JVM生态还需要关注新出现的安全威胁和漏洞，及时发布安全更新和修复措施，保障Java应用的稳定运行和用户数据的安全。</p>
<h4>五、社区与生态的繁荣</h4>
<h5>1. 开源社区的贡献</h5>
<p>JVM生态的繁荣离不开开源社区的贡献。开源项目如Apache、Eclipse等提供了丰富的框架、库和工具，为开发者提供了强大的支持。同时，开源社区还通过举办会议、研讨会、黑客松等活动，促进了技术的交流和分享，推动了JVM生态的创新和发展。</p>
<h5>2. 企业与个人的参与</h5>
<p>除了开源社区外，众多企业和个人也在积极参与JVM生态的建设和发展。他们通过贡献代码、提交bug报告、分享经验等方式，为JVM生态的繁荣做出了重要贡献。同时，企业和个人还通过商业支持和定制开发等方式，将JVM生态应用于各种场景和领域，推动了技术的普及和应用。</p>
<h4>六、总结</h4>
<p>JVM生态作为一个庞大而复杂的系统，在不断创新和发展中展现出了强大的生命力和创新力。从编程语言的创新到框架与库的完善，从平台与服务的拓展到技术深度与广度的提升，JVM生态在不断推动着Java技术的发展和应用。同时，随着开源社区的贡献、企业与个人的参与以及技术趋势的推动，JVM生态将继续保持繁荣和活力，为开发者提供更加高效、灵活和安全的开发环境。</p>
<p>在未来，我们可以期待JVM生态在更多领域和场景中的应用和创新。无论是大数据处理、云计算、微服务架构还是人工智能等领域，JVM生态都将发挥重要作用，为技术的进步和应用的创新提供有力支持。同时，随着技术的不断发展和需求的不断变化，JVM生态也将不断适应和进化，以更好地满足开发者和用户的需求。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140885375</p>]]></description><guid isPermaLink="false">/archives/6f3c9e5b-4a6e-4f2b-9582-a0a92f549ff9</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 03:17:59 GMT</pubDate></item><item><title><![CDATA[Java新版本概述]]></title><link>https://wiki.dgfygk.com/archives/78db1059-42d0-466b-aa12-39e3eb835bd3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E6%96%B0%E7%89%88%E6%9C%AC%E6%A6%82%E8%BF%B0&amp;url=/archives/78db1059-42d0-466b-aa12-39e3eb835bd3" width="1" height="1" alt="" style="opacity:0;">
<p>Java作为一种广泛使用的编程语言，其新版本的不断推出不仅带来了新特性和功能的增加，也针对性能优化进行了诸多改进。以下将详细探讨Java新版本与性能优化.</p>
<h4>一、Java新版本概述</h4>
<p>Java的发展是一个持续演进的过程，每个新版本都致力于提升编程的便利性、提高程序的执行效率和稳定性。近年来，Java的版本更新速度加快，每年都会有新的版本发布，这些新版本中包含了大量的新特性和性能优化。</p>
<h4>二、Java新版本中的性能优化</h4>
<h5>1. 即时编译器（JIT）优化</h5>
<p>即时编译器（JIT）是Java虚拟机（JVM）中的一个重要组成部分，它能够在运行时将Java字节码编译成本地机器码，从而提高程序的执行效率。在新版本的Java中，JIT编译器得到了进一步的优化，主要体现在以下几个方面：</p>
<ul>
 <li><strong>更精准的代码识别</strong>：JIT编译器能够更精准地识别出程序中的热点代码（即频繁执行的代码），并对这些代码进行深度优化，以减少执行时间。</li>
 <li><strong>高效的代码生成策略</strong>：通过引入更先进的算法和策略，JIT编译器能够生成更高效的机器码，从而提升程序的执行速度。</li>
 <li><strong>智能的内存管理</strong>：JIT编译器与垃圾回收器（GC）紧密协作，通过优化内存分配和回收策略，减少内存泄漏和GC停顿时间，提高程序的内存使用效率。</li>
</ul>
<h5>2. 垃圾回收（GC）优化</h5>
<p>垃圾回收是JVM中一个至关重要的功能，它负责回收不再使用的内存空间，以避免内存泄漏和溢出。在新版本的Java中，垃圾回收机制得到了进一步的优化，主要包括以下几个方面：</p>
<ul>
 <li><strong>新的垃圾回收器</strong>：如G1（Garbage-First）垃圾回收器已经成为许多Java应用的默认选择。G1垃圾回收器采用了分区（Region）的概念，将堆内存划分为多个区域，并优先回收垃圾最多的区域，从而提高了回收效率。</li>
 <li><strong>并行与并发回收</strong>：新版本的Java支持并行和并发的垃圾回收方式，能够在不停止应用执行的情况下进行垃圾回收，减少了GC停顿时间对应用性能的影响。</li>
 <li><strong>自适应调节</strong>：部分垃圾回收器能够根据应用的运行情况和资源使用情况自适应地调整回收策略和参数，以达到最优的回收效果。</li>
</ul>
<h5>3. 模块化系统（JPMS）增强</h5>
<p>自Java 9引入模块化系统（JPMS）以来，Java的模块化能力得到了显著提升。在新版本的Java中，模块化系统得到了进一步增强，主要体现在以下几个方面：</p>
<ul>
 <li><strong>更细粒度的模块控制</strong>：开发者可以更加精细地控制模块之间的依赖关系和可见性，减少不必要的模块间耦合，提高代码的可维护性和可重用性。</li>
 <li><strong>灵活的依赖管理机制</strong>：模块化系统提供了灵活的依赖管理机制，允许开发者在编译时和运行时动态地加载和卸载模块，为构建可扩展和可插拔的应用提供了有力支持。</li>
 <li><strong>封装能力提升</strong>：通过增强封装能力，模块化系统能够更好地保护模块内部的实现细节不被外部访问和修改，提高了代码的安全性和稳定性。</li>
</ul>
<h5>4. 新API与语言特性</h5>
<p>新版本的Java还引入了一系列新的API和语言特性，为开发者提供了更加强大和灵活的编程工具集。这些新特性和API的引入不仅提升了编程的便利性，也对性能优化产生了积极影响。例如：</p>
<ul>
 <li><strong>模式匹配（Pattern Matching）</strong>：Java 17引入的预览特性之一，允许开发者以更加直观和简洁的方式处理类型匹配和条件逻辑。这一特性减少了样板代码并提高了代码的可读性，间接提升了程序的执行效率。</li>
 <li><strong>文本块（Text Blocks）</strong>：这一特性使得在Java中编写多行字符串变得更加简单和直观。开发者无需再使用复杂的字符串拼接或转义字符，而是可以直接在代码中嵌入多行文本。这不仅提高了编程的便利性，也减少了因字符串拼接而产生的性能开销。</li>
 <li><strong>新的日期和时间API</strong>：Java 8引入的新的日期和时间API（如java.time包）提供了更加强大和灵活的日期时间处理能力。与传统的java.util.Date和java.util.Calendar相比，新的API不仅易于使用且性能更优。</li>
</ul>
<h4>三、Java性能优化策略</h4>
<p>除了Java新版本中自带的性能优化措施外，开发者还可以通过一些策略来进一步提升Java应用的性能。以下是一些常用的性能优化策略：</p>
<h5>1. 代码优化</h5>
<ul>
 <li><strong>避免不必要的对象创建</strong>：在Java中，对象的创建和销毁都需要消耗一定的时间和资源。因此，在编写代码时应尽量避免不必要的对象创建和销毁操作。</li>
 <li><strong>使用合适的数据结构</strong>：选择合适的数据结构可以大大提高程序的执行效率。例如，在需要频繁查找元素时可以考虑使用HashMap等哈希表结构；在需要保持元素顺序时可以考虑使用ArrayList等列表结构。</li>
 <li><strong>优化循环和递归</strong>：循环和递归是编程中常用的控制结构，但如果不当使用会导致性能问题。因此，在编写循环和递归时应尽量优化其逻辑结构以减少不必要的迭代次数和计算复杂度。</li>
</ul>
<h5>2. 并发与并行处理</h5>
<ul>
 <li><strong>利用多线程和并发API</strong> ：Java提供了丰富的多线程和并发API，如<code>java.util.concurrent</code>包中的类，可以帮助开发者有效地利用多核CPU的优势，通过并发执行多个任务来提升程序的整体性能。</li>
 <li><strong>合理使用线程池</strong>：线程池可以管理一组工作线程，通过复用线程来减少线程创建和销毁的开销。在需要频繁创建和销毁线程的场景中，使用线程池可以显著提高性能。</li>
 <li><strong>避免共享资源竞争</strong>：在并发环境中，多个线程可能会同时访问共享资源，导致性能下降甚至数据不一致。因此，开发者需要采取适当的同步措施来避免共享资源竞争，如使用锁、信号量等同步机制。</li>
</ul>
<h5>3. 内存优化</h5>
<ul>
 <li><strong>减少内存占用</strong>：通过优化数据结构、复用对象、减少内存泄漏等方式来减少应用的内存占用。这不仅可以降低GC的频率和停顿时间，还可以提高应用的稳定性和响应速度。</li>
 <li><strong>选择合适的GC策略</strong>：根据应用的实际情况选择合适的GC策略，如吞吐量优先、停顿时间优先等。不同的GC策略适用于不同的应用场景，合理选择可以显著提升应用的性能。</li>
 <li><strong>监控和调优GC</strong>：通过监控GC的行为和性能数据，及时调整GC参数和策略，以优化GC的性能。这包括调整堆内存大小、设置垃圾回收器的类型和参数等。</li>
</ul>
<h5>4. I/O与网络优化</h5>
<ul>
 <li><strong>减少I/O操作</strong>：I/O操作通常比内存操作慢得多，因此应尽量减少I/O操作的次数和复杂度。例如，可以通过批量读写、使用缓冲区等方式来减少I/O操作的次数。</li>
 <li><strong>使用高效的I/O库</strong>：Java提供了多种I/O库，如NIO（New Input/Output）和NIO.2（也称为AIO，Asynchronous I/O），这些库提供了比传统I/O库更高效的数据传输方式。开发者可以根据需要选择合适的I/O库来优化应用的性能。</li>
 <li><strong>优化网络通信</strong>：对于涉及网络通信的应用，可以通过优化网络协议、减少数据传输量、使用压缩技术等方式来优化网络通信的性能。</li>
</ul>
<h5>5. 性能分析与调优</h5>
<ul>
 <li><strong>使用性能分析工具</strong>：Java提供了多种性能分析工具，如JProfiler、VisualVM等，这些工具可以帮助开发者分析和定位性能瓶颈。通过使用这些工具，开发者可以了解应用的性能表现，并找到优化的方向。</li>
 <li><strong>A/B测试</strong>：在进行性能优化时，可以采用A/B测试的方式来评估不同优化策略的效果。通过对比不同策略下的性能指标，可以选择出最优的优化方案。</li>
 <li><strong>持续监控与优化</strong>：性能优化是一个持续的过程，需要开发者不断监控应用的性能指标，并根据实际情况进行调优。只有保持对性能的持续关注和优化，才能确保应用始终保持最佳的性能表现。</li>
</ul>
<h4>四、结论</h4>
<p>Java新版本的不断推出为开发者提供了更多性能优化的工具和手段。通过合理利用Java新版本中的新特性和优化措施，以及采用适当的性能优化策略，开发者可以显著提升Java应用的性能。然而，需要注意的是，性能优化并非一蹴而就的过程，而是需要持续关注和努力的过程。只有不断学习和实践，才能掌握更多的性能优化技巧和方法，为开发高性能的Java应用奠定坚实的基础。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140885309</p>]]></description><guid isPermaLink="false">/archives/78db1059-42d0-466b-aa12-39e3eb835bd3</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 03:17:42 GMT</pubDate></item><item><title><![CDATA[Spring Boot]]></title><link>https://wiki.dgfygk.com/archives/7cbf4fb3-71e8-44b0-94f2-f3cdf4233144</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot&amp;url=/archives/7cbf4fb3-71e8-44b0-94f2-f3cdf4233144" width="1" height="1" alt="" style="opacity:0;">
<p>Spring Boot 作为一个基于 Spring Framework 的快速开发框架，广泛应用于现代微服务架构中。在 Spring Boot 应用中，循环依赖（Circular Dependency）是一个常见的问题，它指的是两个或多个 bean 相互依赖，形成一个闭环。Spring 框架在默认情况下能够处理单例（Singleton）作用域下的构造器注入（Constructor Injection）之外的循环依赖，这主要归功于其三级缓存机制。然而，理解循环依赖的解决机制以及如何在 Spring Boot 中有效避免或处理循环依赖，对于开发高性能、稳定的微服务应用至关重要。</p>
<h4>一、Spring 的循环依赖处理机制</h4>
<h5>1.1 Spring 容器中的 Bean 生命周期</h5>
<p>在深入探讨循环依赖之前，了解 Spring 容器中的 Bean 生命周期是必要的。Spring 容器管理 Bean 的生命周期，包括 Bean 的实例化、属性赋值、初始化以及销毁等阶段。Spring 提供了多种方式来配置 Bean 的生命周期行为，如使用 <code>@PostConstruct</code> 和 <code>@PreDestroy</code> 注解来定义初始化和销毁方法。</p>
<h5>1.2 循环依赖的场景</h5>
<p>循环依赖通常发生在以下几种情况：</p>
<ul>
 <li><strong>构造器注入</strong>：如果两个或多个 Bean 通过构造器相互注入，Spring 将无法解析循环依赖，因为每个 Bean 的实例化都需要另一个 Bean 已经完全实例化，这构成了一个无解的循环。</li>
 <li><strong>Setter 注入</strong>：在 Setter 注入的情况下，Spring 可以通过其三级缓存机制解决循环依赖问题。</li>
 <li><strong>字段注入</strong>：虽然字段注入（Field Injection）在技术上是可行的，但它并不被推荐用于生产环境，因为它违反了依赖注入的原则（即显式依赖），并且可能导致循环依赖问题难以调试。</li>
</ul>
<h5>1.3 Spring 的三级缓存机制</h5>
<p>Spring 通过三级缓存机制来解决 Setter 注入情况下的循环依赖问题：</p>
<ol>
 <li><strong>一级缓存（SingletonObjects）</strong>：存储完全初始化好的 Bean 实例，用于直接返回 Bean。</li>
 <li><strong>二级缓存（EarlySingletonObjects）</strong>：存储早期暴露的 Bean 实例，这些实例已经完成了实例化，但尚未完成属性填充和初始化。主要用于解决循环依赖问题。</li>
 <li><strong>三级缓存（SingletonFactories）</strong>：存储可以生成早期暴露 Bean 实例的工厂对象。当存在循环依赖时，Spring 会通过这个工厂来生成早期暴露的 Bean 实例。</li>
</ol>
<h4>二、Spring Boot 中循环依赖的处理</h4>
<p>在 Spring Boot 中，由于它建立在 Spring Framework 之上，因此循环依赖的处理机制与 Spring 框架相同。然而，Spring Boot 通过其自动配置和约定优于配置的原则，使得开发者更容易陷入循环依赖的陷阱。以下是一些在 Spring Boot 中处理循环依赖的策略：</p>
<h5>2.1 使用 Setter 注入代替构造器注入</h5>
<p>如前所述，构造器注入无法解决循环依赖问题。因此，在可能的情况下，使用 Setter 注入或字段注入（尽管不推荐用于生产环境）来避免循环依赖。然而，最佳实践是优先使用 Setter 注入，因为它提供了更高的灵活性和更好的测试性。</p>
<h5>2.2 重新设计组件关系</h5>
<p>如果应用中存在循环依赖，这通常意味着组件之间的设计可能存在问题。考虑重新设计组件之间的关系，使用接口、设计模式（如观察者模式、中介者模式等）来解耦组件之间的依赖。</p>
<h5>2.3 使用 <code>@Lazy</code> 注解</h5>
<p>在 Spring Boot 中，可以通过 <code>@Lazy</code> 注解来延迟 Bean 的初始化。当使用 <code>@Lazy</code> 注解时，Spring 容器将不会立即实例化被注解的 Bean，而是返回一个代理对象。这个代理对象在被实际使用时才会触发 Bean 的实例化。通过这种方式，可以在一定程度上解决循环依赖问题。但是，需要注意的是，过度使用 <code>@Lazy</code> 注解可能会导致应用启动时间变长，并且可能会隐藏一些潜在的问题。</p>
<h5>2.4 启用/禁用循环依赖检测</h5>
<p>虽然 Spring 框架默认支持处理 Setter 注入的循环依赖，但开发者可以通过配置来启用或禁用循环依赖的检测。然而，在 Spring Boot 中，通常不建议直接修改这一行为，因为这会改变 Spring 框架的默认行为，并可能引入难以预料的问题。</p>
<h4>三、循环依赖的调试和诊断</h4>
<p>当在 Spring Boot 应用中遇到循环依赖问题时，如何有效地进行调试和诊断是一个重要的问题。以下是一些有用的技巧：</p>
<ol>
 <li><strong>查看异常堆栈</strong>：Spring 在遇到无法解决的循环依赖时会抛出异常。通过查看异常堆栈信息，可以定位到导致循环依赖的 Bean。</li>
 <li><strong>启用调试日志</strong> ：在 <code>application.properties</code> 或 <code>application.yml</code> 文件中配置日志级别为 DEBUG，可以获取更多关于 Spring 容器行为的详细信息，包括 Bean 的创建和注入过程。</li>
 <li><strong>使用 IDE 的依赖分析工具</strong>：许多集成开发环境（IDE）提供了依赖分析工具，可以帮助开发者直观地查看项目中的依赖关系，从而更容易地发现潜在的循环依赖。</li>
</ol>
<h4>四、结论与最佳实践</h4>
<p>在 Spring Boot 应用中处理循环依赖是一个需要细致考虑的问题。虽然 Spring 框架提供了强大的机制来支持 Setter 注入的循环依赖解决，但构造器注入的循环依赖仍然是一个需要避免的陷阱。通过理解 Spring 的 Bean 生命周期和三级缓存机制，我们可以更好地理解循环依赖的成因和解决方案。</p>
<h5>4.1 结论</h5>
<ol>
 <li>
  <p><strong>循环依赖的成因</strong>：循环依赖通常是由于组件之间设计不当或过度耦合导致的。在 Spring Boot 应用中，这可能是由于不恰当的依赖注入方式（如构造器注入）或组件间复杂的依赖关系引起的。</p>
 </li>
 <li>
  <p><strong>解决方案</strong>：</p>
 </li>
 <li>
  <p><strong>使用 Setter 注入</strong>：在可能的情况下，优先使用 Setter 注入来避免循环依赖。</p>
 </li>
 <li>
  <p><strong>重新设计组件</strong>：考虑使用设计模式（如观察者模式、中介者模式等）来解耦组件之间的依赖，从而消除循环依赖。</p>
 </li>
 <li>
  <p><strong>使用 <code>@Lazy</code> 注解</strong> ：在特定情况下，可以使用 <code>@Lazy</code> 注解来延迟 Bean 的初始化，但这应谨慎使用以避免潜在的性能问题。</p>
 </li>
 <li>
  <p><strong>调试和诊断</strong>：利用异常堆栈、调试日志和 IDE 的依赖分析工具来定位和解决循环依赖问题。</p>
 </li>
 <li>
  <p><strong>最佳实践</strong>：</p>
 </li>
 <li>
  <p><strong>避免在构造器中注入依赖</strong>：尽可能使用 Setter 注入或字段注入（尽管不推荐用于生产环境），以避免构造器注入带来的循环依赖问题。</p>
 </li>
 <li>
  <p><strong>保持组件的独立性</strong>：设计时应尽量保持组件的独立性和低耦合性，以减少依赖关系的复杂性。</p>
 </li>
 <li>
  <p><strong>编写单元测试</strong>：编写全面的单元测试来验证组件的行为和依赖关系，有助于在开发早期发现并解决循环依赖问题。</p>
 </li>
</ol>
<h5>4.2 未来的发展趋势</h5>
<p>随着 Spring Boot 和 Spring Framework 的不断发展，我们可以期待在循环依赖处理方面出现更多的改进和优化。例如，Spring 团队可能会继续优化其三级缓存机制，以提高循环依赖处理的效率和稳定性。此外，随着云原生和微服务架构的普及，更多的开发者将关注于如何构建高内聚、低耦合的微服务应用，这将进一步推动对循环依赖问题的深入研究和解决。</p>
<h5>4.3 最后的建议</h5>
<p>在处理 Spring Boot 应用中的循环依赖问题时，重要的是要保持清晰的头脑和耐心。首先，要仔细分析问题的成因和上下文环境；其次，要尝试多种解决方案并评估其优缺点；最后，要关注应用的性能和稳定性，确保所采取的解决方案不会对应用产生负面影响。通过不断地学习和实践，我们可以逐渐掌握处理循环依赖的技巧和方法，为构建高性能、稳定的 Spring Boot 应用打下坚实的基础。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140867791</p>]]></description><guid isPermaLink="false">/archives/7cbf4fb3-71e8-44b0-94f2-f3cdf4233144</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 03:17:28 GMT</pubDate></item><item><title><![CDATA[Hadoop开源的分布式计算框架]]></title><link>https://wiki.dgfygk.com/archives/9297c731-d6a5-4bfa-b034-f1ed3f340e15</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Hadoop%E5%BC%80%E6%BA%90%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E8%AE%A1%E7%AE%97%E6%A1%86%E6%9E%B6&amp;url=/archives/9297c731-d6a5-4bfa-b034-f1ed3f340e15" width="1" height="1" alt="" style="opacity:0;">
<p>在大数据处理的广阔领域中，存在多种框架和技术，它们各自具有独特的特点和优势，适用于不同的数据处理场景和需求。以下是几种常见的大数据处理框架的详细介绍，包括Hadoop、Spark、Flink等。</p>
<h4>一、Hadoop</h4>
<h5>1. 概述</h5>
<p>Hadoop是一个开源的分布式计算框架，主要用于处理大规模数据集的存储和分析。它最初由Yahoo的工程师Doug Cutting和Mike Cafarella在2005年合作开发，后来被贡献给了Apache基金会，成为Apache的开源项目。Hadoop是一个由多个组件组成的生态系统，包括Hadoop分布式文件系统（HDFS）、MapReduce编程模型、YARN（Yet Another Resource Negotiator）资源管理器等。</p>
<h5>2. 主要组件</h5>
<ul>
 <li><strong>HDFS（Hadoop Distributed File System）</strong>：HDFS是Hadoop的分布式文件系统，用于存储大规模数据集。它将数据分成多个块，并存储在不同的节点上，提供高容错性和高吞吐量。HDFS采用主从架构，由一个NameNode作为主节点，管理文件系统的命名空间和客户端对文件的访问，而DataNode作为从节点，负责存储实际的数据块。</li>
 <li><strong>MapReduce</strong>：MapReduce是Hadoop的计算模型，用于处理大规模数据集。它将任务分成Map和Reduce两个阶段，通过并行计算来实现高效的数据处理。Map阶段对输入数据进行分割和映射处理，将数据转化为键值对的形式；Reduce阶段对Map的结果进行合并和归约，生成最终的结果。</li>
 <li><strong>YARN</strong>：YARN是Hadoop的资源管理和作业调度框架，用于管理集群资源和调度作业。它将资源管理和作业控制功能分开，分别由ResourceManager和ApplicationMaster实现。ResourceManager负责所有应用程序的资源分配，而ApplicationMaster仅负责管理一个应用程序。</li>
</ul>
<h5>3. 特点与优势</h5>
<ul>
 <li><strong>可靠性</strong>：Hadoop采用分布式存储和备份机制，可以自动处理节点故障，保证数据的可靠性。</li>
 <li><strong>可扩展性</strong>：Hadoop可以横向扩展，通过添加更多的节点来增加计算和存储能力。</li>
 <li><strong>高性能</strong>：Hadoop采用MapReduce计算模型，可以并行处理大规模数据集，提高计算效率。</li>
 <li><strong>灵活性</strong>：Hadoop支持多种数据类型和数据格式，适用于各种不同的数据处理需求。</li>
</ul>
<h5>4. 应用场景</h5>
<p>Hadoop适用于处理PB级别以上的大规模数据集，广泛应用于电商、金融、物流、医疗、社交网络等领域。例如，在电商领域，Hadoop可以用于分析用户行为数据，优化商品推荐算法；在金融领域，Hadoop可以用于风险控制和欺诈检测，提高金融交易的安全性。</p>
<h4>二、Spark</h4>
<h5>1. 概述</h5>
<p>Apache Spark是一个快速、通用的集群计算系统，用于处理和分析大规模数据。Spark最初由美国加州大学伯克利分校的AMP（Algorithm, Machines and People）实验室于2009年开发，是一个基于内存计算的大数据并行计算框架。Spark提供了高级API，可以支持使用Java、Scala、Python和R等多种语言。</p>
<h5>2. 主要特性</h5>
<ul>
 <li><strong>快速性</strong>：Spark使用内存计算，因此比Hadoop MapReduce快100倍左右。它适用于迭代计算、交互式查询和流处理等场景。</li>
 <li><strong>通用性</strong>：Spark提供了丰富的内置库，包括SQL、流处理、机器学习、图处理等，可以满足不同领域的需求。</li>
 <li><strong>容错性</strong>：Spark通过弹性分布式数据集（RDD）提供了容错性，可以在节点发生故障时恢复计算过程。</li>
 <li><strong>易用性</strong>：Spark可以轻松集成到Hadoop生态系统中，支持HDFS、HBase等数据存储系统。</li>
</ul>
<h5>3. 工作原理</h5>
<p>Spark的工作原理主要包括以下几个关键点：</p>
<ul>
 <li><strong>集群模式</strong>：Spark可以在独立的集群模式、Mesos和YARN上运行。它通过Master节点管理Worker节点上的任务执行。</li>
 <li><strong>弹性分布式数据集（RDD）</strong>：RDD是Spark中的核心概念，它代表一个可以跨集群节点并行操作的数据集合。</li>
 <li><strong>转换与动作</strong>：Spark提供了丰富的转换操作（如map、filter、reduce等）和动作操作（如collect、count、saveAsTextFile等）来处理数据集。</li>
 <li><strong>执行流程</strong>：用户编写的Spark应用程序首先被编译为有向无环图（DAG），然后根据DAG提交作业进行执行。Spark通过将数据存储在内存中来加速数据处理，同时也支持数据持久化到磁盘。</li>
</ul>
<h5>4. 应用场景</h5>
<p>Spark适用于需要快速迭代计算和实时处理的场景，如实时数据分析、机器学习、图计算等。在金融领域，Spark可以用于实时交易分析和欺诈检测；在物联网领域，Spark可以用于处理传感器数据，实现实时监控和预测分析。</p>
<h4>三、Flink</h4>
<h5>1. 概述</h5>
<p>Apache Flink是一款开源的流处理和批处理框架，它被设计成用于处理大规模数据流。Flink具有高吞吐量、低延迟和高度容错性的特点，能够处理有界和无界的数据集。</p>
<h5>2. 主要特性</h5>
<ul>
 <li>
  <p><strong>高性能</strong>：Flink在处理大规模实时数据时具有很高的吞吐量和低延迟，能够实现毫秒级的响应速度。</p>
 </li>
 <li>
  <p><strong>容错性</strong>：Flink能够自动处理故障节点和数据丢失，确保数据处理的准确性和一致性。它采用了Checkpoint机制来保存程序的状态，以便在故障发生时能够恢复到最近一次成功的状态。</p>
 </li>
 <li>
  <p><strong>统一编程模型</strong>：Flink提供了统一的编程模型，即DataStream API，既可以用于处理有界数据集（批处理），也可以用于处理无界数据流（流处理）。这种统一的编程模型简化了复杂数据处理应用的开发。</p>
 </li>
 <li>
  <p><strong>丰富的内置函数和库</strong>：Flink提供了丰富的内置函数和库，包括窗口函数、时间函数、连接函数等，以及支持多种数据源和数据存储系统的连接器，如Kafka、HDFS、Elasticsearch等。</p>
 </li>
</ul>
<h5>3. 工作原理</h5>
<p>Flink的核心是数据流处理引擎，它采用了事件时间（Event Time）来处理数据流中的时间属性。事件时间是指数据产生的时间，而非数据被处理的时间。Flink通过时间戳和水印（Watermarks）来跟踪事件时间，确保数据能够按照正确的顺序和时间窗口进行处理。</p>
<p>Flink的执行流程大致可以分为以下几个步骤：</p>
<ol>
 <li>
  <p><strong>数据流定义</strong>：用户通过DataStream API定义数据流处理逻辑，包括数据源、转换操作和数据汇。</p>
 </li>
 <li>
  <p><strong>任务提交</strong>：将定义好的数据流处理逻辑提交给Flink集群执行。Flink会将任务编译成执行计划，并分配到不同的节点上执行。</p>
 </li>
 <li>
  <p><strong>任务执行</strong>：在任务执行过程中，Flink会根据数据流的特点和集群的资源情况，动态地调整任务的并行度和执行策略。同时，Flink会实时地监控任务的运行状态，并在检测到故障时自动进行恢复。</p>
 </li>
 <li>
  <p><strong>结果输出</strong>：处理后的数据会被发送到指定的数据汇中，如保存到HDFS、写入数据库或发送到其他数据流处理系统等。</p>
 </li>
</ol>
<h5>4. 应用场景</h5>
<p>Flink因其高性能、低延迟和高度容错性的特点，在实时数据分析、实时推荐系统、实时监控和预警系统等领域具有广泛的应用。例如，在电商领域，Flink可以用于实时分析用户行为数据，为用户提供个性化的商品推荐；在金融领域，Flink可以用于实时监控交易数据，及时发现并处理潜在的欺诈行为。</p>
<h4>四、对比与总结</h4>
<p>Hadoop、Spark和Flink作为大数据处理领域的三大主流框架，各自具有独特的特点和优势。Hadoop以其可靠性、可扩展性和灵活性成为处理大规模静态数据集的首选框架；Spark通过内存计算和丰富的内置库，提供了快速且通用的数据处理能力，特别适用于需要迭代计算和实时处理的场景；而Flink则以其高性能、低延迟和高度容错性的特点，在实时数据流处理领域展现出强大的竞争力。</p>
<p>在实际应用中，选择哪个框架取决于具体的数据处理需求、数据规模、实时性要求以及资源环境等因素。例如，在处理PB级别以上的大规模静态数据集时，Hadoop可能是更合适的选择；而在需要快速迭代计算和实时处理的场景中，Spark或Flink则可能更具优势。同时，随着技术的发展和框架的不断演进，这些框架也在不断地相互借鉴和融合，为用户提供更加全面和高效的数据处理解决方案。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140843844</p>]]></description><guid isPermaLink="false">/archives/9297c731-d6a5-4bfa-b034-f1ed3f340e15</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:00:44 GMT</pubDate></item><item><title><![CDATA[AI大模型Prompt提示词工程使用详解]]></title><link>https://wiki.dgfygk.com/archives/8ad08063-c31f-40c0-9200-6c47e72cf775</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AI%E5%A4%A7%E6%A8%A1%E5%9E%8BPrompt%E6%8F%90%E7%A4%BA%E8%AF%8D%E5%B7%A5%E7%A8%8B%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3&amp;url=/archives/8ad08063-c31f-40c0-9200-6c47e72cf775" width="1" height="1" alt="" style="opacity:0;">
<h2>AI大模型Prompt提示词工程使用详解</h2>
<p>在人工智能（AI）的浩瀚宇宙中，大型预训练模型（Large Language Models, LLMs）如GPT系列、BERT等，以其卓越的自然语言处理（NLP）能力，正逐步改变着人类与机器交互的方式。这些模型不仅能够理解和生成人类语言，还能在多种任务上展现出惊人的创造力和适应性。然而，要充分发挥这些大型模型的潜力，就需要一套精细且高效的指令系统来引导它们的行为，这就是Prompt提示词工程（Prompt Engineering）的核心所在。本文将深入探讨AI大模型Prompt提示词工程的使用，从定义、重要性、设计原则、应用案例、优化策略到未来展望，全面解析这一关键技术的各个方面。</p>
<h4>一、Prompt提示词工程的定义与重要性</h4>
<h5>定义</h5>
<p>Prompt提示词工程，简而言之，就是设计并优化用于指导AI模型执行特定任务的文本指令或提示词。这些提示词不仅包含了任务的基本描述，还可能包含上下文信息、期望输出格式、风格指导等，以引导模型生成更加准确、符合预期的输出。</p>
<h5>重要性</h5>
<ol>
 <li><strong>提高任务准确性</strong>：通过精心设计的Prompt，可以显著减少模型对任务的误解，确保模型按照用户的意图执行任务，从而提高任务完成的准确性。</li>
 <li><strong>增强模型灵活性</strong>：Prompt允许用户根据具体需求灵活调整模型的行为，使其能够适应不同的应用场景和任务要求。</li>
 <li><strong>促进模型创新</strong>：通过引导模型进行创造性思考，Prompt可以激发模型生成新颖、独特的输出，为创新应用提供可能。</li>
 <li><strong>优化资源利用</strong>：合理的Prompt设计可以减少模型计算资源的浪费，提高模型运行效率，降低使用成本。</li>
</ol>
<h4>二、Prompt提示词工程的设计原则</h4>
<p>在设计Prompt时，需要遵循一系列原则以确保其有效性和高效性。以下是几个关键的设计原则：</p>
<ol>
 <li><strong>明确性与具体性</strong>：Prompt应清晰、具体地描述任务要求和期望输出，避免模糊或歧义的表达。明确的任务描述有助于模型准确理解并执行任务。</li>
 <li><strong>结构化</strong>：采用逻辑清晰、层次分明的结构组织Prompt，有助于模型更好地理解和处理。例如，可以将Prompt分为任务描述、输入示例、输出要求等部分，使模型能够按照既定的流程执行任务。</li>
 <li><strong>适应性</strong>：根据模型的特性和能力调整Prompt，以确保其能够被模型有效理解和执行。不同的模型可能对Prompt的敏感度和理解程度有所不同，因此需要根据实际情况进行灵活调整。</li>
 <li><strong>简洁性</strong>：在保证明确性和具体性的前提下，尽量使Prompt保持简洁。过长的Prompt可能会增加模型的处理负担，降低运行效率。</li>
 <li><strong>情感与风格</strong>：根据任务需求，可以在Prompt中融入特定的情感色彩和风格要求。这有助于模型生成更加符合用户期望的输出，提高用户满意度。</li>
</ol>
<h4>三、Prompt提示词工程的应用案例</h4>
<p>Prompt提示词工程在多个领域和场景中都有广泛的应用。以下是一些典型的应用案例：</p>
<h5>1. 文本生成</h5>
<p>在文本生成领域，Prompt提示词工程被广泛应用于小说创作、诗歌生成、新闻报道撰写等场景。通过设计不同的Prompt，可以引导模型生成不同风格、不同主题的文本内容。例如，在小说创作中，可以设计包含角色设定、情节梗概、背景设定等信息的Prompt，以引导模型生成符合小说要求的文本内容。</p>
<h5>2. 图像创作</h5>
<p>随着多模态技术的发展，Prompt提示词工程也被应用于图像创作领域。通过描述图像的内容、风格、色彩等要素，可以引导模型生成符合要求的图像作品。例如，在绘画软件中，用户可以通过输入包含场景描述、物体形状、色彩搭配等信息的Prompt，来引导模型绘制出相应的图像作品。</p>
<h5>3. 代码编写</h5>
<p>在编程领域，Prompt提示词工程也展现出巨大的潜力。通过设计包含函数定义、输入输出示例、算法逻辑等信息的Prompt，可以引导模型辅助编程人员编写代码片段、解决编程难题。这不仅可以提高编程效率，还可以降低编程门槛，使更多人能够参与到编程活动中来。</p>
<h5>4. 知识问答</h5>
<p>在知识问答领域，Prompt提示词工程被用于构建智能问答系统。通过设计包含问题描述、上下文信息、期望答案类型等要素的Prompt，可以引导模型准确理解用户问题并给出合适的答案。这种问答系统可以应用于客服、教育、医疗等多个领域，为用户提供便捷的信息查询服务。</p>
<h4>四、Prompt提示词工程的优化策略</h4>
<p>为了进一步提高Prompt提示词工程的效果和效率，需要采取一系列优化策略。以下是一些常见的优化策略：</p>
<ol>
 <li>
  <p><strong>迭代调整</strong>：根据模型的初步输出和反馈结果，对Prompt进行迭代调整。通过不断试错和优化，可以逐步找到最适合当前任务和模型的Prompt设计方案。</p>
 </li>
 <li>
  <p><strong>多样化尝试</strong>：尝试使用不同的Prompt格式、语气和风格来引导模型。通过对比不同Prompt的效果，可以找到能够激发模型最佳性能的那一类Prompt。这种多样化尝试有助于发现模型对特定语言特征的敏感性和偏好，从而定制出更加有效的指令。</p>
 </li>
 <li>
  <p><strong>融入领域知识</strong>：针对特定领域的任务，将相关的领域知识融入Prompt设计中。这包括专业术语、行业规范、常见情境等，以确保Prompt能够准确反映领域内的实际情况和需求。通过融入领域知识，可以提高模型在特定领域任务上的表现力和准确性。</p>
 </li>
 <li>
  <p><strong>利用模板与示例</strong>：创建和使用Prompt模板以及输入/输出示例是一种有效的优化策略。模板为Prompt提供了一个基本的框架，有助于保持结构的一致性和逻辑的清晰性。而示例则提供了具体的任务执行案例，帮助模型更好地理解任务要求和期望输出。通过结合模板和示例，可以显著提高Prompt的引导效果和模型的执行效率。</p>
 </li>
 <li>
  <p><strong>性能评估与反馈循环</strong>：建立一个有效的性能评估机制，对Prompt的效果进行定期评估和反馈。这包括收集用户反馈、分析模型输出质量、比较不同Prompt方案的效果等。通过评估结果，可以及时发现Prompt设计中的问题和不足，并采取相应的优化措施。同时，将评估结果反馈到Prompt设计过程中，形成一个闭环的优化循环，不断提高Prompt的质量和效果。</p>
 </li>
 <li>
  <p><strong>结合上下文信息</strong>：在设计Prompt时，充分利用上下文信息来增强模型的理解能力。上下文信息可以包括历史对话记录、用户行为数据、外部知识库等。通过将这些信息融入Prompt中，可以为模型提供更加丰富的输入和背景知识，有助于模型更准确地理解任务要求和生成符合期望的输出。</p>
 </li>
 <li>
  <p><strong>探索模型特性</strong>：不同的大型预训练模型具有各自独特的特性和优势。因此，在设计Prompt时，需要充分了解并探索模型的特性，以便更好地利用其优势来执行任务。例如，某些模型可能擅长处理长文本输入，而另一些模型则可能更擅长处理结构化数据。通过针对模型特性进行Prompt设计，可以进一步提高模型在特定任务上的表现力和效率。</p>
 </li>
</ol>
<h4>五、未来展望</h4>
<p>随着AI技术的不断发展和大型预训练模型的持续迭代升级，Prompt提示词工程将在更多领域和场景中发挥重要作用。未来，我们可以期待以下几个方面的发展趋势：</p>
<ol>
 <li>
  <p><strong>更加智能化和个性化的Prompt设计</strong>：随着自然语言处理技术的不断进步，Prompt设计将更加智能化和个性化。模型将能够更准确地理解用户的意图和需求，并自动生成符合用户期望的Prompt。这将大大提高用户与模型之间的交互效率和满意度。</p>
 </li>
 <li>
  <p><strong>多模态Prompt的融合</strong>：随着多模态技术的发展，未来的Prompt将不再局限于文本形式，而是将融合图像、音频、视频等多种模态的信息。这将为模型提供更加丰富的输入和背景知识，有助于模型更全面地理解任务要求和生成更加生动、真实的输出。</p>
 </li>
 <li>
  <p><strong>Prompt生成与优化的自动化工具</strong>：为了提高Prompt设计的效率和准确性，未来将出现更多自动化的Prompt生成与优化工具。这些工具将能够根据任务需求和模型特性自动生成符合要求的Prompt，并通过优化算法不断迭代改进其效果。这将大大降低Prompt设计的门槛和成本，使更多人能够利用Prompt提示词工程来指导AI模型完成任务。</p>
 </li>
 <li>
  <p><strong>跨语言与跨文化Prompt的研究</strong>：随着全球化的发展和国际交流的日益频繁，跨语言和跨文化的Prompt研究将成为未来的一个重要方向。未来的Prompt设计将需要考虑不同语言和文化的差异性和相似性，以确保模型能够在全球范围内实现有效的交互和合作。</p>
 </li>
</ol>
<p>总之，AI大模型Prompt提示词工程是一项具有广泛应用前景和重要意义的技术。通过深入研究和不断优化Prompt设计策略和方法，我们可以更好地发挥大型预训练模型的潜力，推动AI技术在更多领域和场景中的应用和发展。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140768761</p>]]></description><guid isPermaLink="false">/archives/8ad08063-c31f-40c0-9200-6c47e72cf775</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 03:00:32 GMT</pubDate></item><item><title><![CDATA[Spring Data JPA的核心概念]]></title><link>https://wiki.dgfygk.com/archives/9cae9733-20af-4b70-82d0-bdd9242bdb9d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Data%20JPA%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5&amp;url=/archives/9cae9733-20af-4b70-82d0-bdd9242bdb9d" width="1" height="1" alt="" style="opacity:0;">
<p>概述Spring Data JPA的核心概念、优势、使用方法以及通过示例展示如何用它来简化Java持久层开发。这将为你提供一个基础框架，你可以根据这个框架进一步深入学习和实践。</p>
<h4>一、引言</h4>
<p>在Java企业级应用中，数据持久化是一个至关重要的部分。传统上，Java开发者需要使用JDBC（Java Database Connectivity）来直接与数据库交互，但这通常涉及到大量的样板代码和复杂的错误处理。Spring Data JPA通过提供一套丰富的抽象和接口，极大地简化了数据访问层的开发。</p>
<h4>二、Spring Data JPA简介</h4>
<p>Spring Data JPA是Spring Data项目的一部分，旨在简化基于JPA（Java Persistence API）的数据访问层开发。它允许开发者通过定义接口来访问数据库，而无需实现这些接口。Spring Data JPA在运行时会自动为这些接口创建实现，从而减少了样板代码的数量，并提高了开发效率。</p>
<h4>三、核心概念和优势</h4>
<h5>1. 仓库接口</h5>
<p>Spring Data JPA的核心是仓库接口（Repository Interface）。开发者只需定义一个接口，继承自<code>JpaRepository</code>（或<code>CrudRepository</code>、<code>PagingAndSortingRepository</code>等），就可以获得一系列用于数据访问的方法，如<code>save()</code>, <code>findAll()</code>, <code>findOne()</code>, <code>delete()</code>等。</p>
<h5>2. 方法命名规则</h5>
<p>Spring Data JPA支持通过方法名来解析查询，这称为方法命名规则（Method Name Resolution）。例如，<code>findByFirstNameAndLastName</code>方法会被解析为根据<code>firstName</code>和<code>lastName</code>字段来查询数据。这种方式避免了编写复杂的JPQL（Java Persistence Query Language）或SQL查询语句。</p>
<h5>3. 查询方法</h5>
<p>除了方法命名规则外，Spring Data JPA还支持通过<code>@Query</code>注解来定义自定义的JPQL或原生SQL查询。这使得开发者可以编写更复杂的查询语句，同时保持代码的简洁性。</p>
<h5>4. 分页和排序</h5>
<p>Spring Data JPA提供了分页和排序的内置支持。通过<code>Pageable</code>和<code>Sort</code>接口，开发者可以轻松地实现分页和排序功能，而无需编写额外的代码。</p>
<h5>5. 自定义仓库</h5>
<p>如果内置的仓库方法不满足需求，开发者可以通过在仓库接口中添加自定义方法，并在接口所在的包或子包中提供一个实现类来自定义这些方法的行为。</p>
<h5>6. 优势总结</h5>
<ul>
 <li><strong>减少样板代码</strong>：通过定义接口而不是实现类来访问数据，减少了大量的样板代码。</li>
 <li><strong>提高开发效率</strong>：提供了丰富的内置方法和查询支持，使得开发者可以更快地编写数据访问逻辑。</li>
 <li><strong>易于测试</strong>：由于数据访问逻辑被封装在接口中，因此更容易进行单元测试。</li>
 <li><strong>灵活性</strong>：支持自定义查询和自定义仓库，满足复杂的业务需求。</li>
</ul>
<h4>四、使用Spring Data JPA</h4>
<h5>1. 添加依赖</h5>
<p>首先，你需要在你的Spring Boot项目中添加Spring Data JPA的依赖。如果你使用Maven，可以在<code>pom.xml</code>中添加如下依赖：</p>
<pre><code class="prism language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.h2database&lt;/groupId&gt;
    &lt;artifactId&gt;h2&lt;/artifactId&gt;
    &lt;scope&gt;runtime&lt;/scope&gt;
&lt;/dependency&gt;
</code></pre>
<p>注意：这里还添加了一个H2数据库的依赖作为示例，但你可以根据需要选择其他数据库。</p>
<h5>2. 配置数据库</h5>
<p>在<code>application.properties</code>或<code>application.yml</code>文件中配置数据库连接信息：</p>
<pre><code class="prism language-properties">spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
</code></pre>
<h5>3. 定义实体类</h5>
<p>使用JPA注解定义实体类，映射数据库表：</p>
<pre><code class="prism language-java">import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {


    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    // 省略getter和setter方法
}
</code></pre>
<h5>4. 定义仓库接口</h5>
<p>继承<code>JpaRepository</code>接口来定义仓库接口：</p>
<pre><code class="prism language-java">import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository&lt;User, Long&gt; {

}

#### 5. 使用仓库接口

在你的服务层或控制器中，你可以注入`UserRepository`接口，并使用它来进行数据访问。
</code></pre>
<p>import org.springframework.beans.factory.annotation.Autowired;
 <br>
 import org.springframework.stereotype.Service;</p>
<p>import java.util.List;</p>
<p>@Service
 <br>
 public class UserService {</p>
<pre><code>@Autowired
private UserRepository userRepository;

public List&lt;User&gt; findAllUsers() {

    return userRepository.findAll();
}

public User findUserById(Long id) {

    return userRepository.findById(id).orElse(null);
}

public User saveUser(User user) {

    return userRepository.save(user);
}

// 其他业务逻辑...
</code></pre>
<p>}</p>
<pre><code>##### 6. 自定义查询

如果你需要执行更复杂的查询，可以使用`@Query`注解来定义自定义查询。
</code></pre>
<p>public interface UserRepository extends JpaRepository<user, long> {</user,></p>
<pre><code>@Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.lastName = :lastName")
List&lt;User&gt; findByFirstNameAndLastName(@Param("firstName") String firstName, @Param("lastName") String lastName);

// 也可以使用JPQL的命名查询或者原生SQL查询
</code></pre>
<p>}</p>
<pre><code>##### 7. 分页和排序

Spring Data JPA支持通过`Pageable`和`Sort`接口进行分页和排序。
</code></pre>
<p>import org.springframework.data.domain.Page;
 <br>
 import org.springframework.data.domain.PageRequest;
 <br>
 import org.springframework.data.domain.Sort;</p>
<p>public List<user> findUsersByPage(int page, int size, String sortBy) {</user></p>
<pre><code>PageRequest pageRequest = PageRequest.of(page, size, Sort.by(sortBy));
Page&lt;User&gt; userPage = userRepository.findAll(pageRequest);
return userPage.getContent();
</code></pre>
<p>}</p>
<pre><code>注意：在上面的例子中，我使用了`List`作为返回类型，但实际上你应该使用`Page&lt;User&gt;`来接收分页结果，并处理分页信息（如总页数、当前页数等）。

##### 8. 自定义仓库

如果你需要实现一些不在`JpaRepository`中的方法，可以创建自定义仓库。

首先，定义仓库接口：
</code></pre>
<p>public interface CustomUserRepository {</p>
<pre><code>// 自定义方法声明
User findUserByCustomLogic(String someCriteria);
</code></pre>
<p>}</p>
<pre><code>然后，实现这个接口，并注入`JpaRepository`的实现（如果需要）：
</code></pre>
<p>import org.springframework.beans.factory.annotation.Autowired;
 <br>
 import org.springframework.stereotype.Repository;</p>
<p>@Repository
 <br>
 public class CustomUserRepositoryImpl implements CustomUserRepository {</p>
<pre><code>@Autowired
private UserRepository userRepository; // 假设你还需要访问JpaRepository的方法

@Override
public User findUserByCustomLogic(String someCriteria) {

    // 实现自定义逻辑
    // ...
    return null; // 示例返回null，实际应返回查询结果
}
</code></pre>
<p>}</p>
<pre><code>注意：在大多数情况下，你不需要直接实现自定义仓库，因为Spring Data JPA已经为你处理了大部分工作。但如果你需要访问底层JPA API或执行一些复杂的逻辑，那么自定义仓库是一个很好的选择。

##### 9. 整合和测试

最后，确保你的Spring Boot应用已经正确配置了数据库连接和JPA设置，并编写适当的单元测试和集成测试来验证你的数据访问层是否按预期工作。

#### 五、结论

Spring Data JPA通过提供一套丰富的抽象和接口，极大地简化了Java持久层开发。通过定义简单的仓库接口，开发者可以轻松地实现复杂的数据访问逻辑，而无需编写大量的样板代码。此外，Spring Data JPA还支持自定义查询、分页和排序等高级功能，为开发者提供了更多的灵活性和控制力。通过学习和使用Spring Data JPA，你可以更高效地开发Java企业级应用，并专注于业务逻辑的实现而不是数据访问层的细节。


原文链接: https://blog.csdn.net/hai40587/article/details/140812962
</code></pre>]]></description><guid isPermaLink="false">/archives/9cae9733-20af-4b70-82d0-bdd9242bdb9d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 02:51:38 GMT</pubDate></item><item><title><![CDATA[Java技术热点问题]]></title><link>https://wiki.dgfygk.com/archives/880a7e2a-35f6-482b-8d1e-3eef571b0747</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E6%8A%80%E6%9C%AF%E7%83%AD%E7%82%B9%E9%97%AE%E9%A2%98&amp;url=/archives/880a7e2a-35f6-482b-8d1e-3eef571b0747" width="1" height="1" alt="" style="opacity:0;">
<p>Java技术热点问题涵盖了多个方面，以下是一些当前较为关注的技术热点：</p>
<h4>1. <strong>Java虚拟机（JVM）优化与性能提升</strong></h4>
<ul>
 <li><strong>JVM性能调优</strong>：随着Java应用的复杂化，对JVM的性能要求也越来越高。如何优化JVM的启动时间、内存使用、垃圾回收（GC）策略等，成为开发者关注的重点。</li>
 <li><strong>JIT编译器优化</strong>：即时编译器（JIT）将Java字节码转换为机器码，其优化策略直接影响程序执行效率。如何提升JIT编译器的优化效果，减少编译时间和提高执行效率，是JVM优化的重要方向。</li>
</ul>
<h4>2. <strong>多线程与并发编程</strong></h4>
<ul>
 <li><strong>线程安全与并发控制</strong>：在多线程环境下，如何保证数据的一致性和线程的安全性，是并发编程的核心问题。Java提供了多种同步机制，如synchronized关键字、Lock接口、原子类等，用于解决并发问题。</li>
 <li><strong>线程池的使用与优化</strong>：线程池是管理多线程的一种有效方式，可以重用线程资源，减少线程创建和销毁的开销。如何合理配置线程池的参数，以及如何根据应用需求进行动态调整，是提升并发性能的关键。</li>
</ul>
<h4>3. <strong>微服务架构与Spring Boot</strong></h4>
<ul>
 <li><strong>微服务架构的普及</strong>：微服务架构通过将应用拆分为多个小型、独立的服务，提高了系统的可扩展性和可维护性。Spring Boot作为构建微服务的优秀框架，简化了配置和部署流程，降低了开发门槛。</li>
 <li><strong>Spring Cloud的集成与应用</strong>：Spring Cloud为微服务架构提供了全面的支持，包括服务发现、配置管理、负载均衡、断路器等功能。如何有效地集成Spring Cloud组件，构建高可用的微服务系统，是当前Java开发者关注的热点。</li>
</ul>
<h4>4. <strong>大数据处理与分布式系统</strong></h4>
<ul>
 <li><strong>Hadoop与Spark的应用</strong>：Java在大数据处理领域具有重要地位，Hadoop和Spark是两大主流的大数据处理框架。如何利用Java进行大规模数据处理和分析，以及如何在分布式系统中实现高效的数据管理和存储，是当前Java技术的重要研究方向。</li>
 <li><strong>Kafka与消息队列</strong>：消息队列是分布式系统中常用的组件，用于实现系统间的解耦和异步通信。Kafka作为高性能的消息队列系统，广泛应用于大数据流处理和实时分析领域。如何有效地使用Kafka等消息队列系统，提升系统的处理能力和响应速度，是Java开发者需要掌握的技能。</li>
</ul>
<h4>5. <strong>云计算与容器化技术</strong></h4>
<ul>
 <li><strong>Docker与Kubernetes的应用</strong>：随着云计算的发展，容器化技术逐渐普及。Docker作为轻量级的容器引擎，为应用的打包、分发和部署提供了便利。Kubernetes则是容器编排的利器，用于管理容器化应用的部署、扩展和运维。如何结合Docker和Kubernetes构建云原生应用，是当前Java开发者需要关注的技术趋势。</li>
</ul>
<h4>6. <strong>安全性与加密技术</strong></h4>
<ul>
 <li><strong>Java安全框架的应用</strong>：Java提供了丰富的安全框架和API，如Java加密扩展（JCE）、Java认证和授权服务（JAAS）等，用于保护应用的安全。如何有效地利用这些安全框架，实现身份认证、数据加密和访问控制等功能，是Java应用安全性的重要保障。</li>
 <li><strong>零信任网络架构</strong>：随着网络威胁的日益严重，零信任网络架构逐渐成为新的安全标准。如何在Java应用中实现零信任安全模型，确保应用在任何网络环境下都能保持安全状态，是当前Java安全领域的研究热点。</li>
</ul>
<h4>7. <strong>Java新特性与未来趋势</strong></h4>
<ul>
 <li><strong>Java新版本的发布</strong>：Oracle和OpenJDK团队不断推出Java的新版本，引入了许多新特性和改进。如何及时了解并掌握这些新特性，以及如何将其应用于实际开发中，是Java开发者需要持续关注的问题。</li>
 <li><strong>函数式编程与Lambda表达式</strong>：Java 8引入了Lambda表达式和函数式编程接口（如Stream API），使得Java在函数式编程方面有了显著提升。如何有效地利用这些新特性编写更加简洁、高效的代码，是当前Java编程的重要趋势。</li>
</ul>
<p>综上所述，Java技术热点问题涉及JVM优化、多线程与并发编程、微服务架构、大数据处理、云计算与容器化技术、安全性与加密技术以及Java新特性与未来趋势等多个方面。随着技术的不断发展，这些热点问题也将不断演变和更新。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140813319</p>]]></description><guid isPermaLink="false">/archives/880a7e2a-35f6-482b-8d1e-3eef571b0747</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 02:51:22 GMT</pubDate></item><item><title><![CDATA[SpringMVC请求流转路径剖析]]></title><link>https://wiki.dgfygk.com/archives/e105cab1-ccbe-45ac-8674-4189a8a3e5e1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringMVC%E8%AF%B7%E6%B1%82%E6%B5%81%E8%BD%AC%E8%B7%AF%E5%BE%84%E5%89%96%E6%9E%90&amp;url=/archives/e105cab1-ccbe-45ac-8674-4189a8a3e5e1" width="1" height="1" alt="" style="opacity:0;">
<h4>SpringMVC请求流转路径剖析</h4>
<p>Spring MVC 是 Spring 框架中用于构建 Web 应用程序的一个模块，它基于 MVC（Model-View-Controller）设计模式。Spring MVC 通过其高度可配置性和灵活性，成为了 Java Web 开发中广泛使用的框架之一。理解 Spring MVC 的请求执行流程对于深入掌握其工作原理、优化性能以及解决开发中遇到的问题至关重要。</p>
<h5>一、Spring MVC 概述</h5>
<p>在深入探讨 Spring MVC 的请求执行流程之前，我们先简要回顾一下 Spring MVC 的基本组件：</p>
<ol>
 <li><strong>DispatcherServlet</strong>：前端控制器，负责接收所有请求并分发到相应的处理器（Controller）。</li>
 <li><strong>HandlerMapping</strong>：映射器，负责根据请求的 URL 找到对应的处理器（Controller）和拦截器（Interceptor）。</li>
 <li><strong>Controller</strong>：处理器，负责处理具体的业务逻辑，并返回 ModelAndView 对象（包含数据和视图名）。</li>
 <li><strong>ModelAndView</strong>：包含了视图名和数据模型，用于将数据和视图进行绑定。</li>
 <li><strong>ViewResolver</strong>：视图解析器，负责解析视图名到具体的视图实现。</li>
 <li><strong>View</strong>：视图，负责将模型数据渲染到页面上，最终展示给用户。</li>
</ol>
<h5>二、请求执行流程详解</h5>
<p>Spring MVC 的请求执行流程大致可以分为以下几个步骤：</p>
<h6>1. 接收请求</h6>
<p>当用户通过浏览器或其他客户端发送 HTTP 请求到服务器时，该请求首先被 Web 容器（如 Tomcat）接收。Web 容器根据配置的 URL 映射（如 web.xml 中的 Servlet 映射），将请求转发给相应的 Servlet，在 Spring MVC 中，这个 Servlet 就是 DispatcherServlet。</p>
<h6>2. DispatcherServlet 查找 Handler</h6>
<p>DispatcherServlet 作为前端控制器，负责接收所有请求。接收到请求后，它会查询 HandlerMapping 以找到处理该请求的 Controller。HandlerMapping 是一个接口，Spring MVC 提供了多种实现，如 SimpleUrlHandlerMapping、BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping（最常用的）等。</p>
<p><strong>RequestMappingHandlerMapping</strong> 通过解析 Controller 类中的 @RequestMapping 注解来找到对应的处理器方法。</p>
<h6>3. 调用 HandlerAdapter</h6>
<p>找到对应的 Controller 后，DispatcherServlet 会查找一个合适的 HandlerAdapter 来执行该 Controller。HandlerAdapter 是一个接口，用于将请求转发给相应的 Controller。Spring MVC 提供了多种 HandlerAdapter 实现，以支持不同类型的 Controller，如 SimpleControllerHandlerAdapter 和 RequestMappingHandlerAdapter（处理 @RequestMapping 注解的 Controller）。</p>
<p><strong>RequestMappingHandlerAdapter</strong> 是处理 @RequestMapping 注解的方法的适配器。</p>
<h6>4. 执行 Controller</h6>
<p>HandlerAdapter 调用 Controller 中的方法来处理请求。Controller 方法执行完毕后，会返回一个 ModelAndView 对象（如果不需要视图，也可以返回其他类型的响应，如 ResponseEntity）。ModelAndView 对象封装了视图名和数据模型。</p>
<h6>5. 视图解析</h6>
<p>DispatcherServlet 将 ModelAndView 对象传递给 ViewResolver，以便解析视图名到具体的视图实现。ViewResolver 是一个接口，Spring MVC 提供了多种实现，如 InternalResourceViewResolver（用于解析 JSP 视图）和 FreeMarkerViewResolver（用于解析 FreeMarker 模板）。</p>
<p><strong>InternalResourceViewResolver</strong> 会将视图名解析为 Web 应用的内部资源路径（如 JSP 页面路径）。</p>
<h6>6. 渲染视图</h6>
<p>解析出具体的视图实现后，DispatcherServlet 会将模型数据传递给视图，并调用视图对象的渲染方法来生成最终的 HTML 响应。视图对象可以使用 JSP、Thymeleaf、FreeMarker 等模板引擎来渲染 HTML 页面。</p>
<h6>7. 响应客户端</h6>
<p>最后，DispatcherServlet 将渲染后的 HTML 响应发送给客户端（浏览器），用户就可以在浏览器中看到最终的页面了。</p>
<h5>三、异常处理</h5>
<p>在请求执行过程中，如果发生异常，Spring MVC 提供了异常处理机制来捕获并处理这些异常。开发者可以通过实现 HandlerExceptionResolver 接口或使用 @ExceptionHandler 注解来定义异常处理方法。</p>
<p><strong>@ExceptionHandler</strong> 注解可以标注在 Controller 类的方法上，用于处理该 Controller 内部发生的特定异常。</p>
<h5>四、拦截器（Interceptor）</h5>
<p>Spring MVC 还支持拦截器，允许开发者在请求处理的前后添加自定义的逻辑。拦截器实现了 HandlerInterceptor 接口，可以在请求处理前（preHandle）、请求处理后（postHandle）以及视图渲染后（afterCompletion）执行特定的操作。</p>
<p>拦截器在 HandlerMapping 找到对应的处理器后、HandlerAdapter 调用处理器方法之前执行 preHandle 方法；在处理器方法执行完毕、视图渲染之前执行 postHandle 方法；在视图渲染完毕后执行 afterCompletion 方法。</p>
<h5>五、总结</h5>
<p>Spring MVC 的请求执行流程是一个高度可配置和灵活的过程，涉及多个组件的协同工作。通过理解这个流程，我们可以更好地掌握 Spring MVC 的工作原理，从而更高效地开发 Web 应用程序。同时，我们也可以根据实际需求，通过配置和扩展 Spring MVC 的各个组件，来优化</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140786722</p>]]></description><guid isPermaLink="false">/archives/e105cab1-ccbe-45ac-8674-4189a8a3e5e1</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 02:51:12 GMT</pubDate></item><item><title><![CDATA[【人工智能】Transformers之Pipeline：深度解析与应用探索(二)]]></title><link>https://wiki.dgfygk.com/archives/6ca7aaff-859e-4da8-aad8-33997dd105a2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E3%80%91Transformers%E4%B9%8BPipeline%EF%BC%9A%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E4%B8%8E%E5%BA%94%E7%94%A8%E6%8E%A2%E7%B4%A2%28%E4%BA%8C%29&amp;url=/archives/6ca7aaff-859e-4da8-aad8-33997dd105a2" width="1" height="1" alt="" style="opacity:0;">
<p>【人工智能】Transformers之Pipeline：深度解析与应用探索</p>
<p>在人工智能的浩瀚星空中，Transformer模型无疑是一颗璀璨的明星，它不仅在自然语言处理（NLP）领域取得了革命性的突破，还逐渐渗透到计算机视觉、语音识别、多模态学习等多个领域，展现了其强大的泛化能力和无限的潜力。Transformer模型的成功，很大程度上归功于其精心设计的Pipeline架构，这一架构为模型的高效训练和推理提供了坚实的基础。本文将从Transformer模型的基本概念出发，深入解析其Pipeline的各个环节，并探讨其在不同领域的应用与前景。</p>
<h4>一、Transformer模型概述</h4>
<p>Transformer模型最初由Vaswani等人在2017年的论文《Attention is All You Need》中提出，旨在解决序列到序列（Seq2Seq）任务中的长期依赖问题。与传统的循环神经网络（RNN）和卷积神经网络（CNN）不同，Transformer完全依赖于自注意力（Self-Attention）机制来捕捉序列中的依赖关系，从而实现了并行计算和更高效的特征提取。</p>
<p>Transformer模型主要由编码器（Encoder）和解码器（Decoder）两部分组成，每部分都包含多个相同的层堆叠而成。每层内部又包含自注意力机制和前馈神经网络（Feed-Forward Neural Network），通过残差连接（Residual Connection）和层归一化（Layer Normalization）来稳定训练过程。</p>
<h4>二、Transformer Pipeline解析</h4>
<p>Transformer模型的Pipeline可以大致分为数据预处理、模型训练、模型评估与调优、模型部署与应用四个关键环节。下面我们将逐一进行解析。</p>
<h5>1. 数据预处理</h5>
<p>数据预处理是任何机器学习或深度学习项目不可或缺的第一步，对于Transformer模型而言同样重要。数据预处理的质量直接影响模型的训练效果和泛化能力。在NLP任务中，数据预处理通常包括以下几个步骤：</p>
<ul>
 <li><strong>文本清洗</strong>：去除文本中的噪音数据，如HTML标签、特殊符号、非文本字符等。</li>
 <li><strong>分词（Tokenization）</strong>：将文本分割成一系列可处理的单元（Token），这些单元通常是单词、子词（Subword）或字符。</li>
 <li><strong>编码（Encoding）</strong>：将Token转换为模型能够理解的数值形式，通常是通过词嵌入（Word Embedding）实现的。</li>
 <li><strong>数据增强</strong>：通过随机替换、删除或插入Token等方式增加数据多样性，提高模型的鲁棒性。</li>
 <li><strong>构建数据批次（Batching）</strong>：为了提高训练效率，通常会将多个样本组合成一个批次进行训练。</li>
</ul>
<h5>2. 模型训练</h5>
<p>模型训练是Pipeline中的核心环节，也是计算资源消耗最大的部分。在训练Transformer模型时，需要注意以下几个关键点：</p>
<ul>
 <li><strong>损失函数</strong>：根据具体任务选择合适的损失函数，如交叉熵损失（Cross-Entropy Loss）用于分类任务，均方误差（MSE）用于回归任务等。</li>
 <li><strong>优化器</strong>：选择合适的优化算法，如Adam、RMSprop等，以高效地调整模型参数。</li>
 <li><strong>学习率调度</strong>：采用适当的学习率调度策略，如Warmup、Decay等，以平衡模型的训练速度和稳定性。</li>
 <li><strong>正则化技术</strong>：使用Dropout、Layer Normalization等技术防止模型过拟合。</li>
 <li><strong>硬件加速</strong>：利用GPU、TPU等高性能计算设备加速模型训练。</li>
</ul>
<h5>3. 模型评估与调优</h5>
<p>模型训练完成后，需要通过评估来检验其性能是否满足预期。评估过程通常包括以下几个步骤：</p>
<ul>
 <li><strong>划分数据集</strong>：将数据集划分为训练集、验证集和测试集，确保模型在未见过的数据上也能表现良好。</li>
 <li><strong>评估指标</strong>：根据任务特点选择合适的评估指标，如准确率、召回率、F1分数、BLEU分数等。</li>
 <li><strong>模型调优</strong>：根据评估结果调整模型结构、超参数或训练策略，以提高模型性能。</li>
</ul>
<h5>4. 模型部署与应用</h5>
<p>模型部署是将训练好的模型应用于实际场景的关键步骤。在部署Transformer模型时，需要考虑以下几个因素：</p>
<ul>
 <li><strong>模型压缩</strong>：通过剪枝、量化、知识蒸馏等技术减小模型体积，提高推理速度。</li>
 <li><strong>服务化</strong>：将模型封装成API或微服务，便于前端或其他系统调用。</li>
 <li><strong>性能优化</strong>：对模型推理过程进行性能优化，如使用更快的推理引擎、优化数据传输等。</li>
 <li><strong>持续监控</strong>：对模型进行持续监控和评估，确保其在实际应用中的稳定性和准确性。</li>
</ul>
<h4>三、Transformer模型的应用探索</h4>
<h5>1. 自然语言处理</h5>
<p>Transformer模型在自然语言处理领域的应用最为广泛，包括文本分类、情感分析、机器翻译、文本摘要、问答系统等。其中，BERT、GPT等基于Transformer的预训练语言模型更是推动了NLP领域的快速发展。</p>
<h5>2. 计算机视觉</h5>
<p>随着Vision Transformer（ViT）等模型的提出，Transformer也逐渐在计算机视觉领域崭露头角。ViT将Transformer模型应用于图像识别、目标检测、图像分割等任务，展示了其处理图像数据的强大能力。与传统的卷积神经网络（CNN）相比，Transformer模型在处理全局依赖关系、捕捉复杂空间结构方面展现出独特的优势。</p>
<h5>3. 语音识别与合成</h5>
<p>在语音识别领域，Transformer模型通过自注意力机制有效捕捉音频序列中的长距离依赖，提高了识别准确率。同时，结合循环神经网络（RNN）或卷积神经网络（CNN）的局部特征提取能力，可以构建出更加高效、准确的语音识别系统。在语音合成方面，Transformer模型也被用于生成自然流畅的语音，通过模拟人类发音过程，实现高质量的文本到语音转换。</p>
<h5>4. 多模态学习</h5>
<p>随着人工智能技术的不断发展，多模态学习成为了一个重要的研究方向。Transformer模型凭借其强大的序列处理能力，能够轻松处理来自不同模态的数据（如文本、图像、音频等），实现跨模态的信息融合与理解。例如，在视觉问答系统中，Transformer模型可以同时处理图像和文本输入，生成准确的答案；在情感分析中，结合文本和图像信息，可以更准确地判断用户的情感倾向。</p>
<h5>5. 医疗健康</h5>
<p>在医疗健康领域，Transformer模型的应用也逐渐增多。例如，在医学影像分析中，Transformer模型可以辅助医生进行病灶检测、病理分类等任务；在电子病历处理中，Transformer模型可以自动提取关键信息、生成诊断报告等。此外，结合自然语言处理技术，Transformer模型还可以用于医疗问答系统、药物研发等领域，为医疗健康行业带来智能化、高效化的解决方案。</p>
<h4>四、未来展望</h4>
<p>随着技术的不断进步和应用场景的不断拓展，Transformer模型在未来有着广阔的发展前景。一方面，随着计算能力的提升和算法的优化，Transformer模型将能够处理更大规模的数据、实现更复杂的任务；另一方面，随着跨领域知识的融合和交叉学科的发展，Transformer模型将在更多领域发挥重要作用。例如，在智能制造、智慧城市、自动驾驶等领域，Transformer模型可以通过处理和分析海量数据，为决策提供有力支持；在教育、娱乐等领域，Transformer模型可以通过生成个性化内容、提供智能推荐等方式提升用户体验。</p>
<p>总之，Transformer模型作为人工智能领域的一项重要技术成果，其Pipeline架构的精心设计为模型的高效训练和推理提供了坚实基础。随着技术的不断发展和应用场景的不断拓展，Transformer模型将在未来发挥更加重要的作用，推动人工智能技术的持续进步和发展。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140731384</p>]]></description><guid isPermaLink="false">/archives/6ca7aaff-859e-4da8-aad8-33997dd105a2</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 02:50:55 GMT</pubDate></item><item><title><![CDATA[智能科技的革命：AI人工智能概览]]></title><link>https://wiki.dgfygk.com/archives/7c5a3bc4-e283-42af-9027-735345246cd2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%99%BA%E8%83%BD%E7%A7%91%E6%8A%80%E7%9A%84%E9%9D%A9%E5%91%BD%EF%BC%9AAI%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E6%A6%82%E8%A7%88&amp;url=/archives/7c5a3bc4-e283-42af-9027-735345246cd2" width="1" height="1" alt="" style="opacity:0;">
<h2>智能科技的革命：AI人工智能概览</h2>
<p>人工智能（Artificial Intelligence, AI）作为21世纪最具变革性的技术之一，正深刻地改变着我们的生活方式、工作模式乃至社会结构。它不仅仅是一个学科领域的名称，更是一种集成了计算机科学、数学、心理学、哲学、语言学、生物学等多个学科知识的综合性技术体系。AI的目标是使机器能够模拟、延伸和扩展人的智能，完成复杂的工作和决策过程。以下是对AI人工智能的一个全面概述，涵盖其定义、发展历程、核心技术、应用领域、挑战与未来展望等方面。</p>
<h4>一、定义与内涵</h4>
<p>人工智能，简而言之，就是让机器具备类似人类的智能能力，包括但不限于感知、理解、学习、推理、决策、创造等。这种智能并非传统意义上的"智慧"或"意识”，而是指机器通过算法和数据处理，能够在特定任务上表现出与人类智能相似或超越的能力。AI的核心在于让机器学会"思考"和"学习”，从而能够在没有人工明确编程的情况下，自主解决问题和应对变化。</p>
<h4>二、发展历程</h4>
<p>人工智能的发展历程可以追溯到20世纪中叶。1950年，艾伦·图灵提出了著名的"图灵测试”，为AI的研究设定了基准。随后几十年间，AI经历了多次起伏和变革。</p>
<ul>
 <li><strong>起步期（1950s-1970s）</strong>：这一时期，AI主要聚焦于符号主义方法，尝试通过逻辑推理和规则匹配来模拟人类智能。虽然取得了一些初步成果，但受限于计算能力和数据量的限制，进展缓慢。</li>
 <li><strong>低谷期（1970s-1990s）</strong>：随着对AI过高期望的破灭，以及计算资源不足和算法瓶颈的暴露，AI研究进入了低谷期。许多项目被取消，资金减少，研究人员流失。</li>
 <li><strong>复兴期（1990s至今）</strong>：随着互联网技术的兴起、大数据的积累以及计算能力的提升，AI迎来了复兴的春天。特别是深度学习的出现，极大地推动了AI在图像识别、语音识别、自然语言处理等领域的进步。</li>
</ul>
<h4>三、核心技术</h4>
<p>AI的快速发展得益于一系列核心技术的突破和创新，主要包括以下几个方面：</p>
<ul>
 <li><strong>机器学习</strong>：是AI的核心技术之一，通过让机器从数据中学习并改进其性能，而无需进行明确的编程。机器学习算法可以自动发现数据中的规律和模式，并据此进行预测和决策。</li>
 <li><strong>深度学习</strong>：作为机器学习的一个分支，深度学习通过构建深层次的神经网络模型，模拟人脑神经元之间的连接和信息传递方式，从而在图像识别、语音识别等领域取得了显著成效。</li>
 <li><strong>自然语言处理（NLP）</strong>：旨在使机器能够理解和生成人类语言。NLP技术包括文本分析、情感分析、机器翻译等，是实现人机交互智能化的关键。</li>
 <li><strong>计算机视觉</strong>：使机器能够像人类一样"看"世界，理解图像和视频中的信息。计算机视觉技术广泛应用于图像识别、视频监控、自动驾驶等领域。</li>
 <li><strong>强化学习</strong>：通过让机器在不断试错中优化其行为策略，以最大化某个特定目标或奖励。强化学习在游戏AI、机器人控制等领域展现出强大潜力。</li>
</ul>
<h4>四、应用领域</h4>
<p>AI的应用领域极为广泛，几乎涵盖了人类生活的各个方面：</p>
<ul>
 <li><strong>智能制造</strong>：通过AI技术实现生产过程的自动化、智能化和高效化，提高产品质量和生产效率。</li>
 <li><strong>智慧医疗</strong>：AI在医疗领域的应用包括疾病诊断、药物研发、健康管理等方面，为患者提供更加精准和个性化的医疗服务。</li>
 <li><strong>智能交通</strong>：自动驾驶汽车、智能交通管理系统等基于AI技术的解决方案正在逐步改变我们的出行方式。</li>
 <li><strong>金融科技</strong>：AI在风险评估、信用评级、智能投顾等方面发挥着重要作用，推动金融行业的数字化转型。</li>
 <li><strong>教育娱乐</strong>：AI在教育领域的应用包括个性化教学、智能辅导等；在娱乐领域则体现在游戏设计、虚拟现实等方面。</li>
</ul>
<h4>五、挑战与未来展望</h4>
<p>尽管AI技术取得了显著进展，但仍面临诸多挑战：</p>
<ul>
 <li><strong>数据隐私与安全</strong>：随着AI应用的普及，如何保护用户数据隐私、防止数据泄露成为亟待解决的问题。</li>
 <li><strong>算法偏见与公平性</strong>：AI算法的决策可能受到训练数据偏见的影响，导致不公平的结果。如何消除算法偏见、确保AI决策的公平性是一个重要课题。</li>
 <li><strong>可解释性与透明度</strong>：当前许多AI模型尤其是深度学习模型缺乏可解释性，难以理解其决策背后的逻辑和依据。提高AI模型的可解释性和透明度对于建立用户信任至关重要。</li>
 <li><strong>技术与伦理的冲突</strong>：AI技术的快速发展也引发了一系列伦理问题，如自动驾驶汽车的道德困境、AI武器化等。如何平衡技术进步与伦理道德的关系是一个复杂而艰巨的任务。</li>
</ul>
<p>未来，随着技术的不断进步和应用场景的持续拓展，AI将在更多领域发挥重要作用。同时，我们也需要关注并解决上述挑战和问题，推动AI技术的健康发展和社会福祉的最大化。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140786767</p>]]></description><guid isPermaLink="false">/archives/7c5a3bc4-e283-42af-9027-735345246cd2</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 02:50:45 GMT</pubDate></item><item><title><![CDATA[BP神经网络及其Python和MATLAB实现预测]]></title><link>https://wiki.dgfygk.com/archives/032780cf-562c-411f-b5e2-6a9e624af7b0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=BP%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E5%8F%8A%E5%85%B6Python%E5%92%8CMATLAB%E5%AE%9E%E7%8E%B0%E9%A2%84%E6%B5%8B&amp;url=/archives/032780cf-562c-411f-b5e2-6a9e624af7b0" width="1" height="1" alt="" style="opacity:0;">
<h4>BP神经网络及其Python和MATLAB实现预测</h4>
<h5>引言</h5>
<p>BP神经网络（Back Propagation Neural Network），即反向传播神经网络，是一种通过反向传播算法进行监督学习的多层前馈网络。这种网络能够通过不断地调整和改变神经元的连接权重，达到对特定任务的学习和优化。由于其高度的灵活性和适应性，BP神经网络在模式识别、函数逼近、优化问题等多个领域有着广泛的应用。本文将详细介绍BP神经网络的基本原理，并通过Python和MATLAB的示例代码展示其在时间序列预测中的应用。</p>
<h5>BP神经网络的基本原理</h5>
<p>BP神经网络主要由输入层、隐藏层和输出层组成。输入层接收外部信号和数据，隐藏层进行信息处理，输出层产生预测结果。在训练过程中，网络通过前向传播计算输出值，并通过反向传播算法调整权重和偏置，以减小输出误差。</p>
<h6>1. 前向传播</h6>
<p>在前向传播过程中，输入数据通过输入层进入网络，经过隐藏层处理，最后由输出层输出预测结果。每个神经元接收来自前一层神经元的加权输入，并通过激活函数计算输出。输出层的输出即为网络的预测值。</p>
<h6>2. 反向传播</h6>
<p>反向传播是BP神经网络的核心算法，用于根据输出误差调整网络的权重和偏置。首先，计算输出层的误差，然后利用误差反向传播到隐藏层，再逐层向前传播，直到输入层。在反向传播过程中，使用梯度下降等优化算法来更新权重和偏置，以减小误差。</p>
<h5>Python实现BP神经网络预测</h5>
<p>在Python中，我们可以使用Keras库来构建和训练BP神经网络。Keras是一个高层神经网络API，它可以运行在TensorFlow、CNTK或Theano之上，以简化神经网络的构建、训练和评估过程。</p>
<h6>示例代码</h6>
<p>以下是一个使用Keras库构建简单BP神经网络进行时间序列预测的示例代码。</p>
<pre><code class="prism language-python">import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense
from sklearn.model_selection import train_test_split

# 随机生成模拟数据
np.random.seed(42)
input_dim = 1
output_dim = 1
X = np.random.rand(1000, input_dim)
y = 3 * X ** 2 + 0.5 * np.sin(2 * np.pi * X)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建Sequential模型
model = Sequential()
model.add(Dense(64, activation='relu', input_shape=(input_dim,)))
model.add(Dense(32, activation='relu'))
model.add(Dense(output_dim, activation='linear'))

# 编译模型
model.compile(loss='mean_squared_error', optimizer='adam')

# 训练模型
history = model.fit(X_train, y_train, epochs=100, batch_size=32, validation_split=0.2)

# 使用模型进行预测
predictions = model.predict(X_test)

# 可视化预测结果与实际值对比
plt.figure(figsize=(10, 6))
plt.scatter(range(1, len(y_test) + 1), y_test, color='blue', linewidth=1, label='Actual Values')
plt.plot(range(1, len(y_test) + 1), predictions, color='red', linewidth=1, label='Predictions')
plt.xlabel('Input Feature')
plt.ylabel('Output Feature')
plt.legend()
plt.title('Actual vs Predicted Values')
plt.savefig('plot.png')

# 计算并打印RMSE
from sklearn.metrics import mean_squared_error
rmse = np.sqrt(mean_squared_error(y_test, predictions))
print(f"Root Mean Squared Error (RMSE): {
     rmse:.2f}")
</code></pre>
<p>在这个示例中，我们首先生成了一些非线性相关的数据，并将其划分为训练集和测试集。然后，我们构建了一个包含两个隐藏层的BP神经网络，并使用均方误差作为损失函数和Adam优化器来训练模型。最后，我们使用训练好的模型对测试集进行预测，并可视化预测结果与实际值的对比。</p>
<h5>MATLAB实现BP神经网络预测</h5>
<p>MATLAB是一个广泛应用于数学、工程和科学计算的商业数学软件，内置了大量用于神经网络设计的工具箱，使得在MATLAB中实现BP神经网络变得相对容易。</p>
<h6>示例代码</h6>
<p>以下是一个使用MATLAB的Neural Network Toolbox来构建和训练BP神经网络进行时间序列预测的示例代码。</p>
<pre><code class="prism language-matlab">% 清理环境
clear; clc; close all;

% 随机生成模拟数据
rng(42); % 设置随机数生成器种子
input_dim = 1;
output_dim = 1;
X = rand(1000, input_dim);
y = 3 * X.^2 + 0.5 * sin(2 * pi * X);

% 划分训练集和测试集
cv = cvpartition(size(X, 1), 'HoldOut', 0.2);
idxTrain = training(cv);
idxTest = test(cv);
X_train = X(idxTrain, :);
y_train = y(idxTrain);
X_test = X(idxTest, :);
y_test = y(idxTest);

% 创建BP神经网络
net = feedforwardnet(10); % 创建一个具有10个神经元的隐藏层的网络

% 配置训练参数
net.trainFcn = 'trainlm'; % 使用Levenberg-Marquardt优化算法
net.divideParam.trainRatio = 70/100;
net.divideParam.valRatio = 15/100;
net.divideParam.testRatio = 15/100;

% 训练网络
[net, tr] = train(net, X_train', y_train'); % 注意MATLAB要求输入是列向量

% 使用训练好的网络进行预测
y_pred = net(X_test');

% 可视化预测结果与实际值对比
figure;
plot(y_test, 'bo-', 'DisplayName', 'Actual Values');
hold on;
plot(y_pred, 'r*-', 'DisplayName', 'Predictions');
legend show;
xlabel('Sample Index');
ylabel('Output Value');
title('Actual vs Predicted Values');
grid on;

% 计算并打印RMSE
rmse = sqrt(mean((y_pred - y_test).^2));
fprintf('Root Mean Squared Error (RMSE): %.2f\n', rmse);
</code></pre>
<p>在这个MATLAB示例中，我们首先使用<code>rng</code>函数设置了随机数生成器的种子，以确保每次运行代码时生成的数据相同。然后，我们生成了一些模拟数据，并将其划分为训练集和测试集。接着，我们使用<code>feedforwardnet</code>函数创建了一个具有单个隐藏层的BP神经网络，该隐藏层包含10个神经元。之后，我们设置了网络的训练函数为Levenberg-Marquardt优化算法，并配置了训练参数。</p>
<p>在训练网络时，我们使用了<code>train</code>函数，并注意到MATLAB要求输入数据是列向量，因此我们对<code>X_train</code>和<code>y_train</code>进行了转置。训练完成后，我们使用训练好的网络对测试集进行了预测，并将预测结果与实际值进行了可视化对比。最后，我们计算了预测结果的均方根误差（RMSE），并将其打印到控制台。</p>
<p>通过这个MATLAB示例，我们可以看到使用MATLAB的Neural Network Toolbox来构建和训练BP神经网络进行时间序列预测是相对直观和简单的。这个工具箱提供了丰富的函数和工具来支持神经网络的设计、训练和评估，使得用户可以轻松地实现复杂的神经网络模型。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140747034</p>]]></description><guid isPermaLink="false">/archives/032780cf-562c-411f-b5e2-6a9e624af7b0</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Fri, 27 Sep 2024 02:50:34 GMT</pubDate></item><item><title><![CDATA[Flink-CDC]]></title><link>https://wiki.dgfygk.com/archives/abb8b58d-ce5a-46f4-b8df-32018b2b618e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Flink-CDC&amp;url=/archives/abb8b58d-ce5a-46f4-b8df-32018b2b618e" width="1" height="1" alt="" style="opacity:0;">
<p>Flink-CDC（Flink Change Data Capture）是一个基于Flink计算框架的数据集成工具，它实现了对数据库变更数据的捕获、处理和传输，支持全量和增量数据的一体化读取。下面将从多个方面对Flink-CDC进行详细的解析。</p>
<h4>一、Flink-CDC概述</h4>
<h5>1.1 CDC简介</h5>
<p>CDC（Change Data Capture）是变更数据捕获的简称，其核心思想是监测并捕获数据库的变动（包括数据或数据表的插入、更新、删除等），将这些变更按发生的顺序完整记录下来，并写入到消息中间件或数据仓库中以供其他服务进行订阅及消费。CDC技术广泛应用于数据同步、数据分发、数据采集等场景，是数据集成领域的重要工具。</p>
<h5>1.2 Flink-CDC定义</h5>
<p>Flink-CDC是基于Flink计算框架实现的CDC工具，它能够高效地捕获数据库的变更数据，并将其传输到指定的目标系统（如数据仓库、数据湖、消息队列等）。Flink-CDC通过集成Debezium等CDC引擎，实现了对多种数据库（如MySQL、PostgreSQL等）的变更数据捕获。</p>
<h4>二、Flink-CDC原理</h4>
<h5>2.1 CDC技术原理</h5>
<p>CDC技术主要分为基于查询和基于Binlog两种方式。</p>
<ul>
 <li><strong>基于查询的CDC</strong>：通过定期查询数据库中的表来获取最新的数据变化。这种方式存在数据一致性和实时性难以保障的问题，因为查询过程中数据可能已经发生了多次变更。</li>
 <li><strong>基于Binlog的CDC</strong>：实时消费数据库的Binlog（二进制日志）文件，这些日志文件完整记录了数据库中的变更操作。基于Binlog的CDC能够保障数据的一致性和实时性，因为Binlog文件包含了所有历史变更明细，并且可以被流式消费。</li>
</ul>
<h5>2.2 Flink-CDC原理</h5>
<p>Flink-CDC主要采用了基于Binlog的CDC技术。它通过集成Debezium等CDC引擎，实现对数据库Binlog文件的实时消费和处理。具体过程如下：</p>
<ol>
 <li><strong>配置连接</strong>：首先，需要配置Flink-CDC与数据库的连接信息，包括数据库地址、端口、用户名、密码等。</li>
 <li><strong>启动捕获</strong>：启动Flink-CDC作业后，它会连接到数据库并订阅指定的Binlog文件。</li>
 <li><strong>数据解析</strong>：Flink-CDC利用Debezium等CDC引擎对Binlog文件进行解析，提取出数据变更的详细信息（如变更类型、变更前后的数据等）。</li>
 <li><strong>数据处理</strong>：解析出的数据变更信息会被送入Flink的DataStream或Table API中进行进一步的处理。</li>
 <li><strong>数据输出</strong>：处理后的数据可以被输出到指定的目标系统（如数据仓库、数据湖、消息队列等）。</li>
</ol>
<h5>2.3 Flink-CDC版本特性</h5>
<p>随着Flink-CDC的不断发展，其版本不断更新，带来了更多的特性和优化。例如：</p>
<ul>
 <li><strong>增量快照读取</strong>：Flink 2.x引入了增量快照读取机制，支持并发读取和以chunk为粒度进行checkpoint，极大地提高了数据读取的效率。</li>
 <li><strong>精确一次性处理</strong>：Flink 2.x引入了Exactly-Once语义，确保数据处理结果的精确一次性。MySQL CDC连接器是Flink的Source连接器，可以利用Flink的checkpoint机制来确保精确一次性处理。</li>
 <li><strong>动态加表</strong>：Flink 2.x支持动态加表，通过使用savepoint来复用之前作业的状态，解决了动态加表的问题。</li>
 <li><strong>无主键表的处理</strong>：Flink 2.x对无主键表的读取和处理进行了优化，在无主键表中，Flink可以通过一些额外的字段来识别数据记录的唯一性，从而实现准确的数据读取和处理。</li>
</ul>
<h4>三、Flink-CDC优势</h4>
<p>Flink-CDC相比其他CDC工具具有以下优势：</p>
<ol>
 <li><strong>高效性</strong>：Flink-CDC基于Flink计算框架实现，能够高效地处理海量数据的实时变更。</li>
 <li><strong>灵活性</strong>：Flink-CDC支持多种数据库和数据源，用户可以根据需求灵活配置。</li>
 <li><strong>可靠性</strong>：Flink-CDC利用Flink的checkpoint机制确保数据处理的精确一次性，保障数据的一致性和可靠性。</li>
 <li><strong>易用性</strong>：Flink-CDC提供了丰富的API和文档，用户可以通过简单的配置和编程实现复杂的数据集成任务。</li>
 <li><strong>扩展性</strong>：Flink-CDC支持分布式部署和水平扩展，能够满足大规模数据处理的需求。</li>
</ol>
<h4>四、Flink-CDC应用场景</h4>
<p>Flink-CDC广泛应用于以下场景：</p>
<ol>
 <li><strong>数据同步</strong>：用于数据库之间的数据同步，如将MySQL数据同步到PostgreSQL等。</li>
 <li><strong>数据分发</strong>：将数据源的数据分发给多个下游系统，如将数据仓库的数据分发到多个分析系统中。</li>
 <li><strong>数据采集</strong>：面向数据仓库/数据湖的ETL数据集成，是数据仓库/数据湖构建的重要工具。通过Flink-CDC，企业可以实时地将业务系统中的数据变化捕获并传输到数据仓库或数据湖中，以便进行后续的数据分析和挖掘。</li>
</ol>
<h4>五、Flink-CDC配置与实现</h4>
<h5>5.1 环境准备</h5>
<p>在使用Flink-CDC之前，需要确保以下环境已经准备好：</p>
<ul>
 <li><strong>Apache Flink</strong>：安装并配置好Apache Flink集群。</li>
 <li><strong>Flink-CDC Connector</strong>：根据目标数据库类型，选择合适的Flink-CDC Connector，并添加到Flink的classpath中。</li>
 <li><strong>目标数据库</strong>：确保目标数据库（如MySQL、PostgreSQL等）已经开启Binlog，并配置好相应的权限。</li>
</ul>
<h5>5.2 配置Flink作业</h5>
<p>配置Flink作业以使用Flink-CDC，主要包括以下几个步骤：</p>
<ol>
 <li>
  <p><strong>设置Source</strong>：在Flink作业中，通过Flink Table API或DataStream API设置Source，指定数据库的连接信息和要捕获的表。</p>
 </li>
 <li>
  <p><strong>数据处理</strong>：在Flink作业中，使用DataStream或Table API对数据进行处理，如过滤、转换、聚合等。</p>
 </li>
 <li>
  <p><strong>设置Sink</strong>：指定数据的输出目标，如将数据写入到Kafka、HDFS、Elasticsearch等。</p>
 </li>
 <li>
  <p><strong>执行作业</strong>：将配置好的Flink作业提交到Flink集群中执行。</p>
 </li>
</ol>
<h5>5.3 示例代码</h5>
<p>以下是一个使用Flink Table API配置Flink-CDC的示例代码片段：</p>
<pre><code class="prism language-java">import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

// 初始化Flink执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

// 注册Flink-CDC Source
String ddl = """
    CREATE TABLE my_table (
        id INT,
        name STRING,
        description STRING,
        PRIMARY KEY (id) NOT ENFORCED
    ) WITH (
        'connector' = 'mysql-cdc',
        'hostname' = 'localhost',
        'port' = '3306',
        'username' = 'root',
        'password' = 'secret',
        'database-name' = 'mydatabase',
        'table-name' = 'mytable',
        'debezium.snapshot.mode' = 'initial'
    )
""";
tableEnv.executeSql(ddl);

// 查询并输出结果
tableEnv.executeSql("SELECT * FROM my_table").print();

// 启动Flink作业
env.execute("Flink CDC Example");
</code></pre>
<p>请注意，上述代码是一个简化的示例，实际使用时需要根据具体的Flink版本和Flink-CDC Connector版本进行调整。</p>
<h4>六、Flink-CDC的挑战与解决方案</h4>
<h5>6.1 挑战</h5>
<ol>
 <li><strong>数据一致性问题</strong>：在分布式系统中，数据一致性的保障是一个复杂的问题。虽然Flink提供了checkpoint机制，但在某些极端情况下，仍可能出现数据丢失或重复。</li>
 <li><strong>性能瓶颈</strong>：随着数据量的增加，Flink-CDC的性能可能会受到网络带宽、CPU处理能力、磁盘I/O等因素的限制。</li>
 <li><strong>配置复杂性</strong>：Flink-CDC的配置相对复杂，需要用户具备一定的数据库和Flink知识。</li>
</ol>
<h5>6.2 解决方案</h5>
<ol>
 <li><strong>增强数据一致性</strong>：通过优化Flink的checkpoint机制，增加checkpoint的频率和容错能力，以提高数据的一致性。</li>
 <li><strong>优化性能</strong>：通过升级硬件、优化网络配置、调整Flink作业参数等方式，提高Flink-CDC的性能。</li>
 <li><strong>简化配置</strong>：提供更为友好的配置界面和文档，帮助用户快速上手并降低配置难度。</li>
</ol>
<h4>七、总结</h4>
<p>Flink-CDC作为一个基于Flink计算框架的CDC工具，具有高效性、灵活性、可靠性、易用性和扩展性等优势，广泛应用于数据同步、数据分发、数据采集等场景。通过合理配置和使用Flink-CDC，企业可以实时地将业务系统中的数据变化捕获并传输到目标系统中，为数据分析和挖掘提供有力支持。然而，在使用Flink-CDC时，也需要注意数据一致性问题、性能瓶颈和配置复杂性等挑战，并采取相应的解决方案进行应对。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140747125</p>]]></description><guid isPermaLink="false">/archives/abb8b58d-ce5a-46f4-b8df-32018b2b618e</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 02:50:22 GMT</pubDate></item><item><title><![CDATA[TCP协议详解]]></title><link>https://wiki.dgfygk.com/archives/506ab40e-f420-468d-a27c-9e4db4deac28</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=TCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3&amp;url=/archives/506ab40e-f420-468d-a27c-9e4db4deac28" width="1" height="1" alt="" style="opacity:0;">
<h4>TCP协议详解</h4>
<p>TCP（Transmission Control Protocol，传输控制协议）是一种面向连接的、可靠的、基于字节流的传输层通信协议。它在网络通信中扮演着至关重要的角色，尤其是在需要保证数据完整性和顺序性的应用场景中。以下是对TCP协议的详细解析，包括其工作原理、特点、应用场合以及关键机制等方面。</p>
<h5>一、TCP协议概述</h5>
<p>TCP协议是TCP/IP协议族中的核心协议之一，位于网络层（IP层）之上，应用层之下。它提供了一种可靠的、面向连接的数据传输服务，确保数据能够按照顺序、无差错、不重复地传输到目的端。TCP协议通过一系列复杂的机制来实现这些目标，包括三次握手建立连接、四次挥手释放连接、重传机制、流量控制、拥塞控制等。</p>
<h5>二、TCP协议的特点</h5>
<ol>
 <li>
  <p><strong>面向连接</strong>：TCP协议在传输数据之前，必须先通过三次握手建立连接。这种面向连接的特性使得TCP能够提供可靠的数据传输服务。</p>
 </li>
 <li>
  <p><strong>可靠交付</strong>：TCP通过确认机制、重传机制等确保数据能够无差错、不丢失、不重复地传输到目的端。</p>
 </li>
 <li>
  <p><strong>全双工通信</strong>：TCP连接的两端都可以同时发送和接收数据，这种全双工通信方式提高了数据传输的效率。</p>
 </li>
 <li>
  <p><strong>面向字节流</strong>：TCP将应用程序交下来的数据看成是一连串的无结构的字节流，它不关心数据的内容，只负责数据的可靠传输。</p>
 </li>
 <li>
  <p><strong>头部开销大</strong>：TCP头部最小为20字节，最大可达60字节。与UDP相比，TCP的头部开销较大，这在一定程度上降低了数据传输的效率。</p>
 </li>
 <li>
  <p><strong>拥塞控制</strong>：TCP通过拥塞控制机制来避免网络拥塞，确保网络的稳定性和可靠性。</p>
 </li>
</ol>
<h5>三、TCP协议的工作原理</h5>
<h6>1. 三次握手建立连接</h6>
<p>TCP连接建立的过程通常被称为三次握手。其过程如下：</p>
<ol>
 <li>
  <p><strong>SYN包发送</strong>：客户端发送一个SYN（同步序列编号）包到服务器，并进入SYN_SENT状态，等待服务器确认。</p>
 </li>
 <li>
  <p><strong>SYN-ACK包确认</strong>：服务器收到SYN包后，发送一个SYN-ACK（同步序列编号确认）包给客户端，表示已收到客户端的连接请求，并进入SYN_RCVD状态。</p>
 </li>
 <li>
  <p><strong>ACK包确认</strong>：客户端收到SYN-ACK包后，发送一个ACK（确认）包给服务器，表示已确认服务器的连接请求，此时连接建立完成，客户端和服务器都进入ESTABLISHED状态。</p>
 </li>
</ol>
<h6>2. 四次挥手释放连接</h6>
<p>TCP连接释放的过程通常被称为四次挥手。其过程如下：</p>
<ol>
 <li>
  <p><strong>FIN包发送</strong>：客户端或服务器中的任意一方想要释放连接时，会发送一个FIN（结束）包给对方，并进入FIN_WAIT_1状态。</p>
 </li>
 <li>
  <p><strong>ACK包确认</strong>：对方收到FIN包后，发送一个ACK包进行确认，并进入CLOSE_WAIT状态。此时，原发送FIN包的一方进入FIN_WAIT_2状态。</p>
 </li>
 <li>
  <p><strong>FIN包发送</strong>：当对方也准备好释放连接时，会发送一个FIN包给对方，并进入LAST_ACK状态。</p>
 </li>
 <li>
  <p><strong>ACK包确认</strong>：原发送FIN包的一方收到对方的FIN包后，发送一个ACK包进行确认，并进入TIME_WAIT状态。经过一段时间后（通常是2MSL，即两倍的报文最大生存时间），如果没有新的数据传输，则连接彻底释放，双方进入CLOSED状态。</p>
 </li>
</ol>
<h5>四、TCP协议的关键机制</h5>
<h6>1. 重传机制</h6>
<p>TCP通过确认机制来确保数据的可靠传输。当发送方发送一个数据包后，会等待接收方的确认（ACK）。如果在一定时间内没有收到确认，发送方会认为数据包丢失，并重新发送该数据包。这种重传机制保证了数据的可靠传输。</p>
<h6>2. 流量控制</h6>
<p>TCP通过滑动窗口机制来实现流量控制。滑动窗口是接收方告诉发送方自己当前能够接收多少数据的一个窗口大小。发送方根据这个窗口大小来发送数据，从而避免了发送方发送过多数据导致接收方处理不过来的问题。</p>
<h6>3. 拥塞控制</h6>
<p>TCP通过拥塞控制机制来避免网络拥塞。当网络出现拥塞时，TCP会降低发送速率，以减少对网络的压力。拥塞控制机制包括慢启动、拥塞避免、快重传和快恢复等算法。</p>
<h5>五、TCP协议的应用场合</h5>
<p>TCP协议因其可靠传输的特性，被广泛应用于各种需要保证数据完整性和顺序性的应用场景中。以下是一些典型的应用场合：</p>
<ol>
 <li>
  <p><strong>网络文件传输</strong>：如FTP（文件传输协议）等，TCP协议支持大文件的传输，具有高可靠性和稳定性。</p>
 </li>
 <li>
  <p><strong>电子邮件传输</strong>：SMTP（简单邮件传输协议）基于TCP协议工作，确保电子邮件从发送者到接收者的可靠传输。通过TCP，SMTP能够确保邮件内容的完整性和顺序性，即使在网络条件不佳的情况下也能尽量保证邮件的送达。</p>
 </li>
 <li>
  <p><strong>网页传输</strong>：虽然HTTP（超文本传输协议）的较新版本HTTP/2和HTTP/3引入了不同的传输方式（如基于UDP的QUIC协议），但在HTTP/1.x版本中，TCP仍然是主要的传输协议。TCP的可靠性确保了网页内容能够完整无误地从服务器传输到用户的浏览器。</p>
 </li>
 <li>
  <p><strong>远程登录和远程桌面协议</strong>：如SSH（安全外壳协议）和RDP（远程桌面协议），这些协议利用TCP的可靠性来确保用户能够安全、稳定地远程访问和管理服务器或桌面环境。</p>
 </li>
 <li>
  <p><strong>数据库连接</strong>：许多数据库系统，如MySQL、PostgreSQL等，都支持通过TCP/IP协议进行远程连接。TCP的面向连接特性和可靠性使得数据库操作能够高效、安全地进行。</p>
 </li>
 <li>
  <p><strong>流媒体传输</strong>：虽然流媒体传输（如视频直播、在线视频等）对实时性要求较高，但TCP仍然在某些场景中被使用，尤其是在需要保证视频质量不受网络波动影响的情况下。TCP的重传机制可以减少视频中的卡顿和丢失帧现象。</p>
 </li>
 <li>
  <p><strong>实时通信应用</strong>：虽然UDP因其低延迟和简单的错误处理机制而常被用于实时通信应用（如VoIP、视频通话等），但TCP在某些需要保证消息完整性和顺序性的实时通信场景中也得到应用。例如，在需要传输重要文件或敏感信息的实时聊天应用中，TCP的可靠性尤为重要。</p>
 </li>
</ol>
<h5>六、TCP协议的优化与挑战</h5>
<h6>优化策略：</h6>
<ol>
 <li>
  <p><strong>TCP Fast Open (TFO)</strong>：TFO允许客户端和服务器在建立连接时减少一次往返时间（RTT），通过发送带有数据的SYN包来加速连接建立过程。</p>
 </li>
 <li>
  <p><strong>TCP窗口缩放</strong>：由于TCP头部的窗口大小字段只有16位，限制了最大窗口大小。TCP窗口缩放选项允许将窗口大小乘以一个缩放因子，从而支持更大的窗口和更高的吞吐量。</p>
 </li>
 <li>
  <p><strong>TCP BBR拥塞控制算法</strong>：由Google开发的BBR（Bottleneck Bandwidth and Round-trip propagation time）算法旨在通过测量网络瓶颈带宽和往返时间来实现更高效的拥塞控制。</p>
 </li>
</ol>
<h6>面临的挑战：</h6>
<ol>
 <li>
  <p><strong>延迟问题</strong>：TCP的重传机制和拥塞控制算法在网络条件较差时可能导致较高的延迟。虽然可以通过优化算法来减轻这一问题，但在某些实时性要求极高的应用中仍可能受限。</p>
 </li>
 <li>
  <p><strong>带宽利用率</strong>：TCP的流量控制和拥塞控制机制在避免网络拥塞的同时也可能限制了带宽的充分利用。在网络条件较好的情况下，TCP可能无法完全利用可用的带宽资源。</p>
 </li>
 <li>
  <p><strong>安全性</strong>：TCP本身不提供加密和认证功能，因此在安全性要求较高的应用中需要与SSL/TLS等安全协议结合使用。然而，这增加了协议的复杂性和处理开销。</p>
 </li>
</ol>
<p>综上所述，TCP协议作为互联网通信中的基石之一，其可靠性和面向连接的特性在众多应用场景中发挥着重要作用。然而，随着网络技术的不断发展，TCP也面临着一些挑战和优化需求。通过不断的研究和创新，我们可以期待TCP在未来继续发挥更加重要的作用。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140768909</p>]]></description><guid isPermaLink="false">/archives/506ab40e-f420-468d-a27c-9e4db4deac28</guid><dc:creator>黄健</dc:creator><category>网络运维</category><category>默认分类</category><pubDate>Fri, 27 Sep 2024 02:50:08 GMT</pubDate></item><item><title><![CDATA[iOS中的类型推断（Type Inference）]]></title><link>https://wiki.dgfygk.com/archives/1bcf415c-e3c0-4bd2-83e4-019d992a4dff</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=iOS%E4%B8%AD%E7%9A%84%E7%B1%BB%E5%9E%8B%E6%8E%A8%E6%96%AD%EF%BC%88Type%20Inference%EF%BC%89&amp;url=/archives/1bcf415c-e3c0-4bd2-83e4-019d992a4dff" width="1" height="1" alt="" style="opacity:0;">
<p>在深入探讨iOS中的类型推断（Type Inference）及其在Swift编程语言中的作用和优势之前，我们需要先理解几个基本概念：编程语言、静态类型系统、动态类型系统，以及Swift作为一门现代编程语言的特点。随后，我们将详细阐述类型推断的定义、它在Swift中的应用、带来的优势，以及它如何促进iOS应用开发的效率和可靠性。</p>
<h4>一、引言</h4>
<p>编程语言是人与计算机之间交流的工具，它们允许我们以特定的语法规则书写指令，这些指令随后被计算机执行以完成特定的任务。在编程语言的设计中，类型系统是一个核心概念，它定义了如何使用数据类型来分类和表示数据。根据类型检查发生的时机，类型系统可以分为静态类型系统和动态类型系统。</p>
<ul>
 <li><strong>静态类型系统</strong>：在编译时检查变量和表达式的类型是否一致。这有助于在代码执行前就发现潜在的类型错误，提高代码的稳定性和可预测性。</li>
 <li><strong>动态类型系统</strong>：在运行时检查变量和表达式的类型。这种类型系统提供了更高的灵活性，但也可能导致运行时错误，增加了调试的难度。</li>
</ul>
<p>Swift作为一门多范式、编译型编程语言，由Apple公司开发并推广，专为iOS、macOS、watchOS和tvOS等Apple平台设计。Swift采用了静态类型系统，但与其他静态类型语言（如C++或Java）相比，它引入了类型推断这一特性，极大地简化了代码编写过程，同时保持了类型安全。</p>
<h4>二、类型推断的定义</h4>
<p>类型推断（Type Inference）是编程语言编译器或解释器自动推断变量或表达式的类型的能力。在支持类型推断的语言中，开发者在声明变量时无需显式指定其类型，编译器会根据上下文（如变量的初始值、赋值操作或函数调用等）自动推断出最合适的类型。</p>
<p>类型推断的好处在于，它减少了代码中的冗余，使代码更加简洁易读。同时，由于类型推断是在编译时进行的，因此它不会牺牲静态类型系统带来的类型安全性和性能优势。</p>
<h4>三、Swift中的类型推断</h4>
<p>在Swift中，类型推断是默认开启的，并且被广泛应用于变量声明、常量声明、函数返回类型等多个方面。</p>
<h5>1. 变量和常量的类型推断</h5>
<p>在Swift中，当你声明一个变量或常量并为其提供一个初始值时，编译器会自动推断出该变量或常量的类型。例如：</p>
<pre><code class="prism language-swift">let message = "Hello, Swift!"
// 编译器自动推断出message的类型为String

var number = 42
// 编译器自动推断出number的类型为Int
</code></pre>
<p>如果你没有为变量或常量提供初始值，则需要在声明时显式指定其类型，因为此时编译器无法从上下文中推断出类型。</p>
<h5>2. 函数的类型推断</h5>
<p>Swift中的函数也可以利用类型推断来简化声明。当函数的返回类型可以通过函数体中的表达式自动推断出来时，你可以省略返回类型的声明。例如：</p>
<pre><code class="prism language-swift">func sayHello(name: String) -&gt; String {

    return "Hello, \(name)!"
    // 由于返回值的类型可以自动推断为String，因此这里的-&gt; String可以省略
}

// 简化为：
func sayHello(name: String) {

    return "Hello, \(name)!"
}
</code></pre>
<p>但需要注意的是，如果函数体中包含多条返回语句，且这些返回语句可能返回不同类型的值，则必须显式指定返回类型，以确保类型安全。</p>
<h5>3. 复杂类型的类型推断</h5>
<p>Swift的类型系统非常强大，支持泛型、元组（Tuple）、可选类型（Optional）等复杂类型。这些类型的类型推断也遵循相同的规则：编译器会根据上下文自动推断出最合适的类型。例如：</p>
<pre><code class="prism language-swift">let point = (x: 10, y: 20)
// 编译器自动推断出point的类型为(x: Int, y: Int)

func calculateArea(radius: Double) -&gt; Double? {

    // ... 计算圆面积的逻辑
    return nil // 表示无法计算面积
    // 这里返回类型的?表示这是一个可选类型，编译器可以自动推断出来
}
</code></pre>
<h4>四、类型推断的优势</h4>
<h5>1. 简化代码</h5>
<p>类型推断最直接的优势是减少了代码中的冗余。在不需要显式指定类型的情况下，开发者可以更加专注于业务逻辑的实现，而不是花费大量时间在类型声明上。这不仅提高了编写代码的速度，也使得代码更加简洁易读。</p>
<h5>2. 提高开发效率</h5>
<p>由于类型推断减少了编写和维护类型声明的工作量，因此可以显著提高开发效率。开发者可以更快地编写出可运行的代码，并更容易地理解和修改现有代码。此外，类型推断还有助于减少因类型错误导致的编译失败，从而减少调试时间。</p>
<h5>3. 保持类型安全</h5>
<p>尽管类型推断简化了代码编写过程，但它并没有牺牲静态类型系统带来的类型安全性。Swift的编译器会在编译时进行严格的类型检查，确保所有变量、常量、函数参数和返回值的类型都符合预期。如果编译器无法根据上下文推断出合适的类型，或者推断出的类型与预期不符，它将报错并提示开发者进行相应的修正。这种机制确保了即使在使用类型推断的情况下，Swift代码也能保持高度的类型安全性和可预测性。</p>
<h5>4. 促进代码重构</h5>
<p>在软件开发的生命周期中，代码重构是一个常见且重要的活动。随着项目的发展和需求的变更，原有的代码结构可能不再满足新的要求，此时就需要进行重构以优化代码结构、提高代码质量。在支持类型推断的语言中，如Swift，代码重构变得更加容易和高效。因为类型信息是由编译器自动推断的，所以在重构过程中，开发者不需要手动更新大量的类型声明，这大大减少了出错的可能性，并提高了重构的效率。</p>
<h5>5. 鼓励编写更清晰的代码</h5>
<p>类型推断的另一个隐性优势是它鼓励开发者编写更清晰的代码。在没有类型推断的情况下，开发者可能会倾向于使用更通用的类型（如<code>Any</code>或<code>AnyObject</code>在Swift中）来避免显式指定具体的类型。然而，这种做法会牺牲类型安全性和可读性。在Swift中，由于类型推断的存在，开发者更倾向于使用具体的、明确的类型来声明变量和函数参数，这有助于提高代码的可读性和可维护性。</p>
<h5>6. 便于跨平台开发</h5>
<p>Swift作为一门由Apple公司开发的编程语言，其设计之初就考虑到了跨平台开发的需求。通过Swift的框架和库（如Foundation、UIKit等），开发者可以编写在iOS、macOS、watchOS和tvOS等多个平台上运行的代码。类型推断作为Swift语言的一个核心特性，使得跨平台开发变得更加容易和高效。因为无论是在哪个平台上，类型推断的规则和机制都是一致的，所以开发者可以专注于业务逻辑的实现，而不必担心类型系统在不同平台上的差异。</p>
<h4>五、结论</h4>
<p>类型推断是Swift编程语言中一个非常重要的特性，它极大地简化了代码编写过程，提高了开发效率，同时保持了静态类型系统带来的类型安全性和可预测性。通过自动推断变量、常量、函数参数和返回值的类型，Swift的编译器帮助开发者减少了编写和维护类型声明的工作量，使得开发者可以更加专注于业务逻辑的实现。此外，类型推断还促进了代码重构的便利性、鼓励了更清晰的代码编写风格，并有利于跨平台开发的实现。因此，在iOS开发中充分利用Swift的类型推断特性，对于提高开发效率、保证代码质量和促进团队协作都具有重要的意义。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140711710</p>]]></description><guid isPermaLink="false">/archives/1bcf415c-e3c0-4bd2-83e4-019d992a4dff</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 02:49:57 GMT</pubDate></item><item><title><![CDATA[Java基本数据类型（Primitive Types）]]></title><link>https://wiki.dgfygk.com/archives/94b30705-f7e6-4091-b8dc-0c63f76a7f27</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%EF%BC%88Primitive%20Types%EF%BC%89&amp;url=/archives/94b30705-f7e6-4091-b8dc-0c63f76a7f27" width="1" height="1" alt="" style="opacity:0;">
<p>在Java中，基本数据类型（Primitive Types）是编程语言内置的数据类型，它们直接映射到硬件的存储单元上，具有固定的存储大小和取值范围。Java的基本数据类型分为四大类：整型（Integer Types）、浮点型（Floating-Point Types）、字符型（Character Types）和布尔型（Boolean Type）。下面我将详细解释每种类型，包括它们的存储大小、取值范围以及一些额外的信息。</p>
<h4>1. 整型（Integer Types）</h4>
<p>整型用于表示没有小数部分的数值。Java提供了四种不同的整型，以适应不同大小的数值需求：</p>
<ul>
 <li>
  <p><strong>byte</strong></p>
 </li>
 <li>
  <p><strong>存储大小</strong>：1字节（8位）</p>
 </li>
 <li>
  <p><strong>取值范围</strong> ：</p>
  <ul>
   <li>最小值：-128 (-2^7)</li>
   <li>最大值：127 (2^7 - 1)</li>
  </ul>
 </li>
 <li>
  <p><strong>描述</strong>：byte类型是最小的整型，常用于需要节省存储空间的场合，如文件处理或网络传输中的小数据。</p>
 </li>
 <li>
  <p><strong>short</strong></p>
 </li>
 <li>
  <p><strong>存储大小</strong>：2字节（16位）</p>
 </li>
 <li>
  <p><strong>取值范围</strong> ：</p>
  <ul>
   <li>最小值：-32,768 (-2^15)</li>
   <li>最大值：32,767 (2^15 - 1)</li>
  </ul>
 </li>
 <li>
  <p><strong>描述</strong>：short类型用于存储比byte类型稍大的整数值。尽管其用途不如byte广泛，但在需要比byte更大范围时很有用。</p>
 </li>
 <li>
  <p><strong>int</strong></p>
 </li>
 <li>
  <p><strong>存储大小</strong>：4字节（32位）</p>
 </li>
 <li>
  <p><strong>取值范围</strong> ：</p>
  <ul>
   <li>最小值：-2,147,483,648 (-2^31)</li>
   <li>最大值：2,147,483,647 (2^31 - 1)</li>
  </ul>
 </li>
 <li>
  <p><strong>描述</strong>：int是Java中最常用的整型，用于存储大多数整数。它是大多数方法返回值的默认类型，也是算术运算（如加、减、乘、除）的默认类型。</p>
 </li>
 <li>
  <p><strong>long</strong></p>
 </li>
 <li>
  <p><strong>存储大小</strong>：8字节（64位）</p>
 </li>
 <li>
  <p><strong>取值范围</strong> ：</p>
  <ul>
   <li>最小值：-9,223,372,036,854,775,808 (-2^63)</li>
   <li>最大值：9,223,372,036,854,775,807 (2^63 - 1)</li>
  </ul>
 </li>
 <li>
  <p><strong>描述</strong>：long类型用于存储非常大的整数值。当int类型的范围不足以满足需求时，可以选择long。在声明long类型的变量时，建议在其后加上"L"或"l"后缀，以避免与int类型混淆（尽管"l"可能因字体问题而与数字"1"混淆，因此推荐使用"L”）。</p>
 </li>
</ul>
<h4>2. 浮点型（Floating-Point Types）</h4>
<p>浮点型用于表示有小数部分的数值。Java提供了两种浮点类型：</p>
<ul>
 <li>
  <p><strong>float</strong></p>
 </li>
 <li>
  <p><strong>存储大小</strong>：4字节（32位）</p>
 </li>
 <li>
  <p><strong>取值范围</strong> ：</p>
  <ul>
   <li>大约 ±3.4028235E+38F（有效位数约7位）</li>
  </ul>
 </li>
 <li>
  <p><strong>精度</strong>：不是完全精确的，因为基于IEEE 754标准实现</p>
 </li>
 <li>
  <p><strong>描述</strong>：float类型用于存储单精度浮点数。虽然它提供了比整型更大的数值范围，但牺牲了精度。在声明float类型的变量时，建议在其后加上"F"或"f"后缀，以避免与double类型混淆。</p>
 </li>
 <li>
  <p><strong>double</strong></p>
 </li>
 <li>
  <p><strong>存储大小</strong>：8字节（64位）</p>
 </li>
 <li>
  <p><strong>取值范围</strong> ：</p>
  <ul>
   <li>大约 ±1.79769313486231570E+308（有效位数约15位）</li>
  </ul>
 </li>
 <li>
  <p><strong>精度</strong>：同样不是完全精确的，但比float类型更高</p>
 </li>
 <li>
  <p><strong>描述</strong>：double类型用于存储双精度浮点数。它是Java中默认的浮点类型，提供了比float更大的数值范围和更高的精度。</p>
 </li>
</ul>
<h4>3. 字符型（Character Types）</h4>
<p>字符型用于表示单个字符。Java中只有一种字符类型：</p>
<ul>
 <li><strong>char</strong></li>
 <li><strong>存储大小</strong>：2字节（16位）</li>
 <li><strong>取值范围</strong> ：
  <ul>
   <li>Unicode字符集中的任意字符（0到65,535）</li>
  </ul></li>
 <li><strong>描述</strong>：char类型用于存储单个Unicode字符。Java使用Unicode标准来表示字符，因此char类型可以表示世界上几乎所有的字符。</li>
</ul>
<h4>4. 布尔型（Boolean Type）</h4>
<p>布尔型用于表示逻辑上的真（true）或假（false）。</p>
<ul>
 <li><strong>boolean</strong></li>
 <li><strong>存储大小</strong>：虽然实际上在Java虚拟机（JVM）中boolean类型的存储大小可能依赖于具体的实现，但通常可以认为它不是一个完整的字节。然而，在布尔数组或布尔字段中，可能会使用字节或更大的存储单位来优化存储。</li>
 <li><strong>取值范围</strong> ：只有两个值：<code>true</code> 和 <code>false</code>。</li>
 <li><strong>描述</strong>：boolean类型用于表示逻辑条件。它是Java中最简单的数据类型之一，常用于控制程序流程，如if语句、循环等。</li>
</ul>
<h4>额外信息</h4>
<h5>1. 默认值和初始化</h5>
<ul>
 <li>基本数据类型的变量在声明时如果没有显式初始化，它们会被赋予一个默认值。例如，数值类型的默认值是0（对于byte、short、int、long）或0.0（对于float、double），boolean类型的默认值是false，char类型的默认值是'\u0000'（即空字符）。</li>
</ul>
<h5>2. 封装类</h5>
<p>Java为每种基本数据类型都提供了一个对应的封装类（Wrapper Class），这些类位于<code>java.lang</code>包中。封装类为基本数据类型提供了面向对象的特性，如可以拥有方法和属性。例如，<code>Integer</code>是<code>int</code>的封装类，<code>Double</code>是<code>double</code>的封装类，依此类推。封装类主要用于集合框架中，因为集合只能存储对象类型，而不能直接存储基本数据类型。</p>
<h5>3. 自动装箱和拆箱</h5>
<p>从Java 5（也称为Java 1.5）开始，引入了自动装箱（Autoboxing）和拆箱（Unboxing）机制。这允许Java编译器在基本数据类型和它们对应的封装类之间进行自动转换。例如，当你将一个<code>int</code>类型的值赋给一个<code>Integer</code>类型的变量时，编译器会自动进行装箱操作，生成一个<code>Integer</code>对象。相反，当你从一个<code>Integer</code>对象中获取<code>int</code>类型的值时，编译器会自动进行拆箱操作。</p>
<h5>4. 选择合适的类型</h5>
<p>在选择使用哪种基本数据类型时，应考虑以下几个因素：</p>
<ul>
 <li><strong>存储需求</strong>：根据要存储的数值大小选择合适的类型，以节省内存空间。</li>
 <li><strong>精度需求</strong> ：对于需要高精度的计算，应选择浮点类型，并根据需要选择<code>float</code>或<code>double</code>。</li>
 <li><strong>性能考虑</strong>：虽然现代JVM的优化已经非常出色，但在某些情况下，选择适当的数据类型仍然可以对性能产生显著影响。</li>
 <li><strong>可读性</strong>：选择清晰、易于理解的数据类型可以提高代码的可读性。</li>
</ul>
<h4>结论</h4>
<p>Java的基本数据类型是Java编程的基础，它们提供了表示数据的基本方式。了解每种数据类型的存储大小、取值范围以及它们之间的区别和联系，对于编写高效、可维护的Java代码至关重要。在实际编程中，应根据具体需求选择合适的数据类型，并充分利用Java提供的自动装箱和拆箱机制以及封装类来简化代码编写和提高代码的可读性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140680534</p>]]></description><guid isPermaLink="false">/archives/94b30705-f7e6-4091-b8dc-0c63f76a7f27</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 02:49:48 GMT</pubDate></item><item><title><![CDATA[Java中的多态性]]></title><link>https://wiki.dgfygk.com/archives/d8fe7c66-cc03-4d82-931c-c69d99b29366</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E7%9A%84%E5%A4%9A%E6%80%81%E6%80%A7&amp;url=/archives/d8fe7c66-cc03-4d82-931c-c69d99b29366" width="1" height="1" alt="" style="opacity:0;">
<p>Java中的多态性（Polymorphism）是面向对象编程（OOP）中的一个核心概念，它允许同一个接口或方法在不同对象上具有不同的实现方式。多态性极大地提高了代码的灵活性和可扩展性，使得程序能够以一种统一的方式处理不同类型的对象。以下是对Java中多态性的详细解释，包括其定义、实现方式、优点以及具体示例。</p>
<h4>一、多态性的定义</h4>
<p>多态性是指允许不同类的对象对同一消息作出响应，即同一操作作用于不同的对象，可以有不同的解释，产生不同的执行结果。在Java中，多态性主要通过两种形式实现：编译时多态（也称为方法重载）和运行时多态（也称为方法重写）。</p>
<ol>
 <li>
  <p><strong>编译时多态（方法重载）</strong>：在同一个类中，允许存在多个同名方法，只要它们的参数类型不同、参数个数不同或参数顺序不同，即可构成重载。编译时多态在编译时就已经确定具体调用哪个方法。</p>
 </li>
 <li>
  <p><strong>运行时多态（方法重写）</strong>：子类可以重写父类中的方法，在运行时，JVM会根据对象的实际类型来调用相应的方法。这是多态性最重要的体现，也是实现接口和抽象类多态性的基础。</p>
 </li>
</ol>
<h4>二、多态性的实现方式</h4>
<h5>1. 方法重载（Method Overloading）</h5>
<p>方法重载是指在同一个类中，可以定义多个方法名相同但参数列表不同的方法。这些方法的参数类型、参数个数或参数顺序至少有一项不同。编译器会根据方法的参数列表在编译时确定调用哪个方法。</p>
<pre><code class="prism language-java">public class OverloadExample {

    public void display(int a) {

        System.out.println("Argument: " + a);
    }

    public void display(String a) {

        System.out.println("Argument: " + a);
    }

    public static void main(String[] args) {

        OverloadExample obj = new OverloadExample();
        obj.display(10);  // 输出: Argument: 10
        obj.display("Hello");  // 输出: Argument: Hello
    }
}
</code></pre>
<h5>2. 方法重写（Method Overriding）</h5>
<p>方法重写是指子类可以提供一个特定签名的方法，这个方法与父类中的某个方法具有相同的名称和参数列表（即方法名和参数类型、顺序、个数都相同），但可以有不同的实现。当子类对象被当作父类对象使用时，如果子类重写了父类的方法，则调用的是子类中的方法。</p>
<pre><code class="prism language-java">class Animal {

    public void makeSound() {

        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {

    @Override
    public void makeSound() {

        System.out.println("Dog barks");
    }
}

class Cat extends Animal {

    @Override
    public void makeSound() {

        System.out.println("Cat meows");
    }
}

public class TestPolymorphism {

    public static void main(String[] args) {

        Animal animal1 = new Dog();  // 使用基类引用指向子类对象
        Animal animal2 = new Cat();
        animal1.makeSound();  // 输出: Dog barks
        animal2.makeSound();  // 输出: Cat meows
    }
}
</code></pre>
<h4>三、多态性的优点</h4>
<ol>
 <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>
</ol>
<h4>总结</h4>
<p>多态性是Java面向对象编程中的一个核心概念，它允许不同的对象对同一消息作出不同的响应。在Java中，多态性主要通过方法重载和方法重写来实现。多态性提高了代码的灵活性、可扩展性和可维护性，使得程序能够以一种统一的方式处理不同类型的对象。在Java集合框架中，多态性得到了广泛的应用，它允许我们通过接口引用操作不同类型的集合，并通过迭代器遍历集合中的元素。这些特性使得Java集合框架成为处理对象集合的强大工具。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140680131</p>]]></description><guid isPermaLink="false">/archives/d8fe7c66-cc03-4d82-931c-c69d99b29366</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 02:37:36 GMT</pubDate></item><item><title><![CDATA[UIKit框架在iOS开发中的作用及核心类]]></title><link>https://wiki.dgfygk.com/archives/a05bdde2-6596-485d-9923-be40a21a173e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=UIKit%E6%A1%86%E6%9E%B6%E5%9C%A8iOS%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8%E5%8F%8A%E6%A0%B8%E5%BF%83%E7%B1%BB&amp;url=/archives/a05bdde2-6596-485d-9923-be40a21a173e" width="1" height="1" alt="" style="opacity:0;">
<p><strong>UIKit框架在iOS开发中的作用及核心类</strong></p>
<h4>一、UIKit框架的作用</h4>
<p>UIKit框架在iOS开发中扮演着至关重要的角色，它是构建和管理iOS应用程序用户界面的核心框架。UIKit提供了一系列丰富的界面元素、视图控制器、控件以及相关的功能类，使得开发者能够高效地创建出美观、流畅、交互性强的用户界面。具体来说，UIKit框架在iOS开发中的作用主要体现在以下几个方面：</p>
<ol>
 <li>
  <p><strong>提供界面元素</strong> ：
   <br>
   UIKit包含了大量的界面元素，如按钮（UIButton）、文本框（UITextField）、标签（UILabel）、滑块（UISlider）等。这些元素是构建用户界面的基本单位，开发者可以直接使用它们来布局和设计应用界面。</p>
 </li>
 <li>
  <p><strong>管理视图层级</strong> ：
   <br>
   UIKit允许开发者以层级结构的方式组织视图（UIView）。每个视图都可以包含子视图，通过添加、移除、调整子视图的位置和大小等操作，开发者可以灵活地管理视图的布局和显示。这种层级结构使得界面的布局更加灵活和动态。</p>
 </li>
 <li>
  <p><strong>处理用户输入</strong> ：
   <br>
   UIKit提供了丰富的用户输入处理机制，如触摸事件（touchesBegan、touchesMoved、touchesEnded等）、手势识别（UIGestureRecognizer的子类如UITapGestureRecognizer、UIPanGestureRecognizer等）等。通过这些机制，开发者可以捕获用户的操作行为，并据此执行相应的逻辑处理。</p>
 </li>
 <li>
  <p><strong>支持动画和过渡效果</strong> ：
   <br>
   UIKit提供了强大的动画和过渡效果支持，如UIView的animateWithDuration方法、UIViewController的transitioningDelegate属性等。开发者可以利用这些功能为应用界面添加平滑的动画和过渡效果，提升用户体验。</p>
 </li>
 <li>
  <p><strong>提供布局和约束系统</strong> ：
   <br>
   UIKit引入了Auto Layout布局系统（通过NSLayoutConstraint类实现）和Size Classes等概念，使得开发者能够更加灵活地应对不同尺寸和方向的设备屏幕。通过定义约束关系和使用Size Classes，开发者可以确保应用界面在不同设备和环境下都能保持良好的布局和显示效果。</p>
 </li>
 <li>
  <p><strong>集成设备特性</strong> ：
   <br>
   UIKit还集成了iOS设备的多种特性，如Touch ID、Face ID、Motion等。通过UIKit提供的相关接口和类，开发者可以轻松地在应用中使用这些特性，增强应用的安全性和用户体验。</p>
 </li>
</ol>
<h4>二、UIKit框架的核心类</h4>
<p>UIKit框架包含了众多核心类，这些类共同协作，为iOS应用程序的用户界面开发提供了全面的支持。以下是一些关键的UIKit核心类及其主要功能的介绍：</p>
<ol>
 <li>
  <p><strong>UIView</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UIView是所有视图控件的基础类，负责渲染和布局界面元素。每个UIView都可以包含子视图，形成视图层级结构。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：frame（视图的位置和大小）、bounds（视图的内部区域）、center（视图的中心点坐标）等。</p>
 </li>
 <li>
  <p><strong>主要方法</strong>：addSubview（添加子视图）、removeFromSuperview（移除子视图）、setNeedsDisplay（标记视图为需要重新绘制）等。</p>
 </li>
 <li>
  <p><strong>UIViewController</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UIViewController是管理视图和控制逻辑的主要类。每个UIViewController都对应一个视图（view属性），用于展示界面内容。</p>
 </li>
 <li>
  <p><strong>主要方法</strong>：viewDidLoad（视图加载完成后调用）、viewWillAppear（视图即将出现时调用）、viewDidAppear（视图已经出现后调用）等生命周期方法。</p>
 </li>
 <li>
  <p><strong>特性</strong>：支持导航（通过UINavigationController）、模态展示（通过presentViewController方法）等。</p>
 </li>
 <li>
  <p><strong>UIButton</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UIButton是按钮控件类，用于响应用户的点击事件。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：titleLabel（标题标签）、imageView（图像视图）、enabled（是否启用）等。</p>
 </li>
 <li>
  <p><strong>主要方法</strong>：addTarget（添加事件监听器）、setTitle（设置标题）、setImage（设置图像）等。</p>
 </li>
 <li>
  <p><strong>UILabel</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UILabel是标签控件类，用于显示文本信息。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：text（文本内容）、textColor（文本颜色）、font（字体）等。</p>
 </li>
 <li>
  <p><strong>特点</strong>：支持多行文本显示、文本对齐方式设置等。</p>
 </li>
 <li>
  <p><strong>UITextField</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UITextField是文本输入框控件类，允许用户输入文本信息。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：text（文本内容）、placeholder（占位符文本）、secureTextEntry（是否隐藏输入内容，常用于密码输入）等。</p>
 </li>
 <li>
  <p><strong>事件处理</strong>：通过代理（UITextFieldDelegate）处理文本变化、开始编辑、结束编辑等事件。</p>
 </li>
 <li>
  <p><strong>UISlider</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UISlider是滑块控件类，允许用户通过拖动滑块来改变值。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：value（当前值）、minimumValue（最小值）、maximumValue（最大值）等。</p>
 </li>
 <li>
  <p><strong>UISwitch</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UISwitch是开关控件类，用于表示一个二进制的选择状态（开/关）。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：isOn（当前状态，YES表示开，NO表示关）、onTintColor（开启时的颜色）、tintColor（默认颜色）等。</p>
 </li>
 <li>
  <p><strong>事件处理</strong>：通过添加target-action机制来处理开关状态变化的事件。</p>
 </li>
 <li>
  <p><strong>UITableView</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UITableView是表格视图控件类，用于展示列表形式的数据。它支持滚动、选择和编辑等功能。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：dataSource（数据源，遵循UITableViewDataSource协议的对象）、delegate（代理，遵循UITableViewDelegate协议的对象）、style（表格样式，如UITableViewStylePlain和UITableViewStyleGrouped）等。</p>
 </li>
 <li>
  <p><strong>数据展示</strong>：通过实现UITableViewDataSource协议中的方法（如numberOfSectionsInTableView、tableView:numberOfRowsInSection:、tableView:cellForRowAtIndexPath:）来提供数据和配置单元格。</p>
 </li>
 <li>
  <p><strong>交互处理</strong>：通过实现UITableViewDelegate协议中的方法（如tableView:didSelectRowAtIndexPath:）来处理用户交互事件。</p>
 </li>
 <li>
  <p><strong>UICollectionView</strong>：</p>
 </li>
 <li>
  <p><strong>作用</strong>：UICollectionView是集合视图控件类，它比UITableView更加灵活，可以展示网格布局、轮播图等多种布局形式的数据。</p>
 </li>
 <li>
  <p><strong>主要属性</strong>：dataSource（数据源，遵循UICollectionViewDataSource协议的对象）、delegate（代理，遵循UICollectionViewDelegate和UICollectionViewDelegateFlowLayout协议的对象）、collectionViewLayout（布局对象，负责控制单元格的布局）等。</p>
 </li>
 <li>
  <p><strong>数据展示</strong>：与UITableView类似，通过实现UICollectionViewDataSource协议中的方法来提供数据和配置单元格。</p>
 </li>
 <li>
  <p><strong>布局定制</strong>：通过自定义UICollectionViewLayout子类或使用现有的布局（如UICollectionViewFlowLayout）来定制单元格的布局。</p>
 </li>
 <li>
  <p><strong>UIAlertController</strong>：</p>
  <ul>
   <li><strong>作用</strong>：UIAlertController是警告控制器类，用于展示模态的警告框、操作表等。它替代了iOS 8之前版本的UIAlertView和UIActionSheet。</li>
   <li><strong>主要方法</strong>：alertControllerWithTitle:message:preferredStyle:（创建警告控制器）、addActions:（添加操作按钮）、presentViewController:animated:completion:（展示警告控制器）等。</li>
   <li><strong>样式</strong>：支持两种样式——UIAlertControllerStyleAlert（警告框）和UIAlertControllerStyleActionSheet（操作表）。</li>
  </ul>
 </li>
 <li>
  <p><strong>UINavigationController</strong>：</p>
  <ul>
   <li><strong>作用</strong>：UINavigationController是导航控制器类，用于管理视图控制器的层次结构，实现页面的导航功能。</li>
   <li><strong>主要属性</strong>：viewControllers（视图控制器数组，表示导航栈中的视图控制器）、topViewController（导航栈顶端的视图控制器）、navigationBar（导航栏）等。</li>
   <li><strong>导航操作</strong>：通过pushViewController:animated:和popViewControllerAnimated:方法实现页面的推入和弹出操作。</li>
  </ul>
 </li>
 <li>
  <p><strong>UITabBarController</strong>：</p>
  <ul>
   <li><strong>作用</strong>：UITabBarController是标签栏控制器类，用于管理多个视图控制器，并通过底部的标签栏来切换它们。</li>
   <li><strong>主要属性</strong>：viewControllers（视图控制器数组，表示标签栏中的视图控制器）、tabBar（标签栏）等。</li>
   <li><strong>切换视图</strong>：用户可以通过点击标签栏中的标签来切换视图控制器。</li>
  </ul>
 </li>
 <li>
  <p><strong>UIImageView</strong>：</p>
  <ul>
   <li><strong>作用</strong>：UIImageView是图像视图控件类，用于显示图像。</li>
   <li><strong>主要属性</strong>：image（显示的图像）、contentMode（内容模式，决定图像如何适应视图的大小）、animationImages（动画图像数组，用于实现图像动画）等。</li>
   <li><strong>图像加载</strong>：支持从文件、URL或UIImage对象加载图像。</li>
  </ul>
 </li>
 <li>
  <p><strong>UIScrollView</strong>：</p>
  <ul>
   <li><strong>作用</strong>：UIScrollView是滚动视图控件类，提供了滚动和缩放功能。许多其他视图控件（如UITableView和UICollectionView）都是基于UIScrollView构建的。</li>
   <li><strong>主要属性</strong>：contentSize（内容大小，决定滚动区域的大小）、contentOffset（内容偏移量，表示滚动视图当前显示内容的位置）、delegate（代理，遵循UIScrollViewDelegate协议的对象）等。</li>
   <li><strong>滚动和缩放</strong>：通过实现UIScrollViewDelegate协议中的方法来处理滚动和缩放事件。</li>
  </ul>
 </li>
</ol>
<p>这些UIKit核心类共同构成了iOS应用程序用户界面的基础。开发者通过组合和配置这些类，可以创建出功能丰富、界面美观的iOS应用程序。同时，随着iOS版本的更新，UIKit框架也在不断完善和扩展，为开发者提供更多强大的功能和更灵活的界面设计选项。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140711526</p>]]></description><guid isPermaLink="false">/archives/a05bdde2-6596-485d-9923-be40a21a173e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 02:31:03 GMT</pubDate></item><item><title><![CDATA[自然语言处理（NLP）]]></title><link>https://wiki.dgfygk.com/archives/0d9bea3c-8b29-463e-8a8c-cf91b328081a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86%EF%BC%88NLP%EF%BC%89&amp;url=/archives/0d9bea3c-8b29-463e-8a8c-cf91b328081a" width="1" height="1" alt="" style="opacity:0;">
<p>自然语言处理（NLP）是计算机科学与人工智能领域的一个重要研究方向，它致力于让计算机能够理解、分析、处理和生成人类语言。在NLP领域，存在着许多常见的任务，这些任务通常对应着不同的算法和技术。以下将详细列举几个NLP领域的常见任务及其对应的算法。</p>
<h4>一、常见任务概述</h4>
<p>在NLP中，常见的任务包括但不限于文本分类、分词、词性标注、命名实体识别、情感分析、机器翻译、问答系统和语音识别等。这些任务各有其独特的应用场景和挑战，同时也对应着不同的算法和技术。</p>
<h4>二、具体任务及对应算法</h4>
<h5>1. 文本分类</h5>
<p><strong>任务描述</strong>：文本分类是将一段文本分配到一个或多个预定义的类别中的任务。这是NLP中最基础的任务之一，广泛应用于垃圾邮件检测、情感分析、新闻分类等领域。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li><strong>基于规则的方法</strong>：通过手工编写规则，如正则表达式、关键词匹配和语法分析等，来实现文本分类。这种方法快速但需要大量人工参与，且对长文本分类效果不佳。</li>
 <li><strong>基于机器学习的方法</strong>：包括朴素贝叶斯、支持向量机（SVM）、神经网络（如卷积神经网络CNN、循环神经网络RNN及其变体LSTM、GRU）等算法。这些算法能够自动学习特征和规则，适用于大规模数据集，但需要大量训练数据。</li>
</ul>
<h5>2. 分词</h5>
<p><strong>任务描述</strong>：分词是将一段文本分割成单词或词组的过程。对于中文等没有自然分隔符的语言，分词尤为重要。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li><strong>基于词表的方法</strong>：如正向最大匹配（FMM）、逆向最大匹配（BMM）和双向最大匹配（BI）等算法。这些算法基于预定义的词典进行分词，简单高效但依赖于词典的完整性和准确性。</li>
 <li><strong>基于统计模型的方法</strong>：如基于N-gram语言模型的分词算法、隐马尔科夫模型（HMM）、条件随机场（CRF）和神经网络等算法。这些算法能够自动学习分词规则，适用于多语言、多领域和未知词语的处理，但需要大量训练数据。</li>
</ul>
<h5>3. 词性标注</h5>
<p><strong>任务描述</strong>：词性标注是为文本中的每个单词标注其词性的过程，如名词、动词、形容词等。词性标注是句法分析、信息抽取等任务的基础。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li><strong>基于字符串匹配的字典查找算法</strong>：通过查找预定义的词性词典来确定单词的词性。这种方法简单直接但无法处理未登录词（即词典中未收录的单词）。</li>
 <li><strong>基于统计的算法</strong>：如隐马尔科夫模型（HMM）、条件随机场（CRF）等。这些算法能够利用大规模语料库中的统计信息来标注词性，具有较好的泛化能力和适应性。</li>
</ul>
<h5>4. 命名实体识别（NER）</h5>
<p><strong>任务描述</strong>：命名实体识别是从文本中识别出具有特定意义的命名实体，如人名、地名、组织名、日期、时间等。NER是信息提取、问答系统等领域的重要任务。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li><strong>基于规则的方法</strong>：通过手工编写规则来识别命名实体，这些规则可以基于词性标注、词典匹配、语法结构等。这种方法在一些特定领域的文本中能够取得较好的效果但需要大量的人工设计和维护规则。</li>
 <li><strong>基于统计学习的方法</strong>：如隐马尔科夫模型（HMM）、条件随机场（CRF）、最大熵模型（MEM）以及深度学习算法（如RNN、LSTM）等。这些算法能够利用带有标注的语料库来学习命名实体的特征和规律并构建相应的模型进行识别。</li>
</ul>
<h5>5. 情感分析</h5>
<p><strong>任务描述</strong>：情感分析是从文本中识别出情感倾向的任务，通常包括正面、负面和中性三种情感。情感分析广泛应用于社交媒体分析、产品评价等领域。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li><strong>基于规则的方法</strong>：通过情感词典、情感句法和情感规则等技术来识别文本中的情感倾向。这种方法可以根据特定的情境和需求进行定制但需要大量的人工参与且对于未知的情感词汇处理效果不佳。</li>
 <li><strong>基于机器学习的方法</strong>：包括朴素贝叶斯、支持向量机（SVM）和神经网络等算法。这些算法能够自动学习特征和规则并适用于多语言、多领域和未知情感词汇的处理但需要大量的训练数据。</li>
</ul>
<h5>6. 机器翻译</h5>
<p><strong>任务描述</strong>：机器翻译是将一种语言的文本翻译成另一种语言的文本的任务。这是NLP中最具挑战性的任务之一也是人工智能领域的重要研究方向。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li>
  <p><strong>基于规则的方法</strong>：通过语法分析、词义翻译和语言生成等技术来实现机器翻译。这种方法可以根据特定的语言和需求进行定制但需要大量的人工参与且对于长句和复杂语言结构的处理效果不佳。</p>
 </li>
 <li>
  <p><strong>基于统计的方法</strong>：如短语对齐、翻译概率和语言模型等技术。这些方法利用大规模的双语语料库来学习翻译规则和概率模型，从而生成翻译结果。其中，IBM模型系列和基于最大熵的模型是这一方法的代表。然而，这些方法在处理语义和上下文信息方面仍有局限。</p>
 </li>
 <li>
  <p><strong>基于深度学习的方法</strong>：近年来，深度学习技术，特别是神经机器翻译（NMT），已成为机器翻译领域的主流方法。NMT利用神经网络（如RNN、LSTM、Transformer等）来自动学习源语言和目标语言之间的映射关系，并生成翻译结果。相比传统的统计方法，NMT能够更好地捕捉语言的语义和上下文信息，生成更自然、流畅的翻译文本。</p>
 </li>
</ul>
<h5>7. 问答系统</h5>
<p><strong>任务描述</strong>：问答系统旨在回答用户提出的问题，提供准确、简洁的答案。这包括理解问题的意图、从大量文本中检索相关信息，并生成合适的回答。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li><strong>信息检索（IR）方法</strong>：首先通过搜索引擎从大量文本中检索与问题相关的文档或段落，然后对这些文本进行摘要或抽取，以生成答案。这种方法依赖于高效的搜索引擎和准确的文本摘要技术。</li>
 <li><strong>基于知识库的方法</strong>：利用预先构建的知识库（如知识图谱、数据库等）来回答用户的问题。这种方法通常针对特定领域或问题类型进行优化，能够提供精确、结构化的答案。</li>
 <li><strong>深度学习方法</strong>：近年来，深度学习在问答系统中的应用越来越广泛。特别是基于序列到序列（Seq2Seq）的模型（如RNN、LSTM、Transformer等），能够直接将问题转换为答案。此外，注意力机制、预训练语言模型（如BERT、GPT等）的引入，进一步提高了问答系统的性能和效果。</li>
</ul>
<h5>8. 语音识别</h5>
<p>虽然语音识别主要属于语音处理领域，但它与NLP密切相关，因为语音识别的最终目标是将语音转换为文本，以便进行后续的自然语言处理。</p>
<p><strong>对应算法</strong>：</p>
<ul>
 <li><strong>基于动态时间规整（DTW）的方法</strong>：DTW是一种用于测量两个时间序列之间相似性的算法。在语音识别中，DTW可以用来对齐语音信号和模板信号，从而识别出语音中的单词或短语。然而，DTW对于噪声和口音的鲁棒性较差。</li>
 <li><strong>基于隐马尔科夫模型（HMM）的方法</strong>：HMM在语音识别中得到了广泛应用。它将语音信号视为由一系列状态组成的序列，并通过训练数据来学习状态之间的转移概率和每个状态对应的观测概率。然后，利用Viterbi算法等解码算法来找到最可能的状态序列，从而识别出语音中的文本内容。</li>
 <li><strong>基于深度学习的方法</strong>：随着深度学习技术的发展，特别是循环神经网络（RNN）及其变体（如LSTM、GRU）和卷积神经网络（CNN）在语音识别中的应用，语音识别的性能得到了显著提升。此外，端到端的语音识别模型（如CTC、Attention-based Models、Transformer等）的提出，进一步简化了语音识别系统的结构，提高了识别准确率。</li>
</ul>
<h4>总结</h4>
<p>自然语言处理（NLP）领域涵盖了众多复杂而有趣的任务，每个任务都对应着不同的算法和技术。从基础的文本分类、分词、词性标注，到高级的命名实体识别、情感分析、机器翻译、问答系统和语音识别等，NLP技术正在不断发展和完善，为人类带来更加智能、便捷的信息处理体验。未来，随着深度学习等技术的不断进步和应用场景的持续拓展，NLP领域将迎来更加广阔的发展前景。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140652428</p>]]></description><guid isPermaLink="false">/archives/0d9bea3c-8b29-463e-8a8c-cf91b328081a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 02:05:01 GMT</pubDate></item><item><title><![CDATA[【人工智能艺术革命：科技灵感与艺术创新的交融纪元】]]></title><link>https://wiki.dgfygk.com/archives/54254544-a94e-400f-a256-7234e7e5d8eb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E8%89%BA%E6%9C%AF%E9%9D%A9%E5%91%BD%EF%BC%9A%E7%A7%91%E6%8A%80%E7%81%B5%E6%84%9F%E4%B8%8E%E8%89%BA%E6%9C%AF%E5%88%9B%E6%96%B0%E7%9A%84%E4%BA%A4%E8%9E%8D%E7%BA%AA%E5%85%83%E3%80%91&amp;url=/archives/54254544-a94e-400f-a256-7234e7e5d8eb" width="1" height="1" alt="" style="opacity:0;">
<p>【人工智能艺术革命：科技灵感与艺术创新的交融纪元】</p>
<p>在21世纪的科技浪潮中，人工智能（AI）作为一股不可忽视的力量，正以前所未有的速度渗透并重塑着我们的生活、工作乃至艺术创作领域。其中，AI绘画作为科技与艺术深度融合的产物，不仅挑战了传统艺术的边界，更开启了一个充满无限想象与可能的新时代。本文将从AI绘画的定义与发展历程、技术原理、对艺术创作的影响、面临的挑战与机遇以及未来展望等多个维度，深入探讨这一新兴艺术形式的独特魅力与深远意义。</p>
<h4>一、AI绘画的定义与发展历程</h4>
<h5>定义</h5>
<p>AI绘画，简而言之，是指利用人工智能技术辅助或完全自动生成艺术作品的过程。这一过程可能涉及机器学习、深度学习、计算机视觉、自然语言处理等多种技术，使得计算机能够模仿甚至超越人类的创作能力，创作出具有独特风格和情感的画作。</p>
<h5>发展历程</h5>
<p>AI绘画的萌芽可以追溯到计算机图形学的早期阶段，但真正意义上的快速发展始于近十年。随着深度学习技术的突破，尤其是卷积神经网络（CNN）和生成对抗网络（GAN）等模型的兴起，AI在图像生成领域取得了显著进展。</p>
<ul>
 <li><strong>初期探索</strong>：早期，AI绘画多集中于简单的图形绘制和风格迁移，如利用算法生成几何图案、纹理等。</li>
 <li><strong>技术突破</strong>：随着GAN等技术的出现，AI开始能够生成更加复杂、逼真的图像，甚至能够模仿特定艺术家的风格进行创作。</li>
 <li><strong>商业化应用</strong>：近年来，AI绘画技术逐渐走向成熟，并开始在艺术市场、设计行业、娱乐产业等多个领域得到应用，如数字艺术创作、广告设计、电影特效等。</li>
</ul>
<h4>二、AI绘画的技术原理</h4>
<p>AI绘画的核心在于深度学习模型，尤其是GAN模型的应用。GAN由两部分组成：生成器（Generator）和判别器（Discriminator）。生成器的任务是生成尽可能真实的图像，而判别器的任务则是区分输入的图像是由生成器生成的假图像还是真实的图像。两者在训练过程中相互竞争、相互学习，最终使得生成器能够生成难以被判别器区分的逼真图像。</p>
<p>此外，还有一些其他技术也被广泛应用于AI绘画中，如变分自编码器（VAE）、风格迁移（Style Transfer）等。风格迁移技术允许将一幅画的风格应用到另一幅画的内容上，创造出全新的艺术效果。这些技术的结合使用，为AI绘画提供了丰富的创作手段和无限的可能性。</p>
<h4>三、AI绘画对艺术创作的影响</h4>
<h5>1. 拓宽创作边界</h5>
<p>AI绘画打破了传统艺术创作在材料、技法、想象力等方面的限制，使得艺术创作不再局限于人类的生理和心理能力。艺术家可以借助AI技术，探索前所未有的艺术形式和表现手法，创造出超越人类想象的作品。</p>
<h5>2. 提高创作效率</h5>
<p>AI绘画能够自动化完成大量重复性的工作，如草图绘制、色彩填充等，从而极大地提高了艺术家的创作效率。艺术家可以将更多精力投入到创意构思和作品优化上，实现更高质量的艺术创作。</p>
<h5>3. 促进艺术创新</h5>
<p>AI绘画为艺术家提供了全新的创作工具和灵感来源。艺术家可以通过与AI的互动，发现新的艺术元素和表现形式，进而推动艺术创新和发展。同时，AI绘画的生成过程也充满了随机性和不可预测性，这为艺术家带来了更多的惊喜和灵感。</p>
<h5>4. 引发艺术观念变革</h5>
<p>AI绘画的出现引发了人们对艺术本质和价值的重新思考。它挑战了传统艺术中"作者中心论"的观念，强调了艺术创作过程中的技术因素和观众参与的重要性。同时，AI绘画也促使人们更加关注艺术作品的创新性和独特性，而非仅仅关注其背后的作者身份或创作过程。</p>
<h4>四、AI绘画面临的挑战与机遇</h4>
<h5>挑战</h5>
<ol>
 <li><strong>版权与伦理问题</strong>：AI绘画作品的版权归属、创作过程中的伦理问题（如数据隐私、算法偏见等）亟待解决。</li>
 <li><strong>技术与艺术融合的难度</strong>：如何将AI技术与艺术创作深度融合，实现技术与艺术的和谐共生，是当前面临的一大挑战。</li>
 <li><strong>艺术价值的认可</strong>：尽管AI绘画在技术上取得了显著进展，但其艺术价值仍面临一定的争议和质疑。</li>
</ol>
<h5>机遇</h5>
<ol>
 <li><strong>推动艺术产业发展</strong>：AI绘画技术的普及和应用将促进艺术产业的数字化、智能化转型，为艺术市场带来新的增长点。</li>
 <li><strong>促进艺术教育普及</strong>：AI绘画技术可以降低艺术创作的门槛，使更多人有机会接触和了解艺术，从而推动艺术教育的普及和发展。</li>
 <li><strong>拓展艺术表现形式</strong>：AI绘画技术为艺术家提供了更多的创作手段和表现形式，有助于推动艺术形式和风格的多样化和创新。</li>
</ol>
<h4>五、未来展望</h4>
<p>随着科技的不断进步和艺术的不断发展，AI绘画的未来充满了无限可能。我们可以预见，在未来的日子里，AI绘画技术将更加成熟和完善，为艺术创作带来更多的惊喜和突破。</p>
<ul>
 <li>
  <p><strong>技术融合与创新</strong>：AI绘画技术将与其他前沿技术（如虚拟现实、增强现实、区块链等）深度融合，推动艺术表现形式的进一步创新。艺术家将能够利用这些技术创造出沉浸式的艺术体验，让观众在虚拟与现实之间自由穿梭，感受前所未有的艺术魅力。</p>
 </li>
 <li>
  <p><strong>个性化与定制化</strong>：随着AI算法的进一步优化，AI绘画将更加注重个性化和定制化服务。用户可以通过简单的交互，如选择主题、风格、色彩偏好等，让AI生成符合自己独特品味的艺术作品。这种个性化的创作方式将极大地满足人们日益增长的审美需求，推动艺术市场的多元化发展。</p>
 </li>
 <li>
  <p><strong>艺术与科技的深度融合</strong>：未来，AI绘画不仅仅是技术的堆砌，而是将科技与艺术深度融合，形成新的艺术语言和表达方式。艺术家将不再局限于传统的创作工具和材料，而是将AI视为一种全新的创作伙伴，共同探索艺术的无限可能。这种深度融合将促进艺术的跨界合作，如艺术与科技、艺术与医学、艺术与环保等领域的交叉融合，产生更多具有社会意义和时代价值的艺术作品。</p>
 </li>
 <li>
  <p><strong>艺术创作的民主化</strong>：AI绘画的普及将降低艺术创作的门槛，使得更多的人能够参与到艺术创作中来。无论是专业艺术家还是普通爱好者，都可以通过AI技术表达自己的创意和想法，实现艺术创作的民主化。这种趋势将促进艺术创作的多样性和包容性，让艺术成为更加贴近人们生活、反映社会现实的文化形式。</p>
 </li>
 <li>
  <p><strong>艺术与科技的伦理对话</strong>：随着AI绘画的深入发展，其背后的伦理问题也将日益凸显。如何确保AI绘画作品的原创性、避免算法偏见、保护个人隐私等问题将成为未来研究和讨论的重点。同时，艺术界和科技界也将开展更多的对话和合作，共同探索如何在保证技术进步的同时，维护艺术的独立性和人文价值。这种伦理对话将有助于构建更加健康、可持续的艺术与科技生态环境。</p>
 </li>
 <li>
  <p><strong>艺术市场的变革</strong>：AI绘画的兴起将对传统艺术市场产生深远影响。一方面，AI绘画作品的出现将丰富艺术市场的供给，为收藏家和投资者提供更多选择；另一方面，AI绘画作品的定价、真伪鉴定等问题也将成为市场关注的焦点。未来，随着区块链等技术的应用，AI绘画作品的版权保护、交易透明度和可信度将得到大幅提升，从而推动艺术市场的进一步规范化和健康发展。</p>
 </li>
</ul>
<p>总之，AI绘画作为科技与艺术交汇的新时代产物，正以其独特的魅力和无限的可能性引领着艺术创作的未来。在未来的日子里，我们有理由相信，AI绘画将在技术融合、个性化定制、艺术与科技深度融合、创作民主化、伦理对话以及艺术市场变革等方面持续发展壮大，为人类社会创造更加丰富多彩的艺术世界。同时，我们也应关注并思考其背后的伦理和社会问题，确保科技进步与人文价值的和谐共生。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140731311</p>]]></description><guid isPermaLink="false">/archives/54254544-a94e-400f-a256-7234e7e5d8eb</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 02:04:09 GMT</pubDate></item><item><title><![CDATA[Cocoa Touch框架是什么？]]></title><link>https://wiki.dgfygk.com/archives/21ffcde0-bf2a-4ce9-98e0-003b31d0a2f2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Cocoa%20Touch%E6%A1%86%E6%9E%B6%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;url=/archives/21ffcde0-bf2a-4ce9-98e0-003b31d0a2f2" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Cocoa Touch框架是什么？</strong></p>
<p>Cocoa Touch框架是苹果公司为iOS应用程序开发提供的一套完整的框架，它基于Cocoa框架，并专为触控设备如iPhone、iPad等设计。Cocoa Touch不仅包含了构建图形用户界面（GUI）所需的工具和类，还提供了访问设备硬件、处理用户输入、管理应用程序生命周期等功能。它使得开发者能够高效地创建出丰富、交互性强的iOS应用程序。</p>
<h4>Cocoa Touch框架的主要特点</h4>
<ol>
 <li><strong>触控优化</strong>：Cocoa Touch框架特别针对触控设备进行了优化，提供了丰富的触控事件处理机制，使得开发者能够轻松实现各种复杂的触控交互效果。</li>
 <li><strong>MVC架构</strong>：Cocoa Touch框架遵循MVC（Model-View-Controller）设计模式，将应用程序的数据、界面和控制逻辑分离，提高了代码的可读性和可维护性。</li>
 <li><strong>丰富的UI组件</strong>：框架中包含了大量的UI组件，如按钮、文本框、滑块、表格视图等，开发者可以直接使用这些组件来构建应用程序的用户界面。</li>
 <li><strong>设备访问API</strong>：Cocoa Touch提供了丰富的设备访问API，使得开发者能够访问设备的摄像头、麦克风、GPS等硬件资源，实现更丰富的功能。</li>
 <li><strong>强大的数据处理能力</strong>：通过Core Data等框架，Cocoa Touch提供了强大的数据管理能力，使得开发者能够轻松实现数据的存储、查询和更新。</li>
</ol>
<h4>Cocoa Touch框架包含的主要组件</h4>
<p>Cocoa Touch框架包含了多个核心组件，这些组件共同协作，为iOS应用程序的开发提供了全面的支持。以下是Cocoa Touch框架中一些主要组件的详细介绍：</p>
<h5>1. UIKit</h5>
<ul>
 <li><strong>概述</strong>：UIKit是Cocoa Touch框架中用于构建iOS应用程序用户界面的核心框架。它提供了一系列用于创建和管理用户界面的类，包括窗口、视图、控件等。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>提供界面元素：UIKit包含了各种界面元素，如按钮（UIButton）、文本框（UITextField）、滑块（UISlider）等，开发者可以直接使用这些元素来构建用户界面。</li>
 <li>管理视图层级：UIKit允许开发者以层级结构的方式组织视图，通过添加子视图、设置约束等方式来管理视图的布局和显示。</li>
 <li>处理用户输入：UIKit提供了丰富的用户输入处理机制，如触摸事件、手势识别等，使得开发者能够轻松实现各种复杂的用户交互效果。</li>
 <li>动画和过渡效果：UIKit还提供了动画和过渡效果的支持，使得开发者能够创建出更加生动、流畅的用户界面。</li>
</ul>
<h5>2. Foundation</h5>
<ul>
 <li><strong>概述</strong>：Foundation是Cocoa Touch框架中提供基本功能的框架，它包含了大量用于iOS应用程序开发的基础类和数据结构。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>字符串处理：Foundation提供了丰富的字符串处理功能，如字符串的拼接、分割、查找、替换等。</li>
 <li>集合类：Foundation包含了多种集合类，如数组（NSArray/NSMutableArray）、字典（NSDictionary/NSMutableDictionary）等，用于存储和管理数据。</li>
 <li>日期和时间处理：Foundation提供了日期和时间处理的功能，使得开发者能够轻松地获取当前日期和时间，并进行日期和时间的计算。</li>
 <li>文件操作：Foundation还提供了文件操作的功能，包括文件的创建、读取、写入、删除等。</li>
</ul>
<h5>3. Core Animation</h5>
<ul>
 <li><strong>概述</strong>：Core Animation是Cocoa Touch框架中用于创建动画效果的框架。它提供了丰富的动画效果，使得开发者能够轻松地为应用程序添加各种生动的动画效果。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>基本动画：Core Animation支持基本动画的创建，如位置、大小、透明度等属性的变化。</li>
 <li>关键帧动画：开发者可以定义动画的关键帧，实现更加复杂的动画效果。</li>
 <li>过渡动画：Core Animation还提供了过渡动画的支持，使得开发者能够在视图之间实现平滑的过渡效果。</li>
</ul>
<h5>4. Core Data</h5>
<ul>
 <li><strong>概述</strong>：Core Data是Cocoa Touch框架中提供数据存储和管理功能的框架。它采用对象关系映射（ORM）技术，将对象模型与数据库模型映射起来，简化了数据的存储和查询过程。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>数据模型定义：开发者可以使用Core Data的数据模型编辑器来定义数据模型，包括实体、属性、关系等。</li>
 <li>数据存储：Core Data支持多种数据存储方式，包括SQLite数据库、内存存储等。</li>
 <li>数据查询：Core Data提供了强大的数据查询功能，使得开发者能够轻松地查询和更新数据。</li>
</ul>
<h5>5. Core Graphics</h5>
<ul>
 <li><strong>概述</strong>：Core Graphics是Cocoa Touch框架中用于绘制2D图形的框架。它提供了一套丰富的绘图API，使得开发者能够在iOS应用程序中绘制各种复杂的图形和图像。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>绘图操作：Core Graphics支持各种绘图操作，如绘制线条、矩形、圆形等。</li>
 <li>路径绘制：开发者可以使用Core Graphics的路径绘制功能来创建复杂的图形路径。</li>
 <li><strong>颜色管理</strong>：Core Graphics 提供了对颜色的全面支持，包括 RGB、CMYK、灰度等多种颜色模式，允许开发者在绘图时精确控制颜色。</li>
 <li><strong>图像处理</strong>：除了绘图外，Core Graphics 还支持图像处理功能，如图像的缩放、旋转、裁剪等，这些功能对于在应用程序中处理和显示图像非常有用。</li>
</ul>
<h5>6. Core Text</h5>
<ul>
 <li><strong>概述</strong>：Core Text 是 Cocoa Touch 框架中用于高级文本布局和渲染的框架。它提供了比 UIKit 文本系统更强大、更灵活的文本处理功能，适用于需要复杂文本布局和渲染的应用程序。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>文本布局：Core Text 允许开发者精确地控制文本的布局，包括字体、字号、行距、段落间距等。</li>
 <li>文本渲染：使用 Core Text，开发者可以实现高质量的文本渲染，包括文本的平滑渲染、抗锯齿处理等。</li>
 <li>文本样式：Core Text 支持多种文本样式，如粗体、斜体、下划线等，以及复杂的文本效果，如阴影、渐变等。</li>
</ul>
<h5>7. Core Location</h5>
<ul>
 <li><strong>概述</strong>：Core Location 是 Cocoa Touch 框架中用于获取设备位置信息的框架。它利用设备的 GPS 和其他位置感知技术（如 Wi-Fi 和蜂窝网络）来确定设备的当前位置。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>位置服务：Core Location 允许应用程序请求访问用户的位置信息，并实时更新位置数据。</li>
 <li>地图集成：Core Location 可以与 MapKit 框架集成，以便在地图上显示用户的位置。</li>
 <li>精度控制：开发者可以设置位置更新的精度要求，以平衡位置数据的准确性和电池消耗。</li>
</ul>
<h5>8. MapKit</h5>
<ul>
 <li><strong>概述</strong>：MapKit 是 Cocoa Touch 框架中用于在 iOS 应用程序中嵌入地图的框架。它提供了丰富的接口来展示和操作地图，包括地图的缩放、平移、标记、路线规划等功能。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>地图显示：MapKit 可以显示多种类型的地图，包括标准地图、卫星地图和混合地图。</li>
 <li>地图交互：用户可以通过触摸屏幕与地图进行交互，如缩放、平移等。</li>
 <li>地图标注：开发者可以在地图上添加自定义的标注（如标记、覆盖物等），以显示特定的位置信息。</li>
 <li>路线规划：MapKit 支持路线规划功能，可以根据用户指定的起点和终点计算并显示最佳路线。</li>
</ul>
<h5>9. AV Foundation</h5>
<ul>
 <li><strong>概述</strong>：AV Foundation 是 Cocoa Touch 框架中用于处理音频和视频数据的框架。它提供了一套丰富的接口来录制、播放、编辑和处理音频和视频文件。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>媒体播放：AV Foundation 支持多种格式的音频和视频文件的播放，包括本地文件和流媒体。</li>
 <li>媒体录制：开发者可以使用 AV Foundation 来录制音频和视频数据，并控制录制过程中的各种参数。</li>
 <li>媒体编辑：AV Foundation 提供了一些基本的媒体编辑功能，如裁剪、合并等，但更复杂的编辑任务可能需要使用其他框架或工具。</li>
 <li>实时处理：AV Foundation 支持对音频和视频数据的实时处理，如添加滤镜、调整音量等。</li>
</ul>
<h5>10. Core Bluetooth</h5>
<ul>
 <li><strong>概述</strong>：Core Bluetooth 是 Cocoa Touch 框架中用于与蓝牙低功耗（BLE）设备进行通信的框架。它允许 iOS 设备作为中心设备（Central）或外设（Peripheral）与其他蓝牙设备进行连接和数据交换。</li>
 <li><strong>主要功能</strong> ：</li>
 <li>设备扫描：Core Bluetooth 可以扫描附近的蓝牙设备，并获取它们的详细信息。</li>
 <li>设备连接：开发者可以建立与蓝牙设备的连接，并进行数据的发送和接收。</li>
 <li>数据处理：Core Bluetooth 提供了处理蓝牙设备发送的原始数据的接口，允许开发者根据需要进行数据的解析和处理。</li>
</ul>
<p>这些只是 Cocoa Touch 框架中一部分主要组件的简要介绍。实际上，Cocoa Touch 框架还包含了许多其他组件和框架，它们共同构成了 iOS 应用程序开发的基础。每个组件都有其特定的功能和用途，开发者可以根据需要选择适合的组件来构建自己的应用程序。通过深入学习 Cocoa Touch 框架，开发者可以充分利用 iOS 设备的功能和特性，创建出功能丰富、性能卓越的应用程序。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140711240</p>]]></description><guid isPermaLink="false">/archives/21ffcde0-bf2a-4ce9-98e0-003b31d0a2f2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 02:03:51 GMT</pubDate></item><item><title><![CDATA[Python中enumerate()函数]]></title><link>https://wiki.dgfygk.com/archives/cd42de30-3b67-47f4-b99e-9319cacea345</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Python%E4%B8%ADenumerate%28%29%E5%87%BD%E6%95%B0&amp;url=/archives/cd42de30-3b67-47f4-b99e-9319cacea345" width="1" height="1" alt="" style="opacity:0;">
<h2>Python中enumerate()函数的作用是什么？</h2>
<p><code>enumerate()</code> 函数在Python中是一个非常有用的内置函数，它用于将一个可遍历的数据对象（如列表、元组或字符串）组合为一个索引序列，同时列出数据和数据下标，一般用在 for 循环当中。使用 <code>enumerate()</code> 可以使你在循环中获取每个元素的同时，也能获取到该元素对应的索引（或位置）。</p>
<p><code>enumerate()</code> 函数的基本语法如下：</p>
<pre><code class="prism language-python">enumerate(iterable, start=0)
</code></pre>
<ul>
 <li><code>iterable</code>：一个可遍历的对象，如列表、元组或字符串。</li>
 <li><code>start</code>：下标起始位置，默认为0。</li>
</ul>
<p><code>enumerate()</code> 函数返回一个枚举对象，该对象是一个迭代器，它生成由 <code>(index, value)</code> 组成的元组，其中 <code>index</code> 是从 <code>start</code> 开始的索引（默认为0），<code>value</code> 是从可迭代对象中取得的元素。</p>
<h4>示例</h4>
<p>下面是一个使用 <code>enumerate()</code> 函数的简单示例：</p>
<pre><code class="prism language-python"># 定义一个列表
seasons = ['Spring', 'Summer', 'Autumn', 'Winter']

# 使用 enumerate() 遍历列表
for index, season in enumerate(seasons):
    print(f"Index: {
     index}, Season: {
     season}")

# 如果想要从1开始计数
for index, season in enumerate(seasons, start=1):
    print(f"Index: {
     index}, Season: {
     season}")
</code></pre>
<p>输出将会是：</p>
<pre><code>Index: 0, Season: Spring
Index: 1, Season: Summer
Index: 2, Season: Autumn
Index: 3, Season: Winter

Index: 1, Season: Spring
Index: 2, Season: Summer
Index: 3, Season: Autumn
Index: 4, Season: Winter
</code></pre>
<p>在这个例子中，<code>enumerate()</code> 函数帮助我们在遍历列表 <code>seasons</code> 的同时，也获取到了每个季节名称的索引。通过改变 <code>start</code> 参数的值，我们还可以控制索引的起始位置。</p>
<p><code>enumerate</code> 和 <code>for</code> 循环在Python中都是用来遍历序列（如列表、元组、字符串等）的重要工具，但它们在功能和用法上存在一些区别。以下是对两者区别的详细解析：</p>
<h2>enumerate和for循环有什么区别？</h2>
<h4>1. 功能差异</h4>
<ul>
 <li>
  <p><strong>for循环</strong>：</p>
 </li>
 <li>
  <p>主要用于遍历序列中的元素。</p>
 </li>
 <li>
  <p>在遍历过程中，默认不提供元素的索引（位置信息），仅用于迭代元素本身。</p>
 </li>
 <li>
  <p>适用于只需遍历元素本身，不需要索引信息的简单场景。</p>
 </li>
 <li>
  <p><strong>enumerate函数</strong>：</p>
 </li>
 <li>
  <p>在遍历序列的同时，返回元素的索引和值。</p>
 </li>
 <li>
  <p>它返回一个枚举对象，该对象在迭代时生成包含索引和值的元组。</p>
 </li>
 <li>
  <p>适用于需要同时访问元素和它们索引的场景，如在进行查找、替换或计数等操作时非常有用。</p>
 </li>
</ul>
<h4>2. 语法差异</h4>
<ul>
 <li>
  <p><strong>for循环</strong>：</p>
  <pre><code class="prism language-python">for element in sequence:
  # 对每个元素执行操作
</code></pre>
 </li>
 <li>
  <p><strong>enumerate函数</strong>：</p>
  <pre><code class="prism language-python">for index, element in enumerate(sequence, start=0):
  # start是可选参数，用于指定索引的起始值，默认为0
  # 对每个索引和元素执行操作
</code></pre>
 </li>
</ul>
<h4>3. 使用场景</h4>
<ul>
 <li>当只关心元素本身，而不需要索引信息时，使用<code>for</code>循环更为直接和简单。</li>
 <li>当需要同时访问元素的索引和值时，<code>enumerate</code>函数提供了更便捷的方式。</li>
</ul>
<h4>4. 示例对比</h4>
<h5>for循环示例</h5>
<pre><code class="prism language-python">fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)
</code></pre>
<p>这个例子中，<code>for</code>循环遍历了<code>fruits</code>列表中的每个元素，并打印出来，但没有打印元素的索引。</p>
<h5>enumerate函数示例</h5>
<pre><code class="prism language-python">fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
    print(f"Index: {
     index}, Fruit: {
     fruit}")
</code></pre>
<p>这个例子中，<code>enumerate</code>函数同时返回了每个水果的索引和元素本身，并将它们一起打印出来。</p>
<h4>5. 注意事项</h4>
<ul>
 <li>在使用<code>enumerate</code>时，可以通过设置<code>start</code>参数来指定索引的起始值，这在某些特定场景下非常有用。</li>
 <li>当处理大型数据集时，如果不需要索引信息，使用<code>for</code>循环可能更节省内存和计算资源。</li>
</ul>
<p>综上所述，<code>enumerate</code>和<code>for</code>循环在Python中各有其独特的用途和优势，选择使用哪一个取决于具体的需求和场景。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140637407</p>]]></description><guid isPermaLink="false">/archives/cd42de30-3b67-47f4-b99e-9319cacea345</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Fri, 27 Sep 2024 02:03:34 GMT</pubDate></item><item><title><![CDATA[Python中的global关键字是如何工作的？]]></title><link>https://wiki.dgfygk.com/archives/feea2501-01da-493a-ac5e-97d947bcf017</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Python%E4%B8%AD%E7%9A%84global%E5%85%B3%E9%94%AE%E5%AD%97%E6%98%AF%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C%E7%9A%84%EF%BC%9F&amp;url=/archives/feea2501-01da-493a-ac5e-97d947bcf017" width="1" height="1" alt="" style="opacity:0;">
<p>在Python中，<code>global</code>关键字扮演着特殊而重要的角色，它主要用于在函数内部声明全局变量。理解<code>global</code>关键字的工作原理，首先需要明确Python中变量作用域的概念，以及为什么需要<code>global</code>关键字。下面，我将详细解释<code>global</code>关键字的工作机制，包括其作用、使用场景、注意事项，以及它在Python编程中的实际应用。</p>
<h4>变量作用域</h4>
<p>在Python中，变量作用域决定了变量的可见性和生命周期。Python中的变量作用域主要分为以下几种：</p>
<ol>
 <li><strong>局部作用域</strong>：变量在函数内部定义，只能在该函数内部访问。</li>
 <li><strong>全局作用域</strong>：变量在函数外部定义，可以在整个模块（Python文件）中访问。</li>
 <li><strong>内置作用域</strong> ：Python内置的函数和变量，如<code>len()</code>、<code>print()</code>和<code>sys</code>等，在整个Python环境中都是可见的。</li>
</ol>
<h4>为什么需要<code>global</code>关键字</h4>
<p>在默认情况下，当你在函数内部尝试修改一个变量时，Python会假设这个变量是局部变量，除非它已经被明确声明为全局变量或作为参数传递给函数。如果函数内部尝试修改一个全局变量但没有使用<code>global</code>关键字声明，Python会抛出一个<code>UnboundLocalError</code>异常，因为Python认为你正在尝试访问一个未定义的局部变量。</p>
<p>这就是<code>global</code>关键字的作用所在：它告诉Python解释器，在函数内部使用的变量是全局作用域中的变量，而不是局部作用域中的新变量。</p>
<h4><code>global</code>关键字的工作机制</h4>
<p>当在函数内部使用<code>global</code>关键字声明一个变量时，Python解释器会查找该变量名是否在全局作用域中。如果找到了，该变量就在函数内部被当作全局变量来处理；如果没有找到，Python会在全局作用域中创建一个新的变量（如果函数内部有对该变量的赋值操作）。</p>
<h5>语法</h5>
<pre><code class="prism language-python">global variable_name1, variable_name2, ...
</code></pre>
<p>这里，<code>variable_name1</code>, <code>variable_name2</code>, … 是要在函数内部声明为全局变量的变量名。</p>
<h5>示例</h5>
<pre><code class="prism language-python">x = "global"

def func():
    global x
    x = "local"
    print("Inside func:", x)

print("Before func:", x)
func()
print("After func:", x)
</code></pre>
<p>输出：</p>
<pre><code>Before func: global
Inside func: local
After func: local
</code></pre>
<p>在这个例子中，<code>x</code>原本是一个全局变量，其值为<code>"global"</code>。在<code>func</code>函数内部，我们使用<code>global</code>关键字声明了<code>x</code>，并改变了它的值。由于<code>x</code>被声明为全局变量，因此这个改变影响到了全局作用域中的<code>x</code>，所以<code>After func:</code>之后的输出是<code>"local"</code>。</p>
<h4>使用场景</h4>
<p>尽管<code>global</code>关键字在某些情况下很有用，但过度使用全局变量通常被认为是不好的编程实践。全局变量使得函数的可测试性、可维护性和可重用性降低，因为它们允许函数之间不经过参数传递而直接通信。然而，在某些特定场景下，全局变量仍然是必要的或方便的：</p>
<ol>
 <li><strong>配置变量</strong>：应用程序的配置参数，如数据库连接信息、日志级别等，经常作为全局变量存储。</li>
 <li><strong>状态管理</strong>：在某些需要跨多个函数或模块共享状态的应用程序中，全局变量可能是必要的。</li>
 <li><strong>单例模式</strong>：在某些实现单例模式的设计中，全局变量用于存储类的唯一实例。</li>
</ol>
<h4>注意事项</h4>
<ol>
 <li><strong>可读性</strong>：使用全局变量时，应确保代码的可读性。全局变量的使用应该明确且易于理解。</li>
 <li><strong>可维护性</strong>：尽量避免在大型项目或复杂系统中过度使用全局变量，因为它们可能使代码难以维护。</li>
 <li><strong>命名冲突</strong>：注意全局变量的命名，避免与内置函数或变量名冲突。</li>
 <li><strong>封装</strong>：考虑使用类或模块来封装全局变量，以减少它们对外部代码的可见性和影响。</li>
 <li><strong>线程安全</strong>：在多线程程序中，对全局变量的访问需要特别小心，以避免竞态条件和数据不一致。</li>
</ol>
<h4>结论</h4>
<p><code>global</code>关键字在Python中提供了一种在函数内部访问和修改全局变量的机制。然而，它的使用应该谨慎，以避免引入难以调试和维护的代码。在可能的情况下，应该优先考虑使用参数传递、返回值、类或模块封装等更安全和更灵活的方法来共享数据。通过合理使用<code>global</code>关键字，并结合良好的编程实践，可以编写出既高效又易于维护的Python代码。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140637121</p>]]></description><guid isPermaLink="false">/archives/feea2501-01da-493a-ac5e-97d947bcf017</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Fri, 27 Sep 2024 02:03:19 GMT</pubDate></item><item><title><![CDATA[Stable Diffusion之最全详解图解]]></title><link>https://wiki.dgfygk.com/archives/53d49622-658d-4667-81c1-d5c99337b1a2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Stable%20Diffusion%E4%B9%8B%E6%9C%80%E5%85%A8%E8%AF%A6%E8%A7%A3%E5%9B%BE%E8%A7%A3&amp;url=/archives/53d49622-658d-4667-81c1-d5c99337b1a2" width="1" height="1" alt="" style="opacity:0;">
<p>Stable Diffusion，作为当前AI图像生成领域的里程碑式模型，以其高性能、高质量的图像生成能力赢得了广泛关注。以下是对Stable Diffusion的最全详解图解，内容涵盖其定义、工作原理、组件结构、特性、应用领域及未来展望，力求全面而深入地解析这一技术。</p>
<h4>一、Stable Diffusion定义</h4>
<p>Stable Diffusion可以被视为一种基于随机微分方程的扩散模型，其核心思想是在潜在空间（latent space）中通过逐步的随机漫步过程来生成图像。这种模型不仅限于图像生成，还能描述许多自然和人工系统中的随机演化行为。在图像生成领域，Stable Diffusion通过训练大量图像数据，学习图像中的特征和规律，从而能够根据输入文本或图像生成逼真的新图像。</p>
<h4>二、Stable Diffusion工作原理</h4>
<p>Stable Diffusion的工作原理复杂而精妙，主要包括文本理解、图像信息创建和图像解码三个主要步骤。</p>
<h5>1. 文本理解</h5>
<ul>
 <li><strong>组件</strong>：Stable Diffusion首先包含一个文本理解组件（文本编码器），通常是一个基于Transformer的CLIP模型文本编码器。</li>
 <li><strong>过程</strong>：该组件将输入的文本信息转化为数字表示（如token embeddings），以捕捉文本中的语义信息。每个token被转换为一个高维向量，这些向量共同构成了后续图像生成过程的输入。</li>
</ul>
<h5>2. 图像信息创建</h5>
<ul>
 <li><strong>组件</strong>：图像信息创建器（Image Information Creator）是Stable Diffusion的核心组件，它基于UNet神经网络和调度算法工作。</li>
 <li><strong>过程</strong> ：</li>
 <li><strong>步骤化生成</strong>：图像信息创建器运行多个步骤（steps）来生成图像信息，这些步骤的数量是Stable Diffusion接口和库中的一个重要参数，通常默认为50或100。</li>
 <li><strong>潜空间操作</strong>：整个生成过程在潜空间中进行，而非直接在像素空间。这使得Stable Diffusion比传统在像素空间工作的扩散模型更快、更高效。</li>
 <li><strong>扩散过程</strong>：在每一步中，图像信息创建器都会对输入的信息进行逐步处理，逐步增加与输入文本相关的图像信息，直至生成最终的图像信息表示。</li>
</ul>
<h5>3. 图像解码</h5>
<ul>
 <li><strong>组件</strong>：图像解码器（Image Decoder）</li>
 <li><strong>过程</strong>：在图像信息创建完成后，图像解码器根据这些信息绘制出最终的像素图像。这个过程只运行一次，即可生成高质量的图像输出。</li>
</ul>
<h4>三、Stable Diffusion组件结构</h4>
<p>Stable Diffusion由多个组件构成，每个组件都承担着不同的角色和功能，共同实现了从文本到图像的转换过程。</p>
<ul>
 <li><strong>Clip Text</strong>：用于文本编码，将输入的文本转换为数字表示。</li>
 <li><strong>UNet + Scheduler</strong>：在信息（潜）空间中逐步处理/扩散信息，生成图像信息。</li>
 <li><strong>Autoencoder Decoder</strong>：使用处理过的信息矩阵绘制最终图像。</li>
</ul>
<h4>四、Stable Diffusion特性</h4>
<ol>
 <li><strong>高性能</strong>：Stable Diffusion生成的图像质量高、运行速度快，且对计算资源和内存的需求相对较低。</li>
 <li><strong>多样性</strong>：能够生成多种不同的图像以匹配相同的文本描述，为用户提供更多选择和灵活性。</li>
 <li><strong>可控性</strong>：通过自然语言处理（NLP）技术，允许用户通过文本输入来指导图像生成的内容和风格。</li>
 <li><strong>易于训练</strong>：与其他生成模型相比，Stable Diffusion在训练上更为高效，使用更少的计算资源和时间。</li>
 <li><strong>通用性</strong>：不仅适用于图像生成任务，还可以扩展到图像编辑、风格迁移等其他领域。</li>
</ol>
<h4>五、Stable Diffusion应用领域</h4>
<p>Stable Diffusion的广泛应用领域体现了其强大的图像生成和编辑能力。</p>
<ol>
 <li><strong>艺术创作</strong>：艺术家可以使用Stable Diffusion根据创意文本生成图像，为创作提供灵感和素材。</li>
 <li><strong>电影特效</strong>：在电影制作中，Stable Diffusion可以生成逼真的特效图像，增强视觉效果。</li>
 <li><strong>游戏开发</strong>：游戏开发者可以利用Stable Diffusion生成多样化的游戏角色、场景和道具，丰富游戏内容。</li>
 <li><strong>图像修复与增强</strong>：通过逆向扩散过程，Stable Diffusion可以从损坏或模糊的图像中恢复出清晰的图像，提高图像质量。</li>
 <li><strong>图像插值与超分辨率</strong>：Stable Diffusion能够从低分辨率图像中生成高分辨率图像，提升图像的细节和清晰度。</li>
</ol>
<h4>七、未来展望</h4>
<p>随着技术的不断进步和应用场景的拓展，Stable Diffusion有望在未来实现更多的突破和创新。</p>
<ol>
 <li><strong>效率提升</strong>：通过优化算法和模型结构，Stable Diffusion的生成效率将进一步提升，满足实时图像生成的需求。</li>
 <li><strong>多样性增强</strong>：模型将能够生成更加多样化、个性化的图像，以更好地满足用户的个性化需求。</li>
 <li><strong>交互性增强</strong>：引入更多的交互性功能，使用户能够更加精细地控制图像生成的过程和结果。</li>
 <li><strong>跨领域应用</strong>：Stable Diffusion的应用领域将进一步拓展，不仅限于图像生成和编辑，还可能涉及语音合成、视频生成等多个领域。</li>
</ol>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140548533</p>]]></description><guid isPermaLink="false">/archives/53d49622-658d-4667-81c1-d5c99337b1a2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 02:02:57 GMT</pubDate></item><item><title><![CDATA[初识LangChain的快速入门指南]]></title><link>https://wiki.dgfygk.com/archives/392b6604-6b8d-4b4f-89db-094990d13542</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%9D%E8%AF%86LangChain%E7%9A%84%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97&amp;url=/archives/392b6604-6b8d-4b4f-89db-094990d13542" width="1" height="1" alt="" style="opacity:0;">
<p><strong>初识LangChain的快速入门指南</strong></p>
<p>LangChain是一个基于大语言模型（LLM）的框架，专为开发由语言模型驱动的应用程序而设计。它简化了LLM应用程序的生命周期，包括开发、生产化和部署等各个阶段。LangChain通过其六大核心组件和多个开源库，为用户提供了一个强大而灵活的系统，以支持各种复杂的语言模型应用。以下是对LangChain的详细快速入门指南。</p>
<h4>一、LangChain概述</h4>
<p>LangChain是一个用于构建端到端语言模型应用的框架，它提供了一系列工具、套件和接口，让开发者能够利用语言模型实现诸如文本到图像的生成、文档问答、聊天机器人等复杂任务。LangChain的核心在于其六大组件：模型（Models）、提示模板（Prompts）、数据检索（Indexes）、记忆（Memory）、链（Chains）和代理（Agents），这些组件相互协作，形成了一个强大而灵活的系统。</p>
<h4>二、LangChain的六大组件</h4>
<ol>
 <li>
  <p><strong>模型（Models）</strong></p>
  <p>LangChain支持多种语言模型，包括但不限于GPT系列、BERT等。它提供了这些模型的LangChain接口和调用细节，以及输出解析机制。开发者可以通过LangChain轻松对接并使用这些模型，实现各种自然语言处理任务。</p>
 </li>
 <li>
  <p><strong>提示模板（Prompts）</strong></p>
  <p>提示模板是LangChain中的一个重要组件，它使提示工程流线化，进一步激发大语言模型的潜力。通过精心设计的提示模板，开发者可以引导模型产生更符合预期的输出。LangChain提供了丰富的提示模板库，并支持自定义提示模板，以满足不同场景的需求。</p>
 </li>
 <li>
  <p><strong>数据检索（Indexes）</strong></p>
  <p>数据检索组件允许开发者构建并操作文档库，接受用户的查询并返回最相关的文档。这对于构建知识库和问答系统至关重要。LangChain提供了多种数据检索方法，包括基于向量的检索和基于关键字的检索，使开发者能够轻松搭建本地知识库。</p>
 </li>
 <li>
  <p><strong>记忆（Memory）</strong></p>
  <p>记忆组件通过短时记忆和长时记忆机制，在对话过程中存储和检索数据，使ChatBot能够记住上下文信息。这对于构建流畅的对话系统至关重要。LangChain的记忆机制能够有效地管理对话状态，提高用户体验。</p>
 </li>
 <li>
  <p><strong>链（Chains）</strong></p>
  <p>链是LangChain中的核心机制之一，它以特定方式封装各种功能，并通过一系列的组合自动而灵活地完成任务。开发者可以将多个功能组件（如模型、提示模板、数据检索等）组合成一个链，以实现复杂的任务流程。LangChain提供了丰富的链模板和自定义链的能力，以满足不同场景的需求。</p>
 </li>
 <li>
  <p><strong>代理（Agents）</strong></p>
  <p>代理是LangChain中的另一个核心机制，它通过"代理"让大模型自主调用外部工具和内部工具，使智能Agent成为可能。代理能够根据任务需求自主决策并执行相应的操作，如调用API、查询数据库等。LangChain的代理机制为构建智能应用提供了强大的支持。</p>
 </li>
</ol>
<h4>三、LangChain的安装与配置</h4>
<ol>
 <li>
  <p><strong>安装LangChain</strong></p>
  <p>首先，确保你的系统中已安装Python。然后，你可以通过pip工具安装LangChain的Python库。在命令行中执行以下命令：</p>
  <pre><code class="prism language-bash">pip install langchain
</code></pre>
  <p>如果你需要安装包含常用LLM库的LangChain版本，可以使用以下命令：</p>
  <pre><code class="prism language-bash">pip install langchain[llms]
</code></pre>
 </li>
 <li>
  <p><strong>配置环境变量</strong></p>
  <p>如果你需要使用OpenAI等第三方服务，需要配置相应的环境变量。例如，对于OpenAI，你需要设置<code>OPENAI_API_KEY</code>环境变量为你的API密钥。在命令行中执行以下命令：</p>
  <pre><code class="prism language-bash">export OPENAI_API_KEY="your-api-key"
</code></pre>
  <p>或者，在Python代码中设置环境变量：</p>
  <pre><code class="prism language-python">import os
os.environ["OPENAI_API_KEY"] = "your-api-key"
</code></pre>
 </li>
 <li>
  <p><strong>初始化模型与创建提示模板</strong></p>
  <p>使用LangChain提供的API初始化语言模型（如OpenAI的GPT系列模型），并创建所需的提示模板。以下是一个简单的示例代码：</p>
  <pre><code class="prism language-python">from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

# 初始化模型
llm = ChatOpenAI()

# 创建提示模板
prompt = ChatPromptTemplate.from_messages([
   ("system", "你是一名技术专家，请尽可能详细和准确地回答我的问题。"),
   ("user", "{input}")
])

# 组合成链
chain = prompt | llm

# 使用链
response = chain.invoke({
    "input": "请解释一下什么是机器学习？"})
</code></pre>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140548640</p>]]></description><guid isPermaLink="false">/archives/392b6604-6b8d-4b4f-89db-094990d13542</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 02:02:47 GMT</pubDate></item><item><title><![CDATA[决策树算法介绍：原理与案例实现]]></title><link>https://wiki.dgfygk.com/archives/3e56236f-9de0-4067-8f99-e1f48088caa5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%86%B3%E7%AD%96%E6%A0%91%E7%AE%97%E6%B3%95%E4%BB%8B%E7%BB%8D%EF%BC%9A%E5%8E%9F%E7%90%86%E4%B8%8E%E6%A1%88%E4%BE%8B%E5%AE%9E%E7%8E%B0&amp;url=/archives/3e56236f-9de0-4067-8f99-e1f48088caa5" width="1" height="1" alt="" style="opacity:0;">
<p><strong>决策树算法介绍：原理与案例实现</strong></p>
<h4>一、决策树算法概述</h4>
<p>决策树（Decision Tree, DT）算法是一种常用的机器学习方法，主要用于分类和回归任务。它以树形结构表示数据决策过程，通过构建决策树模型，将数据特征映射到目标变量上。决策树算法因其直观易懂、易于实现和解释性强等特点，在数据挖掘、机器学习领域得到了广泛应用。</p>
<h4>二、决策树算法原理</h4>
<h5>1. 基本概念</h5>
<p>决策树是一种树形结构，其中每个内部节点表示一个属性上的测试，每个分支代表一个测试输出，每个叶节点代表一种类别（对于分类树）或一个预测值（对于回归树）。在构建决策树时，我们需要选择最佳分割特征，根据该特征将数据划分为不同的子集，并递归地重复这个过程，直到满足停止条件为止。</p>
<h5>2. 树的构建过程</h5>
<p>决策树的构建过程通常包括以下几个步骤：</p>
<ul>
 <li><strong>选择最佳分割特征</strong>：在当前节点，选择能够最有效地将数据划分的特征。这通常通过计算不纯度（如信息增益、信息增益比、基尼指数等）来实现。</li>
 <li><strong>创建分支</strong>：根据选择的特征值，将数据划分为两部分或多部分。</li>
 <li><strong>递归重复</strong>：对每个分支递归地重复上述步骤，构建子树，直到所有数据点都属于同一个类或满足其他停止条件（如树的最大深度、最小样本数等）。</li>
</ul>
<h5>3. 不纯度度量</h5>
<ul>
 <li><strong>信息增益（Information Gain）</strong>：常用于ID3和C4.5算法，基于熵的减少来计算。信息增益衡量的是数据集的熵减少量，即划分前后信息不确定性的减少程度。</li>
 <li><strong>信息增益比（Gain Ratio）</strong>：C4.5算法使用信息增益比作为分割标准，它是信息增益与特征的固有值的比值，用于解决信息增益倾向于选择取值较多的属性的问题。</li>
 <li><strong>基尼指数（Gini Index）</strong>：常用于CART算法，度量数据集的纯度。基尼指数越小，表示数据集纯度越高。</li>
</ul>
<h4>三、决策树算法常见类型</h4>
<h5>1. ID3算法</h5>
<p>ID3算法是一种基于信息增益的决策树构建算法。它选择信息增益最大的特征作为当前节点的分割特征，并递归地构建决策树。然而，ID3算法存在倾向于选择取值较多的属性的问题，且无法处理连续值和缺失值。</p>
<h5>2. C4.5算法</h5>
<p>C4.5算法是ID3算法的改进版，它使用信息增益比作为分割标准，并改进了对连续值和缺失值的处理。C4.5算法能够更有效地处理具有复杂特征的数据集。</p>
<h5>3. CART算法</h5>
<p>CART（Classification and Regression Trees）算法既可以用于分类任务，也可以用于回归任务。对于分类任务，CART算法使用基尼指数作为分割标准；对于回归任务，它使用方差减少作为分割标准。CART算法构建的是二叉树结构，每个节点只有两个分支。</p>
<h4>四、决策树算法的优缺点</h4>
<h5>优点</h5>
<ol>
 <li><strong>易于理解和解释</strong>：决策树模型以树形结构表示，直观易懂，便于非专业人士理解。</li>
 <li><strong>适用范围广</strong>：既能用于分类任务，也能用于回归任务。</li>
 <li><strong>无需特征缩放</strong>：决策树算法不依赖特征的缩放或归一化。</li>
 <li><strong>可解释性强</strong>：决策树提供的决策路径是清晰可追踪的，每次分支都基于数据特征的显著性进行选择。</li>
</ol>
<h5>缺点</h5>
<ol>
 <li><strong>容易过拟合</strong>：决策树容易对训练数据过度拟合，尤其是深度较大的树。这可能导致模型在测试集上的性能下降。</li>
 <li><strong>对噪声敏感</strong>：决策树对数据中的噪声和异常值较为敏感，可能会影响模型的准确性。</li>
 <li><strong>倾向于选择取值较多的属性</strong>：在某些情况下，决策树构建中的分割标准可能倾向于选择取值较多的属性，这不一定是最优的选择。</li>
</ol>
<h4>五、案例实现</h4>
<p>以下是一个使用决策树算法进行分类的简单案例实现，以Python的scikit-learn库为例。</p>
<h5>1. 数据准备</h5>
<p>首先，我们需要准备一些用于分类的数据集。这里我们使用scikit-learn库中的鸢尾花（Iris）数据集作为示例。</p>
<pre><code class="prism language-python">from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 加载数据集
iris = load_iris()
X = iris.data
y = iris.target

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2
</code></pre>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140548759</p>]]></description><guid isPermaLink="false">/archives/3e56236f-9de0-4067-8f99-e1f48088caa5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:49:17 GMT</pubDate></item><item><title><![CDATA[目标检测算法]]></title><link>https://wiki.dgfygk.com/archives/9f90f40c-a320-4109-9200-9f61b837bb3c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B%E7%AE%97%E6%B3%95&amp;url=/archives/9f90f40c-a320-4109-9200-9f61b837bb3c" width="1" height="1" alt="" style="opacity:0;">
<p>目标检测算法是计算机视觉领域中的一项核心技术，旨在从图像或视频中识别和定位一个或多个特定对象实例。这些算法不仅需要确定对象的位置（如通过边界框），还需要识别对象的类别（如人、汽车、狗等）。随着深度学习技术的快速发展，基于深度神经网络的目标检测算法已成为主流，并在各种应用场景中取得了显著的性能提升。以下是对目标检测算法的详细探讨，包括其发展历程、主要算法、关键技术、应用场景以及未来趋势。</p>
<h4>一、目标检测算法的发展历程</h4>
<p>目标检测算法的发展可以大致分为传统方法和基于深度学习的方法两个阶段。</p>
<h5>1. 传统方法</h5>
<p>在传统方法中，目标检测主要依赖于手工设计的特征和分类器。这些方法通常包括三个步骤：区域选择、特征提取和分类器分类。</p>
<ul>
 <li><strong>区域选择</strong>：采用滑动窗口策略，在图像上按照不同大小和长宽比滑动窗口，以覆盖所有可能的对象位置。这种方法计算量大，且存在大量冗余窗口。</li>
 <li><strong>特征提取</strong>：利用手工设计的特征描述符（如SIFT、HOG等）提取每个窗口内的图像特征。这些特征需要具有良好的鲁棒性和区分性。</li>
 <li><strong>分类器分类</strong>：使用训练好的分类器（如SVM、Adaboost等）对提取的特征进行分类，判断窗口内是否包含目标对象。</li>
</ul>
<p>然而，传统方法存在诸多局限性，如特征设计复杂、计算量大、泛化能力差等。随着深度学习技术的兴起，基于深度学习的目标检测算法逐渐取代了传统方法。</p>
<h5>2. 基于深度学习的方法</h5>
<p>基于深度学习的方法利用卷积神经网络（CNN）等深度神经网络自动学习图像特征，并进行目标检测和分类。这些方法主要分为两大类：Two-Stage算法和One-Stage算法。</p>
<ul>
 <li><strong>Two-Stage算法</strong>：代表算法有R-CNN系列（R-CNN、Fast R-CNN、Faster R-CNN等）。这类算法首先生成可能包含目标的候选区域（Region Proposals），然后对这些区域进行分类和边界框回归。Two-Stage算法具有较高的检测精度，但计算量相对较大。</li>
 <li><strong>One-Stage算法</strong>：代表算法有YOLO系列（YOLO、YOLOv2、YOLOv3、YOLOv4等）和SSD（Single Shot MultiBox Detector）。这类算法不生成候选区域，而是直接在图像上预测目标的类别和位置。One-Stage算法具有较快的检测速度，但精度可能稍逊于Two-Stage算法。</li>
</ul>
<h4>二、主要算法介绍</h4>
<h5>1. R-CNN系列</h5>
<p><strong>R-CNN（Regions with CNN features）</strong>：R-CNN是第一个将CNN应用于目标检测的算法。它首先使用选择性搜索（Selective Search）方法生成候选区域，然后将每个候选区域缩放至固定大小并输入到预训练的CNN中提取特征，最后使用SVM分类器对特征进行分类，并使用回归模型精化边界框位置。然而，R-CNN存在计算量大、训练过程繁琐等问题。</p>
<p><strong>Fast R-CNN</strong>：Fast R-CNN对R-CNN进行了改进，通过共享卷积特征图来加速计算。它首先对整个图像进行卷积操作，然后利用ROI Pooling层从特征图中提取每个候选区域的特征，并进行分类和边界框回归。Fast R-CNN显著提高了检测速度，但仍依赖于选择性搜索方法生成候选区域。</p>
<p><strong>Faster R-CNN</strong>：Faster R-CNN进一步引入了区域提议网络（RPN），直接在卷积特征图上生成候选区域，实现了端到端的候选区域生成和检测。RPN网络通过滑动窗口在特征图上生成多个锚点（Anchor Boxes），并对每个锚点进行二分类（前景或背景）和边界框回归。Faster R-CNN在速度和精度上均取得了显著提升。</p>
<h5>2. YOLO系列</h5>
<p><strong>YOLO（You Only Look Once）</strong>：YOLO将目标检测作为回归问题解决，直接在图像上预测类别和边界框。它使用单个卷积神经网络同时完成特征提取、分类和边界框回归。YOLO具有较快的检测速度，但早期版本的精准度有限。</p>
<p><strong>YOLOv2、YOLOv3、YOLOv4</strong>：这些后续版本通过引入新的网络架构、多尺度检测、锚点框等改进，不断提高YOLO系列的准确率和速度。例如，YOLOv2引入了批量归一化（Batch Normalization）和锚点框机制；YOLOv3采用了更深的网络结构和多尺度预测；YOLOv4则集成了多种优化技术和方法，进一步提升了检测性能。</p>
<h5>3. SSD（Single Shot MultiBox Detector）</h5>
<p>SSD与YOLO类似，也在单个前向传递中同时预测类别和边界框。但SSD利用了不同分辨率的特征图来检测不同大小的物体，从而提高了对小物体的检测能力。SSD还提出了一种新的损失函数（Focal Loss的变种），以解决类别不平衡问题### 三、关键技术解析</p>
<h5>1. 卷积神经网络（CNN）</h5>
<p>卷积神经网络是目标检测算法的核心组成部分，它能够从原始图像中自动提取出层次化的特征表示。CNN通过堆叠多个卷积层、激活层、池化层等，逐步抽象出图像的高级特征。这些特征对于目标检测任务至关重要，因为它们能够捕捉到目标的形状、纹理、颜色等关键信息。</p>
<h5>2. 锚点框（Anchor Boxes）</h5>
<p>锚点框是目标检测算法中常用的一种技术，用于在特征图上预设一系列不同大小和长宽比的矩形框。这些锚点框作为目标边界框的初始估计，通过边界框回归进一步调整其位置和大小，以更准确地匹配实际目标。锚点框的使用显著提高了目标检测的召回率和定位精度。</p>
<h5>3. 特征金字塔（Feature Pyramid Networks, FPN）</h5>
<p>特征金字塔是一种多尺度特征融合的技术，旨在利用不同层次的特征图来检测不同大小的物体。在FPN中，低层次的特征图具有较高的分辨率和丰富的细节信息，适合检测小物体；而高层次的特征图则具有较低的分辨率和更强的语义信息，适合检测大物体。通过将不同层次的特征图进行融合，FPN能够同时兼顾不同尺度的目标检测任务。</p>
<h5>4. 非极大值抑制（Non-Maximum Suppression, NMS）</h5>
<p>非极大值抑制是一种用于去除冗余边界框的后处理技术。在目标检测过程中，由于锚点框的密集设置和边界框回归的预测误差，往往会生成多个重叠的边界框来对应同一个目标。NMS通过比较这些边界框的置信度和重叠程度，保留置信度最高且与其他边界框重叠最少的边界框作为最终的检测结果。</p>
<h4>四、应用场景</h4>
<p>目标检测算法在各个领域都有着广泛的应用，包括但不限于以下几个方面：</p>
<ol>
 <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>
</ol>
<h4>五、未来趋势</h4>
<p>随着深度学习技术的不断发展和计算机硬件性能的持续提升，目标检测算法在未来将呈现以下几个发展趋势：</p>
<ol>
 <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>：随着物联网技术的普及和5G等通信技术的发展，边缘计算和云端协同将成为目标检测算法的重要部署方式。通过将部分计算任务迁移到边缘设备上进行处理，可以降低数据传输的延迟和带宽需求，提高系统的响应速度和可靠性。同时，云端可以提供更强大的计算资源和数据支持，为边缘设备提供必要的支持和更新。</p>
 </li>
</ol>
<p>综上所述，目标检测算法作为计算机视觉领域中的核心技术之一，已经取得了显著的发展成果，并在自动驾驶、视频监控、医学影像分析、智能零售以及机器人导航等多个领域展现出了广泛的应用前景。随着深度学习技术的不断突破和计算机硬件性能的持续提升，目标检测算法在精度、速度、实时性、鲁棒性等方面将持续优化，进一步推动相关领域的智能化进程。</p>
<p>未来，我们期待看到更多创新性的目标检测算法涌现，它们将结合多模态信息、小样本学习、无监督学习等先进技术，解决当前算法面临的挑战，并拓展到更多未知和复杂的应用场景中。同时，随着边缘计算和云端协同技术的发展，目标检测算法将更加灵活地部署于各种设备和系统中，实现更高效、更智能的数据处理和应用服务。</p>
<p>总之，目标检测算法作为人工智能领域的重要组成部分，将继续在科技进步的浪潮中乘风破浪，为人类社会带来更多的便利和惊喜。我们有理由相信，在不久的将来，目标检测算法将成为连接物理世界与数字世界的桥梁，推动人类社会向更加智能化、自动化的方向发展。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140610702</p>]]></description><guid isPermaLink="false">/archives/9f90f40c-a320-4109-9200-9f61b837bb3c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:49:08 GMT</pubDate></item><item><title><![CDATA[数据库结构之b树]]></title><link>https://wiki.dgfygk.com/archives/38cd3e2f-e27f-4633-a17a-35b4b8b0167e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BB%93%E6%9E%84%E4%B9%8Bb%E6%A0%91&amp;url=/archives/38cd3e2f-e27f-4633-a17a-35b4b8b0167e" width="1" height="1" alt="" style="opacity:0;">
<p>数据库结构中的B树（B-Tree，平衡树或多路搜索树）是一种自平衡的树数据结构，它能够保持数据排序，并允许搜索、顺序访问、插入和删除操作都在对数时间内完成。B树广泛应用于数据库和文件系统中，尤其是在需要管理大量数据并保持高效访问性能的场景中。以下是对B树结构、性质、操作及其应用的详细阐述。</p>
<h4>一、B树的基本概念</h4>
<h5>1. 定义与结构</h5>
<p>B树是一种多路平衡搜索树，它维护数据元素的有序性，并通过减少I/O操作次数来优化磁盘或其他直接存取辅助设备上的数据访问。B树与二叉搜索树的主要区别在于，B树的每个节点可以拥有多于两个的子节点。具体来说，一个m阶的B树是一个满足以下所有条件的树：</p>
<ul>
 <li>每个节点最多有m个子节点。</li>
 <li>每个非根节点至少有⌈m/2⌉个子节点（其中⌈x⌉表示不小于x的最小整数）。</li>
 <li>根节点至少有两个子节点，除非它是叶子节点。</li>
 <li>所有的叶子节点都位于同一层。</li>
 <li>每个非叶子节点都包含n个关键字，其中⌈m/2⌉-1 ≤ n ≤ m-1。</li>
 <li>每个内部节点（非叶子节点）的关键字对其子树的关键字范围进行分割。即，如果ki是节点中的一个关键字，则节点中所有小于ki的关键字都位于ki的左子树中，所有大于ki的关键字都位于ki的右子树中。特别地，如果ki是节点中的第i个关键字，则ki的左子树中的所有关键字都小于ki，而ki的右子树（如果存在）中的所有关键字都大于ki+1（注意是ki+1，不是ki）。</li>
 <li>每个叶子节点都包含相同数量的信息（通常是空或者包含指向实际数据记录的指针），这些信息不包括关键字信息，但在实践中，叶子节点通常包含指向包含关键字记录的页或块的指针。</li>
</ul>
<h5>2. 关键字与分裂合并</h5>
<p>在B树中，节点的关键字用于指导搜索过程，并定义子树的关键字范围。当向B树中插入新关键字导致节点关键字数量超过上限（m-1）时，节点会分裂成两个节点，并将中间的关键字上移到父节点。相反，如果由于删除操作导致节点的关键字数量低于下限（⌈m/2⌉-1），则可能需要从相邻节点或父节点中借调关键字或合并节点来保持树的平衡。</p>
<h4>二、B树的性质与优势</h4>
<h5>1. 平衡性</h5>
<p>B树的自平衡特性确保了树的高度相对较低，从而保证了所有操作（搜索、插入、删除）的时间复杂度都保持在O(log n)级别，其中n是树中元素的数量。这种平衡性是通过节点的分裂和合并机制自动维护的。</p>
<h5>2. 磁盘I/O优化</h5>
<p>B树的主要设计目标之一是减少磁盘I/O操作次数。由于B树的节点通常设计为与磁盘块或页的大小相匹配，因此每次磁盘I/O操作可以读取或写入整个节点。这减少了磁盘访问的次数，因为每次访问都能处理多个关键字，而不仅仅是二叉搜索树中的一个或两个。</p>
<h5>3. 高效的范围查询</h5>
<p>B树特别适合执行范围查询，因为它允许通过遍历树中的节点来快速定位一个范围内的所有关键字。这种能力使得B树成为数据库索引和数据仓库中的首选数据结构。</p>
<h4>三、B树的操作</h4>
<h5>1. 搜索操作</h5>
<p>在B树中搜索一个关键字的过程类似于在二叉搜索树中的搜索过程，但需要考虑的是多路搜索。从根节点开始，根据节点的关键字将搜索引导到适当的子节点，直到找到包含关键字的叶子节点或确定关键字不存在于树中。</p>
<h5>2. 插入操作</h5>
<p>向B树中插入新关键字的过程包括找到适当的叶子节点，然后将关键字插入到该节点中。如果插入后节点的关键字数量超过了上限（m-1），则节点会分裂成两个节点，并将中间的关键字上移到父节点。这个过程可能会一直向上传播到根节点，如果根节点分裂，则树的高度会增加。</p>
<h5>3. 删除操作</h5>
<p>从B树中删除关键字可能涉及更复杂的情况，因为需要保持节点的关键字数量在允许的范围内。删除操作通常从包含关键字的叶子节点开始。如果删除后节点的关键字数量低于下限（⌈m/2⌉-1），则可能需要从相邻的兄弟节点中借调关键字（如果可能的话），或者与相邻的兄弟节点合并，并将父节点中的相应关键字下移。这个过程可能也会向上传播。</p>
<h4>四、B树的应用</h4>
<h5>1. 数据库索引</h5>
<p>B树是数据库系统中广泛使用的索引结构之一。数据库中的索引用于加速数据检索过程，通过减少需要扫描的数据量来提高查询效率。B树能够高效地支持等值查询、范围查询以及排序操作，因此非常适合作为数据库表的索引结构。在数据库系统中，B树索引通常存储在磁盘上，并且经过优化以减少磁盘I/O操作，从而提高整体性能。</p>
<h5>2. 文件系统</h5>
<p>B树也被用于文件系统中，特别是在管理大型文件目录时。文件系统需要将文件名和文件元数据映射到存储位置，而B树可以有效地组织这些信息，支持快速的查找、插入和删除操作。通过使用B树，文件系统可以高效地管理数百万甚至数十亿个文件，同时保持快速的访问速度。</p>
<h5>3. 搜索引擎</h5>
<p>虽然现代搜索引擎通常使用更复杂的倒排索引和分布式存储技术，但B树及其变种（如B+树）在搜索引擎的某些组件中仍然扮演着重要角色。例如，在构建和维护索引时，B树可以用于对文档中的词汇进行排序和存储，以便后续进行高效的查询处理。</p>
<h5>4. 内存数据库</h5>
<p>内存数据库（如Redis、Memcached等）虽然主要关注于将数据存储在内存中以提高访问速度，但它们也可能使用B树或其变种（如跳表、哈希表加链表等）来优化特定类型的数据结构。尽管内存数据库通常更倾向于使用简单的数据结构来减少CPU缓存未命中的情况，但在需要保持数据有序或执行范围查询时，B树或其变种仍然是有价值的选项。</p>
<h4>五、B树的变种</h4>
<h5>1. B+树</h5>
<p>B+树是B树的一种变体，它在数据库和文件系统中更为常见。与B树相比，B+树有以下几个主要区别：</p>
<ul>
 <li>所有值都存储在叶子节点中，非叶子节点仅存储关键字以指导搜索过程。这使得B+树更适合进行范围查询，因为可以简单地遍历叶子节点链表来获取一个范围内的所有值。</li>
 <li>叶子节点之间通过指针相连，形成了一个有序链表。这进一步提高了范围查询的效率。</li>
 <li>非叶子节点可以包含更多的关键字，因为它们不需要存储值数据。</li>
</ul>
<h5>2. B*树</h5>
<p>B<em>树是B+树的一个变种，它进一步优化了节点的分裂和合并过程，以减少树的高度和增加树的平衡性。B</em>树在节点分裂时更加保守，会尝试将更多的关键字留在原节点中，并在必要时从相邻节点中借调关键字以保持树的平衡。这种策略有助于减少树的高度，并可能提高某些操作的性能。</p>
<h4>六、结论</h4>
<p>B树作为一种高效的数据结构，在数据库、文件系统、搜索引擎等多个领域发挥着重要作用。它通过保持树的平衡性和优化磁盘I/O操作来提高数据访问效率，支持快速的搜索、插入、删除和范围查询操作。随着计算机技术的发展和存储设备的进步，B树及其变种将继续在数据管理和存储领域发挥关键作用。同时，新的数据结构和算法也在不断涌现，为处理大规模数据集和优化性能提供了新的选择。然而，B树作为经典的数据结构之一，其基本原理和优势仍然值得深入学习和研究。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140610919</p>]]></description><guid isPermaLink="false">/archives/38cd3e2f-e27f-4633-a17a-35b4b8b0167e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:48:23 GMT</pubDate></item><item><title><![CDATA[AI绘画工具介绍]]></title><link>https://wiki.dgfygk.com/archives/7a82b96c-3592-4842-8859-745b4c24d752</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AI%E7%BB%98%E7%94%BB%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D&amp;url=/archives/7a82b96c-3592-4842-8859-745b4c24d752" width="1" height="1" alt="" style="opacity:0;">
<h2>AI绘画工具介绍</h2>
<p>AI绘画工具作为人工智能技术在艺术领域的创新应用，近年来取得了显著的发展，不仅为艺术家和设计师提供了高效的创作工具，也让更多的普通用户能够体验到艺术创作的乐趣。以下是对几款主流AI绘画工具的详细介绍，涵盖了它们的特点、功能、应用场景以及使用方式等方面。</p>
<h4>一、Midjourney</h4>
<p><strong>1. 简介</strong></p>
<p>Midjourney是一个独立的研究实验室，专注于探索新的思维方式并扩展人类的想象力。它是一个人工智能绘图平台，以其卓越的图像生成和风格转移能力而闻名。Midjourney的作品展示了人工智能在图像创作领域的巨大潜力，引发了广泛的关注和讨论。</p>
<p><strong>2. 功能特点</strong></p>
<ul>
 <li><strong>图像生成</strong>：用户可以通过输入文本描述或参考图像，让Midjourney生成符合要求的图像。它支持多种风格，包括动漫、油画、水彩等，满足不同用户的创作需求。</li>
 <li><strong>风格转移</strong>：Midjourney能够将一种艺术风格应用于另一幅图像上，实现风格的快速转换。</li>
 <li><strong>高效性</strong>：Midjourney的生成速度非常快，能够在短时间内生成高质量的图像。</li>
</ul>
<p><strong>3. 应用场景</strong></p>
<p>Midjourney的应用场景非常广泛，包括但不限于B端设计（如中后台业务中的图标、数据大屏、登录页、官网插图等）、拍摄素材（适用于业务侧服务场景、新媒体应用、形象宣传等）、实物生成（如玩具）以及艺术创作、产品设计、广告制作等领域。</p>
<p><strong>4. 使用方式</strong></p>
<p>由于Midjourney的服务器位于国外，国内用户需要使用梯子等工具才能访问。用户通常需要通过Discord这个聊天软件进行注册和登录，然后在Discord的聊天界面添加Midjourney的机器人，通过聊天机器人唤起命令的方式使用。此外，Midjourney也提供了网页版，但同样需要翻墙才能访问。</p>
<h4>二、Vega AI</h4>
<p><strong>1. 简介</strong></p>
<p>Vega AI是由右脑科技推出的AI绘画工具，旨在简化操作流程，提高内容创作效率。它是一款能够文字生成图片、条件生成图片、风格训练等功能的AI工具库，帮助用户快速生成富有艺术感的图像或设计。</p>
<p><strong>2. 功能特点</strong></p>
<ul>
 <li><strong>多模式生成</strong>：Vega AI支持文生图、图生图、条件生图等多种生成模式，满足不同用户的创作需求。</li>
 <li><strong>高分辨率</strong>：Vega AI能够生成高达2K和4K的超高分辨率图像，以及提供HD优化功能，确保图像的清晰度和质量。</li>
 <li><strong>易用性</strong>：Vega AI的界面简洁明了，操作流程简单易懂，即使是初学者也能快速上手。</li>
</ul>
<p><strong>3. 应用场景</strong></p>
<p>Vega AI适用于各种需要图像创作的场景，如广告设计、插画制作、产品设计等。它能够帮助用户快速生成高质量的图像，提高创作效率和质量。</p>
<p><strong>4. 使用方式</strong></p>
<p>Vega AI的登陆方式比较简单，用户只需输入网址后直接登录即可。登陆后，用户可以根据需要选择相应的生成模式，输入文本描述或参考图像，然后等待一段时间即可生成图像。Vega AI还支持中文提示词，降低了使用门槛。</p>
<h4>三、WHEE</h4>
<p><strong>1. 简介</strong></p>
<p>WHEE是美图公司自主研发的一款国产AI绘画平台，专注于视觉和文字之间的桥梁构建。它集成了文生图、图生图、创作词库、风格模型等多种功能于一体，为用户提供便捷的视觉创作服务。</p>
<p><strong>2. 功能特点</strong></p>
<ul>
 <li><strong>丰富功能</strong>：WHEE不仅支持文生图和图生图功能，还提供了AI修图功能，满足用户在视觉创作方面的多样化需求。</li>
 <li><strong>易用性</strong>：WHEE的界面设计简洁明了，操作流程简单易懂。用户只需用自然语言表述需求即可轻松上手。</li>
 <li><strong>高质量</strong>：通过自研AI视觉大模型MiracleVision，WHEE能够生成高质量的图像和视频作品。</li>
</ul>
<p><strong>3. 应用场景</strong></p>
<p>WHEE适用于各种需要视觉创作的场景，如广告设计、插画制作、视频编辑等。它能够帮助用户快速生成符合要求的图像和视频作品，提高创作效率和质量。</p>
<p><strong>4. 使用方式</strong></p>
<p>WHEE提供了电脑网页版和移动手机端的不同选择，用户可以根据自己的需求选择合适的版本进行创作。登陆后，用户可以直接进行创作或浏览社区分享的优秀作品以获取灵感。WHEE还提供了丰富的AI创作和改图功能以及出色的AI超清功能让模糊的图片转变为清晰锐利的视觉盛宴。</p>
<h4>四、无界AI</h4>
<p><strong>1. 简介</strong></p>
<p>无界AI是无界版图推出的专注于AIGC（人工智能创作图片、漫画与视频）的社区和平台。它致力于为国内用户提供更加简洁易用、模型丰富的AIGC绘画工具。</p>
<p><strong>2. 功能特点</strong></p>
<ul>
 <li><strong>模型丰富</strong>：无界AI集成了多种AIGC模型供用户选择使用，满足不同用户的创作需求。</li>
 <li><strong>简洁易用</strong>：无界AI的界面设计简洁明了操作流程简单易懂即使是初学者也能快速上手。</li>
 <li><strong>3. 创作自由度</strong></li>
</ul>
<p>无界AI不仅提供了预设的模型和风格，还允许用户进行一定程度的自定义和调整。用户可以通过调整参数、选择不同的笔触、颜色等，来精细控制生成图像的效果，实现个性化的创作。这种高度的创作自由度，使得无界AI能够满足从专业艺术家到普通用户的各种创作需求。</p>
<p><strong>4. 社区互动</strong></p>
<p>无界AI还构建了一个活跃的社区平台，用户可以在这里分享自己的作品、交流创作心得、学习他人的创作技巧。社区中不仅有专业的艺术家和设计师，也有许多对AI绘画感兴趣的普通用户。这种社区互动不仅激发了用户的创作热情，也促进了AI绘画技术的普及和发展。</p>
<p><strong>5. 跨平台支持</strong></p>
<p>为了方便用户在不同设备上使用，无界AI提供了跨平台的支持。无论是电脑、平板还是手机，用户都可以通过浏览器或专用的APP访问无界AI平台，进行创作和分享。这种跨平台的支持，使得无界AI的使用场景更加广泛，用户可以随时随地进行创作。</p>
<p><strong>6. 持续更新与迭代</strong></p>
<p>无界AI的研发团队致力于技术的持续更新与迭代。他们不断引入新的算法、优化模型性能、增加新功能，以提升用户体验和创作效果。用户可以通过更新软件或访问官网，获取最新的功能和改进。这种持续更新与迭代的态度，使得无界AI能够保持领先地位，满足用户不断变化的需求。</p>
<h4>五、DALL-E</h4>
<p><strong>1. 简介</strong></p>
<p>DALL-E是OpenAI推出的一款强大的AI绘画工具，它以其出色的图像生成能力和创意性而闻名。DALL-E利用深度学习技术，能够根据用户输入的文本描述生成高度逼真且富有创意的图像。</p>
<p><strong>2. 功能特点</strong></p>
<ul>
 <li><strong>创意性</strong>：DALL-E能够生成超出人类想象的图像，将文本描述转化为前所未有的视觉内容。</li>
 <li><strong>逼真度</strong>：生成的图像在细节和质感上都非常逼真，几乎可以以假乱真。</li>
 <li><strong>多样性</strong>：DALL-E支持多种风格和主题的图像生成，满足用户的不同需求。</li>
</ul>
<p><strong>3. 应用场景</strong></p>
<p>DALL-E适用于广告创意、产品设计、插画制作等多个领域。它能够帮助用户快速生成独特的视觉元素，激发创作灵感，提升作品的质量和吸引力。</p>
<p><strong>4. 使用方式</strong></p>
<p>由于DALL-E是由OpenAI开发的，因此用户需要通过OpenAI的官网或API接口来使用它。用户需要注册并登录OpenAI账号，然后按照提示输入文本描述或上传参考图像，等待DALL-E生成图像。生成的图像可以下载并用于各种创作场景。</p>
<h4>六、总结与展望</h4>
<p>AI绘画工具作为人工智能技术在艺术领域的创新应用，正逐渐改变着我们的创作方式和审美体验。从Midjourney、Vega AI、WHEE到无界AI和DALL-E等主流工具来看，它们各自具有独特的功能特点和优势，适用于不同的创作场景和需求。未来，随着技术的不断进步和应用的不断拓展，AI绘画工具将会更加智能化、个性化和便捷化。我们有理由相信，在不久的将来，AI绘画将会成为艺术创作领域不可或缺的一部分，为艺术家和设计师带来更多的创作可能性和想象空间。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140541810</p>]]></description><guid isPermaLink="false">/archives/7a82b96c-3592-4842-8859-745b4c24d752</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Fri, 27 Sep 2024 01:48:02 GMT</pubDate></item><item><title><![CDATA[postman接囗测试工具详解]]></title><link>https://wiki.dgfygk.com/archives/7ff4f9d8-3440-4376-877e-883f51742623</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=postman%E6%8E%A5%E5%9B%97%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7%E8%AF%A6%E8%A7%A3&amp;url=/archives/7ff4f9d8-3440-4376-877e-883f51742623" width="1" height="1" alt="" style="opacity:0;">
<h2>postman接囗测试工具详解</h2>
<p>Postman是一款功能强大的API接口测试工具，广泛用于开发人员、测试人员和API设计者之间。它提供了简洁的界面和丰富的功能，使用户能够方便地发送HTTP请求、查看响应、编写测试脚本，并自动生成测试报告。以下是对Postman接口测试工具的详细解析：</p>
<h4>一、功能特点</h4>
<ol>
 <li>
  <p><strong>支持多种HTTP方法</strong>：Postman支持GET、POST、PUT、DELETE、PATCH、OPTIONS等多种HTTP方法，满足不同的测试需求。</p>
 </li>
 <li>
  <p><strong>灵活的请求构建</strong>：用户可以通过图形界面轻松选择HTTP方法，并填写相应的URL。此外，Postman还支持复杂的URL参数设置，包括路径参数、查询参数等。</p>
 </li>
 <li>
  <p><strong>丰富的请求体支持</strong>：请求体支持多种格式，包括表单数据、JSON、XML等，用户可以根据API的要求选择合适的格式并填充数据。</p>
 </li>
 <li>
  <p><strong>详细的响应查看</strong>：Postman提供了详细的响应查看功能，帮助用户全面理解API的响应情况。响应信息包括状态码、响应时间、HTTP头信息以及响应体内容。响应体可以以原始格式显示，也可以转换为JSON或XML格式，方便用户阅读和分析。</p>
 </li>
 <li>
  <p><strong>测试脚本功能</strong>：用户可以在"Tests"标签页中编写JavaScript代码，这些代码将在请求发送后自动执行。测试脚本可以用来检查响应的状态码、验证响应数据、检查响应时间等。</p>
 </li>
 <li>
  <p><strong>环境变量和全局变量</strong>：Postman允许用户定义环境变量和全局变量，以便在不同的开发和测试环境中复用请求。这些变量可以在请求的URL、请求头、请求体等地方引用。</p>
 </li>
 <li>
  <p><strong>集合功能</strong>：集合允许用户将相关的请求和测试脚本组织在一起，方便管理和运行。集合可以包含多个请求，每个请求可以有自己的测试脚本和环境设置。此外，Postman还支持通过其云服务共享集合，团队成员可以轻松访问和使用共享的集合，实现团队协作和知识共享。</p>
 </li>
 <li>
  <p><strong>Mock服务</strong>：Mock服务是Postman的一个独特功能，它允许用户在没有实际后端服务的情况下模拟API的响应。这对于前端开发和测试非常有用，尤其是在后端服务尚未准备就绪时。</p>
 </li>
 <li>
  <p><strong>监控功能</strong>：Postman的监控功能可以帮助用户定期检查API的性能和可用性。用户可以设置监控任务，定时运行集合中的请求，并收集响应数据。监控结果可以通过邮件或其他方式发送给用户，确保用户能够及时了解API的状态。</p>
 </li>
</ol>
<h4>二、使用步骤</h4>
<ol>
 <li>
  <p><strong>安装与启动</strong>：Postman可以在多个平台上安装，包括Windows、MacOS和Linux。安装完成后，启动Postman并创建账户或使用现有账户登录。</p>
 </li>
 <li>
  <p><strong>创建请求</strong>：点击左上角的"New"按钮，选择"Request"来创建一个新的请求。在创建请求时，需要给这个请求命名，并可以选择将它保存到某个具体的集合中。</p>
 </li>
 <li>
  <p><strong>配置请求参数</strong>：在请求详情界面中，选择适当的HTTP方法，填写请求的URL，并在需要时添加查询参数、请求头、请求体等。</p>
 </li>
 <li>
  <p><strong>发送请求并查看响应</strong>：配置好请求参数后，点击"Send"按钮发送请求。发送请求后，界面下方会显示响应的结果，包括状态码、响应时间、响应体等。</p>
 </li>
 <li>
  <p><strong>编写测试脚本</strong> ：在"Tests"标签页中，使用JavaScript编写测试脚本来自动验证响应的正确性。Postman提供了一系列的测试语法，如<code>pm.expect()</code>函数，用于声明预期的结果。</p>
 </li>
 <li>
  <p><strong>管理环境变量和全局变量</strong>：通过"Environment"选项卡创建和管理环境变量，并在请求中使用这些变量。同时，也可以设置全局变量，以便在多个请求中共享。</p>
 </li>
 <li>
  <p><strong>使用集合和Mock服务</strong>：将相关的请求组织到集合中，并通过集合来管理、运行和共享请求。在需要时，使用Mock服务来模拟后端API的响应。</p>
 </li>
 <li>
  <p><strong>监控API</strong>：设置监控任务来定期检查API的性能和可用性，确保API的稳定运行。</p>
 </li>
</ol>
<h4>三、总结</h4>
<p>Postman作为一款功能强大的API接口测试工具，提供了丰富的功能和灵活的操作方式，帮助开发人员和测试人员高效地进行API的调试、测试和文档生成工作。通过合理使用Postman的各项功能，可以显著提高API测试的效率和质量，为软件开发的成功提供有力保障。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140512876</p>]]></description><guid isPermaLink="false">/archives/7ff4f9d8-3440-4376-877e-883f51742623</guid><dc:creator>黄健</dc:creator><category>测试</category><pubDate>Fri, 27 Sep 2024 01:47:47 GMT</pubDate></item><item><title><![CDATA[基于Go 1.19的站点模板爬虫]]></title><link>https://wiki.dgfygk.com/archives/d0efd942-9ed4-477c-bcd2-bcd62d7fb986</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8EGo%201.19%E7%9A%84%E7%AB%99%E7%82%B9%E6%A8%A1%E6%9D%BF%E7%88%AC%E8%99%AB&amp;url=/archives/d0efd942-9ed4-477c-bcd2-bcd62d7fb986" width="1" height="1" alt="" style="opacity:0;">
<p>基于Go 1.19的站点模板爬虫主要涉及创建HTTP连接、发送请求、解析HTML内容以及处理数据等步骤。下面我将详细介绍如何使用Go 1.19来编写一个简单的站点模板爬虫。</p>
<h4>一、环境准备</h4>
<p>首先，确保你的计算机上已安装Go 1.19或更高版本。你可以通过运行<code>go version</code>命令来检查当前安装的Go版本。</p>
<h4>二、编写爬虫程序</h4>
<h5>1. 导入必要的包</h5>
<p>你需要导入<code>net/http</code>包来发送HTTP请求，以及<code>golang.org/x/net/html</code>包来解析HTML内容。此外，<code>fmt</code>和<code>os</code>包也常用于输出和处理命令行参数。</p>
<pre><code class="prism language-go">import (
    "fmt"
    "net/http"
    "os"
    "golang.org/x/net/html"
)
</code></pre>
<h5>2. 发送HTTP请求并获取HTML内容</h5>
<p>编写一个函数来发送HTTP GET请求到目标URL，并返回HTML文档的根节点。这里使用<code>http.Get</code>函数来发送请求，并通过<code>html.Parse</code>函数解析响应体中的HTML内容。</p>
<pre><code class="prism language-go">func fetchURL(url string) (*html.Node, error) {

    resp, err := http.Get(url)
    if err != nil {

        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {

        return nil, fmt.Errorf("error getting URL %s: %s", url, resp.Status)
    }

    doc, err := html.Parse(resp.Body)
    if err != nil {

        return nil, fmt.Errorf("parsing HTML: %s", err)
    }
    return doc, nil
}
</code></pre>
<h5>3. 解析HTML并提取数据</h5>
<p>接下来，编写一个函数来遍历HTML文档，并提取所需的数据。例如，你可能想提取所有的超链接（即<code>&lt;a&gt;</code>标签的<code>href</code>属性）。</p>
<pre><code class="prism language-go">func extractLinks(n *html.Node) {

    if n.Type == html.ElementNode &amp;&amp; n.Data == "a" {

        for _, a := range n.Attr {

            if a.Key == "href" {

                fmt.Println(a.Val)
                break
            }
        }
    }
    for c := n.FirstChild; c != nil; c = c.NextSibling {

        extractLinks(c)
    }
}
</code></pre>
<h5>4. 主函数</h5>
<p>最后，编写<code>main</code>函数来调用上述函数，并处理命令行参数作为目标URL。</p>
<pre><code class="prism language-go">func main() {

    if len(os.Args) &lt; 2 {

        fmt.Println("Usage: go run main.go &lt;url&gt;")
        os.Exit(1)
    }
    url := os.Args[1]
    doc, err := fetchURL(url)
    if err != nil {

        fmt.Fprintln(os.Stderr, "Error fetching URL:", err)
        os.Exit(1)
    }
    extractLinks(doc)
}
</code></pre>
<h4>三、运行爬虫</h4>
<p>将上述代码保存为<code>main.go</code>文件，并在命令行中运行<code>go run main.go [URL]</code>，其中<code>[URL]</code>是你想要爬取的网站地址。程序将输出该网站首页上所有超链接的<code>href</code>属性。</p>
<h4>四、注意事项</h4>
<ol>
 <li><strong>遵守法律法规</strong> ：在使用爬虫时，务必遵守相关法律法规，以及网站的<code>robots.txt</code>文件规定。</li>
 <li><strong>尊重网站权利</strong>：尊重网站所有者的权利，不要过度爬取数据或对网站造成不必要的负担。</li>
 <li><strong>错误处理</strong>：在实际应用中，应增加更完善的错误处理逻辑，以应对各种异常情况。</li>
 <li><strong>性能优化</strong>：对于大规模爬取任务，可能需要考虑并发处理、缓存机制等性能优化措施。</li>
</ol>
<p>基于Go 1.19编写一个简单的站点模板爬虫，用于爬取并解析网页内容。
 <br>
 当然，我可以为你提供一个基于Go 1.19的简单站点模板爬虫示例。这个爬虫将使用<code>net/http</code>包来发送HTTP请求，使用<code>golang.org/x/net/html</code>包来解析HTML内容。在这个示例中，我们将爬取一个网页并打印出所有的<code>&lt;title&gt;</code>标签内容以及<code>&lt;a&gt;</code>标签的<code>href</code>属性。</p>
<p>首先，确保你已经安装了Go 1.19，并且你的工作环境中可以访问<code>golang.org/x/net/html</code>包。如果<code>golang.org/x/net/html</code>包没有自动安装，你可以通过运行<code>go get golang.org/x/net/html</code>来安装它。</p>
<p>下面是一个简单的站点模板爬虫示例：</p>
<pre><code class="prism language-go">package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"

    "golang.org/x/net/html"
)

// fetchAndParseHTML 发送HTTP请求，解析HTML，并调用回调函数处理节点
func fetchAndParseHTML(url string, fn func(*html.Node)) error {

    // 发送HTTP GET请求
    resp, err := http.Get(url)
    if err != nil {

        return err
    }
    defer resp.Body.Close()

    // 检查HTTP状态码
    if resp.StatusCode != http.StatusOK {

        return fmt.Errorf("HTTP error: %s", resp.Status)
    }

    // 读取并解析HTML
    doc, err := html.Parse(resp.Body)
    if err != nil {

        return err
    }

    // 调用回调函数处理文档节点
    fn(doc)

    return nil
}

// forEachNode 遍历HTML节点，并调用处理函数
func forEachNode(n *html.Node, process func(n *html.Node)) {

    if n == nil {

        return
    }
    process(n)
    for c := n.FirstChild; c != nil; c = c.NextSibling {

        forEachNode(c, process)
    }
}

// printInterestingTags 打印&lt;title&gt;和&lt;a&gt;标签
func printInterestingTags(n *html.Node) {

    if n.Type == html.ElementNode {

        switch n.Data {

        case "title":
            for c := n.FirstChild; c != nil; c = c.NextSibling {

                if c.Type == html.TextNode {

                    fmt.Println("Title:", c.Data)
                }
            }
        case "a":
            for _, a := range n.Attr {

                if a.Key == "href" {

                    fmt.Println("Link:", a.Val)
                }
            }
        }
    }
}

func main() {

    if len(os.Args) &lt; 2 {

        fmt.Println("Usage: go run main.go &lt;url&gt;")
        os.Exit(1)
    }

    url := os.Args[1]

    // 发送请求，解析HTML，并打印有趣的标签
    err := fetchAndParseHTML(url, func(doc *html.Node) {

        forEachNode(doc, printInterestingTags)
    })

    if err != nil {

        fmt.Fprintf(os.Stderr, "Error: %s\n", err)
        os.Exit(1)
    }
}
</code></pre>
<p>在这个示例中，<code>fetchAndParseHTML</code>函数负责发送HTTP请求、解析HTML，并接受一个回调函数来处理HTML节点。<code>forEachNode</code>函数是一个递归函数，用于遍历HTML树中的每个节点。<code>printInterestingTags</code>函数检查节点类型，并打印出<code>&lt;title&gt;</code>标签的内容和<code>&lt;a&gt;</code>标签的<code>href</code>属性。</p>
<p>要运行这个爬虫，请保存代码为<code>main.go</code>，并使用<code>go run main.go [URL]</code>命令，其中<code>[URL]</code>是你想要爬取的网页地址。爬虫将输出该网页的标题和所有链接的<code>href</code>属性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140541960</p>]]></description><guid isPermaLink="false">/archives/d0efd942-9ed4-477c-bcd2-bcd62d7fb986</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:47:36 GMT</pubDate></item><item><title><![CDATA[算法工程师面试题一]]></title><link>https://wiki.dgfygk.com/archives/bedb82f4-cb51-46c1-a3fe-1a9fc7c98f59</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%AE%97%E6%B3%95%E5%B7%A5%E7%A8%8B%E5%B8%88%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%80&amp;url=/archives/bedb82f4-cb51-46c1-a3fe-1a9fc7c98f59" width="1" height="1" alt="" style="opacity:0;">
<h2>数据库索引：请解释数据库索引的作用及其实现原理。</h2>
<h4>数据库索引的作用</h4>
<p>数据库索引在数据库管理系统中扮演着至关重要的角色，其主要作用可以归纳为以下几点：</p>
<ol>
 <li>
  <p><strong>提高查询速度</strong>：</p>
 </li>
 <li>
  <p>索引通过创建指向数据的指针或数据结构（如B树、哈希表等），使得数据库系统能够快速定位到所需数据，而无需遍历整个表。这在处理大量数据时尤其重要，可以显著减少查询时间，提高系统效率。</p>
 </li>
 <li>
  <p>索引支持各种查询操作，包括精确查询、范围查询、模糊查询等，通过优化查询路径，减少不必要的磁盘I/O操作。</p>
 </li>
 <li>
  <p><strong>减少磁盘I/O操作</strong>：</p>
 </li>
 <li>
  <p>索引存储了数据的指针和相关信息，数据库引擎可以直接通过索引访问数据存储位置，减少了全表扫描的需要，从而降低了磁盘I/O操作的次数，提高了系统的响应速度。</p>
 </li>
 <li>
  <p><strong>支持排序和分组</strong>：</p>
 </li>
 <li>
  <p>索引可以用于对数据进行排序和分组操作，而无需对整个表进行重新排序。这可以显著提高涉及排序和分组操作的查询性能。</p>
 </li>
 <li>
  <p><strong>提供唯一性约束</strong>：</p>
 </li>
 <li>
  <p>通过创建唯一索引，数据库系统可以自动检查插入和更新操作，确保某列或某些列的值在整个表中是唯一的，从而维护数据的完整性和一致性。</p>
 </li>
 <li>
  <p><strong>优化数据库性能</strong>：</p>
 </li>
 <li>
  <p>索引不仅提高了查询速度，还对整个数据库系统的性能产生了积极影响。在处理复杂查询、联接操作、排序和聚合函数时，索引能够显著提高这些操作的执行效率。</p>
 </li>
 <li>
  <p><strong>支持查询优化</strong>：</p>
 </li>
 <li>
  <p>数据库查询优化器可以利用索引信息来选择最优的查询执行计划，从而提高查询性能。例如，优化器可以决定是使用索引扫描还是全表扫描，以及选择何种联接算法等。</p>
 </li>
</ol>
<h4>数据库索引的实现原理</h4>
<p>数据库索引的实现原理主要基于特定的数据结构，如B树（B-Tree）及其变种B+树（B±Tree）、哈希表（Hashes）等。这些数据结构通过特定的查找算法，实现了对数据的快速定位。</p>
<ol>
 <li>
  <p><strong>B树和B+树</strong>：</p>
 </li>
 <li>
  <p>B树和B+树是数据库索引中最常用的数据结构之一。它们通过保持数据的排序和平衡，实现了对数据的快速查找、插入和删除操作。</p>
 </li>
 <li>
  <p>B树中的每个节点包含多个关键字和指向子节点的指针，这些关键字按照顺序排列。通过逐级向下查找，可以快速定位到目标数据。</p>
 </li>
 <li>
  <p>B+树是B树的一种变种，它在B树的基础上进行了优化。在B+树中，所有的数据都存储在叶子节点上，并且叶子节点之间通过指针相连形成链表，这有利于范围查询和区间访问。</p>
 </li>
 <li>
  <p><strong>哈希表</strong>：</p>
 </li>
 <li>
  <p>哈希表是另一种常用的索引数据结构，它通过哈希函数将关键字映射到表中的一个位置，从而实现对数据的快速查找。</p>
 </li>
 <li>
  <p>哈希表适用于精确查询，但在处理范围查询时可能不够高效。</p>
 </li>
 <li>
  <p><strong>索引的创建和维护</strong>：</p>
 </li>
 <li>
  <p>创建索引时，数据库系统会根据指定的列（或列组合）生成相应的索引数据结构，并将其存储在磁盘上。</p>
 </li>
 <li>
  <p>在数据库的使用过程中，索引需要随着数据的插入、删除和更新操作进行动态的维护，以确保索引的准确性和有效性。</p>
 </li>
 <li>
  <p><strong>索引的代价</strong>：</p>
 </li>
 <li>
  <p>索引虽然可以提高查询性能，但也会带来一定的代价。首先，索引需要占用额外的存储空间来存储索引数据结构本身。其次，索引的创建和维护需要消耗额外的计算资源，这可能会影响到数据库的写入性能。</p>
 </li>
</ol>
<p>综上所述，数据库索引通过特定的数据结构和查找算法，实现了对数据库数据的快速定位和访问，从而提高了数据库系统的整体性能。然而，在使用索引时也需要权衡其带来的好处和代价，以选择最适合当前应用场景的索引策略。</p>
<h2>分布式事务：在分布式系统中，如何保证事务的一致性和持久性？</h2>
<p>在分布式系统中，保证事务的一致性和持久性是确保系统稳定性和数据准确性的关键。以下是关于如何在分布式系统中保证事务一致性和持久性的详细解释：</p>
<h4>一、一致性（Consistency）</h4>
<p>在分布式系统中，一致性指的是在事务执行前后，系统的状态应该保持一致，即所有节点的数据都应该是最新的，并且反映事务的完整结果。</p>
<ol>
 <li>
  <p><strong>CAP理论</strong>：</p>
 </li>
 <li>
  <p>CAP理论指出，在分布式系统中，一致性（Consistency）、可用性（Availability）和分区容忍性（Partition Tolerance）这三个属性中，最多只能同时满足两个。由于分区容忍性在分布式系统中通常是必需的，因此系统设计师往往需要在一致性和可用性之间做出权衡。</p>
 </li>
 <li>
  <p>在需要强一致性的场景下，系统会牺牲一定的可用性，确保数据在所有节点上都是实时一致的。而在可以接受数据最终一致的场景下，系统会优先保证可用性和分区容忍性。</p>
 </li>
 <li>
  <p><strong>BASE理论</strong>：</p>
 </li>
 <li>
  <p>BASE理论是对CAP理论中一致性和可用性权衡的结果，它提出最终一致性（Eventually Consistency）的概念。即，系统允许在一段时间内数据状态的不一致，但最终必须达到一致。</p>
 </li>
 <li>
  <p>BASE理论包括三个要素：基本可用（Basically Available）、软状态（Soft State）和最终一致性（Eventually Consistent）。这种理论适用于许多不需要实时一致性的业务场景。</p>
 </li>
 <li>
  <p><strong>分布式事务协议</strong>：</p>
 </li>
 <li>
  <p><strong>两阶段提交（2PC）</strong>：是分布式系统中常用的协议，用于实现多个参与者之间的分布式事务的一致性。它包括准备阶段和提交阶段，通过协调者节点和参与者节点的交互，确保所有参与者要么全部提交事务，要么全部回滚事务。</p>
 </li>
 <li>
  <p><strong>三阶段提交（3PC）</strong>：是2PC的改进版本，通过增加预提交阶段来解决长时间阻塞和协调者单点故障的问题。</p>
 </li>
 <li>
  <p><strong>分布式事务解决方案</strong>：</p>
 </li>
 <li>
  <p><strong>TCC（Try-Confirm-Cancel）模式</strong>：通过Try阶段预留资源，Confirm阶段提交事务，Cancel阶段回滚事务的方式，确保分布式事务的一致性。这种模式对业务代码有一定的侵入性，但性能较高。</p>
 </li>
 <li>
  <p><strong>SAGA模式</strong>：适用于长事务场景，通过一系列本地事务和补偿操作来确保最终一致性。</p>
 </li>
</ol>
<h4>二、持久性（Durability）</h4>
<p>持久性指的是事务一旦提交，它对数据库的改变就应该是永久性的，即使系统发生故障也不会丢失数据。</p>
<ol>
 <li>
  <p><strong>数据库持久化机制</strong>：</p>
 </li>
 <li>
  <p>分布式系统中的数据库通常采用磁盘或SSD等非易失性存储设备来存储数据，确保数据在系统故障后仍然可以恢复。</p>
 </li>
 <li>
  <p>数据库会定期将内存中的数据写入磁盘，形成持久化的数据文件。同时，数据库还会采用日志（如redo log、undo log）来记录事务的操作和状态，以便在系统故障后进行恢复。</p>
 </li>
 <li>
  <p><strong>分布式事务的持久化</strong>：</p>
 </li>
 <li>
  <p>在分布式事务中，每个参与者节点都需要将事务的操作记录到本地数据库的日志中，并确保这些日志在系统故障后仍然可以恢复。</p>
 </li>
 <li>
  <p>协调者节点在收到所有参与者节点的提交响应后，会向所有参与者节点发送提交确认消息，确保所有参与者节点都完成了事务的持久化操作。</p>
 </li>
 <li>
  <p><strong>容错和恢复机制</strong>：</p>
 </li>
 <li>
  <p>分布式系统需要设计容错和恢复机制来应对各种故障情况，包括节点故障、网络故障等。通过冗余备份、故障转移、数据恢复等技术手段，确保系统在高可用性和数据持久性方面达到要求。</p>
 </li>
</ol>
<p>综上所述，在分布式系统中保证事务的一致性和持久性需要综合考虑CAP理论、BASE理论、分布式事务协议以及数据库持久化机制等多个方面。通过合理的设计和实现，可以确保分布式事务在复杂环境下的稳定性和可靠性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140512640</p>]]></description><guid isPermaLink="false">/archives/bedb82f4-cb51-46c1-a3fe-1a9fc7c98f59</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:47:21 GMT</pubDate></item><item><title><![CDATA[测试面试题（八）]]></title><link>https://wiki.dgfygk.com/archives/bbbb15d6-eeb6-4238-bda3-7e3f25c0e84c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%88%E5%85%AB%EF%BC%89&amp;url=/archives/bbbb15d6-eeb6-4238-bda3-7e3f25c0e84c" width="1" height="1" alt="" style="opacity:0;">
<h2>请描述一下你对API测试的理解，以及如何进行API测试。</h2>
<p>API（Application Programming Interface，应用程序编程接口）测试是软件测试的一个重要组成部分，它专注于测试应用程序之间的接口。这些接口允许不同的软件组件或系统之间进行通信和数据交换。API测试的目的是确保这些接口按照预期工作，能够正确接收请求、处理数据并返回正确的响应。</p>
<h4>对API测试的理解</h4>
<ol>
 <li><strong>独立性</strong>：API测试可以独立于前端应用程序进行，因此可以在前端开发完成之前开始测试工作，提高测试效率。</li>
 <li><strong>自动化</strong>：由于API测试通常基于HTTP请求和响应，因此非常适合自动化测试。自动化测试可以大大提高测试速度和一致性。</li>
 <li><strong>详细性</strong>：通过API测试，可以深入测试系统的各个部分，包括数据验证、错误处理、性能表现等。</li>
 <li><strong>集成性</strong>：API测试还可以帮助测试不同系统或服务之间的集成，确保它们能够无缝协作。</li>
</ol>
<h4>如何进行API测试</h4>
<ol>
 <li>
  <p><strong>需求分析</strong>：首先，需要明确测试的目标和范围，了解API的功能、输入输出参数、安全要求等。</p>
 </li>
 <li>
  <p><strong>获取API文档</strong>：获取并仔细阅读API文档，了解API的端点、请求方法、请求头、请求体、响应格式等信息。</p>
 </li>
 <li>
  <p><strong>设计测试用例</strong>：根据需求分析结果和API文档，设计覆盖各种场景的测试用例。这些场景包括正常操作、边界条件、异常处理等。</p>
 </li>
 <li>
  <p><strong>编写测试脚本</strong>：使用合适的测试工具（如Postman、RestAssured、JMeter等）编写测试脚本。测试脚本应包含API请求的构造、发送和响应验证。</p>
 </li>
 <li>
  <p><strong>执行测试</strong>：运行测试脚本，发送请求到API，并捕获响应。根据测试用例中的预期结果，验证响应的正确性。</p>
 </li>
 <li>
  <p><strong>记录和跟踪缺陷</strong>：如果测试发现任何问题或不符合预期的结果，应详细记录缺陷信息，并跟踪其修复过程。</p>
 </li>
 <li>
  <p><strong>性能测试</strong>：除了功能测试外，还可以对API进行性能测试，评估其在高负载下的表现。这包括响应时间、吞吐量、并发用户数等指标。</p>
 </li>
 <li>
  <p><strong>安全测试</strong>：安全测试也是API测试的一个重要方面。通过模拟攻击和漏洞扫描，检查API是否存在安全漏洞。</p>
 </li>
 <li>
  <p><strong>持续集成/持续部署（CI/CD）</strong>：将API测试集成到CI/CD流程中，实现测试的自动化和持续化。这有助于及时发现和修复问题，提高软件质量。</p>
 </li>
 <li>
  <p><strong>文档和报告</strong>：编写详细的测试文档和报告，记录测试过程、结果和发现的问题。这些文档和报告对于后续的测试工作、系统维护和升级都非常重要。</p>
 </li>
</ol>
<p>通过上述步骤，可以有效地进行API测试，确保系统的接口质量和稳定性。</p>
<h2>API测试有哪些常见的错误类型和防止措施呢</h2>
<p>API测试中的常见错误类型及其防止措施可以归纳如下：</p>
<h4>常见错误类型</h4>
<ol>
 <li>
  <p><strong>请求错误</strong>：</p>
 </li>
 <li>
  <p><strong>原因</strong>：客户端发送的请求格式不正确或参数缺失，如请求头信息不完整、请求体格式错误、请求参数格式不正确、类型不匹配或取值范围不符合API文档要求等。</p>
 </li>
 <li>
  <p><strong>示例</strong>：错误的HTTP方法使用、错误的URL路径、缺失必要的请求参数等。</p>
 </li>
 <li>
  <p><strong>响应错误</strong>：</p>
 </li>
 <li>
  <p><strong>原因</strong>：服务器端处理请求时出现问题，如服务器内部错误、数据库连接失败、业务逻辑错误等。</p>
 </li>
 <li>
  <p><strong>示例</strong>：服务器返回500 Internal Server Error、数据库查询错误导致的404 Not Found等。</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>：API需要身份验证或授权信息，但客户端提供的凭证无效或过期。</p>
 </li>
 <li>
  <p><strong>示例</strong>：API密钥无效、OAuth令牌过期等。</p>
 </li>
 <li>
  <p><strong>限制与配额问题</strong>：</p>
 </li>
 <li>
  <p><strong>原因</strong>：API请求超过了设定的限制或配额，如请求频率过高、达到API调用次数上限等。</p>
 </li>
 <li>
  <p><strong>示例</strong>：因请求过多被API提供商暂时封禁。</p>
 </li>
</ol>
<h4>防止措施</h4>
<ol>
 <li>
  <p><strong>请求错误防止措施</strong>：</p>
 </li>
 <li>
  <p><strong>检查请求格式和参数</strong>：确保请求符合API文档的要求，包括HTTP方法、URL路径、请求头和请求体等。</p>
 </li>
 <li>
  <p><strong>使用API测试工具</strong>：如Postman、RestAssured等，这些工具可以帮助构建和发送请求，并验证响应。</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>：确保网络连接稳定，可以尝试切换网络环境或使用VPN等工具。</p>
 </li>
 <li>
  <p><strong>增加重试机制</strong>：在客户端实现重试逻辑，当遇到网络错误时自动重试请求。</p>
 </li>
 <li>
  <p><strong>设置超时时间</strong>：为请求设置合理的超时时间，避免无限期等待响应。</p>
 </li>
 <li>
  <p><strong>认证与授权错误防止措施</strong>：</p>
 </li>
 <li>
  <p><strong>验证凭证有效性</strong>：在发送请求前验证API密钥、OAuth令牌等凭证的有效性。</p>
 </li>
 <li>
  <p><strong>处理凭证过期</strong>：当凭证过期时，及时刷新或重新获取凭证。</p>
 </li>
 <li>
  <p><strong>遵循认证协议</strong>：确保遵循API提供商的认证协议和流程。</p>
 </li>
 <li>
  <p><strong>限制与配额问题防止措施</strong>：</p>
 </li>
 <li>
  <p><strong>查看API文档</strong>：了解API的限制和配额规定，避免超出限制。</p>
 </li>
 <li>
  <p><strong>优化请求频率</strong>：合理安排请求频率，避免在短时间内发送过多请求。</p>
 </li>
 <li>
  <p><strong>申请更高配额</strong>：如果业务需求超过当前配额限制，可以向API提供商申请更高的配额。</p>
 </li>
</ol>
<p>综上所述，通过仔细规划、测试和维护API，可以大大降低错误发生的概率，并确保API的稳定性和可靠性。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140500163</p>]]></description><guid isPermaLink="false">/archives/bbbb15d6-eeb6-4238-bda3-7e3f25c0e84c</guid><dc:creator>黄健</dc:creator><category>测试</category><pubDate>Fri, 27 Sep 2024 01:47:15 GMT</pubDate></item><item><title><![CDATA[测试面试题（七）]]></title><link>https://wiki.dgfygk.com/archives/6d98ea73-aca7-4ff8-b3bd-abb7c01ac20c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%88%E4%B8%83%EF%BC%89&amp;url=/archives/6d98ea73-aca7-4ff8-b3bd-abb7c01ac20c" width="1" height="1" alt="" style="opacity:0;">
<h2>请描述一下你进行数据库测试的经验，包括哪些内容？</h2>
<p>进行数据库测试是确保数据库系统稳定性和数据完整性的关键步骤，它贯穿于数据库设计、开发、部署及运维的各个阶段。以下是我进行数据库测试时的一些经验和涵盖的主要内容：</p>
<h4>1. 需求分析验证</h4>
<ul>
 <li><strong>理解业务需求</strong>：首先，深入理解业务需求，确保对数据库应支持的功能、性能要求、安全需求等有清晰的认识。</li>
 <li><strong>数据模型验证</strong>：检查数据库设计（如ER图、数据表结构）是否准确反映了业务需求，包括数据类型、约束（如主键、外键、唯一约束、检查约束）、索引等的设计是否合理。</li>
</ul>
<h4>2. 单元测试</h4>
<ul>
 <li><strong>SQL语句测试</strong>：验证所有SQL语句（包括查询、插入、更新、删除等）的正确性和效率。确保它们能正确执行，并返回预期的结果。</li>
 <li><strong>存储过程与函数测试</strong>：测试数据库中的存储过程和函数，确保它们按预期逻辑执行，并处理各种边界条件和异常情况。</li>
 <li><strong>触发器测试</strong>：测试触发器是否正确响应数据变更事件，并产生预期的结果。</li>
</ul>
<h4>3. 集成测试</h4>
<ul>
 <li><strong>接口测试</strong>：测试数据库与应用程序之间的接口，确保数据能够正确地在数据库和应用程序之间传输。</li>
 <li><strong>事务完整性测试</strong>：验证事务的ACID属性（原子性、一致性、隔离性、持久性），确保事务处理正确无误。</li>
 <li><strong>并发测试</strong>：模拟多个用户同时访问数据库的场景，测试数据库的并发处理能力，确保数据一致性和系统稳定性。</li>
</ul>
<h4>4. 性能测试</h4>
<ul>
 <li><strong>基准测试</strong>：使用标准测试集对数据库进行基准测试，以评估其性能表现，如响应时间、吞吐量等。</li>
 <li><strong>压力测试</strong>：通过模拟大量并发请求或高负载情况，测试数据库在极端条件下的性能表现，识别潜在的性能瓶颈。</li>
 <li><strong>负载测试</strong>：在接近或达到数据库预期负载的情况下，评估其稳定性和性能。</li>
</ul>
<h4>5. 安全性测试</h4>
<ul>
 <li><strong>权限测试</strong>：验证数据库权限设置是否正确，确保只有授权用户才能访问敏感数据。</li>
 <li><strong>SQL注入测试</strong>：模拟SQL注入攻击，检查数据库是否能有效防御此类攻击。</li>
 <li><strong>数据加密测试</strong>：测试敏感数据（如密码、个人信息）的加密存储和传输机制是否安全有效。</li>
</ul>
<h4>6. 备份与恢复测试</h4>
<ul>
 <li><strong>备份测试</strong>：验证数据库的备份策略是否有效，能够定期备份数据，并确保备份数据的完整性和可恢复性。</li>
 <li><strong>恢复测试</strong>：模拟数据丢失或损坏的场景，测试从备份中恢复数据的过程，确保恢复操作能够顺利进行并恢复所有关键数据。</li>
</ul>
<h4>7. 兼容性测试</h4>
<ul>
 <li><strong>跨平台测试</strong>：如果数据库需要在不同操作系统或硬件平台上运行，需要进行跨平台测试，确保数据库在这些平台上的兼容性和稳定性。</li>
 <li><strong>版本兼容性测试</strong>：测试数据库新版本与旧版本之间的兼容性，确保升级过程中数据不会丢失或损坏。</li>
</ul>
<h4>8. 回归测试</h4>
<ul>
 <li>在进行任何修改（如代码更新、配置变更）后，重新执行之前的测试用例，确保修改没有引入新的问题。</li>
</ul>
<p>通过上述内容的测试，可以全面评估数据库的质量，确保其满足业务需求，并在各种情况下都能稳定运行。</p>
<h2>你如何保持自己在测试领域的竞争力？</h2>
<p>在测试领域保持竞争力是一个持续学习和不断适应变化的过程。以下是一些关键策略，可以帮助我保持在这一领域的竞争力：</p>
<ol>
 <li>
  <p><strong>持续学习新技术</strong>：测试领域不断发展，新的工具、框架、方法和最佳实践不断涌现。我会定期关注行业动态，学习新技术和工具，如自动化测试框架（Selenium、Appium、Postman等）、性能测试工具（LoadRunner、JMeter等）、API测试工具（RestAssured、Postman等）、持续集成/持续部署（CI/CD）工具（Jenkins、GitLab CI/CD等）以及敏捷和DevOps等现代软件开发方法。</p>
 </li>
 <li>
  <p><strong>深化专业知识</strong>：除了广泛涉猎新技术外，我还会深化自己在特定领域的专业知识，比如性能测试、安全测试、自动化测试或特定行业的测试需求。通过深入研究和实践，我可以成为该领域的专家，并提供更有价值的见解和解决方案。</p>
 </li>
 <li>
  <p><strong>提升软技能</strong>：除了技术技能外，良好的沟通和协作能力、问题解决能力、批判性思维和领导力等软技能也是测试人员不可或缺的。我会通过参与团队项目、跨部门合作、公开演讲和写作等方式来提升自己的软技能。</p>
 </li>
 <li>
  <p><strong>关注质量和最佳实践</strong>：测试的核心是确保软件质量。我会持续关注质量标准和最佳实践，如ISO 29119软件测试国际标准、敏捷测试宣言等，并将它们应用到实际工作中。同时，我也会积极参与质量改进活动，如代码审查、测试案例优化、缺陷预防等。</p>
 </li>
 <li>
  <p><strong>建立专业网络</strong>：与同行建立联系和交流对于保持竞争力至关重要。我会通过参加行业会议、研讨会、线上社区和社交媒体平台等方式来扩展自己的人脉网络，与同行分享经验、交流见解并寻求合作机会。</p>
 </li>
 <li>
  <p><strong>适应变化</strong>：测试领域面临着快速变化的环境，包括技术更新、市场需求变化、开发流程变革等。我会保持开放的心态，积极适应这些变化，并主动寻找新的机会和挑战。</p>
 </li>
 <li>
  <p><strong>关注用户反馈</strong>：最终用户是软件质量的最终评判者。我会密切关注用户反馈，了解他们的需求和痛点，并将其作为改进测试工作的依据。通过关注用户反馈，我可以更好地理解市场需求和趋势，并提升测试工作的有效性和针对性。</p>
 </li>
</ol>
<p>综上所述，保持自己在测试领域的竞争力需要持续学习、深化专业知识、提升软技能、关注质量和最佳实践、建立专业网络、适应变化以及关注用户反馈等多方面的努力。这些策略将帮助我不断提升自己的竞争力和价值。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140499681</p>]]></description><guid isPermaLink="false">/archives/6d98ea73-aca7-4ff8-b3bd-abb7c01ac20c</guid><dc:creator>黄健</dc:creator><category>测试</category><pubDate>Fri, 27 Sep 2024 01:47:07 GMT</pubDate></item><item><title><![CDATA[数据结构与算法 —— DFS的定义与原理]]></title><link>https://wiki.dgfygk.com/archives/064b6392-58b1-4952-9988-e5a95d6fb0c3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%20%E2%80%94%E2%80%94%20DFS%E7%9A%84%E5%AE%9A%E4%B9%89%E4%B8%8E%E5%8E%9F%E7%90%86&amp;url=/archives/064b6392-58b1-4952-9988-e5a95d6fb0c3" width="1" height="1" alt="" style="opacity:0;">
<p>DFS（Distributed File System，分布式文件系统）是一种允许网络连接的多个计算机之间共享信息的系统架构。与传统的文件系统（如NTFS、HFS等）不同，DFS分布在多个文件服务器或多个位置，通过计算机网络进行通信或交换信息。以下是对DFS的详细定义与原理阐述，包括其基本概念、工作原理、关键特性、应用场景以及实现方式等方面。</p>
<h4>一、DFS的定义</h4>
<p>**分布式文件系统（DFS）**是一种特殊的文件系统，它不再局限于单一物理存储介质，而是通过网络将分散在多个节点（可以是服务器、工作站或其他计算设备）上的存储资源逻辑上组织成一个统一的文件系统。DFS允许用户像访问本地文件一样透明地访问存储在网络上的文件，无论这些文件实际存储在哪个节点上。这种架构极大地提高了数据的可用性、可靠性和扩展性。</p>
<h4>二、DFS的工作原理</h4>
<h5>1. <strong>文件系统结构</strong></h5>
<p>DFS为分布在网络上任意位置的资源提供一个逻辑上的树形文件系统结构。在这个结构中，每个节点可以代表一个物理存储设备（如硬盘）、一个逻辑分区或一个远程服务器上的共享文件夹。用户通过DFS提供的统一命名空间来访问这些资源，而无需关心资源的实际物理位置。</p>
<h5>2. <strong>元数据管理</strong></h5>
<p>在DFS中，元数据（metadata）扮演着至关重要的角色。元数据描述了数据的结构、位置、状态等信息，是DFS实现透明访问和数据一致性的基础。DFS系统需要维护一个全局的元数据目录，记录每个文件或目录的逻辑位置与物理位置之间的映射关系。当用户访问DFS中的文件时，DFS首先查询元数据目录以确定文件的实际存储位置，然后将访问请求转发到相应的节点。</p>
<h5>3. <strong>数据访问与同步</strong></h5>
<p>DFS支持多种数据访问模式，包括直接访问、缓存访问和回调访问等。直接访问模式下，用户直接通过网络访问存储在远程节点上的数据；缓存访问模式下，DFS会在本地缓存部分常用数据以提高访问速度；回调访问模式下，当文件发生更改时，DFS会通知所有持有该文件副本的节点进行同步更新。</p>
<h5>4. <strong>容错与恢复</strong></h5>
<p>为了提高系统的可靠性，DFS通常采用数据冗余和容错机制。例如，通过在多个节点上复制文件副本、使用校验和验证数据完整性、实现自动故障转移等方式来确保数据的安全性和可用性。当某个节点发生故障时，DFS能够自动将访问请求重定向到其他健康节点上，从而保持服务的连续性。</p>
<h4>三、DFS的关键特性</h4>
<ol>
 <li><strong>透明性</strong>：DFS实现了访问透明性和命名透明性，用户无需关心数据的实际存储位置即可像访问本地文件一样访问网络上的文件。</li>
 <li><strong>可扩展性</strong>：DFS能够轻松扩展以容纳更多的存储节点和更大的数据量，满足不断增长的业务需求。</li>
 <li><strong>高可用性</strong>：通过数据冗余和容错机制，DFS能够提供高可用性的服务保障，减少因单点故障导致的服务中断。</li>
 <li><strong>性能优化</strong>：DFS通过并行处理、缓存技术和负载均衡等策略来提高数据访问性能，满足高性能应用的需求。</li>
 <li><strong>安全性</strong>：DFS支持多种安全机制，如访问控制列表（ACLs）、加密传输等，以保护用户数据的安全性和隐私性。</li>
</ol>
<h4>四、DFS的应用场景</h4>
<p>DFS广泛应用于各种需要大规模数据存储和共享的场景中，如：</p>
<ol>
 <li><strong>大数据处理</strong>：在大数据处理领域，DFS为海量数据的存储和分析提供了高效的支持。通过分布式存储和并行处理技术，DFS能够显著提高数据处理的速度和效率。</li>
 <li><strong>云计算</strong>：在云计算平台中，DFS作为底层存储架构之一，为虚拟机、容器等计算资源提供统一的存储服务。通过DFS的透明访问和动态扩展特性，云计算平台能够灵活地管理存储资源并满足用户的多样化需求。</li>
 <li><strong>内容分发网络（CDN）</strong>：在CDN领域，DFS通过在网络边缘部署缓存节点来加速用户访问速度并减轻源站压力。DFS的透明访问特性使得用户无需关心内容的具体存储位置即可快速获取所需内容。</li>
 <li><strong>企业存储解决方案</strong>：在企业环境中，DFS可以作为企业存储解决方案的一部分来提供统一的文件共享和协作平台。通过DFS的权限管理和安全特性，企业可以轻松地管理员工对敏感数据的访问权限并确保数据的安全性。</li>
</ol>
<h4>五、DFS的实现方式</h4>
<p>DFS的实现方式多种多样，包括开源和商业解决方案等。以下是一些常见的DFS实现方式：</p>
<ol>
 <li><strong>GlusterFS</strong>：GlusterFS是一种开源的可扩展分布式文件系统，它通过TCP或InfiniBand高速网络将多个服务器上的磁盘资源有效整合在一起并提供一个统一的全局命名空间。GlusterFS支持多种卷类型和丰富的配置选项以满足不同场景下的需求。</li>
 <li><strong>HDFS（Hadoop Distributed File System）</strong>：HDFS是Hadoop生态系统中的分布式文件系统组件之一，专为大规模数据集上的批处理作业设计。HDFS通过高容错性、高吞吐量和可扩展性等特点来支持大规模数据集的存储和处理。</li>
 <li><strong>Ceph</strong>：Ceph是一个统一的分布式存储系统，它提供了对象存储、块存储和文件系统存储等多种存储接口。Ceph具有高可靠性、高可扩展性和高性能等特点，并且支持多种部署模式和存储介质以满足不同场景下的需求。</li>
</ol>
<p>综上所述，DFS作为一种先进的存储架构在多个领域得到了广泛应用并取得了显著成效。随着技术的不断发展和应用场景的不断拓展，DFS将继续发挥其优势为用户提供更加高效、可靠和灵活的存储解决方案。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140401168</p>]]></description><guid isPermaLink="false">/archives/064b6392-58b1-4952-9988-e5a95d6fb0c3</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:46:31 GMT</pubDate></item><item><title><![CDATA[前端测试框架（如Jest、Mocha、Jasmine）特点及优势]]></title><link>https://wiki.dgfygk.com/archives/55fe3874-8fe3-4517-b7f9-dc942263fc96</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%89%8D%E7%AB%AF%E6%B5%8B%E8%AF%95%E6%A1%86%E6%9E%B6%EF%BC%88%E5%A6%82Jest%E3%80%81Mocha%E3%80%81Jasmine%EF%BC%89%E7%89%B9%E7%82%B9%E5%8F%8A%E4%BC%98%E5%8A%BF&amp;url=/archives/55fe3874-8fe3-4517-b7f9-dc942263fc96" width="1" height="1" alt="" style="opacity:0;">
<h2>前端测试框架（如Jest、Mocha、Jasmine）特点和优势。</h2>
<p>在前端开发中，测试是一个非常重要的环节，它确保了代码的质量和稳定性。关于前端测试框架，我虽然没有直接的使用经验，但可以根据业界普遍认可和广泛使用的前端测试框架，如Jest、Mocha和Jasmine，来谈谈它们的特点和优势。</p>
<h4>Jest</h4>
<p><strong>特点与优势</strong>：</p>
<ol>
 <li><strong>零配置测试平台</strong>：Jest是一个集成了断言库、模拟（mocking）库、测试覆盖率报告等多种功能的测试框架，几乎可以做到开箱即用，减少了配置时间。</li>
 <li><strong>快照测试</strong>：Jest支持快照测试（Snapshot Testing），能够自动捕获组件的UI状态并生成快照文件，当组件的UI发生变化时，Jest会通知开发者，从而帮助开发者快速发现潜在的问题。</li>
 <li><strong>丰富的API</strong>：Jest提供了丰富的API，包括模拟函数（mock functions）、定时器模拟（timer mocks）等，方便开发者进行各种复杂的测试场景。</li>
 <li><strong>社区活跃</strong>：Jest拥有庞大的用户社区和丰富的生态系统，开发者可以轻松找到所需的插件和解决方案。</li>
</ol>
<h4>Mocha</h4>
<p><strong>特点与优势</strong>：</p>
<ol>
 <li><strong>灵活性强</strong>：Mocha是一个灵活的测试框架，它本身不提供断言库或模拟库，但可以与任何流行的断言库（如Chai）和模拟库（如Sinon）结合使用，从而满足不同的测试需求。</li>
 <li><strong>异步测试支持</strong>：Mocha原生支持异步测试，可以方便地处理异步代码（如回调函数、Promises、async/await等）的测试。</li>
 <li><strong>易于集成</strong>：Mocha可以轻松集成到各种构建工具和编辑器中，如Webpack、Gulp、Visual Studio Code等。</li>
 <li><strong>报告详细</strong>：Mocha提供了详细的测试报告，包括测试进度、测试结果等信息，方便开发者快速定位问题。</li>
</ol>
<h4>Jasmine</h4>
<p><strong>特点与优势</strong>：</p>
<ol>
 <li><strong>行为驱动开发（BDD）</strong>：Jasmine是一个基于行为驱动开发（BDD）的测试框架，它鼓励开发者以"给定（Given）-当（When）-那么（Then）“的方式来编写测试用例，从而使测试代码更加易于理解和维护。</li>
 <li><strong>丰富的断言库</strong>：Jasmine提供了丰富的断言库，包括布尔断言、数组断言、字符串断言等多种类型，方便开发者进行各种类型的断言操作。</li>
 <li><strong>易于上手</strong>：Jasmine的语法简洁明了，易于上手，特别是对于初学者来说非常友好。</li>
 <li><strong>集成Angular</strong>：由于Jasmine是由Angular团队开发的，因此它与Angular框架的集成非常紧密，是Angular项目中最常用的测试框架之一。</li>
</ol>
<h4>总结</h4>
<p>Jest、Mocha和Jasmine都是前端开发中非常流行的测试框架，它们各有特点和优势。Jest以其零配置、快照测试等功能而备受青睐；Mocha则以其灵活性和异步测试支持而著称；Jasmine则以其BDD风格和与Angular的紧密集成而受到开发者的喜爱。开发者可以根据项目的具体需求和个人偏好来选择合适的测试框架。</p>
<h2>具体示例</h2>
<p>当然，我可以为Jest、Mocha和Jasmine分别提供一些示例。请注意，这些示例旨在展示每个框架的基本用法和特点，而不是完整的测试场景。</p>
<h4>Jest 示例</h4>
<p>Jest的一个典型用法是测试一个简单的函数，并验证其行为。以下是一个使用Jest编写的测试用例示例：</p>
<pre><code class="prism language-javascript">// 假设我们有一个加法函数
function add(a, b) {

  return a + b;
}

// 使用Jest测试该函数
test('adds 1 + 2 to equal 3', () =&gt; {

  expect(add(1, 2)).toBe(3);
});

// Jest还支持异步测试，例如使用Promises
test('resolves to correct value', async () =&gt; {

  const promise = Promise.resolve(3);
  expect(await promise).toBe(3);
});

// 快照测试示例，通常用于React组件
// 假设我们有一个React组件，并希望测试其渲染结果
// 注意：这里需要@testing-library/react等库来支持React组件的渲染和快照
// import renderer from '@testing-library/react-test-renderer';
// import MyComponent from './MyComponent';

// test('renders correctly', () =&gt; {

//   const component = renderer.create(&lt;MyComponent /&gt;);
//   const tree = component.toJSON();
//   expect(tree).toMatchSnapshot();
// });
</code></pre>
<h4>Mocha 示例</h4>
<p>Mocha通常与断言库（如Chai）一起使用。以下是一个使用Mocha和Chai编写的测试用例示例：</p>
<pre><code class="prism language-javascript">const chai = require('chai');
const expect = chai.expect;

// 假设我们有一个加法函数
function add(a, b) {

  return a + b;
}

// 使用Mocha和Chai测试该函数
describe('add function', () =&gt; {

  it('should add two numbers', () =&gt; {

    expect(add(1, 2)).to.equal(3);
  });

  // 异步测试示例
  it('should handle asynchronous operations', (done) =&gt; {

    setTimeout(() =&gt; {

      expect(add(2, 3)).to.equal(5);
      done();
    }, 100);
  });
});
</code></pre>
<h4>Jasmine 示例</h4>
<p>Jasmine遵循BDD（行为驱动开发）风格，以下是一个使用Jasmine编写的测试用例示例：</p>
<pre><code class="prism language-javascript">// 假设我们有一个加法函数
function add(a, b) {

  return a + b;
}

// 使用Jasmine测试该函数
describe("add function", function() {

  it("should add two numbers", function() {

    expect(add(1, 2)).toEqual(3);
  });

  // 异步测试示例，注意Jasmine v3.x及更高版本推荐使用done回调或async/await
  it("should handle asynchronous operations", function(done) {

    setTimeout(function() {

      expect(add(2, 3)).toEqual(5);
      done();
    }, 100);
  });

  // 或者使用async/await（需要Jasmine 3.x及以上版本，并配置适当的支持）
  // 注意：以下代码示例假设环境已配置为支持async/await
  // it("should handle asynchronous operations with async/await", async function() {

  //   await new Promise(resolve =&gt; setTimeout(resolve, 100));
  //   expect(add(2, 3)).toEqual(5);
  // });
});
</code></pre>
<p>请注意，对于Jasmine的异步测试示例，我提供了两种可能的实现方式：一种是使用<code>done</code>回调，这是Jasmine 2.x及早期版本中的常见做法；另一种是使用<code>async/await</code>（但需要Jasmine 3.x及以上版本，并且测试环境需要相应配置以支持Promise和async/await）。然而，由于我无法直接检查您当前的Jasmine版本和配置，因此我提供了两种可能的示例。在实际应用中，您应该根据您的Jasmine版本和测试环境选择适当的异步测试方法。</p>
<p>另外，请注意，对于React组件的快照测试，Jest示例中提到的<code>@testing-library/react-test-renderer</code>是一个流行的库，它提供了将React组件渲染为JSON树并生成快照的功能。然而，在Jasmine或Mocha环境中，您可能需要使用其他库（如<code>enzyme</code>结合<code>enzyme-to-json</code>）或手动实现快照逻辑来支持类似的功能。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140437939</p>]]></description><guid isPermaLink="false">/archives/55fe3874-8fe3-4517-b7f9-dc942263fc96</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:46:23 GMT</pubDate></item><item><title><![CDATA[数据结构与算法 —— DFS的实现方法（递归与迭代）]]></title><link>https://wiki.dgfygk.com/archives/92ea6328-4e25-455e-af79-98d504cb8766</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%20%E2%80%94%E2%80%94%20DFS%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95%EF%BC%88%E9%80%92%E5%BD%92%E4%B8%8E%E8%BF%AD%E4%BB%A3%EF%BC%89&amp;url=/archives/92ea6328-4e25-455e-af79-98d504cb8766" width="1" height="1" alt="" style="opacity:0;">
<p>在讨论文件系统（File System，简称FS）的实现方法时，特别是关注于递归与迭代这两种编程范式，我们实际上是在探讨如何在编程层面上对文件系统进行操作，如遍历目录、创建多级目录等。虽然文件系统的底层实现（如FAT32、NTFS、ext4等）复杂且通常不由应用开发者直接操作，但我们可以从应用层面讨论如何使用递归与迭代来操作文件系统。</p>
<h4>一、递归实现方法</h4>
<p>递归是一种在编程中解决问题的方法，它通过函数自我调用的方式来解决问题。在文件系统的操作中，递归常用于遍历目录树、创建多级目录等场景。</p>
<h5>1. 遍历目录树</h5>
<p>遍历目录树是文件系统操作中的一个常见任务，递归是实现这一任务的自然选择。通过递归调用，程序可以深入目录的每一层，访问并处理其中的文件和子目录。</p>
<p><strong>示例代码</strong>（伪代码）：</p>
<pre><code class="prism language-pseudo">function traverseDirectory(directoryPath) {
    // 获取指定目录下的所有文件和子目录
    filesAndDirectories = listDirectoryContents(directoryPath)

    foreach item in filesAndDirectories {
        if item is directory {
            // 如果是目录，则递归遍历
            traverseDirectory(item.path)
        } else {
            // 如果是文件，则进行相应处理
            processFile(item.path)
        }
    }
}
</code></pre>
<h5>2. 创建多级目录</h5>
<p>在文件系统中创建多级目录时，递归同样是一种直观且强大的方法。通过递归调用，程序可以逐步创建每一级目录，直到达到目标路径。</p>
<p><strong>示例代码</strong>（Node.js中的fs模块）：</p>
<pre><code class="prism language-javascript">const fs = require('fs');
const path = require('path');

function createDirectoryRecursive(dirPath) {

    if (!fs.existsSync(dirPath)) {

        // 如果目录不存在，则创建父目录（递归调用）
        createDirectoryRecursive(path.dirname(dirPath));
        // 创建当前目录
        fs.mkdirSync(dirPath);
    }
}

// 使用示例
createDirectoryRecursive('a/b/c');
</code></pre>
<p>注意：从Node.js v10.12.0开始，<code>fs.mkdir()</code>方法支持<code>{recursive: true}</code>选项，可以一次性创建多级目录，无需递归。但了解递归实现方法对于理解编程逻辑和解决问题仍然是有益的。</p>
<h4>二、迭代实现方法</h4>
<p>迭代是另一种解决问题的方法，它通过循环结构（如for循环、while循环）来重复执行代码块，直到满足某个条件为止。在文件系统的操作中，迭代常用于遍历文件列表、按序处理文件等场景。</p>
<h5>1. 遍历文件列表</h5>
<p>当需要遍历一个目录下的所有文件（不包括子目录中的文件）时，迭代是一个简单直接的选择。程序可以通过循环结构逐一访问并处理文件列表中的每个文件。</p>
<p><strong>示例代码</strong>（伪代码）：</p>
<pre><code class="prism language-pseudo">function traverseFilesInDirectory(directoryPath) {
    files = listFilesInDirectory(directoryPath)

    for each file in files {
        processFile(file.path)
    }
}
</code></pre>
<h5>2. 迭代创建多级目录（非直接，但可通过辅助结构实现）</h5>
<p>虽然迭代不是创建多级目录的自然选择（因为目录的层级结构天然适合递归处理），但可以通过一些辅助结构（如栈）来实现迭代创建多级目录的效果。不过，这种方法相对复杂且不如递归直观。</p>
<p>在实际应用中，更常见的是使用迭代结合文件系统提供的API（如Node.js中的<code>fs.mkdir({recursive: true})</code>）来一次性创建多级目录，而不是手动实现迭代的创建过程。</p>
<h4>三、递归与迭代的比较</h4>
<p>在文件系统操作中，递归和迭代各有优缺点：</p>
<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>
</ul>
<p>综上所述，递归和迭代在实现文件系统操作时各有适用场景。开发者应根据具体问题的特点和需求选择合适的实现方法。对于Node.js等现代编程环境，通常可以利用标准库提供的高级API（如<code>fs.mkdir({recursive: true})</code>）来简化操作，而无需手动实现递归或迭代逻辑。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140401253</p>]]></description><guid isPermaLink="false">/archives/92ea6328-4e25-455e-af79-98d504cb8766</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:46:01 GMT</pubDate></item><item><title><![CDATA[数据结构与算法 —— Transformers之Pipeline]]></title><link>https://wiki.dgfygk.com/archives/5a2312f1-0b06-42ef-be2f-a3e04abe9206</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%20%E2%80%94%E2%80%94%20Transformers%E4%B9%8BPipeline&amp;url=/archives/5a2312f1-0b06-42ef-be2f-a3e04abe9206" width="1" height="1" alt="" style="opacity:0;">
<p>Transformers之Pipeline是Hugging Face Transformers库中提供的一种使用预训练模型进行推理的极简方式。这些Pipeline对象从库中抽象出大部分复杂代码，为多项任务（如命名实体识别、情感分析、特征提取和问答等）提供了简单的API。以下是对Transformers之Pipeline的详细介绍：</p>
<h4>一、Pipeline概述</h4>
<p>Pipeline是Transformers库中的一个基础功能，它封装了预训练模型和对应的前处理与后处理环节。用户只需输入数据，Pipeline就能自动完成预处理、模型推理和后处理，最终返回易于理解的结果。这种方式极大地简化了模型的使用流程，使得非专业用户也能轻松上手。</p>
<h4>二、Pipeline支持的任务类型</h4>
<p>Transformers库将目前的NLP任务归纳为以下几大类，并提供了相应的Pipeline支持：</p>
<ul>
 <li><strong>文本分类</strong>：如情感分析、句子对关系判断等。</li>
 <li><strong>词语分类</strong>：如词性标注（POS）、命名实体识别（NER）等。</li>
 <li><strong>文本生成</strong>：如填充预设的模板（prompt）、预测文本中被遮掩（masked）的词语、文本翻译、自动摘要等。</li>
 <li><strong>问答系统</strong>：如根据给定的问题从一段文本中抽取出对应的答案。</li>
 <li><strong>其他任务</strong>：还包括特征提取、零训练样本分类、图像分类、自动语音识别等。</li>
</ul>
<p>目前常用的Pipeline包括但不限于：</p>
<ul>
 <li><code>feature-extraction</code>：获得文本的向量化表示。</li>
 <li><code>fill-mask</code>：填充被遮盖的词或片段。</li>
 <li><code>ner</code>（命名实体识别）。</li>
 <li><code>question-answering</code>：自动问答。</li>
 <li><code>sentiment-analysis</code>：情感分析。</li>
 <li><code>summarization</code>：自动摘要。</li>
 <li><code>text-generation</code>：文本生成。</li>
 <li><code>translation</code>：机器翻译。</li>
 <li><code>zero-shot-classification</code>：零训练样本分类。</li>
</ul>
<h4>三、Pipeline的创建与使用</h4>
<p>创建和使用Pipeline非常简单，主要有两种方法：</p>
<ol>
 <li>
  <p><strong>根据任务类型直接创建Pipeline</strong>：</p>
  <pre><code class="prism language-python">from transformers import pipeline
classifier = pipeline("sentiment-analysis")
result = classifier("I've been waiting for a HuggingFace course my whole life.")
print(result)
</code></pre>
 </li>
 <li>
  <p><strong>指定任务类型和模型创建Pipeline</strong>：</p>
  <pre><code class="prism language-python">from transformers import pipeline
classifier = pipeline("text-classification", model="uer/roberta-base-finetuned-dianping-chinese")
result = classifier("我觉得不太行!")
print(result)
</code></pre>
 </li>
</ol>
<h4>四、Pipeline的高级功能</h4>
<p>Pipeline还支持一些高级功能，如批处理、GPU推理、自定义后处理等。例如，可以通过设置<code>device</code>参数将Pipeline绑定到GPU上进行推理，以提高处理速度。</p>
<h4>五、Pipeline的实现原理</h4>
<p>Pipeline的实现原理大致可以分为三个步骤：</p>
<ol>
 <li><strong>数据预处理</strong>：将输入数据转换为模型可以理解的格式，如文本分词、编码等。</li>
 <li><strong>模型推理</strong>：将预处理后的数据送入预训练模型进行推理。</li>
 <li><strong>后处理</strong>：对模型的输出进行后处理，如解码、格式化等，最终返回易于理解的结果。</li>
</ol>
<h4>六、总结</h4>
<p>Transformers之Pipeline是Hugging Face Transformers库中提供的一种非常实用的功能，它极大地简化了预训练模型的使用流程，使得用户能够轻松地进行各种NLP任务的推理。无论是专业研究人员还是非专业用户，都可以通过Pipeline快速上手并应用这些强大的预训练模型。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140401492</p>]]></description><guid isPermaLink="false">/archives/5a2312f1-0b06-42ef-be2f-a3e04abe9206</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:45:53 GMT</pubDate></item><item><title><![CDATA[数据结构与算法 ——DFS的应用场景]]></title><link>https://wiki.dgfygk.com/archives/5c48a3b0-81b2-4fe9-9c97-dbe4198634a9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%20%E2%80%94%E2%80%94DFS%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF&amp;url=/archives/5c48a3b0-81b2-4fe9-9c97-dbe4198634a9" width="1" height="1" alt="" style="opacity:0;">
<p>DFS（Depth-First Search，深度优先搜索）是一种用于遍历或搜索树或图的算法。它通过从起始节点开始，沿着一条路径尽可能深地搜索，直到达到叶子节点或无法继续为止，然后回溯到上一个节点，继续探索其他未遍历的路径。DFS算法在多个领域有着广泛的应用场景，以下将详细探讨其常见应用场景。</p>
<h4>一、图的遍历</h4>
<p>DFS算法最直接的应用之一就是图的遍历。无论是无向图还是有向图，DFS都能有效地遍历图中的每个节点，确保每个节点都被访问一次且仅被访问一次。在图的遍历过程中，DFS可以用来查找图中的路径、环、连通分量等结构信息。</p>
<h4>二、拓扑排序</h4>
<p>对于有向无环图（DAG），DFS可以用来进行拓扑排序。拓扑排序是将DAG中的所有顶点排成一个线性序列，使得对于任意一条从顶点u到顶点v的有向边(u, v)，顶点u在序列中都出现在顶点v之前。这种排序对于任务调度、编译器中的依赖关系解析等场景非常有用。</p>
<h4>三、连通性分析</h4>
<p>DFS还可以用于判断图的连通性，即检查图中的所有节点是否可以通过一系列边相互到达。对于无向图，如果从一个节点出发的DFS遍历能够访问到图中的所有节点，则说明该图是连通的。对于有向图，则可能需要从多个节点出发进行DFS遍历，或者利用强连通分量（SCC）的概念来判断图的连通性。</p>
<h4>四、迷宫求解</h4>
<p>DFS算法是解决迷宫问题的一种有效方法。迷宫可以看作是一个由墙壁和通道组成的二维网格图，起点和终点分别位于网格的某个位置。通过DFS算法，可以从起点开始，尝试沿着通道深入探索，直到找到终点或所有可能的路径都被探索完毕。DFS在迷宫求解中的应用展示了其在路径查找问题中的强大能力。</p>
<h4>五、社交网络分析</h4>
<p>在社交网络领域，DFS算法可以用于模拟信息的传播过程。以某个用户为起点，通过DFS算法可以模拟信息在其关注者之间的传播路径，从而分析信息在社交网络中的扩散速度和影响力。此外，DFS还可以用于社交网络中的社区发现、关键节点识别等任务。</p>
<h4>六、数独求解</h4>
<p>数独是一种经典的逻辑填数游戏，玩家需要根据给定的数字线索，在9x9的网格中填入1到9的数字，使得每行、每列以及九个3x3的子网格中的数字都不重复。DFS算法可以通过递归地尝试在每个空格中填入可能的数字，并验证填入的数字是否满足数独的规则，从而求解数独问题。虽然DFS在求解大规模数独时可能效率不高，但它提供了一种直观且易于实现的求解方法。</p>
<h4>七、图像处理</h4>
<p>在图像处理领域，DFS算法可以用于分析图像中的连通区域。通过将图像中的像素点看作图中的节点，相邻的像素点之间建立边，就可以将图像转换为一个图结构。然后，利用DFS算法可以遍历图像中的每个连通区域，提取出图像中的目标对象或特征。</p>
<h4>八、网络安全</h4>
<p>在网络安全领域，DFS算法可以用于漏洞探测和安全审计。通过模拟攻击者的行为，以网络中的各个节点或服务作为起点，利用DFS算法可以探测网络中的漏洞和弱点，从而加强网络的安全性。此外，DFS还可以用于分析网络的拓扑结构、识别网络中的关键节点和链路等任务。</p>
<h4>九、生物信息学</h4>
<p>在生物信息学研究中，DFS算法可以应用于序列比对和基因组匹配等任务。通过DFS算法可以搜索基因序列中的相似片段和保守区域，进而揭示物种之间的进化关系和功能相似性。此外，DFS还可以用于蛋白质结构预测、RNA结构预测等领域。</p>
<h4>十、自动驾驶与路径规划</h4>
<p>在自动驾驶和路径规划领域，DFS算法可以用于规划车辆或机器人的行驶路径。通过DFS算法可以搜索地图中的各个可能路径，并考虑交通规则、障碍物等因素来评估路径的可行性和安全性。虽然DFS在路径规划中可能不是最高效的算法（因为它可能会探索大量不必要的路径），但在某些特定场景下（如地图规模较小或障碍物较少时），DFS仍然可以提供一种可行的解决方案。</p>
<p>综上所述，DFS算法在图的遍历、拓扑排序、连通性分析、迷宫求解、社交网络分析、数独求解、图像处理、网络安全、生物信息学以及自动驾驶与路径规划等多个领域都有着广泛的应用场景。这些应用场景不仅展示了DFS算法的多样性和灵活性，也体现了其在解决实际问题中的重要作用。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140401322</p>]]></description><guid isPermaLink="false">/archives/5c48a3b0-81b2-4fe9-9c97-dbe4198634a9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:45:45 GMT</pubDate></item><item><title><![CDATA[在JavaScript中，如何实现异步编程？]]></title><link>https://wiki.dgfygk.com/archives/58518ff4-0c74-4927-9660-98e71def1bc5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8JavaScript%E4%B8%AD%EF%BC%8C%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%EF%BC%9F&amp;url=/archives/58518ff4-0c74-4927-9660-98e71def1bc5" width="1" height="1" alt="" style="opacity:0;">
<h2>在JavaScript中，如何实现异步编程？请列举几种常见的方法（如Promise、async/await）。</h2>
<p>在JavaScript中，异步编程是处理长时间运行的任务（如网络请求、文件读写、大量计算等）的关键。JavaScript是单线程的，但通过使用异步编程模式，我们可以编写出既不会阻塞主线程执行又能处理耗时任务的代码。以下是几种在JavaScript中实现异步编程的常见方法：</p>
<h4>1. 回调函数（Callbacks）</h4>
<p>回调函数是最早也是最直观的异步编程模式。当一个函数作为参数传递给另一个函数，并在后者执行完成时被调用，这就是回调函数。然而，回调函数可能导致"回调地狱”（Callback Hell），使代码难以阅读和维护。</p>
<pre><code class="prism language-javascript">function fetchData(callback) {

    setTimeout(() =&gt; {

        const data = "数据加载完成";
        callback(data);
    }, 1000);
}

fetchData(data =&gt; {

    console.log(data);
});
</code></pre>
<h4>2. Promise</h4>
<p>Promise是ES6引入的一种异步编程解决方案，它代表了一个最终可能完成（fulfilled）或失败（rejected）的异步操作及其结果值。使用Promise可以使得异步代码更加清晰和易于管理。</p>
<pre><code class="prism language-javascript">function fetchData() {

    return new Promise((resolve, reject) =&gt; {

        setTimeout(() =&gt; {

            const data = "数据加载完成";
            resolve(data);
        }, 1000);
    });
}

fetchData().then(data =&gt; {

    console.log(data);
}).catch(error =&gt; {

    console.error(error);
});
</code></pre>
<h4>3. async/await</h4>
<p><code>async</code>和<code>await</code>是建立在Promise之上的更高层次的抽象，用于以同步的方式编写异步代码。<code>async</code>关键字用于声明一个异步函数，该函数会隐式地返回一个Promise。<code>await</code>关键字用于等待一个Promise解决，并且它只能在<code>async</code>函数内部使用。</p>
<pre><code class="prism language-javascript">async function fetchData() {

    try {

        const data = await new Promise((resolve) =&gt; {

            setTimeout(() =&gt; {

                resolve("数据加载完成");
            }, 1000);
        });
        console.log(data);
    } catch (error) {

        console.error(error);
    }
}

fetchData();
</code></pre>
<h4>4. Generators（生成器）</h4>
<p>虽然Generators本身并不是专为异步编程设计的，但它们可以被用来处理异步操作，通过结合<code>yield</code>关键字和某些库（如co）来暂停和恢复函数的执行。然而，随着<code>async/await</code>的普及，使用Generators进行异步编程的做法已经不那么常见了。</p>
<h4>5. 事件监听（Event Listeners）</h4>
<p>虽然不直接用于控制函数执行的流程，但事件监听是实现异步行为的一种常见方式。通过监听DOM事件、自定义事件或第三方库/框架提供的事件，可以在特定事件发生时执行代码。</p>
<h4>结论</h4>
<p>在JavaScript中，有多种方法可以实现异步编程，每种方法都有其适用场景。<code>Promise</code>和<code>async/await</code>是现代JavaScript开发中推荐使用的异步编程模式，因为它们提供了更清晰、更易于维护的代码结构。</p>
<h2>其他一些流行的异步编程方式是什么？</h2>
<p>在JavaScript中，除了Promise和async/await这两种非常流行的异步编程方式外，还有其他几种也值得提及的异步编程方法。这些方法各有特点，适用于不同的场景和需求。以下是几种流行的异步编程方式：</p>
<h4>1. 回调函数（Callbacks）</h4>
<p><strong>特点</strong>：</p>
<ul>
 <li>最早的异步编程模式。</li>
 <li>通过将耗时操作放在回调函数中，并在操作完成后调用回调函数来处理结果。</li>
 <li>容易产生"回调地狱”（Callback Hell）问题，即多层嵌套的回调函数导致代码难以阅读和维护。</li>
</ul>
<p><strong>适用场景</strong>：</p>
<ul>
 <li>简单的异步操作，不需要复杂的流程控制。</li>
</ul>
<h4>2. Generators（生成器）</h4>
<p><strong>特点</strong>：</p>
<ul>
 <li>ES6提供的一种异步编程解决方案。</li>
 <li>通过<code>yield</code>关键字在函数执行过程中暂停和恢复，允许通过<code>next()</code>方法控制函数的执行流程。</li>
 <li>可以与特定的库（如co）结合使用来实现异步控制流。</li>
</ul>
<p><strong>适用场景</strong>：</p>
<ul>
 <li>需要更精细控制异步流程的场景。</li>
 <li>但随着<code>async/await</code>的普及，Generators在异步编程中的使用逐渐减少。</li>
</ul>
<h4>3. Observables（可观察对象）</h4>
<p><strong>特点</strong>：</p>
<ul>
 <li>一种表示可观察的数据流的对象。</li>
 <li>允许添加和移除观察者，并在数据发生变化时通知所有观察者。</li>
 <li>常用于处理异步事件流，如RxJS库提供了丰富的操作符来处理Observable。</li>
</ul>
<p><strong>适用场景</strong>：</p>
<ul>
 <li>需要处理复杂异步数据流或事件流的场景。</li>
 <li>特别是在响应式编程和前端框架（如Angular）中广泛使用。</li>
</ul>
<h4>4. 事件监听（Event Listeners）</h4>
<p><strong>特点</strong>：</p>
<ul>
 <li>采用事件驱动模式，任务的执行不取决于代码的顺序，而取决于某个事件是否发生。</li>
 <li>通过为特定事件添加监听器（Listener）来处理异步操作的结果。</li>
</ul>
<p><strong>适用场景</strong>：</p>
<ul>
 <li>需要在特定事件发生时执行异步操作的场景。</li>
 <li>如DOM事件处理、WebSocket通信等。</li>
</ul>
<h4>5. 发布/订阅模式（Publish/Subscribe）</h4>
<p><strong>特点</strong>：</p>
<ul>
 <li>又称观察者模式（Observer Pattern）。</li>
 <li>存在一个"信号中心”，任务执行完成后向中心"发布"信号，其他任务可以向中心"订阅"该信号以知道何时可以开始执行。</li>
</ul>
<p><strong>适用场景</strong>：</p>
<ul>
 <li>需要实现松耦合的组件间通信的场景。</li>
 <li>如全局状态管理、跨组件通信等。</li>
</ul>
<h4>总结</h4>
<p>在JavaScript中，除了Promise和async/await外，回调函数、Generators、Observables、事件监听和发布/订阅模式也是流行的异步编程方式。每种方式都有其特点和适用场景，开发者可以根据具体需求选择最合适的方法。随着JavaScript语言的发展，<code>async/await</code>和Observables等现代异步编程方式正逐渐成为主流。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140437638</p>]]></description><guid isPermaLink="false">/archives/58518ff4-0c74-4927-9660-98e71def1bc5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Fri, 27 Sep 2024 01:45:36 GMT</pubDate></item><item><title><![CDATA[python热门面试题三]]></title><link>https://wiki.dgfygk.com/archives/ea7ea841-e240-4f39-9cbf-1410f7c9768f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=python%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%89&amp;url=/archives/ea7ea841-e240-4f39-9cbf-1410f7c9768f" width="1" height="1" alt="" style="opacity:0;">
<h4>面试题1：Python中的列表推导式是什么？请给出一个例子。</h4>
<p><strong>回答</strong>：</p>
<p>列表推导式（List Comprehension）是Python中一种非常强大且简洁的构建列表的工具。它允许你通过一个表达式来创建一个新的列表，这个表达式定义了新列表中的元素应该如何从旧列表或其他可迭代对象中派生出来。列表推导式通常比使用循环来构建列表更加简洁和易读。</p>
<p>例子：</p>
<p>假设我们有一个数字列表，我们想创建一个新列表，其中包含原列表中每个数字的平方。使用列表推导式，我们可以这样做：</p>
<pre><code class="prism language-python">original_list = [1, 2, 3, 4, 5]
squared_list = [x**2 for x in original_list]
print(squared_list)
</code></pre>
<p>这段代码会输出：<code>[1, 4, 9, 16, 25]</code>。这里，<code>[x**2 for x in original_list]</code>就是一个列表推导式，它遍历<code>original_list</code>中的每个元素<code>x</code>，计算<code>x</code>的平方，并将结果收集到一个新的列表中。</p>
<h4>面试题2：解释一下Python中的装饰器是什么，并给出一个使用装饰器的例子。</h4>
<p><strong>回答</strong>：</p>
<p>装饰器是Python中的一个高级且非常有用的功能，它允许你在不修改原有函数代码的情况下，为函数添加新的功能。装饰器本质上是一个函数，它接受一个函数作为参数，并返回一个新的函数。这个新的函数通常会在原有函数的基础上增加一些额外的功能，比如日志记录、性能测试、事务处理、缓存等。</p>
<p>使用装饰器的好处是，你可以将额外的功能与原有的函数逻辑分离，使得代码更加清晰和易于维护。同时，装饰器也提高了代码的复用性，因为你可以将同一个装饰器应用到多个不同的函数上。</p>
<p>例子：</p>
<p>下面是一个使用装饰器的例子，我们定义了一个<code>timer</code>装饰器，用于测量函数的执行时间。</p>
<pre><code class="prism language-python">import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{
     func.__name__} executed in {
     end_time - start_time} seconds.")
        return result
    return wrapper

@timer
def some_function(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

print(some_function(1000000))
</code></pre>
<p>在这段代码中，<code>timer</code>是一个装饰器，它接受一个函数<code>func</code>作为参数，并返回一个新的函数<code>wrapper</code>。<code>wrapper</code>函数在调用原始函数<code>func</code>之前和之后分别记录时间，从而测量函数的执行时间。</p>
<p><code>@timer</code>语法是Python提供的一种方便的应用装饰器的方式，它相当于<code>some_function = timer(some_function)</code>。这样，当我们调用<code>some_function(1000000)</code>时，实际上调用的是<code>wrapper</code>函数，它会测量<code>some_function</code>的执行时间并打印出来。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140395034</p>]]></description><guid isPermaLink="false">/archives/ea7ea841-e240-4f39-9cbf-1410f7c9768f</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Fri, 27 Sep 2024 01:45:17 GMT</pubDate></item><item><title><![CDATA[python热门面试题五]]></title><link>https://wiki.dgfygk.com/archives/276bbc65-3c0e-4910-972b-d74e76c30b00</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=python%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%A2%98%E4%BA%94&amp;url=/archives/276bbc65-3c0e-4910-972b-d74e76c30b00" width="1" height="1" alt="" style="opacity:0;">
<p>当然，以下是两个Python经典面试题及其详细回答：</p>
<h4>面试题1：Python中的深拷贝和浅拷贝有什么区别？请举例说明。</h4>
<p><strong>回答</strong>：</p>
<p>在Python中，深拷贝（deep copy）和浅拷贝（shallow copy）是两种不同的拷贝机制，它们用于创建对象的副本。</p>
<ul>
 <li>
  <p>浅拷贝：创建一个新对象，但它包含的是对原始对象中包含项的引用，而不是项本身的副本。这意味着，如果原始对象包含如列表这样的可变项，则新对象和原始对象将共享这些内部项，对其中一个所做的更改将影响到另一个。</p>
 </li>
 <li>
  <p>深拷贝：创建一个新对象，同时递归地复制其包含的所有项和子项。这意味着，副本和原始对象将不共享任何子对象，对副本所做的更改不会影响到原始对象。</p>
 </li>
</ul>
<p><strong>例子</strong>：</p>
<pre><code class="prism language-python">import copy

# 原始列表
original_list = [[1, 2, 3], [4, 5, 6]]

# 浅拷贝
shallow_copied_list = copy.copy(original_list)

# 深拷贝
deep_copied_list = copy.deepcopy(original_list)

# 修改原始列表的一个子列表
original_list[0][0] = "X"

print("Original List:", original_list)
print("Shallow Copied List:", shallow_copied_list)
print("Deep Copied List:", deep_copied_list)
</code></pre>
<p><strong>输出</strong>：</p>
<pre><code>Original List: [['X', 2, 3], [4, 5, 6]]
Shallow Copied List: [['X', 2, 3], [4, 5, 6]]
Deep Copied List: [[1, 2, 3], [4, 5, 6]]
</code></pre>
<p>在这个例子中，修改原始列表的一个子列表也影响了浅拷贝的列表，因为它们共享子列表。而深拷贝的列表则不受影响，因为它创建了子列表的副本。</p>
<h4>面试题2：解释一下Python中的GIL（Global Interpreter Lock），以及它对Python多线程的影响。</h4>
<p><strong>回答</strong>：</p>
<p>GIL（Global Interpreter Lock）是Python解释器中的一个锁，它用于保证在同一时刻只有一个线程在执行Python字节码。这是因为Python的内存管理并不是线程安全的，所以GIL是必要的，以防止多个线程同时执行Python代码时导致数据竞争和破坏。</p>
<p>然而，GIL的存在也意味着Python的多线程并不是真正的并行执行。即使在多核处理器上，由于GIL的存在，Python线程也只能在解释器级别上串行执行。这导致了所谓的"线程饥饿"问题，其中一个线程可能会因为无法获得GIL而长时间等待，即使有其他核心空闲。</p>
<p>为了解决这个问题，Python提供了一些机制来绕过GIL，例如使用多进程而不是多线程，或者使用特定的库（如<code>numpy</code>）来释放GIL并执行真正的并行计算。此外，还有一些尝试去除GIL或减轻其影响的Python实现，如Jython和IronPython，但它们并不是Python的官方实现。</p>
<p>总的来说，GIL是Python多线程性能的一个瓶颈，开发者在使用Python进行多线程编程时需要意识到这一点，并考虑使用其他并行编程方法或工具来提高性能。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140395171</p>]]></description><guid isPermaLink="false">/archives/276bbc65-3c0e-4910-972b-d74e76c30b00</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Fri, 27 Sep 2024 01:45:04 GMT</pubDate></item><item><title><![CDATA[JAVA热门面试题二]]></title><link>https://wiki.dgfygk.com/archives/af824b4c-ee76-4a80-a5f4-aa32f0899161</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JAVA%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%A2%98%E4%BA%8C&amp;url=/archives/af824b4c-ee76-4a80-a5f4-aa32f0899161" width="1" height="1" alt="" style="opacity:0;">
<h2>. Java中怎样将bytes转换为long类型？</h2>
<p>在Java中，将<code>byte</code>数组（<code>byte[]</code>）转换为<code>long</code>类型，特别是当你想将这个数组看作是一个大整数（可能是有符号或无符号的）时，需要一些额外的步骤，因为<code>byte</code>数组和<code>long</code>类型在内存中的表示方式并不完全相同。特别是，<code>byte</code>是8位有符号整数，而<code>long</code>是64位有符号整数。</p>
<p>下面是一些常见的场景和对应的解决方案：</p>
<h4>1. 假设bytes数组表示一个64位的整数（有符号或无符号）</h4>
<p>如果你有一个<code>byte[]</code>数组，它正好包含8个字节，并且你希望将这个数组转换为一个<code>long</code>类型的值，你可以使用<code>ByteBuffer</code>类或者位操作来实现。</p>
<h5>使用<code>ByteBuffer</code></h5>
<pre><code class="prism language-java">import java.nio.ByteBuffer;

public class BytesToLong {

    public static long bytesToLong(byte[] bytes) {

        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        return buffer.getLong();
    }

    public static void main(String[] args) {

        byte[] bytes = new byte[] {
   0x12, 0x34, 0x56, 0x78, (byte)0x9A, (byte)0xBC, (byte)0xDE, (byte)0xF0};
        long value = bytesToLong(bytes);
        System.out.println(Long.toHexString(value)); // 输出：123456789abcdef0
    }
}
</code></pre>
<p>注意：<code>ByteBuffer.getLong()</code>方法默认使用大端字节序（big-endian）。如果你的数据是小端字节序（little-endian），你需要先调用<code>buffer.order(ByteOrder.LITTLE_ENDIAN)</code>。</p>
<h5>使用位操作</h5>
<p>如果你想要更底层地控制转换过程，或者出于某种原因不想使用<code>ByteBuffer</code>，你可以使用位操作来手动构建<code>long</code>值。但这种方法更加复杂且容易出错，通常不推荐除非有特别的需求。</p>
<h4>2. 假设bytes数组表示一个更短的整数，但需要扩展为long</h4>
<p>如果<code>byte[]</code>数组的长度小于8个字节，但你仍然想将其转换为一个<code>long</code>类型，你需要决定如何处理额外的位（通常是填充0或符号扩展）。</p>
<h5>填充0</h5>
<p>如果你想要将较短的<code>byte</code>数组视为无符号整数并转换为<code>long</code>（填充剩余的位为0），你可以通过位移和或操作来实现。</p>
<h5>符号扩展</h5>
<p>如果你想要将较短的<code>byte</code>数组视为有符号整数并转换为<code>long</code>（进行符号扩展），你需要根据最高位（符号位）来决定是填充0还是1。</p>
<h4>结论</h4>
<p>在大多数情况下，使用<code>ByteBuffer</code>是转换<code>byte[]</code>到<code>long</code>的最简单和最安全的方法。如果你有特殊的需求（如小端字节序或特定的填充/符号扩展逻辑），你可能需要编写额外的代码来处理这些情况。</p>
<h2>我们能将int强制转换为byte类型的变量吗？如果该值大于byte类型的范围，将会出现什么现象？</h2>
<p>在Java中，你可以将<code>int</code>类型强制转换为<code>byte</code>类型的变量，但这是一个潜在的危险操作，因为<code>int</code>类型占用32位，而<code>byte</code>类型仅占用8位。这意味着<code>int</code>类型可以表示的值范围远大于<code>byte</code>类型。</p>
<p>当你尝试将一个<code>int</code>值强制转换为<code>byte</code>时，Java会执行所谓的"窄化原始类型转换”（narrowing primitive conversion）。在这个过程中，Java会简单地将<code>int</code>值的高24位丢弃，只保留最低的8位作为<code>byte</code>类型的值。</p>
<p>如果<code>int</code>值大于<code>byte</code>类型的范围（即大于127或小于-128，因为<code>byte</code>是有符号的，范围是-128到127），则结果将是一个在<code>byte</code>范围内但可能不是你预期的值。具体来说，它将是原始<code>int</code>值模256（因为2^8=256）的结果。</p>
<p>例如：</p>
<pre><code class="prism language-java">int intValue = 257; // 二进制表示为 0000 0001 0000 0001
byte byteValue = (byte) intValue; // 执行窄化原始类型转换
// byteValue 现在是 1，因为 257 % 256 = 1

intValue = -1; // 二进制表示为 1111 1111 1111 1111
byteValue = (byte) intValue; // 执行窄化原始类型转换
// byteValue 现在是 -1，因为 -1 的二进制表示（补码形式）的低8位仍然是 1111 1111，这等于 -1
</code></pre>
<p>在上面的例子中，你可以看到，当<code>int</code>值大于<code>byte</code>的最大值时，通过模256操作，它"回绕"到<code>byte</code>的表示范围内。类似地，负数的补码形式在转换为<code>byte</code>时也会保留其最低的8位，这可能会产生预期之外的结果（但在二进制层面是准确的）。</p>
<p>因此，当你需要执行此类转换时，应该格外小心，并确保了解结果如何被计算和表示。如果你在处理数据范围敏感的逻辑（如文件I/O、网络通信等），这种转换可能会引入错误或导致意外的行为。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140285954</p>]]></description><guid isPermaLink="false">/archives/af824b4c-ee76-4a80-a5f4-aa32f0899161</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Fri, 27 Sep 2024 01:03:52 GMT</pubDate></item><item><title><![CDATA[JAVA热门面试题一]]></title><link>https://wiki.dgfygk.com/archives/1d4a9b29-5acc-498c-87ce-7430f68d03ff</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JAVA%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%80&amp;url=/archives/1d4a9b29-5acc-498c-87ce-7430f68d03ff" width="1" height="1" alt="" style="opacity:0;">
<h2>Java中应该使用什么数据类型来代表价格？</h2>
<p>在Java中，选择哪种数据类型来代表价格取决于你的具体需求，比如价格的精度、范围以及是否需要处理货币单位的计算等。下面是一些常见的选择：</p>
<ol>
 <li>
  <p><strong><code>int</code> 或 <code>long</code></strong> ：
   <br>
   如果你不需要处理小数点后的数值（即只处理整数金额，比如以分为单位），那么可以使用<code>int</code>或<code>long</code>类型。这种方式的优点是整数运算通常比浮点数运算更快且更精确，缺点是它需要你手动处理货币单位（比如，总是以分为单位存储和计算）。</p>
 </li>
 <li>
  <p><strong><code>float</code> 或 <code>double</code></strong> ：
   <br>
   对于需要处理小数点后数值的场景，<code>float</code>或<code>double</code>是常见的选择。然而，需要注意的是，浮点数运算可能会引入精度问题，特别是在金融计算中，这可能会导致不可接受的结果。因此，尽管它们易于使用，但在处理货币时通常不是首选。</p>
 </li>
 <li>
  <p><strong><code>BigDecimal</code></strong> ：
   <br>
   <code>BigDecimal</code>是Java中用于精确小数计算的类，非常适合于金融和商业计算。它提供了对任意精度的十进制数的支持，并且能够执行精确的算术运算。使用<code>BigDecimal</code>可以避免浮点数运算中的精度问题，但相应的，它的性能可能会比基本数据类型稍差，并且使用起来也更复杂一些。</p>
 </li>
 <li>
  <p><strong><code>java.util.Currency</code> 和 <code>BigDecimal</code></strong> ：
   <br>
   如果你需要处理不同货币的价格，并且希望代码能够清晰地表达这一点，那么可以结合使用<code>java.util.Currency</code>枚举和<code>BigDecimal</code>来表示金额。<code>Currency</code>枚举提供了对货币的支持，包括ISO 4217货币代码和符号等，而<code>BigDecimal</code>则用于表示具体的金额数值。</p>
 </li>
</ol>
<p>总结来说，对于大多数需要处理价格的场景，推荐使用<code>BigDecimal</code>，因为它能够提供精确的小数计算，非常适合金融和商业应用。如果你确信价格总是整数（比如以分为单位），并且不需要处理不同的货币，那么使用<code>int</code>或<code>long</code>类型也是可行的。然而，在处理货币时，应该尽量避免使用<code>float</code>或<code>double</code>类型，因为它们可能会引入精度问题。</p>
<h2>怎么将byte转换为String？</h2>
<p>在Java中，将<code>byte</code>数组（<code>byte[]</code>）转换为<code>String</code>是一个常见的操作，尤其是当你从文件、网络或数据库等来源读取二进制数据时。这里有两种主要的方法来完成这个任务，具体取决于数据的编码方式。</p>
<h4>1. 使用<code>String</code>的构造函数</h4>
<p>如果你知道数据的编码（如UTF-8, ISO-8859-1等），你可以使用<code>String</code>的构造函数之一，它接受一个<code>byte</code>数组和一个字符集编码。</p>
<pre><code class="prism language-java">public String byteArrayToString(byte[] bytes, String encoding) {

    try {

        return new String(bytes, encoding);
    } catch (UnsupportedEncodingException e) {

        // 处理异常，比如返回null或抛出RuntimeException
        throw new RuntimeException("Unsupported encoding: " + encoding, e);
    }
}

// 使用示例
byte[] bytes = ...; // 你的字节数组
String str = byteArrayToString(bytes, "UTF-8");
</code></pre>
<h4>2. 使用<code>StandardCharsets</code>（Java 7及以上）</h4>
<p>如果你使用的是Java 7或更高版本，可以使用<code>StandardCharsets</code>类来获取预定义的字符集，这样你就不需要担心<code>UnsupportedEncodingException</code>异常了，因为<code>StandardCharsets</code>中的字符集总是被支持的。</p>
<pre><code class="prism language-java">public String byteArrayToString(byte[] bytes) {

    return new String(bytes, StandardCharsets.UTF_8);
}

// 或者，如果你知道使用的是其他编码
// return new String(bytes, StandardCharsets.ISO_8859_1);

// 使用示例
byte[] bytes = ...; // 你的字节数组
String str = byteArrayToString(bytes);
</code></pre>
<h4>注意</h4>
<ul>
 <li>在进行字节到字符串的转换时，确保你使用的字符集编码与原始字节数据的编码一致，否则可能会得到乱码。</li>
 <li>如果你不确定数据的编码，可能需要根据上下文进行猜测，或者询问数据的来源以获取正确的编码信息。</li>
 <li>在处理网络数据或文件时，有时数据的编码会在数据本身中指定（如HTTP响应头中的<code>Content-Type</code>字段），这时你可以根据这些信息来确定正确的编码。</li>
 <li>对于二进制数据（如图片、音频文件等），直接将其转换为字符串通常没有意义，因为这样的数据不是为了被解释成文本而设计的。在这些情况下，你应该直接处理字节数据，或者将其转换为其他格式（如Base64字符串），以便在需要文本表示时使用。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140285868</p>]]></description><guid isPermaLink="false">/archives/1d4a9b29-5acc-498c-87ce-7430f68d03ff</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 10:02:31 GMT</pubDate></item><item><title><![CDATA[Python热门面试题二]]></title><link>https://wiki.dgfygk.com/archives/502cf5af-7846-47e3-856d-881418d22f24</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Python%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%A2%98%E4%BA%8C&amp;url=/archives/502cf5af-7846-47e3-856d-881418d22f24" width="1" height="1" alt="" style="opacity:0;">
<h2>如何选择正确的Python版本？</h2>
<p>选择正确的Python版本是一个需要综合考虑多个因素的过程。以下是一些关键的建议，帮助您做出决策：</p>
<h4>一、项目需求</h4>
<ol>
 <li>
  <p><strong>依赖关系</strong>：首先，要考虑您的项目是否依赖于特定的Python版本或第三方库。某些库或框架可能仅支持特定的Python版本。在这种情况下，您需要选择与这些依赖项兼容的Python版本。</p>
 </li>
 <li>
  <p><strong>兼容性</strong>：如果您的项目需要与现有的系统或库进行交互，确保所选的Python版本与这些系统或库兼容也是非常重要的。</p>
 </li>
</ol>
<h4>二、社区支持和生态系统</h4>
<ol>
 <li>
  <p><strong>社区支持</strong>：较新的Python版本通常会得到更多的社区支持，包括安全更新和Bug修复。选择社区活跃且支持良好的版本可以降低潜在的安全风险。</p>
 </li>
 <li>
  <p><strong>生态系统</strong>：Python拥有丰富的第三方库和工具生态系统。确保您所需的库和工具都能在您选择的Python版本下正常运行。一些老旧的库可能不再支持较旧的Python版本，而新的库可能仅支持较新的版本。</p>
 </li>
</ol>
<h4>三、版本稳定性</h4>
<ol>
 <li><strong>稳定性</strong>：虽然较新的版本可能包含更多的功能和改进，但它们也可能包含尚未发现的Bug。如果您希望避免潜在的运行时问题，可以选择经过时间验证的稳定版本。</li>
</ol>
<h4>四、未来可维护性</h4>
<ol>
 <li><strong>长期支持</strong>：选择一个在未来一段时间内仍将继续获得支持的Python版本可以确保项目的长期可维护性。避免选择已经停止支持的版本，因为它们可能不再接收安全更新和Bug修复。</li>
</ol>
<h4>五、具体版本建议</h4>
<ol>
 <li>
  <p><strong>对于新项目</strong>：如果没有特定的依赖关系限制，通常建议选择最新的Python 3.x版本。Python 3是未来的方向，具有更好的特性和社区支持。</p>
 </li>
 <li>
  <p><strong>对于旧项目</strong>：如果项目当前在较旧的Python版本上运行，并且没有迫切的升级需求，可以先保持现状。但是，考虑到安全性和可维护性，建议逐步迁移到较新的Python版本。</p>
 </li>
 <li>
  <p><strong>特定版本选择</strong>：在选择具体版本时，可以参考Python官方网站上的发布说明和版本历史记录。了解每个版本的主要改进、新增功能和已知问题有助于您做出更明智的决策。</p>
 </li>
</ol>
<h4>六、其他注意事项</h4>
<ol>
 <li>
  <p><strong>操作系统兼容性</strong>：确保下载的Python版本与您的操作系统兼容。Python提供了Windows、Mac OS和Linux等多个操作系统的版本。</p>
 </li>
 <li>
  <p><strong>安装和配置</strong>：在安装Python时，可以选择自定义安装以添加所需的功能和工具。同时，将Python添加到系统路径中可以方便地在命令行中调用Python。</p>
 </li>
</ol>
<p>综上所述，选择正确的Python版本需要综合考虑项目需求、社区支持、生态系统、版本稳定性和未来可维护性等多个因素。对于大多数新项目来说，选择最新的Python 3.x版本是一个不错的选择。然而，具体选择哪个版本还需根据您的实际情况和需求来决定。</p>
<h2>简述Python的内存管理机制</h2>
<p>Python的内存管理机制是一个复杂但高效的系统，它主要负责动态内存分配、释放以及垃圾回收等工作，确保程序能够高效、稳定地运行。以下是对Python内存管理机制的简要概述：</p>
<h4>1. 内存分配与释放</h4>
<ul>
 <li><strong>动态内存分配</strong>：Python在运行时动态分配和管理内存，无需开发者显式地进行内存分配和释放操作。这种机制大大简化了内存管理的复杂性，提高了开发效率。</li>
 <li><strong>堆内存</strong>：Python中的大多数对象和数据结构都存储在堆内存中。堆内存用于动态分配内存，对象的内存不会自动释放，需要垃圾收集器来管理。</li>
 <li><strong>栈内存</strong>：栈内存则用于存储函数调用过程中创建的局部变量。当函数调用结束时，这些局部变量会被自动销毁，释放栈内存。</li>
</ul>
<h4>2. 引用计数</h4>
<ul>
 <li><strong>核心机制</strong>：Python使用引用计数来跟踪对象的生命周期。每当一个对象被引用时，其引用计数增加；当引用失效或销毁时，引用计数减少。当引用计数降至零时，Python会自动回收该对象的内存。</li>
 <li><strong>示例</strong> ：创建一个列表对象<code>a = []</code>，此时引用计数为1。当执行<code>b = a</code>时，<code>a</code>和<code>b</code>都引用同一个列表对象，引用计数增加到2。如果执行<code>del a</code>，引用计数减少到1；再执行<code>del b</code>，引用计数变为0，此时列表对象的内存将被释放。</li>
</ul>
<h4>3. 垃圾回收器</h4>
<ul>
 <li><strong>处理循环引用</strong>：尽管引用计数机制非常有效，但它无法处理循环引用的情况。循环引用是指两个或多个对象相互引用，导致它们的引用计数永远不为零，但实际上已经没有任何有效的引用指向这些对象。</li>
 <li><strong>标记-清除算法</strong>：Python的垃圾回收器使用标记-清除算法来检测和回收循环引用中的对象。垃圾回收器会遍历所有的根对象，标记它们及其可达的对象，然后清除未被标记的对象，并释放它们占用的内存空间。</li>
</ul>
<h4>4. 内存池</h4>
<ul>
 <li><strong>管理小对象</strong>：Python使用内存池来管理小型对象的内存分配。内存池将内存分成小块，以减少内存碎片，并加速对象的分配和释放。对于小型对象（如整数和短字符串），Python通常从内存池中分配内存，而不是直接使用系统的内存分配函数。</li>
 <li><strong>优势</strong>：内存池机制可以显著提高小型对象的分配和释放效率，减少内存碎片的产生。</li>
</ul>
<h4>5. C扩展模块</h4>
<ul>
 <li><strong>底层内存管理</strong>：由于Python的内核部分是用C语言编写的，因此可以使用C扩展模块来管理内存。这允许Python与底层系统和库进行交互，并有效地管理内存。</li>
</ul>
<h4>6. 内存优化工具</h4>
<ul>
 <li><strong>gc模块</strong>：Python提供了gc模块，允许开发者手动控制垃圾回收器的行为，如强制进行垃圾回收等。</li>
 <li><strong>sys模块</strong> ：sys模块中的某些函数（如<code>sys.getrefcount()</code>）可以用于查看对象的引用计数，帮助开发者了解内存使用情况。</li>
</ul>
<p>综上所述，Python的内存管理机制通过动态内存分配、引用计数、垃圾回收器、内存池等多种机制共同作用，确保Python程序能够高效、稳定地运行。开发者在编写Python程序时，通常不需要过多关注内存管理的细节，但了解这些机制有助于编写更高效、更可靠的代码。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140312562</p>]]></description><guid isPermaLink="false">/archives/502cf5af-7846-47e3-856d-881418d22f24</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Thu, 26 Sep 2024 10:02:20 GMT</pubDate></item><item><title><![CDATA[Python热门面试问题一]]></title><link>https://wiki.dgfygk.com/archives/0c737de8-b97a-4d60-b272-f642eae2f1cd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Python%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E4%B8%80&amp;url=/archives/0c737de8-b97a-4d60-b272-f642eae2f1cd" width="1" height="1" alt="" style="opacity:0;">
<h2>Python和Java在面向对象编程方面有哪些主要区别？</h2>
<p>Python和Java在面向对象编程（OOP）方面确实存在一些主要区别，这些区别主要体现在以下几个方面：</p>
<h4>1. 编译与解释执行</h4>
<ul>
 <li><strong>Java</strong>：是一种编译型语言，需要在编译阶段将代码转换为字节码，然后由Java虚拟机（JVM）来解释执行。这种机制使得Java程序具有跨平台性，但编译过程相对复杂。</li>
 <li><strong>Python</strong>：则是一种解释型语言，源代码直接由Python解释器逐行解释执行，无需事先编译。这种方式简化了开发流程，但可能导致执行效率相对较低。不过，Python也可以通过与C、C++等语言集成来提高性能。</li>
</ul>
<h4>2. 语法风格</h4>
<ul>
 <li><strong>Python</strong>：语法相对简洁，注重易读性。在Python中，不需要显式声明变量的类型，块结构通过缩进来定义，这使得代码更加清晰易懂。</li>
 <li><strong>Java</strong>：语法相对严格，变量类型必须显式声明，块结构使用花括号来定义。Java的语法风格更加严谨，有助于减少因语法错误导致的程序问题。</li>
</ul>
<h4>3. 类型系统</h4>
<ul>
 <li><strong>Java</strong>：是静态类型语言，变量的类型在编译时确定，这有助于在编译阶段发现类型错误。</li>
 <li><strong>Python</strong>：是动态类型语言，变量的类型在运行时确定，这提供了更大的灵活性，但也可能导致一些运行时错误。</li>
</ul>
<h4>4. 面向对象编程特性</h4>
<ul>
 <li><strong>封装、继承、多态</strong> ：两者都支持这些面向对象编程的基本特性，但在具体实现上存在差异。例如，Java通过关键字<code>extends</code>实现继承，而Python则通过在类定义中列出基类来实现。Java支持接口（interface）和抽象类（abstract class），而Python则通过抽象基类（ABCs）来实现类似的功能。</li>
 <li><strong>访问修饰符</strong>：Java提供了多种访问修饰符（如public、protected、private）来控制类成员的访问权限。而Python没有内置的访问修饰符，但约定上单下划线表示受保护的成员，双下划线表示私有成员。</li>
</ul>
<h4>5. 多线程支持</h4>
<ul>
 <li><strong>Java</strong>：拥有强大的多线程支持，通过内置的线程类（如Thread）和同步机制（如synchronized关键字）来实现线程间的同步和通信。</li>
 <li><strong>Python</strong>：虽然也支持多线程编程，但由于全局解释器锁（GIL）的存在，无法有效利用多核CPU来提高性能。不过，Python提供了多进程编程的支持，可以通过多进程来绕过GIL的限制。</li>
</ul>
<h4>6. 标准库与生态</h4>
<ul>
 <li><strong>Java</strong>：拥有庞大的标准库和丰富的第三方库，支持从企业级应用到移动开发、游戏开发等多个领域。</li>
 <li><strong>Python</strong>：同样拥有庞大的标准库和丰富的第三方库，特别是在数据分析、人工智能、Web开发等领域表现出色。</li>
</ul>
<h4>7. 应用场景</h4>
<ul>
 <li><strong>Java</strong>：更适用于企业级应用开发、移动开发、游戏开发等需要高性能和稳定性的场景。</li>
 <li><strong>Python</strong>：则更适用于数据分析、人工智能、Web开发和小型脚本等领域，其简洁的语法和丰富的库支持使得开发效率更高。</li>
</ul>
<p>综上所述，Python和Java在面向对象编程方面存在多方面的区别，这些区别体现在编译与解释执行、语法风格、类型系统、面向对象编程特性、多线程支持、标准库与生态以及应用场景等多个方面。开发者在选择使用哪种语言时，需要根据具体需求和项目特点进行综合考虑。</p>
<h2>Python中常用的包有哪些？并简述其应用场景</h2>
<p>Python中常用的包非常丰富，这些包覆盖了从数据分析、Web开发、人工智能到科学计算等多个领域。以下是一些常用的Python包及其应用场景的简述：</p>
<h4>1. NumPy</h4>
<ul>
 <li><strong>应用场景</strong>：NumPy是Python科学计算的基础包，它提供了高性能的多维数组对象以及这些数组的操作。常用于数值计算、线性代数、傅里叶变换等领域。</li>
 <li><strong>特点</strong>：基于C语言实现，提供了大量的数学函数库，支持大量的维度数组与矩阵运算，此外也针对数组运算提供大量的数学函数库。</li>
</ul>
<h4>2. Pandas</h4>
<ul>
 <li><strong>应用场景</strong>：Pandas是Python数据分析的神器，它提供了快速、灵活和表达式丰富的数据结构，旨在使"关系"或"标签"数据的处理工作变得既简单又直观。常用于数据处理、清洗和分析。</li>
 <li><strong>特点</strong>：Pandas构建在NumPy之上，提供了大量的便捷功能，如数据清洗、数据转换、数据合并等，同时也支持数据的可视化。</li>
</ul>
<h4>3. Matplotlib</h4>
<ul>
 <li><strong>应用场景</strong>：Matplotlib是Python的绘图库，它提供了类似MATLAB的绘图框架和API，可以生成出版质量级别的图形。常用于数据可视化。</li>
 <li><strong>特点</strong>：Matplotlib能够绘制线图、散点图、柱状图、饼图等多种类型的图形，并且支持图形的自定义，如颜色、线型、标签等。</li>
</ul>
<h4>4. Scikit-learn</h4>
<ul>
 <li><strong>应用场景</strong>：Scikit-learn是Python的一个开源机器学习库，它建立在NumPy、SciPy和Matplotlib之上。提供了简单、高效的数据挖掘和数据分析工具。常用于机器学习模型的训练和评估。</li>
 <li><strong>特点</strong>：Scikit-learn支持多种机器学习算法，如分类、回归、聚类、降维等，并且提供了丰富的数据预处理和特征工程功能。</li>
</ul>
<h4>5. TensorFlow和PyTorch</h4>
<ul>
 <li><strong>应用场景</strong>：TensorFlow和PyTorch是Python中两个非常流行的深度学习框架，它们提供了构建和训练神经网络所需的各种工具和库。常用于深度学习、自然语言处理、计算机视觉等领域。</li>
 <li><strong>特点</strong>：TensorFlow由Google开发，支持分布式计算，适合大规模模型训练；PyTorch由Facebook开发，具有动态计算图的特点，代码更加灵活易读。</li>
</ul>
<h4>6. Django和Flask</h4>
<ul>
 <li><strong>应用场景</strong>：Django和Flask是Python中两个非常流行的Web开发框架。它们提供了快速开发Web应用程序所需的各种工具和库，常用于Web应用的开发。</li>
 <li><strong>特点</strong>：Django是一个"大而全"的框架，提供了大量的内置组件和中间件，可以快速构建复杂的Web应用；Flask则是一个轻量级的框架，提供了核心功能，并允许开发者根据需要添加扩展。</li>
</ul>
<h4>7. Requests</h4>
<ul>
 <li><strong>应用场景</strong>：Requests是Python的一个HTTP库，用于发送HTTP请求。它简化了与HTTP服务器交互的过程，常用于网络爬虫、API调用等场景。</li>
 <li><strong>特点</strong>：Requests支持多种HTTP方法（如GET、POST等），提供了会话保持、自动处理cookies等功能，并且支持SSL验证和连接池等高级特性。</li>
</ul>
<h4>8. Scrapy</h4>
<ul>
 <li><strong>应用场景</strong>：Scrapy是一个快速高级的Web抓取框架，用于抓取网站并从页面中提取结构化的数据。它被广泛用于网络爬虫的开发。</li>
 <li><strong>特点</strong>：Scrapy提供了强大的选择器（Selectors）来解析HTML和XML数据，支持多种数据导出格式（如JSON、CSV等），并且支持异步请求和中间件等高级特性。</li>
</ul>
<h4>9. SciPy</h4>
<ul>
 <li><strong>应用场景</strong>：SciPy是基于NumPy的一个开源Python算法库和数学工具包，提供了许多数学算法和函数，用于科学计算。</li>
 <li><strong>特点</strong>：SciPy包含了线性代数、优化、积分、插值、特殊函数、快速傅里叶变换、信号处理、图像处理和统计等模块，是Python科学计算不可或缺的一部分。</li>
</ul>
<p>这些包只是Python生态系统中众多优秀包的一部分，它们各自在不同的领域发挥着重要作用，极大地扩展了Python的应用范围和功能。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140312537</p>]]></description><guid isPermaLink="false">/archives/0c737de8-b97a-4d60-b272-f642eae2f1cd</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Thu, 26 Sep 2024 10:02:05 GMT</pubDate></item><item><title><![CDATA[测试工程师面试热门问题（六）]]></title><link>https://wiki.dgfygk.com/archives/ee344547-53e9-4e51-be67-4472e081b0f1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E5%B7%A5%E7%A8%8B%E5%B8%88%E9%9D%A2%E8%AF%95%E7%83%AD%E9%97%A8%E9%97%AE%E9%A2%98%EF%BC%88%E5%85%AD%EF%BC%89&amp;url=/archives/ee344547-53e9-4e51-be67-4472e081b0f1" width="1" height="1" alt="" style="opacity:0;">
<h2>你了解持续集成（CI）和持续部署（CD）吗？请描述它们与测试的关系。</h2>
<p><strong>持续集成（Continuous Integration, CI）和持续部署（Continuous Deployment, CD）是现代软件开发中的重要概念，它们与测试的关系密不可分</strong>。以下是对这两个概念及其与测试关系的详细描述：</p>
<h4>一、持续集成（CI）</h4>
<p><strong>定义</strong> ：
 <br>
 持续集成是一种软件开发实践，其核心思想是将开发人员的工作集成到共享的代码库中，并在每次代码提交时自动执行测试。这种实践有助于提高软件质量，减少集成和合并代码时的冲突，并快速发现和修复错误。</p>
<p><strong>与测试的关系</strong>：</p>
<ol>
 <li><strong>自动化测试</strong>：持续集成的基础是自动化测试。每当有新的代码提交到共享的代码库时，自动化测试工具会立即执行测试用例，并根据预定的标准判断测试结果。这种自动化的方式大大提高了测试的速度和准确性，减少了人工干预的时间和成本。</li>
 <li><strong>快速反馈</strong>：持续集成通过自动化测试为开发人员提供快速反馈。如果测试失败，持续集成服务器会立即发出警报，以便开发人员及时发现和修复错误。这种快速的反馈机制有助于开发人员更快地迭代和交付高质量的软件。</li>
 <li><strong>集成测试</strong>：持续集成还包括集成测试，即将新开发的代码与现有的代码库集成在一起进行测试。这有助于确保新代码不会破坏现有的功能，从而提高软件的整体稳定性和可靠性。</li>
</ol>
<h4>二、持续部署（CD）</h4>
<p><strong>定义</strong> ：
 <br>
 持续部署是在持续集成的基础上，将集成后的代码自动部署到生产环境中。这种实践可以加速软件交付过程，提高软件的可靠性和稳定性，并减少人工干预的时间和成本。</p>
<p><strong>与测试的关系</strong>：</p>
<ol>
 <li><strong>自动化部署</strong>：持续部署依赖于自动化测试的结果。只有当代码通过自动化测试后，持续部署服务器才会将其部署到生产环境中。这种自动化的方式确保了只有经过充分测试的代码才会被部署到生产环境中，从而提高了软件的稳定性和可靠性。</li>
 <li><strong>环境一致性</strong>：为了确保测试和生产环境的一致性，持续部署通常会采用容器化技术（如Docker）来创建一致的运行环境。这有助于减少由于环境差异导致的测试和生产结果不一致的问题。</li>
 <li><strong>持续监控</strong>：在持续部署过程中，监控系统会监控生产环境中的软件系统，并在出现问题时发出警报。这种持续的监控机制有助于及时发现问题并进行修复，从而确保软件的稳定性和可靠性。</li>
</ol>
<h4>总结</h4>
<p>持续集成和持续部署是现代软件开发中不可或缺的实践，它们与测试的关系密不可分。通过自动化测试和部署，持续集成和持续部署能够加速软件的开发和交付过程，提高软件的质量和稳定性，并减少人工干预的时间和成本。这种自动化的流程为开发人员和测试人员提供了更好的合作方式，共同推动软件的持续迭代和交付。</p>
<h2>在测试过程中，你是如何确保测试环境的稳定性和一致性的？</h2>
<p>在测试过程中，确保测试环境的稳定性和一致性是至关重要的，因为这直接影响到测试结果的准确性和可靠性。以下是一些关键步骤和方法，用于确保测试环境的稳定性和一致性：</p>
<h4>一、硬件资源的稳定性管理</h4>
<ol>
 <li><strong>硬件资源选择</strong>：选择稳定可靠的计算机硬件和网络硬件，确保它们能够满足测试需求并具备较高的性能。</li>
 <li><strong>硬件资源维护</strong>：定期对硬件资源进行维护和检查，包括清洁、散热、更换老化部件等，以确保其处于良好的工作状态。</li>
 <li><strong>硬件资源更新</strong>：随着技术的发展，及时更新硬件资源以保持其先进性和稳定性。</li>
</ol>
<h4>二、软件资源的稳定性管理</h4>
<ol>
 <li><strong>操作系统和编程语言的稳定性</strong>：选择稳定版本的操作系统和编程语言，避免使用存在已知缺陷或漏洞的版本。</li>
 <li><strong>测试框架和工具的稳定性</strong>：选择成熟、可靠的测试框架和工具，以确保测试过程的稳定性和准确性。</li>
 <li><strong>软件资源更新</strong>：定期更新软件资源，包括操作系统补丁、编程语言库和测试框架等，以修复已知问题并引入新功能。</li>
</ol>
<h4>三、数据资源的稳定性管理</h4>
<ol>
 <li><strong>测试数据准备</strong>：确保测试数据准确、完整，并符合测试需求。可以使用数据管理工具来管理和维护测试数据。</li>
 <li><strong>数据资源备份</strong>：定期对测试数据进行备份，以防止数据丢失或损坏对测试结果的影响。</li>
 <li><strong>数据资源更新</strong>：根据测试需求的变化，及时更新测试数据，以确保测试结果的时效性。</li>
</ol>
<h4>四、测试环境的监控和管理</h4>
<ol>
 <li><strong>环境监控</strong>：使用监控工具对测试环境进行实时监控，包括硬件资源使用情况、软件资源运行状态和数据资源变化等。一旦发现异常，立即进行处理。</li>
 <li><strong>环境备份</strong>：定期对测试环境进行备份，包括硬件配置、软件安装和数据资源等，以便在需要时快速恢复测试环境。</li>
 <li><strong>环境管理</strong>：建立有效的测试环境管理机制，记录和跟踪测试环境的配置、变更、使用和状态。这有助于确保测试环境的一致性和可追溯性。</li>
</ol>
<h4>五、其他措施</h4>
<ol>
 <li><strong>隔离测试环境</strong>：将测试环境与生产环境隔离开来，避免测试过程中的误操作对生产环境造成影响。</li>
 <li><strong>团队协作</strong>：与开发团队、运维团队和其他相关方保持良好的沟通和协作，共同维护测试环境的稳定性和一致性。</li>
 <li><strong>采用容器化和虚拟化技术</strong>：使用容器化（如Docker）和虚拟化技术来创建一致、可重复的测试环境，减少环境差异对测试结果的影响。</li>
</ol>
<p>综上所述，通过上述措施的综合运用，可以有效地确保测试环境的稳定性和一致性，为测试工作的顺利进行提供有力保障。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140369488</p>]]></description><guid isPermaLink="false">/archives/ee344547-53e9-4e51-be67-4472e081b0f1</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 10:01:44 GMT</pubDate></item><item><title><![CDATA[测试工程师面试题（五）]]></title><link>https://wiki.dgfygk.com/archives/bc98c367-f472-402f-bfd6-2d00b3bd7a60</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E5%B7%A5%E7%A8%8B%E5%B8%88%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%88%E4%BA%94%EF%BC%89&amp;url=/archives/bc98c367-f472-402f-bfd6-2d00b3bd7a60" width="1" height="1" alt="" style="opacity:0;">
<h2>什么是敏捷测试</h2>
<p>敏捷测试（Agile testing）是一种遵循敏捷软件开发规则和原则的测试实践。它强调从客户的角度，即从使用系统的用户角度，来测试系统，并重点关注持续迭代地测试新开发的功能，而不再强调传统测试过程中严格的测试阶段。敏捷测试不仅仅是测试软件本身，还包含了软件测试的过程和模式，旨在不断修正质量指标，正确建立测试策略，确认客户的有效需求得以圆满实现，并确保整个生产过程安全、及时地发布最终产品。</p>
<h4>敏捷测试在敏捷开发团队中的应用</h4>
<p>在敏捷开发团队中，敏捷测试扮演着至关重要的角色，其应用主要体现在以下几个方面：</p>
<ol>
 <li>
  <p><strong>提供及时反馈</strong>：</p>
 </li>
 <li>
  <p>敏捷开发强调快速迭代和持续交付，而敏捷测试能够及时发现和反馈软件中的问题和缺陷。通过不断进行测试，可以及早发现和解决问题，从而提高软件的质量和稳定性。</p>
 </li>
 <li>
  <p><strong>推动需求明确</strong>：</p>
 </li>
 <li>
  <p>测试人员与产品负责人和开发人员密切合作，参与需求的讨论和明确。通过测试人员对需求的理解和反馈，有助于团队更好地定义和优化需求，减少需求变更的次数和范围，提高项目的可控性和稳定性。</p>
 </li>
 <li>
  <p><strong>保障产品质量</strong>：</p>
 </li>
 <li>
  <p>敏捷测试通过单元测试、集成测试、系统测试等多种测试手段，全面评估产品的质量和稳定性。测试人员还负责规范和优化测试流程和方法，提高测试的效率和可靠性。</p>
 </li>
 <li>
  <p><strong>促进团队协作</strong>：</p>
 </li>
 <li>
  <p>敏捷测试强调测试与开发人员的紧密合作，共同解决问题和优化设计。通过测试人员对开发成果的验证和反馈，有助于促进团队的协作效率和项目的整体进展。</p>
 </li>
 <li>
  <p><strong>实施敏捷测试策略</strong>：</p>
 </li>
 <li>
  <p>敏捷测试策略是为敏捷开发项目定制的一套测试计划和方法。它强调测试与开发人员的协作，侧重于在不同阶段和不同层次上进行测试，并鼓励持续改进和优化测试过程。例如，采用测试驱动开发（TDD）、持续集成（CI）和自动化测试等方法，以提高测试的效率和准确性。</p>
 </li>
 <li>
  <p><strong>参与敏捷会议</strong>：</p>
 </li>
 <li>
  <p>敏捷测试人员几乎要参加整个项目组的所有会议，如每日站会、迭代规划会、回顾会等，以确保测试工作与开发工作紧密同步，及时沟通和解决问题。</p>
 </li>
 <li>
  <p><strong>注重版本更新和沟通</strong>：</p>
 </li>
 <li>
  <p>敏捷开发中版本更换较为频繁，测试人员需要注意版本的更新情况，并与开发人员保持密切沟通，确保测试工作的顺利进行。</p>
 </li>
 <li>
  <p><strong>关注非功能性要求</strong>：</p>
 </li>
 <li>
  <p>敏捷测试还关注非功能性要求，如性能、安全性、稳定性等，确保软件在满足功能需求的同时，也具备良好的用户体验和稳定性。</p>
 </li>
</ol>
<p>综上所述，敏捷测试在敏捷开发团队中发挥着至关重要的作用，它通过提供及时反馈、推动需求明确、保障产品质量、促进团队协作等方式，助力团队实现快速迭代和持续交付，提高项目的整体质量和效率。</p>
<h2>敏捷测试的优点和不足分别有哪些？</h2>
<p>敏捷测试作为顺应敏捷开发方法的一种测试实践，具有一系列的优点，同时也存在一些不足之处。以下是对敏捷测试优点和不足的详细分析：</p>
<h4>敏捷测试的优点</h4>
<ol>
 <li>
  <p><strong>持续反馈和快速迭代</strong>：</p>
 </li>
 <li>
  <p>敏捷测试强调在开发过程中的早期和频繁反馈，这有助于团队及时发现和修复问题，减少后期修复的成本和风险。通过快速迭代，团队能够更快地适应需求的变化，提高开发效率。</p>
 </li>
 <li>
  <p><strong>提高团队协作</strong>：</p>
 </li>
 <li>
  <p>敏捷测试鼓励测试人员与开发人员的紧密合作，共同解决问题。这种协作方式有助于打破传统测试与开发之间的壁垒，促进团队之间的沟通和理解，从而提高整体的工作效率。</p>
 </li>
 <li>
  <p><strong>关注用户体验</strong>：</p>
 </li>
 <li>
  <p>敏捷测试更加关注用户体验和系统使用场景，而不仅仅是功能上的正确性。这使得开发团队能够更好地理解用户需求，并据此进行开发和测试，从而提高产品的用户满意度。</p>
 </li>
 <li>
  <p><strong>自动化测试的支持</strong>：</p>
 </li>
 <li>
  <p>敏捷测试强调自动化测试的重要性，通过引入自动化测试工具和方法，可以显著提高测试效率和准确性。自动化测试还可以帮助团队在迭代过程中进行回归测试，确保新功能没有破坏旧功能。</p>
 </li>
 <li>
  <p><strong>灵活的测试策略</strong>：</p>
 </li>
 <li>
  <p>敏捷测试允许测试策略根据项目的实际情况和需求进行灵活调整。这种灵活性使得测试团队能够更好地应对项目中的不确定性和变化，确保测试工作的有效性和针对性。</p>
 </li>
 <li>
  <p><strong>提升产品质量</strong>：</p>
 </li>
 <li>
  <p>通过持续测试、及时反馈和快速迭代，敏捷测试有助于提升产品的质量。测试团队能够在开发过程中及时发现和修复问题，减少缺陷的遗留和扩散，从而提高产品的稳定性和可靠性。</p>
 </li>
</ol>
<h4>敏捷测试的不足</h4>
<ol>
 <li>
  <p><strong>对测试人员的要求较高</strong>：</p>
 </li>
 <li>
  <p>敏捷测试要求测试人员具备较高的技术水平和综合素质，能够与开发团队紧密合作并快速适应变化。这对于一些技术水平较低的测试人员来说可能是一个挑战。</p>
 </li>
 <li>
  <p><strong>测试文档的维护成本</strong>：</p>
 </li>
 <li>
  <p>敏捷测试强调快速迭代和持续反馈，这可能导致测试文档的更新和维护成本较高。如果团队不能有效地管理测试文档，可能会影响测试的效率和准确性。</p>
 </li>
 <li>
  <p><strong>对测试计划的灵活性要求</strong>：</p>
 </li>
 <li>
  <p>敏捷测试要求测试计划具有较高的灵活性，以应对项目中的不确定性和变化。然而，这种灵活性也可能导致测试计划的制定和执行变得复杂和困难。</p>
 </li>
 <li>
  <p><strong>测试覆盖率的挑战</strong>：</p>
 </li>
 <li>
  <p>在敏捷开发中，由于迭代周期较短且需求频繁变更，测试团队可能面临测试覆盖率不足的挑战。特别是在时间紧迫的情况下，团队可能会优先考虑实现核心功能而忽略了非核心功能的测试。</p>
 </li>
 <li>
  <p><strong>对团队沟通和协作的依赖</strong>：</p>
 </li>
 <li>
  <p>敏捷测试的成功在很大程度上取决于团队之间的沟通和协作。如果团队成员之间缺乏有效的沟通和协作机制，可能会影响测试的效率和效果。</p>
 </li>
</ol>
<p>综上所述，敏捷测试在带来一系列优点的同时，也存在一些不足之处。为了充分发挥敏捷测试的优势并克服其不足，团队需要不断学习和实践敏捷测试的理念和方法，并结合项目的实际情况进行灵活调整和优化。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140369119</p>]]></description><guid isPermaLink="false">/archives/bc98c367-f472-402f-bfd6-2d00b3bd7a60</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 10:01:32 GMT</pubDate></item><item><title><![CDATA[测试面试题（三）]]></title><link>https://wiki.dgfygk.com/archives/02073ee7-c4de-4481-9144-0e4885d46c25</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%88%E4%B8%89%EF%BC%89&amp;url=/archives/02073ee7-c4de-4481-9144-0e4885d46c25" width="1" height="1" alt="" style="opacity:0;">
<h2>什么是缺陷（Bug）跟踪系统？你使用过哪些缺陷跟踪工具？</h2>
<h4>缺陷（Bug）跟踪系统</h4>
<p>缺陷（Bug）跟踪系统，简称DTS，是在软件或产品的整个开发生命周期中，用于识别、记录、监控和管理缺陷或问题的系统过程。它就像一本数字日记，记录产品或项目中的所有问题或错误，类似于列出所有非正常运作的项目的清单。缺陷跟踪系统的主要目标是确保所有发现的缺陷都能被及时记录、分配给合适的工程师进行修复，并跟踪其修复进度，直至问题被解决。</p>
<p>缺陷跟踪系统的主要功能和特点包括：</p>
<ol>
 <li><strong>有效沟通</strong>：促进开发人员、测试人员和经理之间的有效沟通，确保缺陷信息的准确传递。</li>
 <li><strong>准确记录</strong>：详细记录缺陷信息，包括重现缺陷的步骤、缺陷的严重性和影响，帮助开发人员快速定位和修复问题。</li>
 <li><strong>定期报告</strong>：提供定期报告，帮助识别缺陷趋势或模式，以改进缺陷跟踪过程并防止未来类似问题的发生。</li>
 <li><strong>优先级排序</strong>：根据缺陷的严重性和影响，对缺陷进行优先级排序，确保先修复最严重的缺陷。</li>
 <li><strong>跟踪进展</strong>：跟踪缺陷的修复进展，包括状态更新、指定的工程师和预计的修复时间。</li>
 <li><strong>持续改进</strong>：通过分析和总结缺陷数据，不断优化缺陷跟踪过程，提高软件质量。</li>
</ol>
<h4>使用过的缺陷跟踪工具</h4>
<p>在软件开发和测试过程中，我使用过多种缺陷跟踪工具，以下是一些常见且实用的工具及其特点：</p>
<ol>
 <li>
  <p><strong>PingCode</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：PingCode是国内市场占有率较高的产品研发项目管理工具，非常适合中大型团队使用。它提供了强大的缺陷管理能力，能够定制化工作流、实时可视化缺陷状态、集成和自动化缺陷处理等功能。</p>
 </li>
 <li>
  <p><strong>特点</strong>：支持多种项目管理方法（如敏捷、看板、瀑布等），提供丰富的报告和数据分析工具，以及强大的自动化和集成能力。</p>
 </li>
 <li>
  <p><strong>适用场景</strong>：适用于需要全面项目管理和缺陷跟踪的中大型团队。</p>
 </li>
 <li>
  <p><strong>Jira</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：Jira是由Atlassian公司开发的全球知名的缺陷管理和项目管理工具，广泛应用于各种规模的团队，尤其在敏捷开发团队中备受欢迎。</p>
 </li>
 <li>
  <p><strong>特点</strong>：具备强大的自定义工作流、丰富的插件生态系统和实时协作功能，支持敏捷开发方法和实时数据报告。</p>
 </li>
 <li>
  <p><strong>适用场景</strong>：适用于从小型初创公司到大型企业的各种规模团队，特别是软件开发团队。</p>
 </li>
 <li>
  <p><strong>Bugzilla</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：Bugzilla是一个开源的缺陷跟踪系统，因其强大的定制和扩展能力而被广泛采用。</p>
 </li>
 <li>
  <p><strong>特点</strong>：提供高级搜索、电子邮件通知、时间跟踪和报告功能，帮助团队有效地管理和解决缺陷。界面较为老旧，但功能全面且免费。</p>
 </li>
 <li>
  <p><strong>适用场景</strong>：适合预算有限的小型到中型开发团队，需要较强的定制和扩展能力。</p>
 </li>
 <li>
  <p><strong>Worktile</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：Worktile是一个灵活的项目管理工具，虽然不是专门为缺陷管理设计，但其灵活性和易用性使其成为处理简单缺陷追踪的有效工具。</p>
 </li>
 <li>
  <p><strong>特点</strong>：支持创建看板来管理项目缺陷，提供标签系统、搜索过滤和实时评论等功能，增强协作和透明度。</p>
 </li>
 <li>
  <p><strong>适用场景</strong>：适用于需要简单、直观且具有高度可定制性的缺陷追踪解决方案的中小团队。</p>
 </li>
</ol>
<p>以上是根据我的经验和市场调研整理的缺陷跟踪系统及其常用工具的信息。不同的团队和项目可能需要根据自身需求选择合适的工具。</p>
<h2>你了解哪些自动化测试工具和框架？请描述一种你熟悉的工具及其工作原理。</h2>
<p>在自动化测试领域，存在多种工具和框架，它们各自具有不同的特点和适用场景。以下是我所了解的一些自动化测试工具和框架，以及我对其中一种熟悉工具（Selenium）的详细描述。</p>
<h4>自动化测试工具和框架概述</h4>
<ol>
 <li>
  <p><strong>Selenium</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：Selenium是一个功能强大的Web应用程序测试框架，支持多种编程语言，如Java、Python、C#等。它通过模拟用户在浏览器中的操作，可以实现自动化测试。</p>
 </li>
 <li>
  <p><strong>工作原理</strong>：Selenium的工作原理基于WebDriver协议，它允许开发者编写测试脚本来模拟用户在浏览器中的行为，如点击、输入、导航等。Selenium WebDriver直接与浏览器交互，控制浏览器执行测试脚本中的操作，并捕获浏览器界面的变化作为测试结果。通过这种方式，Selenium能够确保Web应用程序在各种浏览器和操作系统上的兼容性和功能性。</p>
 </li>
 <li>
  <p><strong>应用</strong>：Selenium广泛应用于Web自动化测试领域，包括单元测试、集成测试和端到端测试。它支持多种浏览器，如Chrome、Firefox、Safari等，以及不同的操作系统平台。</p>
 </li>
 <li>
  <p><strong>Appium</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：Appium是一个开源的移动应用程序自动化测试框架，用于测试移动应用程序，支持Android、iOS和Windows平台。</p>
 </li>
 <li>
  <p><strong>工作原理</strong>：Appium使用WebDriver协议来与移动设备进行交互，模拟用户在移动设备上的操作。它允许开发者使用同一套测试脚本在多个移动平台上进行测试，提高了测试效率和复用性。</p>
 </li>
 <li>
  <p><strong>JUnit</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：JUnit是一个Java语言的单元测试框架，用于编写和运行单元测试。</p>
 </li>
 <li>
  <p><strong>工作原理</strong>：JUnit提供了一系列的注解和断言方法，帮助开发人员编写和组织测试用例。测试人员可以编写测试类和方法，并使用JUnit的注解来标记测试方法和测试套件。JUnit会自动运行这些测试方法，并生成测试报告，帮助开发人员了解测试结果。</p>
 </li>
 <li>
  <p><strong>TestNG</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：TestNG是一个基于Java的测试框架，功能比JUnit更为强大，支持并发测试、数据驱动测试等高级功能。</p>
 </li>
 <li>
  <p><strong>工作原理</strong>：TestNG提供了丰富的注解和配置选项，允许开发人员编写和组织复杂的测试用例。它支持并行执行，可以显著提高测试效率。同时，TestNG还支持数据驱动测试，允许测试人员使用外部数据源来驱动测试，增加了测试的灵活性和可重用性。</p>
 </li>
 <li>
  <p><strong>Robot Framework</strong></p>
 </li>
 <li>
  <p><strong>简介</strong>：Robot Framework是一个通用的自动化测试框架，支持多种应用程序和平台。</p>
 </li>
 <li>
  <p><strong>工作原理</strong>：Robot Framework使用关键字驱动的测试方法，支持多种编程语言和关键字库。测试人员可以使用预定义的关键字或自定义关键字来编写测试用例，这些关键字代表了测试过程中需要执行的操作。Robot Framework会按照测试用例中的关键字顺序执行操作，并生成测试报告。</p>
 </li>
</ol>
<h4>详细描述Selenium</h4>
<p><strong>Selenium</strong>作为一种广泛应用的Web自动化测试工具，其工作原理基于WebDriver协议。具体来说，Selenium WebDriver通过以下步骤实现自动化测试：</p>
<ol>
 <li>
  <p><strong>环境准备</strong>：测试人员需要在测试环境中安装Selenium WebDriver和相应的浏览器驱动程序。这些驱动程序允许Selenium WebDriver与浏览器进行交互。</p>
 </li>
 <li>
  <p><strong>脚本编写</strong>：测试人员使用支持的编程语言（如Java、Python等）编写测试脚本。脚本中包含了模拟用户在浏览器中操作的一系列指令，如打开网页、输入文本、点击按钮等。</p>
 </li>
 <li>
  <p><strong>脚本执行</strong>：Selenium WebDriver读取测试脚本，并按照脚本中的指令与浏览器进行交互。它模拟用户在浏览器中的操作，并捕获浏览器界面的变化作为测试结果。</p>
 </li>
 <li>
  <p><strong>结果比对</strong>：测试脚本中通常会包含一些断言语句，用于验证实际结果与预期结果是否一致。Selenium WebDriver会执行这些断言语句，并生成测试结果。</p>
 </li>
 <li>
  <p><strong>报告生成</strong>：测试完成后，Selenium WebDriver会生成测试报告，包括测试通过率、测试覆盖率等指标，以及详细的测试日志。这些报告有助于开发人员和测试人员了解测试情况，并采取相应的措施来改进软件质量。</p>
 </li>
</ol>
<p>总的来说，Selenium通过模拟用户在浏览器中的操作来实现Web自动化测试，提高了测试效率和准确性。它是Web自动化测试领域中最受欢迎的工具之一。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140258406</p>]]></description><guid isPermaLink="false">/archives/02073ee7-c4de-4481-9144-0e4885d46c25</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 10:00:53 GMT</pubDate></item><item><title><![CDATA[测试面试题（四）]]></title><link>https://wiki.dgfygk.com/archives/c2ba5e57-2eac-48b4-8ca2-eb6f53fa117f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%88%E5%9B%9B%EF%BC%89&amp;url=/archives/c2ba5e57-2eac-48b4-8ca2-eb6f53fa117f" width="1" height="1" alt="" style="opacity:0;">
<h2>请解释什么是性能测试，并列举几个常用的性能指标。</h2>
<h4>性能测试解释</h4>
<p>性能测试是软件测试的一个重要环节，它关注的是软件系统的非功能特性，即软件在完成特定功能时展示出来的及时性、效率以及稳定性等。性能测试旨在通过模拟多种正常、峰值以及异常负载条件，对系统的各项性能指标进行全面测试。这种测试有助于评估系统的能力、识别潜在的性能瓶颈，并为系统的优化提供数据支持。</p>
<h4>常用的性能指标</h4>
<ol>
 <li>
  <p><strong>响应时间（Response Time）</strong></p>
 </li>
 <li>
  <p>定义：指用户从客户端发起一个请求开始，到客户端收到从服务器端返回的结果，整个过程所耗费的时间。</p>
 </li>
 <li>
  <p>重要性：响应时间是最能反应服务器性能的指标之一，也是用户最关心的业务体验之一。较短的响应时间能够提升用户满意度和系统的整体性能。</p>
 </li>
 <li>
  <p><strong>吞吐量（Throughput）</strong></p>
 </li>
 <li>
  <p>定义：指的是单位时间内处理的客户端请求数量，直接体现软件系统的性能承载能力。</p>
 </li>
 <li>
  <p>常用指标：</p>
  <ul>
   <li>每秒事务数（TPS, Transactions Per Second）：表示系统每秒能够处理的事务数量。</li>
   <li>每秒查询数（QPS, Queries Per Second）：表示系统每秒能够处理的查询请求数量。</li>
  </ul>
 </li>
 <li>
  <p>重要性：吞吐量是衡量系统处理能力的关键指标，对于高并发系统尤为重要。</p>
 </li>
 <li>
  <p><strong>并发用户数（Concurrent Users）</strong></p>
 </li>
 <li>
  <p>定义：指某一时刻同时向服务器发送请求的用户数。</p>
 </li>
 <li>
  <p>重要性：并发用户数反映了系统在高并发场景下的处理能力，是评估系统性能和稳定性的重要指标。</p>
 </li>
 <li>
  <p><strong>资源使用率（Resource Utilization）</strong></p>
 </li>
 <li>
  <p>定义：指对不同系统资源（如CPU、内存、磁盘I/O、网络等）的使用情况，通常以占用最大值的百分比来衡量。</p>
 </li>
 <li>
  <p>重要性：资源使用率的高低直接影响系统的性能和稳定性。过高的资源使用率可能导致系统响应变慢、甚至出现宕机等问题。</p>
 </li>
 <li>
  <p><strong>事务（Transactions）</strong></p>
 </li>
 <li>
  <p>定义：事务是应用程序中一系列严密的操作，所有操作必须成功完成，否则在每个操作中所作的所有更改都会被撤消。事务具有原子性，即要么全部成功，要么全部失败。</p>
 </li>
 <li>
  <p>重要性：事务是评估系统业务处理能力的重要指标，它反映了系统在处理复杂业务场景时的稳定性和可靠性。</p>
 </li>
 <li>
  <p><strong>点击数（Hits）</strong></p>
 </li>
 <li>
  <p>定义：指客户端向服务端发送请求时，所有的页面资源元素（如图片、链接、框架、CSS、JS等）的请求总数量。</p>
 </li>
 <li>
  <p>重要性：点击数反映了用户在访问系统时产生的请求量，对于评估系统的负载能力和优化请求处理策略具有重要意义。</p>
 </li>
 <li>
  <p><strong>服务器可靠性（Server Reliability）</strong></p>
 </li>
 <li>
  <p>定义：指服务器能在相对高负载情况下持续运行的能力。</p>
 </li>
 <li>
  <p>重要性：服务器可靠性是保障系统稳定运行的关键因素之一。在性能测试中，需要关注服务器在高负载下的表现，确保系统能够稳定运行并满足业务需求。</p>
 </li>
</ol>
<p>综上所述，性能测试通过模拟多种负载条件对系统的各项性能指标进行全面测试，旨在评估系统的能力、识别潜在的性能瓶颈，并为系统的优化提供数据支持。常用的性能指标包括响应时间、吞吐量、并发用户数、资源使用率、事务、点击数以及服务器可靠性等。这些指标共同构成了评估系统性能的重要标准。</p>
<h2>什么是安全测试？在安全测试中需要注意哪些关键点？</h2>
<h4>什么是安全测试</h4>
<p>安全测试是在软件产品开发基本完成时，验证产品是否符合安全需求定义和产品质量标准的过程。它主要检查系统对非法侵入渗透的防范能力，以确保系统在面对潜在的安全威胁时能够保持稳定和安全。安全测试的目标是通过对系统进行全面的脆弱性安全测试，发现系统未知的安全隐患并提出相关建议，从而确保系统的安全性。</p>
<h4>安全测试中需要注意的关键点</h4>
<ol>
 <li>
  <p><strong>明确测试目的</strong>：</p>
 </li>
 <li>
  <p>首先要清楚为什么要进行安全测试，是为了满足审计要求、了解新应用在现实世界中的表现，还是验证新换的安全基础设施组件的有效性等。明确测试目的有助于确立测试范围和预期结果，使测试规划工作更有效率。</p>
 </li>
 <li>
  <p><strong>了解测试环境</strong>：</p>
 </li>
 <li>
  <p>对被测试的系统和网络环境有充分的了解，包括系统的架构、配置、存在的漏洞等。绘制公司网络地图，包括逻辑和拓扑方面，是渗透测试的强制性前提条件。</p>
 </li>
 <li>
  <p><strong>设置合理的测试范围</strong>：</p>
 </li>
 <li>
  <p>测试范围应根据测试目的来确定，避免过宽或过窄。过窄可能导致无法发现关键的安全问题，过宽则可能增加测试成本并降低测试效率。</p>
 </li>
 <li>
  <p><strong>制定详细的测试计划</strong>：</p>
 </li>
 <li>
  <p>测试计划应明确测试条件、需求、返回数据类型等，以确保测试的顺利进行。同时，测试计划应具有灵活性，以便在测试过程中根据需要进行调整。</p>
 </li>
 <li>
  <p><strong>选择专业的测试团队</strong>：</p>
 </li>
 <li>
  <p>确保所选的测试团队具备足够的技术能力和经验，能够按照测试计划执行测试，并准确报告测试结果。</p>
 </li>
 <li>
  <p><strong>避免人为干预</strong>：</p>
 </li>
 <li>
  <p>渗透测试的目的是展现系统安全的真实情况，因此应避免为了得到好看的测试结果而人为干预测试过程。任何干预都可能影响测试结果的准确性和可靠性。</p>
 </li>
 <li>
  <p><strong>注重结果分析和沟通</strong>：</p>
 </li>
 <li>
  <p>测试完成后，应对测试结果进行深入分析，识别系统存在的安全隐患并提出改进建议。同时，应将测试结果以适当的方式传达给相关部门和人员，以便采取相应的措施来加强系统的安全防护。</p>
 </li>
 <li>
  <p><strong>关注敏感数据保护</strong>：</p>
 </li>
 <li>
  <p>在安全测试中，应特别注意对敏感数据的保护。避免在测试过程中泄露敏感信息，确保测试过程的安全性和合规性。</p>
 </li>
 <li>
  <p><strong>持续监控和改进</strong>：</p>
 </li>
 <li>
  <p>安全测试不是一次性的工作，而是一个持续的过程。应定期对系统进行安全测试，及时发现并修复潜在的安全问题，不断提升系统的安全防护能力。</p>
 </li>
</ol>
<p>综上所述，安全测试是确保软件产品安全性的重要环节。在测试过程中，需要明确测试目的、了解测试环境、设置合理的测试范围、制定详细的测试计划、选择专业的测试团队、避免人为干预、注重结果分析和沟通、关注敏感数据保护以及持续监控和改进。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140258811</p>]]></description><guid isPermaLink="false">/archives/c2ba5e57-2eac-48b4-8ca2-eb6f53fa117f</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 10:00:38 GMT</pubDate></item><item><title><![CDATA[测试工程师热门面试题二]]></title><link>https://wiki.dgfygk.com/archives/a4be19da-3c84-4402-bf3c-be79e45baffd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%A2%98%E4%BA%8C&amp;url=/archives/a4be19da-3c84-4402-bf3c-be79e45baffd" width="1" height="1" alt="" style="opacity:0;">
<h2>你熟悉的测试用例设计方法有哪些？请举例说明。</h2>
<p>测试用例设计方法主要包括以下几种：等价类划分法、边界值分析法、错误推测法、判定表法、正交实验法以及流程图法（也称场景法）。下面将详细解释每种方法，并给出相应的例子。</p>
<h4>1. 等价类划分法</h4>
<p><strong>定义</strong>：等价类划分法是将测试的范围划分成几个互不相交的子集，他们的并集是全集，从每个子集选出若干个有代表性的值作为测试用例。</p>
<p><strong>例子</strong>：假设要测试一个用户注册功能，其中用户名必须为6-12位的字母和数字组合。我们可以将输入划分为以下几个等价类：</p>
<ul>
 <li>有效等价类：</li>
 <li>6位字母和数字组合</li>
 <li>12位字母和数字组合</li>
 <li>任意8位字母和数字组合</li>
 <li>无效等价类：</li>
 <li>5位字母和数字组合</li>
 <li>13位字母和数字组合</li>
 <li>包含特殊字符的字符串</li>
 <li>空字符串</li>
</ul>
<p>从每个有效和无效等价类中各选择一个测试用例进行测试。</p>
<h4>2. 边界值分析法</h4>
<p><strong>定义</strong>：边界值分析法是针对输入或输出范围的边界值设计测试用例，因为大量的错误是发生在边界上。</p>
<p><strong>例子</strong>：假设某系统要求用户输入的年龄必须在18到60岁之间。我们可以设计以下边界值测试用例：</p>
<ul>
 <li>17岁（刚好小于最小值）</li>
 <li>18岁（最小值）</li>
 <li>59岁（接近最大值）</li>
 <li>60岁（最大值）</li>
 <li>61岁（刚好大于最大值）</li>
</ul>
<h4>3. 错误推测法</h4>
<p><strong>定义</strong>：错误推测法是基于经验和直觉推测程序中可能存在的错误，从而有针对性地设计测试用例。</p>
<p><strong>例子</strong>：在测试一个银行转账系统时，根据经验，我们可能会推测在账户余额不足、转账金额过大、输入非法字符等情况下系统会出错。因此，可以设计相应的测试用例来验证这些假设。</p>
<h4>4. 判定表法</h4>
<p><strong>定义</strong>：判定表法是一种通过表格形式来表达多条件逻辑判断的工具，适合于逻辑判断复杂的场景。</p>
<p><strong>例子</strong>：某商场的会员折扣政策根据会员级别和购物金额来决定折扣率。我们可以设计一个判定表，列出所有可能的会员级别和购物金额组合，以及对应的折扣率。然后根据判定表设计测试用例。</p>
<h4>5. 正交实验法</h4>
<p><strong>定义</strong>：正交实验法是在各因素互相独立的情况下，利用正交表来设计测试用例，以少数测试用例覆盖尽可能多的因素组合。</p>
<p><strong>例子</strong>：假设一个软件测试需要考虑三个因素：操作系统（Windows、Linux、MacOS）、浏览器（Chrome、Firefox、IE）、分辨率（1080p、4K）。如果使用传统的穷举法，需要设计27个测试用例。但使用正交实验法，可以选择一个合适的正交表（如L9(3^4)），只需要9个测试用例就能覆盖所有因素组合。</p>
<h4>6. 流程图法（场景法）</h4>
<p><strong>定义</strong>：流程图法是针对软件业务流程进行测试的方法，通过识别业务流程中的关键路径和场景来设计测试用例。</p>
<p><strong>例子</strong>：测试一个在线购物网站的结账流程，可以绘制出结账流程的流程图，然后识别出所有可能的路径（如成功结账、登录失败、支付失败等），并为每条路径设计测试用例。</p>
<p>以上六种方法各有特点，可以根据具体的测试需求和场景选择合适的方法进行设计。</p>
<h2>请解释什么是代码覆盖率，并说明其重要性。</h2>
<h4>代码覆盖率</h4>
<p><strong>定义</strong>：</p>
<p>代码覆盖率（Code Coverage）是一种软件测试度量工具，用于评估在代码执行过程中有多少部分被测试到。它通常以百分比的形式表示，表示已经被执行或覆盖的代码行、语句、分支或路径占总代码的比例。具体来说，代码覆盖率可以细分为多种类型，如行覆盖率（Line Coverage/Statement Coverage）、分支覆盖率（Branch Coverage）、条件覆盖率（Condition Coverage）和路径覆盖率（Path Coverage）等，这些类型分别关注不同的代码元素和测试场景。</p>
<ul>
 <li><strong>行覆盖率/语句覆盖率</strong>：衡量测试用例执行的代码行数占总代码行数的比例。</li>
 <li><strong>分支覆盖率</strong>：衡量测试用例执行的代码分支占总分支数的比例，主要用于评估条件语句（如if语句）的测试覆盖。</li>
 <li><strong>条件覆盖率</strong>：衡量测试用例对每个条件的不同取值进行测试的程度。</li>
 <li><strong>路径覆盖率</strong>：衡量测试用例执行的不同代码路径占总可能路径的比例。</li>
</ul>
<p><strong>重要性</strong>：</p>
<ol>
 <li>
  <p><strong>提高软件质量</strong>：</p>
 </li>
 <li>
  <p>代码覆盖率是衡量测试质量的重要指标之一。通过提高代码覆盖率，可以确保更多的代码被执行和测试，从而发现更多的潜在缺陷和错误。这有助于提高软件的整体质量，减少软件发布后的故障率。</p>
 </li>
 <li>
  <p><strong>指导测试工作</strong>：</p>
 </li>
 <li>
  <p>代码覆盖率可以为测试人员提供有价值的反馈，指导他们优化测试用例和测试策略。通过查看哪些代码路径尚未被测试到，测试人员可以有针对性地增加测试用例，提高测试的全面性和有效性。</p>
 </li>
 <li>
  <p><strong>满足行业标准和法规要求</strong>：</p>
 </li>
 <li>
  <p>在某些行业和领域，如医疗、航空等，对软件的质量要求非常高。这些行业通常会制定严格的标准和法规，要求软件必须达到一定的代码覆盖率水平才能被接受和使用。因此，代码覆盖率对于这些行业的软件开发和测试至关重要。</p>
 </li>
 <li>
  <p><strong>提升团队协作和沟通</strong>：</p>
 </li>
 <li>
  <p>代码覆盖率报告可以作为团队成员之间沟通和协作的桥梁。开发人员和测试人员可以通过查看代码覆盖率报告来了解彼此的工作进度和测试效果，共同协作解决问题，提高软件开发的效率和质量。</p>
 </li>
 <li>
  <p><strong>促进持续改进</strong>：</p>
 </li>
 <li>
  <p>代码覆盖率还可以作为持续改进的驱动力。通过定期分析和评估代码覆盖率报告，团队可以发现测试中的不足和盲点，并采取措施进行改进。这种持续改进的过程有助于提升团队的技术能力和软件质量。</p>
 </li>
</ol>
<p>综上所述，代码覆盖率在软件测试中具有重要意义，它不仅是衡量测试质量的重要指标之一，还可以为测试工作提供有价值的指导，促进团队协作和持续改进。因此，在软件开发和测试过程中，应该高度重视代码覆盖率的提升和优化。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140243712</p>]]></description><guid isPermaLink="false">/archives/a4be19da-3c84-4402-bf3c-be79e45baffd</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 09:59:16 GMT</pubDate></item><item><title><![CDATA[测试工程师热门面试问题一]]></title><link>https://wiki.dgfygk.com/archives/b326cd1e-692f-4488-ab61-8d6853a419ea</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%83%AD%E9%97%A8%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E4%B8%80&amp;url=/archives/b326cd1e-692f-4488-ab61-8d6853a419ea" width="1" height="1" alt="" style="opacity:0;">
<h2>请简述软件测试的目的和重要性。</h2>
<p>软件测试的目的和重要性在软件开发过程中占据着至关重要的地位。以下是对软件测试目的和重要性的详细阐述：</p>
<h4>软件测试的目的</h4>
<ol>
 <li>
  <p><strong>发现缺陷（Bug）</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>
</ol>
<h4>软件测试的重要性</h4>
<ol>
 <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>
</ol>
<p>综上所述，软件测试的目的在于发现缺陷、确保软件质量、提供可靠信息、验证软件需求以及预防缺陷。而软件测试的重要性则体现在提高软件质量、降低维护成本、增强用户信任、符合法规标准和提升团队能力等多个方面。因此，在软件开发过程中，软件测试是不可或缺的重要环节。</p>
<h2>什么是软件测试的生命周期？包括哪些阶段？</h2>
<p>软件测试的生命周期是指从软件测试计划开始，到测试结束并生成测试报告的一系列有序过程。它贯穿于软件开发的整个过程中，是确保软件质量的重要环节。以下是软件测试生命周期的详细阶段划分：</p>
<h4>1. 需求阶段</h4>
<ul>
 <li><strong>目的</strong>：测试人员需要了解并深入理解软件的需求，确保对需求有清晰的认识。</li>
 <li><strong>活动</strong>：测试人员会对需求进行分解，以便更好地理解和测试。同时，他们还会与项目团队的其他成员（如产品经理、开发人员等）进行沟通，确保对需求的理解一致。</li>
</ul>
<h4>2. 计划阶段</h4>
<ul>
 <li><strong>目的</strong>：制定详细的测试计划，为后续的测试活动提供指导。</li>
 <li><strong>活动</strong>：根据需求分析的结果，测试人员会制定测试计划，包括测试范围、测试策略、测试资源、测试时间表等。同时，还会制定质量保证计划，明确质量目标、测试标准等。</li>
</ul>
<h4>3. 设计阶段</h4>
<ul>
 <li><strong>目的</strong>：设计测试用例和测试场景，为后续的测试实施提供具体的指导。</li>
 <li><strong>活动</strong>：测试人员会根据测试计划和需求分析的结果，设计测试用例和测试场景。这些测试用例将覆盖软件的各种功能和场景，以确保测试的全面性和有效性。同时，测试人员还会根据测试用例设计测试数据，为测试实施做好准备。</li>
</ul>
<h4>4. 构建/编码阶段（有时也作为独立阶段）</h4>
<ul>
 <li><strong>目的</strong>：确保软件代码的正确性和可测试性。</li>
 <li><strong>活动</strong>：虽然这个阶段主要由开发人员负责，但测试人员也需要参与，确保代码的质量符合测试要求。此外，测试人员还会针对已编码的模块进行单元测试，以验证代码的正确性。</li>
</ul>
<h4>5. 测试实施阶段</h4>
<ul>
 <li><strong>目的</strong>：执行测试用例，发现并记录软件中的缺陷。</li>
 <li><strong>活动</strong>：测试人员会根据测试计划和测试用例，执行测试活动。在测试过程中，他们会记录测试结果，包括发现的缺陷和测试数据。同时，测试人员还会与开发人员进行沟通，确认缺陷并跟踪其修复情况。</li>
</ul>
<h4>6. 测试评估阶段</h4>
<ul>
 <li><strong>目的</strong>：对测试结果进行分析和评估，确定软件是否满足质量要求。</li>
 <li><strong>活动</strong>：测试人员会对测试结果进行统计和分析，评估软件的质量水平。他们还会根据评估结果，编写测试报告，向项目团队和管理层汇报测试情况和发现的问题。</li>
</ul>
<h4>7. 实施后阶段（可选，但重要）</h4>
<ul>
 <li><strong>目的</strong>：在软件发布后，持续监控软件的运行状态，确保软件的稳定性和可靠性。</li>
 <li><strong>活动</strong>：测试人员会在软件发布后，继续收集用户反馈和问题报告。他们会对这些问题进行分析和评估，确定是否需要进行修复或优化。同时，测试人员还会与开发人员和其他团队成员合作，确保软件的持续改进和升级。</li>
</ul>
<p>需要注意的是，软件测试生命周期的具体阶段可能会因不同的项目和组织而有所不同。但总体上，它们都遵循了从需求到计划、设计、实施、评估再到实施后的基本流程。此外，随着软件测试技术和方法的不断发展，软件测试生命周期也在不断演进和完善。</p>
<p>原文链接: https://blog.csdn.net/hai40587/article/details/140243622</p>]]></description><guid isPermaLink="false">/archives/b326cd1e-692f-4488-ab61-8d6853a419ea</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 09:59:00 GMT</pubDate></item><item><title><![CDATA[day-57 边积分最高的节点]]></title><link>https://wiki.dgfygk.com/archives/21d17501-6f6a-4439-ab08-3121004b53ca</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-57%20%E8%BE%B9%E7%A7%AF%E5%88%86%E6%9C%80%E9%AB%98%E7%9A%84%E8%8A%82%E7%82%B9&amp;url=/archives/21d17501-6f6a-4439-ab08-3121004b53ca" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7016d6e27fec4741ba6300ad55b5f209.png&amp;size=m" alt=""></p>
<p><strong>思路</strong>
 <br>
 因为节点编号从0到n-1，所有可以创建一个数组用来记录每个节点的边积分，遍历edges数组，并且令ansarr[edges[i]]+=i，遍历结束后即可得到每个节点的边积分，再遍历ansarr数组，找到值最大的那个索引即为答案，有相同的则返回较小的索引</p>
<p><strong>解题过程</strong>
 <br>
 注意：节点的边积分可能超过Integer型的最大值，所以要用long型</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int edgeScore(int[] edges) {

        int len=edges.length;
        long ansarr[]=new long[len];
        for(int i=0;i&lt;len;i++){

            ansarr[edges[i]]+=i;
        }
        int ans=-1;
        long max=-999;
        for(int i=0;i&lt;len;i++){

            if(ansarr[i]&gt;max){

                ans=i;
                max=ansarr[i];
            }
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/node-with-highest-edge-score/solutions/2925303/bian-ji-fen-zui-gao-de-jie-dian-chao-yue-lz3u/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142423900</p>]]></description><guid isPermaLink="false">/archives/21d17501-6f6a-4439-ab08-3121004b53ca</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:54:35 GMT</pubDate></item><item><title><![CDATA[day-56 整数转罗马数字]]></title><link>https://wiki.dgfygk.com/archives/357e6087-091b-41b3-a5f0-b2cf90679ffe</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-56%20%E6%95%B4%E6%95%B0%E8%BD%AC%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97&amp;url=/archives/357e6087-091b-41b3-a5f0-b2cf90679ffe" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F278719fadf7f4a7cac37e65708ebefb0.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 因为题目已经告知num&lt;=3999，所以只需依次对1000,900,500,400,100,90,50,40,10,9,5,4,1这些数进行除法，判断商是否大于等于1，如果大于等于1则将对应的罗马数字拼接到字符串中即可</p>
<p><strong>解题过程</strong>
 <br>
 为了方便拼接，可以使用哈希键值对，key为整数，value为对应的罗马数字</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public String intToRoman(int num) {

        Map&lt;Integer,String&gt; map=new HashMap&lt;&gt;();
        map.put(1,"I");
        map.put(4,"IV");
        map.put(5,"V");
        map.put(9,"IX");
        map.put(10,"X");
        map.put(40,"XL");
        map.put(50,"L");
        map.put(90,"XC");
        map.put(100,"C");
        map.put(400,"CD");
        map.put(500,"D");
        map.put(900,"CM");
        map.put(1000,"M");
        int arr[]={
   1000,900,500,400,100,90,50,40,10,9,5,4,1};
        String ans="";
        int id=0;
        while(id&lt;13&amp;&amp;num&gt;0){

            if(num/arr[id]!=0){

                ans+=map.get(arr[id]);
                num=num-arr[id];
            }else{

                id++;
            }
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/integer-to-roman/solutions/2923381/zheng-shu-zhuan-luo-ma-shu-zi-by-ashi-ji-vz7b/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142376292</p>]]></description><guid isPermaLink="false">/archives/357e6087-091b-41b3-a5f0-b2cf90679ffe</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:54:10 GMT</pubDate></item><item><title><![CDATA[day-58 最接近的三数之和]]></title><link>https://wiki.dgfygk.com/archives/61dbede6-a262-4c29-8cf4-d37b3180a64a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-58%20%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C&amp;url=/archives/61dbede6-a262-4c29-8cf4-d37b3180a64a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F57bf5119c0c044afba6962d5fa8f4608.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 双指针：首先利用Arrays.sort()函数对数组进行排序，通过枚举中间位置的那个数，题目就变为了双指针</p>
<p><strong>解题过程</strong>
 <br>
 指针边界（right&gt;i\&amp;\&amp;left<i），当三数之和小于等于target时，left++,反之right&ndash;< p> </i），当三数之和小于等于target时，left++,反之right&ndash;<></p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int threeSumClosest(int[] nums, int target) {

        Arrays.sort(nums);
        int len=nums.length;
        int ans=nums[0]+nums[1]+nums[2];
        int num=Math.abs(ans-target);
        int left=0;
        int right=len-1;
        for(int i=1;i&lt;len-1;i++){

            left=0;
            right=len-1;
            while(right&gt;i&amp;&amp;left&lt;i){

                if(Math.abs(nums[i]+nums[left]+nums[right]-target)&lt;num){

                    ans=nums[i]+nums[left]+nums[right];
                    num=Math.abs(nums[i]+nums[left]+nums[right]-target);
                }
                if(nums[i]+nums[left]+nums[right]&lt;=target)left++;
                else right--;
            }
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/3sum-closest/solutions/2927721/zui-jie-jin-de-san-shu-zhi-he-by-ashi-ji-jmb8/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142466994</p>]]></description><guid isPermaLink="false">/archives/61dbede6-a262-4c29-8cf4-d37b3180a64a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:53:58 GMT</pubDate></item><item><title><![CDATA[day-59 两两交换链表中的节点]]></title><link>https://wiki.dgfygk.com/archives/44a5adeb-255e-4c09-81d1-d3578433fd54</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-59%20%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9&amp;url=/archives/44a5adeb-255e-4c09-81d1-d3578433fd54" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd6ead53f01f04e18bcd2d30a31876fbe.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 只需将链表两两交换节点即可，如果是奇数个节点，最后一个节点则不用交换</p>
<p><strong>解题过程</strong>
 <br>
 可以先自定义一个头结点thead，这样更便于思考交换，最后返回thead.next即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode swapPairs(ListNode head) {

        if(head==null||head.next==null) return head;
        ListNode thead=new ListNode();
        thead.next=head;
        ListNode pre=thead;
        ListNode mid=thead.next;
        ListNode next=thead.next.next;
        while(true){

            mid.next=next.next;
            next.next=mid;
            pre.next=next;

            pre=pre.next.next;
            if(pre.next!=null) mid=pre.next;
            else break;
            if(pre.next.next!=null) next=pre.next.next;
            else break;
        }
        return thead.next;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/swap-nodes-in-pairs/solutions/2928982/liang-liang-jiao-huan-lian-biao-zhong-de-jq44/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142497000</p>]]></description><guid isPermaLink="false">/archives/44a5adeb-255e-4c09-81d1-d3578433fd54</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:53:49 GMT</pubDate></item><item><title><![CDATA[day-60 字符串中最多数目的子序列]]></title><link>https://wiki.dgfygk.com/archives/afeec0f1-28d1-4f28-96ba-511f5595fe39</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-60%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%9C%80%E5%A4%9A%E6%95%B0%E7%9B%AE%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97&amp;url=/archives/afeec0f1-28d1-4f28-96ba-511f5595fe39" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0af8a7ced1714f15ab490a677e708180.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 由题目可以得出，当字符串开头插入pattern[0]或在字符串结尾插入pattern[1]这两种情况中的一种所得到的子序列数目一定是最多的</p>
<p><strong>解题过程</strong>
 <br>
 我们可以遍历字符串，统计pattern[0]的个数，每当遇到一个pattern[1]时，序列数就会加上已经遍历的pattern[0]的个数，最后加上Math.max(l,r)</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public long maximumSubsequenceCount(String text, String pattern) {

        int len=text.length();
         int l=0,r=0;
         long ans=0;
        for(int i=0;i&lt;len;i++){

            if(text.charAt(i)==pattern.charAt(0)){

               if(pattern.charAt(0)==pattern.charAt(1)) {

                   ans+=l;
               }
               l++;

            }else if(text.charAt(i)==pattern.charAt(1)){

               r++;
               ans+=l;
            }
        }
        return ans+Math.max(l,r);
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string/solutions/2930151/zi-fu-chuan-zhong-zui-duo-shu-mu-de-zi-x-0bna/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142530292</p>]]></description><guid isPermaLink="false">/archives/afeec0f1-28d1-4f28-96ba-511f5595fe39</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:53:38 GMT</pubDate></item><item><title><![CDATA[SpringCloud-07 GateWay01 网关技术]]></title><link>https://wiki.dgfygk.com/archives/f3f0b37d-9950-45f0-bf0d-6c2d1453edec</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-07%20GateWay01%20%E7%BD%91%E5%85%B3%E6%8A%80%E6%9C%AF&amp;url=/archives/f3f0b37d-9950-45f0-bf0d-6c2d1453edec" width="1" height="1" alt="" style="opacity:0;">
<p>Spring Cloud Gateway组件的核心是一系列的过滤器，通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器，隐藏微服务结点IP端口信息，从而加强安全保护。Spring Cloud Gateway本身也是一个微服务，需要注册进服务注册中心。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd82c7a8a775d44c6a6db51197e2cd0cd.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F04c56133cdfe47528bba857f4211e557.png&amp;size=m" alt=""></p>
<p><i&ndash;图片均来自尚硅谷&ndash;></i&ndash;图片均来自尚硅谷&ndash;></p>
<p>Nginx 和 Spring Cloud Gateway 都是流行的网关技术，但它们在多个方面有所不同。Nginx 是一个用 C 语言编写的高性能 Web 服务器和反向代理，适合处理静态资源和负载均衡。而 Gateway 是基于 Spring 生态系统的响应式 API 网关，专为微服务设计，提供动态路由和细粒度的 API 管理。Nginx 通过 Lua 脚本扩展，而 Gateway 支持在 Java 框架上进行代码扩展。选择使用哪一个通常取决于项目需求：Nginx 适合作为流量入口和负载均衡器，Gateway 适合作为微服务架构中的业务网关。</p>
<p><strong>三大核心：</strong> <em>路由(Route) 断言(Predicate) 过滤器(Filter)</em></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7e5212728b124de2aa8d2c2023accaa7.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff4a7a344c9024598bc991daf29d2c6cb.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F897aa1d1fe174e3891125bbdeed0cae0.png&amp;size=m" alt=""></p>
<p>客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由，将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑，然后返回。
 <br>
 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。
 <br>
 在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;
 <br>
 在"post"类型的过滤器中可以做响应内容、响应头的修改，日志的输出，流量监控等有着非常重要的作用。</p>
<p><strong>核心逻辑：</strong> <em>路由转发+断言判断+执行过滤器链</em></p>
<p>//新建Module</p>
<p>//改pom</p>
<pre><code class="prism language-java">    &lt;dependencies&gt;
        &lt;!--gateway--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-consul-discovery&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
</code></pre>
<p>//改yml</p>
<pre><code class="prism language-java">server:
  port: 9527

spring:
  application:
    name: cloud-gateway #以微服务注册进consul或nacos服务列表内
  cloud:
    consul: #配置consul地址
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true
        service-name: ${
   spring.application.name}
</code></pre>
<p>//启动类</p>
<pre><code class="prism language-java">@SpringBootApplication
@EnableDiscoveryClient//注册服务与发现
public class Main9527 {

    public static void main(String[] args) {

        SpringApplication.run(Main9527.class,args);
    }
}
</code></pre>
<p>GateWay也会作为一个微服务入驻进服务注册中心
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F648127fe942442af9eca8798bc9f3661.png&amp;size=m" alt="">
 <br>
 在8001端口新建PayGateWayController类</p>
<pre><code class="prism language-java">@RestController
public class PayGateWayController {

    @Resource
    PayService payService;

    @GetMapping(value = "/pay/gateway/get/{id}")
    public ResultData&lt;Pay&gt; getById(@PathVariable("id") Integer id)
    {

        Pay pay = payService.getById(id);
        return ResultData.success(pay);
    }

    @GetMapping(value = "/pay/gateway/info")
    public ResultData&lt;String&gt; getGatewayInfo()
    {

        return ResultData.success("gateway info test："+ IdUtil.simpleUUID());
    }
}
</code></pre>
<p>修改9527端口yml配置</p>
<pre><code class="prism language-java">server:
  port: 9527

spring:
  application:
    name: cloud-gateway #以微服务注册进consul或nacos服务列表内
  cloud:
    consul: #配置consul地址
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true
        service-name: ${
   spring.application.name}
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID)，没有固定规则但要求唯一，建议配合服务名
          uri: http://localhost:8001                #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言，路径相匹配的进行路由


        - id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID)，没有固定规则但要求唯一，建议配合服务名
          uri: http://localhost:8001                #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/info/**              # 断言，路径相匹配的进行路由
</code></pre>
<p>访问9527映射到了8001
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa0069fbb0e7745ce884eec6f26439556.png&amp;size=m" alt="">
 <br>
 在通用接口新增</p>
<pre><code class="prism language-java">    @GetMapping(value = "/pay/gateway/get/{id}")
    public ResultData getById(@PathVariable("id") Integer id);

    @GetMapping(value = "/pay/gateway/info")
    public ResultData&lt;String&gt; getGatewayInfo();
</code></pre>
<p>在feign80端口新增OrderGateWayController类</p>
<pre><code class="prism language-java">@RestController
public class OrderGateWayController
{

    @Resource
    private PayFeignApi payFeignApi;

    @GetMapping(value = "/feign/pay/gateway/get/{id}")
    public ResultData getById(@PathVariable("id") Integer id)
    {

        return payFeignApi.getById(id);
    }

    @GetMapping(value = "/feign/pay/gateway/info")
    public ResultData&lt;String&gt; getGatewayInfo()
    {

        return payFeignApi.getGatewayInfo();
    }
}
</code></pre>
<p>网关关闭和网关开启都能正常访问
 <br>
 这是因为：<em>同一个项目内部自己人，不经过网关，直接找微服务 外部人员访问才会经过网关</em></p>
<p>在通用接口修改代码（模拟外部人员访问）</p>
<pre><code class="prism language-java">//@FeignClient(value="cloud-payment-service")
@FeignClient(value="cloud-gateway")
</code></pre>
<p>网关关闭
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2a5ccd962644408695bcbbe46d1ae8e4.png&amp;size=m" alt="">
 <br>
 网关开启
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7d390a0e7e9949eb82cd3b6b9f1c97ac.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142368616</p>]]></description><guid isPermaLink="false">/archives/f3f0b37d-9950-45f0-bf0d-6c2d1453edec</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:53:25 GMT</pubDate></item><item><title><![CDATA[day-58 最佳观光组合]]></title><link>https://wiki.dgfygk.com/archives/1a8d0331-0593-443d-9a2c-eb05b20e910a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-58%20%E6%9C%80%E4%BD%B3%E8%A7%82%E5%85%89%E7%BB%84%E5%90%88&amp;url=/archives/1a8d0331-0593-443d-9a2c-eb05b20e910a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbbba2f7bb82f4d748f56b947c7609224.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 用right表示最佳观光组合的右边的景点的索引，同时维护一个该景点左边values[left]+left的最大值</p>
<p><strong>解题过程</strong>
 <br>
 这样在遍历过程中，以right为右边景点的最佳得分即为values[right]-right+leftmax，再与ans比较，选择较大的那一个，同时维护leftmax的值，leftmax=Math.max(leftmax,values[right]+right)</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int maxScoreSightseeingPair(int[] values) {

        int len=values.length;
        int leftmax=0;
        int ans=0;
       for(int right=0;right&lt;len;right++){

        ans=Math.max(ans,values[right]-right+leftmax);
        leftmax=Math.max(leftmax,values[right]+right);
       }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/best-sightseeing-pair/solutions/2926968/zui-jia-guan-guang-zu-he-by-ashi-jian-ch-ey5l/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142451025</p>]]></description><guid isPermaLink="false">/archives/1a8d0331-0593-443d-9a2c-eb05b20e910a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:52:58 GMT</pubDate></item><item><title><![CDATA[day-59 四数之和]]></title><link>https://wiki.dgfygk.com/archives/bcbafc71-55af-468b-9ff4-3d1f5760df99</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-59%20%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C&amp;url=/archives/bcbafc71-55af-468b-9ff4-3d1f5760df99" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F972bc8b5854b437eb8dc6240d005b965.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 双指针：类似16. 最接近的三数之和，将数组排序后，只需要枚举第一个数，则会变为与第16题相似的解题思路</p>
<p><strong>解题过程</strong>
 <br>
 枚举选取的第一个数，0&lt;=i<len-3,然后就是第16题的解题思路< p> </len-3,然后就是第16题的解题思路<></p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;Integer&gt;&gt; fourSum(int[] nums, int target) {

        Arrays.sort(nums);
        int len=nums.length;
        int m=0;
        int n=0;
        List&lt;List&lt;Integer&gt;&gt; list=new ArrayList&lt;&gt;();
        for(int i=0;i&lt;len-3;i++){

            for(int j=2;j&lt;len-1;j++){

                m=i+1;
                n=len-1;
                while(m&lt;j&amp;&amp;n&gt;j){

                    long num=(long)nums[i]+nums[j]+nums[m]+nums[n];
                    if(num==target){

                        List&lt;Integer&gt; t=new ArrayList&lt;&gt;();
                        t.add(nums[i]);
                        t.add(nums[j]);
                        t.add(nums[m]);
                        t.add(nums[n]);
                        if(!list.contains(t))
                        list.add(t);
                    }
                    if(num&lt;=target) m++;
                    else n--;
                }
            }
        }
        return list;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/4sum/solutions/2928969/si-shu-zhi-he-by-ashi-jian-chong-dan-lia-rkvp/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142496823</p>]]></description><guid isPermaLink="false">/archives/bcbafc71-55af-468b-9ff4-3d1f5760df99</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:52:48 GMT</pubDate></item><item><title><![CDATA[day-59 两数相除]]></title><link>https://wiki.dgfygk.com/archives/f8b20c6d-5525-4293-95f7-e38f4d6ac29e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-59%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E9%99%A4&amp;url=/archives/f8b20c6d-5525-4293-95f7-e38f4d6ac29e" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb01e1b6d09414453b5284acee11a2a0c.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 首先判断商的正负，然后便可将两个数都转换为整数，方便商的计算，商的计算用被除数不断减去除数即可</p>
<p><strong>解题过程</strong>
 <br>
 注意：整型的负值比正值多一个数，这种特殊情况需要需要单独讨论，同时当除数为1时，很可能会超时也需要单独讨论</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int divide(int dividend, int divisor) {

        boolean flag=true;
        if((dividend&lt;0&amp;&amp;divisor&gt;0)||(dividend&gt;0&amp;&amp;divisor&lt;0)) flag=false;
        int ans=0;
        long tdividend=Math.abs((long)dividend);
        long tdivisor=Math.abs((long)divisor);
        if(tdividend==-1*(long)Integer.MIN_VALUE&amp;&amp;tdivisor==1&amp;&amp;flag) return Integer.MAX_VALUE;
        if(tdividend==-1*(long)Integer.MIN_VALUE&amp;&amp;tdivisor==1&amp;&amp;!flag) return Integer.MIN_VALUE;
        if(tdividend==(long)Integer.MAX_VALUE&amp;&amp;tdivisor==1&amp;&amp;flag) return Integer.MAX_VALUE;
        if(tdividend==(long)Integer.MAX_VALUE&amp;&amp;tdivisor==1&amp;&amp;!flag) return -Integer.MAX_VALUE;
        while(tdividend&gt;=tdivisor){

            ans++;
            tdividend=tdividend-tdivisor;
        }
        if(flag) return ans;
        else return -1*ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/divide-two-integers/solutions/2929015/liang-shu-xiang-chu-by-ashi-jian-chong-d-81tj/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142497422</p>]]></description><guid isPermaLink="false">/archives/f8b20c6d-5525-4293-95f7-e38f4d6ac29e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:52:37 GMT</pubDate></item><item><title><![CDATA[day-56 整数反转]]></title><link>https://wiki.dgfygk.com/archives/3240cd95-4999-4cbf-a078-0f735aec7e43</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-56%20%E6%95%B4%E6%95%B0%E5%8F%8D%E8%BD%AC&amp;url=/archives/3240cd95-4999-4cbf-a078-0f735aec7e43" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2e8cab9cb1ad4dc58d2f14917bf4246a.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 首先将整数变为字符串，然后判断正负，如果是负数把负号去掉，再将字符串从后向前遍历，遍历过程中用一个long型记录转换后的数字</p>
<p><strong>解题过程</strong>
 <br>
 最后判断转换后的数是否超出整型的边界即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int reverse(int x) {

        String s=Integer.toString(x);
        boolean flag=true;
        if(s.charAt(0)=='-'){

            s=s.substring(1,s.length());
            flag=false;
        }
        long ans=0;
        int id=s.length()-1;
        while(id&gt;=0){

            ans=ans*10+(s.charAt(id)-'0');
            id--;
        }
        if(flag){

            if(ans&gt;Integer.MAX_VALUE)return 0;
            else return (int)ans;
        }else{

            if(-1*ans&lt;Integer.MIN_VALUE)return 0;
            else{

                ans=ans*-1;
                return (int)ans;
            } 
        }
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/reverse-integer/solutions/2923353/zheng-shu-fan-zhuan-by-ashi-jian-chong-d-uepn/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142375547</p>]]></description><guid isPermaLink="false">/archives/3240cd95-4999-4cbf-a078-0f735aec7e43</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:52:26 GMT</pubDate></item><item><title><![CDATA[day-56 字符串转换整数 (atoi)]]></title><link>https://wiki.dgfygk.com/archives/d76d508a-4ee3-4825-9708-945efdbf27b4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-56%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E6%95%B0%20%28atoi%29&amp;url=/archives/d76d508a-4ee3-4825-9708-945efdbf27b4" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F48c598bac9524d119b31acd78f102176.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用trim()函数去掉前后空格，然后判断是否有'+'或'-'号，如果有则去掉，然后依次向后遍历字符串，直到遇到非数字字符或遍历结束，遍历过程中用一个long型记录当前整数大小</p>
<p><strong>解题过程</strong>
 <br>
 最后判断整数大小是否超过整型的边界即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int myAtoi(String s) {

        s=s.trim();
        boolean flag=true;
        if(s.length()&gt;0&amp;&amp;s.charAt(0)=='-'){

            flag=false;
            s=s.substring(1,s.length());
        }else if(s.length()&gt;0&amp;&amp;s.charAt(0)=='+'){

            s=s.substring(1,s.length());
        }
        long ans=0;
        int id=0;
        while(id&lt;s.length()&amp;&amp;0&lt;=s.charAt(id)-'0'&amp;&amp;s.charAt(id)-'0'&lt;=9){

            ans=ans*10+s.charAt(id)-'0';
            if(flag&amp;&amp;ans&gt;=Integer.MAX_VALUE)return Integer.MAX_VALUE;
            if(!flag&amp;&amp;-1*ans&lt;=Integer.MIN_VALUE)return Integer.MIN_VALUE;
            id++;
        }
        if(flag){

            if(ans&gt;=Integer.MAX_VALUE)return Integer.MAX_VALUE;
            else{

                return (int)ans;
            }
        }else{

            ans=-1*ans;
            if(ans&lt;=Integer.MIN_VALUE)return Integer.MIN_VALUE;
            else return (int)ans;
        }
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/string-to-integer-atoi/solutions/2923358/zi-fu-chuan-zhuan-huan-zheng-shu-atoi-by-2ce7/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142375722</p>]]></description><guid isPermaLink="false">/archives/d76d508a-4ee3-4825-9708-945efdbf27b4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:51:31 GMT</pubDate></item><item><title><![CDATA[day-56 最长的字母序连续子字符串的长度]]></title><link>https://wiki.dgfygk.com/archives/b111c14c-48fe-46c1-93f2-a5329bfbfa2e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-56%20%E6%9C%80%E9%95%BF%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BA%8F%E8%BF%9E%E7%BB%AD%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E9%95%BF%E5%BA%A6&amp;url=/archives/b111c14c-48fe-46c1-93f2-a5329bfbfa2e" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6f1b0c07baec4838953e999dc7d676fa.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 双指针，用left和right表示以right为右边界的连续子字符串，left表示其左边界</p>
<p><strong>解题过程</strong>
 <br>
 right从0到s.length()-1遍历：1.如果当前字符减去前一个字符的值为1,则当前字符与前面的字符是连续的，此时left不变，right++;2.如果当前字符减去前一个字符的值为1,则当前字符与前面的字符是不连续的，此时left=right，right++。在遍历过程中维持一个ans变量表示最大长度，遍历结束后最后返回ans即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int longestContinuousSubstring(String s) {

        int ans=1;
        for(int i=0,j=1;j&lt;s.length();j++){

           if(s.charAt(j)-s.charAt(j-1)==1) {

               ans=Math.max(ans,j-i+1);
           }else{

               i=j;
           }
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring/solutions/2923370/zui-chang-de-zi-mu-xu-lian-xu-zi-zi-fu-c-ymuq/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142376020</p>]]></description><guid isPermaLink="false">/archives/b111c14c-48fe-46c1-93f2-a5329bfbfa2e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:51:17 GMT</pubDate></item><item><title><![CDATA[SpringCloud-05 Resilience4J 服务降级和熔断]]></title><link>https://wiki.dgfygk.com/archives/8fa957e7-eb6c-47eb-8f31-680bd0c14488</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-05%20Resilience4J%20%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7%E5%92%8C%E7%86%94%E6%96%AD&amp;url=/archives/8fa957e7-eb6c-47eb-8f31-680bd0c14488" width="1" height="1" alt="" style="opacity:0;">
<p><em>服务雪崩</em> ：指在多个服务之间存在依赖关系时，当一个服务发生故障或不可用时，导致其他服务也无法正常工作的情况。这种现象通常是因为服务之间的依赖关系过于紧密，当一个服务发生故障时，其他服务无法正确处理该服务的请求或返回结果，从而导致级联故障，整个系统无法正常运行。所以，有问题的节点，快速熔断（快速返回失败处理或者返回默认兜底数据【服务降级】）。
 <br>
 “断路器"本身是一种开关装置，当某个服务单元发生故障之后，通过断路器的故障监控（类似熔断保险丝），向调用方返回一个符合预期的、可处理的备选响应(FallBack)，而不是长时间的等待或者抛出调用方无法处理的异常，这样就保证了服务调用方的线程不会被长时间、不必要地占用，从而避免了故障在分布式系统中的蔓延，乃至雪崩。</p>
<p><em>服务熔断</em>：服务熔断（Circuit Breaker）是一种用来控制服务间调用的一种机制，目的是为了减少故障对系统的影响。当某个服务出现故障或者响应时间过长时，熔断器会自动打开，停止向该服务发送请求，而是返回一个预设的错误响应。这样可以防止故障或响应缓慢的服务拖垮整个系统，同时也可以快速失败，减少用户的等待时间。</p>
<p><em>服务降级</em>：服务降级是指在系统负载过高或者出现异常情况下，为了保证核心功能的稳定性和可用性，暂时关闭一些非核心功能或者降低服务的质量，以保证系统整体的稳定运行。服务降级可以通过调整系统配置参数、限流、降低并发请求等方式实现。</p>
<p><em>服务限流</em> <em>服务限时</em> <em>服务预热</em> <em>接近实时的监控</em> <em>兜底的处理动作</em></p>
<p><em>Circuit Breaker</em>：一种设计模式，用于在分布式系统中处理故障和延迟。在分布式系统中，服务之间通常会相互调用。当一个服务发生故障或延迟时，调用链上的其他服务可能会受到影响，导致整个系统出现级联故障。为了解决这个问题，引入了Circuit Breaker模式。Circuit Breaker模式包含三个状态：关闭、开启和半开。</p>
<p>Circuit Breaker是一套规范和接口，真正落地实现的是Resilience4J</p>
<p>Resilience4J是一个用于构建可靠和弹性应用程序的Java库。它提供了一套轻量级的容错模式和工具，帮助开发人员更容易地实现应用程序的弹性。 <em>需要Java17</em></p>
<pre><code class="prism language-java">//核心模块
resilience4j-circuitbreaker: 熔断     √
resilience4j-ratelimiter: 限流            √
resilience4j-bulkhead: 舱壁               √
resilience4j-retry: ⾃动重试
resilience4j-cache: 结果缓存
resilience4j-timelimiter: 超时处理
</code></pre>
<p><strong>1.熔断</strong>
 <br>
 熔断器有三种状态：关闭状态（正常状态）、开启状态（不允许通过）和半开状态。</p>
<pre><code class="prism language-java">failure-rate-threshold 
以百分比配置失败率峰值

sliding-window-type 
断路器的滑动窗口期类型
可以基于"次数"（COUNT_BASED）或者"时间"（TIME_BASED）进行熔断，默认是COUNT_BASED。

sliding-window-size
若COUNT_BASED，则10次调用中有50%失败（即5次）打开熔断断路器；
若为TIME_BASED则，此时还有额外的两个设置属性，含义为：在N秒内（sliding-window-size）100%（slow-call-rate-threshold）的请求超过N秒（slow-call-duration-threshold）打开断路器。

slowCallRateThreshold
以百分比的方式配置，断路器把调用时间大于slowCallDurationThreshold的调用视为慢调用，当慢调用比例大于等于峰值时，断路器开启，并进入服务降级。

slowCallDurationThreshold
配置调用时间的峰值，高于该峰值的视为慢调用。

permitted-number-of-calls-in-half-open-state
运行断路器在HALF_OPEN状态下时进行N次调用，如果故障或慢速调用仍然高于阈值，断路器再次进入打开状态。

minimum-number-of-calls
在每个滑动窗口期样本数，配置断路器计算错误率或者慢调用率的最小调用数。比如设置为5意味着，在计算故障率之前，必须至少调用5次。如果只记录了4次，即使4次都失败了，断路器也不会进入到打开状态。

wait-duration-in-open-state
从OPEN到HALF_OPEN状态需要等待的时间
</code></pre>
<p>配置 滑动窗口类型： COUNT_BASED</p>
<pre><code class="prism language-java">//在8001端口新增controller
@RestController
public class PayCircuitController {

    //=========Resilience4j CircuitBreaker 的例子
    @GetMapping(value = "/pay/circuit/{id}")
    public String myCircuit(@PathVariable("id") Integer id)
    {

        if(id == -4) throw new RuntimeException("----circuit id 不能负数");
        if(id == 9999){

            try {
    TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) {

                e.printStackTrace(); }
        }
        return "Hello, circuit! inputId:  "+id+" \t " + IdUtil.simpleUUID();
    }
}


//在通用接口添加
/**
     * Resilience4j CircuitBreaker 的例子
     * @param id
     * @return
     */
    @GetMapping(value = "/pay/circuit/{id}")
    public String myCircuit(@PathVariable("id") Integer id);



//在80端口pom文件新增依赖
&lt;!--resilience4j-circuitbreaker--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-circuitbreaker-resilience4j&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;!-- 由于断路保护等需要AOP实现，所以必须导入AOP包 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-aop&lt;/artifactId&gt;
&lt;/dependency&gt;


//改yml文件
server:
  port: 80

spring:
  application:
    name: cloud-consumer-openfeign-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${
   spring.application.name}
    openfeign:
      client:
        config:
          default:
            #连接超时时间
            connectTimeout: 3000          #3秒
            #读取超时时间
            readTimeout: 3000
          cloud-payment-service:
            #连接超时时间
            connectTimeout: 20000
            #读取超时时间
            readTimeout: 20000
      httpclient:
        hc5:
          enabled: true
      compression:
        request:
          enabled: true
          min-request-size: 2048 #最小触发压缩的大小
          mime-types: text/xml,application/xml,application/json #触发压缩数据类型
        response:
          enabled: true
        # 开启circuitbreaker和分组激活 spring.cloud.openfeign.circuitbreaker.enabled
        circuitbreaker:
          enabled: true
          group:
            enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后
# feign日志以什么级别监控哪个接口        OpenFeign内容
logging:
  level:
    com:
      dc:
        cloud:
          apis:
            PayFeignApi: debug

# Resilience4j CircuitBreaker 按照次数：COUNT_BASED 的例子
#  6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
#  等待5秒后，CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态，允许一些请求通过以测试服务是否恢复正常。
#  如还是异常CircuitBreaker 将重新进入开启OPEN状态；如正常将进入关闭CLOSE闭合状态恢复正常处理请求。
resilience4j:
  circuitbreaker:
    configs:
      default:
        failureRateThreshold: 50 #设置50%的调用失败时打开断路器，超过失败请求百分⽐CircuitBreaker变为OPEN状态。
        slidingWindowType: COUNT_BASED # 滑动窗口的类型
        slidingWindowSize: 6 #滑动窗⼝的⼤⼩配置COUNT_BASED表示6个请求，配置TIME_BASED表示6秒
        minimumNumberOfCalls: 6 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果minimumNumberOfCalls为10，则必须最少记录10个样本，然后才能计算失败率。如果只记录了9次调用，即使所有9次调用都失败，断路器也不会开启。
        automaticTransitionFromOpenToHalfOpenEnabled: true # 是否启用自动从开启状态过渡到半开状态，默认值为true。如果启用，CircuitBreaker将自动从开启状态过渡到半开状态，并允许一些请求通过以测试服务是否恢复正常
        waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
        permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数，默认值为10。在半开状态下，CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过，如果其中有任何一个请求失败，CircuitBreaker将重新进入开启状态。
        recordExceptions:
          - java.lang.Exception
    instances:
      cloud-payment-service:
        baseConfig: default


//在80端口新增OrderCircuitController类
@RestController
public class OrderCircuitController {

    @Resource
    private PayFeignApi payFeignApi;

    @GetMapping(value = "/feign/pay/circuit/{id}")
    @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
    public String myCircuitBreaker(@PathVariable("id") Integer id)
    {

        return payFeignApi.myCircuit(id);
    }
    //myCircuitFallback就是服务降级后的兜底处理方法
    public String myCircuitFallback(Integer id,Throwable t) {

        // 这里是容错处理逻辑，返回备用结果
        return "myCircuitFallback，系统繁忙，请稍后再试-----/(ㄒoㄒ)/~~";
    }
}
</code></pre>
<p>滑动窗口类型： TIME_BASED</p>
<pre><code class="prism language-java">//把之前的基于COUNT_BASED的yml配置注释掉注释掉，改为新的yml配置
# Resilience4j CircuitBreaker 按照时间：TIME_BASED 的例子
resilience4j:
  timelimiter:
    configs:
      default:
        timeout-duration: 10s #神坑的位置，timelimiter 默认限制远程1s，超于1s就超时异常，配置了降级，就走降级逻辑
  circuitbreaker:
    configs:
      default:
        failureRateThreshold: 50 #设置50%的调用失败时打开断路器，超过失败请求百分⽐CircuitBreaker变为OPEN状态。
        slowCallDurationThreshold: 2s #慢调用时间阈值，高于这个阈值的视为慢调用并增加慢调用比例。
        slowCallRateThreshold: 30 #慢调用百分比峰值，断路器把调用时间⼤于slowCallDurationThreshold，视为慢调用，当慢调用比例高于阈值，断路器打开，并开启服务降级
        slidingWindowType: TIME_BASED # 滑动窗口的类型
        slidingWindowSize: 2 #滑动窗口的大小配置，配置TIME_BASED表示2秒
        minimumNumberOfCalls: 2 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。
        permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数，默认值为10。
        waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
        recordExceptions:
          - java.lang.Exception
    instances:
      cloud-payment-service:
        baseConfig: default 
</code></pre>
<p><strong>2.舱壁隔离</strong>
 <br>
 Spring Cloud的舱壁隔离是一种微服务架构中的隔离机制，用于防止服务之间的相互影响和故障扩散。舱壁隔离通过在不同的线程池或线程组中执行不同的服务实例，将它们隔离开来。
 <br>
 Spring Cloud提供了两种常用的舱壁隔离实现方法：<em>线程池隔离</em> 和 <em>信号量隔离</em>。</p>
<p><em>1.信号隔离</em>：</p>
<pre><code class="prism language-java">//PayCircuitController类中添加
//=========Resilience4j bulkhead 的例子
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id)
{

    if(id == -4) throw new RuntimeException("----bulkhead id 不能-4");

    if(id == 9999)
    {

        try {
    TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) {
    e.printStackTrace(); }
    }

    return "Hello, bulkhead! inputId:  "+id+" \t " + IdUtil.simpleUUID();
}

//在PayFeignApi接口新增方法
/**
 * Resilience4j Bulkhead 的例子
 * @param id
 * @return
 */
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id);

//在feign80端口新增依赖
&lt;!--resilience4j-bulkhead--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;io.github.resilience4j&lt;/groupId&gt;
    &lt;artifactId&gt;resilience4j-bulkhead&lt;/artifactId&gt;
&lt;/dependency&gt;

//新增yml配置
####resilience4j bulkhead 的例子
resilience4j:
  bulkhead:
    configs:
      default:
        maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
                maxWaitDuration: 1s # 当达到并发调用数量时，新的线程的阻塞时间，我只愿意等待1秒，过时不候进舱壁兜底fallback
    instances:
      cloud-payment-service:
        baseConfig: default
  timelimiter:
    configs:
      default:
        timeout-duration: 20s

//在OrderCircuitController类新增方法
/**
 *(船的)舱壁,隔离
 * @param id
 * @return
 */
@GetMapping(value = "/feign/pay/bulkhead/{id}")                                             //Bulkhead.Type.SEMAPHORE类型
@Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)  
public String myBulkhead(@PathVariable("id") Integer id)
{

    return payFeignApi.myBulkhead(id);
}
public String myBulkheadFallback(Throwable t)
{

    return "myBulkheadFallback，隔板超出最大数量限制，系统繁忙，请稍后再试-----/(ㄒoㄒ)/~~";
}
</code></pre>
<p>线程池隔离 FixedThreadPoolBulkhead 固定线程池舱壁</p>
<pre><code class="prism language-java">//修改yml配置
####resilience4j bulkhead -THREADPOOL的例子
resilience4j:
  timelimiter:
    configs:
      default:
        timeout-duration: 10s #timelimiter默认限制远程1s，超过报错不好演示效果所以加上10秒
  thread-pool-bulkhead:
    configs:
      default:
        core-thread-pool-size: 1
        max-thread-pool-size: 1
        queue-capacity: 1
    instances:
      cloud-payment-service:
        baseConfig: default
# spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离

 //修改controller
 @RestController
public class OrderCircuitController {

    @Resource
    private PayFeignApi payFeignApi;

    @GetMapping(value = "/feign/pay/circuit/{id}")
    @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
    public String myCircuitBreaker(@PathVariable("id") Integer id)
    {

        return payFeignApi.myCircuit(id);
    }
    //myCircuitFallback就是服务降级后的兜底处理方法
    public String myCircuitFallback(Integer id,Throwable t) {

        // 这里是容错处理逻辑，返回备用结果
        return "myCircuitFallback，系统繁忙，请稍后再试-----/(ㄒoㄒ)/~~";
    }

    /**
     *(船的)舱壁,隔离
     * @param id
     * @return
     */
//    @GetMapping(value = "/feign/pay/bulkhead/{id}")
//    @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)
//    public String myBulkhead(@PathVariable("id") Integer id)
//    {

//        return payFeignApi.myBulkhead(id);
//    }
//    public String myBulkheadFallback(Throwable t)
//    {

//        return "myBulkheadFallback，隔板超出最大数量限制，系统繁忙，请稍后再试-----/(ㄒoㄒ)/~~";
//    }

    //type = Bulkhead.Type.THREADPOOL
    @GetMapping(value = "/feign/pay/bulkhead/{id}")
    @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.THREADPOOL)
    public CompletableFuture&lt;String&gt; myBulkheadTHREADPOOL(@PathVariable("id") Integer id)
    {

        System.out.println(Thread.currentThread().getName()+"\t"+"开始进入");
        try{

            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e) {

            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"\t"+"准备离开");
        return CompletableFuture.supplyAsync(() -&gt; payFeignApi.myBulkhead(id)+"\t"+"Bulkhead.Type.THREADPOOL");
    }
    public CompletableFuture&lt;String&gt; myBulkheadFallbackTHREADPOOL(Throwable t)
    {

        return CompletableFuture.supplyAsync(() -&gt; "Bulkhead.Type.THREADPOOL,系统繁忙，请稍后再试-----/(ㄒoㄒ)/~~");
    }
}
</code></pre>
<p><strong>3.限流</strong>
 <br>
 几种常见的限流算法：
 <br>
 1.固定窗口限流算法（Fixed Window Algorithm）：在固定时间窗口内，限制请求的数量。例如，每秒最多允许处理100个请求。</p>
<p>2.滑动窗口限流算法（Sliding Window Algorithm）：在滑动的时间窗口内，限制请求的数量。滑动窗口可以是固定长度的时间段，也可以是固定数量的请求。根据滑动窗口的长度和滑动步长，可以调整限流的精度和准确性。</p>
<p>3.漏桶算法（Leaky Bucket Algorithm）：将请求以固定的速率放入一个容器（桶）中，如果桶已满，则丢弃请求。这种算法可以平滑请求的流量，避免突发的请求压力。</p>
<p>4.令牌桶算法（Token Bucket Algorithm）：桶中有一定数量的令牌，每个请求需要消耗一个令牌才能通过。每秒钟会向桶中添加一定数量的令牌。当桶中的令牌用完时，请求将被限制。 【常用】</p>
<pre><code class="prism language-java">//在PayCircuitController类中新增方法
//=========Resilience4j ratelimit 的例子
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id)
{

    return "Hello, myRatelimit欢迎到来 inputId:  "+id+" \t " + IdUtil.simpleUUID();
}

//新增接口
//=========Resilience4j ratelimit 的例子
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id)
{

    return "Hello, myRatelimit欢迎到来 inputId:  "+id+" \t " + IdUtil.simpleUUID();
}

//导入pom依赖
&lt;!--resilience4j-ratelimiter--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;io.github.resilience4j&lt;/groupId&gt;
    &lt;artifactId&gt;resilience4j-ratelimiter&lt;/artifactId&gt;
&lt;/dependency&gt;

//改yml配置
####resilience4j ratelimiter 限流的例子
resilience4j:
  ratelimiter:
    configs:
      default:
        limitForPeriod: 2 #在一次刷新周期内，允许执行的最大请求数
        limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次，将允许处理的最大请求数量重置为limitForPeriod
        timeout-duration: 1 # 线程等待权限的默认等待时间
    instances:
        cloud-payment-service:
          baseConfig: default

//新增OrderCircuitController类方法
    @GetMapping(value = "/feign/pay/ratelimit/{id}")
    @RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")
    public String myBulkhead(@PathVariable("id") Integer id)
    {

        return payFeignApi.myRatelimit(id);
    }
    public String myRatelimitFallback(Integer id,Throwable t)
    {

        return "你被限流了，禁止访问/(ㄒoㄒ)/~~";
    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142281216</p>]]></description><guid isPermaLink="false">/archives/8fa957e7-eb6c-47eb-8f31-680bd0c14488</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:51:05 GMT</pubDate></item><item><title><![CDATA[深度学习-03 Pytorch]]></title><link>https://wiki.dgfygk.com/archives/67dc61ac-463e-4be8-9b29-ddf9e27f2b35</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0-03%20Pytorch&amp;url=/archives/67dc61ac-463e-4be8-9b29-ddf9e27f2b35" width="1" height="1" alt="" style="opacity:0;">
<p>损失函数是用来衡量模型预测结果与真实值之间的差异，并用来优化模型的指标。在机器学习和神经网络中，常用的损失函数包括均方误差（Mean Squared Error，MSE）、交叉熵（Cross-Entropy）等。
 <br>
 反向传播（Backpropagation）是一种基于梯度下降算法的优化方法，用来计算模型中每个参数对于损失函数的梯度，从而更新参数以最小化损失函数。反向传播通过链式法则将损失函数的梯度传递回每个参数，从输出层向输入层反向计算梯度。具体而言，反向传播算法可以分为两个步骤：
 <br>
 前向传播（Forward Propagation）：将输入数据通过模型的参数计算出预测值，并计算出损失函数的值。
 <br>
 反向传播（Backward Propagation）：通过链式法则计算出损失函数对于每个参数的梯度，并更新参数。
 <br>
 反向传播的过程中，需要根据损失函数的类型来计算梯度。例如，对于均方误差损失函数，梯度的计算可以通过对每个参数的偏导数进行求解；对于交叉熵损失函数，梯度的计算可以通过softmax函数的导数进行求解。
 <br>
 反向传播算法的实现主要包括两个步骤：计算梯度和参数更新。在计算梯度时，通过对损失函数进行求导，得到每个参数的梯度；在参数更新时，根据梯度和学习率进行参数的更新。这个过程不断迭代，直到达到收敛条件或达到一定的迭代次数为止。</p>
<p>优化器是机器学习中一个重要的组件，用于调整模型的参数以使其最优化。在机器学习任务中，目标就是最小化或最大化一个特定的损失函数。优化器的作用就是通过调整模型的参数，使得损失函数的值最小化或最大化。
 <br>
 常见的优化器有梯度下降法（Gradient Descent）、随机梯度下降法（Stochastic Gradient Descent）、动量法（Momentum）、Nesterov Accelerated Gradient（NAG）、Adagrad、RMSprop、Adam等。</p>
<p>常见的Pytorch模型有：
 <br>
 1.线性回归模型（Linear Regression Model）：用于拟合线性关系数据的模型。
 <br>
 2.逻辑回归模型（Logistic Regression Model）：用于分类问题的模型。
 <br>
 3.多层感知机模型（Multi-Layer Perceptron Model）：由多个全连接层组成的深度神经网络模型。
 <br>
 4.卷积神经网络模型（Convolutional Neural Network Model）：用于处理图像和视觉数据的模型。
 <br>
 5.循环神经网络模型（Recurrent Neural Network Model）：用于处理序列数据的模型。
 <br>
 6.长短期记忆网络模型（Long Short-Term Memory Model）：一种循环神经网络的变种，用于处理长序列数据的模型。
 <br>
 7.生成对抗网络模型（Generative Adversarial Network Model）：由生成器和判别器组成的模型，用于生成新的数据样本。
 <br>
 8.注意力机制模型（Attention Mechanism Model）：用于处理序列数据的模型，通过对输入序列的不同部分赋予不同的注意权重来提升模型性能。
 <br>
 9.Transformer模型：基于注意力机制的模型，用于处理序列数据的模型，如自然语言处理任务中的机器翻译和文本生成等。</p>
<p><strong>模型保存</strong></p>
<pre><code class="prism language-python">vgg16=torchvision.models.vgg16(pretrained=False)

# 保存方式一,保存模型结构+模型参数
torch.save(vgg16,"vgg16_method1.pth")

# 保存方式二，保存模型参数  （推荐）
torch.save(vgg16.state_dict(),"vgg16_method2.pth")




# 保存方式一：加载模型
model=torch.load("vgg16_method1.pth")

# 保存方式二：加载模型
vgg16=torchvision.models.vgg16(pretrained=False)
vgg16.load_state_dict()
model=torch.load("vgg16_method2.pth")
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142265104</p>]]></description><guid isPermaLink="false">/archives/67dc61ac-463e-4be8-9b29-ddf9e27f2b35</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Thu, 26 Sep 2024 09:50:48 GMT</pubDate></item><item><title><![CDATA[day-55 不同路径]]></title><link>https://wiki.dgfygk.com/archives/811d4a37-d454-41f4-b559-d479f5185260</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-55%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84&amp;url=/archives/811d4a37-d454-41f4-b559-d479f5185260" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0513426b685947b3b63e368b142938de.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 动态规划：因为只能向右或向下移动，可以得出状态转换方程：dp[i][j]=dp[i-1][j]+dp[i][j-1]</p>
<p><strong>解题过程</strong>
 <br>
 直接令第一行和第一列全为1，然后通过状态转换方程进行计算，返回dp[m-1][n-1]即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int uniquePaths(int m, int n) {

        int dp[][]=new int[m][n];
        for(int i=0;i&lt;m;i++){

            dp[i][0]=1;
        }
        for(int i=0;i&lt;n;i++){

            dp[0][i]=1;
        }
        for(int i=1;i&lt;m;i++){

            for(int j=1;j&lt;n;j++)
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
        }
        return dp[m-1][n-1];
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/unique-paths/solutions/2917160/bu-tong-lu-jing-by-ashi-jian-chong-dan-l-52zc/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142264977</p>]]></description><guid isPermaLink="false">/archives/811d4a37-d454-41f4-b559-d479f5185260</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:50:28 GMT</pubDate></item><item><title><![CDATA[day-56 坐上公交的最晚时间]]></title><link>https://wiki.dgfygk.com/archives/e5f94447-3b12-4f62-b938-dcb1cee1cce6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-56%20%E5%9D%90%E4%B8%8A%E5%85%AC%E4%BA%A4%E7%9A%84%E6%9C%80%E6%99%9A%E6%97%B6%E9%97%B4&amp;url=/archives/e5f94447-3b12-4f62-b938-dcb1cee1cce6" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7233e208375545159195df082c9d0d2a.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 首先对两个数组进行排序，然后模拟上车情况，维护剩余空间space和已上车人数po-1</p>
<p><strong>解题过程</strong>
 <br>
 关键在于最后一辆车是否坐满：1如果最后一辆车坐满，则从ans=passengers[po-1]开始向下遍历，找到第一个未在passengers数组中的数即为答案；2如果最后一辆车坐满，则从ans=buses[buses.length-1]开始向下遍历，找到第一个未在passengers数组中的数即为答案</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int latestTimeCatchTheBus(int[] buses, int[] passengers, int capacity) {

        int n=buses.length;
        int m=passengers.length;
        Arrays.sort(buses);
        Arrays.sort(passengers);
        int po=0;
        int space=capacity;
        for(int i:buses){

          for(space=capacity;space&gt;0&amp;&amp;po&lt;passengers.length&amp;&amp;passengers[po]&lt;=i;space--){

                po++;
           }
        }
        int ans=0;
        if(space&gt;0){

            ans=buses[n-1];
        }else{

            ans=passengers[po-1];
            }
        po--;
        while(po&gt;=0&amp;&amp;ans==passengers[po]){

            po--;
            ans--;
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/the-latest-time-to-catch-a-bus/solutions/2923343/zuo-shang-gong-jiao-de-zui-wan-shi-jian-zsrqu/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142375283</p>]]></description><guid isPermaLink="false">/archives/e5f94447-3b12-4f62-b938-dcb1cee1cce6</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:50:20 GMT</pubDate></item><item><title><![CDATA[Redis-01 入门和十大数据类型]]></title><link>https://wiki.dgfygk.com/archives/a4f9fa51-118d-4bfc-be90-2e3b3c1455e3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis-01%20%E5%85%A5%E9%97%A8%E5%92%8C%E5%8D%81%E5%A4%A7%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B&amp;url=/archives/a4f9fa51-118d-4bfc-be90-2e3b3c1455e3" width="1" height="1" alt="" style="opacity:0;">
<p>Redis支持两种持久化方式：RDB持久化和AOF持久化。
 <br>
 1.RDB持久化是将Redis的数据以快照的形式保存在磁盘上，可以手动触发或通过配置文件设置定时触发。RDB保存的是Redis在某个时间点上的数据快照，可以通过恢复RDB文件来恢复数据。</p>
<p>2.AOF持久化是将Redis的每个写操作追加到一个日志文件中，当Redis重启时，会通过重放日志文件来恢复数据。AOF持久化有两种模式：appendfsync always、appendfsync everysec和appendfsync no。其中always模式可以保证数据的安全性，但性能较差；everysec模式在每秒fsync一次，可以提供较好的性能和一定的数据安全性；no模式下只是将数据追加到日志文件，不进行fsync操作，性能最好，但数据安全性较差。</p>
<p>原始配置文件在：/opt/redis-7.2.5
 <br>
 自定义配置文件放在：/myredis
 <br>
 密码：dc123
 <br>
 启动命令：redis-server /myredis/redis7.conf redis-cli -a dc123
 <br>
 关闭命令：shutdown</p>
<p>1.字符串 (Strings)：最基本的数据类型，可以存储任何类型的数据，包括文本、数字等。
 <br>
 2.列表 (Lists)：有序的字符串列表，可以在列表的两端进行插入和删除操作。
 <br>
 3.哈希 (Hashes)：键值对的无序集合，适合存储对象。
 <br>
 4.集合 (Sets)：无序的字符串集合，不允许重复值的存在。
 <br>
 5.有序集合 (Sorted Sets)：有序的字符串集合，每个字符串都有一个分数值，可以根据分数值进行排序。
 <br>
 6.位图 (Bitmaps)：由二进制位组成的数据结构，可以进行位运算。
 <br>
 7.布隆过滤器 (Bloom Filters)：用于判断一个元素是否在集合中的数据结构。
 <br>
 8.HyperLogLog：用于计数的概率数据结构，可以估算一个集合中的唯一元素的个数。
 <br>
 9.地理空间索引 (Geospatial Index)：用于存储和查询地理位置信息的数据结构。
 <br>
 10.流 (Streams)：可以发布、订阅和消费消息的数据结构。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F88dd998789794e37b672d4dc3704699f.png&amp;size=m" alt="">
 <br>
 <em>命令不区分大小写</em></p>
<p><strong>1.String</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3500be1780664ed7a7f6d76e4e9ddc88.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6659d068e27840bd843b2036e73f8663.png&amp;size=m" alt=""></p>
<p><strong>2.List</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa93f578a87b64c6f9eab4963438f76c4.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd6663deee9b74aaa87fa6092787e3705.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F00c775e853814aec9a9e13b9222ba4ec.png&amp;size=m" alt="">
 <br>
 <strong>3.Hash</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F92a34fca0c6d4863923d249aac53b847.png&amp;size=m" alt=""></p>
<p><strong>4.Set</strong>
 <br>
 单值多value,value不重复
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F85846bc0ae094c8e89018760e37757ac.png&amp;size=m" alt=""></p>
<p><strong>5.Zset</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7ebbc72a2ca64dedadb1a788432d0d1d.png&amp;size=m" alt=""></p>
<p><strong>6.bitmap</strong>
 <br>
 <em>值只能是0或1</em>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F92ea6f96805946ab9b36e2881035469f.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc359cafb1a25496abd0d9f1812255931.png&amp;size=m" alt=""></p>
<p><strong>7.HyperLogLog</strong>
 <br>
 <em>去重后的数量</em>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fca069e1b84a44394ad7f5341a2caad9b.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6b91be3f98d7404d9b75073faf0fcecf.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd7f3cf7205264348b6c23d8450bd106a.png&amp;size=m" alt=""></p>
<p><strong>8.GEO</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7a40e03b0c2d4eeb9408512affe02e05.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5d9bbd21c9ca49a9a346813f49ee547c.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4781574db9a043c088abab441e267722.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142288916</p>]]></description><guid isPermaLink="false">/archives/a4f9fa51-118d-4bfc-be90-2e3b3c1455e3</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:50:10 GMT</pubDate></item><item><title><![CDATA[SpringCloud-06 Micrometer Tracing 分布式链路追踪]]></title><link>https://wiki.dgfygk.com/archives/447d012c-5ee0-41ef-b889-42a87a5ead73</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-06%20Micrometer%20Tracing%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA&amp;url=/archives/447d012c-5ee0-41ef-b889-42a87a5ead73" width="1" height="1" alt="" style="opacity:0;">
<p>在微服务框架中，一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果，每一个前段请求都会形成一条复杂的分布式服务调用链路，链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。</p>
<p>在分布式与微服务场景下，我们需要解决如下问题：
 <br>
 在大规模分布式与微服务集群下，如何实时观测系统的整体调用链路情况。在大规模分布式与微服务集群下，如何快速发现并定位到问题。在大规模分布式与微服务集群下，如何尽可能精确的判断故障对系统的影响范围与影响程度。在大规模分布式与微服务集群下，如何尽可能精确的梳理出服务之间的依赖关系，并判断出服务之间的依赖关系是否合理。在大规模分布式与微服务集群下，如何尽可能精确的分析整个系统调用链路的性能与瓶颈点。在大规模分布式与微服务集群下，如何尽可能精确的分析系统的存储瓶颈与容量规划。</p>
<p>上述问题的答案：
 <br>
 分布式链路追踪技术要解决的问题，分布式链路追踪（Distributed Tracing），就是将一次分布式请求还原成调用链路，进行日志记录，性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。</p>
<p>Micrometer Tracing是基于Micrometer指标库的分布式追踪解决方案。Micrometer是一种用于应用程序监控的指标库，它允许开发者收集和暴露应用程序运行时的各种指标。Micrometer Tracing通过集成分布式追踪系统，如Jaeger或Zipkin，提供对分布式系统中请求的追踪支持。它可以用于测量请求的开始和结束时间，并将这些信息与其他指标一起报告。</p>
<p>Zipkin是一个开源的分布式追踪系统，它可以帮助开发者实现对分布式系统中请求的跟踪和记录。Zipkin提供了一个可视化的UI界面，开发者可以通过该界面查看请求的跟踪信息，包括请求的起始时间、处理时间、服务调用链等。Zipkin还提供了一组API，允许开发者将请求的跟踪信息导出到其他系统进行进一步分析。</p>
<p>Micrometer Tracing可以与Zipkin集成，通过Micrometer Tracing将请求的跟踪信息导出到Zipkin进行跟踪和分析。开发者只需要配置Micrometer Tracing，然后将Zipkin作为信息的目标，就可以将跟踪信息发送到Zipkin服务器。然后，开发者可以使用Zipkin UI界面查看请求的跟踪信息，以及分析和优化应用程序性能。</p>
<p>Zipkin是一种分布式链路跟踪系统图形化的工具，Zipkin 是 Twitter 开源的分布式跟踪系统，能够收集微服务运行过程中的实时调用链路信息，并能够将这些调用链路信息展示到Web图形化界面上供开发人员分析，开发人员能够从ZipKin中分析出调用链路中的性能瓶颈，识别出存在问题的应用程序，进而定位问题和解决问题。</p>
<p>在文件目录下打开cmd窗口，执行 java -jar zipkin-server-3.4.1-exec.jar 命令</p>
<p>网址：http://localhost:9411/
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff0a0498f1d204216b6cbba311b03b05e.png&amp;size=m" alt=""></p>
<pre><code class="prism language-java">//父工程添加依赖
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;groupId&gt;com.dc.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;cloud2024&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;packaging&gt;pom&lt;/packaging&gt;
    &lt;modules&gt;
        &lt;module&gt;mybatis_generator2024&lt;/module&gt;
        &lt;module&gt;cloud-provider-payment8001&lt;/module&gt;
        &lt;module&gt;cloud-consumer-order80&lt;/module&gt;
        &lt;module&gt;cloud-api-commons&lt;/module&gt;
        &lt;module&gt;cloud_provider-payment8002&lt;/module&gt;
        &lt;module&gt;cloud-consumer-feign-order80&lt;/module&gt;
    &lt;/modules&gt;

    &lt;properties&gt;
        &lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
        &lt;hutool.version&gt;5.8.22&lt;/hutool.version&gt;
        &lt;lombok.version&gt;1.18.26&lt;/lombok.version&gt;
        &lt;druid.version&gt;1.1.20&lt;/druid.version&gt;
        &lt;mybatis.springboot.version&gt;3.0.2&lt;/mybatis.springboot.version&gt;
        &lt;mysql.version&gt;8.0.11&lt;/mysql.version&gt;
        &lt;swagger3.version&gt;2.2.0&lt;/swagger3.version&gt;
        &lt;mapper.version&gt;4.2.3&lt;/mapper.version&gt;
        &lt;fastjson2.version&gt;2.0.41&lt;/fastjson2.version&gt;
        &lt;persistence-api.version&gt;1.0.2&lt;/persistence-api.version&gt;
        &lt;spring.boot.test.version&gt;3.1.5&lt;/spring.boot.test.version&gt;
        &lt;spring.boot.version&gt;3.2.0&lt;/spring.boot.version&gt;
        &lt;spring.cloud.version&gt;2023.0.0&lt;/spring.cloud.version&gt;
        &lt;spring.cloud.alibaba.version&gt;2022.0.0.0-RC2&lt;/spring.cloud.alibaba.version&gt;
        &lt;micrometer-tracing.version&gt;1.2.0&lt;/micrometer-tracing.version&gt;
        &lt;micrometer-observation.version&gt;1.12.0&lt;/micrometer-observation.version&gt;
        &lt;feign-micrometer.version&gt;12.5&lt;/feign-micrometer.version&gt;
        &lt;zipkin-reporter-brave.version&gt;2.17.0&lt;/zipkin-reporter-brave.version&gt;
    &lt;/properties&gt;

    &lt;dependencyManagement&gt;
        &lt;dependencies&gt;
            &lt;!--springboot 3.2.0--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
                &lt;version&gt;${
   spring.boot.version}&lt;/version&gt;
                &lt;type&gt;pom&lt;/type&gt;
                &lt;scope&gt;import&lt;/scope&gt;
            &lt;/dependency&gt;
            &lt;!--springcloud 2023.0.0--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
                &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;
                &lt;version&gt;${
   spring.cloud.version}&lt;/version&gt;
                &lt;type&gt;pom&lt;/type&gt;
                &lt;scope&gt;import&lt;/scope&gt;
            &lt;/dependency&gt;
            &lt;!--springcloud alibaba 2022.0.0.0-RC2--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
                &lt;artifactId&gt;spring-cloud-alibaba-dependencies&lt;/artifactId&gt;
                &lt;version&gt;${
   spring.cloud.alibaba.version}&lt;/version&gt;
                &lt;type&gt;pom&lt;/type&gt;
                &lt;scope&gt;import&lt;/scope&gt;
            &lt;/dependency&gt;
            &lt;!--SpringBoot集成mybatis--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
                &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
                &lt;version&gt;${
   mybatis.springboot.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--Mysql数据库驱动8 --&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;mysql&lt;/groupId&gt;
                &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
                &lt;version&gt;${
   mysql.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--SpringBoot集成druid连接池--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
                &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;
                &lt;version&gt;${
   druid.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--通用Mapper4之tk.mybatis--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;tk.mybatis&lt;/groupId&gt;
                &lt;artifactId&gt;mapper&lt;/artifactId&gt;
                &lt;version&gt;${
   mapper.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--persistence--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;javax.persistence&lt;/groupId&gt;
                &lt;artifactId&gt;persistence-api&lt;/artifactId&gt;
                &lt;version&gt;${
   persistence-api.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!-- fastjson2 --&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;com.alibaba.fastjson2&lt;/groupId&gt;
                &lt;artifactId&gt;fastjson2&lt;/artifactId&gt;
                &lt;version&gt;2.0.40&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;org.springdoc&lt;/groupId&gt;
                &lt;artifactId&gt;springdoc-openapi-starter-webmvc-ui&lt;/artifactId&gt;
                &lt;version&gt;${
   swagger3.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--hutool--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;cn.hutool&lt;/groupId&gt;
                &lt;artifactId&gt;hutool-all&lt;/artifactId&gt;
                &lt;version&gt;${
   hutool.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--lombok--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
                &lt;artifactId&gt;lombok&lt;/artifactId&gt;
                &lt;version&gt;${
   lombok.version}&lt;/version&gt;
                &lt;optional&gt;true&lt;/optional&gt;
            &lt;/dependency&gt;
            &lt;!-- spring-boot-starter-test --&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
                &lt;version&gt;${
   spring.boot.test.version}&lt;/version&gt;
                &lt;scope&gt;test&lt;/scope&gt;
            &lt;/dependency&gt;
            &lt;!--micrometer-tracing-bom导入链路追踪版本中心  1--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
                &lt;artifactId&gt;micrometer-tracing-bom&lt;/artifactId&gt;
                &lt;version&gt;${
   micrometer-tracing.version}&lt;/version&gt;
                &lt;type&gt;pom&lt;/type&gt;
                &lt;scope&gt;import&lt;/scope&gt;
            &lt;/dependency&gt;
            &lt;!--micrometer-tracing指标追踪  2--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
                &lt;artifactId&gt;micrometer-tracing&lt;/artifactId&gt;
                &lt;version&gt;${
   micrometer-tracing.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--micrometer-tracing-bridge-brave适配zipkin的桥接包 3--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
                &lt;artifactId&gt;micrometer-tracing-bridge-brave&lt;/artifactId&gt;
                &lt;version&gt;${
   micrometer-tracing.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--micrometer-observation 4--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
                &lt;artifactId&gt;micrometer-observation&lt;/artifactId&gt;
                &lt;version&gt;${
   micrometer-observation.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--feign-micrometer 5--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;io.github.openfeign&lt;/groupId&gt;
                &lt;artifactId&gt;feign-micrometer&lt;/artifactId&gt;
                &lt;version&gt;${
   feign-micrometer.version}&lt;/version&gt;
            &lt;/dependency&gt;
            &lt;!--zipkin-reporter-brave 6--&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;io.zipkin.reporter2&lt;/groupId&gt;
                &lt;artifactId&gt;zipkin-reporter-brave&lt;/artifactId&gt;
                &lt;version&gt;${
   zipkin-reporter-brave.version}&lt;/version&gt;
            &lt;/dependency&gt;

        &lt;/dependencies&gt;
    &lt;/dependencyManagement&gt;
&lt;/project&gt;


//8001端口添加依赖
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;parent&gt;
        &lt;groupId&gt;com.dc.cloud&lt;/groupId&gt;
        &lt;artifactId&gt;cloud2024&lt;/artifactId&gt;
        &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;/parent&gt;

    &lt;artifactId&gt;cloud-provider-payment8001&lt;/artifactId&gt;

    &lt;properties&gt;
        &lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;/properties&gt;


    &lt;dependencies&gt;

        &lt;!--micrometer-tracing指标追踪  1--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
            &lt;artifactId&gt;micrometer-tracing&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
            &lt;artifactId&gt;micrometer-tracing-bridge-brave&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--micrometer-observation 3--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
            &lt;artifactId&gt;micrometer-observation&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--feign-micrometer 4--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;io.github.openfeign&lt;/groupId&gt;
            &lt;artifactId&gt;feign-micrometer&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--zipkin-reporter-brave 5--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;io.zipkin.reporter2&lt;/groupId&gt;
            &lt;artifactId&gt;zipkin-reporter-brave&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--SpringCloud consul config--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-consul-config&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-bootstrap&lt;/artifactId&gt;
        &lt;/dependency&gt;


        &lt;!--SpringCloud consul discovery --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-consul-discovery&lt;/artifactId&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;commons-logging&lt;/groupId&gt;
                    &lt;artifactId&gt;commons-logging&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
        &lt;/dependency&gt;

        &lt;!-- 引入自己定义的api通用包 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.dc.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;cloud-api-commons&lt;/artifactId&gt;
            &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;!--SpringBoot通用依赖模块--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--SpringBoot集成druid连接池--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
            &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springdoc&lt;/groupId&gt;
            &lt;artifactId&gt;springdoc-openapi-starter-webmvc-ui&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--mybatis和springboot整合--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
            &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--Mysql数据库驱动8 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--persistence--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.persistence&lt;/groupId&gt;
            &lt;artifactId&gt;persistence-api&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--通用Mapper4--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;tk.mybatis&lt;/groupId&gt;
            &lt;artifactId&gt;mapper&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--hutool--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;cn.hutool&lt;/groupId&gt;
            &lt;artifactId&gt;hutool-all&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!-- fastjson2 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba.fastjson2&lt;/groupId&gt;
            &lt;artifactId&gt;fastjson2&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--lombok--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
            &lt;version&gt;1.18.28&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;!--test--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
                &lt;version&gt;3.3.0&lt;/version&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;

&lt;/project&gt;


//8001端口添加yml配置
# ========================zipkin===================
management:
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans
  tracing:
    sampling:
      probability: 1.0 #采样率默认为0.1(0.1就是10次只能有一次被记录下来)，值越大收集越及时。


//8001新建业务类PayMicrometerController
@RestController
public class PayMicrometerController
{

    /**
     * Micrometer(Sleuth)进行链路监控的例子
     * @param id
     * @return
     */
    @GetMapping(value = "/pay/micrometer/{id}")
    public String myMicrometer(@PathVariable("id") Integer id)
    {

        return "Hello, 欢迎到来myMicrometer inputId:  "+id+" \t    服务返回:" + IdUtil.simpleUUID();
    }
}

//通用接口新增
    /**
     * Micrometer(Sleuth)进行链路监控的例子
     * @param id
     * @return
     */
    @GetMapping(value = "/pay/micrometer/{id}")
    public String myMicrometer(@PathVariable("id") Integer id);


//80端口新增依赖
    &lt;!--micrometer-tracing指标追踪  1--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
        &lt;artifactId&gt;micrometer-tracing&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
        &lt;artifactId&gt;micrometer-tracing-bridge-brave&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--micrometer-observation 3--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
        &lt;artifactId&gt;micrometer-observation&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--feign-micrometer 4--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.github.openfeign&lt;/groupId&gt;
        &lt;artifactId&gt;feign-micrometer&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--zipkin-reporter-brave 5--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.zipkin.reporter2&lt;/groupId&gt;
        &lt;artifactId&gt;zipkin-reporter-brave&lt;/artifactId&gt;
    &lt;/dependency&gt;

//80端口yml配置
# zipkin图形展现地址和采样率设置
management:
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans
  tracing:
    sampling:
      probability: 1.0 #采样率默认为0.1(0.1就是10次只能有一次被记录下来)，值越大收集越及时。

//80端口新建业务类
@RestController
@Slf4j
public class OrderMicrometerController
{

    @Resource
    private PayFeignApi payFeignApi;

    @GetMapping(value = "/feign/micrometer/{id}")
    public String myMicrometer(@PathVariable("id") Integer id)
    {

        return payFeignApi.myMicrometer(id);
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F044704f744534cb195b70209e1a8fdfc.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffb7983cf30304fdaaaa5bdc7627718c2.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142315448</p>]]></description><guid isPermaLink="false">/archives/447d012c-5ee0-41ef-b889-42a87a5ead73</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:49:46 GMT</pubDate></item><item><title><![CDATA[SpringCloud问题解决方案]]></title><link>https://wiki.dgfygk.com/archives/b8da6937-ca8f-4913-a1cb-c0189ff82414</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&amp;url=/archives/b8da6937-ca8f-4913-a1cb-c0189ff82414" width="1" height="1" alt="" style="opacity:0;">
<p>1.电脑更新后consul的持久化失效？
 <br>
 解决方法：重新以管理员身份运行consul_start.bat文件</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142252646</p>]]></description><guid isPermaLink="false">/archives/b8da6937-ca8f-4913-a1cb-c0189ff82414</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:49:30 GMT</pubDate></item><item><title><![CDATA[day-51 合并零之间的节点]]></title><link>https://wiki.dgfygk.com/archives/a4a2c258-c08d-42c3-b68e-83d7cba50130</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-51%20%E5%90%88%E5%B9%B6%E9%9B%B6%E4%B9%8B%E9%97%B4%E7%9A%84%E8%8A%82%E7%82%B9&amp;url=/archives/a4a2c258-c08d-42c3-b68e-83d7cba50130" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fca07a501e1fb4ad58bc311e72905f233.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 直接遍历链表即可，遇到val=0跳过，val非零则加在一起，最后返回即可</p>
<p><strong>解题过程</strong>
 <br>
 返回链表可以有头结点，方便插入，返回head.next</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public int sum=0;
    public ListNode ans=new ListNode();
    public ListNode tans=ans;
    public ListNode mergeNodes(ListNode head) {

        traversal(head.next);
        return ans.next;
    }
    public void traversal(ListNode p){

        if(p==null) return;
        if(p.val==0){

            ListNode t=new ListNode(sum);
            tans.next=t;
            tans=tans.next;
            sum=0;
            traversal(p.next);
        }else{

            sum+=p.val;
            traversal(p.next);
        }

    }
}

作者：菜卷
链接：https://leetcode.cn/problems/merge-nodes-in-between-zeros/solutions/2910089/he-bing-ling-zhi-jian-de-jie-dian-by-ash-gb1j/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142052758</p>]]></description><guid isPermaLink="false">/archives/a4a2c258-c08d-42c3-b68e-83d7cba50130</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:49:13 GMT</pubDate></item><item><title><![CDATA[day-54 求出最多标记下标]]></title><link>https://wiki.dgfygk.com/archives/d0cc3b22-d931-4808-9f6d-80eae14d57e3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-54%20%E6%B1%82%E5%87%BA%E6%9C%80%E5%A4%9A%E6%A0%87%E8%AE%B0%E4%B8%8B%E6%A0%87&amp;url=/archives/d0cc3b22-d931-4808-9f6d-80eae14d57e3" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1a32514974214209bd6ba20a35dbc35b.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 假设nums的长度为len，则返回数最大最大为（len/2）*2,所以可以将数组分为两部分，[0~(len-1)/2]为一部分，[(len-1）/2~len-]为第二部分</p>
<p><strong>解题过程</strong>
 <br>
 指针right从第二部分从右向左开始遍历，指针left从第一部分从右向左开始遍历，如果满足nums[left]*2&lt;=nums[right]则返回值加2,并且两个指针都要迁移，如果不满足，则left指针前移</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int maxNumOfMarkedIndices(int[] nums) {

        Arrays.sort(nums);
        int len=nums.length;
        int ans=0;
        int left=(len-1)/2;
        int right=len-1;
        while(left&gt;=0&amp;&amp;right&gt;(len-1)/2){

            if(nums[left]*2&lt;=nums[right]){

                ans+=2;
                left--;
                right--;

            }else left--;
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices/solutions/2914686/qiu-chu-zui-duo-biao-ji-xia-biao-by-ashi-kiye/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142183658</p>]]></description><guid isPermaLink="false">/archives/d0cc3b22-d931-4808-9f6d-80eae14d57e3</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:48:59 GMT</pubDate></item><item><title><![CDATA[SpringCloud-04 OpenFeign服务调用与负载均衡]]></title><link>https://wiki.dgfygk.com/archives/5ddb8446-e4c8-4dd5-8638-89ad82632173</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-04%20OpenFeign%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E4%B8%8E%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1&amp;url=/archives/5ddb8446-e4c8-4dd5-8638-89ad82632173" width="1" height="1" alt="" style="opacity:0;">
<p>OpenFeign是一个声明式、模板化的HTTP客户端，它简化了在Java应用程序中调用RESTful API的过程。OpenFeign是Netflix开发的一个开源项目，它构建在Feign的基础上，为开发者提供了更加简单、灵活的方式来实现HTTP请求。OpenFeign的特点包括：</p>
<p>前面在使用SpringCloud LoadBalancer+RestTemplate时，利用RestTemplate对http请求的封装处理形成了一套模版化的调用方法。但是在实际开发中，由于对服务依赖的调用可能不止一处，往往一个接口会被多处调用，所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以，OpenFeign在此基础上做了进一步封装，由他来帮助我们定义和实现依赖服务接口的定义。在OpenFeign的实现下，我们只需创建一个接口并使用注解的方式来配置它(在一个微服务接口上面标注一个@FeignClient注解即可)，即可完成对服务提供方的接口绑定，统一对外暴露可以被调用的接口方法，大大简化和降低了调用客户端的开发量，也即由服务提供者给出调用接口清单，消费者直接通过OpenFeign调用即可。</p>
<p>OpenFeign同时还<em>集成SpringCloud LoadBalancer</em>，可以在使用OpenFeign时提供Http客户端的负载均衡，也可以集成阿里巴巴Sentinel来提供熔断、降级等功能。而与SpringCloud LoadBalancer不同的是，通过OpenFeign只需要定义服务绑定接口且以声明式的方法，优雅而简单的实现了服务调用。</p>
<p>在一个接口上添加@FeugnClient注解即可</p>
<p><strong>OpenFeign使用：</strong></p>
<p><strong>1.新建一个Module，添加pom依赖</strong></p>
<pre><code class="prism language-java">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;parent&gt;
        &lt;groupId&gt;com.dc.cloud&lt;/groupId&gt;
        &lt;artifactId&gt;cloud2024&lt;/artifactId&gt;
        &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;/parent&gt;

    &lt;artifactId&gt;cloud-consumer-feign-order80&lt;/artifactId&gt;

    &lt;properties&gt;
        &lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;/properties&gt;

    &lt;dependencies&gt;
        &lt;!--openfeign--&gt;
&lt;!--        &lt;dependency&gt;--&gt;
&lt;!--            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;--&gt;
&lt;!--            &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;--&gt;
&lt;!--            &lt;version&gt;&lt;/version&gt;--&gt;
&lt;!--        &lt;/dependency&gt;--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;
            &lt;version&gt;4.1.3&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!--SpringCloud consul discovery--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-consul-discovery&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!-- 引入自己定义的api通用包 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.dc.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;cloud-api-commons&lt;/artifactId&gt;
            &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!--web + actuator--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--lombok--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
            &lt;optional&gt;true&lt;/optional&gt;
        &lt;/dependency&gt;
        &lt;!--hutool-all--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;cn.hutool&lt;/groupId&gt;
            &lt;artifactId&gt;hutool-all&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--fastjson2--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba.fastjson2&lt;/groupId&gt;
            &lt;artifactId&gt;fastjson2&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springdoc&lt;/groupId&gt;
            &lt;artifactId&gt;springdoc-openapi-starter-webmvc-ui&lt;/artifactId&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
&lt;/project&gt;
</code></pre>
<p><strong>2.修改yml配置文件</strong></p>
<pre><code class="prism language-java">server:
  port: 80

spring:
  application:
    name: cloud-consumer-openfeign-order
  ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${
   spring.application.name}
</code></pre>
<p><strong>3.主启动类</strong></p>
<pre><code class="prism language-java">@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul为注册中心时注册服务
@EnableFeignClients//启用feign客户端,定义服务+绑定接口，以声明式的方法优雅而简单的实现服务调用
public class MainOpenFeign80 {

    public static void main(String[] args) {

        SpringApplication.run(MainOpenFeign80.class,args);
    }
}
</code></pre>
<p><strong>4.修改cloud-api-commons通用模块</strong></p>
<pre><code class="prism language-java">//在通用模块端口也需添加pom依赖
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;
            &lt;version&gt;4.1.3&lt;/version&gt;
        &lt;/dependency&gt;
//新建接口
@FeignClient("cloud-payment-service")
public interface PayFeignApi {

    @PostMapping(value="/pay/add")
    public ResultData addPay(@RequestBody payDTO paydto);

    @GetMapping(value="/pay/get/{id}")
    public ResultData getPayInfo(@PathVariable("id") Integer id);

    @GetMapping(value="/pay/get/info")
    public String mylb();

}
//在cloud-consumer-feign-order80模块新增controller
@RestController
public class OrderController {

   @Resource
    private PayFeignApi payFeignApi;

   @PostMapping(value="/feign/pay/add")
   public ResultData addOrder(@RequestBody payDTO payDTO){

       System.out.println("新增订单操作");
       ResultData resultData=payFeignApi.addPay(payDTO);
       return resultData;
   }

   @GetMapping(value="/feign/pay/get/{id}")
    public ResultData getPayInfo(@PathVariable("id")Integer id){

       System.out.println("按照id查看订单支付流水");
       ResultData resultData=payFeignApi.getPayInfo(id);
       return resultData;
   }

   @GetMapping(value="/feign/pay/mylb")
    public String mylb(){

       return payFeignApi.mylb();
   }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd246f6e07583436a848e8d54dc232d06.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0c38cd5482a4483dad613fac0c27873f.png&amp;size=m" alt="">
 <br>
 <strong>OpenFeign高级特性</strong></p>
<p><strong>1.超时控制</strong>
 <br>
 在使用OpenFeign进行服务调用时，可以设置超时控制来限制请求的最大执行时间。OpenFeign提供了两种设置超时的方式：全局超时设置和针对特定请求的超时设置。
 <br>
 默认等待60秒，超过60秒拿不到查询结果就会报错
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb2034520ef2f404cb7e656a047fa8113.png&amp;size=m" alt=""></p>
<pre><code class="prism language-java">//全局设置
server:
  port: 80

spring:
  application:
    name: cloud-consumer-openfeign-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${
   spring.application.name}
    openfeign:
      client:
        config:
          default:
            #连接超时时间
            connectTimeout: 3000
            #读取超时时间
            readTimeout: 3000


//局部配置会覆盖全局配置
server:
  port: 80

spring:
  application:
    name: cloud-consumer-openfeign-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${
   spring.application.name}
    openfeign:
      client:
        config:
          default:
            #连接超时时间
            connectTimeout: 3000          #3秒
            #读取超时时间
            readTimeout: 3000
          cloud-payment-service:
            #连接超时时间
            connectTimeout: 10000
            #读取超时时间
            readTimeout: 10000
</code></pre>
<p><strong>2.重试机制</strong>
 <br>
 重试机制默认关闭（只会在规定时间调用一次）</p>
<pre><code class="prism language-java">//在80端口添加配置类OpenFeignConfig类
@Configuration
public class OpenFeignConfig {

    @Bean
    public Retryer myRetryer() {

//        return Retryer.NEVER_RETRY;//默认：不会重试
        return new Retryer.Default(100,1,4);//初始间隔时间   重试间隔时间1s  最大重试次数   
    }
}
</code></pre>
<p><strong>3.OpenFeign中的http client</strong>
 <br>
 如果不做特殊配置，OpenFeign默认使用JDK自带的HttpURLConnection发送HTTP请求，由于默认HttpURLConnection没有连接池、性能和效率比较低，如果采用默认，性能上不是最好的。</p>
<pre><code class="prism language-java">//在OPenFeign80端口添加pom依赖
            &lt;groupId&gt;org.apache.httpcomponents.client5&lt;/groupId&gt;
            &lt;artifactId&gt;httpclient5&lt;/artifactId&gt;
            &lt;version&gt;5.3&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- feign-hc5--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;io.github.openfeign&lt;/groupId&gt;
            &lt;artifactId&gt;feign-hc5&lt;/artifactId&gt;
            &lt;version&gt;13.1&lt;/version&gt;
        &lt;/dependency&gt;

//修改yml文件
      httpclient:
        hc5:
          enabled: true
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb5cd4940dbaa4708bce5cf9665b2a4e6.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fbd693482d78b42d39a35748dc3858fe3.png&amp;size=m" alt="">
 <br>
 <strong>4.请求/响应压缩功能</strong>
 <br>
 Spring Cloud OpenFeign支持对请求和响应进行GZIP压缩，以减少通信过程中的性能损耗。</p>
<pre><code class="prism language-java">//yml文件修改
server:
  port: 80

spring:
  application:
    name: cloud-consumer-openfeign-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${
   spring.application.name}
    openfeign:
      client:
        config:
          default:
            #连接超时时间
            connectTimeout: 3000          #3秒
            #读取超时时间
            readTimeout: 3000
          cloud-payment-service:
            #连接超时时间
            connectTimeout: 10000
            #读取超时时间
            readTimeout: 10000
      httpclient:
        hc5:
          enabled: true
      compression:
        request:
          enabled: true
          min-request-size: 2048 #最小触发压缩的大小
          mime-types: text/xml,application/xml,application/json #触发压缩数据类型
        response:
          enabled: true
</code></pre>
<p><strong>5.日志打印功能</strong>
 <br>
 Feign 提供了日志打印功能，我们可以通过配置来调整日志级别，从而了解 Feign 中 Http 请求的细节，就是对Feign接口的调用情况进行监控和输出。</p>
<p>NONE：默认的，不显示任何日志；
 <br>
 BASIC：仅记录请求方法、URL、响应状态码及执行时间；
 <br>
 HEADERS：除了 BASIC 中定义的信息之外，还有请求和响应的头信息；
 <br>
 FULL：除了 HEADERS 中定义的信息之外，还有请求和响应的正文及元数据。</p>
<pre><code class="prism language-java">//修改OpenFeignConfig类
@Configuration
public class OpenFeignConfig {

    @Bean
    public Retryer myRetryer() {

        return Retryer.NEVER_RETRY;//默认：不会重试
//        return new Retryer.Default(100,1,4);//初始间隔时间   重试间隔时间1s  最大重试次数
    }
    @Bean
    Logger.Level feignLoggerLevel() {

        return Logger.Level.FULL;
    }
}

//yml文件修改
server:
  port: 80

spring:
  application:
    name: cloud-consumer-openfeign-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${
   spring.application.name}
    openfeign:
      client:
        config:
          default:
            #连接超时时间
            connectTimeout: 3000          #3秒
            #读取超时时间
            readTimeout: 3000
          cloud-payment-service:
            #连接超时时间
            connectTimeout: 10000
            #读取超时时间
            readTimeout: 10000
      httpclient:
        hc5:
          enabled: true
      compression:
        request:
          enabled: true
          min-request-size: 2048 #最小触发压缩的大小
          mime-types: text/xml,application/xml,application/json #触发压缩数据类型
        response:
          enabled: true
# feign日志以什么级别监控哪个接口
logging:
  level:
    com:
      dc:
        cloud:
          apis:
            PayFeignApi: debug
</code></pre>
<p>压缩
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F172b1073ee664df980e2be91c872d806.png&amp;size=m" alt=""></p>
<p>三次重试
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2cefdacb411b48cd9399f85f27e05e8b.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9cecc2295ca94142b9b7b8881373aed8.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6c105416183340a3a3f2eee429b738f5.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142150476</p>]]></description><guid isPermaLink="false">/archives/5ddb8446-e4c8-4dd5-8638-89ad82632173</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:48:49 GMT</pubDate></item><item><title><![CDATA[深度学习-02 Pytorch]]></title><link>https://wiki.dgfygk.com/archives/513e83bc-377e-45e6-b4cb-9b88550ae1eb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0-02%20Pytorch&amp;url=/archives/513e83bc-377e-45e6-b4cb-9b88550ae1eb" width="1" height="1" alt="" style="opacity:0;">
<p>torchvision是一个用于计算机视觉任务的Python包，它是PyTorch的一个扩展库。它提供了一些流行的数据集、模型架构和图像转换函数，以方便用户进行计算机视觉任务的开发和研究。
 <br>
 1.torchvision中包含了许多常用的计算机视觉数据集，如MNIST、CIFAR10、CIFAR100和ImageNet等。用户可以通过简单的接口加载这些数据集，并进行数据预处理、数据增强和数据加载等操作。</p>
<p>2.此外，torchvision还提供了一些常用的计算机视觉模型架构，如AlexNet、VGG、ResNet和DenseNet等。用户可以使用这些预训练的模型，或者根据自己的需求进行微调。</p>
<p>3.在图像转换方面，torchvision提供了一系列的图像预处理和数据增强函数，如裁剪、缩放、翻转、旋转、亮度调整和颜色增强等。这些函数可以用来对图像进行预处理，以提高模型的训练效果。</p>
<pre><code class="prism language-python">import torchvision
from torch.utils.tensorboard import SummaryWriter

dataset_transform=torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()
])


# train_set=torchvision.datasets.CIFAR10("./dataset",train=True,download=True)
# test_set=torchvision.datasets.CIFAR10("./dataset",train=False,download=True)

# print(test_set[0])
# print(test_set.classes)
#
# img,target=test_set[0]
# print(img)
# print(target)
# print(test_set.classes(target))
# img.show()

# 把所有图片转换为tensor类型
train_set=torchvision.datasets.CIFAR10("./dataset",train=True,transform=dataset_transform,download=True)
test_set=torchvision.datasets.CIFAR10("./dataset",train=False,transform=dataset_transform,download=True)
print(test_set[0])

writer=SummaryWriter("p10")
for i in range(10):
    img,target=test_set[i]
    writer.add_image("test_set",img,i)

writer.close()
</code></pre>
<p>DataLoader是PyTorch库中的一个实用工具，用于加载和预处理数据，以供训练和测试深度学习模型使用。它提供了许多功能，使数据加载变得更加高效和灵活。DataLoader的主要功能包括：
 <br>
 1.数据加载和预处理：DataLoader能够自动加载数据，并在需要时进行预处理。它可以从本地磁盘、网络或其他数据源中加载数据，并将其转换为PyTorch张量格式。它还支持数据增强操作，例如随机裁剪、翻转和旋转，以增加训练数据的多样性。</p>
<p>2.批处理和并行处理：DataLoader能够将数据分成小批量进行处理，以加快训练过程。它还支持使用多个CPU核心或GPU对数据进行并行处理，以提高训练的效率。</p>
<p>3.数据打乱和重复：DataLoader能够自动打乱数据的顺序，并可以设置数据的重复次数，以增加训练的随机性和多样性。</p>
<p>4.数据加载器的配置和使用：DataLoader提供了丰富的配置选项，可以根据具体需求进行调整。它还提供了简单易用的API，使得数据加载和使用变得更加方便。</p>
<p>卷积层（Convolutional Layer）是神经网络中的核心层级，它能够对输入数据进行特征提取。它通过使用一系列的卷积核（也称为过滤器）对输入数据进行卷积操作，从而生成一系列的特征图。卷积操作实际上是通过卷积核与输入数据的局部区域进行点乘运算，并将结果求和得到一个特征元素。通过在不同的位置应用不同的卷积核，卷积层能够提取出不同的特征，例如边缘、纹理等。卷积层的输出通常会经过非线性激活函数（如ReLU）进行激活。</p>
<p>池化层（Pooling Layer）是用于降低卷积层输出数据的空间维度的层级。它通过对输入数据的局部区域进行池化运算，生成一个池化特征。池化操作通常有两种方式：最大池化和平均池化。最大池化选取局部区域中的最大值作为池化特征，而平均池化则是取局部区域的平均值。池化层的作用是减小特征图的尺寸，同时保留重要的特征。这样可以减少网络的参数数量，加快计算速度，并且能够提高模型的鲁棒性。</p>
<p>卷积层和池化层通常会交替使用，构成深度卷积神经网络。通过多层的特征提取，网络能够逐渐学习到更加抽象和高级的特征，从而提高模型的性能。</p>
<p>在深度学习中，非线性是指输入和输出之间存在复杂的关系，无法用简单的线性方程来描述。这种非线性关系常常出现在图像、语音和自然语言处理等领域中，因为这些领域的数据具有高度的复杂性和多样性。
 <br>
 PyTorch提供了许多非线性函数，如激活函数和损失函数，以帮助我们建立非线性的神经网络模型。</p>
<p>在PyTorch中，可以使用nn.BatchNorm2d和nn.Dropout等层来实现正则化。可以帮助提高神经网络的泛化能力，并减少过拟合的风险。根据具体的网络结构和任务需求，可以选择合适的正则化层来进行模型训练。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142058547</p>]]></description><guid isPermaLink="false">/archives/513e83bc-377e-45e6-b4cb-9b88550ae1eb</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:48:31 GMT</pubDate></item><item><title><![CDATA[day-52 三数之和]]></title><link>https://wiki.dgfygk.com/archives/1a9301f8-5b64-4adc-9c2e-c2812b9369ee</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-52%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C&amp;url=/archives/1a9301f8-5b64-4adc-9c2e-c2812b9369ee" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe7bddb3ed8a44bbaaa018635bbbe679f.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 双指针：确定最小的那个数（i0&lt;=i<nums.length-2），剩下两个数可以利用双指针的思想。当最小的那个数大于零时，可以直接返回，因为后面的不可能还有满足题意的组合。< p> </nums.length-2），剩下两个数可以利用双指针的思想。当最小的那个数大于零时，可以直接返回，因为后面的不可能还有满足题意的组合。<></p>
<p><strong>解题过程</strong>
 <br>
 为了所有满足题意的组合不重合，每当选定一个元素时，若该元素与排在他之前相邻的那个元素相同，需要直接跳过</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;Integer&gt;&gt; threeSum(int[] nums) {

        Arrays.sort(nums);
       List&lt;List&lt;Integer&gt;&gt; list=new ArrayList&lt;&gt;();
       int len=nums.length;
       for(int i=0;i&lt;len-2;i++){

           if(nums[i]&gt;0) break;
           if(i&gt;0&amp;&amp;nums[i]==nums[i-1]) continue;
           int j=i+1;
           int k=len-1;
           while(j&lt;k){

               if(nums[i]+nums[j]+nums[k]==0){

                   List&lt;Integer&gt; p=new ArrayList&lt;&gt;();
                   p.add(nums[i]);
                   p.add(nums[j]);
                   p.add(nums[k]);
                   list.add(p);
                   j++;
                   while(j&lt;len&amp;&amp;nums[j]==nums[j-1]) j++;
               }else if(nums[i]+nums[j]+nums[k]&lt;0){

                   j++;
                   while(j&lt;len&amp;&amp;nums[j]==nums[j-1]) j++;
               }else{

                   k--;
                   while(k&gt;0&amp;&amp;nums[k]==nums[k+1]) k--;
               }
           }
       }
       return list;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/3sum/solutions/2912217/san-shu-zhi-he-by-ashi-jian-chong-dan-li-oecj/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142108878</p>]]></description><guid isPermaLink="false">/archives/1a9301f8-5b64-4adc-9c2e-c2812b9369ee</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:48:23 GMT</pubDate></item><item><title><![CDATA[day-52 下一个排列]]></title><link>https://wiki.dgfygk.com/archives/d27bba75-ec86-4172-bd45-db0da825851c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-52%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%8E%92%E5%88%97&amp;url=/archives/d27bba75-ec86-4172-bd45-db0da825851c" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6b8f900bba194b6c82b5c62f75043260.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 从后向前遍历数组，把遍历过的元素加入一个有序链表，没变里一个元素判断链表中是否有元素大于当前遍历元素，如果有，把链表中大于当前遍历元素的元素集合中最小的那一个元素赋给当前元素，然后将链表中剩余元素依次赋值给当前遍历元素之后的元素</p>
<p><strong>解题过程</strong>
 <br>
 如果输入数组已经是能组成最大的数，则数组升序排列再返回</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public void nextPermutation(int[] nums) {

        int len=nums.length;
        List&lt;Integer&gt; list=new ArrayList&lt;&gt;();
        boolean flag=true;
        for(int i=len-1;i&gt;=0;i--){

            list.add(nums[i]);
            Collections.sort(list);
            if(list.size()&gt;0&amp;&amp;list.get(list.size()-1)&gt;nums[i]){

                int id=list.size()-1;
                while(id&gt;=0&amp;&amp;list.get(id)&gt;nums[i]){

                    id--;
                }
                nums[i]=list.get(id+1);
                list.remove(id+1);
                for(int j=i+1;j&lt;len;j++){

                    nums[j]=list.get(0);
                    list.remove(0);
                }
                flag=false;
           }
           if(!flag) break;
        }
        if(flag)Arrays.sort(nums);
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/next-permutation/solutions/2912238/xia-yi-ge-pai-lie-by-ashi-jian-chong-dan-4ewp/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142109347</p>]]></description><guid isPermaLink="false">/archives/d27bba75-ec86-4172-bd45-db0da825851c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:48:15 GMT</pubDate></item><item><title><![CDATA[day-52 字母异位词分组]]></title><link>https://wiki.dgfygk.com/archives/1b4b0bbf-71d8-4697-93b2-638c01ed189a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-52%20%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D%E5%88%86%E7%BB%84&amp;url=/archives/1b4b0bbf-71d8-4697-93b2-638c01ed189a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F26e18cfa991b411b92e35d8dac10a937.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 将所有字符串转换为按字典序列排列的字符串然后存入哈希表中，便可判断是否为字母异位词</p>
<p><strong>解题过程</strong>
 <br>
 首先将字符串转换为字符数组，再利用Arrays.sort()函数排序后重新转换为字符串存入哈希表中</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;String&gt;&gt; groupAnagrams(String[] strs) {

        Map&lt;String,List&lt;String&gt;&gt; map=new HashMap&lt;&gt;();
        for(String s:strs){

            char[] arr=s.toCharArray();
            Arrays.sort(arr);
            String ts=new String(arr);
            if(map.get(ts)!=null){

                List&lt;String&gt; tl=map.get(ts);
                tl.add(s);
                map.put(ts,tl);
            }else{

                List&lt;String&gt; tl=new ArrayList&lt;&gt;();
                tl.add(s);
                map.put(ts,tl);
            }
        }
        return new ArrayList&lt;&gt;(map.values());
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/group-anagrams/solutions/2912222/zi-mu-yi-wei-ci-fen-zu-by-ashi-jian-chon-aiao/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142109028</p>]]></description><guid isPermaLink="false">/archives/1b4b0bbf-71d8-4697-93b2-638c01ed189a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:48:07 GMT</pubDate></item><item><title><![CDATA[day-53 两个线段获得的最多奖品]]></title><link>https://wiki.dgfygk.com/archives/30462071-b8cc-4236-acd3-bcd4223dbfc7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-53%20%E4%B8%A4%E4%B8%AA%E7%BA%BF%E6%AE%B5%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%9A%E5%A5%96%E5%93%81&amp;url=/archives/30462071-b8cc-4236-acd3-bcd4223dbfc7" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8af9db23ac9c4439bef9609e0678a28e.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 dp[right] 表示右端点不超过 prizePositions[right] 的线段可以覆盖最大奖品数量，然后枚举后面一条线段的右端点（第一条线段在第二条线段左边），第二条线段的长度可以表示为right-left+1;第一条线段的长度可以表示dp[left]</p>
<p><strong>解题过程</strong>
 <br>
 通过枚举dp[left]+right-left+1的值，最大的那个即为答案</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int maximizeWin(int[] prizePositions, int k) {

        int len=prizePositions.length;
        int dp[]=new int[len+1];
        int left=0;
        int ans=0;
        for(int right=0;right&lt;len;right++){

            while(prizePositions[right]-prizePositions[left]&gt;k){
   //长度超过k
                left++;
            }
            ans=Math.max(ans,right-left+1+dp[left]);
            dp[right+1]=Math.max(dp[right],right-left+1);
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/maximize-win-from-two-segments/solutions/2913554/liang-ge-xian-duan-huo-de-de-zui-duo-jia-8mtm/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142149642</p>]]></description><guid isPermaLink="false">/archives/30462071-b8cc-4236-acd3-bcd4223dbfc7</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:47:58 GMT</pubDate></item><item><title><![CDATA[SpringCloud-03 LoadBalancer服务调用与负载均衡]]></title><link>https://wiki.dgfygk.com/archives/0f5b8ca1-c67c-462f-a31c-70883ead78ca</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-03%20LoadBalancer%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E4%B8%8E%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1&amp;url=/archives/0f5b8ca1-c67c-462f-a31c-70883ead78ca" width="1" height="1" alt="" style="opacity:0;">
<p>LoadBalancer（负载均衡器）是一种网络设备或软件，用于将网络流量分配到多个服务器，以实现负载均衡和高可用性。它可以帮助确保网络服务器能够有效地处理大量的请求，并避免某些服务器过载而导致性能下降或服务不可用。</p>
<p>Nginx是<em>服务器负载均衡</em>，客户端所有请求都会交给nginx，然后由nginx实现转发请求，即负载均衡是由服务端实现的。</p>
<p>loadbalancer<em>本地负载均衡</em>，在调用微服务接口时候，会在注册中心上获取注册信息服务列表之后缓存到JVM本地，从而在本地实现RPC远程服务调用技术。</p>
<p>LoadBalancer 在工作时分成两步：
 <br>
 第一步，先选择ConsulServer从服务端查询并拉取服务列表，知道了它有多个服务(上图3个服务)，这3个实现是完全一样的，
 <br>
 默认轮询调用谁都可以正常执行。
 <br>
 第二步，按照指定的负载均衡策略从server取到的服务注册列表中由客户端自己选择一个地址，所以LoadBalancer是一个客户端的负载均衡器。</p>
<pre><code class="prism language-java">//在800端口拷贝与8001端口一样的服务
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F37cff145d74a4127934c172fb11121ab.png&amp;size=m" alt=""></p>
<p><strong>Consul数据持久化配置</strong></p>
<pre><code class="prism language-java">//在consul同级目录下新建consul_startt.bat文件，内容如下
@echo.服务启动......  
@echo off  
@sc create Consul binpath= "D:\devSoft\consul_1.17.0_windows_386\consul.exe agent -server -ui -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect  1  -data-dir D:\devSoft\consul_1.17.0_windows_386\mydata   "
@net start Consul
@sc config Consul start= AUTO  
@echo.Consul start is OK......success
@pause
</code></pre>
<p>**注意：**consul里data配置的缩进不要用tab键，要用空格
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5e2338e53c424f72911835e691f2e8b3.png&amp;size=m" alt=""></p>
<p><strong>修改80端口（调用端口）</strong></p>
<pre><code class="prism language-java">//在80端口服务pom文件中添加依赖
&lt;!--loadbalancer--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-loadbalancer&lt;/artifactId&gt;
&lt;/dependency&gt;

//修改controller
    @GetMapping("/consumer/pay/get/info")
    public String getByConsul() {

        return restTemplate.getForObject(paymentSrv_URL+"/pay/get/info", String.class);
    }
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2681c58bff7d441ba208971895ca7911.png&amp;size=m" alt=""></p>
<p>会自动交替调用8001端口和8002端口
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fedf4809ed8a3444e952a3e930a80872a.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8f40fbcc0ede4ddbb3c2f0099713aa80.png&amp;size=m" alt="">
 <br>
 通过DiscoveryClient动态获取服务列表</p>
<pre><code class="prism language-java">    @Resource
    private DiscoveryClient discoveryClient;
    @GetMapping("/consumer/discovery")
    public String discovery()
    {

        List&lt;String&gt; services = discoveryClient.getServices();
        for (String element : services) {

            System.out.println(element);
        }

        System.out.println("===================================");

        List&lt;ServiceInstance&gt; instances = discoveryClient.getInstances("cloud-payment-service");
        for (ServiceInstance element : instances) {

            System.out.println(element.getServiceId()+"\t"+element.getHost()+"\t"+element.getPort()+"\t"+element.getUri());
        }

        return instances.get(0).getServiceId()+":"+instances.get(0).getPort();
    }
</code></pre>
<p>负载均衡有两种算法：轮询算法 和 随机算法 一般用轮询</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141902871</p>]]></description><guid isPermaLink="false">/archives/0f5b8ca1-c67c-462f-a31c-70883ead78ca</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:47:50 GMT</pubDate></item><item><title><![CDATA[day-49 让所有学生保持开心的分组方法数]]></title><link>https://wiki.dgfygk.com/archives/580688bc-a815-4136-9e20-a04f0c3b5dd9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-49%20%E8%AE%A9%E6%89%80%E6%9C%89%E5%AD%A6%E7%94%9F%E4%BF%9D%E6%8C%81%E5%BC%80%E5%BF%83%E7%9A%84%E5%88%86%E7%BB%84%E6%96%B9%E6%B3%95%E6%95%B0&amp;url=/archives/580688bc-a815-4136-9e20-a04f0c3b5dd9" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F90ba578b90a34e66a038a5de755dfdcf.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用Collections.sort()函数对数组进行排序，依次向后遍历即可，如果nums.get(i)<i+1\&amp;\&amp;nums.get(i+1)>i+1</i+1\&amp;\&amp;nums.get(i+1)></p>
<p><strong>解题过程</strong>
 <br>
 注意特殊情况：全选和不选要单独讨论</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int countWays(List&lt;Integer&gt; nums) {

        int len=nums.size();
        Collections.sort(nums);
        int ans=0;
        for(int i=0;i&lt;len-1;i++){

         if(nums.get(i)&lt;i+1&amp;&amp;nums.get(i+1)&gt;i+1){

                ans++;
            }

        }
        if(nums.get(0)&gt;0)ans++;
        if(nums.get(len-1)&lt;len)ans++;
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/happy-students/solutions/2904871/rang-suo-you-xue-sheng-bao-chi-kai-xin-d-9nxo/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141903130</p>]]></description><guid isPermaLink="false">/archives/580688bc-a815-4136-9e20-a04f0c3b5dd9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:47:22 GMT</pubDate></item><item><title><![CDATA[day-49 行和列中一和零的差值]]></title><link>https://wiki.dgfygk.com/archives/9fb7b9d3-c243-4564-bd55-652b82a85e16</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-49%20%E8%A1%8C%E5%92%8C%E5%88%97%E4%B8%AD%E4%B8%80%E5%92%8C%E9%9B%B6%E7%9A%84%E5%B7%AE%E5%80%BC&amp;url=/archives/9fb7b9d3-c243-4564-bd55-652b82a85e16" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F768c3c7fbf22455abfbb3035aff68c74.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用两个矩阵存储每行和每列的零的个数和一的个数</p>
<p><strong>解题过程</strong>
 <br>
 最后对应位置一的个数减去零的个数即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int[][] onesMinusZeros(int[][] grid) {

        int row=grid.length;
        int col=grid[0].length;
        int arr1[][]=new int[row][col];
        int arr2[][]=new int[row][col];

        for(int i=0;i&lt;row;i++){

            int znum=0;
            int onum=0;
            for(int j=0;j&lt;col;j++){

                if(grid[i][j]==0) znum++;
                else onum++;
            }
            for(int j=0;j&lt;col;j++){

               arr1[i][j]=znum;
               arr2[i][j]=onum;
            }
        }

        for(int j=0;j&lt;col;j++){

            int znum=0;
            int onum=0;
            for(int i=0;i&lt;row;i++){

                if(grid[i][j]==0) znum++;
                else onum++;
            }
            for(int i=0;i&lt;row;i++){

               arr1[i][j]+=znum;
               arr2[i][j]+=onum;
            }
        }

        for(int i=0;i&lt;row;i++){

            for(int j=0;j&lt;col;j++){

               grid[i][j]=arr2[i][j]-arr1[i][j];
            }
        }
        return grid;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/difference-between-ones-and-zeros-in-row-and-column/solutions/2904908/xing-he-lie-zhong-yi-he-ling-de-chai-zhi-x2z8/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141903767</p>]]></description><guid isPermaLink="false">/archives/9fb7b9d3-c243-4564-bd55-652b82a85e16</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:47:15 GMT</pubDate></item><item><title><![CDATA[day-50 求出最长好子序列 I]]></title><link>https://wiki.dgfygk.com/archives/80a179f8-d21f-4de2-92fd-637b7c51dad8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-50%20%E6%B1%82%E5%87%BA%E6%9C%80%E9%95%BF%E5%A5%BD%E5%AD%90%E5%BA%8F%E5%88%97%20I&amp;url=/archives/80a179f8-d21f-4de2-92fd-637b7c51dad8" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F5688296ced894d0e8a20b1e08db121ad.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 二维dp,dp[i][h]表示nums[i] 结尾，且有不超过 h 个下标满足条件的最长好子序列的长度（0&lt;=h&lt;=k），二维数组dp初始值全为1</p>
<p><strong>解题过程</strong>
 <br>
 状态转换方程：
 <br>
 1.nums[i]==nums[j],dp[i,h]=Math.max(dp[i,h],dp[j,h]+1)</p>
<p>2.nums[i]!=nums[j],dp[i,h]=Math.max(dp[i,h],dp[j,h-1]+1)</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int maximumLength(int[] nums, int k) {

        int n = nums.length;
        int[][] dp = new int[n][k + 1];
        int ans = 0;
        for(int i=0;i&lt;n;i++){

            Arrays.fill(dp[i],1);
        }
        for (int i = 0; i &lt; n; i++) {

            for (int h = 0; h &lt;= k; h++) {

                for (int j = 0; j &lt; i; j++) {

                    if (nums[i] == nums[j]) {

                        dp[i][h] = Math.max(dp[i][h], dp[j][h]+1);
                    } else if (h &gt; 0) {

                        dp[i][h] = Math.max(dp[i][h], dp[j][h - 1]+1);
                    }
                }
            }
            ans = Math.max(ans, dp[i][k]);
        }
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141990108</p>]]></description><guid isPermaLink="false">/archives/80a179f8-d21f-4de2-92fd-637b7c51dad8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:47:06 GMT</pubDate></item><item><title><![CDATA[深度学习-01 Pytorch]]></title><link>https://wiki.dgfygk.com/archives/2bcd98cf-ee38-4879-ace5-adb7d3890dd4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0-01%20Pytorch&amp;url=/archives/2bcd98cf-ee38-4879-ace5-adb7d3890dd4" width="1" height="1" alt="" style="opacity:0;">
<p>PyTorch是一个开源的机器学习框架，它提供了一个灵活的高级接口，可以帮助开发者构建神经网络模型以及进行深度学习的训练和推理。PyTorch基于Torch库和Lua编程语言开发而来，但在PyTorch中使用Python作为主要编程语言。PyTorch的设计目标是简单易用、灵活性强，并且可以提供高性能的计算。它的核心特点包括：
 <br>
 1.动态图计算：PyTorch使用动态图计算方式，这意味着在编写代码时可以直接操作和修改计算图，而不需要预先定义静态图。
 <br>
 2.自动求导：PyTorch能够自动计算张量的导数，这对于训练神经网络模型非常重要。开发者可以使用autograd模块轻松地定义和计算导数。
 <br>
 3.强大的GPU加速：PyTorch支持使用GPU进行计算，可以充分发挥GPU在深度学习任务中的性能优势。开发者可以通过简单的代码更改，将计算转移到GPU上。
 <br>
 4.大型社区支持：PyTorch拥有一个庞大的用户社区，有许多开发者不断贡献新的功能和工具，以及解决问题和提供支持。
 <br>
 5.深度学习框架的完整功能：PyTorch提供了许多用于构建和训练深度学习模型的工具和功能，如各种层、优化算法和损失函数等。</p>
<p>本地解释器位置： D:\deeplearning\anaconda3\envs\pytorch\python.exe</p>
<p>Tensboard是一个用于可视化和分析深度学习模型训练过程中中间结果的工具。它是TensorBoard的一个扩展，可用于PyTorch和TensorFlow模型。
 <br>
 1.Tensboard为我们提供了一个交互式的界面，可以帮助我们更好地理解我们的模型如何学习和改进。它可以显示训练过程中的损失函数变化、模型准确率、学习率等指标的趋势图。此外，Tensboard还提供了可视化工具，可以显示模型中的权重、梯度和激活值的分布情况，帮助我们分析模型的表现和瓶颈。
 <br>
 2.使用Tensboard非常简单，只需在代码中添加几行代码，就可以将关键的训练指标和中间结果保存到日志文件中。然后，我们可以在Tensboard界面上加载这些日志文件，即可查看模型训练的详细信息和可视化结果。</p>
<pre><code class="prism language-bash">tensorboard --logdir=logs       //打开 tensorboard生成的logs下的文件
</code></pre>
<p>Transforms是PyTorch中的一个重要模块，用于数据预处理和数据增强。它提供了多种常用的转换函数，可以对图像、文本、张量等进行各种操作。
 <br>
 在PyTorch中，数据预处理通常是在数据加载之前进行的。通过使用transforms模块，可以对原始数据进行多种操作，如缩放、裁剪、旋转、翻转、标准化等，以便更好地适应模型的训练要求。同时，transforms还可以用于数据增强，通过随机变换数据的方式来扩充训练集，提高模型的泛化能力。</p>
<pre><code class="prism language-java">tensor_trans=transforms.ToTensor()              #声明一个转换tensor类型的工具
tensor_img=tensor_trans(img)                    #转换图片类型


#一些例子
from PIL import Image
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter
from torchvision.transforms import Resize

writer=SummaryWriter("logs")
img=Image.open("hymenoptera_data/train/bees/39747887_42df2855ee.jpg")
print(img)

# ToTensor
trans_totensor=transforms.ToTensor()
img_tensor=trans_totensor(img)
writer.add_image("ToTensor",img_tensor)


# Normalize
print(img_tensor[0][0][0])
trans_norm=transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])
img_norm=trans_norm(img_tensor)
print(img_norm[0][0][0])
writer.add_image("Normlize",img_norm)

# Resize
print(img.size)
trans_resize=transforms.Resize((512,512))
# img PIL-&gt;resize-&gt;img_resize PIL
img_resize=trans_resize(img)
# img_resize PIL-&gt;totensor-&gt;img_resize tensor
img_resize=trans_totensor(img_resize)
writer.add_image("Resize",img_resize)
print(img_resize)

# Compose - resize - 2
#还有很多方法。。。。。。

writer.close()
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142005158</p>]]></description><guid isPermaLink="false">/archives/2bcd98cf-ee38-4879-ace5-adb7d3890dd4</guid><dc:creator>黄健</dc:creator><category>python</category><pubDate>Thu, 26 Sep 2024 09:46:20 GMT</pubDate></item><item><title><![CDATA[day-47 组合]]></title><link>https://wiki.dgfygk.com/archives/7d03132f-d215-4d46-9f1c-46c25c43db22</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-47%20%E7%BB%84%E5%90%88&amp;url=/archives/7d03132f-d215-4d46-9f1c-46c25c43db22" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9f5af0bdca1c49019b272a94e332ebe9.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 回溯：利用个dfs方法递归调用，每个元素有选或不选两种抉择，当选中元素个数等于k时，将链表p加入答案，当ids==n且选中元素个数小于n时，直接返回</p>
<p><strong>解题过程</strong>
 <br>
 每次选中元素调用dfs方法后记得还原</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;Integer&gt;&gt; list =new ArrayList&lt;&gt;();
    public int nn;
    public int kk;
    public List&lt;List&lt;Integer&gt;&gt; combine(int n, int k) {

        List&lt;Integer&gt; p=new ArrayList&lt;&gt;();
        nn=n;
        kk=k;
        dfs(p,0,0);
        return list;
    }
    public void dfs(List&lt;Integer&gt; p,int ids,int num){

        if(num==kk){

            list.add(new ArrayList(p));
            return;
        }
        if(ids==nn) return;
        dfs(p,ids+1,num);

        p.add(ids+1);
        dfs(p,ids+1,num+1);
        p.remove(p.size()-1);
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/combinations/solutions/2902088/zu-he-by-ashi-jian-chong-dan-liao-shi-ya-bwh6/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141830289</p>]]></description><guid isPermaLink="false">/archives/7d03132f-d215-4d46-9f1c-46c25c43db22</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:45:10 GMT</pubDate></item><item><title><![CDATA[day-48 分割回文串]]></title><link>https://wiki.dgfygk.com/archives/4a135147-28b4-482e-9542-91f77d6439f3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-48%20%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2&amp;url=/archives/4a135147-28b4-482e-9542-91f77d6439f3" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F906fba5e7de74bb19f4346dee38ea7a5.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用dfs算法，用ids表示当前所指向字符的位置，依次判断s.charAt(ids),s.charAt(ids)+s.charAt(ids+1)…是否为回文字符串，如果是则加入链表p,再递归调用dfs函数</p>
<p><strong>解题过程</strong>
 <br>
 每次调用dfs函数后记得还原现场</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public String ss;
    public int len;
    public List&lt;List&lt;String&gt;&gt; list=new ArrayList&lt;&gt;();
    public List&lt;List&lt;String&gt;&gt; partition(String s) {

        ss=s;
        len=s.length();
        List&lt;String&gt; p=new ArrayList&lt;&gt;();
        dfs(p,0);
        return list;

    }

    public void dfs(List&lt;String&gt; p,int ids){

        if(ids==len){

            list.add(new ArrayList(p));
            return;
        }
        String t="";
        for(int i=ids;i&lt;len;i++){

            t+=ss.charAt(i);
            if(isTrue(t)){

                p.add(t);
                dfs(p,i+1);
                p.remove(p.size()-1);
            }
        }
    }

    public boolean isTrue(String str) {

        int len = str.length();
        for (int i = 0; i &lt; len / 2; i++) {

            if (str.charAt(i) != str.charAt(len - 1 - i)) {

                return false;
            }
        }
        return true;
    }

}

作者：菜卷
链接：https://leetcode.cn/problems/palindrome-partitioning/solutions/2903400/fen-ge-hui-wen-chuan-by-ashi-jian-chong-bap36/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141870136</p>]]></description><guid isPermaLink="false">/archives/4a135147-28b4-482e-9542-91f77d6439f3</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:45:02 GMT</pubDate></item><item><title><![CDATA[day-48 一个小组的最大实力值]]></title><link>https://wiki.dgfygk.com/archives/5f35e9b2-5668-457d-9bf5-ad1de600671e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-48%20%E4%B8%80%E4%B8%AA%E5%B0%8F%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%AE%9E%E5%8A%9B%E5%80%BC&amp;url=/archives/5f35e9b2-5668-457d-9bf5-ad1de600671e" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F96a620e5b423439eb7f0e72e5a25f7ca.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 想把所有非零数相乘，再统计负数的个数，如果负数为奇数个，则把乘机除以最大的那个负数即为答案，如果为偶数个，那么乘机即为答案</p>
<p><strong>解题过程</strong>
 <br>
 但要考虑特殊情况：1.只有零和一个负数，返回零
 <br>
 2.全为零，返回零</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public long maxStrength(int[] nums) {

        long ans=1;
        int num=0;
        Arrays.sort(nums);
        int len=nums.length;
        if(len==1) return nums[0];
        for(int i=0;i&lt;len;i++){

            if(nums[i]!=0)
            ans*=nums[i];
            if(nums[i]&lt;0)num++;
        }
        if(nums[len-1]==0&amp;&amp;num==1)return 0;
if(nums[len-1]==0&amp;&amp;nums[0]==0)return 0;
        if(num&gt;0&amp;&amp;num%2==1) return ans/nums[num-1];
        else return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/maximum-strength-of-a-group/solutions/2903368/yi-ge-xiao-zu-de-zui-da-shi-li-zhi-by-as-5e64/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141869749</p>]]></description><guid isPermaLink="false">/archives/5f35e9b2-5668-457d-9bf5-ad1de600671e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:44:32 GMT</pubDate></item><item><title><![CDATA[day-49 使数组中所有元素相等的最小操作数]]></title><link>https://wiki.dgfygk.com/archives/2c919a3a-ad0a-43e7-900c-e1220352bd8b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-49%20%E4%BD%BF%E6%95%B0%E7%BB%84%E4%B8%AD%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E6%93%8D%E4%BD%9C%E6%95%B0&amp;url=/archives/2c919a3a-ad0a-43e7-900c-e1220352bd8b" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0e54ad8e08bd4dd78f2c0c6138cfd043.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 第一个数和最后一个数要变为一致，需要操作n-1次，然后第二个数和倒数第二个数要操作n-3次</p>
<p><strong>解题过程</strong>
 <br>
 以此类推即可得出答案</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int minOperations(int n) {

        int ans=0;
        int t=(n-1);
        while(t&gt;0){

           ans+=t;
           t-=2;
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/minimum-operations-to-make-array-equal/solutions/2904892/shi-shu-zu-zhong-suo-you-yuan-su-xiang-d-g0g2/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141903521</p>]]></description><guid isPermaLink="false">/archives/2c919a3a-ad0a-43e7-900c-e1220352bd8b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:44:17 GMT</pubDate></item><item><title><![CDATA[SpringCloud-02 Consul服务注册与发现]]></title><link>https://wiki.dgfygk.com/archives/da2f0d84-4f38-4eb7-87c6-4a548bb84916</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-02%20Consul%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0&amp;url=/archives/da2f0d84-4f38-4eb7-87c6-4a548bb84916" width="1" height="1" alt="" style="opacity:0;">
<p>Consul是一种用于服务发现、配置和分布式协调的开源工具。Consul提供了以下主要功能：
 <br>
 1.服务发现：Consul允许开发人员在微服务架构中注册和发现服务。它可以自动检测新添加的服务并为它们分配唯一的网络地址。</p>
<p>2.健康检查：Consul可以定期检查注册的服务的健康状况，以确保它们正常运行并能够提供服务。如果服务不可用，Consul会自动将其从服务注册表中移除。</p>
<p>3.分布式键值存储：Consul提供了一个分布式键值存储，可以用于存储配置信息、应用程序状态等。</p>
<p>4.多数据中心支持：Consul支持跨多个数据中心的部署，并提供了强大的数据中心间的服务发现和同步功能。</p>
<p>5.安全性：Consul提供了基于ACL的安全机制，可以通过访问控制列表来限制对服务和资源的访问。</p>
<p>consul agent -dev //开发者模式启动consul，打来后不要关闭cmd窗口
 <br>
 url地址： http://localhost:8500
 <br>
 把8001端口的服务注册进consul</p>
<pre><code class="prism language-java">//在对应端口的pom文件添加依赖
&lt;!--SpringCloud consul discovery --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-starter-consul-discovery&lt;/artifactId&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;commons-logging&lt;/groupId&gt;
                    &lt;artifactId&gt;commons-logging&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
        &lt;/dependency&gt;

spring:
  application:
    name: cloud-consumer-order
//添加yml文件配置
####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${
   spring.application.name}

在启动类上添加注解   @EnableDiscoveryClient  再启动
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fba70babd0811403bb64a9a9df116c91c.png&amp;size=m" alt=""></p>
<pre><code class="prism language-java">//80端口的controller类中调用8001端口
public static final String paymentSrv_URL = "http://cloud-payment-service";

//80端口下config下RestTemplateConfig类添加@LoadBalanced标签
@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {

        return new RestTemplate();
    }
}
</code></pre>
<p>除了常见的Consul(CP) 还有Eureka(AP) Zookeeper(CP)
 <br>
 CP:强调一致性
 <br>
 AP:强调可用性</p>
<p>当下每个微服务都有application.yml配置文件，繁琐且不易修改，这亦可以利用Consul解决</p>
<pre><code class="prism language-java">//添加pom依赖
&lt;!--SpringCloud consul config--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-consul-config&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-bootstrap&lt;/artifactId&gt;
&lt;/dependency&gt;

 //bootstrap.yml内容
 spring:
  application:
    name: cloud-payment-service
    ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${
   spring.application.name}
      config:
        profile-separator: '-' # default value is ","，we update '-'
        format: YAML

  # config/cloud-payment-service/data
  #       /cloud-payment-service-dev/data
  #       /cloud-payment-service-prod/data


 //application.yml内容
 server:
  port: 8001

# ==========applicationName + druid-mysql8 driver===================
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=GMT%2B8&amp;rewriteBatchedStatements=true&amp;allowPublicKeyRetrieval=true
    username: root
    password: root
  profiles:
    active: dev # 多环境配置加载内容dev/prod,不写就是默认default配置

# ========================mybatis===================
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.dc.cloud.entities
  configuration:
    map-underscore-to-camel-case: true



//在consul添加配置，注意配置文件夹格式

//在controller中添加
   @Value("$server.port")
    private String port;

    @GetMapping("/pay/get/info")
    public String getInfoByConsul(@Value("${dc.info}") String dcInfo){
        return "dcInfo:" + dcInfo+"\t"+"port:" + port;
    }
</code></pre>
<p>Consul 动态及时刷新</p>
<pre><code class="prism language-java">//启动类添加 @RefreshScope 注解

//修改bootstrap.yml配置     但一般不要改，默认55秒
        watch:
          wait-time: 1
</code></pre>
<p>Consul持久化配置</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141829337</p>]]></description><guid isPermaLink="false">/archives/da2f0d84-4f38-4eb7-87c6-4a548bb84916</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:43:09 GMT</pubDate></item><item><title><![CDATA[day-47 子集]]></title><link>https://wiki.dgfygk.com/archives/9508e001-37a8-45f5-801e-41fe0fb88e62</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-47%20%E5%AD%90%E9%9B%86&amp;url=/archives/9508e001-37a8-45f5-801e-41fe0fb88e62" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fce096e8f86e8450d9d7574c2149bc1a1.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用深度优先遍历算法，对于每个数有选或不选两种抉择，每次遍历到ids==len时将p加入答案中</p>
<p><strong>解题过程</strong>
 <br>
 选中当前数字，调用dfs函数之后记得还原</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int len;
    public List&lt;List&lt;Integer&gt;&gt; list=new ArrayList&lt;&gt;();
    public List&lt;List&lt;Integer&gt;&gt; subsets(int[] nums) {

        len=nums.length;
        List&lt;Integer&gt; p=new ArrayList&lt;&gt;();
        dfs(0,p,nums);
        return list;
    }
    public void dfs(int ids,List&lt;Integer&gt; p,int []nums){

        if(ids==len){

            list.add(new ArrayList(p));
            return;
        }
        dfs(ids+1,p,nums);
        p.add(nums[ids]);
        dfs(ids+1,p,nums);
        p.remove(p.size()+-1);
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/subsets/solutions/2902071/zi-ji-by-ashi-jian-chong-dan-liao-shi-ya-0bm6/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141830111</p>]]></description><guid isPermaLink="false">/archives/9508e001-37a8-45f5-801e-41fe0fb88e62</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:42:48 GMT</pubDate></item><item><title><![CDATA[day-47 最大连续1的个数 III]]></title><link>https://wiki.dgfygk.com/archives/1b514141-fd66-4545-bce1-fdc68698c17c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-47%20%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0%20III&amp;url=/archives/1b514141-fd66-4545-bce1-fdc68698c17c" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7850a2ee9f3c440a9569e569a053569c.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 滑动窗口：如果用left和right表示连续1的左右边界，那么意味着可以把0修改为1的次数为k次</p>
<p><strong>解题过程</strong>
 <br>
 用一个变量num记录left和right之间0的个数，当num&lt;=k时，right++,num&gt;k时，将left向右移动，直到不再满足num&gt;k</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int longestOnes(int[] nums, int k) {

        int len=nums.length;
        int ans=0;
        int left=0;
        int num=0;
        for(int right=0;right&lt;len;right++){

            if(nums[right]!=1)num++;
            while(num&gt;k){

                if(nums[left]!=1)num--;
                left++;
            }
            ans=Math.max(ans,right-left+1);
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/max-consecutive-ones-iii/solutions/2901086/zui-da-lian-xu-1de-ge-shu-iii-by-ashi-ji-le8t/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141813288</p>]]></description><guid isPermaLink="false">/archives/1b514141-fd66-4545-bce1-fdc68698c17c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:42:41 GMT</pubDate></item><item><title><![CDATA[day-55 从字符串中移除星号]]></title><link>https://wiki.dgfygk.com/archives/2a1b273b-47bc-415b-959e-a9fe9901c01f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-55%20%E4%BB%8E%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%A7%BB%E9%99%A4%E6%98%9F%E5%8F%B7&amp;url=/archives/2a1b273b-47bc-415b-959e-a9fe9901c01f" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8e4384091e1c42e7b1689c2e1d3cb535.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 因为删掉及其左边第一个字符，所以从右向左遍历，维护一个num的值，num表示还需要删掉多少个的左边第一个字符</p>
<p><strong>解题过程</strong>
 <br>
 1.当前字符为时：num++；
 <br>
 2.当前字符不为时：2.1如果num&gt;0，直接跳过当前字符；2.2如果num==0，将当前字符头插入需要返回的字符串</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public String removeStars(String s) {

        String ans="";
        int len=s.length();
        int num=0;
        for(int i=len-1;i&gt;=0;i--){

            if(s.charAt(i)=='*')num++;
            else{

                if(num==0){

                    ans=""+s.charAt(i)+ans;
                }else{

                    num--;
                }
            }
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/removing-stars-from-a-string/solutions/2916446/cong-zi-fu-chuan-zhong-yi-chu-xing-hao-b-9zau/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/142249935</p>]]></description><guid isPermaLink="false">/archives/2a1b273b-47bc-415b-959e-a9fe9901c01f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:42:32 GMT</pubDate></item><item><title><![CDATA[day-43 有效的数独]]></title><link>https://wiki.dgfygk.com/archives/57b31ac2-c9d7-4617-b71c-59d8e4456c40</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-43%20%E6%9C%89%E6%95%88%E7%9A%84%E6%95%B0%E7%8B%AC&amp;url=/archives/57b31ac2-c9d7-4617-b71c-59d8e4456c40" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1e49ca71c5e94cf0a1c847331163e469.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 暴力解法：1.判断每行是否有重复的数字;2.判断每列是否有重复的数字；3.判断每一个以粗实线分隔的 3x3 宫内是否有重复的数字</p>
<p><strong>解题过程</strong>
 <br>
 如果以上三种情况都不存在重复的数字，则返回true</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public boolean isValidSudoku(char[][] board) {

        for(int i=0;i&lt;9;i++){

            List&lt;Integer&gt; list =new ArrayList&lt;&gt;();
            for(int j=0;j&lt;9;j++){

                if(board[i][j]!='.'){

                    int num=board[i][j]-'0';
                    if(list.contains(num)) return false;
                    list.add(num);
                }
            }
        }

        for(int i=0;i&lt;9;i++){

            List&lt;Integer&gt; list =new ArrayList&lt;&gt;();
            for(int j=0;j&lt;9;j++){

                if(board[j][i]!='.'){

                    int num=board[j][i]-'0';
                    if(list.contains(num)) return false;
                    list.add(num);
                }
            }
        }

        for( int m=0;m&lt;3;m++){

            for(int n=0;n&lt;3;n++){

                List&lt;Integer&gt; list =new ArrayList&lt;&gt;();
                for(int i=0;i&lt;3;i++){

                    for(int j=0;j&lt;3;j++){

                        if(board[3*m+i][3*n+j]!='.'){

                        int num=board[3*m+i][3*n+j]-'0';
                        if(list.contains(num)) return false;
                        list.add(num);
                        }
                    }
                }
            }
        }
        return true;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/valid-sudoku/solutions/2897308/you-xiao-de-shu-du-by-ashi-jian-chong-da-o6ll/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141689292</p>]]></description><guid isPermaLink="false">/archives/57b31ac2-c9d7-4617-b71c-59d8e4456c40</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:42:20 GMT</pubDate></item><item><title><![CDATA[day-44 所有数对中数位不同之和]]></title><link>https://wiki.dgfygk.com/archives/91e73d50-2ecd-4d3b-804e-89f6f927aafc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-44%20%E6%89%80%E6%9C%89%E6%95%B0%E5%AF%B9%E4%B8%AD%E6%95%B0%E4%BD%8D%E4%B8%8D%E5%90%8C%E4%B9%8B%E5%92%8C&amp;url=/archives/91e73d50-2ecd-4d3b-804e-89f6f927aafc" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0525f55ea7a943e6b78160381de5a934.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 因为所有整数都有相同的数位长度，所以先算出数位长度，然后依次统计个位、十位、百位…的数位不同之和，最后除以2</p>
<p><strong>解题过程</strong>
 <br>
 例如：13、12的数位不同之和，可以简化为个位2、3和十位1、1的数位不同之和，即答案为1。需要注意（2,3）会被计算两次，所以最终答案需要除以2</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public long sumDigitDifferences(int[] nums) {

        int len=nums.length;
        int n=nums[0];
        int nLen=0;     //数位长度
        while(n&gt;0){

            n/=10;
            nLen++;
        } 
        long arr[][]=new long[nLen][10];
        for(int i=0;i&lt;nLen;i++){

            for(int j=0;j&lt;len;j++){

                arr[i][nums[j]%10]++;
                nums[j]/=10;
            }
        }
        long ans=0;
        for(int i=0;i&lt;nLen;i++){

            for(int j=0;j&lt;10;j++){

                ans+=arr[i][j]*(len-arr[i][j]);
            }
        }
        return ans/2;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/sum-of-digit-differences-of-all-pairs/solutions/2897598/suo-you-shu-dui-zhong-shu-wei-bu-tong-zh-cbc4/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141707262</p>]]></description><guid isPermaLink="false">/archives/91e73d50-2ecd-4d3b-804e-89f6f927aafc</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:42:09 GMT</pubDate></item><item><title><![CDATA[day-44 组合总和]]></title><link>https://wiki.dgfygk.com/archives/7cef14c7-8792-4ffd-8771-e88b3d6e38ad</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-44%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C&amp;url=/archives/7cef14c7-8792-4ffd-8771-e88b3d6e38ad" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0e2c7589b1a0452a9e4605141a71ba1d.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 回溯的思想：用一个i变量记录当前抉择数字所在位置，对于每一个数字，都有选或不选两种可能，如果选择当前数字，则i不变，并把当前数字加入链表当中，sum也加上当前数字的值；如果不选，则只需将i++。当ilen返回，当sumtarget时把当前的p加入答案。</p>
<p><strong>解题过程</strong>
 <br>
 关键在于选或不选两种抉择怎么用代码表示
 <br>
 选：p.add(arr[i]); dfs(p,i,sum+arr[i]); p.remove(p.size()-1);
 <br>
 不选：dfs(p,i+1,sum); //相当于直接跳过</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    List&lt;List&lt;Integer&gt;&gt; list=new ArrayList&lt;&gt;();
    public int tar;
    public int len;
    public int arr[];
    public List&lt;List&lt;Integer&gt;&gt; combinationSum(int[] candidates, int target) {

        len=candidates.length;
        tar=target;
        arr=new int[len];
        arr=candidates;
        List&lt;Integer&gt; p=new ArrayList&lt;&gt;();
        dfs(p,0,0);
        return list;
    }

    public void dfs(List&lt;Integer&gt; p,int i,int sum){

        if(i==len) return;
        if(sum==tar){

            list.add(new ArrayList(p));
            return;
        } 
        if(sum&lt;tar){

            p.add(arr[i]);
            dfs(p,i,sum+arr[i]);
            p.remove(p.size()-1);
        }
         dfs(p,i+1,sum);   
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/combination-sum/solutions/2897900/zu-he-zong-he-by-ashi-jian-chong-dan-lia-imuy/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141717318</p>]]></description><guid isPermaLink="false">/archives/7cef14c7-8792-4ffd-8771-e88b3d6e38ad</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:42:01 GMT</pubDate></item><item><title><![CDATA[day-45 全排列 II]]></title><link>https://wiki.dgfygk.com/archives/764b10aa-f254-41bc-a556-c4b6f49f0d56</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-45%20%E5%85%A8%E6%8E%92%E5%88%97%20II&amp;url=/archives/764b10aa-f254-41bc-a556-c4b6f49f0d56" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F399e9dea6e434302bb98fe17dfece07f.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 与上一题思路相同，代码也基本一致，只是需要全排列不重复</p>
<p><strong>解题过程</strong>
 <br>
 可以利用Arrays.sort()函数将nums数组进行排序，这样相同的全排列数字的位置也会相同，可以利用List的contains()函数进行判断，如果不重复则加入答案</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;Integer&gt;&gt; list=new ArrayList&lt;&gt;();
    public int len;
    public int choose[];
    public List&lt;List&lt;Integer&gt;&gt; permuteUnique(int[] nums) {

        len=nums.length;
        choose=new int[len];
        Arrays.fill(choose,1);
        Arrays.sort(nums);
        List&lt;Integer&gt; p=new ArrayList&lt;&gt;();
        dfs(p,0,nums);
        return list;
    }
    public void dfs(List&lt;Integer&gt; p,int num,int[] arr){

        if(num==len){

            if(!list.contains(p)){

                list.add(new ArrayList(p));
                return;
            }
        }
        for(int j=0;j&lt;len;j++){

            if(choose[j]!=-1){

                choose[j]=-1;
                p.add(arr[j]);
                choose[j]=-1;
                dfs(p,num+1,arr);
                p.remove(p.size()-1);
                choose[j]=1;
            }
        }
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/permutations-ii/solutions/2899318/quan-pai-lie-ii-by-ashi-jian-chong-dan-l-cgpf/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141758787</p>]]></description><guid isPermaLink="false">/archives/764b10aa-f254-41bc-a556-c4b6f49f0d56</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:41:52 GMT</pubDate></item><item><title><![CDATA[day-45 全排列]]></title><link>https://wiki.dgfygk.com/archives/0dda19bb-f97c-4ceb-9e3a-4f8788282146</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-45%20%E5%85%A8%E6%8E%92%E5%88%97&amp;url=/archives/0dda19bb-f97c-4ceb-9e3a-4f8788282146" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0a99f9db9c6c4a5d832dcdf145ded84b.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 递归调用dfs,利用一个数组来表示当前位置是否已经被选中，如果没有则选中当前数字，num+1(num表示已经选中数字个数)，每当num==nums.length时，将List加入答案</p>
<p><strong>解题过程</strong>
 <br>
 注意：使用num+1作为传参，不要num++（num++是先传参，再++）</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;Integer&gt;&gt; list=new ArrayList&lt;&gt;();
    public int len;
    public int choose[];
    public List&lt;List&lt;Integer&gt;&gt; permute(int[] nums) {

        len=nums.length;
        choose=new int[len];
        Arrays.fill(choose,1);
        List&lt;Integer&gt; p=new ArrayList&lt;&gt;();
        dfs(p,0,nums);
        return list;
    }
    public void dfs(List&lt;Integer&gt; p,int num,int[] arr){

        if(num==len){

            list.add(new ArrayList(p));
            return;
        }
        for(int j=0;j&lt;len;j++){

            if(choose[j]!=-1){

                choose[j]=-1;
                p.add(arr[j]);
                choose[j]=-1;
                dfs(p,num+1,arr);
                p.remove(p.size()-1);
                choose[j]=1;
            }
        }

    }
}

作者：菜卷
链接：https://leetcode.cn/problems/permutations/solutions/2899311/quan-pai-lie-by-ashi-jian-chong-dan-liao-br1q/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141758665</p>]]></description><guid isPermaLink="false">/archives/0dda19bb-f97c-4ceb-9e3a-4f8788282146</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:41:29 GMT</pubDate></item><item><title><![CDATA[day-46 旋转图像]]></title><link>https://wiki.dgfygk.com/archives/28ce5133-383c-4bd5-a3db-9f7c8d95aefd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-46%20%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F&amp;url=/archives/28ce5133-383c-4bd5-a3db-9f7c8d95aefd" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc87e3366950b41b29bc7018e38e20ac4.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 不能使用辅助数组，所以关键在于弄清楚旋转后坐标的变化规律。当矩阵的大小n为偶数时，以n/2行和n/2列的元素为起点，当矩阵的大小n为奇数时，以n/2行和（n+1）/2列的元素为起点</p>
<p><strong>解题过程</strong>
 <br>
 关键：旋转过程中互相交换的四个元素的坐标为：1.matrix[i][j] 2. matrix[n - 1 - j][i] 3.matrix[n - 1 - i][n - 1 - j] 4.matrix[j][n - 1 - i]</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public void rotate(int[][] matrix) {

        int n=matrix.length;
            int row=n/2;
            int col=(n+1)/2;
            for(int i=0;i&lt;row;i++){

                for(int j=0;j&lt;col;j++){

                    int t=matrix[i][j];
                    matrix[i][j] = matrix[n - 1 - j][i];
                    matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j];
                    matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i];
                    matrix[j][n - 1 - i] = t;
                }
            }
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/rotate-image/solutions/2900533/xuan-zhuan-tu-xiang-by-ashi-jian-chong-d-4nud/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141789305</p>]]></description><guid isPermaLink="false">/archives/28ce5133-383c-4bd5-a3db-9f7c8d95aefd</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:41:20 GMT</pubDate></item><item><title><![CDATA[day-47 考试的最大困扰度]]></title><link>https://wiki.dgfygk.com/archives/5f41aa21-e24b-4be6-a1b7-cc3b591a8784</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-47%20%E8%80%83%E8%AF%95%E7%9A%84%E6%9C%80%E5%A4%A7%E5%9B%B0%E6%89%B0%E5%BA%A6&amp;url=/archives/5f41aa21-e24b-4be6-a1b7-cc3b591a8784" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F9a8268dd3fb34961a207b9dd6b709aa5.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 滑动窗口：如果用left和right表示连续相同结果的题数的左右边界，那么意味着可以修改k次，我们可以分别统计T和F连续相同的题数的最大值，然后返回Math.max(maxLen('T',k),maxLen('F',k))即可</p>
<p><strong>解题过程</strong>
 <br>
 假设计算连续相同答案为T的题数，用一个变量num记录left和right之间F的个数
 <br>
 num&lt;=k时，right++,num&gt;k时，将left向右移动，直到不再满足num&gt;k</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public String s;
    public int len;
    public int maxConsecutiveAnswers(String answerKey, int k) {

        len=answerKey.length();
        s=answerKey;
        return Math.max(maxLen('T',k),maxLen('F',k));
    }

    public int maxLen(char ch,int k){

        int ans=0;
        int num=0;
        int left=0;
        for(int right=0;right&lt;len;right++){

            if(s.charAt(right)!=ch)num++;
            while(num&gt;k){

                if(s.charAt(left)!=ch){

                    num--;
                }
                left++;
            }
            ans=Math.max(ans,right-left+1);
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/maximize-the-confusion-of-an-exam/solutions/2901061/kao-shi-de-zui-da-kun-rao-du-by-ashi-jia-svzy/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141812815</p>]]></description><guid isPermaLink="false">/archives/5f41aa21-e24b-4be6-a1b7-cc3b591a8784</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:41:10 GMT</pubDate></item><item><title><![CDATA[SpringCloud-01]]></title><link>https://wiki.dgfygk.com/archives/d97c3ecf-d760-4850-b21e-76fba6ef225a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringCloud-01&amp;url=/archives/d97c3ecf-d760-4850-b21e-76fba6ef225a" width="1" height="1" alt="" style="opacity:0;">
<p>Spring Cloud是一个开源的微服务框架，它基于Spring Boot构建，提供了一系列的工具和组件，用于简化微服务的开发和部署。
 <br>
 Spring Cloud包含了许多常用的微服务模式和解决方案，比如服务发现、负载均衡、断路器、配置中心、网关等。它通过集成各种组件，帮助开发人员快速构建和部署微服务应用。</p>
<p>微服务小口诀：1.建Module
 <br>
 2.改pom
 <br>
 3.写YML
 <br>
 4.主启动
 <br>
 5.业务类</p>
<p>DTO（Data Transfer Object）和 VO（Value Object）都是一种设计模式，用于封装数据和提供服务。它们的主要区别在于：
 <br>
 DTO：用于封装数据传输对象，可以将数据库中的数据转换为前端需要的格式，方便前后端之间的数据交互。
 <br>
 VO：用于封装值对象，可以根据具体的需求来封装不同的数据属性，方便前端页面的显示和交互。</p>
<p>假设我们有一个 User 类，表示用户信息，包含 id、name 和 age 三个属性
 <br>
 1.如果我们需要在前端页面上显示用户信息，但是不需要显示用户的 id 属性。这时候，我们可以使用 VO 来封装用户信息的部分属性，UserVO 只包含了需要在前端页面上显示的属性 name 和 age，而不包含 id 属性。用于在前端页面上显示，这样可以避免将敏感数据传输到前端页面上，增加数据的安全性。
 <br>
 2.如果我们需要在前后端之间传输用户信息的全部属性，可以使用 DTO 来封装数据。UserDTO 包含了用户信息的全部属性，用于在前后端之间传输数据。由于 DTO 只包含数据属性，不包含任何业务逻辑，因此可以避免数据的重复查询和传输，提高程序的性能和效率。</p>
<p>一般而言，调用者不应该获取服务提供者的entity并知道表结构关系，所以服务提供者给出的接口文档都应该成为DTO</p>
<p>一般DTO是前端给后端，VO是后端给前端</p>
<pre><code class="prism language-java">@PutMapping("/pay/update")
    public String updatePay(@RequestBody PayDTO payDTO){

        Pay pay=new Pay();
        BeanUtil.copyProperties(payDTO,pay);        //payDTO拷贝到pay
        System.out.println(payDTO.toString());
        int i=payService.update(pay);
        return "成功更新记录，返回值"+i;
    }

BeanUtil.copyProperties(payDTO,pay);        //payDTO拷贝到pay
</code></pre>
<p>推荐@Resource 尽量不用@Autowired</p>
<p>测试工具PostMan Swagger3</p>
<p><strong>规范时间格式</strong>
 <br>
 <em>第一种：</em> 加注解</p>
<pre><code class="prism language-java">@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date createTime;
</code></pre>
<p><em>第二种：</em> yml文件里配置
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F784f6763dc2841a5aed7b1499180142d.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2a337588c4494b2fa081f14d92333d08.png&amp;size=m" alt=""></p>
<p><strong>统一返回格式</strong>
 <br>
 3大标配：1.code状态值 2.message描述 3.data数据
 <br>
 拓展：时间戳</p>
<pre><code class="prism language-java">//枚举值
@Getter
public enum ReturnCodeEnum {

    /**操作失败**/
    RC999("999","操作XXX失败"),
    /**操作成功**/
    RC200("200","success"),
    /**服务降级**/
    RC201("201","服务开启降级保护,请稍后再试!"),
    /**热点参数限流**/
    RC202("202","热点参数限流,请稍后再试!"),
    /**系统规则不满足**/
    RC203("203","系统规则不满足要求,请稍后再试!"),
    /**授权规则不通过**/
    RC204("204","授权规则不通过,请稍后再试!"),
    /**access_denied**/
    RC403("403","无访问权限,请联系管理员授予权限"),
    /**access_denied**/
    RC401("401","匿名用户访问无权限资源时的异常"),
    RC404("404","404页面找不到的异常"),
    /**服务异常**/
    RC500("500","系统异常，请稍后重试"),
    RC375("375","数学运算异常，请稍后重试"),

    INVALID_TOKEN("2001","访问令牌不合法"),
    ACCESS_DENIED("2003","没有权限访问该资源"),
    CLIENT_AUTHENTICATION_FAILED("1001","客户端认证失败"),
    USERNAME_OR_PASSWORD_ERROR("1002","用户名或密码错误"),
    BUSINESS_ERROR("1004","业务逻辑异常"),
    UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式");

    /**自定义状态码**/
    private final String code;
    /**自定义描述**/
    private final String message;

    ReturnCodeEnum(String code, String message){

        this.code = code;
        this.message = message;
    }

    //第一种：遍历枚举V1 传统方法
    public static ReturnCodeEnum getReturnCodeEnum(String code)
    {

        //ReturnCodeEnum.values()相当于上面枚举值数组
        for (ReturnCodeEnum element : ReturnCodeEnum.values()) {

            if(element.getCode().equalsIgnoreCase(code))
            {

                return element;
            }
        }
        return null;
    }
    //第二种：遍历枚举V2
    public static ReturnCodeEnum getReturnCodeEnumV2(String code)
    {

        return Arrays.stream(ReturnCodeEnum.values()).filter(x -&gt; x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null);
    }
}
</code></pre>
<pre><code>```prism language-java
//统一返回对象
@Data
@Accessors(chain = true)
public class ResultData&lt;T&gt; {

    private String code;/** 结果状态 ,具体状态码参见枚举类ReturnCodeEnum.java*/
    private String message;
    private T data;
    private long timestamp ;


    public ResultData (){

        this.timestamp = System.currentTimeMillis();    //调用接口的时间
    }

    public static &lt;T&gt; ResultData&lt;T&gt; success(T data) {

        ResultData&lt;T&gt; resultData = new ResultData&lt;&gt;();
        resultData.setCode(ReturnCodeEnum.RC200.getCode());
        resultData.setMessage(ReturnCodeEnum.RC200.getMessage());
        resultData.setData(data);
        return resultData;
    }

    public static &lt;T&gt; ResultData&lt;T&gt; fail(String code, String message) {

        ResultData&lt;T&gt; resultData = new ResultData&lt;&gt;();
        resultData.setCode(code);
        resultData.setMessage(message);
        return resultData;
    }

}
</code></pre>
<pre><code>```prism language-java
//异常统一返回
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 默认全局异常处理。
     * @param e the e
     * @return ResultData
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultData&lt;String&gt; exception(Exception e) {

        System.out.println("----come in GlobalExceptionHandler");
        log.error("全局异常信息exception:{}", e.getMessage(), e);
        return ResultData.fail(ReturnCodeEnum.RC500.getCode(),e.getMessage());
    }
}


//controller中调用
if(id==-1)throw new RuntimeException("id不能为负数");
</code></pre>
<p>RestTemplate是Spring框架中的一个HTTP客户端工具，用于发送HTTP请求并接收响应。它提供了一种简单、方便的方式来调用RESTful风格的Web服务。</p>
<p>使用restTemplate访问restful接口非常的简单。(url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。</p>
<pre><code class="prism language-java">@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {

        return new RestTemplate();
    }
}


//在80端口的controller中调用8001端口
@PostMapping("/consumer/pay/add")
    public ResultData addOrder(payDTO payDTO) {

        return restTemplate.postForObject(paymentSrv_URL+"/pay/add", payDTO, ResultData.class);
    }
</code></pre>
<p>Maven基本命令作用</p>
<pre><code class="prism language-bash">Maven是一个构建和管理项目的工具，它可以简化项目的构建过程，帮助开发者更方便地管理项目的依赖和编译过程。以下是几个常用的Maven命令及其作用：

1. mvn clean：清除项目的target目录及其他生成的文件。             ***

2. mvn compile：编译项目的源代码。

3. mvn test：运行项目的测试代码。

4. mvn package：将项目打包成可执行的jar或war文件。

5. mvn install：将构建的项目发布到本地的Maven仓库，以供其他项目使用。    ***

6. mvn deploy：将构建的项目发布到远程的Maven仓库，以供其他开发者使用。

7. mvn dependency:tree：显示项目的依赖树，包括传递依赖。

8. mvn clean install：清除项目并重新构建、打包、发布到本地仓库。

9. mvn clean package：清除项目并重新构建、打包。

10. mvn clean test：清除项目并重新构建、运行测试。

这些命令可以根据项目的需要进行自由组合，并根据开发者的需求进行扩展和定制。
</code></pre>
<p>对外暴露的通用组件/api/工具类等（如payDTO、统一返回等）可以新建一个通用模块，然后<em>利用Maven的install命令生成jar包，导入其他模块的pom文件</em>即可使用</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141750114</p>]]></description><guid isPermaLink="false">/archives/d97c3ecf-d760-4850-b21e-76fba6ef225a</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:40:59 GMT</pubDate></item><item><title><![CDATA[基于微信小程序的挂号管理系统-web管理端]]></title><link>https://wiki.dgfygk.com/archives/7ff30509-02e1-4141-9e73-f6daea7d260c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8E%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%8C%82%E5%8F%B7%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F-web%E7%AE%A1%E7%90%86%E7%AB%AF&amp;url=/archives/7ff30509-02e1-4141-9e73-f6daea7d260c" width="1" height="1" alt="" style="opacity:0;">
<p>流量和预约挂号数可视化功能
 <br>
 系统具备人流量和预约挂号数的可视化功能，能够实时展示各项数据，通过图表等形式直观呈现。这有助于医疗机构快速了解服务状况，优化资源配置，提升运营效率，为患者提供更为便捷的挂号服务。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F872869f51fee46cf8e984c829ff8b711.png&amp;size=m" alt=""></p>
<p>患者信息管理功能
 <br>
 系统提供了患者信息管理功能，患者信息管理功能全面且高效，支持患者信息的录入、查询、编辑与删除，确保数据准确完整。通过权限管理保护患者隐私，提升医疗效率。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F11ec60a8c0784686bd8f1777de26739e.png&amp;size=m" alt=""></p>
<p>患者预约记录管理功能
 <br>
 系统能能够记录、存储并更新患者的预约信息，包括预约时间、科室及医生等。管理员可轻松查询、编辑和导出预约记录，确保预约流程的顺畅与高效，优化医疗资源配置，提升患者就医体验。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc00890f2f7274555b467b2c47ffebcfd.png&amp;size=m" alt=""></p>
<p>医生信息管理功能
 <br>
 挂号管理后台系统医生信息管理功能完备，支持医生信息的录入、编辑与查询。通过整合医生资源，便于患者选择合适的医生，优化就医体验。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F36fa0a5c784d47d1a54e224648f890f0.png&amp;size=m" alt=""></p>
<p>医生排班管理功能
 <br>
 医生排班管理功能可对三天后的医生排班进行设置。分为全天、上午、和下午三种设置方式。管理者需点击需要排班的医生，然后进行排班设置。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F222994e7616349249a982eb0cc69d79e.png&amp;size=m" alt=""></p>
<p>科室信息管理功能
 <br>
 系统科室信息管理功能全面，可轻松进行科室信息的增删改查。实时更新科室状态，确保信息的准确性，为医生和患者提供便捷的科室查询服务，有助于医疗机构优化资源配置。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1b8fbf82df5740bd81184c9f5a5eecf0.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141789656</p>]]></description><guid isPermaLink="false">/archives/7ff30509-02e1-4141-9e73-f6daea7d260c</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:40:42 GMT</pubDate></item><item><title><![CDATA[基于微信小程序的挂号管理系统-小程序端]]></title><link>https://wiki.dgfygk.com/archives/2432fb89-6fa8-4f3d-9f60-ebc96d8f7252</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8E%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%8C%82%E5%8F%B7%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F-%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%AB%AF&amp;url=/archives/2432fb89-6fa8-4f3d-9f60-ebc96d8f7252" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序端系统功能实现
 <br>
 登录功能
 <br>
 系统登录功能中，用户只需在登录界面输入正确的用户名和密码，即可快速进入系统。登录功能还采用了先进的加密技术，保障用户信息的安全性，让用户能够放心使用。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F31b6eaf03e6940b5be3dde31a256be47.png&amp;size=m" alt=""></p>
<p>注册功能
 <br>
 系统注册功中，用户仅需按照提示填写基本信息，如姓名、手机号、身份证号等，即可完成注册。系统会对输入信息进行实时验证，确保信息的准确性和安全性。注册成功后，用户可自定义用户名和密码，便于后续登录使用。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6fe5dc658fa440d0a0b9aff2f7a7181b.png&amp;size=m" alt=""></p>
<p>预约挂号功能
 <br>
 用户进入预约挂号页面，可以浏览医院提供的医生列表和其对应的排班信息，选择预约挂号的医生和时间，用户可以在系统中查看自己的预约记录，方便随时了解就诊信息。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0d162afab9ce4689835ced9d8915e1fe.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F3e590f3037844eaba8a70decedb57d69.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F90d4c5695838467ebdc83d53e1beccf4.png&amp;size=m" alt=""></p>
<p>查看科室和医生详情功能
 <br>
 查看科室和医生详情功能中，用户可轻松浏览各科室的简介、专业特色及医生团队等信息，全面了解医院的服务能力。系统还展示了医生的姓名、职称、擅长领域等详细资料，方便用户根据需求选择合适的医生进行挂号。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fcb8eea341e724f4faa4cd947d5fc69eb.png&amp;size=m" alt=""></p>
<p>门诊缴费功能
 <br>
 门诊缴费功能中，用户只需在系统中选择相应的缴费项目，便可快速完成支付，无需再到窗口排队等候。该功能支持多种支付方式，操作简便快捷，大大节省了患者的时间。同时，还提供了缴费记录查询功能，方便用户随时查看自己的缴费情况。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F93db9e9996834e07b932aa7b0bd18909.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F75d1872316604c1d9794d346fa2b0690.png&amp;size=m" alt=""></p>
<p>添加就诊卡功能
 <br>
 用户登录微信小程序后，可以添加就诊卡在该页面，用户需输入就诊卡号、姓名、身份证号等信息，并选择绑定该就诊卡的手机号码，用户也可以在个人中心页面查看已绑定的就诊卡信息，也可以对就诊卡进行删除操作。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6996009c45194d8b92ce8f6e389a5922.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe06189016ae748588963f010faaff170.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F8aad2f383a8941e89b3abe7689e0d33d.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fad97ff55778642df8cf0d95bc669b63b.png&amp;size=m" alt=""></p>
<p>健康宣讲功能
 <br>
 系统还提供了健康宣讲功能，该功能为用户提供了一个便捷获取健康知识的平台。会定期发布各类健康资讯和宣讲视频，内容涵盖疾病预防、健康饮食和日常保健等多个方面。用户可随时随地浏览学习，提升自我健康管理意识。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F60fce2575ec3487ca39a217e68f9e0f9.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F43fa77d466cc450d8fcba6ca5c549373.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141789432</p>]]></description><guid isPermaLink="false">/archives/2432fb89-6fa8-4f3d-9f60-ebc96d8f7252</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:40:25 GMT</pubDate></item><item><title><![CDATA[day-38 交错字符串]]></title><link>https://wiki.dgfygk.com/archives/b8b1b884-9e6f-4c0b-801f-be48769858ea</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-38%20%E4%BA%A4%E9%94%99%E5%AD%97%E7%AC%A6%E4%B8%B2&amp;url=/archives/b8b1b884-9e6f-4c0b-801f-be48769858ea" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F560d0e2b5875434790fc8626f7c680f7.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 动态规划，利用一个二维数组，dp[i][j]表示s3前i+j个字符能否用s1前i个字符和s2前j个字符交错形成</p>
<p><strong>解题过程</strong>
 <br>
 转换方程：dp[i][j]=(dp[i-1][j]\&amp;\&amp;s3.charAt(i+j-1)==s1.charAt(i-1))\|\|(dp[i][j-1]\&amp;\&amp;s3.charAt(i+j-1)==s2.charAt(j-1))</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public boolean isInterleave(String s1, String s2, String s3) {

        if(s1.length()+s2.length()!=s3.length())return false;
        int len1=s1.length();
        int len2=s2.length();
        int len3=s3.length();
        boolean ans[][]=new boolean[len1+1][len2+1];
        ans[0][0]=true;
        for(int i=0;i&lt;=len1;i++){

            for(int j=0;j&lt;=len2;j++){

                if(i&gt;0)
                ans[i][j]=ans[i-1][j]&amp;&amp;s3.charAt(i+j-1)==s1.charAt(i-1);
                if(j&gt;0)
                ans[i][j]=ans[i][j]||(ans[i][j-1]&amp;&amp;s3.charAt(i+j-1)==s2.charAt(j-1));
            }
        }
        return ans[len1][len2];
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141168637</p>]]></description><guid isPermaLink="false">/archives/b8b1b884-9e6f-4c0b-801f-be48769858ea</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:40:09 GMT</pubDate></item><item><title><![CDATA[day-39 矩阵中的最大得分]]></title><link>https://wiki.dgfygk.com/archives/22a19d4e-7d32-41ed-bd3b-9ff577b2d075</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-39%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86&amp;url=/archives/22a19d4e-7d32-41ed-bd3b-9ff577b2d075" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F98dd495fe46646bfa1f15517907bf0b0.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 动态规划。利用一个二维数组记录对应位置可以达到的最高得分（位置（0,0）不可能），然后找出最大值即可（如果除了（0,0）上的值其他值都一样，则返回任意一个即可）。</p>
<p><strong>解题过程</strong>
 <br>
 首先算出第一行和第一列的最大得分,其余位置的最高得分只与左边位置和上边位置的最高得分有关联。</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int maxScore(List&lt;List&lt;Integer&gt;&gt; grid) {

        int row=grid.size();
        int col=grid.get(0).size();
        int arr1[][]=new int[row][col];
        for(int i=1;i&lt;row;i++){

            arr1[i][0]=Math.max(grid.get(i).get(0)-grid.get(i-1).get(0),arr1[i-1][0]+grid.get(i).get(0)-grid.get(i-1).get(0));
        }
        for(int i=1;i&lt;col;i++){

            arr1[0][i]=Math.max(grid.get(0).get(i)-grid.get(0).get(i-1),arr1[0][i-1]+grid.get(0).get(i)-grid.get(0).get(i-1));
        }
        for(int i=1;i&lt;row;i++){

            for(int j=1;j&lt;col;j++){

                arr1[i][j]=Math.max(grid.get(i).get(j)-grid.get(i).get(j-1),arr1[i][j-1]+grid.get(i).get(j)-grid.get(i).get(j-1));
                arr1[i][j]=Math.max(arr1[i][j],arr1[i-1][j]+grid.get(i).get(j)-grid.get(i-1).get(j));
                arr1[i][j]=Math.max(arr1[i][j],grid.get(i).get(j)-grid.get(i-1).get(j));
            }
        }
        boolean flag=true;
        int ans=arr1[0][1]-arr1[0][0];
        for(int i=0;i&lt;row;i++){

            for(int j=0;j&lt;col;j++){

                if((i!=0||j!=0)&amp;&amp;arr1[i][j]&gt;ans){

                    ans=arr1[i][j];
                    flag=false;
                }
            }
        }
        return ans;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/maximum-difference-score-in-a-grid/solutions/2881705/ju-zhen-zhong-de-zui-da-de-fen-by-ashi-j-2g15/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141228407</p>]]></description><guid isPermaLink="false">/archives/22a19d4e-7d32-41ed-bd3b-9ff577b2d075</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:40:01 GMT</pubDate></item><item><title><![CDATA[day-40 合并区间]]></title><link>https://wiki.dgfygk.com/archives/86125408-8886-4e44-8049-6e92836eec3a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-40%20%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4&amp;url=/archives/86125408-8886-4e44-8049-6e92836eec3a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7a4c7e9c2893491ea720268fb1663618.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 将二维数组按照第一列升序排列，把intervals[0][0]作为第一个区间的起点，将
 <br>
 intervals[0][1]和intervals[1][0]进行比较，如果intervals[0][1]<intervals[1][0]，则不能合并，否则可以合并，将math.max(intervals[position][1],intervals[position+1][1])作为临时右端点，依次类推< p> </intervals[1][0]，则不能合并，否则可以合并，将math.max(intervals[position][1],intervals[position+1][1])作为临时右端点，依次类推<></p>
<p><strong>解题过程</strong>
 <br>
 注意： 要写为list.add(new ArrayList§)，如果list.add§这样，p.clear()会清空数据</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public  List&lt;List&lt;Integer&gt;&gt; list=new ArrayList&lt;&gt;();
    public  int row;
    public int[][] merge(int[][] intervals) {

        Arrays.sort(intervals,(a,b)-&gt;a[0]-b[0]);//二维数组中每行数组中的第一个数按从小到大的顺序进行排列
        row=intervals.length;
        List&lt;Integer&gt; p=new ArrayList&lt;&gt;();
        p.add(intervals[0][0]);
        method(p,0,intervals);
        int ans[][]=listTo2DArray(list);
        return ans;
    }
    public void method(List&lt;Integer&gt; p,int position,int[][] intervals){

        if(position+1==row){
       //最后一行
            p.add(intervals[position][1]);
            list.add(p);
        }else{

            if(intervals[position][1]&gt;=intervals[position+1][0]){

                intervals[position+1][1]=Math.max(intervals[position][1],intervals[position+1][1]);
                method(p,position+1,intervals);
            }else{

                p.add(intervals[position][1]);
                list.add(new ArrayList(p));
                p.clear();
                p.add(intervals[position+1][0]);
                method(p,position+1,intervals);
            }
        }
    }

    public static int[][] listTo2DArray(List&lt;List&lt;Integer&gt;&gt; listOfLists) {

        int rows = listOfLists.size();
        int cols = 0;
        for (List&lt;Integer&gt; list : listOfLists) {

            cols = Math.max(cols, list.size()); // 获取最大列数
        }

        int[][] twoDimArray = new int[rows][cols];
        for (int i = 0; i &lt; rows; i++) {

            List&lt;Integer&gt; rowList = listOfLists.get(i);
            for (int j = 0; j &lt; rowList.size(); j++) {

                twoDimArray[i][j] = rowList.get(j);
            }
        }
        return twoDimArray;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/merge-intervals/solutions/2893781/he-bing-qu-jian-by-ashi-jian-chong-dan-l-0mr3/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141574941</p>]]></description><guid isPermaLink="false">/archives/86125408-8886-4e44-8049-6e92836eec3a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:39:53 GMT</pubDate></item><item><title><![CDATA[day-40 员工的重要性]]></title><link>https://wiki.dgfygk.com/archives/272ee210-1807-4e1c-967c-087223a7d053</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-40%20%E5%91%98%E5%B7%A5%E7%9A%84%E9%87%8D%E8%A6%81%E6%80%A7&amp;url=/archives/272ee210-1807-4e1c-967c-087223a7d053" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F39568682b66941a48051414677556e93.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用一个哈希表，把员工id作为key值，把员工信息（Employee）作为value值，再利用深度优先遍历即可</p>
<p><strong>解题过程</strong>
 <br>
 把查询员工的重要度加入返回值，再dfs算法将下属员工的重要度加入返回值（如果下属员工还有下属员工，也利用dfs加入，以此类推）</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">/*
// Definition for Employee.
class Employee {
    public int id;
    public int importance;
    public List&lt;Integer&gt; subordinates;
};
*/

class Solution {

    public int getImportance(List&lt;Employee&gt; employees, int id) {

     Map&lt;Integer,Employee&gt; map=new HashMap&lt;&gt;();
     for(Employee e:employees){

        map.put(e.id,e);
     }
     return dfs(map,id);
    }
    public int dfs(Map&lt;Integer,Employee&gt; map,int id){

        Employee e=map.get(id);
        int ans=e.importance;
        for(int i:e.subordinates){

            ans+=dfs(map,i);
        }
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141557208</p>]]></description><guid isPermaLink="false">/archives/272ee210-1807-4e1c-967c-087223a7d053</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:39:43 GMT</pubDate></item><item><title><![CDATA[day-41 零钱兑换]]></title><link>https://wiki.dgfygk.com/archives/ada1cb6e-3752-4002-89e2-b93897519df5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-41%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2&amp;url=/archives/ada1cb6e-3752-4002-89e2-b93897519df5" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F43acd7d718804034b6bf2e99470e0b05.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 动态规划的思想，创建一个长度为amount的数组arr，arr[i]表示当amount=i时的最少硬币数</p>
<p><strong>解题过程</strong>
 <br>
 arr初始化值为Integer.MAX_VALUE，再令arr[0]=0，arr[coins[j]]=1(0&lt;=j&lt;=coins.length),然后i从1向后遍历（i=coins[j]时跳过），当i-coins[j]&gt;0\&amp;\&amp;arr[i-coins[j]]!=Integer.MAX_VALUE时，则arr[i]=Math.min(arr[i],arr[i-coins[j]]+1)
 <br>
 最后如果arr[amount]=Integer.MAX_VALUE，返回-1
 <br>
 飞则返回arr[amount]</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int coinChange(int[] coins, int amount) {

        int arr[]=new int[amount+1];
        Arrays.fill(arr,Integer.MAX_VALUE);
        int len=coins.length;
        arr[0]=0;
        for(int i=0;i&lt;len;i++){

            if(coins[i]&lt;=amount)
            arr[coins[i]]=1;
        }
        for(int i=1;i&lt;=amount;i++){

            if(arr[i]!=1){

                for(int j=0;j&lt;len;j++){

                    if(i-coins[j]&gt;0&amp;&amp;arr[i-coins[j]]!=Integer.MAX_VALUE){

                        arr[i]=Math.min(arr[i],arr[i-coins[j]]+1);
                    }
                }
            }
        }
        if(arr[amount]!=Integer.MAX_VALUE)return arr[amount];
        else return -1;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/coin-change/solutions/2894860/ling-qian-dui-huan-by-ashi-jian-chong-da-m3vm/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141611980</p>]]></description><guid isPermaLink="false">/archives/ada1cb6e-3752-4002-89e2-b93897519df5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:38:49 GMT</pubDate></item><item><title><![CDATA[day-42 分割字符频率相等的最少子字符串]]></title><link>https://wiki.dgfygk.com/archives/2d2758b7-e348-4a7a-b761-6cb30c31531a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-42%20%E5%88%86%E5%89%B2%E5%AD%97%E7%AC%A6%E9%A2%91%E7%8E%87%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%91%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2&amp;url=/archives/2d2758b7-e348-4a7a-b761-6cb30c31531a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4b17a5462b6246e9ba5ae91d42026050.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 动态规划的思想，dp[i]表示从s[0]到s[i]这一子串的最少平衡子串数，当s[i]到s[n-1]是平衡字符串时，dp[i]=dp[j-1]+1,所以状态转换方程为dp[i]=Math.min(dp[j-1]+1)（1&lt;=j&lt;=i）</p>
<p><strong>解题过程</strong>
 <br>
 判断是否为平衡字符串，利用哈希表存储存储每种字符出现的次数。为了判断快速判断所有字符出现的次数是否相等，我们可以维护所有字符出现次数的最大值 maxVal，当满足map.size()*maxVal==i-j+1，所有字符出现的次数相等。</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int minimumSubstringsInPartition(String s) {

        int len=s.length();
        int dp[]=new int[len+1];
        Arrays.fill(dp,Integer.MAX_VALUE);
        dp[0]=0;
        for(int i=1;i&lt;=len;i++){

            Map&lt;Character,Integer&gt; map=new HashMap&lt;&gt;();
            int maxVal=0;
            for(int j=i;j&gt;=1;j--){

                map.put(s.charAt(j-1),map.getOrDefault(s.charAt(j-1),0)+1);
                maxVal=Math.max(maxVal,map.get(s.charAt(j-1)));
                if(map.size()*maxVal==i-j+1&amp;&amp;dp[j-1]!=Integer.MAX_VALUE){
   //是平衡字符串
                dp[i]=Math.min(dp[i],dp[j-1]+1);
                }
            }
        }
        return dp[len];
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/minimum-substring-partition-of-equal-character-frequency/solutions/2896070/fen-ge-zi-fu-pin-lu-xiang-deng-de-zui-sh-v1yo/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141649638</p>]]></description><guid isPermaLink="false">/archives/2d2758b7-e348-4a7a-b761-6cb30c31531a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:38:36 GMT</pubDate></item><item><title><![CDATA[day-43 Z 字形变换]]></title><link>https://wiki.dgfygk.com/archives/8e3ef653-80f5-4f8a-8d79-874c54843f47</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-43%20Z%20%E5%AD%97%E5%BD%A2%E5%8F%98%E6%8D%A2&amp;url=/archives/8e3ef653-80f5-4f8a-8d79-874c54843f47" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc6f08321a8bc435487628b7de96c17dd.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 只需要依次按照行进行遍历即可</p>
<p><strong>解题过程</strong>
 <br>
 第一行位置：0 2*(numRows-1) 4*(numRows-1)
 <br>
 第二行位置：1 2*(numRows-1)-1 2*(numRows-1)+1
 <br>
 。。。。。。
 <br>
 注意：当numRows为1时，直接返回s</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public String convert(String s, int numRows) {

        if(numRows==1)return s;
        int len=s.length();
        String ans=""+s.charAt(0);
        int position=2*(numRows-1);
        while(position&lt;len){

            ans+=s.charAt(position);
            position+=2*(numRows-1);
        }
        for(int i=1;i&lt;=numRows-2;i++){

            position=0;
            while(position&lt;len){

                if(position+i&lt;len)ans+=s.charAt(position+i);
                if((position+2*(numRows-1)-i)&lt;len)ans+=s.charAt(position+2*(numRows-1)-i);
                 position+=2*(numRows-1);
            }
        }
        if(len&gt;=numRows)ans+=s.charAt(numRows-1);
        position=numRows-1+2*(numRows-1);
        while(position&lt;len){

            ans+=s.charAt(position);
            position+=2*(numRows-1);
        }
         return ans;
    }
  }

作者：菜卷
链接：https://leetcode.cn/problems/zigzag-conversion/solutions/2897235/z-zi-xing-bian-huan-by-ashi-jian-chong-d-98uy/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141688128</p>]]></description><guid isPermaLink="false">/archives/8e3ef653-80f5-4f8a-8d79-874c54843f47</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:38:23 GMT</pubDate></item><item><title><![CDATA[day-43 括号生成]]></title><link>https://wiki.dgfygk.com/archives/68b991c3-24f0-4ffa-882f-c66602dcb7b8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-43%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90&amp;url=/archives/68b991c3-24f0-4ffa-882f-c66602dcb7b8" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F944baea1b036416db497cfaf851a8e0d.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 通过深度优先遍历，把所有可能的组合枚举出来，然后依次判断是否符合括号规则，符合则加入链表</p>
<p><strong>解题过程</strong>
 <br>
 判断是否括号规则：第一个位置只能是(，维护一个val值(初始值为0)，遍历字符串，每当加入(时,val–，每当遇到)时，val++。遍历过程中，如果存在val&gt;0，则不满足括号规则。</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution 
    List&lt;String&gt; list=new ArrayList&lt;&gt;();
    public List&lt;String&gt; generateParenthesis(int n) {

        int left=n-1,right=n;
        String s="(";
        dfs(s,left,right);
        return list;
    }
    public void dfs(String s,int left,int right){

        if(left==0&amp;&amp;right==0){

            if(isTrue(s)){

                list.add(s);
                return;
            }
        }
        if(left&gt;0) dfs(s+"(",left-1,right);
        if(right&gt;0) dfs(s+")",left,right-1);
    }
    public boolean isTrue(String s){

        int val=0;
        for(int i=0;i&lt;s.length();i++){

            if(s.charAt(i)=='('){

                val--;
            }
            else val++;
            if(val&gt;0){

               return false;
            }
        }
        return true;
    }

}

作者：菜卷
链接：https://leetcode.cn/problems/generate-parentheses/solutions/2897271/gua-hao-sheng-cheng-by-ashi-jian-chong-d-fecu/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141688710</p>]]></description><guid isPermaLink="false">/archives/68b991c3-24f0-4ffa-882f-c66602dcb7b8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:38:10 GMT</pubDate></item><item><title><![CDATA[day-43 盛最多水的容器]]></title><link>https://wiki.dgfygk.com/archives/8331dd28-e3ef-4b39-bed8-965922e247d9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-43%20%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8&amp;url=/archives/8331dd28-e3ef-4b39-bed8-965922e247d9" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F967d24bc962a456ba772b26b300b1e80.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 双指针：首先令i=0,j=height.length-1,选取短板（即Math.min(height[i],height[j])）,然后将短板向内移动，直达i&gt;=j即可得到答案。</p>
<p><strong>解题过程</strong>
 <br>
 短板向内移动：水的容量可能增大
 <br>
 长板向内移动：水的容量不可能增大
 <br>
 所以只需要维护一个最大值max，通过短板的不断内移，维护max=Math.max(max,Math.min(height[i],height[j])*(j-i))即可。</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

    public int maxArea(int[] height) {

        int len=height.length;
        int i=0;
        int j=len-1;
        int max=Math.min(height[i],height[j])*(j-i);
        while(i&lt;j){

            if(height[i]&lt;=height[j]){

                i++;
                max=Math.max(max,Math.min(height[i],height[j])*(j-i));
            }else{

                j--;
                max=Math.max(max,Math.min(height[i],height[j])*(j-i));
            }
        }
        return max;
    }
}

作者：菜卷
链接：https://leetcode.cn/problems/container-with-most-water/solutions/2897259/sheng-zui-duo-shui-de-rong-qi-by-ashi-ji-7w2y/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141688424</p>]]></description><guid isPermaLink="false">/archives/8331dd28-e3ef-4b39-bed8-965922e247d9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:38:00 GMT</pubDate></item><item><title><![CDATA[算法笔记2]]></title><link>https://wiki.dgfygk.com/archives/3b6d0923-5929-45e4-a6e9-f940e593a1a1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%AE%97%E6%B3%95%E7%AC%94%E8%AE%B02&amp;url=/archives/3b6d0923-5929-45e4-a6e9-f940e593a1a1" width="1" height="1" alt="" style="opacity:0;">
<p><strong>1.判断平衡子串</strong>
 <br>
 判断是否为平衡字符串，利用哈希表存储存储每种字符出现的次数。为了判断快速判断所有字符出现的次数是否相等，我们可以维护所有字符出现次数的最大值 maxVal，当满足map.size()*maxVal==i-j+1，所有字符出现的次数相等。</p>
<pre><code class="prism language-java">int maxVal=0;
for(int i=0;i&lt;s.length();i++){

    map.put(s.charAt(i),map.getOrDefault(s.charAt(i),0)+1); //存储出现次数
    maxVal=Math.max(maxVal,map.get(s.charAt(i)));
    if(map.size()*maxVal==i+1){
   //是平衡字符串
   //需要进行的操作
}
</code></pre>
<p><strong>2.判断回文字符串</strong></p>
<pre><code class="prism language-java">public boolean isTrue(String str) {

        int len = str.length();
        for (int i = 0; i &lt; len / 2; i++) {

            if (str.charAt(i) != str.charAt(len - 1 - i)) {

                return false;
            }
        }
        return true;
    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/141649678</p>]]></description><guid isPermaLink="false">/archives/3b6d0923-5929-45e4-a6e9-f940e593a1a1</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:37:50 GMT</pubDate></item><item><title><![CDATA[day-37 最大正方形]]></title><link>https://wiki.dgfygk.com/archives/dbfbb429-b393-44e8-b5f6-829a5242cb7b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-37%20%E6%9C%80%E5%A4%A7%E6%AD%A3%E6%96%B9%E5%BD%A2&amp;url=/archives/dbfbb429-b393-44e8-b5f6-829a5242cb7b" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe92ca56d0f30761903193f9432d680f9.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 动态规划，这题主要得弄明白状态转换方程，dp[i][j]表示以（i,j）为右下角的最大正方形</p>
<p><strong>解题方法</strong>
 <br>
 1.首先将第一行和第一列初始化，当对应位置的matrix为'0'时，dp数组对应位置也为零，否则为1
 <br>
 2.对剩下其他位置进行遍历，若对应位置的matrix为'0'时，dp数组对应位置也为零，若不为'0'，则为dp[i][j]=Math.min(dp[i-1][j],Math.min(dp[i-1][j-1],dp[i][j-1]))+1
 <br>
 3.最后dp数组中最大值的平方即为答案</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

    public int maximalSquare(char[][] matrix) {

        int ans=0;
        int row=matrix.length;
        int cols=matrix[0].length;
        int arr[][]=new int[row][cols];
        for(int i=0;i&lt;row;i++){

            if(matrix[i][0]=='0')arr[i][0]=0;
            else{

                arr[i][0]=1;
                ans=1;
                }
        }
        for(int i=0;i&lt;cols;i++){

            if(matrix[0][i]=='0')arr[0][i]=0;
            else{

                arr[0][i]=1;
                ans=1;
            } 
        }
        for(int i=1;i&lt;row;i++){

            for(int j=1;j&lt;cols;j++){

                if(matrix[i][j]=='0'){

                    arr[i][j]=0;
                }else{

                    arr[i][j]=arr[i][j]=Math.min(arr[i-1][j],Math.min(arr[i-1][j-1],arr[i][j-1]))+1;
                }
                ans=Math.max(arr[i][j],ans);
            }
        }
        return ans*ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139398353</p>]]></description><guid isPermaLink="false">/archives/dbfbb429-b393-44e8-b5f6-829a5242cb7b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:37:40 GMT</pubDate></item><item><title><![CDATA[day-35 被围绕的区域]]></title><link>https://wiki.dgfygk.com/archives/592409f5-5cc3-4774-8301-b3e2cab45da5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-35%20%E8%A2%AB%E5%9B%B4%E7%BB%95%E7%9A%84%E5%8C%BA%E5%9F%9F&amp;url=/archives/592409f5-5cc3-4774-8301-b3e2cab45da5" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F93b022bb201ad9a04fe6c476e54aa850.png&amp;size=m" alt=""></p>
<p><strong>思路</strong>
 <br>
 很明显，只有与边界上的O连接的O才不会X覆盖</p>
<p><strong>解题方法</strong>
 <br>
 检测边界上的字符，如果是O则向周围探测，访问与之连接的不会被覆盖的X。边界探测结束后，没有访问过的O皆会被X覆盖</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int dir[][]={
   {
   0,1},{
   0,-1},{
   1,0},{
   -1,0}};
    public boolean vis[][];
    public char[][] map;
    public int m;
    public int n;
    public void solve(char[][] board) {

        m=board.length;
        n=board[0].length;
        map=board;
        vis=new boolean[m][n];
        for(int i=0;i&lt;m;i++){

            dfs(i,0);
            dfs(i,n-1);
        }
        for(int i=1;i&lt;n-1;i++){

            dfs(0,i);
            dfs(m-1,i);
        }
        for(int i=0;i&lt;m;i++){

            for(int j=0;j&lt;n;j++){

                board[i][j]='X';
            }
        }
        for(int i=0;i&lt;m;i++){

            for(int j=0;j&lt;n;j++){

                if(map[i][j]=='X'&amp;&amp;vis[i][j]){

                    board[i][j]='O';
                }
            }
        }
    }
    void dfs(int x,int y){

        if(map[x][y]=='X') return;
        vis[x][y]=true;
        for(int i=0;i&lt;4;i++){

            int newx=x+dir[i][0];
            int newy=y+dir[i][1];
            if(0&lt;=newx&amp;&amp;newx&lt;m&amp;&amp;0&lt;=newy&amp;&amp;newy&lt;n&amp;&amp;map[newx][newy]=='O'&amp;&amp;
            !vis[newx][newy]){

                dfs(newx,newy);
            }
        }
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138792087</p>]]></description><guid isPermaLink="false">/archives/592409f5-5cc3-4774-8301-b3e2cab45da5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:37:31 GMT</pubDate></item><item><title><![CDATA[day-35 二叉树的右视图]]></title><link>https://wiki.dgfygk.com/archives/1578cdbe-61c9-400a-bc28-a4462f882cb8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-35%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%B3%E8%A7%86%E5%9B%BE&amp;url=/archives/1578cdbe-61c9-400a-bc28-a4462f882cb8" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff499a2a9bfad0eb5992f9d57267a63eb.png&amp;size=m" alt=""></p>
<p><strong>思路</strong>
 <br>
 根据层序遍历的思路。将每一层的最右边元素加入返回序列即可</p>
<p><strong>解题方法</strong>
 <br>
 注意：链表删除一个数据后会立即重排，所以删除同一层的节点时，每次都删除第一个节点。</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    public List&lt;Integer&gt; rightSideView(TreeNode root) {

        List&lt;Integer&gt; ans=new ArrayList&lt;&gt;();
        if(root==null) return ans;
        List&lt;TreeNode&gt; queue=new ArrayList&lt;&gt;();
        queue.add(root);
        while(queue.size()&gt;0){

            int size=queue.size();
            ans.add(queue.get(size-1).val);
            for(int i=0;i&lt;size;i++){

                if(queue.get(i).left!=null)queue.add(queue.get(i).left);
                if(queue.get(i).right!=null)queue.add(queue.get(i).right);
            }
            for(int i=0;i&lt;size;i++){

                queue.remove(0);
            }
        }
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138786818</p>]]></description><guid isPermaLink="false">/archives/1578cdbe-61c9-400a-bc28-a4462f882cb8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:35:52 GMT</pubDate></item><item><title><![CDATA[day-36 删除链表的倒数第 N 个结点]]></title><link>https://wiki.dgfygk.com/archives/5b6c2372-e76d-4d64-aa98-a9416059bb70</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-36%20%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%AC%20N%20%E4%B8%AA%E7%BB%93%E7%82%B9&amp;url=/archives/5b6c2372-e76d-4d64-aa98-a9416059bb70" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fed46b6c77616455fb6e38751608b3e63.png&amp;size=m" alt=""></p>
<p><strong>思路</strong>
 <br>
 首先计算出链表的长度，然后删除第n个节点即可，但要注意考虑特殊情况</p>
<p><strong>解题方法</strong>
 <br>
 特殊情况：1.删除节点为最后一个节点
 <br>
 2.删除节点为头结点</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode removeNthFromEnd(ListNode head, int n) {

        ListNode t=head;
        int len=0;
        while(t!=null){

            len++;
            t=t.next;
        }
        if(n==len) return head.next;
        t=head;
        for(int i=1;i&lt;len-n;i++){

            t=t.next;
        }
        if(t.next==null){

            t=null;
        }else if(t.next.next!=null){

            t.next=t.next.next;
        }else{

            t.next=null;
        }
        return head;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139375179</p>]]></description><guid isPermaLink="false">/archives/5b6c2372-e76d-4d64-aa98-a9416059bb70</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:35:41 GMT</pubDate></item><item><title><![CDATA[day-37 电话号码的字母组合]]></title><link>https://wiki.dgfygk.com/archives/0a97d580-4bc1-4d7e-a8f0-8376c711e624</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-37%20%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88&amp;url=/archives/0a97d580-4bc1-4d7e-a8f0-8376c711e624" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4bd03ee6a89e1ccdf90fb691d1186515.png&amp;size=m" alt=""></p>
<p><strong>思路</strong>
 <br>
 设置一个char型的二维数组，每次从号码对应的子母中选出一个，拼接在一起即可</p>
<p><strong>解题方法</strong>
 <br>
 注意：有的数字对应三个字母，有的对应四个字母</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public char arr[][]={
   {
   'a','b','c',' '},{
   'd','e','f',' '},{
   'g','h','i',' '},
    {
   'j','k','l',' '},{
   'm','n','o',' '},{
   'p','q','r','s'},{
   't','u','v',' '},
    {
   'w','x','y','z'}};
    public List&lt;String&gt; ans=new ArrayList&lt;String&gt;();
    public int len;
    public List&lt;String&gt; letterCombinations(String digits) {

        if(digits.isEmpty())return ans;
        len=digits.length();
        dfs(0,digits,"");
        return ans;
    }
    public void dfs(int i,String digits,String t){

        if(i==len){

            ans.add(t);
            return;
        }
        int row=digits.charAt(i)-'2';
        int col;
        if(arr[row][3]==' ')col=3;
        else col=4;

        for(int j=0;j&lt;col;j++){

            dfs(i+1,digits,t+arr[row][j]);
        }
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139390995</p>]]></description><guid isPermaLink="false">/archives/0a97d580-4bc1-4d7e-a8f0-8376c711e624</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:35:26 GMT</pubDate></item><item><title><![CDATA[Git-01]]></title><link>https://wiki.dgfygk.com/archives/590f5204-0a92-40a5-a5d3-2c78a94ef88a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Git-01&amp;url=/archives/590f5204-0a92-40a5-a5d3-2c78a94ef88a" width="1" height="1" alt="" style="opacity:0;">
<p>Git是一个免费且开源的分布式版本控制系统，它可以跟踪文件的修改、记录变更的历史，并且在多人协作开发中提供了强大的工具和功能。
 <br>
 Git最初是由Linus Torvalds开发的，用于Linux内核的开发，现在已经成为了广泛使用的版本控制系统，被许多项目和开发者使用。
 <br>
 Git的特点包括：</p>
<ol>
 <li>分布式：每个开发者都拥有完整的项目副本，可以离线工作并且彼此之间无需依赖中央服务器。</li>
 <li>高效：Git对代码的存储和传输进行了优化，使得提交、拉取和推送等操作非常快速。</li>
 <li>强大的分支管理：Git支持创建和管理多个分支，开发者可以方便地并行进行不同功能的开发，而不会影响其他分支。</li>
 <li>完整的历史记录：Git保存了项目的完整历史记录，开发者可以轻松地查看每次提交的差异并进行回滚操作。</li>
 <li>基于内容的存储：Git不仅仅是按照文件进行版本控制，它会对文件的内容进行哈希计算，确保文件的完整性。</li>
 <li>支持大型项目：Git可以处理非常大的项目和文件，而不会出现性能问题。
  <br>
  Git的使用非常广泛，它不仅可以用于个人开发，还可以用于团队协作和开源项目的管理。它提供了许多命令行工具和图形界面工具，使得开发者可以根据自己的喜好选择适合自己的方式来使用Git。</li>
</ol>
<p>SVN是<strong>集中式版本控制</strong>系统，版本库是集中放在中央服务器的，而工作的时候，用的都是自己的电脑，所以首先要从中央服务器得到最新的版本，然后工作，完成工作后，需要把自己做完的活推送到中央服务器。集中式版本控制系统是必须联网才能工作，对网络带宽要求较高。</p>
<p>Git是<strong>分布式版本控制</strong>系统，没有中央服务器，每个人的电脑就是一个完整的版本库，工作的时候不需要联网了，因为版本都在自己电脑上。协同的方法是这样的：比如说自己在电脑上改了文件A，其他人也在电脑上改了文件A，这时，你们两之间只需把各自的修改推送给对方，就可以互相看到对方的修改了。Git可以直接看到更新了哪些代码和文件。</p>
<p>Git Bash：Unix与Linux风格的命令行，使用最多，推荐最多
 <br>
 Git CMD：Windows风格的命令行
 <br>
 Git GUI：图形界面的Git</p>
<p>1.Git\etc\gitconfig ：Git 安装目录下的 gitconfig –system 系统级</p>
<p>2.C:\Users\Lenovo\ .gitconfig 只适用于当前登录用户的配置 –global 全局
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F82a39b3650f5c1b20dde4afafd7105c2.png&amp;size=m" alt="">
 <br>
 Git本地有三个工作区域：工作目录（Working Directory）、暂存区(Stage/Index)、资源库(Repository或Git Directory)。如果在加上远程的git仓库(Remote Directory)就可以分为四个工作区域。</p>
<p>Workspace：工作区，就是平时存放项目代码的地方
 <br>
 Index / Stage：暂存区，用于临时存放你的改动，事实上它只是一个文件，保存即将提交到文件列表信息
 <br>
 Repository：仓库区（或本地仓库），就是安全存放数据的位置，这里面有提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
 <br>
 Remote：远程仓库，托管代码的服务器，可以简单的认为是项目组中的一台电脑用于远程数据交换</p>
<p>Workspace-&gt;(add)-&gt;Index / Stage-&gt;(commit)-&gt;Repository-&gt;(push)-&gt;Remote</p>
<p><strong>Git项目搭建</strong>
 <br>
 1.本地创建
 <br>
 在当前目录新建一个Git代码库 git init</p>
<p>2.克隆远程仓库
 <br>
 克隆一个项目和它的整个代码历史(版本信息) git clone [https://gitee.com/dai-mhbfy/git-base.git]</p>
<p><strong>Untracked:</strong> 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged.</p>
<p><strong>Unmodify:</strong> 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified. 如果使用git rm移出版本库, 则成为Untracked文件</p>
<p><strong>Modified:</strong> 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用git checkout 则丢弃修改过, 返回到unmodify状态, 这个git checkout即从库中取出文件, 覆盖当前修改</p>
<p><strong>Staged:</strong> 暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存, 文件状态为Modified</p>
<pre><code class="prism language-bash">#查看指定文件状态
git status [filename]

#查看所有文件状态
git status

# git add .                  添加所有文件到暂存区
# git commit -m "消息内容"    提交暂存区中的内容到本地仓库 -m 提交信息
</code></pre>
<p>如果我们不想把某些文件纳入版本控制中，比如数据库文件，临时文件，设计文件等，则可以在主目录下建立”.gitignore"文件</p>
<p>ssh秘钥位置 C:\Users\Lenovo.ssh</p>
<p>Git分支常用命令</p>
<pre><code class="prism language-bash">创建分支：git branch &lt;branch-name&gt;

例如：git branch feature
切换分支：git checkout &lt;branch-name&gt;

例如：git checkout feature
创建并切换到新分支：git checkout -b &lt;branch-name&gt;

例如：git checkout -b feature
查看分支：git branch

加上 -a 选项可以查看所有分支，包括远程分支
删除分支：git branch -d &lt;branch-name&gt;

例如：git branch -d feature
强制删除分支：git branch -D &lt;branch-name&gt;

例如：git branch -D feature
合并分支：git merge &lt;branch-name&gt;

例如：git merge feature
重新命名分支：git branch -m &lt;old-branch-name&gt; &lt;new-branch-name&gt;

例如：git branch -m feature new-feature
查看某个分支的提交历史：git log &lt;branch-name&gt;

例如：git log feature
查看分支最近的一个提交：git branch -v
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139226036</p>]]></description><guid isPermaLink="false">/archives/590f5204-0a92-40a5-a5d3-2c78a94ef88a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:35:17 GMT</pubDate></item><item><title><![CDATA[Java基础-多线程]]></title><link>https://wiki.dgfygk.com/archives/9aef028a-93d0-4443-952a-b472e8286c32</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%9F%BA%E7%A1%80-%E5%A4%9A%E7%BA%BF%E7%A8%8B&amp;url=/archives/9aef028a-93d0-4443-952a-b472e8286c32" width="1" height="1" alt="" style="opacity:0;">
<p><strong>线程</strong>
 <br>
 Java多线程指的是在Java编程语言中同时执行多个线程的能力。Java中的多线程是通过Thread类和Runnable接口实现的。</p>
<p>通过以下代码运行可发现，线程间交替执行</p>
<pre><code class="prism language-java">//创建线程方式1：继承Thread类，重写run()方法，调用start开启线程
public class testThread1 extends Thread{

    public static void main(String[] args){

        testThread1 test1=new testThread1();
        test1.start();
        for(int i=0;i&lt;10000;i++){

            System.out.println(i);
        }
    }
    @Override
    public void run() {

        for(int i=0;i&lt;10000;i++){

            System.out.println("线程"+i);
        }
    }
}
</code></pre>
<pre><code>```prism language-java
//创建线程方式1：实现Runnable接口类，重写run()方法，执行线程需要丢入Runnable接口实现类，调用start开启线程         【推荐】
public class testThread2 implements Runnable{

    public static void main(String[] args){

        testThread2 test1=new testThread2();
        new Thread(test1).start();
        for(int i=0;i&lt;10000;i++){

            System.out.println(i);
        }

    }
    @Override
    public void run() {

        for(int i=0;i&lt;10000;i++){

            System.out.println("线程"+i);
        }
    }
}
</code></pre>
<p>Java线程方法包括以下几种常用的方法：</p>
<pre><code class="prism language-java">1. start(): 启动线程，使其进入可运行状态。

2. run(): 线程的主体方法，包含线程要执行的代码。

3. sleep(long millisecond): 让线程休眠指定的时间，单位为毫秒。

4. join(): 等待线程结束，主线程会暂停等待，直到被调用线程执行完毕。

5. interrupt(): 中断线程，发送一个中断信号，使线程退出阻塞状态。

6. isAlive(): 判断线程是否存活，即线程是否处于可运行状态。

7. yield(): 暂停当前线程，使其他线程有机会执行。

8. setPriority(int priority): 设置线程的优先级，优先级范围为1-10，默认为5。

9. getId(): 获取线程的唯一标识符。

10. isInterrupted(): 判断线程是否被中断，返回一个布尔值。

11. getName(): 获取线程的名字。

12. currentThread(): 返回当前执行的线程对象。

13. setDaemon(boolean on): 设置线程是否为守护线程，守护线程会在所有非守护线程结束后自动结束。

14. wait(): 线程进入等待状态，直到被其他线程唤醒。

15. notify(): 唤醒一个等待中的线程。

16. notifyAll(): 唤醒所有等待中的线程。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139365879</p>]]></description><guid isPermaLink="false">/archives/9aef028a-93d0-4443-952a-b472e8286c32</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:35:05 GMT</pubDate></item><item><title><![CDATA[Redis-02]]></title><link>https://wiki.dgfygk.com/archives/c7f501b1-6bd4-4e34-970c-acc5d85df245</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis-02&amp;url=/archives/c7f501b1-6bd4-4e34-970c-acc5d85df245" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="prism language-bash">redis安装包位置  /opt/redis-7.2.5
redis默认安装路径：
配置文件路径：/usr/local/bin/redisconfig
gcc安装位置     /opt/rh

windows在D:\bs\code\Redis\Redis-x64-3.2.100目录下 
启动：进入redis安装目录---&gt;cmd打开---&gt;输入"redis-server.exe redis.windows.conf"命令---&gt;双击安装目录下的"redis-cli.exe"文件，打开redis客户端。

redis启动：
在/usr/local/bin目录下输入redis-server redisconfig/redis.conf
                         redis-cli -p 6379
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe78d9177fd13ab3d231ebe1b6bfc76d5.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2ff17ddfa9546a75bd3d171c10783259.png&amp;size=m" alt="">
 <br>
 redis性能测试命令</p>
<pre><code class="prism language-java">redis-benchmark [option] [option value]

redis-benchmark -h localhost -p 6379 -c 2 -n 10
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F88b85412a25d3f23b0b3f98cddb138b6.png&amp;size=m" alt=""></p>
<p>参考的安装教程：https://blog.csdn.net/qq_52227892/article/details/130649748</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139390971</p>]]></description><guid isPermaLink="false">/archives/c7f501b1-6bd4-4e34-970c-acc5d85df245</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:34:53 GMT</pubDate></item><item><title><![CDATA[Redis-03]]></title><link>https://wiki.dgfygk.com/archives/a8ad2321-2aba-4536-9c42-4ee53f07f6f3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis-03&amp;url=/archives/a8ad2321-2aba-4536-9c42-4ee53f07f6f3" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Redis常用命令</strong></p>
<pre><code class="prism language-java">1. SET key value: 设置指定 key 的值为 value。
2. GET key: 获取指定 key 的值。
3. DEL key: 删除指定 key 及其对应的值。
4. KEYS pattern: 查找所有符合给定 pattern 的 key。
5. EXISTS key: 判断指定 key 是否存在。
6. INCR key: 将指定 key 的值增加 1。
7. DECR key: 将指定 key 的值减少 1。
8. EXPIRE key seconds: 设置指定 key 的过期时间，单位为秒。
9. TTL key: 获取指定 key 的剩余过期时间，单位为秒。
10. HSET key field value: 设置指定 key 的哈希表中指定字段的值。
11. HGET key field: 获取指定 key 的哈希表中指定字段的值。
12. LPUSH key value: 将指定 value 插入到列表的头部。
13. RPUSH key value: 将指定 value 插入到列表的尾部。
14. LPOP key: 移除并返回列表头部的元素。
15. RPOP key: 移除并返回列表尾部的元素。
16. SADD key member: 将指定 member 添加到集合中。
17. SMEMBERS key: 返回集合中的所有成员。
18. ZADD key score member: 将指定 member 添加到有序集合中，并给定一个分数。
19. ZRANGE key start stop: 返回有序集合中指定范围的成员。
20. SCAN cursor [MATCH pattern] [COUNT count]: 遍历整个数据库，返回所有符合给定 pattern 的 key。
</code></pre>
<p>Redis有16个数据库，编号从0到15。可以使用SELECT命令来切换数据库，例如SELECT 2就会切换到数据库2。 默认0号数据库</p>
<p>Redis是一个基于<em>内存</em> 的数据存储系统，采用<em>单线程</em>的方式处理客户端请求。</p>
<p><strong>Redis常见数据类型</strong> 5+3</p>
<pre><code class="prism language-bash">字符串（String）：最基本的数据类型，可以存储字符串、整数或浮点数。

列表（List）：按照插入顺序存储的字符串列表，可以按照索引访问和操作列表中的元素。

哈希（Hash）：类似于字典，存储键值对，可以按照键名访问和操作哈希中的键值对。

集合（Set）：无序且唯一的字符串集合，支持对集合进行交集、并集、差集等操作。

有序集合（Sorted Set）：类似于Set，每个元素都关联了一个分数（score），可以按照分数排序，并且每个元素是唯一的。

Bitmap：位图数据类型，可以对每个位进行操作和查询。

HyperLogLog：基数估计算法，用于估计集合中的元素数量。

地理位置（Geospatial）：可以存储地理位置坐标，并支持查询附近的位置。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139393878</p>]]></description><guid isPermaLink="false">/archives/a8ad2321-2aba-4536-9c42-4ee53f07f6f3</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:34:43 GMT</pubDate></item><item><title><![CDATA[Redis-04]]></title><link>https://wiki.dgfygk.com/archives/d48d7384-fe00-4215-aaa9-23d15d7a94ca</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis-04&amp;url=/archives/d48d7384-fe00-4215-aaa9-23d15d7a94ca" width="1" height="1" alt="" style="opacity:0;">
<p><strong>String类型</strong></p>
<pre><code class="prism language-bash">String类型可以存储字符串、整数或浮点数。
在Redis中，String类型的数据可以进行以下操作：

SET key value：将键key的值设为value。
GET key：获取键key的值。
INCR key：将键key的值按照整数递增1。
DECR key：将键key的值按照整数递减1。
INCRBY key increment：将键key的值按照指定的增量increment递增。
DECRBY key decrement：将键key的值按照指定的减量decrement递减。
APPEND key value：将value追加到键key的值末尾。
STRLEN key：获取键key的值的长度。
SETEX key seconds value：设置键key的值，并指定过期时间（以秒为单位）。
GETSET key value：将键key的值设置为value，并返回旧的值。
MSET key value [key value ...]：同时设置多个键值对。
MGET key [key ...]：同时获取多个键的值。
</code></pre>
<p><strong>List类型</strong></p>
<pre><code class="prism language-bash">Redis的List类型是一个链表结构，可以进行插入、获取、修改和删除等操作。常用的命令包括：

LPUSH：在列表的头部插入一个或多个元素
RPUSH：在列表的尾部插入一个或多个元素
LPOP：移除并返回列表的头部元素
RPOP：移除并返回列表的尾部元素
LINDEX：返回列表中指定位置的元素
LRANGE：返回列表中指定范围内的元素
LLEN：返回列表的长度
LREM：根据元素值移除列表中的元素
LINSERT：在列表中指定元素之前或之后插入新元素
LSET：设置列表中指定位置的元素的值
LTRIM：修剪列表，只保留指定范围内的元素
RPOPLPUSH：移除列表的尾部元素，并添加到另一个列表的头部
</code></pre>
<p><strong>Set类型</strong></p>
<pre><code class="prism language-bash">Set中不允许相同的值，常用的Redis Set类型的命令包括：

SADD key member1 [member2 ...]：向集合key中添加一个或多个成员。
SREM key member1 [member2 ...]：从集合key中移除一个或多个成员。
SMEMBERS key：返回集合key中的所有成员。
SCARD key：返回集合key中的成员数量。
SISMEMBER key member：判断member是否是集合key的成员，返回1表示是，返回0表示不是。
SRANDMEMBER key [count]：从集合key中返回随机的一个成员，如果指定count参数，则返回count个随机成员。
SPOP key [count]：从集合key中移除并返回随机的一个或多个成员。
SINTER key1 [key2 ...]：返回给定集合key1, key2, ...之间的交集。
SUNION key1 [key2 ...]：返回给定集合key1, key2, ...之间的并集。
SDIFF key1 [key2 ...]：返回给定集合key1, key2, ...之间的差集。
</code></pre>
<p><strong>Hash类型</strong></p>
<pre><code class="prism language-bash">Redis Hash类型是一种存储键值对的数据结构，常用的命令包括：

HSET key field value：设置哈希表 key 中的字段 field 的值为 value。
HGET key field：获取哈希表 key 中字段 field 的值。
HDEL key field1 [field2 ...]：删除哈希表 key 中的一个或多个字段。
HGETALL key：获取哈希表 key 中所有的字段和值。
HINCRBY key field increment：将哈希表 key 中字段 field 的值增加给定的增量 increment。
HKEYS key：获取哈希表 key 中所有字段的名字。
HVALS key：获取哈希表 key 中所有字段的值。
HEXISTS key field：检查哈希表 key 中是否存在字段 field。
HLEN key：获取哈希表 key 中字段的数量。
HMSET key field1 value1 [field2 value2 ...]：同时设置多个字段的值到哈希表 key 中。
HMGET key field1 [field2 ...]：获取所有给定字段的值。
HSCAN key cursor [MATCH pattern] [COUNT count]：迭代哈希表 key 中的键值对。
</code></pre>
<p><strong>Sorted Set类型</strong></p>
<pre><code class="prism language-bash">Redis 的有序集合类型（sorted set）是一种存储有序数据的数据结构。它是基于哈希表和跳跃表（skip list）实现的，可以在 O(log(N)) 的时间复杂度内完成插入、删除、查找等操作。以下是一些常用的 Redis 有序集合命令：     会自动排序

ZADD：将一个或多个成员元素及其分数值添加到有序集合中。
ZREM：从有序集合中移除一个或多个成员元素。
ZRANGE：通过索引范围获取有序集合中的成员元素列表。
ZRANK：获取有序集合中成员的排名（按分数值从低到高）。
ZREVRANK：获取有序集合中成员的排名（按分数值从高到低）。
ZSCORE：获取有序集合中指定成员的分数值。
ZCOUNT：计算有序集合中指定分数范围内的成员数量。
ZINCRBY：将有序集合中指定成员的分数值增加一个给定的增量。
ZREVRANGE：通过索引范围获取有序集合中的成员元素列表（按分数值逆序）。
ZCARD：获取有序集合的成员数量。
ZRANGEBYSCORE：通过分数范围获取有序集合中的成员元素列表。
ZREMRANGEBYRANK：移除有序集合中指定排名范围内的成员元素。
ZREMRANGEBYSCORE：移除有序集合中指定分数范围内的成员元素。
</code></pre>
<p><strong>Geospatial类型</strong></p>
<pre><code class="prism language-bash">Redis的geospatial类型常用命令如下：

GEOADD key longitude latitude member [longitude latitude member ...] 将一个或多个经纬度的成员添加到指定的key中。
GEODIST key member1 member2 [unit] 计算两个成员之间的距离，默认返回的距离单位是米，可以使用可选的unit参数来指定距离单位。
GEOHASH key member [member ...] 返回一个或多个成员的geohash表示。
GEOPOS key member [member ...] 返回一个或多个成员的经纬度。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] 查找给定经纬度范围内的成员，可以选择返回成员的经纬度、距离和geohash值。
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] 查找给定成员周围的成员，可以选择返回成员的经纬度、距离和geohash值。
GEODEL key member [member ...] 删除指定key中的一个或多个成员。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/139409264</p>]]></description><guid isPermaLink="false">/archives/d48d7384-fe00-4215-aaa9-23d15d7a94ca</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:34:26 GMT</pubDate></item><item><title><![CDATA[day-33 收集垃圾的最少总时间]]></title><link>https://wiki.dgfygk.com/archives/e1818d3c-7dfd-47d1-8c8f-e35cd57e9890</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-33%20%E6%94%B6%E9%9B%86%E5%9E%83%E5%9C%BE%E7%9A%84%E6%9C%80%E5%B0%91%E6%80%BB%E6%97%B6%E9%97%B4&amp;url=/archives/e1818d3c-7dfd-47d1-8c8f-e35cd57e9890" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fffad27a5e317198a41081c5d1b56b4ac.png&amp;size=m" alt=""></p>
<p><strong>思路</strong>
 <br>
 利用一个二维数组（数组行数为3，分别对应三种垃圾）记录垃圾数量，arr[0][i]表示第i个房子的金属、纸和或玻璃垃圾。</p>
<p><strong>解题方法</strong>
 <br>
 将三种垃圾数量（值与时间相同）相加，最后对应垃圾车最远需要走到哪个房间，加上对应的花费时间数</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int garbageCollection(String[] garbage, int[] travel) {

        int n=garbage.length;
        int arr[][]=new int[3][n];
        int ans=0;
        for(int i=0;i&lt;n;i++){

            int len=garbage[i].length();
            int M=0,P=0,G=0;
            for(int j=0;j&lt;len;j++){

                if(garbage[i].charAt(j)=='M')M++;
                if(garbage[i].charAt(j)=='P')P++;
                if(garbage[i].charAt(j)=='G')G++;
            }
            ans+=M+P+G;
            arr[0][i]=M;
            arr[1][i]=P;
            arr[2][i]=G;
        }
        for(int i=n-1;i&gt;0;i--){

          if(arr[0][i]!=0){

            for(int j=i-1;j&gt;=0;j--){

                ans+=travel[j];
            }
            break;
          }
        }
        for(int i=n-1;i&gt;0;i--){

          if(arr[1][i]!=0){

            for(int j=i-1;j&gt;=0;j--){

                ans+=travel[j];
            }
            break;
          }
        }
        for(int i=n-1;i&gt;0;i--){

          if(arr[2][i]!=0){

            for(int j=i-1;j&gt;=0;j--){

                ans+=travel[j];
            }
            break;
          }
        }
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138722217</p>]]></description><guid isPermaLink="false">/archives/e1818d3c-7dfd-47d1-8c8f-e35cd57e9890</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:34:16 GMT</pubDate></item><item><title><![CDATA[day-30 三角形最小路径和]]></title><link>https://wiki.dgfygk.com/archives/48acfdb1-be6a-4586-a790-a26ab6f2eaf2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-30%20%E4%B8%89%E8%A7%92%E5%BD%A2%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C&amp;url=/archives/48acfdb1-be6a-4586-a790-a26ab6f2eaf2" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F500f646ca0c173b7c4e44371f3587eda.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 典型的动态规划问题,状态方程可以理解为min[i][j]=min[i][j]+Math.min(min[i-1][j-1],min[i-1][j])，在考虑边界的特殊处理即可</p>
<p><strong>解题方法</strong>
 <br>
 最后得到的最后一行中的最小值即为最小路径和</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int minimumTotal(List&lt;List&lt;Integer&gt;&gt; triangle) {

        int n=triangle.size();
        for(int i=1;i&lt;n;i++){

            int m=triangle.get(i).size();
            for(int j=0;j&lt;m;j++){

                if(j==0){

                    triangle.get(i).set(j,triangle.get(i-1).get(j)+triangle.get(i).get(j));
                }else if(j==m-1){

                    triangle.get(i).set(j,triangle.get(i-1).get(j-1)+triangle.get(i).get(j));
                }else{

                    triangle.get(i).set(j,triangle.get(i).get(j)+
                    Math.min(triangle.get(i-1).get(j),triangle.get(i-1).get(j-1)));
                }
            }

        }
        int ans=99999;
        for(int i=0;i&lt;triangle.get(n-1).size();i++){

            if(triangle.get(n-1).get(i)&lt;ans)
            ans=triangle.get(n-1).get(i);
        }
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138525524</p>]]></description><guid isPermaLink="false">/archives/48acfdb1-be6a-4586-a790-a26ab6f2eaf2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:33:46 GMT</pubDate></item><item><title><![CDATA[day-31 给植物浇水]]></title><link>https://wiki.dgfygk.com/archives/82a16063-96d7-49fa-9f16-300b7cf2f229</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-31%20%E7%BB%99%E6%A4%8D%E7%89%A9%E6%B5%87%E6%B0%B4&amp;url=/archives/82a16063-96d7-49fa-9f16-300b7cf2f229" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8a4ef7ad127a6fed54ba93d64a51e05d.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 每次前进浇水时进行判断：
 <br>
 （1）如果当前水足够，则前进一步浇水
 <br>
 （2）如果当前水量不够,则返回-1处加水再返回，再前进一步浇水</p>
<p><strong>解题方法</strong>
 <br>
 用一个变量po记录即将浇水的植物编号，当po==plants.length结束while循环</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int wateringPlants(int[] plants, int capacity) {

        int step=0;
        int water=capacity;
        int n=plants.length;
        int po=0;
        while(po&lt;n){

            if(water&gt;=plants[po]){

                step++;
                water=water-plants[po];
            }else{

                step=step+po;
                step=step+po+1;
                water=capacity-plants[po];
            }
            po++;
        }
        return step;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138617278</p>]]></description><guid isPermaLink="false">/archives/82a16063-96d7-49fa-9f16-300b7cf2f229</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:33:36 GMT</pubDate></item><item><title><![CDATA[day-32 不同路径 II]]></title><link>https://wiki.dgfygk.com/archives/8653359f-2980-49b7-97eb-d4e419567f3a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-32%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84%20II&amp;url=/archives/8653359f-2980-49b7-97eb-d4e419567f3a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F810948f9ed5276459dcf50b44b33f76e.png&amp;size=m" alt=""></p>
<p><strong>思路</strong>
 <br>
 典型的DP问题，状态转换方程为dp[i][j]=dp[i-1][j]+dp[i][j-1],边界情况单独讨论</p>
<p><strong>解题方法</strong>
 <br>
 对于第一行：如果当前元素为0且在该元素的同一行的左边不存在1，则路径数皆为1，否则为零
 <br>
 对于第一列：如果当前元素为0且在该元素的同一列的上边不存在1，则路径数皆为1，否则为零
 <br>
 对于其他元素的路径数则按状态转换方程进行计算
 <br>
 最后返回dp[m-1][n-1]即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int uniquePathsWithObstacles(int[][] obstacleGrid) {

        int  m=obstacleGrid.length;
        int n=obstacleGrid[0].length;
         int ans[][]=new int[m][n];
         boolean flag=true;
         for(int i=0;i&lt;n;i++){

            if(obstacleGrid[0][i]==0&amp;&amp;flag)ans[0][i]=1;
            if(obstacleGrid[0][i]==1||!flag){

                ans[0][i]=0;
                flag=false;
            }
         }
         flag=true;
         for(int i=0;i&lt;m;i++){

            if(obstacleGrid[i][0]==0&amp;&amp;flag)ans[i][0]=1;
            if(obstacleGrid[i][0]==1||!flag){

                ans[i][0]=0;
                flag=false;
            }
         }
         for(int i=1;i&lt;m;i++){

            for(int j=1;j&lt;n;j++){

                if(obstacleGrid[i][j]!=1){

                    ans[i][j]=ans[i-1][j]+ans[i][j-1];
                }else{

                    ans[i][j]=0;
                }
            }
         }
         return ans[m-1][n-1];
    }

}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138654641</p>]]></description><guid isPermaLink="false">/archives/8653359f-2980-49b7-97eb-d4e419567f3a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:33:27 GMT</pubDate></item><item><title><![CDATA[day-31 给植物浇水 II]]></title><link>https://wiki.dgfygk.com/archives/3132e711-21c4-49fa-80f5-dc665bebba35</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-31%20%E7%BB%99%E6%A4%8D%E7%89%A9%E6%B5%87%E6%B0%B4%20II&amp;url=/archives/3132e711-21c4-49fa-80f5-dc665bebba35" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffee5304a395996b7172d8b91beb9b19f.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 用两个变量start和end记录浇水位置，当前剩余水量大于需要浇水量时，进行浇水并前进一步，否则需要返回加水（即重新灌满水罐的次数+1）</p>
<p><strong>解题方法</strong>
 <br>
 用while（start<end）进行上述循环，循环结束时，如果start==end且math.max(startwater,endwater)<plants[start]则重新灌满水罐的次数+1< p> </end）进行上述循环，循环结束时，如果start==end且math.max(startwater,endwater)<plants[start]则重新灌满水罐的次数+1<></p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int minimumRefill(int[] plants, int capacityA, int capacityB) {

        int n=plants.length;
        int start=0;
        int startwater=capacityA;
        int end=n-1;
        int endwater=capacityB;
        int ans=0;
        while(start&lt;end){

            if(startwater&gt;=plants[start]){

                startwater=startwater-plants[start];
                start++;
            }else{

                startwater=capacityA-plants[start];
                start++;
                ans++;
            }
            if(endwater&gt;=plants[end]){

                endwater=endwater-plants[end];
                end--;
            }else{

                endwater=capacityB-plants[end];
                end--;
                ans++;
            }
        }
        if(start==end&amp;&amp;Math.max(startwater,endwater)&lt;plants[start])ans++;
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138632879</p>]]></description><guid isPermaLink="false">/archives/3132e711-21c4-49fa-80f5-dc665bebba35</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:33:16 GMT</pubDate></item><item><title><![CDATA[day-32 最小路径和]]></title><link>https://wiki.dgfygk.com/archives/2ccf36ed-d423-4c84-85e3-0497899a8a30</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-32%20%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C&amp;url=/archives/2ccf36ed-d423-4c84-85e3-0497899a8a30" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa08e74eee26bacf5f3e8d8ff104e8dc6.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 典型的DP问题，状态方程为distance[i][j]=distance[i][j]+Math.min(distance[i][j-1],distance[i-1][j]),边界情况需要单独讨论</p>
<p><strong>解题方法</strong>
 <br>
 对二维数组进行遍历，对于当前元素：
 <br>
 1:i0\&amp;\&amp;j0,则distance[i][j]=grid[i][j]
 <br>
 2:i0\&amp;\&amp;j!=0,则distance[i][j]=grid[i][j]+grid[i][j-1]
 <br>
 3:i!=0\&amp;\&amp;j0,则distance[i][j]=grid[i][j]+grid[i-1][j]
 <br>
 4:以上情况皆不满足,则distance[i][j]=grid[i][j]+Math.min(grid[i][j-1],grid[i-1][j])
 <br>
 最后返回distance[m][n]即可</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int minPathSum(int[][] grid) {

        int m=grid.length;
        int n=grid[0].length;
        for(int i=0;i&lt;m;i++){

            for(int j=0;j&lt;n;j++){

                if(i==0&amp;&amp;j==0){
   }
                else if(i==0&amp;&amp;j!=0){

                    grid[i][j]+=grid[i][j-1];
                }else if(i!=0&amp;&amp;j==0){

                    grid[i][j]+=grid[i-1][j];
                }else{

                    grid[i][j]+=Math.min(grid[i][j-1],grid[i-1][j]);
                }
            }
        }
        return grid[m-1][n-1];
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138648802</p>]]></description><guid isPermaLink="false">/archives/2ccf36ed-d423-4c84-85e3-0497899a8a30</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:32:54 GMT</pubDate></item><item><title><![CDATA[day-34 二叉树的锯齿形层序遍历]]></title><link>https://wiki.dgfygk.com/archives/b487bb27-8e93-44e6-a007-e5172ec22c5f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-34%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86&amp;url=/archives/b487bb27-8e93-44e6-a007-e5172ec22c5f" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F22f24659c6c5a0b4010f7ddacc3888fb.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 相较于二叉树的层序遍历，多了一个flag变量,当flag等于0时，把当前层的数组从左到右放入链表，当flag等于1时，把当前层的数组从右到左放入链表。</p>
<p><strong>解题方法</strong>
 <br>
 注意：链表删除一个数据后会立即重排，所以删除同一层的节点时，每次都删除第一个节点。</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    public List&lt;List&lt;Integer&gt;&gt; zigzagLevelOrder(TreeNode root) {

        List&lt;List&lt;Integer&gt;&gt; res=new ArrayList&lt;List&lt;Integer&gt;&gt;();
        if(root==null) return res;
        List&lt;TreeNode&gt; queue = new ArrayList&lt;TreeNode&gt;();
        queue.add(root);
        int flag=0;
        while(queue.size()&gt;0){

            int size=queue.size();
           List&lt;Integer&gt; t = new ArrayList&lt;Integer&gt;();
           if(flag==0){

              for(int i=0;i&lt;size;i++){

                t.add(queue.get(i).val);
            }
            flag=1;
           }else{

              for(int i=size-1;i&gt;=0;i--){

                t.add(queue.get(i).val);
                flag=0;
            }
           }
            for(int i=0;i&lt;size;i++){

                if(queue.get(i).left!=null){

                    queue.add(queue.get(i).left);
                }
                if(queue.get(i).right!=null){

                    queue.add(queue.get(i).right);
                }  
            }
            for(int i=0;i&lt;size;i++){

                queue.remove(0);
            }
            res.add(t);
        }
        return res;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138769116</p>]]></description><guid isPermaLink="false">/archives/b487bb27-8e93-44e6-a007-e5172ec22c5f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:32:45 GMT</pubDate></item><item><title><![CDATA[Linux-04]]></title><link>https://wiki.dgfygk.com/archives/07c56936-6d36-4169-9fff-7cd7950ff7f1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux-04&amp;url=/archives/07c56936-6d36-4169-9fff-7cd7950ff7f1" width="1" height="1" alt="" style="opacity:0;">
<p><strong>账号管理</strong></p>
<pre><code class="prism language-bash">添加账号
useradd 选项 用户名
useradd -m dai

删除帐号
userdel 选项 用户名
userdel -r dai

修改帐号
usermod 选项 用户名
 usermod -d /home/user dai  （修改位置）

切换帐号
su username
su dai

退出账号
exit    
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb8a12bb6bd61061ca8767ebff1a82bd7.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F54a2c8ff378a23c74b96f41622caffc9.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3c02f7e1113f4be0e83c557e787b51c4.png&amp;size=m" alt="">
 <br>
 $表示普通用户</p>
<h1>表示超级用户，也就是root用户</h1>
<pre><code class="prism language-bash">passwd 选项 用户名

普通账号
$ passwd
Old password:******
New password:*******
Re-enter new password:*******

超级账号        （可以修改人一账号的密码）
# passwd dai
New password:*******
Re-enter new password:*******


# passwd -l dai （-l表示锁定一个账号）
</code></pre>
<p><strong>用户组管理</strong></p>
<pre><code class="prism language-bash">增加一个新的用户组使用groupadd命令
groupadd 选项 用户组
# groupadd kaifa
# groupadd -g 111 kaifa         指定组的id

删除一个已有的用户组
groupdel 用户组

修改用户组
groupmod 选项 用户组
groupmod -g 222 fabu
groupmod --g 10000 -n kaifa fabu
-g GID 为用户组指定新的组标识号。
-n新用户组 将用户组的名字改为新名字

切换组
 newgrp root
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9b3b5966332e2f235808f44b7603f1c9.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138519494</p>]]></description><guid isPermaLink="false">/archives/07c56936-6d36-4169-9fff-7cd7950ff7f1</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 09:32:34 GMT</pubDate></item><item><title><![CDATA[Linux-05]]></title><link>https://wiki.dgfygk.com/archives/7921b9f4-425c-4951-b6eb-68713d051955</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux-05&amp;url=/archives/7921b9f4-425c-4951-b6eb-68713d051955" width="1" height="1" alt="" style="opacity:0;">
<p><strong>磁盘管理</strong></p>
<pre><code class="prism language-bash">查看磁盘使用量
df -h
du

mount /dev/dai /mnt     将外部设备dai挂载到mnt目录下

umount                  卸载
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe34a04a44f2c9150c89a0513235d15be.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa49df8de24eea9e023c0364f121ea2f6.png&amp;size=m" alt="">
 <br>
 <strong>进程管理</strong></p>
<pre><code class="prism language-bash">ps -a       所有进程
ps -u       

ps aux|grep redis     常用     查看redis相关的进程

kill -9 进程id          结束进程
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3327e6c8b922c6dbbf3972932627092b.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5bf742724b6b078acc5355149602e9a7.png&amp;size=m" alt=""></p>
<pre><code class="prism language-bash">jdk安装
rpm -ivh 文件名.rpm
</code></pre>
<p>查看自带jdk
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbf857e0bdb7a6491fa1bd6833a62461d.png&amp;size=m" alt=""></p>
<p>Tomcat安装并解压
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb56610b7382640c248aecc1f889b30df.png&amp;size=m" alt="">
 <br>
 Tomcat启动和关闭
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6644f427420012898693f49d99c9c4bc.png&amp;size=m" alt=""></p>
<p>防火墙相关命令</p>
<pre><code class="prism language-bash"># 查看firewall服务状态
systemctl status firewalld

# 开启、重启、关闭、firewalld.service服务
# 开启
service firewalld start
# 重启
service firewalld restart
# 关闭
service firewalld stop

# 查看防火墙规则
firewall-cmd --list-all    # 查看全部信息
firewall-cmd --list-ports  # 只看端口信息

# 开启端口
开端口命令：firewall-cmd --zone=public --add-port=80/tcp --permanent
重启防火墙：systemctl restart firewalld.service

命令含义：
--zone #作用域
--add-port=80/tcp  #添加端口，格式为：端口/通讯协议
--permanent   #永久生效，没有此参数重启后失效
</code></pre>
<p>Docker是一种容器化平台，它能够将应用程序及其依赖项打包在一个独立的、可移植的容器中。</p>
<pre><code class="prism language-bash">//安装环境
yum -y install gcc
yum -y install gcc-c++

//安装需要的软件包
yum install -y yum-utils device-mapper-persistent-data lvm2

//设置stable镜像仓库
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

//更新yum软件包索引
yum makecache fast

//安装Docker CE
yum -y install docker-ce docker-ce-cli containerd.io

//启动docker
systemctl start docker

//测试
docker version
docker run hello-world      //没有会自己去官网下载
docker images
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F551abfb51795c8ee82c8c9aec37e636a.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138522989</p>]]></description><guid isPermaLink="false">/archives/7921b9f4-425c-4951-b6eb-68713d051955</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 09:32:17 GMT</pubDate></item><item><title><![CDATA[Redis八股]]></title><link>https://wiki.dgfygk.com/archives/df523711-d3e6-44c4-bf11-c35134efa047</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%E5%85%AB%E8%82%A1&amp;url=/archives/df523711-d3e6-44c4-bf11-c35134efa047" width="1" height="1" alt="" style="opacity:0;">
<p><em>Redisde 的持久化方式有几种，各自的实现过程？</em>
 <br>
 Redis有两种持久化方式：RDB（Redis Database）和AOF（Append Only File）
 <br>
 RDB是将Redis在内存中的数据保存到硬盘上的一种快照方式。RDB是通过fork一个子进程来实现的，子进程会将数据写入一个临时文件，然后替换原来的RDB文件。RDB持久化方式适合用于备份和灾难恢复，可以在恢复数据时快速加载，但可能会丢失最后一次持久化后的数据。
 <br>
 AOF是通过将Redis的操作命令追加到一个文件中来实现的，类似于操作日志。当Redis重新启动时，可以通过重放AOF文件中的命令来恢复数据。AOF持久化方式适合用于数据持久化，可以保证数据的完整性，但对于大量写入操作的情况，AOF文件可能会变得很大，导致恢复数据的时间较长。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138586191</p>]]></description><guid isPermaLink="false">/archives/df523711-d3e6-44c4-bf11-c35134efa047</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:32:05 GMT</pubDate></item><item><title><![CDATA[6-动态路由]]></title><link>https://wiki.dgfygk.com/archives/d7841bac-4836-4202-945e-858bc2b9d23a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=6-%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1&amp;url=/archives/d7841bac-4836-4202-945e-858bc2b9d23a" width="1" height="1" alt="" style="opacity:0;">
<p>前文提到用户可以通过url访问到不属于自己权限的页面，这需要通过动态路由来解决
 <br>
 1.将后端返回的菜单数据存储到Cookie之后，调用状态管理中添加路由方法</p>
<pre><code class="prism language-javascript">//调用store中的setMenu函数
 this.$store.commit('setMenu',data.data.data)
 // 动态路由配置
 this.$store.commit('registerouter',this.$router)
</code></pre>
<pre><code>```prism language-java
//添加路由方法
//动态注册路由
registerouter(state, router) {

    // 没有数据
    if (!Cookie.get("menu"))
        return;
    //有数据
    const menu = JSON.parse(Cookie.get("menu"))
    state.menu = menu;
    //动态组装路由
    const menuArray = []
    menu.forEach(item =&gt; {

            if (item.children) {
    //有子菜单
                item.children = item.children.map(item =&gt; {

                    item.component = () =&gt;
                        import (`../views/${
   item.url}`)
                    return item
                })
                menuArray.push(...item.children)
            } else {
    //没有子菜单
                item.component = () =&gt;
                    import (`../views/${
   item.url}`)
                menuArray.push(item)
            }
        })
        // console.log(menuArray, "menu")
        //路由的动态添加
    menuArray.forEach(item =&gt; {

        router.addRoute('Main', item)
    })
}
</code></pre>
<p>3.router下的index.js文件如下</p>
<pre><code class="prism language-javascript">import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
    //创建路由组件
import Main from '../views/Main.vue'
import Login from '../views/Login.vue'
//将路由与组件进行映射
const routes = [

        {

            path: '/login',
            name: 'login',
            component: Login,
        },
        //主路由
        {

            path: '/',
            component: Main,
            name: 'Main',
            redirect: '/home', //路径为 / 时，重定向到home
            //子路由
            children: []
        }
    ]
    //创建router实例
const router = new VueRouter({

    routes
})
export default router
</code></pre>
<p>4.注意：此时虽然可以实现动态路由，但是刷新页面则会失效，在main.js文件中也需要调用添加路由方法</p>
<pre><code class="prism language-javascript">new Vue({

    router, //将router挂载到vue实例上
    store, //将store挂载到vue实例上
    render: h =&gt; h(App),
    created() {

        store.commit('registerouter', router)
    }
}).$mount('#app')
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138326290</p>]]></description><guid isPermaLink="false">/archives/d7841bac-4836-4202-945e-858bc2b9d23a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:31:52 GMT</pubDate></item><item><title><![CDATA[day-28 除自身以外数组的乘积]]></title><link>https://wiki.dgfygk.com/archives/29833952-d214-45c2-bfb1-5eb13ee44f7b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-28%20%E9%99%A4%E8%87%AA%E8%BA%AB%E4%BB%A5%E5%A4%96%E6%95%B0%E7%BB%84%E7%9A%84%E4%B9%98%E7%A7%AF&amp;url=/archives/29833952-d214-45c2-bfb1-5eb13ee44f7b" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fed80d7a50a938819e17bcfc2fcd0df25.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用两个空数组left（left[i]表示nums下标从0到i的乘积）和right（right[i]表示nums下标从n-1到i的乘积）</p>
<p><strong>解题方法</strong>
 <br>
 返回数组nums[i]=right[i+1]*left[i-1],第一个元素和最后一个元素单独考虑</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int[] productExceptSelf(int[] nums) {

        int n=nums.length;
        int left[]=new int[n];
        int right[]=new int[n];
        left[0]=nums[0];
        right[n-1]=nums[n-1];
        for(int i=1;i&lt;n;i++){

            left[i]=nums[i]*left[i-1];
        }
        for(int i=n-2;i&gt;=0;i--){

            right[i]=nums[i]*right[i+1];
        }
      for(int i=0;i&lt;n;i++){

        if(i==0){

            nums[i]=right[i+1];
        }else if(i==n-1){

            nums[i]=left[i-1];
        }else{

            nums[i]=right[i+1]*left[i-1];
        }
      }
      return nums;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138466687</p>]]></description><guid isPermaLink="false">/archives/29833952-d214-45c2-bfb1-5eb13ee44f7b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:31:34 GMT</pubDate></item><item><title><![CDATA[JVM-02]]></title><link>https://wiki.dgfygk.com/archives/d85fd016-7643-4f3b-9ef1-7934946114c3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JVM-02&amp;url=/archives/d85fd016-7643-4f3b-9ef1-7934946114c3" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8174e50f21c2533b53c67e1ee5464acd.png&amp;size=m" alt="">
 <br>
 ps;图片来自https://www.cnblogs.com/tomakemyself/p/14147989.html
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F846751afb2ebfbeb631f7d227a4684ab.png&amp;size=m" alt="">
 <br>
 ps;图片来自https://zhuanlan.zhihu.com/p/87360857</p>
<p>JVM (Java Virtual Machine) 是一种虚拟机，它是Java程序运行的基础。JVM主要由以下几个组成部分组成：
 <br>
 1.类加载器（ClassLoader）：负责将字节码文件加载到内存中，并将其转换为JVM能够理解的格式。它将类的数据从Class文件中读取到JVM的方法区中，并在堆中创建一个Class对象来存储类的相关信息。</p>
<p>2.执行引擎（Execution Engine）：执行引擎负责解释和执行Java字节码。它将字节码解释为具体的机器指令并执行。</p>
<p>3.运行时数据区（Runtime Data Area）：运行时数据区是JVM运行时的内存区域，用于存储程序执行时所需要的数据。它包括方法区、堆、栈、程序计数器等。</p>
<p>4.垃圾收集器（Garbage Collector）：垃圾收集器负责在程序运行过程中自动回收不再使用的对象。它通过检测不再使用的对象，并释放其所占用的内存空间，以便让其他对象继续使用。</p>
<p>5.JIT编译器（Just-In-Time Compiler）：JIT编译器负责将频繁执行的字节码动态编译为本地机器码，以提高程序的执行效率。</p>
<p>其中JVM（Java Virtual Machine）<em>运行时数据区</em> 由以下几个部分组成：
 <br>
 1.程序计数器（Program Counter Register）：用于指示当前线程所执行的字节码指令的位置。</p>
<p>2.Java虚拟机栈（Java Virtual Machine Stack）：每个线程在执行期间会创建一个栈帧（Stack Frame），用于保存局部变量、操作数栈、动态链接、返回地址等信息。</p>
<p>3.本地方法栈（Native Method Stack）：与Java虚拟机栈类似，但用于执行本地方法。</p>
<p>4.Java堆（Java Heap）：用于存储对象实例。</p>
<p>5.方法区（Method Area）：用于存储类信息、静态变量、常量池等数据。</p>
<p>6.运行时常量池（Runtime Constant Pool）：每个类或接口在编译期会生成一个常量池，用于存储编译期生成的各种字面量和符号引用。</p>
<p>7.直接内存（Direct Memory）：在JDK1.4中引入。JVM可以直接通过堆外内存进行IO操作，提高IO性能。</p>
<pre><code class="prism language-java">以下是一个简单的JVM思维导图：

JVM（Java虚拟机）
1.类加载器
启动类加载器
扩展类加载器
应用程序类加载器
2.运行时数据区
方法区
堆
栈
本地方法栈
程序计数器
3.执行引擎
解释器
即时编译器
4.垃圾回收器
标记-清除算法
复制算法
标记-整理算法
</code></pre>
<p><em>双亲委派机制</em>：基本思想是，当一个类加载器需要加载一个类时，首先将加载请求委派给它的父类加载器去完成。只有当父类加载器无法加载该类时，子类加载器才会尝试自己加载该类。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138436834</p>]]></description><guid isPermaLink="false">/archives/d85fd016-7643-4f3b-9ef1-7934946114c3</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:31:23 GMT</pubDate></item><item><title><![CDATA[Linux-01]]></title><link>https://wiki.dgfygk.com/archives/ffd15f75-0892-4789-929e-f6babeaa56e1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux-01&amp;url=/archives/ffd15f75-0892-4789-929e-f6babeaa56e1" width="1" height="1" alt="" style="opacity:0;">
<p>Linux是一种开源的操作系统内核，它是基于UNIX的设计原理开发而来的。它最初由芬兰的林纳斯·托瓦兹创造并发布于1991年。Linux操作系统具有很强的稳定性、安全性和可靠性，被广泛应用于服务器、个人电脑、移动设备和嵌入式系统等领域。
 <br>
 Linux的主要特点包括：</p>
<ol>
 <li>开源性：Linux的源代码对任何人都是开放的，用户可以自由修改和分发它。</li>
 <li>多用户多任务支持：Linux支持多用户同时登录，并且可以同时执行多个程序，实现多任务运行。</li>
 <li>强大的网络支持：Linux操作系统提供丰富的网络功能和协议支持，可以轻松实现网络通信。</li>
 <li>高度可定制性：用户可以根据自己的需求选择不同的Linux发行版，并能够灵活地定制自己的系统。</li>
 <li>高效的性能和稳定性：Linux操作系统具有优秀的性能和稳定性，能够高效地运行各种软件和服务。</li>
 <li>安全性：Linux操作系统具有较高的安全性，拥有强大的权限管理和安全策略，可以防止恶意软件和攻击。</li>
 <li>大量的软件支持：Linux拥有丰富的开源软件资源，用户可以免费获取和使用大量的软件工具。
  <br>
  总的来说，Linux是一个功能强大、稳定可靠、安全性高、灵活可定制的操作系统，它在服务器领域得到广泛应用，并且越来越受到个人用户和企业用户的青睐。</li>
</ol>
<pre><code class="prism language-java">ifstat

nmcli c up enp0s3

//检测yum是否可以使用
yum list | tail

//安装图形化页面
yum groupinstall -y "GNOME Desktop"
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff3b504ebac73b0b8f95fe6efc9193e45.png&amp;size=m" alt="">
 <br>
 <strong>Linux常用命令：</strong></p>
<pre><code class="prism language-bash">1.基本操作命令：
shutdown：关机命令。
reboot：重启命令。
logout 或 exit 或 ctrl+d：退出当前会话。
Ctrl+c：停掉正在运行的程序。
Ctrl+l：清屏。
Tab：命令补齐功能。
help 或 --help：查看命令的帮助信息。
man 或 info：查看命令的手册页（详细文档）。

2.目录操作命令：
ls：列出目录内容。
cd：切换目录。
pwd：显示当前工作目录。
mkdir：创建目录。
rmdir：删除空目录。
cp：复制文件或目录。
mv：移动文件或目录，或重命名文件或目录。
rm：删除文件或目录。
find：查找文件或目录。

3.文件操作命令：
cat：查看文件内容。
less 或 more：分页查看文件内容。
echo：输出内容到文件或终端。
touch：创建空文件或更新文件时间戳。
chmod：改变文件或目录的权限。
chown：改变文件或目录的所有者和所属组。
tar：打包和解包文件。
gzip、bzip2：压缩和解压缩文件。

4.进程管理命令：
ps：查看当前运行的进程。
top 或 htop：实时查看进程状态。
kill：终止进程。
nohup：使进程在用户退出后继续运行。
&amp;：将命令放到后台执行。

5.网络管理命令：
ifconfig 或 ip：查看和配置网络接口。
ping：测试网络连接。
netstat：查看网络连接、路由表、接口统计等。
ssh：远程登录到其他计算机。
scp：远程复制文件。

6.系统信息命令：
uname：查看系统信息（如内核版本）。
df：查看磁盘空间使用情况。
du：查看文件和目录的磁盘使用情况。
free：查看内存使用情况。
top 或 vmstat：查看系统性能信息。
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6cf3951511fc13bf18141cacb0d116af.png&amp;size=m" alt="">
 <br>
 <strong>Linux目录介绍：</strong></p>
<pre><code class="prism language-bash">/bin：这个目录包含了一些基本的可执行命令和工具，用于系统启动和在单用户模式下执行。例如，ls、cd等命令就位于该目录。
/boot：该目录包含了用于系统引导的文件，如内核和引导加载程序（bootloader）相关的文件。
/dev：该目录包含了系统中的设备文件，这些文件代表了连接到系统的各种设备，如硬盘、光盘驱动器等。
/etc：该目录包含了系统的配置文件，这些文件包含了系统运行和应用程序所需的参数和设置。
/home：每个用户在系统中都有一个主目录，用于存储个人文件和文档。这些主目录都位于/home目录下。
/lib：该目录包含了系统所需的共享库文件。这些库文件是程序在执行过程中需要调用的额外参数。
/media 和 /mnt：这两个目录都用于挂载可移动媒体设备（如USB驱动器、光驱等）或其他文件系统的挂载点。/media目录通常用于自动挂载可移动媒体设备，而/mnt目录则是一个用于临时挂载其他文件系统的挂载点。
/proc：该目录是一个虚拟文件系统，用于存储系统和正在运行进程的相关信息。
/usr：这是一个重要的目录，包含了大部分的用户级应用程序和库文件。/usr/bin目录包含了大量的用户级命令和工具，/usr/lib目录则包含了各种编程语言的库文件。
/tmp：这个目录用于存放临时文件。这些文件通常在系统重启后会被删除。
/var：这个目录用于存放经常变动的文件，如日志文件、数据库文件等。/var/tmp目录与/tmp目录类似，但其中的文件在系统重启后不会被删除。
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138468226</p>]]></description><guid isPermaLink="false">/archives/ffd15f75-0892-4789-929e-f6babeaa56e1</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 09:31:14 GMT</pubDate></item><item><title><![CDATA[Linux-02]]></title><link>https://wiki.dgfygk.com/archives/2b6ded2c-a24a-4e44-b663-bbbcb82c0c68</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux-02&amp;url=/archives/2b6ded2c-a24a-4e44-b663-bbbcb82c0c68" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Linux常用命令：</strong></p>
<pre><code class="prism language-bash">ls: 列出目录

touch: 创建文件
touch test.txt
echo:往文件写内容
 echo "i love linux" &gt;&gt;test.txt

cd：切换目录

pwd：显示目前的目录

mkdir：创建一个新的目录
mkdir dai:创建目录dai
mkdir -p test1/test2/test3/test4: 创建多层目录

rmdir：删除一个空的目录

cp: 复制文件或目录

rm: 移除文件或目录
-f ：就是 force 的意思，忽略不存在的文件，不会出现警告信息；
-i ：互动模式，在删除前会询问使用者是否动作
-r ：递归删除！最常用在目录的删除了
 rm -i install.sh

mv: 移动文件与目录，或修改文件与目录的名称
-f ：force 强制的意思，如果目标文件已经存在，不会询问而直接覆盖；
-i ：若目标文件 (destination) 已经存在时，就会询问是否覆盖
-u ：若目标文件已经存在，且 source 比较新，才会升级 (update)
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd00130dff00f5865d59095604644d771.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7a12071e93fd563381e789dc4ae82ba8.png&amp;size=m" alt="">
 <br>
 在Linux中第一个字符代表这个文件是目录、文件或链接文件等等：
 <br>
 当为[ d ]则是目录
 <br>
 当为[ - ]则是文件；
 <br>
 若是[ l ]则表示为链接文档 ( link file )；
 <br>
 若是[ b ]则表示为装置文件里面的可供储存的接口设备 ( 可随机存取装置 )；
 <br>
 若是[ c ]则表示为装置文件里面的串行端口设备，例如键盘、鼠标 ( 一次性读取装置 )。</p>
<p>Linux系统按<strong>文件所有者、文件所有者同组用户</strong> 和<strong>其他用户</strong>来规定了不同的文件访问权限。</p>
<pre><code class="prism language-bash">chgrp：更改文件属组
chgrp [-R] 属组名 文件名

chown：更改文件属主，也可以同时更改文件属组
chown [--R] 属主名 文件名
chown [-R] 属主名：属组名 文件名

chmod：更改文件9个属性      //重要
r:4     w:2         x:1
rw-     6
rwx     7
---     0
chmod 770 filename  （文件所有者和文件所有者同组用户可读可写可执行，其他用户不可读不可写不可执行）
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138492879</p>]]></description><guid isPermaLink="false">/archives/2b6ded2c-a24a-4e44-b663-bbbcb82c0c68</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 09:31:02 GMT</pubDate></item><item><title><![CDATA[5-用户权限控制（前端）]]></title><link>https://wiki.dgfygk.com/archives/bed132b5-96dd-401a-920d-60bbd50faaef</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=5-%E7%94%A8%E6%88%B7%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6%EF%BC%88%E5%89%8D%E7%AB%AF%EF%BC%89&amp;url=/archives/bed132b5-96dd-401a-920d-60bbd50faaef" width="1" height="1" alt="" style="opacity:0;">
<p>1.登录成功后，调用状态管理中对应的设置菜单的方法</p>
<pre><code class="prism language-javascript">//调用store中的setMenu函数
this.$store.commit('setMenu',data.data.data)
</code></pre>
<p>2.将后端返回的菜单数据存入Cookie中</p>
<pre><code class="prism language-javascript">//设置菜单的方法
       setMenu(state, val) {

            state.menu = val
            Cookie.set("menu", JSON.stringify(val))
        },
</code></pre>
<p>3.在对应的侧边框组件中获取菜单数据，并通过循环遍历将其显示到页面</p>
<pre><code class="prism language-javascript">menuData(){

            //Cookie中有则获取，否则从store获取（store中的数据不具备持久化，刷新页面会消失）
            if(Cookie.get("menu")!=undefined){

                return JSON.parse(Cookie.get("menu"))
            }else{

                return this.$store.state.tab.menu
            }
        }
</code></pre>
<p>注意：此时虽然可以实现用户权限控制的菜单显示，但仍可以通过url访问不属于自己权限的页面（这需要通过动态路由技术实现）</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138325854</p>]]></description><guid isPermaLink="false">/archives/bed132b5-96dd-401a-920d-60bbd50faaef</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:30:50 GMT</pubDate></item><item><title><![CDATA[day-26 H 指数]]></title><link>https://wiki.dgfygk.com/archives/062938b4-0a64-4515-845c-33e1ca60299f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-26%20H%20%E6%8C%87%E6%95%B0&amp;url=/archives/062938b4-0a64-4515-845c-33e1ca60299f" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F10763cecceb6e0b4edb628c4030f7d98.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 利用Arrays.sort()函数排序，然后从后面开始计算H指数</p>
<p><strong>解题方法</strong>
 <br>
 H指数初始化为零，排序后从数组最后一个元素开始，如果当前元素大于等于H指数，则比较前一个元素，并将H指数加1，直到循环结束。</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int hIndex(int[] citations) {

        Arrays.sort(citations);
        int loc=citations.length-1;
        int h=0;
        while(loc&gt;=0&amp;&amp;citations[loc]&gt;h){

            h++;
            loc--;
        }
        return h;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138447193</p>]]></description><guid isPermaLink="false">/archives/062938b4-0a64-4515-845c-33e1ca60299f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:30:37 GMT</pubDate></item><item><title><![CDATA[day-29 旋转链表]]></title><link>https://wiki.dgfygk.com/archives/8264e539-0e73-461f-8b06-fe4b8a65b349</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-29%20%E6%97%8B%E8%BD%AC%E9%93%BE%E8%A1%A8&amp;url=/archives/8264e539-0e73-461f-8b06-fe4b8a65b349" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3888536c833e5eb9228c5169b29a4002.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 找到需要返回的链表的头结点，将其断开为两个链表，再拼接起来即可。例如链表长度为len,那么第len-k个元素即为头结点</p>
<p><strong>解题方法</strong>
 <br>
 因为0 &lt;= k &lt;= 2 * 109，所以先将k对len取模</p>
<p><strong>Code</strong></p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode rotateRight(ListNode head, int k) {

        if(head==null) return head;
        int len=0;
        ListNode t=head;
        while(t!=null){

            len++;
            t=t.next;
        }
        k=k%len;//防止k超过链表长度
        if(k==0) return head;
        int tt=len-k;
        t=head;
        ListNode ans=null;
        while(tt&gt;0){

            if(tt==1){

                ans=t.next;//需要返回的链表的头结点
                t.next=null;
                tt--;
            }else{

                t=t.next;
                tt--;
            }
        }
        t=ans;
        while(t.next!=null){

            t=t.next;
        }
        t.next=head;
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138488306</p>]]></description><guid isPermaLink="false">/archives/8264e539-0e73-461f-8b06-fe4b8a65b349</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:30:19 GMT</pubDate></item><item><title><![CDATA[JVM-01]]></title><link>https://wiki.dgfygk.com/archives/3fd1ba28-2e6a-46cc-819f-a8376d7dbc0c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JVM-01&amp;url=/archives/3fd1ba28-2e6a-46cc-819f-a8376d7dbc0c" width="1" height="1" alt="" style="opacity:0;">
<p>JVM（Java虚拟机）是Java平台的核心组件之一，它是一个虚拟计算机，可以执行Java字节码文件。JVM解释并执行Java字节码，并提供了其他功能，如内存管理和垃圾回收。
 <br>
 JVM的基本功能包括以下几个方面：</p>
<ol>
 <li>字节码执行：JVM通过解释Java字节码来执行程序。字节码是一种中间代码形式，它使得Java程序可以在不同的平台上运行。</li>
 <li>内存管理：JVM负责分配和管理程序使用的内存。它使用垃圾回收机制来自动回收不再使用的内存，从而减少程序员手动释放内存的工作量和避免内存泄漏。</li>
 <li>辅助功能：JVM提供了一些辅助功能，如异常处理、线程管理和安全机制等。这些功能使得Java程序开发更加方便和安全。</li>
 <li>类加载：JVM在运行时动态加载和链接Java类。它使用类加载器来查找和加载类文件，并创建类的运行时表示。
  <br>
  JVM的主要优势是跨平台性。由于JVM可以解释和执行Java字节码，Java程序可以在任何支持JVM的平台上运行，而不需要重新编译。这种可移植性使得Java成为一种广泛应用的编程语言。
  <br>
  总之，JVM是Java平台的核心组件，提供了字节码执行、内存管理、辅助功能和类加载等功能。它的跨平台性使得Java成为一种广泛应用的编程语言。</li>
</ol>
<p>JVM 本质上是一个运行在计算机上的程序，他的职责是运行Java字节码文件。
 <br>
 1.对字节码文件中的指令，实时的解释成机器码，让计算机执行。
 <br>
 2.自动为对象、方法等分配内存；自动的垃圾回收机制，回收不再使用的对象。
 <br>
 3.对热点代码进行优化，提升执行效率。</p>
<p>JVM 包含内存管理、解释执行虚拟机指令、即时编译三大功能。</p>
<p>JVM提供了即时编译（Just-In-Time 简称JIT) 进行性能的优化，最终能达到接近C、C++语言的运行性能。</p>
<p>字节码文件是一种特殊的文件格式，它包含了将源代码转换为机器可执行代码所需的指令集。字节码文件通常是由编译器将源代码编译为字节码的中间表示形式。
 <br>
 在Java中，字节码文件的扩展名为.class，它存储了编译后的Java代码。这些字节码文件可以在Java虚拟机上运行，并将字节码转换为机器代码来执行。
 <br>
 字节码文件中的指令通常是基于栈的操作，它们被设计成与Java虚拟机的指令集兼容。这使得字节码文件可以在不同的计算机上运行，只要这些计算机有一个能够解释和执行字节码的Java虚拟机。
 <br>
 除了Java，其他编程语言如Python、Ruby和C#等也有类似的字节码文件。这些字节码文件在执行时会被相应的虚拟机解释和执行。</p>
<p>字节码文件主要信息：
 <br>
 1.基础信息：魔数、字节码文件对应的Java版本号，访问标识(public final等等)，父类和接口。
 <br>
 2.常量池：保存了字符串常量、类或接口名、字段名，主要在字节码指令中使用。
 <br>
 3.字段：当前类或接口声明的字段信息。
 <br>
 4.方法：当前类或接口声明的方法信息，字节码指令。
 <br>
 5.属性：类的属性，比如源码的文件名，内部类的列表等。</p>
<p>Java字节码文件中，将文件头称为magic魔数。 CAFEBABE</p>
<p>常量池中的数据都有一个编号，编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。
 <br>
 字节码指令中通过编号引用到常量池的过程称之为符号引用。</p>
<p>Arthas 是一款线上监控诊断产品，通过全局视角实时查看应用 load、内存、gc、线程的状态信息，并能在不修改应用代码的情况下，对业务问题进行诊断，大大提升线上问题排查效率。
 <br>
 官网：https://arthas.aliyun.com/doc/
 <br>
 dump 类的全限定名：dump已加载类的字节码文件到特定目录。
 <br>
 jad类的全限定名：反编译已加载类的源码。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138418791</p>]]></description><guid isPermaLink="false">/archives/3fd1ba28-2e6a-46cc-819f-a8376d7dbc0c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:30:03 GMT</pubDate></item><item><title><![CDATA[Linux-03]]></title><link>https://wiki.dgfygk.com/archives/cff21544-4e14-4c0c-95dd-a68b48e9e311</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux-03&amp;url=/archives/cff21544-4e14-4c0c-95dd-a68b48e9e311" width="1" height="1" alt="" style="opacity:0;">
<p>cat 由第一行开始显示文件内容
 <br>
 tac 从最后一行开始显示，可以看出 tac 是 cat 的倒着写
 <br>
 nl 显示的时候，顺道输出行号
 <br>
 more 一页一页的显示文件内容
 <br>
 less 与 more 类似，但是比 more 更好的是，他可以往前翻页 (空格翻页 退出q命令)
 <br>
 head 只看头几行
 <br>
 tail 只看尾巴几行</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5290fc4545559762820e1235289c6ef3.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F18f316ba104794f3b60aae7e44899113.png&amp;size=m" alt=""></p>
<pre><code class="prism language-bash">ln f1 f2     # 创建f1的一个硬连接文件f2

ln -s f1 f3   # 创建f1的一个符号连接文件f3
</code></pre>
<p>vi/vim 共分为三种模式，分别是命令模式（Command mode），输入模式（Insert mode）和底线命令模式（Last line mode）。</p>
<pre><code class="prism language-bash">vim test.txt    (test.txt存在就修改，不存在则会新建)
</code></pre>
<p>进入了命令模式
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F50cad877db0405115cbfc8c7400036b9.png&amp;size=m" alt="">
 <br>
 按下i，切换到输入模式
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F707ac3eea2ca169b2123d9454c085cea.png&amp;size=m" alt="">
 <br>
 按下:（英文冒号）就进入了底线命令模式(如果在输入模式需要先按ESC键)
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7daa3bed3e2b59d5e082d13b0ccc5fbb.png&amp;size=m" alt="">
 <br>
 底线命令模式下：q 退出程序
 <br>
 w 保存文件
 <br>
 或者直接wq</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138499386</p>]]></description><guid isPermaLink="false">/archives/cff21544-4e14-4c0c-95dd-a68b48e9e311</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 09:29:52 GMT</pubDate></item><item><title><![CDATA[3-Vuex状态管理]]></title><link>https://wiki.dgfygk.com/archives/59a59dce-820c-4fe4-81ab-b014efd64653</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=3-Vuex%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86&amp;url=/archives/59a59dce-820c-4fe4-81ab-b014efd64653" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Vuex</strong> 是一个专门为Vue.js应用程序开发的状态管理模式。它集中管理应用程序中的所有组件的状态，并提供了一种可预测的方式来修改和查询状态。
 <br>
 Vuex的核心概念包括：</p>
<ol>
 <li>State（状态）：应用程序的状态存储在一个单一的对象中，称为state。在Vue组件中，可以通过this.$store.state来访问状态。</li>
 <li>Getters（计算属性）：用于从状态中派生出一些衍生数据，类似于Vue组件中的计算属性。可以通过this.$store.getters来访问计算属性。</li>
 <li>Mutations（变更）：用于修改状态的唯一方式。Mutations是同步的操作，通过提交一个mutation来修改状态，并且每个mutation都有一个对应的字符串类型的事件名和一个处理函数。</li>
 <li>Actions（动作）：用于处理异步的操作，例如从服务器获取数据。Action提交一个mutation来间接修改状态。Actions可以包含任何异步操作，并且可以通过this.$store.dispatch来触发。</li>
 <li>Modules（模块）：当应用程序变得复杂时，可以将state、getters、mutations和actions分割成模块。每个模块拥有自己的state、getters、mutations和actions。
  <br>
  通过使用Vuex，我们可以更好地组织和管理应用程序的状态，实现组件之间的数据共享和通信，并且使状态变更的过程更加可追踪和可维护。</li>
</ol>
<p>Vuex一般用来存储全局可访问的数据</p>
<p>步骤：
 <br>
 1.安装</p>
<pre><code class="prism language-javascript">npm install vuex@版本号
</code></pre>
<p>2.在一个文件夹下创建两个.js文件如 tab.js和index.js</p>
<pre><code class="prism language-javascript">//index.js
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab'
Vue.use(Vuex)

//创建vuex的实例


export default new Vuex.Store({

    modules: {

        tab
    }
})


//tab.js
import Cookie from 'js-cookie'
export default {

    state: {

        isCollapse: false, //控制菜单展开或收起变量
        tabsList: [ //面包屑数据
            {

                path: '/',
                name: 'home',
                label: '首页',
                icon: 's-home',
                url: 'Home/Home'
            },
        ],
        //侧边框菜单数据
        menu: [],
    },
    mutations: {

        collapseMenu(state) {
    //控制菜单展开或收起方法
            state.isCollapse = !state.isCollapse
        },
        //更新面包屑数据
        selectMenu(state, val) {

            // console.log(val)
            //判断添加数据是否为首页
            if (val.name !== 'home') {

                const index = state.tabsList.findIndex(item =&gt; item.name === val.name)
                    //不存在
                if (index == -1) {

                    state.tabsList.push(val)
                }
            }
        },
        //删除指定的tag数据
        closeTag(state, item) {

            // console.log(item)
            const index = state.tabsList.findIndex(val =&gt; val.name === item.name)
            state.tabsList.splice(index, 1)
        },
        setMenu(state, val) {

            state.menu = val
            Cookie.set("menu", JSON.stringify(val))
        },
        //动态注册路由
        registerouter(state, router) {

            // 没有数据
            if (!Cookie.get("menu")) return
                //有数据
        }
    }
}
</code></pre>
<p>3.在main.js（或相应的入口文件）中，需要引入刚刚创建的store，并在创建Vue实例时传入</p>
<p>4.访问数据或方法
 <br>
 在你的Vue组件中，可以通过this. s t o r e . s t a t e 访问状态，如 t h i s . store.state访问状态，如this. store.state访问状态，如this.store.state.tab.isCollapse
 <br>
 this.$store.commit('setMenu',data.data.data)调用方法
 <br>
 但更推荐的做法是使用mapState、mapGetters、mapMutations和mapActions辅助函数来简化这些操作。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138231945</p>]]></description><guid isPermaLink="false">/archives/59a59dce-820c-4fe4-81ab-b014efd64653</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 09:29:38 GMT</pubDate></item><item><title><![CDATA[4-用户权限控制（后端）]]></title><link>https://wiki.dgfygk.com/archives/511832be-1421-48d2-b5d2-876a76550a8e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=4-%E7%94%A8%E6%88%B7%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6%EF%BC%88%E5%90%8E%E7%AB%AF%EF%BC%89&amp;url=/archives/511832be-1421-48d2-b5d2-876a76550a8e" width="1" height="1" alt="" style="opacity:0;">
<p>在计算机系统中，用户权限控制是一种机制，用于限制用户对系统资源的访问和操作。它可以确保只有经过授权的用户可以执行特定的操作，并限制未经授权的用户的访问权限。
 <br>
 用户权限控制通常涉及以下几个方面：</p>
<ol>
 <li>用户认证：用户必须通过身份验证来证明自己的身份，通常使用用户名和密码进行认证。认证成功后，系统会分配一个特定的用户身份，用于后续的权限控制。</li>
 <li>权限分配：系统管理员可以为每个用户分配特定的权限级别，以确定其可以访问和操作的资源。不同的权限级别通常对应不同的系统功能和操作。</li>
 <li>访问控制列表（ACL）：ACL是一种定义了特定资源的权限控制规则的列表。它可以指定哪些用户有权访问某个资源、以及对该资源的具体操作权限（读、写、执行等）。</li>
 <li>角色权限控制：系统管理员可以为不同的用户定义不同的角色，并为每个角色分配特定的权限。这样，当用户被分配给某个角色后，其权限将自动继承该角色的权限。
  <br>
  通过合理的用户权限控制，系统可以实现以下几个目标：</li>
 <li>数据安全：限制未经授权的用户对敏感数据的访问，防止数据泄露和滥用。</li>
 <li>系统稳定性：限制用户对系统资源的操作，防止恶意用户对系统进行破坏或影响系统稳定性。</li>
 <li>合规性：确保系统遵守相关法律法规和行业标准，保护用户隐私和数据安全。
  <br>
  总之，用户权限控制是一项重要的安全措施，它可以帮助保护系统免受未经授权的访问和操作。在设计和实施用户权限控制时，需要根据具体的需求和风险评估，合理分配用户的权限，并定期审查和更新权限设置。</li>
</ol>
<p>此处只有不同用户返回不同的菜单，通过用户名判断（admin为超级管理员 其他为普通管理员）
 <br>
 Controller接收请求，然后调用对应的service接口，再具体实现类中实现（Result是一个封装类，在我的文章"2-token生成"有代码）</p>
<pre><code class="prism language-java">@Override
    public Result getMenuByUsername(String username) {

//        System.out.println(username);
        if(username.equals("admin")) {

//            System.out.println("执行");
            Map data1 = new HashMap();
            data1.put("path", "/");
            data1.put("name", "home");
            data1.put("label", "首页");
            data1.put("icon", "s-home");
            data1.put("url", "Home/Home");


            Map data2 = new HashMap();
            data2.put("label", "患者管理");
            data2.put("icon", "s-grid");
            Map data23 = new HashMap();
            data23.put("path", "/patient/page1");
            data23.put("name", "patient/page1");
            data23.put("label", "患者信息管理");
            data23.put("icon", "setting");
            data23.put("url", "Patient/PageOne");
            Map data24 = new HashMap();
            data24.put("path", "/patient/page2");
            data24.put("name", "patient/page2");
            data24.put("label", "患者预约记录管理");
            data24.put("icon", "setting");
            data24.put("url", "Patient/PageTwo");
            List list2 = new ArrayList();
            list2.add(data23);
            list2.add(data24);
            data2.put("children", list2);

            Map data3 = new HashMap();
            data3.put("label", "医生管理");
            data3.put("icon", "s-grid");
            Map data33 = new HashMap();
            data33.put("path", "/doctor/page1");
            data33.put("name", "doctor-page1");
            data33.put("label", "医生信息管理");
            data33.put("icon", "setting");
            data33.put("url", "Doctor/PageOne");
            Map data34 = new HashMap();
            data34.put("path", "/doctor/page2");
            data34.put("name", "doctor-page2");
            data34.put("label", "医生排班管理");
            data34.put("icon", "setting");
            data34.put("url", "Doctor/PageTwo");
            List list3 = new ArrayList();
            list3.add(data33);
            list3.add(data34);
            data3.put("children", list3);


            Map data4 = new HashMap();
            data4.put("label", "科室管理");
            data4.put("icon", "s-grid");
            Map data43 = new HashMap();
            data43.put("path", "/department/page1");
            data43.put("name", "department/page1");
            data43.put("label", "科室信息管理");
            data43.put("icon", "setting");
            data43.put("url", "Department/PageOne");
            Map data44 = new HashMap();
            data44.put("path", "/department/page2");
            data44.put("name", "department/page2");
            data44.put("label", "科室评价管理");
            data44.put("icon", "setting");
            data44.put("url", "Department/PageTwo");
            List list4 = new ArrayList();
            list4.add(data43);
            list4.add(data44);
            data4.put("children", list4);


            Map data5 = new HashMap();
            data5.put("label", "系统设置");
            data5.put("icon", "setting");
            Map data53 = new HashMap();
            data53.put("path", "/set/page1");
            data53.put("name", "set/page1");
            data53.put("label", "用户权限管理");
            data53.put("icon", "setting");
            data53.put("url", "Set/PageOne");
            Map data54 = new HashMap();
            data54.put("path", "/set/page2");
            data54.put("name", "/set/page2");
            data54.put("label", "系统日志查看");
            data54.put("icon", "setting");
            data54.put("url", "Set/PageTwo");
            List list5 = new ArrayList();
            list5.add(data53);
            list5.add(data54);
            data5.put("children", list5);

            List&lt;Map&gt; list = new ArrayList&lt;&gt;();
            list.add(data1);
            list.add(data2);
            list.add(data3);
            list.add(data4);
            list.add(data5);
            return Result.ok(list);
        }else{

            System.out.println(username);
            Map data1 = new HashMap();
            data1.put("path", "/");
            data1.put("name", "home");
            data1.put("label", "首页");
            data1.put("icon", "s-home");
            data1.put("url", "Home/Home");


            Map data2 = new HashMap();
            data2.put("label", "患者管理");
            data2.put("icon", "s-grid");
            Map data23 = new HashMap();
            data23.put("path", "/patient/page1");
            data23.put("name", "patient/page1");
            data23.put("label", "患者信息管理");
            data23.put("icon", "setting");
            data23.put("url", "Patient/PageOne");
            Map data24 = new HashMap();
            data24.put("path", "/patient/page2");
            data24.put("name", "patient/page2");
            data24.put("label", "患者预约记录管理");
            data24.put("icon", "setting");
            data24.put("url", "Patient/PageTwo");
            List list2 = new ArrayList();
            list2.add(data23);
            list2.add(data24);
            data2.put("children", list2);

            Map data3 = new HashMap();
            data3.put("label", "医生管理");
            data3.put("icon", "s-grid");
            Map data33 = new HashMap();
            data33.put("path", "/doctor/page1");
            data33.put("name", "doctor-page1");
            data33.put("label", "医生信息管理");
            data33.put("icon", "setting");
            data33.put("url", "Doctor/PageOne");
            Map data34 = new HashMap();
            data34.put("path", "/doctor/page2");
            data34.put("name", "doctor-page2");
            data34.put("label", "医生排班管理");
            data34.put("icon", "setting");
            data34.put("url", "Doctor/PageTwo");
            List list3 = new ArrayList();
            list3.add(data33);
            list3.add(data34);
            data3.put("children", list3);


            Map data4 = new HashMap();
            data4.put("label", "科室管理");
            data4.put("icon", "s-grid");
            Map data43 = new HashMap();
            data43.put("path", "/department/page1");
            data43.put("name", "department/page1");
            data43.put("label", "科室信息管理");
            data43.put("icon", "setting");
            data43.put("url", "Department/PageOne");
            Map data44 = new HashMap();
            data44.put("path", "/department/page1");
            data44.put("name", "department/page1");
            data44.put("label", "科室评价管理");
            data44.put("icon", "setting");
            data44.put("url", "Department/PageT");
            List list4 = new ArrayList();
            list4.add(data43);
            list4.add(data44);
            data4.put("children", list4);

            List&lt;Map&gt; list = new ArrayList&lt;&gt;();
            list.add(data1);
            list.add(data2);
            list.add(data3);
            list.add(data4);
            return Result.ok(list);
        }
    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138232368</p>]]></description><guid isPermaLink="false">/archives/511832be-1421-48d2-b5d2-876a76550a8e</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:29:24 GMT</pubDate></item><item><title><![CDATA[day-26 查询网格图中每一列的宽度]]></title><link>https://wiki.dgfygk.com/archives/cfd75614-d844-43d4-9e54-203330fd768f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-26%20%E6%9F%A5%E8%AF%A2%E7%BD%91%E6%A0%BC%E5%9B%BE%E4%B8%AD%E6%AF%8F%E4%B8%80%E5%88%97%E7%9A%84%E5%AE%BD%E5%BA%A6&amp;url=/archives/cfd75614-d844-43d4-9e54-203330fd768f" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8e9eef34df35177e14d6061931868cce.png&amp;size=m" alt="">
 <br>
 <strong>思路：</strong>
 <br>
 利用两个for循环，外层for用于遍历列数，内层for用于计算每一列的宽度</p>
<p><strong>解题方法：</strong>
 <br>
 内层for循环时：
 <br>
 1.当前所在位置的值为0 则宽度为1
 <br>
 2.当前所在位置的值大于0 则宽度通过不断取商得到
 <br>
 3.当前所在位置的值大于0 则宽度在情况2的基础上再加1</p>
<p><strong>code:</strong></p>
<pre><code class="prism language-java">class Solution {

    public int[] findColumnWidth(int[][] grid) {

        int n=grid[0].length;
        int m=grid.length;
        int ans[]=new int[n];
        for(int i=0;i&lt;n;i++){

            int length=0;
            for(int j=0;j&lt;m;j++){

                int len=0;
                int num=grid[j][i];
                if(num==0)len=1;
                if(num&lt;0)len=1;
                while(num!=0){

                    len++;
                    num/=10;
                }
                length=Math.max(length,len);
            }
            ans[i]=length;
        }
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138267917</p>]]></description><guid isPermaLink="false">/archives/cfd75614-d844-43d4-9e54-203330fd768f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:29:06 GMT</pubDate></item><item><title><![CDATA[Java基础八股]]></title><link>https://wiki.dgfygk.com/archives/f415721b-2979-4eca-a6f9-ed105bec2d2a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%9F%BA%E7%A1%80%E5%85%AB%E8%82%A1&amp;url=/archives/f415721b-2979-4eca-a6f9-ed105bec2d2a" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Java集合</strong> ：在Java中，集合是一种存储数据的容器。它们提供了一种方便的方式来管理和操作数据。Java集合框架提供了许多不同类型的集合类，每个类都有其自己的特点和用途。
 <br>
 Java集合框架主要分为两个接口层次结构：Collection和Map。
 <br>
 Collection接口是用于存储一组对象的集合。它包括了List、Set和Queue等接口。List接口是一个有序集合，可以包含重复的元素。Set接口是一个无序集合，不允许包含重复的元素。Queue接口是一个先进先出的集合，通常用于实现队列。
 <br>
 Map接口是一个键值对的集合，它允许使用键来查找值。Map接口的实现类包括了HashMap、TreeMap和LinkedHashMap等。
 <br>
 除了Collection和Map接口之外，Java集合框架还包括了一些其他重要的类，如Iterator迭代器、Collections工具类等。迭代器可以用来遍历集合中的元素，而Collections工具类提供了一些常用的静态方法，如排序、搜索、填充等。
 <br>
 Java集合框架的设计目标是提供高性能、高效的数据存储和操作方式。它被广泛应用于Java程序的开发中，可以帮助我们更方便地处理和管理数据。无论是小型项目还是大型项目，使用Java集合框架都能提高开发效率和代码质量。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138258464</p>]]></description><guid isPermaLink="false">/archives/f415721b-2979-4eca-a6f9-ed105bec2d2a</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:28:57 GMT</pubDate></item><item><title><![CDATA[Redis-01]]></title><link>https://wiki.dgfygk.com/archives/0e0b9143-798d-4a97-a8ba-cfde243f28af</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis-01&amp;url=/archives/0e0b9143-798d-4a97-a8ba-cfde243f28af" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Redis</strong> 是一个开源的内存数据结构存储系统，可以用作数据库、缓存和消息中间件。它支持多种数据结构，如字符串、哈希表、列表、集合、有序集等，并提供了丰富的处理这些数据结构的命令。
 <br>
 Redis具有以下特点：</p>
<ol>
 <li>快速：Redis的数据存储在内存中，读写速度非常快，平均读写速度可达10万次/秒以上。</li>
 <li>支持多种数据结构：Redis支持多种数据结构，如字符串、哈希表、列表、集合、有序集等，可以灵活地处理各种类型的数据。</li>
 <li>持久化：Redis支持两种持久化方式，一种是RDB（Redis Database）方式，将数据快照保存到磁盘上；另一种是AOF（Append Only File）方式，将每次写操作追加到文件中，以实现数据持久化。</li>
 <li>高可用性：Redis支持主从复制，可以创建多个Redis实例，其中一个为主节点，其余为从节点。主节点负责写操作，从节点负责读操作，以提高系统的可用性。</li>
 <li>分布式：Redis Cluster是Redis的分布式解决方案，可以将数据分布到多个节点上存储，同时保证数据的一致性和高可用性。</li>
 <li>支持发布/订阅机制：Redis支持发布/订阅机制，可以将消息发布到频道，订阅者可以实时接收到消息。</li>
 <li>安全性：Redis可以设置密码进行访问控制，以保障数据的安全性。
  <br>
  总之，Redis是一个快速、灵活、高可用的内存数据结构存储系统，可以广泛应用于缓存、数据库、消息中间件等场景。</li>
</ol>
<p>NoSQL:非结构化 无关联的
 <br>
 NoSQL数据库适用于需要处理<em>大量数据、高并发</em>、非结构化数据和快速迭代的应用场景。它们提供了高可扩展性和高可用性，适合实时和流式处理。</p>
<p>NoSQL存储分类：1.键值对 （Redis）
 <br>
 2.文档型数据库 （MongoDB）
 <br>
 3.列存储数据库
 <br>
 4.图形数据库</p>
<p>1.键值存储（Key-Value Store）：这种类型的数据库使用键值对的形式存储数据，其中键是唯一的标识符，值可以是结构化、半结构化或非结构化的数据。键值存储数据库非常适合需要快速存取和查询数据的场景，如缓存系统和会话存储。常见的键值存储数据库有Redis、Riak、Memcached等。
 <br>
 2.列存储（Column Store）：列存储数据库将数据组织为列族的形式，每个列族包含一组列。这种类型的数据库非常适合需要高效读取大量数据的场景，如日志分析和数据仓库。列存储数据库的优势在于其查找速度快，可扩展性强，容易进行分布式扩展。常见的列存储数据库有BigTable、HBase、Cassandra等。
 <br>
 3.文档存储（Document Store）：文档存储是一种将数据存储为文档的形式，每个文档都是一个独立的数据结构，可以包含多个字段。文档存储的主要优点是灵活性高，可以方便地修改数据结构。常见的文档存储数据库有MongoDB、CouchDB等。
 <br>
 4.图形数据库（Graph Database）：图形数据库使用图形结构来存储数据，其中节点表示实体，边表示实体之间的关系。图形数据库非常适合需要处理复杂关系数据的场景，如社交网络、推荐系统等。常见的图形数据库有Neo4J、FlockDB等。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138258666</p>]]></description><guid isPermaLink="false">/archives/0e0b9143-798d-4a97-a8ba-cfde243f28af</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:28:43 GMT</pubDate></item><item><title><![CDATA[1-密码加密]]></title><link>https://wiki.dgfygk.com/archives/c36858f6-7158-429e-97bf-edfd0757a8a4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=1-%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86&amp;url=/archives/c36858f6-7158-429e-97bf-edfd0757a8a4" width="1" height="1" alt="" style="opacity:0;">
<p>密码加密是指将用户的密码进行加密处理，以增加密码的安全性。加密后的密码可以在存储、传输或验证时提供更高的保护。</p>
<p>利用一个工具类MD5Util</p>
<pre><code class="prism language-java">package com.dc.utils;

import org.springframework.stereotype.Component;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

@Component
public final class MD5Util {

    public static String encrypt(String strSrc) {

        try {

            char hexChars[] = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i &lt; bytes.length; i++) {

                byte b = bytes[i];
                chars[k++] = hexChars[b &gt;&gt;&gt; 4 &amp; 0xf];
                chars[k++] = hexChars[b &amp; 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {

            e.printStackTrace();
            throw new RuntimeException("MD5加密出错！！+" + e);
        }
    }
}
</code></pre>
<p>在Controller接收请求，调用service层方法，然后再具体的实现函数中调用MD5Util工具类</p>
<pre><code class="prism language-java">//注册
 @Override
    public void zc(Weblogin weblogin) {

        LambdaQueryWrapper&lt;Weblogin&gt; lambdaQueryWrapper=new LambdaQueryWrapper&lt;&gt;();
        lambdaQueryWrapper.eq(Weblogin::getUsername,weblogin.getUsername());
        Weblogin user=webloginMapper.selectOne(lambdaQueryWrapper);
        if(user!=null){
   //用户存在
            return;
        }else{
   //用户不存在
            weblogin.setPassword(MD5Util.encrypt(weblogin.getPassword()));//密码加密
            webloginMapper.insert(weblogin);
        }
    }

//登录
    @Override
    public Result login(Weblogin weblogin) {

        System.out.println(weblogin);
        LambdaQueryWrapper&lt;Weblogin&gt; lambdaQueryWrapper=new LambdaQueryWrapper&lt;&gt;();
        lambdaQueryWrapper.eq(Weblogin::getUsername,weblogin.getUsername());
        Weblogin user=webloginMapper.selectOne(lambdaQueryWrapper);
        //账号不存在
        if(user==null){

            return Result.build(null, ResultCodeEnum.USERNAME_ERROR);//返回501异常
        }
        //密码不为空且密码正确（密码进行了加密）
        if(!StringUtils.isEmpty(weblogin.getPassword())&amp;&amp;
                MD5Util.encrypt(weblogin.getPassword()).equals(user.getPassword())){

            //登陆成功
            //根据用户id生成token
            System.out.println("成功");
            String token=jwtHelper.createToken(user.getUserid());
            //将token封装到Result中
            Map data=new HashMap();
            data.put("token",token);
            Result.ok(data);
        }
        //密码错误
        return Result.build(null,ResultCodeEnum.PASSWORD_ERROR);
    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138186702</p>]]></description><guid isPermaLink="false">/archives/c36858f6-7158-429e-97bf-edfd0757a8a4</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:26:54 GMT</pubDate></item><item><title><![CDATA[2-token生成]]></title><link>https://wiki.dgfygk.com/archives/3bdc2651-bedc-4799-a2b1-23216887dea6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=2-token%E7%94%9F%E6%88%90&amp;url=/archives/3bdc2651-bedc-4799-a2b1-23216887dea6" width="1" height="1" alt="" style="opacity:0;">
<p>Token是密码学中的一个概念，可以用作身份验证凭证。在计算机领域中，token可以是一个字符串，用于标识用户的身份和权限。当用户进行身份验证时，他们通常会收到一个token，以便在将来的请求中用作凭证。
 <br>
 在互联网应用程序中，token通常用于实现单点登录（Single Sign-On），这样用户只需通过一次身份验证就可以访问多个应用。当用户登录成功后，应用服务器会生成一个token，并将其保存在用户的浏览器中，例如通过cookie或localStorage。
 <br>
 每当用户访问受保护的资源时，应用服务器会要求用户提供token，以验证其身份。服务器会通过对token进行验证来确定用户的身份和权限。
 <br>
 常见的token类型有JWT（JSON Web Token）和OAuth token。JWT是一种开放的标准，定义了在网络上以JSON格式传输信息的一种方式，可以用于在应用程序间安全地传输信息。OAuth token则用于授权和认证，允许用户将其身份验证信息共享给其他应用。
 <br>
 总之，Token是一种用于身份验证和授权的凭证，在网络应用程序中起到了重要的作用。</p>
<p>需要的三个工具类</p>
<pre><code class="prism language-java">package com.dc.utils;

import com.alibaba.druid.util.StringUtils;
import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Date;

@Data
@Component
@ConfigurationProperties(prefix = "jwt.token")
public class JwtHelper {

    private  long tokenExpiration; //有效时间,单位毫秒 1000毫秒 == 1秒
    private  String tokenSignKey;  //当前程序签名秘钥

    //生成token字符串
    public  String createToken(Long userId) {

//        System.out.println("tokenExpiration = " + tokenExpiration);
//        System.out.println("tokenSignKey = " + tokenSignKey);
        String token = Jwts.builder()
                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration*1000*60)) //单位毫秒
                .claim("userId", userId)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

    //从token字符串获取userid
    public  Long getUserId(String token) {

        if(StringUtils.isEmpty(token)) return null;
        Jws&lt;Claims&gt; claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
    }
    //判断token是否有效
    public  boolean isExpiration(String token){

        try {

            boolean isExpire = Jwts.parser()
                    .setSigningKey(tokenSignKey)
                    .parseClaimsJws(token)
                    .getBody()
                    .getExpiration().before(new Date());
            //没有过期，有效，返回false
            return isExpire;
        }catch(Exception e) {

            //过期出现异常，返回true
            return true;
        }
    }
}
</code></pre>
<pre><code>```prism language-java
package com.dc.utils;

/**
 * 全局统一返回结果类
 */
public class Result&lt;T&gt; {

    // 返回码
    private Integer code;
    // 返回消息
    private String message;
    // 返回数据
    private T data;
    public Result(){
   }
    // 返回数据
    protected static &lt;T&gt; Result&lt;T&gt; build(T data) {

        Result&lt;T&gt; result = new Result&lt;T&gt;();
        if (data != null)
            result.setData(data);
        return result;
    }
    public static &lt;T&gt; Result&lt;T&gt; build(T body, Integer code, String message) {

        Result&lt;T&gt; result = build(body);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
    public static &lt;T&gt; Result&lt;T&gt; build(T body, ResultCodeEnum resultCodeEnum) {

        Result&lt;T&gt; result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }
    /**
     * 操作成功
     * @param data  baseCategory1List
     * @param &lt;T&gt;
     * @return
     */
    public static&lt;T&gt; Result&lt;T&gt; ok(T data){

        Result&lt;T&gt; result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }
    public Result&lt;T&gt; message(String msg){

        this.setMessage(msg);
        return this;
    }
    public Result&lt;T&gt; code(Integer code){

        this.setCode(code);
        return this;
    }
    public Integer getCode() {

        return code;
    }
    public void setCode(Integer code) {

        this.code = code;
    }
    public String getMessage() {

        return message;
    }
    public void setMessage(String message) {

        this.message = message;
    }
    public T getData() {

        return data;
    }
    public void setData(T data) {

        this.data = data;
    }
}
</code></pre>
<pre><code>```prism language-java
package com.dc.utils;

/**
 * 统一返回结果状态信息类
 *
 */
public enum ResultCodeEnum {


    SUCCESS(200,"success"),
    USERNAME_ERROR(501,"usernameError"),
    PASSWORD_ERROR(503,"passwordError"),
    NOTLOGIN(504,"notLogin"),
    USERNAME_USED(505,"userNameUsed");

    private Integer code;
    private String message;
    private ResultCodeEnum(Integer code, String message) {

        this.code = code;
        this.message = message;
    }
    public Integer getCode() {

        return code;
    }
    public String getMessage() {

        return message;
    }
}
</code></pre>
<p>pom文件中引入依赖</p>
<pre><code class="prism language-java"> &lt;dependency&gt;
            &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt;
            &lt;artifactId&gt;jjwt&lt;/artifactId&gt;
            &lt;version&gt;0.9.1&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;javax.xml.bind&lt;/groupId&gt;
            &lt;artifactId&gt;jaxb-api&lt;/artifactId&gt;
            &lt;version&gt;2.3.0&lt;/version&gt;
        &lt;/dependency&gt;
</code></pre>
<p>yaml文件配置</p>
<pre><code class="prism language-java">#jwt配置
jwt:
  token:
    tokenExpiration: 120 #有效时间,单位分钟
    tokenSignKey: dc123456  #当前程序签名秘钥 自定义
</code></pre>
<p>Controller接收请求，然后调用对应的service接口，再具体实现类中实现</p>
<pre><code class="prism language-java"> @Override
    public Result login(Weblogin weblogin) {

//        System.out.println(weblogin);
        LambdaQueryWrapper&lt;Weblogin&gt; lambdaQueryWrapper=new LambdaQueryWrapper&lt;&gt;();
        lambdaQueryWrapper.eq(Weblogin::getUsername,weblogin.getUsername());
        Weblogin user=webloginMapper.selectOne(lambdaQueryWrapper);
        //账号不存在
        if(user==null){

            return Result.build(null, ResultCodeEnum.USERNAME_ERROR);//返回501异常
        }
        //密码不为空且密码正确（密码进行了加密）
        if(!StringUtils.isEmpty(weblogin.getPassword())&amp;&amp;
                MD5Util.encrypt(weblogin.getPassword()).equals(user.getPassword())){

            //登陆成功
            //根据用户id生成token
            String token=jwtHelper.createToken(user.getUserid());
            //将token封装到Result中
            Map data=new HashMap();
            data.put("token",token);
            return Result.ok(data);
        }
        //密码错误
        return Result.build(null,ResultCodeEnum.PASSWORD_ERROR);
    }
</code></pre>
<p>前端将返回的token进行存储</p>
<pre><code class="prism language-javascript">submit() {

            getMenu(this.form).then((data) =&gt; {

                if (data.data.code == 501) {

                    this.$message.error('用户不存在');
                } else if (data.data.code == 503) {

                    this.$message.error('密码错误');
                } else if (data.data.code == 200) {

                    this.$message({

                        message: '登陆成功',
                        type: 'success'
                    });
                    Cookie.set("token",data.data.data.token)
                    this.$router.push('/home')
                }
            })
         }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138201140</p>]]></description><guid isPermaLink="false">/archives/3bdc2651-bedc-4799-a2b1-23216887dea6</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:26:38 GMT</pubDate></item><item><title><![CDATA[Vue-路由护卫]]></title><link>https://wiki.dgfygk.com/archives/b8bde53e-db4c-4e57-a26f-b4f4c51944d6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue-%E8%B7%AF%E7%94%B1%E6%8A%A4%E5%8D%AB&amp;url=/archives/b8bde53e-db4c-4e57-a26f-b4f4c51944d6" width="1" height="1" alt="" style="opacity:0;">
<p>Vue路由护卫是一种用于在访问路由之前和之后执行某些逻辑的机制。它们可以用来控制用户是否可以访问某个特定路由，并在路由变化时执行一些操作。在Vue路由中，有三种类型的路由护卫：
 <br>
 **全局路由护卫：**在任何路由变化时都会触发的路由护卫。可以用来检查用户的登录状态或者执行某些全局的操作。全局路由护卫分为三种：
 <br>
 beforeEach: 在进入每个路由之前触发的路由护卫。
 <br>
 afterEach: 在离开每个路由之后触发的路由护卫。
 <br>
 beforeResolve: 在导航被确认之前触发的路由护卫。
 <br>
 **路由独享的守卫：**只对某个特定的路由生效的路由护卫。可以为每个路由配置自己的路由护卫函数。
 <br>
 **组件级别的守卫：**在某个路由对应的组件内部触发的路由护卫。可以在组件内部定义beforeRouteEnter、beforeRouteUpdate和beforeRouteLeave这三个钩子函数来实现。
 <br>
 使用路由守卫可以控制访问权限，实现用户认证和授权，以及在路由切换时执行一些操作，如获取数据、清理资源等。</p>
<p>步骤：
 <br>
 1.下载 js-cookie (npm i js-cookie@3.0.1)
 <br>
 2.登录界面引入</p>
<pre><code class="prism language-javascript">import Cookie from 'js-cookie'
</code></pre>
<p>3.token信息存入cookie，用于不同页面的通信</p>
<pre><code class="prism language-javascript">Cookie.set("token",token)
</code></pre>
<p>4.//添加全局前置导航守卫</p>
<pre><code class="prism language-javascript">router.beforeEach((to, from, next) =&gt; {

    //判断token是否存在
    const token = Cookie.get('token')
    if (!token &amp;&amp; to.name !== 'login') {

        //用户未登录且当前未在登录界面
        next({
    name: 'login' })
    } else if (token &amp;&amp; to.name === 'login') {

        next({
    name: 'home' })
    } else {

        next()
    }
})
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138162002</p>]]></description><guid isPermaLink="false">/archives/b8bde53e-db4c-4e57-a26f-b4f4c51944d6</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 09:26:16 GMT</pubDate></item><item><title><![CDATA[基于微信小程序的挂号管理系统-01]]></title><link>https://wiki.dgfygk.com/archives/d327531f-c653-4a69-a67b-a250c555f6f9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8E%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%8C%82%E5%8F%B7%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F-01&amp;url=/archives/d327531f-c653-4a69-a67b-a250c555f6f9" width="1" height="1" alt="" style="opacity:0;">
<p>技术：Vue 微信小程序 SpringBoot Mybatis-plus SpringMVC Mysql elementUI ECharts</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138139001</p>]]></description><guid isPermaLink="false">/archives/d327531f-c653-4a69-a67b-a250c555f6f9</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:25:59 GMT</pubDate></item><item><title><![CDATA[深度学习-01]]></title><link>https://wiki.dgfygk.com/archives/4b1c729d-834e-4213-9f29-c9b5090ac59e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0-01&amp;url=/archives/4b1c729d-834e-4213-9f29-c9b5090ac59e" width="1" height="1" alt="" style="opacity:0;">
<p><strong>机器学习</strong> 是一种人工智能的分支，主要研究如何让计算机系统通过从大量的数据中学习并自动改进其性能。机器学习算法通过统计和数学模型来分析和理解数据，从而能够自动发现数据中的模式和规律，并根据这些规律进行预测和决策。
 <br>
 机器学习可以应用于各种领域，如图像识别、语音识别、自然语言处理、推荐系统等。它可以帮助我们解决一些复杂的问题，如自动驾驶、智能助理、金融风险评估等。
 <br>
 在机器学习中，有监督学习、无监督学习和强化学习等不同的学习方式。监督学习通过已知的输入和输出来建立模型，使其能够预测新的输入对应的输出。无监督学习则是从无标签的数据中发现模式和关系。强化学习则是通过试错的方式，根据环境的反馈来学习最优的决策策略。
 <br>
 机器学习的关键是数据。通过大量的数据训练模型，可以提高模型的准确性和泛化能力。同时，机器学习也涉及特征工程、模型选择和调参等过程，以及对模型的解释和评估。
 <br>
 总而言之，机器学习是一种通过数据和算法来让计算机系统自动学习和改进的技术，它为我们提供了解决复杂问题的一种新方法。</p>
<p><strong>深度学习</strong> 是机器学习的一种技术，它模仿人脑神经网络的工作原理来解决复杂的问题。深度学习的核心是构建深层次的神经网络模型，通过大量的训练数据来自动学习和提取特征，从而实现对数据的理解、分类和预测。
 <br>
 深度学习的主要优势在于能够处理大规模的非结构化数据，如图片、语音和文本等。它可以通过多层次的神经网络来学习和挖掘数据中的隐藏模式和特征，从而提取更高级别的抽象表示。
 <br>
 深度学习在许多领域中取得了重大突破，如计算机视觉、自然语言处理、语音识别和推荐系统等。它已经被广泛应用于图像分类、目标检测、人脸识别、语义分析和机器翻译等任务。
 <br>
 然而，深度学习也有其限制和挑战，如需要大量的训练数据、计算资源和时间等。此外，深度学习模型的解释性较差，往往只能给出结果而难以理解其背后的推理过程。
 <br>
 总而言之，深度学习是一种强大的机器学习技术，能够处理复杂的问题和大规模的数据。它在人工智能领域中具有广泛的应用前景，并将继续推动人工智能的发展。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/138138746</p>]]></description><guid isPermaLink="false">/archives/4b1c729d-834e-4213-9f29-c9b5090ac59e</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 09:25:45 GMT</pubDate></item><item><title><![CDATA[day-24 加油站]]></title><link>https://wiki.dgfygk.com/archives/43c0bc13-73d6-4e72-b43f-6ad37923dd4e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-24%20%E5%8A%A0%E6%B2%B9%E7%AB%99&amp;url=/archives/43c0bc13-73d6-4e72-b43f-6ad37923dd4e" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0d58c8ea13f5a06db21a08cdb026d8de.png&amp;size=m" alt="">
 <br>
 参考了答案。。。。。。</p>
<p>思路：借用用户Tristan的一段话：
 <br>
 首先判断总gas能不能大于等于总cost，如果总gas不够，一切都白搭对吧（总（gas- cost）不用单独去计算，和找最低点时一起计算即可，只遍历一次）；
 <br>
 再就是找总（gas-cost）的最低点，不管正负（当然如果最低点都是正的话那肯定能跑完了）此时返回零；
 <br>
 找到最低点后，如果有解，那么<strong>解就是最低点的下一个点</strong>，因为总（gas-cost）是大于等于0的，所以前面损失的gas我从最低点下一个点开始都会拿回来！，别管后面的趋势是先加后减还是先减后加，最终结果我是能填平前面的坑的。</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int canCompleteCircuit(int[] gas, int[] cost) {

        int length = gas.length;
        int rest = 0;
        int minRest = Integer.MAX_VALUE;
        int minIndex = 0;

        for (int i = 0; i &lt; length; i++) {

            rest += gas[i] - cost[i];//到达下一加油站剩余油量
            if (rest &lt; minRest) {

                minRest = rest;
                minIndex = i;
            }
        }
        if (rest &lt; 0) return -1;//无解
        else if (minRest &gt;= 0) return 0;//任何点开始都能绕一圈
        else return (minIndex + 1) % length;//有解 解就是最低点的下一个点
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136833155</p>]]></description><guid isPermaLink="false">/archives/43c0bc13-73d6-4e72-b43f-6ad37923dd4e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:25:22 GMT</pubDate></item><item><title><![CDATA[day-24 跳跃游戏 III]]></title><link>https://wiki.dgfygk.com/archives/1a0ba160-2945-4864-a040-b57c208af65b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-24%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20III&amp;url=/archives/1a0ba160-2945-4864-a040-b57c208af65b" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff9721a62210ae25a236541ab8b31ac99.png&amp;size=m" alt=""></p>
<p>思路：dfs方法，从开始节点开始进行深度优先遍历，利用一个数组vis[]记录该位置是否被访问过，如果遍历到一个已经访问的位置，返回false 如果遍历到某位置的值为0，返回true</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public boolean ans=false;
    public boolean vis[];
    public boolean canReach(int[] arr, int start) {

        vis=new boolean[arr.length];
        dfs(arr,start);
        return ans;
    }
    public void dfs(int arr[],int start){

        if(vis[start]==true){
   //已经访问过
        return;
        }
        vis[start]=true;
        if(arr[start]==0){

            ans=true;
            return;
        }
        if(0&lt;=start+arr[start]&amp;&amp;start+arr[start]&lt;arr.length)
        dfs(arr,start+arr[start]);
        if(0&lt;=start-arr[start]&amp;&amp;start-arr[start]&lt;arr.length)
        dfs(arr,start-arr[start]);
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136845718</p>]]></description><guid isPermaLink="false">/archives/1a0ba160-2945-4864-a040-b57c208af65b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:25:12 GMT</pubDate></item><item><title><![CDATA[day-25 无重复字符的最长子串]]></title><link>https://wiki.dgfygk.com/archives/e978ad37-4685-4e6a-98f6-691734f43925</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-25%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2&amp;url=/archives/e978ad37-4685-4e6a-98f6-691734f43925" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F099f8a25637baf794b175427b401e384.png&amp;size=m" alt=""></p>
<p>思路：动态规划的思想，遍历字符串，每遇到一个新的字符，将其存入HashMap中，并给其一个唯一值value(value递增)，当前字符若与HashMap中的字符都不一样，则存入HashMap中，若已经存在，则将对应value小于当前字符value的字符全部删除，最后返回dp数组的最大值即可</p>
<p>注意点：for循环遍历HashMap时，若调用remove函数，HashMap.会立即重排</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int lengthOfLongestSubstring(String s) {

        if(s.length()==0)return 0;
Map&lt;Character,Integer&gt; map=new HashMap&lt;Character,Integer&gt;();
int dp[]=new int[s.length()];
dp[0]=1;
int total=1;
map.put(s.charAt(0),1);
for(int i=1;i&lt;s.length();i++){

     if(map.getOrDefault(s.charAt(i),-1)==-1){

        dp[i]=dp[i-1]+1;
        total++;
        map.put(s.charAt(i),total);
    }else{

      total=total+1;
      List&lt;Character&gt; list=new ArrayList&lt;&gt;();
      dp[i]=total-map.get(s.charAt(i));
     for(char ch:map.keySet()){

         if(map.get(ch)&lt;map.get(s.charAt(i))){

             list.add(ch);
         }
     }
     for(int j=0;j&lt;list.size();j++){

         map.remove(list.get(j));
     }
      map.put(s.charAt(i),total);
    } 
}
Arrays.sort(dp);
return dp[s.length()-1];
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136922174</p>]]></description><guid isPermaLink="false">/archives/e978ad37-4685-4e6a-98f6-691734f43925</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:24:57 GMT</pubDate></item><item><title><![CDATA[ES6语法笔记]]></title><link>https://wiki.dgfygk.com/archives/2c255af7-6236-423a-b66f-cd72edbe9ae2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ES6%E8%AF%AD%E6%B3%95%E7%AC%94%E8%AE%B0&amp;url=/archives/2c255af7-6236-423a-b66f-cd72edbe9ae2" width="1" height="1" alt="" style="opacity:0;">
<p>在ES6（ECMAScript 2015）中，… 是所谓的展开运算符（spread operator）或剩余运算符（rest operator），它有几个用途。
 <br>
 1.展开运算符（Spread Operator）：
 <br>
 当你看到…在函数调用、数组字面量或对象字面量前时，它用作展开运算符。
 <br>
 <strong>数组</strong>：用于将一个数组的元素展开到另一个数组中，或者用于函数调用时传递数组的元素作为单独的参数。</p>
<pre><code class="prism language-javascript">const arr1 = [1, 2, 3];  
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]   
function myFunction(a, b, c) {

  console.log(a, b, c);  
  const args = [1, 2, 3];  
myFunction(...args); // 输出: 1 2 3
}  
</code></pre>
<p><strong>对象</strong>：用于将一个对象的所有可枚举属性复制到另一个对象中。</p>
<pre><code class="prism language-javascript">const obj1 = {
    a: 1, b: 2 };  
const obj2 = {
    ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
</code></pre>
<p>2.剩余运算符（Rest Operator）：
 <br>
 在函数参数或解构赋值中，…用作剩余运算符，用于收集一个函数的不定数量的参数到一个数组中，或者在解构赋值时收集剩余的属性到一个对象中。
 <br>
 <strong>函数参数</strong>：用于收集一个函数的不定数量的参数到一个数组中。</p>
<pre><code class="prism language-javascript">function myFunction(...args) {

  console.log(args); // 数组形式，包含所有参数  
}  
myFunction(1, 2, 3); // 输出: [1, 2, 3]
</code></pre>
<p><strong>解构赋值</strong>：用于在解构数组或对象时收集剩余的元素或属性。</p>
<pre><code class="prism language-javascript">const [first, ...rest] = [1, 2, 3, 4]; // first: 1, rest: [2, 3, 4]  
const {
    a, ...others } = {
    a: 1, b: 2, c: 3 }; // a: 1, others: { b: 2, c: 3 }
</code></pre>
<p>通过结合这些功能，ES6中的…运算符为处理数组和对象提供了更加灵活和简洁的方式。</p>
<p>箭头函数（Arrow Function）是ES6中引入的一种新的函数语法，它使用=&gt;符号来定义函数。箭头函数提供了更简洁的语法，并且不绑定自己的this，arguments，super或new.target。这使得箭头函数在回调函数中特别有用，因为它可以更容易地处理this的上下文。
 <br>
 箭头函数的基本格式如下：</p>
<pre><code class="prism language-javascript">(parameters) =&gt; {
    functionBody }
</code></pre>
<p>无参数：</p>
<pre><code class="prism language-javascript">() =&gt; {
    console.log('Hello, World!'); }
单个参数：
javascript
x =&gt; {
    console.log(x); }
</code></pre>
<p>如果函数体只有一条语句，并且这条语句是一个返回语句，你还可以进一步简化箭头函数：</p>
<pre><code class="prism language-javascript">x =&gt; console.log(x)
多个参数：
javascript
(x, y) =&gt; {
    console.log(x, y); }
</code></pre>
<p>立即执行的函数表达式（IIFE）：
 <br>
 虽然箭头函数不能用作构造函数（即不能使用new关键字），但它们可以用作立即执行的函数表达式（IIFE）：</p>
<pre><code class="prism language-javascript">(() =&gt; {

    console.log('This will run immediately.');  
})();
</code></pre>
<p>返回值：
 <br>
 当箭头函数返回一个值时，可以省略花括号，并且如果返回的是一个对象字面量，需要用圆括号包围对象，以避免与函数体的花括号混淆：</p>
<pre><code class="prism language-javascript">// 返回数字  
x =&gt; x * x  
// 返回一个对象  
x =&gt; ({
    key: x })
</code></pre>
<p>使用this：
 <br>
 箭头函数不绑定自己的this，它会捕获其所在上下文的this值，作为自己的this值：</p>
<pre><code class="prism language-javascript">function OuterFunction() {

    this.value = 1;  
    this.innerFunction = () =&gt; {

        console.log(this.value); // 输出：1  
    };  
    this.innerFunction2 = function() {

        console.log(this.value); // 输出：1  
    };  
}  

const outer = new OuterFunction();  
outer.innerFunction(); // 使用箭头函数  
outer.innerFunction2(); // 使用传统函数
</code></pre>
<p>注意，箭头函数没有arguments对象。如果需要访问类似arguments的东西，可以使用剩余参数（rest parameters）：</p>
<pre><code class="prism language-javascript">(...args) =&gt; {

    for (let arg of args) {

        console.log(arg);  
    }  
}
</code></pre>
<p>总之，箭头函数提供了一种更简洁、更直观的方式来编写函数，尤其是在处理回调函数和this上下文时。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/137564847</p>]]></description><guid isPermaLink="false">/archives/2c255af7-6236-423a-b66f-cd72edbe9ae2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:24:49 GMT</pubDate></item><item><title><![CDATA[Vue开发注意事项]]></title><link>https://wiki.dgfygk.com/archives/b3cd6853-3357-4701-a218-4f337e9ad4da</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue%E5%BC%80%E5%8F%91%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9&amp;url=/archives/b3cd6853-3357-4701-a218-4f337e9ad4da" width="1" height="1" alt="" style="opacity:0;">
<p>后端返回的Long型数据太长（比如身份证号），部分数据可能被前端变为0
 <br>
 解决方法：Long变为String</p>
<p>使用delete发送请求时，用 <strong>?参数=值\&amp;参数=值</strong> 的形式</p>
<pre><code class="prism language-javascript">export const deletepatient = (id) =&gt; {

    // console.log(id)
    //返回一个promise对象
    return http.delete('/card/deleteById?id=' + id)
}
</code></pre>
<p>修改ElemrntUI样式不生效时,可以在样式前加/deep/ 样式穿透</p>
<p>使用Cookie存储数据时，要先将数据转换为字符串，在需要使用时进行解析</p>
<pre><code class="prism language-java">//转换为字符串后再存储到Cookie中
Cookie.set("menu", JSON.stringify(val))

//将字符串解析为数据
JSON.parse(Cookie.get("menu"))
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/137682862</p>]]></description><guid isPermaLink="false">/archives/b3cd6853-3357-4701-a218-4f337e9ad4da</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:24:41 GMT</pubDate></item><item><title><![CDATA[day-23 N 叉树的层序遍历]]></title><link>https://wiki.dgfygk.com/archives/3e0a84a3-11c9-48e9-9748-0bd1e6fb769c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-23%20N%20%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86&amp;url=/archives/3e0a84a3-11c9-48e9-9748-0bd1e6fb769c" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7b50b141551d84db6969be3041b0d567.png&amp;size=m" alt=""></p>
<p>思路：利用bfs,利用队列将当前层的孩子元素加入队列，再将本层元素出队即可</p>
<p>注意点：res.remove()执行后，ArrayList的元素索引会改变，可以先添加下一层所有元素后，再将本层元素一起出队</p>
<p>code:</p>
<pre><code class="prism language-java">/*
// Definition for a Node.
class Node {
    public int val;
    public List&lt;Node&gt; children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List&lt;Node&gt; _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {

    public List&lt;List&lt;Integer&gt;&gt; levelOrder(Node root) {

        List&lt;List&lt;Integer&gt;&gt; res=new ArrayList&lt;List&lt;Integer&gt;&gt;();
        if(root==null)return res;
        List&lt;Node&gt; now=new ArrayList&lt;Node&gt;();
        now.add(root);
        while(now.size()&gt;0){

            int num=now.size();
            List&lt;Integer&gt; t=new ArrayList&lt;Integer&gt;();
            for(int i=0;i&lt;num;i++){
   //遍历同一层元素
                t.add(now.get(i).val);
                int cur=now.get(i).children.size();
                for(int j=0;j&lt;cur;j++){
   //遍历每个元素的孩子
                    if(now.get(i).children.get(j)!=null){

                        now.add(now.get(i).children.get(j));
                    }
                }
            }
            for(int i=0;i&lt;num;i++){

                now.remove(0);
            }
            res.add(t);
        }
        return res;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136818833</p>]]></description><guid isPermaLink="false">/archives/3e0a84a3-11c9-48e9-9748-0bd1e6fb769c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:24:32 GMT</pubDate></item><item><title><![CDATA[day-24 不同路径]]></title><link>https://wiki.dgfygk.com/archives/3896aed7-2de4-441d-84e4-b869410f3fa7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-24%20%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84&amp;url=/archives/3896aed7-2de4-441d-84e4-b869410f3fa7" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F56e0bdf9befce068429aef3a8f6a177e.png&amp;size=m" alt=""></p>
<p>思路：动态规划，因为只能向下或向右移动，所以第一行和第一列的路径数皆为1，其余位置的路径数dp[i][j]=dp[i-1][j]+dp[i][j-1] 最后返回dp[m-1][n-1]即可</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int uniquePaths(int m, int n) {

        int dp[][]=new int[m][n];
        for(int i=0;i&lt;m;i++){

            dp[i][0]=1;
        }
        for(int i=0;i&lt;n;i++){

            dp[0][i]=1;
        }
        for(int i=1;i&lt;m;i++){

            for(int j=1;j&lt;n;j++){

                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136844038</p>]]></description><guid isPermaLink="false">/archives/3896aed7-2de4-441d-84e4-b869410f3fa7</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:24:20 GMT</pubDate></item><item><title><![CDATA[day-24 单词搜索]]></title><link>https://wiki.dgfygk.com/archives/c3c16b64-ac93-47ee-85e9-f6aecda5a593</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-24%20%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2&amp;url=/archives/c3c16b64-ac93-47ee-85e9-f6aecda5a593" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F72fe4b38691267ad3f4c21bbbd4b9a02.png&amp;size=m" alt=""></p>
<p>思路：dfs思想，找到与word第一个字母相同的位置开始bfs,用id记录当前位置，如果id==word.length-1则返回true</p>
<p>注意：dfs的不同访问顺序可能导致结果不同，所以在每次遍历一条路径失败后，需要把相应的vis[i][j]恢复为未访问状态</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int[][] dir={
   {
   0,1},{
   0,-1},{
   1,0},{
   -1,0}};
    public int rows;
    public int cols;
    public String word;
    public boolean ans=false;
    public boolean exist(char[][] board, String word) {

        rows=board.length;
        cols=board[0].length;
        this.word=word;
        for(int i=0;i&lt;rows;i++){

            for(int j=0;j&lt;cols;j++){

                if(word.charAt(0)==board[i][j]){

                    boolean vis[][]=new boolean[rows][cols];
                    bfs(board,vis,i,j,0);
                }
            }
        }
        return ans;
    }
    public void bfs(char[][] board,boolean vis[][],int i,int j,int id){

        vis[i][j]=true;
        if(id==word.length()-1){

            ans=true;
            return;
        }
        for(int k=0;k&lt;4;k++){

            int newi=i+dir[k][0];
            int newj=j+dir[k][1];
            if(0&lt;=newi&amp;&amp;newi&lt;rows&amp;&amp;0&lt;=newj&amp;&amp;newj&lt;cols
            &amp;&amp;!vis[newi][newj]&amp;&amp;board[newi][newj]==word.charAt(id+1)){

                bfs(board,vis,newi,newj,id+1);
                vis[newi][newj]=false;
            }
        }
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136842883</p>]]></description><guid isPermaLink="false">/archives/c3c16b64-ac93-47ee-85e9-f6aecda5a593</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:24:12 GMT</pubDate></item><item><title><![CDATA[day-25 两数相加]]></title><link>https://wiki.dgfygk.com/archives/7f7e4bc6-a446-49b8-99b1-dd42c2e84a4a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-25%20%E4%B8%A4%E6%95%B0%E7%9B%B8%E5%8A%A0&amp;url=/archives/7f7e4bc6-a446-49b8-99b1-dd42c2e84a4a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5ea26859118c30a2e414df8a8d04bee3.png&amp;size=m" alt=""></p>
<p>思路：因为两个数字是逆序的，所以只需将对应位置的值相加，有进位则进位，再将结果利用头插法存入一个新的链表</p>
<p>注意点：两个链表不一定一样长</p>
<p>code:</p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {

ListNode head=new ListNode(-1);
ListNode thead=head;
int add=0;
while(l1!=null&amp;&amp;l2!=null){

    ListNode t=new ListNode();
    t.val=(l1.val+l2.val+add)%10;
    add=(l1.val+l2.val+add)/10;
    thead.next=t;
    thead=thead.next;
    l1=l1.next;
    l2=l2.next;
}
while(l1!=null){

    ListNode t=new ListNode();
    t.val=(l1.val+add)%10;
    add=(l1.val+add)/10;
    thead.next=t;
    thead=thead.next;
    l1=l1.next;
}
while(l2!=null){

    ListNode t=new ListNode();
    t.val=(l2.val+add)%10;
    add=(l2.val+add)/10;
    thead.next=t;
    thead=thead.next;
    l2=l2.next;
}
if(add!=0){

    ListNode t=new ListNode();
    t.val=add;
    thead.next=t;
    thead=thead.next;
}
return head.next;

    }

}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136921774</p>]]></description><guid isPermaLink="false">/archives/7f7e4bc6-a446-49b8-99b1-dd42c2e84a4a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:24:04 GMT</pubDate></item><item><title><![CDATA[day-22 岛屿数量]]></title><link>https://wiki.dgfygk.com/archives/ac7b1e29-1ac1-433a-b938-e25ca896517a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-22%20%E5%B2%9B%E5%B1%BF%E6%95%B0%E9%87%8F&amp;url=/archives/ac7b1e29-1ac1-433a-b938-e25ca896517a" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fff1667feb2faeb616bcf4865dbd6966e.png&amp;size=m" alt="">
 <br>
 参考了答案。。。。。。</p>
<p>思路：深度优先遍历，对每个是陆地且未曾访问过的位置进行dfs，每进行一次，岛屿数量加一</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    private static final int[][] dir = {
   {
   -1, 0}, {
   0, -1}, {
   1, 0}, {
   0, 1}};
    private boolean[][] visited;
    private int rows;
    private int cols;
    private char[][] grid;

    public int numIslands(char[][] grid) {

        rows=grid.length;
        if(rows==0){

            return 0;
        }
        cols=grid[0].length;

        this.grid=grid;
        visited=new boolean[rows][cols];
        int count=0;
        for(int i=0;i&lt;rows;i++){

            for(int j=0;j&lt;cols;j++){

                if(!visited[i][j]&amp;&amp;grid[i][j]=='1'){

                    dfs(i,j);
                    count++;
                }
            }
        }
        return count;
    }

    public void dfs(int i,int j){

        visited[i][j]=true;
        for(int k=0;k&lt;4;k++){

            int newx=i+dir[k][0];
            int newy=j+dir[k][1];
            if(0&lt;=newx&amp;&amp;newx&lt;rows&amp;&amp;0&lt;=newy&amp;&amp;newy&lt;cols&amp;&amp;!visited[newx][newy]&amp;&amp;grid[newx][newy]=='1'){

                dfs(newx,newy);
            }
        }
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136790821</p>]]></description><guid isPermaLink="false">/archives/ac7b1e29-1ac1-433a-b938-e25ca896517a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:23:53 GMT</pubDate></item><item><title><![CDATA[day-23 岛屿的最大面积]]></title><link>https://wiki.dgfygk.com/archives/21960e8a-f43b-4d2e-93c2-78d5cfd28634</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-23%20%E5%B2%9B%E5%B1%BF%E7%9A%84%E6%9C%80%E5%A4%A7%E9%9D%A2%E7%A7%AF&amp;url=/archives/21960e8a-f43b-4d2e-93c2-78d5cfd28634" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2c676d1877e8166d1ce53ff7fa2af52d.png&amp;size=m" alt="">
 <br>
 思路：深度优先遍历，对每个是陆地且未曾访问过的位置进行dfs，统计每次进行dfs时的岛屿面积are，ans=Math.max(ans,are)</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int dir[][]={
   {
   0,1},{
   0,-1},{
   1,0},{
   -1,0}};
    public boolean vis[][];
    public int cows;
    public int cols;
    public int grid[][];
    public int ans=0;
    public int are=1;
    public int maxAreaOfIsland(int[][] grid) {

        cows=grid.length;
        if(cows==0)return 0;
        cols=grid[0].length;
        this.grid=grid;
        vis=new boolean[cows][cols];
        for(int i=0;i&lt;cows;i++){

            for(int j=0;j&lt;cols;j++){

                if(!vis[i][j]&amp;&amp;grid[i][j]==1){

                    dfs(i,j);
                    ans=Math.max(ans,are);
                    are=1;
                }
            }
        }
        return ans;
    }
    public void dfs(int i,int j){

        vis[i][j]=true;
        for(int k=0;k&lt;4;k++){

            int newx=i+dir[k][0];
            int newy=j+dir[k][1];
            if(0&lt;=newx&amp;&amp;newx&lt;cows&amp;&amp;0&lt;=newy&amp;&amp;newy&lt;cols&amp;&amp;!vis[newx][newy]
            &amp;&amp;grid[newx][newy]==1){

                are=are+1;
                dfs(newx,newy);
            }
        }
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136797597</p>]]></description><guid isPermaLink="false">/archives/21960e8a-f43b-4d2e-93c2-78d5cfd28634</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:23:45 GMT</pubDate></item><item><title><![CDATA[day-23 分发糖果]]></title><link>https://wiki.dgfygk.com/archives/1d86056f-c79e-4ea8-b3f5-e44290f1f2af</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-23%20%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C&amp;url=/archives/1d86056f-c79e-4ea8-b3f5-e44290f1f2af" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd69f004c2dbc41c6cf4c4bb91d256c7e.png&amp;size=m" alt=""></p>
<p>参考了答案。。。。。。</p>
<p>思路：所有位置的糖果数应该满足左规则和右规则 左规则：如果ratings[i]&gt;ratings[i-1]，则应该比左边多一颗糖果 右规则：如果ratings[i]&gt;ratings[i+1]，则应该比右边多一颗糖果 最终糖果数为ans[i]=Math.max(left[i],right[i])</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int candy(int[] ratings) {

        int n=ratings.length;
        int left[]=new int[n];
        int right[]=new int[n];
        int ans[]=new int[n];
        Arrays.fill(left,1);
        for(int i=1;i&lt;n;i++){
   //满足左规则
            if(ratings[i]&gt;ratings[i-1]){

                left[i]=left[i-1]+1;
            }
        }
        for(int i=0;i&lt;n;i++){

            right[i]=left[i];
        }
        for(int i=n-2;i&gt;=0;i--){
   //满足右规则
            if(ratings[i]&gt;ratings[i+1]){

                right[i]=right[i+1]+1;
            }
        }
        for(int i=0;i&lt;n;i++){

            ans[i]=Math.max(left[i],right[i]);
        }
        int total=0;
        for(int i=0;i&lt;n;i++){

            total+=ans[i];
        }
        return total;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136801833</p>]]></description><guid isPermaLink="false">/archives/1d86056f-c79e-4ea8-b3f5-e44290f1f2af</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:23:38 GMT</pubDate></item><item><title><![CDATA[day-23 买卖股票的最佳时机 II]]></title><link>https://wiki.dgfygk.com/archives/f1e8221c-406d-476f-bfa8-d27d523ffb02</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-23%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%20II&amp;url=/archives/f1e8221c-406d-476f-bfa8-d27d523ffb02" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F42e254f9c48fe64d61fad52b5b1b7dc9.png&amp;size=m" alt=""></p>
<p>思路：因为要求的是最大收益，所以可以假设每天都买，第二天卖 利用一个数组来统计，如果收益为负则ans[i]=0,否则ans[i]=prices[i+1]-prices[i]</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int maxProfit(int[] prices) {

        int n=prices.length;
        int ans[]=new int[n-1];
        for(int i=0;i&lt;n-1;i++){

            if(prices[i+1]&gt;prices[i])
            ans[i]=prices[i+1]-prices[i];
            else
            ans[i]=0;
        }
        int res=0;
        for(int i=0;i&lt;n-1;i++){

            res+=ans[i];
        }
        return res;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136815606</p>]]></description><guid isPermaLink="false">/archives/f1e8221c-406d-476f-bfa8-d27d523ffb02</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:23:25 GMT</pubDate></item><item><title><![CDATA[day-23 重排链表]]></title><link>https://wiki.dgfygk.com/archives/c8502395-7266-46f7-b038-8bdb489b0604</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-23%20%E9%87%8D%E6%8E%92%E9%93%BE%E8%A1%A8&amp;url=/archives/c8502395-7266-46f7-b038-8bdb489b0604" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbc11bf87f6c90f336e6171d2328cce9b.png&amp;size=m" alt=""></p>
<p>参考了答案。。。。。。</p>
<p>思路：利用快慢指针找到中间节点，再将后半部分利用头插法倒置，最后将两部分拼接起来</p>
<p>注意：分清ListNode t=null 未分配空间 和ListNode t=new Lis他Node() 分配了空间</p>
<p>code:</p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public void reorderList(ListNode head) {

     ListNode mid=findmid(head);    //找到中间节点
     ListNode a=head;
     ListNode b=reverse(mid.next);//倒置后半部分
     mid.next=null;//将连链表划分为两部分
     merge(a,b);
    }

    public ListNode findmid(ListNode head){

        ListNode t=new ListNode();
        t.next=head;
        ListNode fast=t,slow=t;
        while(fast!=null&amp;&amp;fast.next!=null){

            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }

    public ListNode reverse(ListNode head){

      ListNode reverse=null;
      while(head!=null){

        ListNode next=head.next;
        head.next=reverse;
        reverse=head;
        head=next;
      }
      return reverse;
    }

    public ListNode merge(ListNode l1,ListNode l2){

        ListNode head=new ListNode();
        while(l1!=null&amp;&amp;l2!=null){

            head.next=l1;
            l1=l1.next;
            head=head.next;
            head.next=l2;
            l2=l2.next;
            head=head.next;
        }
        while(l1!=null){

            head.next=l1;
            l1=l1.next;
            head=head.next;
        }
        while(l2!=null){

            head.next=l2;
            l2=l2.next;
            head=head.next;
        }
        return head.next;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136808182</p>]]></description><guid isPermaLink="false">/archives/c8502395-7266-46f7-b038-8bdb489b0604</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:23:19 GMT</pubDate></item><item><title><![CDATA[day-24 腐烂的橘子]]></title><link>https://wiki.dgfygk.com/archives/cfa4dc47-5495-400d-aba5-7108a062bcb6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-24%20%E8%85%90%E7%83%82%E7%9A%84%E6%A9%98%E5%AD%90&amp;url=/archives/cfa4dc47-5495-400d-aba5-7108a062bcb6" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F908b2a74cf3c07b4db86ced847b05d6b.png&amp;size=m" alt=""></p>
<p>思路：利用bfs的思想 首先利用bfs遍历值为2的橘子（遍历时1和2都可以走）结束后如果存在某个橘子未被访问过且值为1则返回-1 再利用bfs遍历值为2的橘子 每遍历一次 时间加1 注意不要立即将map的值变为2，可以通过一个缓存数组临时存放 这样即可确定腐烂时间</p>
<p>注意点：map=grid只是把指针给map map改变grid也会改变 map=new int[rows][cols]; map=grid map改变grid不会改变</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int dir[][]={
   {
   0,1},{
   0,-1},{
   1,0},{
   -1,0}};
    public int map[][];
    public int tmap[][];
    public boolean vis[][];
    public int cows;
    public int cols;
    public int num1=0;
    public int num2=0;
    public int orangesRotting(int[][] grid) {

        cows=grid.length;
        if(cows==0) return -1;
        cols=grid[0].length;
        map=new int[cows][cols];
        tmap=new int[cows][cols];
        for(int i=0;i&lt;cows;i++){

            for(int j=0;j&lt;cols;j++){

                map[i][j]=grid[i][j];
                tmap[i][j]=grid[i][j];
            }
        }
        vis=new boolean[cows][cols];
        int ans=0;
        for(int i=0;i&lt;cows;i++){
   //判断是否存在不能腐烂的橘子
            for(int j=0;j&lt;cols;j++){

                if(!vis[i][j]&amp;&amp;map[i][j]==2){

                    istrue(i,j);
                }
            }
        }
        boolean flag=true;
        for(int i=0;i&lt;cows;i++){

            for(int j=0;j&lt;cols;j++){

                if(!vis[i][j]&amp;&amp;map[i][j]==1){

                    flag=false;
                }
            }
        }
        if(flag==false)return -1;

        flag=true;
        while(flag){

            flag=false;
            for(int i=0;i&lt;cows;i++){

                for(int j=0;j&lt;cols;j++){

                    if(map[i][j]==1){
   //还存在新鲜橘子
                        flag=true;
                        break;
                    }
                }
                if(flag==true) break;
            }
            if(flag==false) break;//不存在新鲜橘子

            for(int i=0;i&lt;cows;i++){

                for(int j=0;j&lt;cols;j++){

                    if(map[i][j]==2){

                        bfs(i,j);
                    }
                }
            }
            ans++;
            for(int i=0;i&lt;cows;i++){

                for(int j=0;j&lt;cols;j++){

                    map[i][j]=tmap[i][j];
                }
            }
        }
        return ans;
    }

    public void bfs(int i,int j){

        for(int k=0;k&lt;4;k++){

            int newi=i+dir[k][0];
            int newj=j+dir[k][1];
            if(0&lt;=newi&amp;&amp;newi&lt;cows&amp;&amp;0&lt;=newj&amp;&amp;newj&lt;cols&amp;&amp;
            tmap[newi][newj]==1){

                tmap[newi][newj]=2;//变为坏橘子
            }
        }
    }

    public void istrue(int i,int j){

        vis[i][j]=true;
        for(int k=0;k&lt;4;k++){

            int newi=i+dir[k][0];
            int newj=j+dir[k][1];
            if(0&lt;=newi&amp;&amp;newi&lt;cows&amp;&amp;0&lt;=newj&amp;&amp;newj&lt;cols&amp;&amp;(
            map[newi][newj]==1||map[newi][newj]==2)&amp;&amp;!vis[newi][newj]){

                istrue(newi,newj);
            }
        }
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136841133</p>]]></description><guid isPermaLink="false">/archives/cfa4dc47-5495-400d-aba5-7108a062bcb6</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:23:10 GMT</pubDate></item><item><title><![CDATA[day-20 组合总和]]></title><link>https://wiki.dgfygk.com/archives/f4330e74-b02d-4310-a5d5-5b7f350d6af6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-20%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C&amp;url=/archives/f4330e74-b02d-4310-a5d5-5b7f350d6af6" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F13ded062717c9101376a6fd2fc6ba1a7.png&amp;size=m" alt=""></p>
<p>参考了答案。。。。。。</p>
<p>思路：利用dfs对所有可能进行尝试，用id作为当前位置的索引，当idcandidates.length失败 当target0时，添加当前组合，直至遍历完毕</p>
<p>注意：res.add(new ArrayList(now)) 不能为res.add(now)),否则为空</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;Integer&gt;&gt; combinationSum(int[] candidates, int target) {

        List&lt;List&lt;Integer&gt;&gt; res=new ArrayList&lt;List&lt;Integer&gt;&gt;();
        List&lt;Integer&gt; now=new ArrayList&lt;Integer&gt;();
        dfs(res,now,candidates,target,0);
        return res;
    }
    public void dfs(List&lt;List&lt;Integer&gt;&gt; res,List&lt;Integer&gt; now,
    int[] candidates, int target,int id){

        if(id==candidates.length) return;
        if(target==0){

            res.add(new ArrayList&lt;Integer&gt;(now));
            return;
        }
        dfs(res,now,candidates,target,id+1);
        if(target-candidates[id]&gt;=0){

            now.add(candidates[id]);
            dfs(res,now,candidates,target-candidates[id],id);
            now.remove(now.size()-1);
        }
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136749305</p>]]></description><guid isPermaLink="false">/archives/f4330e74-b02d-4310-a5d5-5b7f350d6af6</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:23:02 GMT</pubDate></item><item><title><![CDATA[day-22 三数之和]]></title><link>https://wiki.dgfygk.com/archives/e00f453c-d845-4a34-b369-bf57d8d985b2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-22%20%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C&amp;url=/archives/e00f453c-d845-4a34-b369-bf57d8d985b2" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F982a1069573c3f19f5b328f217845165.png&amp;size=m" alt="">
 <br>
 参考了答案。。。。。。</p>
<p>思路：先对nums[]数组进行排序，i表示第一个数的的索引，i从零遍历到nums.length j表示第二个数的索引，j从i+1开始向后遍历 k表示第三个数的索引，k从nums.length-1向前开始遍历，直到j k相交</p>
<p>注意点：不能重复 所以i j不能取连续相同的数</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public List&lt;List&lt;Integer&gt;&gt; threeSum(int[] nums) {

        Arrays.sort(nums);
        List&lt;List&lt;Integer&gt;&gt; res=new ArrayList&lt;List&lt;Integer&gt;&gt;();
        for(int i=0;i&lt;nums.length;i++){

            if(i&gt;0&amp;&amp;nums[i]==nums[i-1])continue;//避免i选到相同的数字
            int j=i+1;
            int k=nums.length-1;
            while(j&lt;k){

                while (j &gt; i + 1 &amp;&amp; j &lt; nums.length &amp;&amp; nums[j] == nums[j - 1]) j++;//避免j选到相同的数字
                if (j &gt;= k) break;//j与k相交

                if(nums[i]+nums[j]+nums[k]==0){

                List&lt;Integer&gt; t=new ArrayList&lt;Integer&gt;();
                t.add(nums[i]);
                t.add(nums[j]);
                t.add(nums[k]);
                res.add(t);
                j++;
            }else if(nums[i]+nums[j]+nums[k]&gt;0){

                k--;
            }else if(nums[i]+nums[j]+nums[k]&lt;0){

                j++;
            }
            }

        }

        return res;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136787501</p>]]></description><guid isPermaLink="false">/archives/e00f453c-d845-4a34-b369-bf57d8d985b2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:22:55 GMT</pubDate></item><item><title><![CDATA[day-21 前 K 个高频元素]]></title><link>https://wiki.dgfygk.com/archives/1c73bb09-83d9-43d7-8c75-b6327b695106</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-21%20%E5%89%8D%20K%20%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0&amp;url=/archives/1c73bb09-83d9-43d7-8c75-b6327b695106" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F229cf5bb6e43a096478d360126592e99.png&amp;size=m" alt="">
 <br>
 思路：用ans[]存储频次最高的k个元素，用anslen[]存储对应的索引，将nums进行排序依次统计每个元素出现次数，再判断是否需要对ans[]和anslen[]进行替换，最后ans即为答案</p>
<p>注意点：遍历结束后，还需要进行依次判断是否替换</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int[] topKFrequent(int[] nums, int k) {

        if(nums.length==1)return nums;
        int ans[]=new int[k];
        int anslen[]=new int[k];
        Arrays.sort(nums);
        int t=nums[0];
        int len=0;
        for(int i=0;i&lt;nums.length;i++){

            if(nums[i]==t)len++;
            else{

                int min=Integer.MAX_VALUE;
                int minpo=-1;
                for(int j=0;j&lt;k;j++){

                    if(anslen[j]&lt;min){

                        min=anslen[j];
                        minpo=j;
                    }
                }
                if(len&gt;anslen[minpo]){

                    ans[minpo]=t;
                    anslen[minpo]=len;

                }
                len=1;
                    t=nums[i];
            }
        }
        int min=Integer.MAX_VALUE;
                int minpo=-1;
                for(int j=0;j&lt;k;j++){

                    if(anslen[j]&lt;min){

                        min=anslen[j];
                        minpo=j;
                    }
                }
                if(len&gt;anslen[minpo]){

                    ans[minpo]=t;
                    anslen[minpo]=len;

                }


        return ans;

    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136761652</p>]]></description><guid isPermaLink="false">/archives/1c73bb09-83d9-43d7-8c75-b6327b695106</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:22:47 GMT</pubDate></item><item><title><![CDATA[day-22 最大子数组和]]></title><link>https://wiki.dgfygk.com/archives/55fcfb5a-a19f-498a-9339-2ca0a15e1d48</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-22%20%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C&amp;url=/archives/55fcfb5a-a19f-498a-9339-2ca0a15e1d48" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4c8a9472b85bbb3c6ea21e5ea3303b2a.png&amp;size=m" alt=""></p>
<p>参考了答案。。。。。。</p>
<p>思路：动态规划，dp[i]表示以索引i结尾的最大子数组和，那么如果dp[i-1]&gt;0 dp[i]=dp[i]+nums[i] 如果dp[i-1]&lt;0 dp[i]=nums[i] ，最后dp数组中的最大值即为答案</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int maxSubArray(int[] nums) {

        int dp[]=new int[nums.length];
        dp[0]=nums[0];
        for(int i=1;i&lt;nums.length;i++){

            if(dp[i-1]&gt;0){

                dp[i]=dp[i-1]+nums[i];
            }else{

                dp[i]=nums[i];
            }
        }
        Arrays.sort(dp);
        return dp[dp.length-1];
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136781177</p>]]></description><guid isPermaLink="false">/archives/55fcfb5a-a19f-498a-9339-2ca0a15e1d48</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:22:37 GMT</pubDate></item><item><title><![CDATA[day-20 二叉树的层序遍历]]></title><link>https://wiki.dgfygk.com/archives/387ceee3-45e9-43eb-8014-a98b7b966e57</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-20%20%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86&amp;url=/archives/387ceee3-45e9-43eb-8014-a98b7b966e57" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffec1658e95b080599a34d713f523cdbc.png&amp;size=m" alt=""></p>
<p>思路：利用队列进行广度优先遍历即可</p>
<p>注意点：ArrayList执行remove之后，索引i会立即重排，注意可能越界</p>
<p>code:</p>
<pre><code class="prism language-java">/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    public List&lt;List&lt;Integer&gt;&gt; levelOrder(TreeNode root) {


        List&lt;List&lt;Integer&gt;&gt; res=new ArrayList&lt;List&lt;Integer&gt;&gt;();
        if(root==null) return res;
        List&lt;TreeNode&gt; queue = new ArrayList&lt;TreeNode&gt;();
        queue.add(root);
        while(queue.size()&gt;0){

            int size=queue.size();
           List&lt;Integer&gt; t = new ArrayList&lt;Integer&gt;();
            for(int i=0;i&lt;size;i++){

                t.add(queue.get(i).val);
                if(queue.get(i).left!=null){

                    queue.add(queue.get(i).left);
                }
                if(queue.get(i).right!=null){

                    queue.add(queue.get(i).right);
                }  
            }
            for(int i=0;i&lt;size;i++){

                queue.remove(0);
            }
            res.add(t);
        }
        return res;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136743074</p>]]></description><guid isPermaLink="false">/archives/387ceee3-45e9-43eb-8014-a98b7b966e57</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:22:22 GMT</pubDate></item><item><title><![CDATA[day-17 加油站]]></title><link>https://wiki.dgfygk.com/archives/7904877b-9537-439c-86ed-48fbc08e9157</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-17%20%E5%8A%A0%E6%B2%B9%E7%AB%99&amp;url=/archives/7904877b-9537-439c-86ed-48fbc08e9157" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F54f41fc17684d95445315f2b1e6b6375.png&amp;size=m" alt="">
 <br>
 自己写的时间复杂度O（n²）时间超时了
 <br>
 code</p>
<pre><code class="prism language-java">class Solution {

    public int canCompleteCircuit(int[] gas, int[] cost) {

        int n=gas.length;
        int ans=-1;
        for(int i=0;i&lt;n;i++){

            if(gas[i]&lt;cost[i])continue;
            else{

                int have=0;
                boolean flag=true;
                for(int j=i;j&lt;n+i;j++){

                    if(have+gas[j%n]-cost[j%n]&lt;0){

                        flag=false;
                        break;
                    }
                    have=have+gas[j%n]-cost[j%n];
                }
                if(flag==true){

                    ans=i;
                    break;
                }
            }
        }
        return ans;
    }
}
</code></pre>
<p>答案code</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136585120</p>]]></description><guid isPermaLink="false">/archives/7904877b-9537-439c-86ed-48fbc08e9157</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:22:13 GMT</pubDate></item><item><title><![CDATA[day-18 猜数字游戏]]></title><link>https://wiki.dgfygk.com/archives/fd94cde1-e255-49d8-b055-5bfa2283aed4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-18%20%E7%8C%9C%E6%95%B0%E5%AD%97%E6%B8%B8%E6%88%8F&amp;url=/archives/fd94cde1-e255-49d8-b055-5bfa2283aed4" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F445e778b7d594d1cca176d65f2824d31.png&amp;size=m" alt="">
 <br>
 1.由于两个数字位数相同，所以可以遍历字符串吧，找出公牛个数
 <br>
 2.在通过两个数组分别统计两个数字0-9的个数
 <br>
 3.Math.min(arr1[i],arr2[i])即为对应数字的奶牛个数
 <br>
 code</p>
<pre><code class="prism language-java">class Solution {

    public String getHint(String secret, String guess) {

        int arr1[]=new int[10];
        int arr2[]=new int[10];
        int n=secret.length();
        int a=0,b=0;
        for(int i=0;i&lt;n;i++){

            if(secret.charAt(i)==guess.charAt(i)){

                a++;
            }else{

                arr1[secret.charAt(i)-'0']++;
                arr2[guess.charAt(i)-'0']++;
            }  
        }
        for(int i=0;i&lt;10;i++){

                b=b+Math.min(arr1[i],arr2[i]);
            }
        String ans=""+a+"A"+b+"B";
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136597305</p>]]></description><guid isPermaLink="false">/archives/fd94cde1-e255-49d8-b055-5bfa2283aed4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:22:03 GMT</pubDate></item><item><title><![CDATA[day-18 轮转数组]]></title><link>https://wiki.dgfygk.com/archives/c8be4187-b1b5-469b-ac4c-2c5655209ddf</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-18%20%E8%BD%AE%E8%BD%AC%E6%95%B0%E7%BB%84&amp;url=/archives/c8be4187-b1b5-469b-ac4c-2c5655209ddf" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe0a25a54892c8f50da5a5a97ca6d1d83.png&amp;size=m" alt="">
 <br>
 时间复杂度为O（n）
 <br>
 code:</p>
<pre><code class="prism language-java">class Solution {

    public void rotate(int[] nums, int k) {

        int n=nums.length;
        k=k%n;
        int arr[]=new int[n];
        for(int i=0;i&lt;n;i++){

            arr[(i+k)%n]=nums[i];
        }
        for(int i=0;i&lt;n;i++){

            nums[i]=arr[i];
        }
    }
}
</code></pre>
<p>参考答案 进行三次翻转 空间复杂度O（1）
 <br>
 code:</p>
<pre><code class="prism language-java">class Solution {

    public void rotate(int[] nums, int k) {

        k %= nums.length;
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }
    public void reverse(int[] nums, int start, int end) {

        while (start &lt; end) {

            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start += 1;
            end -= 1;
        }
    }
}

作者：御三五 🥇
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136581783</p>]]></description><guid isPermaLink="false">/archives/c8be4187-b1b5-469b-ac4c-2c5655209ddf</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:21:36 GMT</pubDate></item><item><title><![CDATA[day-18 跳跃游戏]]></title><link>https://wiki.dgfygk.com/archives/4ca6d97d-3d8e-4614-9107-cf4e37c0fd1e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-18%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F&amp;url=/archives/4ca6d97d-3d8e-4614-9107-cf4e37c0fd1e" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2382438790b5fcdd9a586ea2d2dfbdc8.png&amp;size=m" alt="">
 <br>
 参考了答案，遍历整个数组，同时用一个变量记录可以到达的最远距离ans=Math.max(ans,i+nums[i])；遍历结束后如果可以到达的最远距离大于等于数组长度减一（从零开始的）则可以到达最后一个下标。</p>
<p>code</p>
<pre><code class="prism language-java">class Solution {

    public boolean canJump(int[] nums) {

        int ans=0;//最远可达
        for(int i=0;i&lt;nums.length;i++){

            if(ans&gt;=i){

                ans=Math.max(ans,i+nums[i]);
            }else{

                break;
            }
        }
        if(ans&gt;=nums.length-1) return true;
        return false;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136652986</p>]]></description><guid isPermaLink="false">/archives/4ca6d97d-3d8e-4614-9107-cf4e37c0fd1e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:21:26 GMT</pubDate></item><item><title><![CDATA[day-18 长度最小的子数组]]></title><link>https://wiki.dgfygk.com/archives/c1f83f58-5da8-49cf-a1ab-1f5d48967867</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-18%20%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84&amp;url=/archives/c1f83f58-5da8-49cf-a1ab-1f5d48967867" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8540cb4d089bca1183007583af897d6e.png&amp;size=m" alt="">
 <br>
 运用队列的思维，求出每种满足题意的子数组长度，最小的即为答案，否则返回0
 <br>
 code</p>
<pre><code class="prism language-java">class Solution {

    public int minSubArrayLen(int target, int[] nums) {

        int l=0,r=0;
        int ans=Integer.MAX_VALUE;
        int total=0;
        while(r&lt;nums.length){

            total+=nums[r++];
            if(total&gt;=target){

                while(total-nums[l]&gt;=target){

                    total=total-nums[l++];
                }
                ans=Math.min(ans,r-l);
            }
        }
        if(ans==Integer.MAX_VALUE) return 0;
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136605133</p>]]></description><guid isPermaLink="false">/archives/c1f83f58-5da8-49cf-a1ab-1f5d48967867</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:21:15 GMT</pubDate></item><item><title><![CDATA[day-20 接雨水]]></title><link>https://wiki.dgfygk.com/archives/060209e1-f689-4dba-9e86-c20ad50f03fa</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-20%20%E6%8E%A5%E9%9B%A8%E6%B0%B4&amp;url=/archives/060209e1-f689-4dba-9e86-c20ad50f03fa" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F85655a292f8c638ffb9855a1e334f828.png&amp;size=m" alt="">
 <br>
 参考了答案。。。。。。</p>
<p>思路：用两个数组分别记录height[i]左右两边最高的墙（i0和iheight.length-1不可能存水）遍历heigth<a href="https://wiki.dgfygk.com/i%E4%BB%8E1%E5%88%B0height.length-2">i</a>，如果Math.min(maxl[i],maxr[i])&gt;height[i] 则当前位置可存储 Math.min(maxl[i],maxr[i])-height[i]的水。</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int trap(int[] height) {

      int maxl[]=new int[height.length];
      int maxr[]=new int[height.length];
      for(int i=1;i&lt;height.length;i++){

        maxl[i]=Math.max(maxl[i-1],height[i-1]);
      }
      for(int i=height.length-2;i&gt;=0;i--){

        maxr[i]=Math.max(maxr[i+1],height[i+1]);
      }
      int ans=0;
      for(int i=1;i&lt;height.length-1;i++){

        if(Math.min(maxl[i],maxr[i])&gt;height[i])
        ans+=Math.min(maxl[i],maxr[i])-height[i];
      }
      return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136727906</p>]]></description><guid isPermaLink="false">/archives/060209e1-f689-4dba-9e86-c20ad50f03fa</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:21:06 GMT</pubDate></item><item><title><![CDATA[day-20 括号生成]]></title><link>https://wiki.dgfygk.com/archives/8c0fdd0d-4e34-4e1a-85af-f7903cc20588</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-20%20%E6%8B%AC%E5%8F%B7%E7%94%9F%E6%88%90&amp;url=/archives/8c0fdd0d-4e34-4e1a-85af-f7903cc20588" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7b23a4d562494116d85c89a942d4a8ad.png&amp;size=m" alt="">
 <br>
 思路:用dfs遍历所有的可能的括号组合，再通过istrue（）方法判断是否合法</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    List&lt;String&gt; ans=new ArrayList&lt;&gt;();
    public List&lt;String&gt; generateParenthesis(int n) {

        int l=n,r=n;
        String s="";
        dep(s,l,r);
        return ans;
    }
    public void dep(String s,int l,int r){

        if(l==0&amp;&amp;r==0){

            if(istrue(s))
            ans.add(s);
        }else{

            if(l!=0){

                String ts=s+'(';
                dep(ts,l-1,r);
            }
            if(r!=0){

                String ts=s+')';
                dep(ts,l,r-1);
            }
        }
    }
    public boolean istrue(String s){

        int len=s.length();
        int ans=0;
        for(int i=0;i&lt;len;i++){

            if(s.charAt(i)=='('){

                if(ans&gt;0) return false;
                ans--;
            }else{

                if(ans&gt;=0) return false;
                else{

                    ans++;
                }
            }
        }
        if(ans!=0) return false;
        return true;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136736431</p>]]></description><guid isPermaLink="false">/archives/8c0fdd0d-4e34-4e1a-85af-f7903cc20588</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:20:57 GMT</pubDate></item><item><title><![CDATA[day-20 跳跃游戏 II]]></title><link>https://wiki.dgfygk.com/archives/7f2eef2e-bc57-488c-be2c-21bbf644dce9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-20%20%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F%20II&amp;url=/archives/7f2eef2e-bc57-488c-be2c-21bbf644dce9" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6888763d797a2376f5442072446eb16f.png&amp;size=m" alt="">
 <br>
 思路：用一个数字来存储到对应索引i的最少跳跃次数，ans[j]=Math.min(ans[j],ans[i]+1)</p>
<p>code:</p>
<pre><code class="prism language-java">class Solution {

    public int jump(int[] nums) {

        int n=nums.length;
        int ans[]=new int[n];
        for(int i=0;i&lt;n;i++){

            ans[i]=Integer.MAX_VALUE;
        }
        ans[0]=0;
        for(int i=0;i&lt;n-1;i++){

            for(int j=i+1;j&lt;=i+nums[i]&amp;&amp;j&lt;n;j++){

                ans[j]=Math.min(ans[j],ans[i]+1);
            }
        }
        return ans[n-1];
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136739298</p>]]></description><guid isPermaLink="false">/archives/7f2eef2e-bc57-488c-be2c-21bbf644dce9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:20:47 GMT</pubDate></item><item><title><![CDATA[算法笔记1]]></title><link>https://wiki.dgfygk.com/archives/a08f3bf0-3394-47ae-85c0-ce589f88a8a2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%AE%97%E6%B3%95%E7%AC%94%E8%AE%B01&amp;url=/archives/a08f3bf0-3394-47ae-85c0-ce589f88a8a2" width="1" height="1" alt="" style="opacity:0;">
<p><strong>HashMap</strong> （会根据key值自动排序）
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa67b90ad8051bd5827bc8fc7abfef166.png&amp;size=m" alt=""></p>
<p>HashMap<string, integer> hash = new HashMap&lt;&gt;() hash.put(15,18)</string,></p>
<p>hash.getOrDefault(ts, -1) //如果ts(key)存在，返回对应的value 否则返回-1</p>
<p>hashMap1.get(words1[i])==1会报错，因为可能返回null 所以可以使用hashMap1.getOrDefault(words1[i],-1)==1</p>
<p>putIfAbsent() :不存在才添加</p>
<p>List&lt;&gt; list=new ArrayList&lt;&gt;(map.values()) //map的valus转换为List</p>
<p>for(int x:map.keySet()){} //遍历key值</p>
<p>int max=Collections.max(map.values()) //获取最大值</p>
<p><strong>Set</strong> （不允许重复元素）
 <br>
 Set dictionarySet = new HashSet&lt;&gt;() dictionarySet.add(word)</p>
<p>dictionarySet.contains(s.substring(j, i + 1)) set中是否有字符串 s[j]–s[i]</p>
<pre><code class="prism language-java">add(E e): 向集合中添加元素，如果集合中已有该元素，则不会添加。

remove(Object o): 从集合中移除元素。

contains(Object o): 判断集合中是否包含指定元素。

size(): 返回集合中元素的数量。

isEmpty(): 判断集合是否为空。

clear(): 清空集合中的所有元素。

iterator(): 返回一个迭代器，用于遍历集合中的元素。

equals(Object o): 判断集合与指定对象是否相等。

hashCode(): 返回集合的哈希码值
</code></pre>
<p><strong>List</strong>
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5d9b51d4dc745d5346b5304a6e3070e3.png&amp;size=m" alt="">
 <br>
 remove()后，所有i会立即重新排序
 <br>
 Collections.sort（list） List链表排序
 <br>
 Collections.reverse(list) 倒置链表</p>
<p>int []array = new Integer[list.size()]; //List转化为数组
 <br>
 自定义int [][]twoDimArray = listTo2DArray(listOfLists); // 转换为二维数组</p>
<pre><code class="prism language-java">public static int[][] listTo2DArray(List&lt;List&lt;Integer&gt;&gt; listOfLists) {

        int rows = listOfLists.size();
        int cols = 0;
        for (List&lt;Integer&gt; list : listOfLists) {

            cols = Math.max(cols, list.size()); // 获取最大列数
        }

        int[][] twoDimArray = new int[rows][cols];
        for (int i = 0; i &lt; rows; i++) {

            List&lt;Integer&gt; rowList = listOfLists.get(i);
            for (int j = 0; j &lt; rowList.size(); j++) {

                twoDimArray[i][j] = rowList.get(j);
            }
        }
        return twoDimArray;
    }
</code></pre>
<p><strong>String</strong>
 <br>
 substring(int beginIndex)：从指定索引位置 beginIndex（包括）开始截取字符串的剩余部分。</p>
<p>substring(int beginIndex, int endIndex)：从指定索引位置 beginIndex（包括）开始截取字符串，直到索引位置 endIndex（不包括）为止。</p>
<p>String str = “Hello World”;
 <br>
 char ch = str.charAt(4); // 获取第5个位置上的字符（从0开始计算）</p>
<p>char[] charArray = s.toCharArray(); //字符串转char数组
 <br>
 Arrays.sort(charArray); //字符数组也可以排序</p>
<p>String ss = “,aa,bb,cc,dd,“;
 <br>
 String[] array = ss.split(“,“); //只去掉一个,
 <br>
 String arr[]=s.split(“[\s]+“); //去掉所有连续任意多个空格（不包括开头、结尾）</p>
<p>string .trim() s.trim()是去掉首尾空格</p>
<p>str.replace(” “,““); 去掉所有空格，包括首尾、中间</p>
<p>使用 reverse 将字符串数组进行反转 Collections.reverse(wordList); 需要list</p>
<p>使用 join 方法将字符串数组拼成一个字符串 String.join(” “, wordList);</p>
<p>数组
 <br>
 Arrays.sort(beans)
 <br>
 Arrays.fill(array, 10); // 将数组所有元素填充为10</p>
<p>Arrays.sort(boxTypes, (a, b) -&gt; a[0] - b[0]) 把二维数组中每行数组中的第一个数按从小到大的顺序进行比较。
 <br>
 Arrays.sort(boxTypes, (a, b) -&gt; b[3] - a[3]) 把二维数组中每行数组中的第三个数按从大到小的顺序进行比较。</p>
<p>不足补零：
 <br>
 int number = 123;
 <br>
 String formatted = String.format(“%05d”, number);
 <br>
 System.out.println(formatted); // 输出：00123</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc974e0bad4f8420b91ff992825148fcb.png&amp;size=m" alt=""></p>
<p>String s=Integer.toString(x) //整型变为字符串
 <br>
 int num=(int)longnum //强制转换</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135489609</p>]]></description><guid isPermaLink="false">/archives/a08f3bf0-3394-47ae-85c0-ce589f88a8a2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:20:38 GMT</pubDate></item><item><title><![CDATA[day-19 合并后数组中的最大元素]]></title><link>https://wiki.dgfygk.com/archives/5584a56a-19fd-4c82-a73a-80bba3b2a6c8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-19%20%E5%90%88%E5%B9%B6%E5%90%8E%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0&amp;url=/archives/5584a56a-19fd-4c82-a73a-80bba3b2a6c8" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F959991a4e16f0f4738e2605e62bfa622.png&amp;size=m" alt="">
 <br>
 思路：从后向前遍历数组，用tans记录每一种可能的最大值，ans为实际最大值。
 <br>
 注意：若ans==0,返回nums[0] 要用long</p>
<p>code</p>
<pre><code class="prism language-java">class Solution {

    public long maxArrayValue(int[] nums) {

         long ans=0;
         long tans=0;
         boolean flag=true;
         for(int i=nums.length-1;i&gt;0;i--){

            if(flag==true&amp;&amp;nums[i]&gt;=nums[i-1]){

                flag=false;
                tans=nums[i]+nums[i-1];
                ans=Math.max(ans,tans);
            }else{

                if(tans&gt;=nums[i-1]){

                    tans+=nums[i-1];
                    ans=Math.max(ans,tans);
                }else{

                    ans=Math.max(nums[i-1],ans);
                    flag=true;
                    tans=0;
                }
            }

         }
         if(ans==0) return nums[0];
         return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136720337</p>]]></description><guid isPermaLink="false">/archives/5584a56a-19fd-4c82-a73a-80bba3b2a6c8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:20:28 GMT</pubDate></item><item><title><![CDATA[day-16 找出美丽数组的最小和]]></title><link>https://wiki.dgfygk.com/archives/82a4a02f-3562-4d0c-be63-b61dd2c0e68e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-16%20%E6%89%BE%E5%87%BA%E7%BE%8E%E4%B8%BD%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E5%92%8C&amp;url=/archives/82a4a02f-3562-4d0c-be63-b61dd2c0e68e" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffefa9fe2a15c61c94703c5325429790c.png&amp;size=m" alt="">
 <br>
 code</p>
<pre><code class="prism language-java">class Solution {

    public int minimumPossibleSum(int n, int target) {

        final int mod = (int) 1e9 + 7;
        int m = target / 2;
        if (n &lt;= m) {

            return (int) ((1L + n) * n / 2 % mod);
        }
        long a = (1L + m) * m / 2 % mod;
        long b = ((1L * target + target + n - m - 1) * (n - m) / 2) % mod;
        return (int) ((a + b) % mod);
    }
}
</code></pre>
<p>参考了答案，自己写的超时了</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136572553</p>]]></description><guid isPermaLink="false">/archives/82a4a02f-3562-4d0c-be63-b61dd2c0e68e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:20:19 GMT</pubDate></item><item><title><![CDATA[day-16 最大交换]]></title><link>https://wiki.dgfygk.com/archives/e20be2fa-34fa-4147-a597-ec06b4ea932c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-16%20%E6%9C%80%E5%A4%A7%E4%BA%A4%E6%8D%A2&amp;url=/archives/e20be2fa-34fa-4147-a597-ec06b4ea932c" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6dd3396f421dd04fd9caf3447196701c.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 用一个数组存储各数位上的数字，然后从最高位开始依次检查是否存在低位数位上的数大于自己，有则交换</p>
<p>解题方法
 <br>
 用一个数组存储各数位上的数字
 <br>
 求出num的位数
 <br>
 注意：当数位上的数字相等时，可能交换，也可能不交换，注意分类讨论</p>
<p>时间复杂度,： O(n²)
 <br>
 空间复杂度,： O(n)</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

    public static int maximumSwap(int num) {

        int arr[]=new int[8];
        int i=0;
        while(num!=0){

            arr[i]=num%10;
            num=num/10;
            i++;
        }
        i=7;
        while(arr[i]==0){

            i--;
        }
        int len=i+1;
        int j,max;
        for(i=len-1;i&gt;0;i--){

            boolean flag=true;
            max=arr[i];
            int po=i;
            for( j=i-1;j&gt;=0;j--){

                if(arr[j]&gt;max||(arr[j]==max&amp;&amp;flag==false)){

                    po=j;
                    max=arr[po];
                    flag=false;
                }
            }
            if(flag==false){

                int t=arr[i];
                arr[i]=arr[po];
                arr[po]=t;
                break;
            }
        }
        int ans=0;
        for(i=len-1;i&gt;=0;i--){

            ans=ans+f(i)*arr[i];
        }
        return ans;
    }

    static int f(int num){

        int ans=1;
        for(int i=0;i&lt;num;i++){

            ans=ans*10;
        }
        return ans;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135760087</p>]]></description><guid isPermaLink="false">/archives/e20be2fa-34fa-4147-a597-ec06b4ea932c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:20:08 GMT</pubDate></item><item><title><![CDATA[day-17 删除有序数组中的重复项 II]]></title><link>https://wiki.dgfygk.com/archives/59e03788-6408-4aa6-a856-b732b15ba118</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-17%20%E5%88%A0%E9%99%A4%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E9%A1%B9%20II&amp;url=/archives/59e03788-6408-4aa6-a856-b732b15ba118" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1e6ae066686fa07c07371593fcbce1e7.png&amp;size=m" alt="">
 <br>
 没做出来，参考了官方答案,利用双指针
 <br>
 code:</p>
<pre><code class="prism language-java">class Solution {

    public int removeDuplicates(int[] nums) {

        int n = nums.length;
        if (n &lt;= 2) {

            return n;
        }
        int slow = 2, fast = 2;
        while (fast &lt; n) {

            if (nums[slow - 2] != nums[fast]) {

                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }
        return slow;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136579002</p>]]></description><guid isPermaLink="false">/archives/59e03788-6408-4aa6-a856-b732b15ba118</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:19:56 GMT</pubDate></item><item><title><![CDATA[Vue3-01]]></title><link>https://wiki.dgfygk.com/archives/bc44818f-eaf2-404d-b605-c5686e0fcf1e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue3-01&amp;url=/archives/bc44818f-eaf2-404d-b605-c5686e0fcf1e" width="1" height="1" alt="" style="opacity:0;">
<p>Vue.js （通常简称为 Vue）是一套用于构建用户界面的渐进式框架。与其他框架不同的是，Vue采用了自底向上增量开发的设计。Vue的核心库只关注视图层，并且非常容易与现有项目集成。Vue也完全支持通过插件的方式进行扩展。Vue的设计理念是通过尽可能简单的API来实现响应的数据绑定和组合的视图组件。
 <br>
 Vue的核心特点包括：
 <br>
 响应的数据绑定： Vue可以将数据和DOM元素进行双向绑定，使数据的变化可以自动反映在视图上。
 <br>
 组件化：Vue提供了一种组合视图组件的方式，可以方便地构建复杂的用户界面。
 <br>
 虚拟DOM：Vue利用虚拟DOM技术对DOM操作进行优化，提高渲染性能。
 <br>
 渐进式框架：Vue可以逐步引入到现有项目中，也可以作为完整的前端框架使用。
 <br>
 总的来说，Vue是一个灵活、高效并且易于上手的前端框架，已经在很多项目中得到了广泛的应用。</p>
<pre><code class="prism language-typescript">import {
    createApp } from 'vue'      //创建应用
import App from './App.vue'             //引入根组件
createApp(App).mount('#app')        


&lt;template&gt;          //写html
&lt;script&gt;            //JS或者TS
&lt;style&gt;             //样式
</code></pre>
<p>Composition API(Vue3)和Options API(Vue2)的区别
 <br>
 Options API使用data属性来定义组件的数据，而Composition API使用setup函数来定义组件的状态。这是两种API的主要区别之一。另外，Composition API还提供了更多的功能和优势，例如：可以使用函数式组件、可以直接访问props、可以使用ref和computed等。这些功能可以让组件更加灵活和可维护。</p>
<p>setup是所有Composition API（组合API表演的舞台 “,组件中所用到的：数据、方法等等，均要配置在setup中。</p>
<pre><code class="prism language-typescript">    setup(){

        //数据
        let name="张三"
        let tel='18848339298'

        //方法
        function showTel(){

            alert(tel)
        }
        return {
   name,showTel}
    }
</code></pre>
<p>Vue2.x配置（data、methos、computed…）中可以访问到setup中的属性、方法
 <br>
 但在setup中不能访问到Vue2.x配置（data、methos、computed…）</p>
<p>语法糖</p>
<pre><code class="prism language-typescript">&lt;script setup lang="ts" name="Person1"&gt;
//数据
let name = "张三"
let tel = '18848339298'

//方法npm run dev
function showTel() {

    alert(tel)
}
&lt;/script&gt;
</code></pre>
<p>ref函数作用: 定义一个<em>响应式</em> 的数据(<em>基本类型</em>响应数据)</p>
<pre><code class="prism language-typescript">import {
   ref} from 'vue'
let name = ref("张三")
</code></pre>
<p>reactive函数作用: 定义一个<em>对象类型</em>的响应式数据（基本类型不要用它，要用ref函数）</p>
<pre><code class="prism language-typescript">import {
   reactive} from 'vue'
let car=reactive({
   brand:'奔驰',price:100})
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136102030</p>]]></description><guid isPermaLink="false">/archives/bc44818f-eaf2-404d-b605-c5686e0fcf1e</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 09:19:45 GMT</pubDate></item><item><title><![CDATA[Vue后台管理系统笔记-01]]></title><link>https://wiki.dgfygk.com/archives/15c2457d-2014-43e0-80b8-c01b6f197850</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F%E7%AC%94%E8%AE%B0-01&amp;url=/archives/15c2457d-2014-43e0-80b8-c01b6f197850" width="1" height="1" alt="" style="opacity:0;">
<p>npm（Node Package Manager）和 yarn 是两个常用的包管理工具，用于在 Node.js 项目中安装、管理和更新依赖项。它们有以下几个区别：
 <br>
 性能和速度：在包的安装和下载方面，yarn 通常比 npm 更快速。yarn 使用了并行下载和缓存等优化策略，可以提供更快的安装速度。
 <br>
 缓存机制：yarn 具有更强大的缓存机制，能够更好地利用缓存，减少重复下载。这对于团队协作和构建机器上的重复构建是有益的，可以节省时间和带宽。
 <br>
 安全性：yarn 在包的下载和安装过程中更加注重安全性。它通过使用 yarn.lock 文件来锁定依赖项的版本，确保在不同环境下使用的是相同的依赖版本，从而减少由于依赖项版本不一致导致的问题。
 <br>
 用户界面：yarn 提供了更友好和直观的命令行界面，显示更详细的信息，如安装进度、依赖关系树等。相比之下，npm 的命令行界面较为简洁。
 <br>
 生态系统：npm 是 Node.js 生态系统的默认包管理工具，拥有庞大的包库和活跃的社区支持。大多数开源项目和文档都以 npm 为主要的依赖管理工具。虽然 yarn 的用户数量在不断增加，但 npm 仍然是广泛使用的。
 <br>
 尽管存在这些区别，npm 和 yarn 的基本功能和使用方法是相似的。你可以根据个人偏好、项目需求和团队约定来选择使用 npm 还是 yarn。无论选择哪个工具，都应该确保在项目中一致使用，以避免依赖版本不一致和构建问题。</p>
<p><strong>ElementUI</strong>
 <br>
 ElementUI是一个基于Vue.js的桌面端组件库，它提供了一套丰富的UI组件，可以快速构建用户界面。ElementUI的设计风格简洁大方，同时也非常符合现代化的UI设计趋势。
 <br>
 ElementUI提供了诸如按钮、表单、弹窗、菜单、导航栏、表格等常见的UI组件，这些组件可以直接在项目中使用，也可以根据需要进行定制化。ElementUI还提供了丰富的功能组件，例如图片上传、富文本编辑器、日期选择器等，可以方便地满足各种业务需求。
 <br>
 ElementUI的组件具有良好的可重用性和可扩展性，可以根据自己的需求进行定制化开发。另外，ElementUI还提供了丰富的主题样式，可以轻松地切换不同的主题风格。
 <br>
 总之，ElementUI是一个功能丰富、易于使用和定制的桌面端组件库，可以帮助开发者快速构建美观、高效的用户界面。</p>
<p><strong>Vue Router</strong>
 <br>
 Vue Router是Vue.js官方提供的路由管理器，用于在Vue应用中实现路由功能。它通过监听URL的变化来匹配对应的组件，并将组件渲染到指定的路由出口。Vue Router具有以下特点：</p>
<ol>
 <li>嵌套路由：Vue Router支持嵌套路由，可以通过配置路由的子路由来实现组件的嵌套和嵌套路由的匹配。</li>
 <li>路由参数：可以通过配置路由参数来动态传递数据，并在组件内使用。</li>
 <li>路由导航：可以通过Vue Router提供的导航守卫功能来进行路由的拦截和控制，实现权限验证、登录验证等功能。</li>
 <li>路由传参：可以通过编程式导航来实现路由的传参，可以在组件内获取到上一个路由传递的参数。</li>
 <li>命名路由和命名视图：可以给路由和路由出口命名，方便进行路由的跳转和组件的渲染。</li>
 <li>路由懒加载：可以通过配置动态import来实现路由的懒加载，提升应用的性能。
  <br>
  总的来说，Vue Router提供了简洁、灵活的API，能够帮助我们快速构建单页应用的路由功能。它与Vue.js非常配合，能够方便地实现页面组件之间的切换和导航。</li>
</ol>
<pre><code class="prism language-javascript">import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI); //全局注入


import {
    Row, Button } from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Row); //按需注入
Vue.use(Button)
</code></pre>
<p>Vuex是一个用于Vue.js应用程序的状态管理模式。它允许我们在应用程序中集中管理和共享状态，并且可以在不同组件之间进行通信和更新状态。Vuex解决了在大型应用程序中状态分散和管理困难的问题。
 <br>
 Vuex的核心概念包括：
 <br>
 State（状态）：用于存储应用程序的所有状态（数据）。
 <br>
 Getters（获取器）：用于从状态中获取数据的计算属性。
 <br>
 Mutations（突变）：用于修改状态的方法，可以理解为是对状态的同步修改。
 <br>
 Actions（行动）：类似于突变，但是可以进行异步操作，可以用来处理业务逻辑。
 <br>
 Modules（模块）：用于将状态、突变、行动进行模块化，使得状态管理更具可维护性和可扩展性。
 <br>
 使用Vuex可以使得组件之间共享相同的状态变得简单和可预测，而不需要通过props和事件进行繁琐的传递和监听。它使我们能够在应用程序中集中处理状态的变化，并提供了一种可追踪和调试的方式来查看状态的变化历史。
 <br>
 总结来说，Vuex提供了一种更优雅和高效的方式来管理Vue.js应用程序的状态，使得应用程序更加可维护、可扩展和可测试。</p>
<pre><code class="prism language-javascript">import Vue from 'vue'
import Vuex from 'vuex'
import tab from '/tab'
Vue.use(Vuex)
//创建vuex的实例
export default new Vuex.Store({

    modules: {

        tab
    }
})
</code></pre>
<p>Ajax是一种用于创建交互式Web应用程序的技术。它是一种在不刷新整个页面的情况下，通过异步方式向服务器发送请求并接收响应的技术。
 <br>
 Ajax全称为"Asynchronous JavaScript and XML”（异步JavaScript和XML），但实际上，现代的Ajax技术已经不再局限于使用XML格式。它可以与服务器进行数据交换，不需要重新加载整个网页，并且可以在后台处理数据。
 <br>
 使用Ajax，可以通过JavaScript在后台与服务器进行通信，并根据服务器的响应更新网页的一部分。这意味着网站可以更快地响应用户的操作，并且可以提供更流畅的用户体验。
 <br>
 Ajax的主要特点包括：</p>
<ol>
 <li>异步性：Ajax请求是在后台进行的，可以在等待服务器响应的同时继续进行其他操作。</li>
 <li>实时性：Ajax可以实时更新网页内容，而无需重新加载整个页面。</li>
 <li>无需刷新：Ajax可以部分更新网页的内容，而不需要重新加载整个页面。</li>
 <li>互动性：Ajax可以响应用户的操作，并根据用户的输入实时更新网页。</li>
 <li>跨浏览器兼容性：Ajax技术可以在各种不同的浏览器上运行。
  <br>
  Ajax在构建现代Web应用程序中扮演着重要的角色，它被广泛应用于网页表单验证、数据加载、动态内容更新等方面。</li>
</ol>
<p>Axios是一个基于Promise的HTTP客户端，用于发送异步HTTP请求。它可以在浏览器和Node.js中使用，并且可以处理GET、POST、PUT、DELETE等多种HTTP请求。Axios的特点包括以下几个方面：</p>
<ol>
 <li>支持Promise：Axios基于Promise实现，可以使用async/await简化异步操作的处理，使得代码更加清晰和易于维护。</li>
 <li>跨平台：Axios可以同时在浏览器和Node.js中使用，可以方便地进行前后端开发。</li>
 <li>可拦截请求和响应：Axios提供了拦截器，可以在请求和响应过程中对数据进行拦截和处理，例如在请求中添加token，或者在响应中进行状态码的判断和处理。</li>
 <li>提供了强大的验证和错误处理机制：Axios支持自定义验证和错误处理函数，可以在请求和响应过程中对数据进行校验和处理。</li>
 <li>支持取消请求：Axios提供了取消请求的功能，可以在发送请求后取消未完成的请求，避免浪费资源。
  <br>
  总的来说，Axios是一个功能强大、易于使用的HTTP客户端库，可以简化HTTP请求的发送和处理过程，并且具有跨平台的优势。</li>
</ol>
<pre><code class="prism language-javascript">import axios from 'axios'
const http = axios.create({

        baseURL: '/api',
        timeout: 10000, //超时时间

    })
    // Add a request interceptor
http.interceptors.request.use(function(config) {

    // Do something before request is sent
    return config;
}, function(error) {

    // Do something with request error
    return Promise.reject(error);
});

// Add a response interceptor
http.interceptors.response.use(function(response) {

    // Do something with response data
    return response;
}, function(error) {

    // Do something with response error
    return Promise.reject(error);
});


export default http
</code></pre>
<p>Mock.js 是一个用于生成随机数据的 JavaScript 库。它可以在前端开发过程中，模拟后端接口的数据返回。Mock.js 可以帮助开发人员在开发过程中快速搭建前端页面，并且不依赖后端接口的数据。
 <br>
 使用 Mock.js，可以快速生成各种类型的随机数据，包括数字、字符串、布尔值、对象、数组等。Mock.js 还提供了一些常用的辅助函数，用于生成更具体的数据，如生成姓名、邮箱、手机号码等。
 <br>
 Mock.js 的使用非常简单，只需要在前端代码中引入 Mock.js，并使用其提供的 API 来定义数据模板，然后调用相应的接口即可。Mock.js 可以拦截 AJAX 请求，根据定义的数据模板返回相应的随机数据。
 <br>
 Mock.js 还提供了一些高级功能，如设置响应延迟、生成随机图片、生成随机日期等。这些功能可以帮助开发人员更好地模拟真实的数据返回，提高开发效率。
 <br>
 总结来说，Mock.js 是一个非常实用的前端开发工具，可以帮助开发人员快速生成随机数据，模拟后端接口的返回，提高开发效率。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/136290789</p>]]></description><guid isPermaLink="false">/archives/15c2457d-2014-43e0-80b8-c01b6f197850</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 09:19:32 GMT</pubDate></item><item><title><![CDATA[微信小程序-02]]></title><link>https://wiki.dgfygk.com/archives/cce32d63-631c-4434-acfc-2bde177a64c2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-02&amp;url=/archives/cce32d63-631c-4434-acfc-2bde177a64c2" width="1" height="1" alt="" style="opacity:0;">
<p><strong>swiper 和 swiper-item 组件的基本使用</strong>
 <br>
 轮播图</p>
<pre><code class="prism language-javascript">&lt;!--pages/list/list.wxml--&gt;
&lt;swiper class="swiper-container"&gt;
&lt;swiper-item&gt;
  &lt;view class="item"&gt;A&lt;/view&gt;
&lt;/swiper-item&gt;
&lt;swiper-item&gt;
  &lt;view class="item"&gt;B&lt;/view&gt;
&lt;/swiper-item&gt;
&lt;swiper-item&gt;
  &lt;view class="item"&gt;C&lt;/view&gt;
&lt;/swiper-item&gt;
&lt;/swiper&gt;

/* pages/list/list.wxss */
.swiper-container{

  height:150px;
}
.item{

  height:100%;
  line-height: 150px;
  text-align: center;
}
swiper-item:nth-child(1) .item{

  background-color: seagreen;
}
swiper-item:nth-child(2) .item{

  background-color:springgreen;
}
swiper-item:nth-child(3) .item{

  background-color:wheat;
}
</code></pre>
<p>swiper 组件的常用属性
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fca8e0e652e3d17298b05a9746fbb082b.png&amp;size=m" alt="">
 <br>
 text
 <br>
 文本组件
 <br>
 类似于 HTML 中的 span 标签，是一个行内元素
 <br>
 通过 text 组件的 selectable 属性，实现长按选中文本内容的效果</p>
<pre><code class="prism language-javascript">&lt;view&gt;
手机号支持长按选中
&lt;text selectable&gt;18884567893&lt;/text&gt;
&lt;/view&gt;
</code></pre>
<p>rich-text
 <br>
 富文本组件
 <br>
 支持把 HTML 字符串渲染为 WXML 结构</p>
<pre><code class="prism language-javascript">&lt;rich-text nodes="&lt;h1 style='color:red'&gt;标题&lt;/h1&gt;"&gt;
&lt;/rich-text&gt;
</code></pre>
<p>button
 <br>
 按钮组件
 <br>
 功能比 HTML 中的 button 按钮丰富
 <br>
 通过 open-type 属性可以调用微信提供的各种功能（客服、转发、获取用户授权、获取用户信息等
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff47ecf50b7b78b7dbeee3531ef3c8296.png&amp;size=m" alt=""></p>
<p>image
 <br>
 图片组件
 <br>
 image 组件默认宽度约 300px、高度约 240px
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Feef112dae64d5f17112b851cfd37139d.png&amp;size=m" alt=""></p>
<p>navigator
 <br>
 页面导航组件
 <br>
 类似于 HTML 中的 a 链接</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135718593</p>]]></description><guid isPermaLink="false">/archives/cce32d63-631c-4434-acfc-2bde177a64c2</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:19:16 GMT</pubDate></item><item><title><![CDATA[微信小程序开发注意事项]]></title><link>https://wiki.dgfygk.com/archives/3d82188f-23c1-44b2-b01d-616a13190683</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9&amp;url=/archives/3d82188f-23c1-44b2-b01d-616a13190683" width="1" height="1" alt="" style="opacity:0;">
<p>跳转tabBar页面时，不能使用navigator标签、 wx.navigateTo、wx.redirectTo方法跳转页面，可以使用wx.switchTab(Object object)和wx.reLaunch(Object object)</p>
<p>延时执行</p>
<pre><code class="prism language-javascript">setTimeout(function(){

  console.log("延时1秒后执行"); 
}, 1000); 
</code></pre>
<p>wxss中的本地资源图片无法通过 WXSS 获取，可以使用网络图片，或者 base64，或者使用标签。</p>
<p>自定义导航栏</p>
<pre><code class="prism language-javascript">"navigationStyle":"custom"
"renderer": "skyline",
</code></pre>
<p>border-radius设置失效：给container元素添加overflow: hidden 或 transform: translateY(0)</p>
<p>是图片充满容器</p>
<pre><code class="prism language-javascript">&lt;view class="location-img"&gt;&lt;image src="" mode=""/&gt;&lt;/view&gt;
//设置:
img{

width:100%;
height:100%
}
</code></pre>
<p>注意 e.currentTarget.dataset.value 和 e.targetarget.dataset.value</p>
<p>success不能使用this.setData</p>
<pre><code class="prism language-javascript">var that=this   //然后that.setData
</code></pre>
<p>报错：No primary or single unique constructor found for interface java.util.List</p>
<pre><code class="prism language-javascript">//添加
(@RequestParam(value = "arr") List&lt;Integer&gt; arr)
</code></pre>
<p>wx.getLocation({})不执行</p>
<pre><code class="prism language-java">//在app.json中添加
"requiredPrivateInfos": [
    "getLocation"
  ],
</code></pre>
<p>小程序做删除的时候，请求用的是delete时，按平时做GET，POST时的方式传参，后台是接收不到参数的。原因是因为传的是json格式，但是delete接口要的是query string格式的，所以就导致参数传过去，后端无法接收。 PUT也是
 <br>
 解决方法：</p>
<pre><code class="prism language-java">//1.手动拼接
wx.request({

            url: 'http://localhost:8080/personalinfo/delete?id='+that.data.personalInfo.id, //手动拼接
            method: 'DELETE',
            data: {

            },
            header: {
    'content-type': 'application/json' },
            success(res) {

            }
          })
//2.content-type用formData的
wx.request({

     url: "http://test/xxx",
     method:'DELETE',
     data:{
   id},
     header:{

         "content-type":"application/x-www-form-urlencoded"
     },
</code></pre>
<p>解决swiper轮播图不能占满</p>
<pre><code class="prism language-java">swiper {

  width: 100%;
  height: calc(100vw * 327 /  491);
}
image {

  width: 100%;
}
</code></pre>
<p>解决switchTab数据不刷新 switchTab成功跳转后调用success,此时可以拿到跳转后页面的page对象,从而调用页面onLoad方法重载页面;微信后期应该会加相应的参数来决定是否刷新跳转</p>
<pre><code class="prism language-java">wx.switchTab({

    url: '../index/index',
    success: function (e) {

        var page = getCurrentPages().pop();
        if (page == undefined || page == null) return;
        page.onLoad();
    }
})
</code></pre>
<p>获取当前时间</p>
<pre><code class="prism language-java">const now = new Date();  
    const year = now.getFullYear();  
    const month = this.formatNumber(now.getMonth() + 1);  
    const day = this.formatNumber(now.getDate());  
    const hours = this.formatNumber(now.getHours());  
    const minutes = this.formatNumber(now.getMinutes());  
    const currentTime = `${
   year}-${
   month}-${
   day} ${
   hours}:${
   minutes}`;  
    this.setData({

      time: currentTime  
    });  

formatNumber(n) {

    n = n.toString();  
    return n[1] ? n : '0' + n;  
  },
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135906675</p>]]></description><guid isPermaLink="false">/archives/3d82188f-23c1-44b2-b01d-616a13190683</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:19:06 GMT</pubDate></item><item><title><![CDATA[微信小程序-01]]></title><link>https://wiki.dgfygk.com/archives/a658b451-1925-4cf1-959e-2cfb2a5b1e72</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-01&amp;url=/archives/a658b451-1925-4cf1-959e-2cfb2a5b1e72" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序是一种运行在微信客户端的应用程序，可以在微信中直接使用，无需下载安装。微信小程序开发是指开发和构建微信小程序的过程。开发者可以使用小程序开发工具进行开发，并使用微信支付、微信登录等功能，实现各种功能和服务。
 <br>
 微信小程序开发使用的是前端技术，主要基于HTML、CSS、JavaScript进行开发。开发者可以利用小程序提供的API接口和组件，实现小程序的功能和界面设计。小程序可以实现实时通信、定位导航、音视频播放等各种功能，可以满足不同需求的用户。
 <br>
 微信小程序开发相对于传统的App开发来说，开发周期较短、成本较低，且具有更好的用户体验。用户可以直接通过微信搜索、扫描二维码等方式找到并使用小程序，无需下载安装，方便快捷。
 <br>
 微信小程序开发有一些基本的步骤：确定需求和功能、界面设计、编写代码、测试和调试、发布上线等。开发者可以根据自己的需求和技术能力选择合适的开发方式和工具，进行微信小程序开发。</p>
<p>pages 用来存放所有小程序的页面
 <br>
 utils 用来存放工具性质的模块（例如：格式化时间的自定义模块）
 <br>
 app.js 小程序项目的入口文件
 <br>
 app.json 小程序项目的全局配置文件
 <br>
 app.wxss 小程序项目的全局样式文件
 <br>
 project.config.json 项目的配置文件
 <br>
 sitemap.json 用来配置小程序及其页面是否允许被微信索引</p>
<p>WXML（WeiXin Markup Language）是小程序框架设计的一套标签语言，用来构建小程序页面的结构，其作用类似于网页开发中的 HTML。
 <br>
 WXML 和 HTML 的区别：
 <br>
 HTML （div, span, img, a）
 <br>
 WXML（view, text, image, navigator）
 <br>
 属性节点不同
 <br>
 <a href="https://wiki.dgfygk.com">超链接</a></p>
<p>提供了类似于 Vue 中的模板语法
 <br>
 数据绑定
 <br>
 列表渲染
 <br>
 条件渲染</p>
<p>WXSS (WeiXin Style Sheets)是一套样式语言，用于描述 WXML 的组件样式，类似于网页开发中的 CSS。
 <br>
 WXSS 和 CSS 的区别：
 <br>
 新增了 rpx 尺寸单位
 <br>
 CSS 中需要手动进行像素单位换算，例如 rem
 <br>
 WXSS 在底层支持新的尺寸单位 rpx，在不同大小的屏幕上小程序会自动进行换算
 <br>
 提供了全局的样式和局部样式
 <br>
 项目根目录中的 app.wxss 会作用于所有小程序页面
 <br>
 局部页面的 .wxss 样式仅对当前页面生效
 <br>
 WXSS 仅支持部分 CSS 选择器
 <br>
 .class 和 #id
 <br>
 element
 <br>
 并集选择器、后代选择器
 <br>
 ::after 和 ::before 等伪类选择器</p>
<p>小程序中的 JS 文件分为<em>三大类</em> ，分别是：
 <br>
 app.js
 <br>
 是整个小程序项目的入口文件，通过调用 App() 函数来启动整个小程序
 <br>
 页面的 .js 文件
 <br>
 是页面的入口文件，通过调用 Page() 函数来创建并运行页面
 <br>
 普通的 .js 文件
 <br>
 是普通的功能模块文件，用来封装公共的函数或属性供页面使用</p>
<p>小程序中的组件也是由宿主环境提供的，开发者可以基于组件快速搭建出漂亮的页面结构。官方把小程序的组件分为了 <em>9 大类</em> ，分别是：
 <br>
 视图容器
 <br>
 基础内容
 <br>
 表单组件
 <br>
 导航组件
 <br>
 媒体组件
 <br>
 map 地图组件
 <br>
 canvas 画布组件
 <br>
 开放能力
 <br>
 无障碍访问</p>
<p><strong>常用的视图容器类组件：</strong>
 <br>
 view
 <br>
 普通视图区域
 <br>
 类似于 HTML 中的 div，是一个块级元素
 <br>
 常用来实现页面的布局效果
 <br>
 scroll-view
 <br>
 可滚动的视图区域
 <br>
 常用来实现滚动列表效果
 <br>
 swiper 和 swiper-item
 <br>
 轮播图容器组件 和 轮播图 item 组件</p>
<pre><code class="prism language-javascript">"navigationStyle": "default"    并删除 "renderer": "skyline"       //否则会顶格
</code></pre>
<p>上下滚动</p>
<pre><code class="prism language-javascript">&lt;scroll-view class="container1" scroll-y&gt;
&lt;view&gt;A&lt;/view&gt;
&lt;view&gt;B&lt;/view&gt;
&lt;view&gt;C&lt;/view&gt;
&lt;/scroll-view&gt;

.container1 {

    height:120px;
  }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135714511</p>]]></description><guid isPermaLink="false">/archives/a658b451-1925-4cf1-959e-2cfb2a5b1e72</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:18:52 GMT</pubDate></item><item><title><![CDATA[微信小程序-03]]></title><link>https://wiki.dgfygk.com/archives/67a3ab29-6fcd-4f73-a2c9-fe9de07ee0e4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-03&amp;url=/archives/67a3ab29-6fcd-4f73-a2c9-fe9de07ee0e4" width="1" height="1" alt="" style="opacity:0;">
<p>小程序官方把 API 分为了如下 3 大类：
 <br>
 事件监听 API
 <br>
 特点：以 on 开头，用来监听某些事件的触发
 <br>
 举例：wx.onWindowResize(function callback) 监听窗口尺寸变化的事件
 <br>
 同步 API
 <br>
 特点1：以 Sync 结尾的 API 都是同步 API
 <br>
 特点2：同步 API 的执行结果，可以通过函数返回值直接获取，如果执行出错会抛出异常
 <br>
 举例：wx.setStorageSync('key', 'value') 向本地存储中写入内容
 <br>
 异步 API
 <br>
 特点：类似于 jQuery 中的 $.ajax(options) 函数，需要通过 success、fail、complete 接收调用的结果
 <br>
 举例：wx.request() 发起网络数据请求，通过 success 回调函数接收数据</p>
<p>动态绑定</p>
<pre><code class="prism language-javascript">&lt;view&gt;{
   {
   info}}&lt;/view&gt;
&lt;image src="{
   {imgSrc}}"&gt;&lt;/image&gt;
&lt;view&gt; {
   {
   randomNum&gt;=5 ?"数字大于等于5":"数字小于5"}}&lt;/view&gt;
</code></pre>
<p>事件是渲染层到逻辑层的通讯方式。通过事件可以将用户在渲染层产生的行为，反馈到逻辑层进行业务的处理
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4ee1f17ba90bdf0cf8677febd3356934.png&amp;size=m" alt="">
 <br>
 事件对象属性
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9f6c615a0b39e9b7da27c0c6dbfb48aa.png&amp;size=m" alt="">
 <br>
 target 是触发该事件的源头组件，而 currentTarget 则是当前事件所绑定的组件。</p>
<pre><code class="prism language-javascript">//在事件处理函数中为 data 中的数据赋值
countChange(){

  this.setData({

    count: this.data.count+1
  })
},


//参数传递          data-参数名            e.target.dataset.参数名
&lt;button type="primary" bindtap="btnTap2" data-info="{
   {2}}"&gt;2&lt;/button&gt;

btnTap2(e){

this.setData({

  count: this.data.count+e.target.dataset.info,
})
},



//input实时获取输入       e.detail.value
&lt;input bindinput="inputHandler"&gt;&lt;/input&gt;

inputHandler(e){

console.log(e.detail.value)
},
</code></pre>
<p>条件渲染</p>
<pre><code class="prism language-javascript">&lt;view wx:if="{
   {type==1}}"&gt;男&lt;/view&gt;
&lt;view wx:elif="{
   {type==2}}"&gt;女&lt;/view&gt;
&lt;view wx:else&gt;保密&lt;/view&gt;

//多个组件
&lt;block wx:if="{
   {type===1}}"&gt;
  &lt;view&gt;View2&lt;/view&gt;
  &lt;view&gt;View2&lt;/view&gt;
&lt;/block&gt;


&lt;view hidden="{
   {flag}}"&gt;条件为true时隐藏&lt;/view&gt;
</code></pre>
<p>wx:if 与 hidden 的对比：
 <br>
 运行方式不同
 <br>
 wx:if 以动态创建和移除元素的方式，控制元素的展示与隐藏
 <br>
 hidden 以切换样式的方式（display: none/block;），控制元素的显示与隐藏
 <br>
 使用建议
 <br>
 频繁切换时，建议使用 hidden
 <br>
 控制条件复杂时，建议使用 wx:if 搭配 wx:elif、wx:else 进行展示与隐藏的切换</p>
<p>列表渲染 wx:for 可以根据指定的数组，循环渲染重复的组件结构</p>
<pre><code class="prism language-javascript">arr1:['小红','小白','小蓝']

&lt;view wx:for="{
   {arr1}}"&gt;
  索引：{
   {
   index}},当前项：{
   {
   item}}
&lt;/view&gt;


userList:[
  {
   id:1,name:"小明"},
  {
   id:2,name:"小花"},
  {
   id:3,name:"小张"},
]

&lt;view wx:for="{
   {userList}}" wx:key="id"&gt;{
   {
   item.name}}&lt;/view&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135740674</p>]]></description><guid isPermaLink="false">/archives/67a3ab29-6fcd-4f73-a2c9-fe9de07ee0e4</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:18:37 GMT</pubDate></item><item><title><![CDATA[微信小程序-04]]></title><link>https://wiki.dgfygk.com/archives/679e1c26-17a0-45f4-ab34-cc14852cc034</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-04&amp;url=/archives/679e1c26-17a0-45f4-ab34-cc14852cc034" width="1" height="1" alt="" style="opacity:0;">
<p>rpx（responsive pixel）是微信小程序独有的，用来<em>解决屏适配</em>的尺寸单位。</p>
<p>@import 后跟需要导入的外联样式表的相对路径，用 ; 表示语句结束。</p>
<p>定义在 app.wxss 中的样式为全局样式，作用于每一个页面。
 <br>
 在页面的 .wxss 文件中定义的样式为局部样式，只作用于当前页面。
 <br>
 当局部样式和全局样式冲突时，根据就近原则，局部样式会覆盖全局样式
 <br>
 当局部样式的权重大于或等于全局样式的权重时，才会覆盖全局的样式</p>
<p>window 节点常用的配置项
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3029ada470f57fa21b9d592131505352.png&amp;size=m" alt="">
 <br>
 tabBar 是移动端应用常见的页面效果，用于实现多页面的快速切换。小程序中通常将其分为：
 <br>
 底部 tabBar
 <br>
 顶部 tabBar
 <br>
 注意：
 <br>
 tabBar中只能配置最少 2 个、最多 5 个 tab 页签
 <br>
 当渲染顶部 tabBar 时，不显示 icon（图标），只显示文本</p>
<p>tabBar 节点的配置项
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2645e9ba33effaaf04e740a0dff64943.png&amp;size=m" alt="">
 <br>
 每个 tab 项的配置选项
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdf26fc86dacd8452a89f4c2c7fa33226.png&amp;size=m" alt="">
 <br>
 <strong>页面配置和全局配置的关系</strong>
 <br>
 小程序中，app.json 中的 window 节点，可以全局配置小程序中每个页面的窗口表现。
 <br>
 如果某些小程序页面想要拥有特殊的窗口表现，此时，“页面级别的 .json 配置文件"就可以实现这种需求。</p>
<p>页面配置中常用的配置项
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F486b10f89e2b0efefb1cb0451f6779e5.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135751185</p>]]></description><guid isPermaLink="false">/archives/679e1c26-17a0-45f4-ab34-cc14852cc034</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 09:18:24 GMT</pubDate></item><item><title><![CDATA[day-13 拿出最少数目的魔法豆]]></title><link>https://wiki.dgfygk.com/archives/c62a7889-0409-4c43-92f9-f02379fbc720</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-13%20%E6%8B%BF%E5%87%BA%E6%9C%80%E5%B0%91%E6%95%B0%E7%9B%AE%E7%9A%84%E9%AD%94%E6%B3%95%E8%B1%86&amp;url=/archives/c62a7889-0409-4c43-92f9-f02379fbc720" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F868705f3aff0e96cd1410d1ef3473915.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 将beans的每个数值当做袋子最后豆子剩余数，选择取豆子最少的一种方案</p>
<p>解题方法
 <br>
 //从小到大，将每个beans[i]作为剩余豆子数
 <br>
 //对于beans[i]，i之前的全为零，i之后的全变为beans[i]
 <br>
 ans=Math.min(ans,sum-(beans.length-i)*beans[i]);//sum-(beans.length-i)*beans[i]表示取走的豆子总数</p>
<p>时间复杂度： O(n)</p>
<p>空间复杂度： O(1)</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

    public long minimumRemoval(int[] beans) {

        Arrays.sort(beans);
        long sum =0;
        for(int num:beans)sum+=num;
        long ans=sum;
        for(int i=0;i&lt; beans.length;++i){

            //从小到大，将每个beans[i]作为剩余豆子数
            //对于beans[i]，i之前的全为零，i之后的全变为beans[i]
            ans=Math.min(ans,sum-(long)(beans.length-i)*beans[i]);//sum-(beans.length-i)*beans[i]表示取走的豆子总数
        }
        return ans;
    }
}
</code></pre>
<p>注：参考了答案。。。。。。。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135683735</p>]]></description><guid isPermaLink="false">/archives/c62a7889-0409-4c43-92f9-f02379fbc720</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:18:05 GMT</pubDate></item><item><title><![CDATA[day-14 使数组和小于等于 x 的最少时间]]></title><link>https://wiki.dgfygk.com/archives/66bcbd39-1b1d-44a4-9a19-cb0019ab8ed2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-14%20%E4%BD%BF%E6%95%B0%E7%BB%84%E5%92%8C%E5%B0%8F%E4%BA%8E%E7%AD%89%E4%BA%8E%20x%20%E7%9A%84%E6%9C%80%E5%B0%91%E6%97%B6%E9%97%B4&amp;url=/archives/66bcbd39-1b1d-44a4-9a19-cb0019ab8ed2" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff0a4c8c182e3bce15f7c9a702bb2eb10.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 操作次数0-nums1.size()之间</p>
<p>时间复杂度： O(n²)
 <br>
 空间复杂度： O(n)</p>
<p>Code：</p>
<pre><code class="prism language-java">class Solution {

    public int minimumTime(List&lt;Integer&gt; nums1, List&lt;Integer&gt; nums2, int x) {

        int n = nums1.size();
        int[][] f = new int[n + 1][n + 1];
        int[][] nums = new int[n][0];
        for (int i = 0; i &lt; n; ++i) {

            nums[i] = new int[] {
   nums1.get(i), nums2.get(i)};
        }
        Arrays.sort(nums, Comparator.comparingInt(a -&gt; a[1]));
        for (int i = 1; i &lt;= n; ++i) {

            for (int j = 0; j &lt;= n; ++j) {

                f[i][j] = f[i - 1][j];
                if (j &gt; 0) {

                    int a = nums[i - 1][0], b = nums[i - 1][1];
                    f[i][j] = Math.max(f[i][j], f[i - 1][j - 1] + a + b * j);
                }
            }
        }
        int s1 = 0, s2 = 0;
        for (int v : nums1) {

            s1 += v;
        }
        for (int v : nums2) {

            s2 += v;
        }

        for (int j = 0; j &lt;= n; ++j) {

            if (s1 + s2 * j - f[n][j] &lt;= x) {

                return j;
            }
        }
        return -1;
    }
}
</code></pre>
<p>注:不会，参考了题解。。。。。。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135693563</p>]]></description><guid isPermaLink="false">/archives/66bcbd39-1b1d-44a4-9a19-cb0019ab8ed2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:17:55 GMT</pubDate></item><item><title><![CDATA[day-15 按分隔符拆分字符串]]></title><link>https://wiki.dgfygk.com/archives/ccdd4d0f-a415-4f1a-9db2-d429cb263a7d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-15%20%E6%8C%89%E5%88%86%E9%9A%94%E7%AC%A6%E6%8B%86%E5%88%86%E5%AD%97%E7%AC%A6%E4%B8%B2&amp;url=/archives/ccdd4d0f-a415-4f1a-9db2-d429cb263a7d" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8d59aa77af138242cd4dc8f64b7aa860.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 依次对words的每个字符进行split(),然后将非空的加入List</p>
<p>解题方法
 <br>
 String arr[]=s.split(ss);利用split()方法将words的每个字符串划分为String数组
 <br>
 if(arr[i]!=““) //将非空的加入
 <br>
 list.add(arr[i]);</p>
<p>String ss=““+separator; //使用转义字符</p>
<p>时间复杂度： O(mn)</p>
<p>空间复杂度： O(n)</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

   public List&lt;String&gt; splitWordsBySeparator(List&lt;String&gt; words, char separator) {

        List&lt;String&gt; list=new ArrayList&lt;&gt;();
        for(String s:words){

            String ss="\\"+separator;
            String arr[]=s.split(ss);
            for(int i=0;i&lt; arr.length;i++){

                if(arr[i]!="")
                list.add(arr[i]);
            }
        }
        return list;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135712663</p>]]></description><guid isPermaLink="false">/archives/ccdd4d0f-a415-4f1a-9db2-d429cb263a7d</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:17:10 GMT</pubDate></item><item><title><![CDATA[MyBatis-plus-02]]></title><link>https://wiki.dgfygk.com/archives/d0e64e41-7c5a-44ce-b539-3ae2b82e3d7e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-plus-02&amp;url=/archives/d0e64e41-7c5a-44ce-b539-3ae2b82e3d7e" width="1" height="1" alt="" style="opacity:0;">
<p><strong>分页查询</strong>
 <br>
 分页查询是一种常见的数据库查询技术，用于在大量数据中检索特定范围的数据。该技术可以将数据划分为多个页面（或称为页），每页包含固定数量的数据条目。通过指定页码和每页数据条目的数量，可以有效地实现分页查询。
 <br>
 分页查询通常包括以下几个步骤：</p>
<ol>
 <li>指定每页数据条目的数量（通常为固定值）。</li>
 <li>指定要检索的页码。</li>
 <li>计算起始行数和结束行数，即确定要从数据库中检索的数据范围。</li>
 <li>使用起始行数和结束行数作为条件执行数据库查询。</li>
 <li>获取查询结果，并将其呈现给用户。
  <br>
  例如，假设有一张名为"users"的用户表，包含100条用户记录。要查询第3页，每页显示10条记录，可以按以下步骤执行分页查询：</li>
 <li>指定每页数据条目的数量为10。</li>
 <li>指定要查询的页码为3。</li>
 <li>计算起始行数和结束行数。对于第3页，起始行数为21（=（3-1）<em>10+1），结束行数为30（=3</em>10）。</li>
 <li>使用起始行数和结束行数作为条件执行数据库查询语句，例如"SELECT * FROM users LIMIT 21, 10”。</li>
 <li>获取查询结果，即第3页的用户记录。
  <br>
  分页查询在Web应用程序中非常常见，因为它允许用户分批加载大量数据，提高性能和用户体验。</li>
</ol>
<p>步骤：
 <br>
 1.启动类中导入分页插件</p>
<pre><code class="prism language-java">//将mybatis-plus插件加入ioc容器
    @Bean
    public MybatisPlusInterceptor plusInterceptor(){

        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }
</code></pre>
<p>2.使用分页查询</p>
<pre><code class="prism language-java">    @Test
    public void testPage(){

        Page&lt;User&gt; page=new Page&lt;&gt;(1,3); //查询第一页第三个参数
        userMapper.selectPage(page,null);
        long current = page.getCurrent();   //页码
        long size = page.getSize();         //页容量
        List&lt;User&gt; records = page.getRecords(); //当前页的数据
        long total = page.getTotal();       //总条数
    }
</code></pre>
<p>自定义的mapper方法使用分页</p>
<pre><code class="prism language-java">public interface UserMapper extends BaseMapper&lt;User&gt; {

    //自定义根据年龄查询并分页      age&gt;xx
    IPage&lt;User&gt; queryByAge(IPage&lt;User&gt; page,@Param("age") Integer age);
}


    //自定义分页
    @Test
    public void testMyPage(){

        Page&lt;User&gt; page=new Page&lt;&gt;(1,3);
        userMapper.queryByAge(page,1);
        long current = page.getCurrent();   //页码
        System.out.println("current = " + current);
        long size = page.getSize();         //页容量
        System.out.println("size = " + size);
        List&lt;User&gt; records = page.getRecords(); //当前页的数据
        System.out.println("records = " + records);
        long total = page.getTotal();       //总条数
        System.out.println("total = " + total);
    }


&lt;mapper namespace="com.dc.mapper.UserMapper"&gt;
    &lt;select id="queryByAge" resultType="user"&gt;
        select * from user where age&gt;#{
   age}
    &lt;/select&gt;
&lt;/mapper&gt;

type-aliases-package: com.dc.pojo       #开启别名
</code></pre>
<p><strong>条件构造器</strong>
 <br>
 使用MyBatis-Plus的条件构造器，可以构建灵活、高效的查询条件，而不需要手动编写复杂的 SQL 语句。它提供了许多方法来支持各种条件操作符，并且可以通过链式调用来组合多个条件。这样可以简化查询的编写过程，并提高开发效率。</p>
<p>基于QueryWrapper 组装条件：</p>
<pre><code class="prism language-java">@Test
    public void test_01(){

        //查询用户名包含a，年龄在20到30之间，并且邮箱不为null的用户信息
        QueryWrapper&lt;User&gt; queryWrapper=new QueryWrapper&lt;&gt;();
        queryWrapper.like("name","a");
        queryWrapper.between("age",20,30);
        queryWrapper.isNotNull("email");
        /*链式调用
        * queryWrapper.like("name","a").between("age",20,30).isNotNull("email");
        * */
        List&lt;User&gt; users=userMapper.selectList(queryWrapper);
        System.out.println("users = " + users);
    }

默认and       queryWrapper.like("name","a").between("age",20,30)
    or      queryWrapper.like("name","a").or().between("age",20,30)

queryMapper.select("name","age");   //指定查询的列


//动态语句查询    name不为空时才作为查询条件
       //动态语句查询    name不为空时才作为查询条件
        String name="xxxx";
        queryMapper.eq(StringUtils.isNotBlank(name),"name",name);
</code></pre>
<p>基于 UpdateWrapper组装条件:
 <br>
 使用queryWrapper + 实体类形式可以实现修改，但是无法将列值修改为null值
 <br>
 使用updateWrapper可以随意设置列的值</p>
<p>基于LambdaQueryWrapper组装条件 基于LambdaUpdateWrapper组装条件: 【推荐】
 <br>
 相比于 QueryWrapper，LambdaQueryWrapper 使用了实体类的属性引用（例如 <code>User::getName</code>、<code>User::getAge</code>），而不是字符串来表示字段名，这提高了代码的可读性和可维护性。</p>
<pre><code class="prism language-java">QueryWrapper&lt;User&gt; queryWrapper=new QueryWrapper&lt;&gt;();
        queryWrapper.like("name","a").between("age",20,30)
                .isNotNull("email");

LambdaQueryWrapper&lt;User&gt; userLambdaQueryWrapper=new LambdaQueryWrapper&lt;&gt;();
userLambdaQueryWrapper.like(User::getName,"a")
            .between(User::getAge,20,30)
            .isNotNull(User::getEmail);
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135677707</p>]]></description><guid isPermaLink="false">/archives/d0e64e41-7c5a-44ce-b539-3ae2b82e3d7e</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:16:50 GMT</pubDate></item><item><title><![CDATA[MyBatis-plus-03]]></title><link>https://wiki.dgfygk.com/archives/bfb51102-b643-48a5-a7e6-fb5592631284</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-plus-03&amp;url=/archives/bfb51102-b643-48a5-a7e6-fb5592631284" width="1" height="1" alt="" style="opacity:0;">
<p><strong>核心注解使用</strong>
 <br>
 MyBatis-Plus是一个基于MyBatis框架的增强工具，提供了一系列简化和增强的功能，用于加快开发人员在使用MyBatis进行数据库访问时的效率。
 <br>
 MyBatis-Plus提供了一种基于注解的方式来定义和映射数据库操作，其中的注解起到了重要作用。</p>
<p>@TableName注解</p>
<ul>
 <li>描述：表名注解，标识实体类对应的表</li>
 <li>使用位置：实体类</li>
</ul>
<pre><code class="prism language-java">@TableName("user")  //当数据库的表名和实体类名不同时使用（忽略大小写） 指定数据库表名
@Data
public class User {

    private Long id;
    private String name;
    private Integer age;
    private String email;
}
</code></pre>
<pre><code>```prism language-yaml
mybatis-plus:
    global-config:
        db-config:
            table-prefix:  t_   #所有数据库表名前缀 可以不用逐一配置@TableName("")
</code></pre>
<p>@TableId 注解</p>
<ul>
 <li>
  <p>描述：主键注解</p>
 </li>
 <li>
  <p>使用位置：实体类主键字段
   <br>
   在以下场景下，添加<code>@TableId</code>注解是必要的：</p>
 </li>
 <li>
  <p>实体类的字段与数据库表的主键字段不同名：如果实体类中的字段与数据库表的主键字段不一致，需要使用<code>@TableId</code>注解来指定实体类中表示主键的字段。</p>
 </li>
 <li>
  <p>主键生成策略不是默认策略：如果需要使用除了默认主键生成策略以外的策略，也需要添加<code>@TableId</code>注解，并通过<code>value</code>属性指定生成策略。</p>
 </li>
</ul>
<p>雪花算法（Snowflake Algorithm）是一种用于生成唯一ID的算法。它由Twitter公司提出，用于解决分布式系统中生成全局唯一ID的需求。</p>
<pre><code class="prism language-java">//@TableId(type= IdType.AUTO)     //主键自增长
    private Long id;
</code></pre>
<pre><code>```prism language-yaml
mybatis-plus:
  global-config:
    db-config:
      id-type: auto     #全局的主键设置为自增长
</code></pre>
<p>@TableField</p>
<ul>
 <li>描述：字段注解（非主键）</li>
</ul>
<pre><code class="prism language-java">    @TableField(value="name",exist = true)
    private String name;
</code></pre>
<p><strong>MyBatis-Plus高级扩展</strong>
 <br>
 逻辑删除：假删除，将对应数据中代表是否被删除字段的状态修改为"被删除状态”，之后在数据库中仍旧能看到此条数据记录</p>
<pre><code class="prism language-java">    @TableLogic     //该属性对应的列为逻辑删除的状态字段
    private Integer deleted;
</code></pre>
<pre><code>```prism language-yaml
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted   #全局设置逻辑删除字段
</code></pre>
<p>自定义SQL语句：
 <br>
 MyBatis-plus擅长构建where条件，所以用MyBatis-plus构建where条件，然后自定义SQL语句剩下的部分（在mapper文件下自定义）</p>
<p>返回对象与实体类属性不完全相同时，可以使用VO实体类，不用一个个复制，可以利用拷贝函数进行拷贝</p>
<p>分页</p>
<pre><code class="prism language-java">@Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {

        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //分页
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());  //乐观锁
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());  //防全局修改和删除
        return interceptor;
    }
</code></pre>
<p><strong>乐观锁实现</strong>
 <br>
 乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!!
 <br>
 悲观锁：
 <br>
 悲观锁的基本思想是，在整个数据访问过程中，将共享资源<em>锁定</em> ，以确保其他线程或进程不能同时访问和修改该资源。悲观锁的核心思想是"先保护，再修改”。在悲观锁的应用中，线程在访问共享资源之前会获取到锁，并在整个操作过程中保持锁的状态，阻塞其他线程的访问。只有当前线程完成操作后，才会释放锁，让其他线程继续操作资源。这种锁机制可以确保资源独占性和数据的一致性，但是在高并发环境下，悲观锁的效率相对较低。
 <br>
 乐观锁：
 <br>
 乐观锁的基本思想是，认为并发冲突的概率较低，因此<em>不需要提前加锁</em> ，而是在数据更新阶段进行冲突检测和处理。乐观锁的核心思想是"先修改，后校验”。在乐观锁的应用中，线程在读取共享资源时不会加锁，而是记录特定的版本信息。当线程准备更新资源时，会先检查该资源的版本信息是否与之前读取的版本信息一致，如果一致则执行更新操作，否则说明有其他线程修改了该资源，需要进行相应的冲突处理。乐观锁通过避免加锁操作，提高了系统的并发性能和吞吐量，但是在并发冲突较为频繁的情况下，乐观锁会导致较多的冲突处理和重试操作。
 <br>
 理解点: 悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术。</p>
<ol>
 <li>乐观锁实现方案和技术：</li>
 <li>版本号/时间戳：为数据添加一个版本号或时间戳字段，每次更新数据时，比较当前版本号或时间戳与期望值是否一致，若一致则更新成功，否则表示数据已被修改，需要进行冲突处理。</li>
 <li>CAS（Compare-and-Swap）：使用原子操作比较当前值与旧值是否一致，若一致则进行更新操作，否则重新尝试。</li>
 <li>无锁数据结构：采用无锁数据结构，如无锁队列、无锁哈希表等，通过使用原子操作实现并发安全。</li>
 <li>悲观锁实现方案和技术：</li>
 <li>锁机制：使用传统的锁机制，如互斥锁（Mutex Lock）或读写锁（Read-Write Lock）来保证对共享资源的独占访问。</li>
 <li>数据库锁：在数据库层面使用行级锁或表级锁来控制并发访问。</li>
 <li>信号量（Semaphore）：使用信号量来限制对资源的并发访问。</li>
</ol>
<pre><code class="prism language-java">    //乐观锁版本号插件
    mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

    @Version
    private Integer Version;        //版本号字段


    public void test(){

        User user1=userMapper.selectById(3);
        User user2=userMapper.selectById(3);
        user1.setAge(50);
        user2.setAge(51);
        userMapper.updateById(user1);
        userMapper.updateById(user2);   //乐观锁生效，修改失败
</code></pre>
<p><strong>防全表更新和删除实现</strong></p>
<pre><code class="prism language-java">        //防止全表更新和删除实现拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

        //全表删除
        userMapper.delete(null);    //会报错
</code></pre>
<p><strong>MyBatis-Plus代码生成器(MyBatisX插件)</strong></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135688706</p>]]></description><guid isPermaLink="false">/archives/bfb51102-b643-48a5-a7e6-fb5592631284</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:10:41 GMT</pubDate></item><item><title><![CDATA[day-12 最大字符串配对数目]]></title><link>https://wiki.dgfygk.com/archives/bac36fdc-bd41-445c-900d-f7f24ee52946</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-12%20%E6%9C%80%E5%A4%A7%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%85%8D%E5%AF%B9%E6%95%B0%E7%9B%AE&amp;url=/archives/bac36fdc-bd41-445c-900d-f7f24ee52946" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd52539737f6cbcaeef4ccc35916cda55.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 利用hashMap存储每个字符串的反转字符的出现次数，然后进行逐一比较</p>
<p>解题方法
 <br>
 for(int j=words[i].length()-1;j&gt;=0;j–){//反转字符串
 <br>
 s=s+words[i].charAt(j);
 <br>
 }
 <br>
 注意：对于xx型字符串与xy型字符串比较方式不同</p>
<p>时间复杂度： O(n)</p>
<p>空间复杂度,： O(n)</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

    public static int maximumNumberOfStringPairs(String[] words) {

        int ans=0;
        HashMap&lt;String,Integer&gt; hashMap=new HashMap&lt;&gt;();
        for(int i=0;i&lt;words.length;i++){

            String s="";
            for(int j=words[i].length()-1;j&gt;=0;j--){

                s=s+words[i].charAt(j);
            }
            hashMap.put(s,hashMap.getOrDefault(s,0)+1);
        }
        for(int i=0;i&lt;words.length;i++) {

            if (words[i].charAt(0) == words[i].charAt(1)) {
   //xx型字符串
                if (hashMap.getOrDefault(words[i], -1) &gt; 1) {

                    ans++;
                    hashMap.put(words[i], hashMap.get(words[i]) - 1);
                }
            } else {
                                        //xy型字符串
                if (hashMap.getOrDefault(words[i], -1) &gt;= 1) {

                    ans++;
                    hashMap.put(words[i], hashMap.get(words[i]) - 1);
                }
            }
        }
        return ans/2;
    }
}
</code></pre>
<p>官方题解</p>
<pre><code class="prism language-java">class Solution {

    public int maximumNumberOfStringPairs(String[] words) {

        int n = words.length;
        int ans = 0;
        for (int i = 0; i &lt; n; ++i) {

            for (int j = i + 1; j &lt; n; ++j) {

                if (words[i].charAt(0) == words[j].charAt(1) &amp;&amp; words[i].charAt(1) == words[j].charAt(0)) {

                    ++ans;
                }
            }
        }
        return ans;
    }
}

作者：力扣官方题解
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135657010</p>]]></description><guid isPermaLink="false">/archives/bac36fdc-bd41-445c-900d-f7f24ee52946</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:10:25 GMT</pubDate></item><item><title><![CDATA[SpringBoot-02]]></title><link>https://wiki.dgfygk.com/archives/6785e0ac-87ab-4402-8431-8ff84d555a35</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot-02&amp;url=/archives/6785e0ac-87ab-4402-8431-8ff84d555a35" width="1" height="1" alt="" style="opacity:0;">
<p><strong>SpringBoot3整合SpringMVC</strong></p>
<ol>
 <li>创建程序</li>
 <li>引入依赖</li>
 <li>创建启动类</li>
 <li>创建实体类</li>
 <li>编写Controller</li>
 <li>访问测试</li>
</ol>
<p>当涉及Spring Boot的Web应用程序配置时，以下是五个重要的配置参数：</p>
<ol>
 <li><code>server.port</code>: 指定应用程序的HTTP服务器端口号。默认情况下，Spring Boot使用8080作为默认端口。您可以通过在配置文件中设置<code>server.port</code>来更改端口号。</li>
 <li><code>server.servlet.context-path</code>: 设置应用程序的上下文路径。这是应用程序在URL中的基本路径。默认情况下，上下文路径为空。您可以通过在配置文件中设置<code>server.servlet.context-path</code>属性来指定自定义的上下文路径。</li>
 <li><code>spring.mvc.view.prefix</code>和<code>spring.mvc.view.suffix</code>: 这两个属性用于配置视图解析器的前缀和后缀。视图解析器用于解析控制器返回的视图名称，并将其映射到实际的视图页面。<code>spring.mvc.view.prefix</code>定义视图的前缀，<code>spring.mvc.view.suffix</code>定义视图的后缀。</li>
 <li><code>spring.resources.static-locations</code>: 配置静态资源的位置。静态资源可以是CSS、JavaScript、图像等。默认情况下，Spring Boot会将静态资源放在<code>classpath:/static</code>目录下。您可以通过在配置文件中设置<code>spring.resources.static-locations</code>属性来自定义静态资源的位置。</li>
 <li><code>spring.http.encoding.charset</code>和<code>spring.http.encoding.enabled</code>: 这两个属性用于配置HTTP请求和响应的字符编码。<code>spring.http.encoding.charset</code>定义字符编码的名称（例如UTF-8），<code>spring.http.encoding.enabled</code>用于启用或禁用字符编码的自动配置。
  <br>
  这些是在Spring Boot的配置文件中与Web应用程序相关的一些重要配置参数。根据您的需求，您可以在配置文件中设置这些参数来定制和配置您的Web应用程序。</li>
</ol>
<p><strong>默认的静态资源路径为：</strong>
 <br>
 <strong>· classpath:/META-INF/resources/</strong>
 <br>
 <strong>· classpath:/resources/</strong>
 <br>
 <strong>· classpath:/static/</strong>
 <br>
 <strong>· classpath:/public/</strong>
 <br>
 只要静态资源放在这些目录中任何一个，SpringMVC都会帮我们处理。 我们习惯会把静态资源放在classpath:/static/ 目录下。</p>
<pre><code class="prism language-yaml"># web相关的配置
  # 端口号设置
  port: 80
  # 项目根路径
  servlet:
    context-path: /boot
spring:
  web:
    resources:
      # 配置静态资源地址,如果设置,会覆盖默认值            覆盖/static目录下的
      static-locations: classpath:/webapp
</code></pre>
<p>自定义拦截器(SpringMVC配置)
 <br>
 1.拦截器声明
 <br>
 2.拦截器配置，正常使用配置类，只要保证，配置类要在启动类的同包或者子包方可生效。
 <br>
 3.拦截器效果测试</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135665323</p>]]></description><guid isPermaLink="false">/archives/6785e0ac-87ab-4402-8431-8ff84d555a35</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:10:12 GMT</pubDate></item><item><title><![CDATA[SpringBoot-03]]></title><link>https://wiki.dgfygk.com/archives/6f9be158-e52a-41da-a8b4-2546e8d7fe00</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot-03&amp;url=/archives/6f9be158-e52a-41da-a8b4-2546e8d7fe00" width="1" height="1" alt="" style="opacity:0;">
<p><strong>SpringBoot3整合Druid数据源</strong></p>
<ol>
 <li>创建程序</li>
 <li>引入依赖</li>
 <li>启动类</li>
 <li>配置文件编写</li>
 <li>编写Controller</li>
 <li>启动测试</li>
 <li>问题解决</li>
</ol>
<pre><code class="prism language-yaml">spring:
  datasource:
    # 连接池类型 
    type: com.alibaba.druid.pool.DruidDataSource

    # Druid的其他属性配置 springboot3整合情况下,数据库连接信息必须在Druid属性下!
    druid:
      url: jdbc:mysql://localhost:3306/day01
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
      # 初始化时建立物理连接的个数
      initial-size: 5
      # 连接池的最小空闲数量
      min-idle: 5
      # 连接池最大连接数量
      max-active: 20
      # 获取连接时最大等待时间，单位毫秒
      max-wait: 60000
      # 申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 既作为检测的间隔时间又作为testWhileIdel执行的依据
      time-between-eviction-runs-millis: 60000
      # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时，关闭当前连接(配置连接在池中的最小生存时间)
      min-evictable-idle-time-millis: 30000
      # 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual)
      validation-query: select 1
      # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-borrow: false
      # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-return: false
      # 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大，比如说oracle,在mysql下建议关闭。
      pool-prepared-statements: false
      # 要启用PSCache，必须配置大于0，当大于0时，poolPreparedStatements自动触发修改为true。在Druid中，不会存在Oracle下PSCache占用内存过多的问题，可以把这个数值配置大一些，比如说100
      max-pool-prepared-statement-per-connection-size: -1
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true

logging:
  level:
    root: debug
</code></pre>
<p><strong>SpringBoot3整合Mybatis</strong></p>
<ol>
 <li>导入依赖：在您的Spring Boot项目的构建文件（如pom.xml）中添加MyBatis和数据库驱动的相关依赖。例如，如果使用MySQL数据库，您需要添加MyBatis和MySQL驱动的依赖。</li>
 <li>配置数据源：在<code>application.properties</code>或<code>application.yml</code>中配置数据库连接信息，包括数据库URL、用户名、密码、mybatis的功能配置等。</li>
 <li>创建实体类：创建与数据库表对应的实体类。</li>
 <li>创建Mapper接口：创建与数据库表交互的Mapper接口。</li>
 <li>创建Mapper接口SQL实现： 可以使用mapperxml文件或者注解方式</li>
 <li>创建程序启动类</li>
 <li>注解扫描：在Spring Boot的主应用类上添加<code>@MapperScan</code>注解，用于扫描和注册Mapper接口。</li>
 <li>使用Mapper接口：在需要使用数据库操作的地方，通过依赖注入或直接实例化Mapper接口，并调用其中的方法进行数据库操作。</li>
</ol>
<pre><code class="prism language-yaml">server:
  port: 80
  servlet:
    context-path: /
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:mysql:///day01
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:  # setting配置
    auto-mapping-behavior: full
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  type-aliases-package: com.dc.pojo # 配置别名
  mapper-locations: classpath:/mapper/*.xml # mapperxml位置
</code></pre>
<p>在启动类中指定Mapper接口位置： @MapperScan(“com.dc.mapper”) //指定Mapper接口的位置</p>
<p><strong>声明式事务整合配置</strong>
 <br>
 1.依赖导入
 <br>
 2.直接使用aop注解即可</p>
<p><strong>AOP整合配置</strong>
 <br>
 1.依赖导入
 <br>
 2.直接使用aop注解即可</p>
<p><strong>SpringBoot3项目打包和运行</strong></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135666302</p>]]></description><guid isPermaLink="false">/archives/6f9be158-e52a-41da-a8b4-2546e8d7fe00</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:09:58 GMT</pubDate></item><item><title><![CDATA[day-08 构造限制重复的字符串]]></title><link>https://wiki.dgfygk.com/archives/da8de9d6-0433-4906-9186-27f4b19208e0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-08%20%E6%9E%84%E9%80%A0%E9%99%90%E5%88%B6%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2&amp;url=/archives/da8de9d6-0433-4906-9186-27f4b19208e0" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe6ec9936fbb24889e09ca4a2f921df8e.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 首先统计每个字符的个数，然后从后向前按照题意添加字符</p>
<p>解题方法
 <br>
 从后向前添加字符：1.当前字符个数&lt;=repeatLimit,直接添加
 <br>
 2.当前字符个数&gt;repeatLimit,添加repeatLimit个，然后插入一个下一级字符</p>
<p>时间复杂度:O(n²)</p>
<p>空间复杂度:O(n)</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

   public String repeatLimitedString(String s, int repeatLimit) {

                int[] cnt = new int[26];
                for(int i=0;i&lt;s.length();i++){
     //使用数组统计每次字符个数
                    char c = s.charAt(i);
                    cnt[c-'a']++;
                }
                StringBuilder sb = new StringBuilder(); //添加字符
                for(int i=25;i&gt;=0;i--){

                    if(cnt[i]&gt;0 &amp;&amp; cnt[i]&lt;=repeatLimit){
       // 0&lt;字符个数&lt;=limit
                        while(cnt[i]&gt;0){

                            sb.append((char)('a' + i));     //加入
                            cnt[i]--;
                        }
                    }else if(cnt[i] &gt; repeatLimit){
                  // 字符个数&gt;limit
                        int n = repeatLimit;
                        while(n&gt;0){

                            sb.append((char)('a' + i));     //加入repeatLimit个最大字符
                            cnt[i]--;
                            n--;
                        }
                        int j = i-1;                        //寻找一个小一级的
                        while(j&gt;=0 &amp;&amp; cnt[j]==0){

                            j--;
                        }
                        if(j!=-1){

                            sb.append((char)('a' + j)); //找到了
                            cnt[j]--;
                            i++;    //重新插入上一个剩余的字母
                        }else{
             //没找到
                            return sb.toString();
                        }
                    }//if--elseif
                }
                return sb.toString();

    }
}
</code></pre>
<p>注：依旧是参考答案的一天。。。。。。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135576395</p>]]></description><guid isPermaLink="false">/archives/da8de9d6-0433-4906-9186-27f4b19208e0</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:09:39 GMT</pubDate></item><item><title><![CDATA[day-10 删除排序链表中的重复元素]]></title><link>https://wiki.dgfygk.com/archives/b64c90ab-c630-42fb-b288-757a21256d97</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-10%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0&amp;url=/archives/b64c90ab-c630-42fb-b288-757a21256d97" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0ed7f4bf84f69435b5531f8eb14160f5.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 先统计每个值出现的次数，然后将出现次数为一的节点链接为一个链表即可</p>
<p>解题方法
 <br>
 while(t!=null){ //统计每个值出现次数
 <br>
 arr[t.val+100]+=1;
 <br>
 t=t.next;
 <br>
 }</p>
<p>while(t!=null\&amp;\&amp;arr[t.val+100]!=1) t=t.next;//确定返回的头结点
 <br>
 tt=t;</p>
<p>while(t!=null\&amp;\&amp;t.next!=null){ //将出现次数为一的节点链接为新的链表
 <br>
 if(arr[t.next.val+100]==1) t=t.next;
 <br>
 else{</p>
<p>t.next=t.next.next;
 <br>
 }
 <br>
 }</p>
<p>时间复杂度： O(n)</p>
<p>空间复杂度,： O(n)</p>
<p>Code</p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode deleteDuplicates(ListNode head) {

        if(head==null)return head;
        ListNode t=new ListNode();
        ListNode tt=new ListNode();
        int arr[]=new int[250];
        t=head;
        tt=head;
        while(t!=null){

            arr[t.val+100]+=1;
            t=t.next;
        }
        t=head;
        while(t!=null&amp;&amp;arr[t.val+100]!=1) t=t.next;
        tt=t;


        t=tt;
        while(t!=null&amp;&amp;t.next!=null){

            if(arr[t.next.val+100]==1) t=t.next;
            else{

                t.next=t.next.next;

            } 
        }
        return tt;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135612530</p>]]></description><guid isPermaLink="false">/archives/b64c90ab-c630-42fb-b288-757a21256d97</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:09:25 GMT</pubDate></item><item><title><![CDATA[day-11 统计整数数目]]></title><link>https://wiki.dgfygk.com/archives/423e626e-3197-4152-be41-55d33dc92be2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-11%20%E7%BB%9F%E8%AE%A1%E6%95%B4%E6%95%B0%E6%95%B0%E7%9B%AE&amp;url=/archives/423e626e-3197-4152-be41-55d33dc92be2" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F841e3ef49c05d9121776fd323428ce69.png&amp;size=m" alt="">
 <br>
 注：无思路 参考答案
 <br>
 code</p>
<pre><code class="prism language-java">class Solution {

    static final int N = 23;
    static final int M = 401;
    static final int MOD = 1000000007;
    int[][] d;
    String num;
    int min_sum;
    int max_sum;

    public int count(String num1, String num2, int min_sum, int max_sum) {

        d = new int[N][M];
        for (int i = 0; i &lt; N; i++) {

            Arrays.fill(d[i], -1);
        }
        this.min_sum = min_sum;
        this.max_sum = max_sum;
        return (get(num2) - get(sub(num1)) + MOD) % MOD;
    }

    public int get(String num) {

        this.num = new StringBuffer(num).reverse().toString();
        return dfs(num.length() - 1, 0, true);
    }

    // 求解 num - 1，先把最后一个非 0 字符减去 1，再把后面的 0 字符变为 9
    public String sub(String num) {

        char[] arr = num.toCharArray();
        int i = arr.length - 1;
        while (arr[i] == '0') {

            i--;
        }
        arr[i]--;
        i++;
        while (i &lt; arr.length) {

            arr[i] = '9';
            i++;
        }
        return new String(arr);
    }

    public int dfs(int i, int j, boolean limit) {

        if (j &gt; max_sum) {

            return 0;
        }
        if (i == -1) {

            return j &gt;= min_sum ? 1 : 0;
        }
        if (!limit &amp;&amp; d[i][j] != -1) {

            return d[i][j];
        }
        int res = 0;
        int up = limit ? num.charAt(i) - '0' : 9;
        for (int x = 0; x &lt;= up; x++) {

            res = (res + dfs(i - 1, j + x, limit &amp;&amp; x == up)) % MOD;
        }
        if (!limit) {

            d[i][j] = res;
        }
        return res;
    }
}

作者：力扣官方
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135636218</p>]]></description><guid isPermaLink="false">/archives/423e626e-3197-4152-be41-55d33dc92be2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:09:14 GMT</pubDate></item><item><title><![CDATA[MyBatis-plus-01]]></title><link>https://wiki.dgfygk.com/archives/7bad6b63-2a90-44aa-a59f-09c4210dcac4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-plus-01&amp;url=/archives/7bad6b63-2a90-44aa-a59f-09c4210dcac4" width="1" height="1" alt="" style="opacity:0;">
<p>Mybatis-Plus是一个开源的持久层框架，是对Mybatis框架的增强和扩展。它简化了基于Mybatis的DAO开发，提供了一些常用的增强功能，使开发者可以更加方便、快速地进行数据库操作。
 <br>
 Mybatis-Plus提供了一系列的注解和API，可以通过简单的配置就实现常见的数据库操作，比如增删改查、分页查询、条件查询等。
 <br>
 Mybatis-Plus的优点有：</p>
<ol>
 <li>简化开发：Mybatis-Plus提供了简单易用的API和注解，可以大大减少开发人员的开发工作量。</li>
 <li>提高效率：Mybatis-Plus提供了丰富的增强功能，比如自动生成SQL、分页查询等，可以提高开发效率。</li>
 <li>提供了更强的查询能力：Mybatis-Plus支持Lambda表达式查询，可以通过函数式编程方式进行复杂的条件查询。</li>
 <li>支持代码生成：Mybatis-Plus提供了代码生成器，可以根据数据库表自动生成实体类、Mapper接口和XML文件，减少了手动编写的工作。
  <br>
  总的来说，Mybatis-Plus是一个功能强大、易于使用的持久层框架，可以帮助开发者快速、高效地进行数据库操作。</li>
</ol>
<p><strong>只适用单表</strong></p>
<p><strong>mybatis:</strong> 1.mapper接口 crudf方法 2.mapperxml配置文件 crud sql语句
 <br>
 <strong>mybatis-plus:</strong> 1.继承BaseMapper&lt;&gt; 2.crud方法 sql语句</p>
<p>只需继承BaseMapper&lt;&gt;</p>
<pre><code class="prism language-java">public interface UserMapper extends BaseMapper&lt;User&gt; {

}
</code></pre>
<p><strong>1.基于BaseMapper接口CRUD</strong></p>
<p>Insert方法</p>
<pre><code class="prism language-java">// 插入一条记录
// T 就是要插入的实体对象
// 默认主键生成策略为雪花算法（后面讲解）
int insert(T entity);
</code></pre>
<p>Delete方法</p>
<pre><code class="prism language-java">// 根据 entity 条件，删除记录
int delete(@Param(Constants.WRAPPER) Wrapper&lt;T&gt; wrapper);

// 删除（根据ID 批量删除）
int deleteBatchIds(@Param(Constants.COLLECTION) Collection&lt;? extends Serializable&gt; idList);

// 根据 ID 删除
int deleteById(Serializable id);

// 根据 columnMap 条件，删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map&lt;String, Object&gt; columnMap);
</code></pre>
<p>Update方法</p>
<pre><code class="prism language-java">// 根据 whereWrapper 条件，更新记录
int update(@Param(Constants.ENTITY) T updateEntity, 
            @Param(Constants.WRAPPER) Wrapper&lt;T&gt; whereWrapper);

// 根据 ID 修改  主键属性必须值
int updateById(@Param(Constants.ENTITY) T entity);
</code></pre>
<p>Select方法</p>
<pre><code class="prism language-java">// 根据 ID 查询
T selectById(Serializable id);

// 根据 entity 条件，查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper&lt;T&gt; queryWrapper);

// 查询（根据ID 批量查询）
List&lt;T&gt; selectBatchIds(@Param(Constants.COLLECTION) Collection&lt;? extends Serializable&gt; idList);

// 根据 entity 条件，查询全部记录
List&lt;T&gt; selectList(@Param(Constants.WRAPPER) Wrapper&lt;T&gt; queryWrapper);

// 查询（根据 columnMap 条件）
List&lt;T&gt; selectByMap(@Param(Constants.COLUMN_MAP) Map&lt;String, Object&gt; columnMap);

// 根据 Wrapper 条件，查询全部记录
List&lt;Map&lt;String, Object&gt;&gt; selectMaps(@Param(Constants.WRAPPER) Wrapper&lt;T&gt; queryWrapper);

// 根据 Wrapper 条件，查询全部记录。注意： 只返回第一个字段的值
List&lt;Object&gt; selectObjs(@Param(Constants.WRAPPER) Wrapper&lt;T&gt; queryWrapper);

// 根据 entity 条件，查询全部记录（并翻页）
IPage&lt;T&gt; selectPage(IPage&lt;T&gt; page, @Param(Constants.WRAPPER) Wrapper&lt;T&gt; queryWrapper);

// 根据 Wrapper 条件，查询全部记录（并翻页）
IPage&lt;Map&lt;String, Object&gt;&gt; selectMapsPage(IPage&lt;T&gt; page, @Param(Constants.WRAPPER) Wrapper&lt;T&gt; queryWrapper);

// 根据 Wrapper 条件，查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper&lt;T&gt; queryWrapper);
</code></pre>
<p><strong>2.基于IService接口CRUD</strong>
 <br>
 对比Mapper接口CRUD区别：
 <br>
 1.service添加了批量方法
 <br>
 2.service层的方法自动添加事务</p>
<pre><code class="prism language-java">public interface UserService extends IService&lt;User&gt;{

}


public class UserServiceImpl extends ServiceImpl&lt;UserMapper, User&gt; implements UserService{


}
</code></pre>
<p>crud方法</p>
<pre><code class="prism language-java">保存：
// 插入一条记录（选择字段，策略插入）
boolean save(T entity);
// 插入（批量）
boolean saveBatch(Collection&lt;T&gt; entityList);
// 插入（批量）
boolean saveBatch(Collection&lt;T&gt; entityList, int batchSize);

修改或者保存：
// TableId 注解存在更新记录，否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新，否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper&lt;T&gt; updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection&lt;T&gt; entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection&lt;T&gt; entityList, int batchSize);

移除：
// 根据 queryWrapper 设置的条件，删除记录
boolean remove(Wrapper&lt;T&gt; queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件，删除记录
boolean removeByMap(Map&lt;String, Object&gt; columnMap);
// 删除（根据ID 批量删除）
boolean removeByIds(Collection&lt;? extends Serializable&gt; idList);

更新：
// 根据 UpdateWrapper 条件，更新记录 需要设置sqlset
boolean update(Wrapper&lt;T&gt; updateWrapper);
// 根据 whereWrapper 条件，更新记录
boolean update(T updateEntity, Wrapper&lt;T&gt; whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection&lt;T&gt; entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection&lt;T&gt; entityList, int batchSize);

数量： 
// 查询总记录数
int count();
// 根据 Wrapper 条件，查询总记录数
int count(Wrapper&lt;T&gt; queryWrapper);

查询：
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper，查询一条记录。结果集，如果是多个会抛出异常，随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper&lt;T&gt; queryWrapper);
// 根据 Wrapper，查询一条记录
T getOne(Wrapper&lt;T&gt; queryWrapper, boolean throwEx);
// 根据 Wrapper，查询一条记录
Map&lt;String, Object&gt; getMap(Wrapper&lt;T&gt; queryWrapper);
// 根据 Wrapper，查询一条记录
&lt;V&gt; V getObj(Wrapper&lt;T&gt; queryWrapper, Function&lt;? super Object, V&gt; mapper);

集合：
// 查询所有
List&lt;T&gt; list();
// 查询列表
List&lt;T&gt; list(Wrapper&lt;T&gt; queryWrapper);
// 查询（根据ID 批量查询）
Collection&lt;T&gt; listByIds(Collection&lt;? extends Serializable&gt; idList);
// 查询（根据 columnMap 条件）
Collection&lt;T&gt; listByMap(Map&lt;String, Object&gt; columnMap);
// 查询所有列表
List&lt;Map&lt;String, Object&gt;&gt; listMaps();
// 查询列表
List&lt;Map&lt;String, Object&gt;&gt; listMaps(Wrapper&lt;T&gt; queryWrapper);
// 查询全部记录
List&lt;Object&gt; listObjs();
// 查询全部记录
&lt;V&gt; List&lt;V&gt; listObjs(Function&lt;? super Object, V&gt; mapper);
// 根据 Wrapper 条件，查询全部记录
List&lt;Object&gt; listObjs(Wrapper&lt;T&gt; queryWrapper);
// 根据 Wrapper 条件，查询全部记录
&lt;V&gt; List&lt;V&gt; listObjs(Wrapper&lt;T&gt; queryWrapper, Function&lt;? super Object, V&gt; mapper);
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135672269</p>]]></description><guid isPermaLink="false">/archives/7bad6b63-2a90-44aa-a59f-09c4210dcac4</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:09:04 GMT</pubDate></item><item><title><![CDATA[SpringBoot-01]]></title><link>https://wiki.dgfygk.com/archives/a2ddb320-ad4b-4121-9604-fe72489b39c2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot-01&amp;url=/archives/a2ddb320-ad4b-4121-9604-fe72489b39c2" width="1" height="1" alt="" style="opacity:0;">
<p>Spring Boot是一个开源的Java框架，用于快速构建独立的、可执行的、生产级的Spring应用程序。它基于Spring框架，简化了Spring应用程序的配置和部署过程，使开发者能够更快速地创建高效、可扩展的应用。
 <br>
 Spring Boot具有以下特点：</p>
<ol>
 <li>简化配置：Spring Boot提供了自动配置功能，根据类路径和依赖自动配置应用程序。开发者只需提供少量配置，即可快速启动应用。</li>
 <li>内嵌容器：Spring Boot默认使用嵌入式的Servlet容器（如Tomcat、Jetty），无需部署到外部容器。</li>
 <li>自动化依赖管理：Spring Boot使用了依赖管理工具，可以自动管理Spring和第三方库的版本。开发者只需声明所需的库，Spring Boot即可自动下载并管理依赖。</li>
 <li>监控和管理：Spring Boot提供了一套完整的监控和管理功能，可以通过Actuator模块实时监控应用的运行状况，并提供了一系列的健康检查、指标统计、追踪日志等功能。</li>
 <li>生产就绪性：Spring Boot提供了一些有用的功能，如可执行的JAR文件、应用程序上下文管理、日志记录、安全性等，使得应用程序可以更容易地部署和运行在生产环境中。
  <br>
  总之，Spring Boot是一个简化Spring开发的工具，它能够帮助开发者快速构建、配置和部署Spring应用程序，同时提供一些有用的功能和工具，使应用程序更加高效、可扩展。</li>
</ol>
<p><strong>总结：简化开发，简化配置，简化整合，简化部署，简化监控，简化运维。</strong></p>
<p>启动类</p>
<pre><code class="prism language-java">@SpringBootApplication  //启动类
public class Main {

    public static void main(String[] args) {

        SpringApplication.run(Main.class,args);    //自动创建ioc容器，启动tomcat服务器
    }
</code></pre>
<p>SpringBoot工程下，进行统一的配置管理，你想设置的任何参数（端口号、项目根路径、数据库连接信息等等)都集中到一个固定位置和命名的配置文件（<code>application.properties</code>或<code>application.yml</code>）中！</p>
<p>YAML（Yet Another Markup Language）是一种数据序列化格式，常用于配置文件的编写。以下是一个简单的示例：</p>
<pre><code class="prism language-yaml"># Server Configuration

server:
  port: 8080
  host: localhost
  debug: true

# Database Configuration

database:
  host: localhost
  port: 3306
  username: root
  password: password
  name: mydatabase
</code></pre>
<p>在这个示例中，配置文件包含了服务器和数据库的配置信息。可以通过简单的缩进来表示层级关系，使用冒号来标志键值对。在服务器配置中，端口号设定为8080，主机设定为localhost，还开启了调试模式。在数据库配置中，设置了主机为localhost，端口号为3306，用户名为root，密码为password，数据库名称为mydatabase。这只是一个简单的示例，实际的配置文件可能包含更多的配置项。</p>
<p>@ConfigurationProperties(prefix = “dc.user”) //批量配置读取，填通用前缀</p>
<p>在Spring Boot中，可以使用多环境配置来根据不同的运行环境（如开发、测试、生产）加载不同的配置。SpringBoot支持多环境配置让应用程序在不同的环境中使用不同的配置参数，例如数据库连接信息、日志级别、缓存配置等。
 <br>
 以下是实现Spring Boot多环境配置的常见方法：</p>
<ol>
 <li>属性文件分离：将应用程序的配置参数分离到不同的属性文件中，每个环境对应一个属性文件。例如，可以创建<code>application-dev.properties</code>、<code>application-prod.properties</code>和<code>application-test.properties</code>等文件。在这些文件中，可以定义各自环境的配置参数，如数据库连接信息、端口号等。然后，在<code>application.properties</code>中通过<code>spring.profiles.active</code>属性指定当前使用的环境。Spring Boot会根据该属性来加载对应环境的属性文件，覆盖默认的配置。</li>
 <li>YAML配置文件：与属性文件类似，可以将配置参数分离到不同的YAML文件中，每个环境对应一个文件。例如，可以创建<code>application-dev.yml</code>、<code>application-prod.yml</code>和<code>application-test.yml</code>等文件。在这些文件中，可以使用YAML语法定义各自环境的配置参数。同样，通过<code>spring.profiles.active</code>属性指定当前的环境，Spring Boot会加载相应的YAML文件。</li>
 <li>命令行参数(动态)：可以通过命令行参数来指定当前的环境。例如，可以使用<code>--spring.profiles.active=dev</code>来指定使用开发环境的配置。
  <br>
  通过上述方法，Spring Boot会根据当前指定的环境来加载相应的配置文件或参数，从而实现多环境配置。这样可以简化在不同环境之间的配置切换，并且确保应用程序在不同环境中具有正确的配置。</li>
</ol>
<pre><code class="prism language-java">#激活
spring:
  profiles:
    active: test,dev
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135644255</p>]]></description><guid isPermaLink="false">/archives/a2ddb320-ad4b-4121-9604-fe72489b39c2</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:08:47 GMT</pubDate></item><item><title><![CDATA[SSM整合-01]]></title><link>https://wiki.dgfygk.com/archives/33938b7c-48d9-4605-a578-bfc3ba5160da</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SSM%E6%95%B4%E5%90%88-01&amp;url=/archives/33938b7c-48d9-4605-a578-bfc3ba5160da" width="1" height="1" alt="" style="opacity:0;">
<p>两个容器：1.Web Ioc容器 controller层 子容器（controller可以调用service）
 <br>
 2.Root Ioc容器 service层和mapper层 父容器
 <br>
 一般两个容器，三个配置类</p>
<p>cmd中配置node镜像 npm config set registry https://registry.npmjs.org/
 <br>
 npm更新 npm install -g npm@9.6.6</p>
<p>Node.js是一个基于Chrome V8引擎的JavaScript运行环境。Node.js使用事件驱动、非阻塞I/O模型，使得它非常适合构建高性能、可扩展的网络应用程序。Node.js可以在服务器端运行JavaScript代码，因此可以用来构建Web应用程序、API服务、实时通信应用程序等。
 <br>
 Node.js的特点包括：</p>
<ol>
 <li>高性能：Node.js使用了底层的非阻塞I/O模型，可以处理大量并发请求，提供高性能的网络服务。</li>
 <li>单线程：Node.js采用单线程模型，通过事件循环机制处理多个并发请求，避免了传统多线程模型中的线程切换开销。</li>
 <li>异步编程：Node.js使用回调函数来处理异步操作，使得代码更加简洁、可读性更强。</li>
 <li>轻量级：Node.js的核心是一个轻量级的运行时环境，可以快速启动和部署。</li>
 <li>跨平台：Node.js可以运行在多个操作系统上，包括Windows、Linux和Mac OS。
  <br>
  Node.js拥有一个强大的包管理工具，叫做npm，可以方便地安装、管理和共享代码包。Node.js的生态系统非常丰富，有大量的第三方模块可以供开发者使用。</li>
</ol>
<p>npm（Node Package Manager，节点包管理器）是一个用于管理Node.js包的工具。它是Node.js的默认包管理器，用于安装、发布和管理依赖关系。通过npm，开发者可以轻松地安装各种开源模块，以便在项目中使用。
 <br>
 npm允许用户通过命令行界面来安装包，可以安装全局包或将其作为项目的依赖项进行安装。它还提供了一些有用的命令，例如更新包、卸载包、查找包信息等。npm还允许用户创建自己的模块，并将其发布到npm仓库中供其他人使用。
 <br>
 除了作为一个包管理器，npm还是一个在线的社区平台，开发者可以在这里分享和探索各种开源模块。npm仓库中有数以百万计的包可供下载和使用，开发者可以找到适合自己项目需求的模块，并通过npm快速集成到自己的项目中。
 <br>
 总之，npm是一个功能强大且易于使用的包管理器，为开发者提供了方便快捷的安装、发布和管理Node.js模块的工具。</p>
<p>@CrossOrigin //允许其他源访问我们的controller 浏览器不再拦截</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135529053</p>]]></description><guid isPermaLink="false">/archives/33938b7c-48d9-4605-a578-bfc3ba5160da</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:08:22 GMT</pubDate></item><item><title><![CDATA[day-06 构造有效字符串的最少插入数]]></title><link>https://wiki.dgfygk.com/archives/ec924632-275b-45d6-b8a4-03ab8ee873a6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-06%20%E6%9E%84%E9%80%A0%E6%9C%89%E6%95%88%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%B0%91%E6%8F%92%E5%85%A5%E6%95%B0&amp;url=/archives/ec924632-275b-45d6-b8a4-03ab8ee873a6" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4772b4d4cd0ccae17586e43b8bfd40f0.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 动态规划：
 <br>
 Word[i]单独组成abc
 <br>
 如果Word[i]&gt;word[i-1] 则word[i]和word[i-1]一起构成abc</p>
<p>解题方法
 <br>
 关系式：dp[i]=dp[i-1]+2或dp[i]=dp[i-1]-1</p>
<p>时间复杂度： O(n)</p>
<p>空间复杂度： O(1)</p>
<p>Code</p>
<pre><code class="prism language-java">class Solution {

    /*动态规划：1.Word[i]单独组成abc
    *          2.如果Word[i]&gt;word[i-1] 则word[i]和word[i-1]一起构成abc
    * 
     */
    public int addMinimum(String word) {

        int n = word.length();
        int[] d = new int[n + 1];
        for (int i = 1; i &lt;= n; i++) {

            d[i] = d[i - 1] + 2;
            if (i &gt; 1 &amp;&amp; word.charAt(i - 1) &gt; word.charAt(i - 2)) {

                d[i] = d[i - 1] - 1;
            }

        }
        return d[n];
    }
}
</code></pre>
<p>注：脑子没转过来，看了题解。。。。。。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135538868</p>]]></description><guid isPermaLink="false">/archives/ec924632-275b-45d6-b8a4-03ab8ee873a6</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:08:05 GMT</pubDate></item><item><title><![CDATA[day-07 统计出现过一次的公共字符串]]></title><link>https://wiki.dgfygk.com/archives/14b25640-f8f8-4633-98a6-e134d4187e59</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-07%20%E7%BB%9F%E8%AE%A1%E5%87%BA%E7%8E%B0%E8%BF%87%E4%B8%80%E6%AC%A1%E7%9A%84%E5%85%AC%E5%85%B1%E5%AD%97%E7%AC%A6%E4%B8%B2&amp;url=/archives/14b25640-f8f8-4633-98a6-e134d4187e59" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc8677213025d6b792c2219c159d3dcc8.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 用哈希表统计words1和words2中各个字符串的出现次数，次数皆为1的字符串符合题意</p>
<p>解题方法
 <br>
 //用于存储words1中各个字符串的出现次数
 <br>
 HashMap<string,integer> hashMap1=new HashMap&lt;&gt;();</string,integer></p>
<p>//用于存储words2中各个字符串的出现次数
 <br>
 HashMap<string,integer> hashMap2=new HashMap&lt;&gt;();</string,integer></p>
<p>//如果words1中某个字符串出现一次且在words2中也出现一次，则ans++
 <br>
 if(hashMap1.getOrDefault(words1[i],-1)==1\&amp;\&amp;hashMap2.getOrDefault(words1[i],-1)==1) ans++;</p>
<p>时间复杂度,： O(n)</p>
<p>空间复杂度,： O(n)</p>
<p>Code</p>
<pre><code class="prism language-java">public static int countWords(String[] words1, String[] words2) {

        int ans=0;
        HashMap&lt;String,Integer&gt; hashMap1=new HashMap&lt;&gt;();//用于存储words1中各个字符串的出现次数
        HashMap&lt;String,Integer&gt; hashMap2=new HashMap&lt;&gt;();//用于存储words2中各个字符串的出现次数
        for(int i=0;i&lt;words1.length;i++){
      //记录words1中各个字符串的出现次数
            hashMap1.put(words1[i],hashMap1.getOrDefault(words1[i],0)+1);
        }

        for(int i=0;i&lt;words2.length;i++){
      //记录words2中各个字符串的出现次数
            hashMap2.put(words2[i],hashMap2.getOrDefault(words2[i],0)+1);
        }

        for(int i=0;i&lt;words1.length;i++){
      //如果words1中某个字符串出现一次且在words2中也出现一次，则ans++
            if(hashMap1.getOrDefault(words1[i],-1)==1&amp;&amp;hashMap2.getOrDefault(words1[i],-1)==1) ans++;
        }
        return ans; //返回结果
    }
</code></pre>
<p>注：hashMap1.get(words1[i])==1会报错，因为可能返回null
 <br>
 所以使用hashMap1.getOrDefault(words1[i],-1)==1</p>
<p>官方：</p>
<pre><code class="prism language-java">class Solution {

    public int countWords(String[] words1, String[] words2) {

        // 统计字符串出现频率
        Map&lt;String, Integer&gt; freq1 = new HashMap&lt;&gt;();
        Map&lt;String, Integer&gt; freq2 = new HashMap&lt;&gt;();
        for (String w : words1) {

            freq1.put(w, freq1.getOrDefault(w, 0) + 1);
        }
        for (String w : words2) {

            freq2.put(w, freq2.getOrDefault(w, 0) + 1);
        }

        // 遍历 words1 出现的字符并判断是否满足要求
        int res = 0;
        for (String w : freq1.keySet()) {

            if (freq1.get(w) == 1 &amp;&amp; freq2.getOrDefault(w, 0) == 1) {

                res++;
            }
        }
        return res;
    }
}

作者：力扣官方题解
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135561567</p>]]></description><guid isPermaLink="false">/archives/14b25640-f8f8-4633-98a6-e134d4187e59</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:07:53 GMT</pubDate></item><item><title><![CDATA[day-09 删除排序链表中的重复元素]]></title><link>https://wiki.dgfygk.com/archives/ec39f290-474c-4313-9be7-fcb0f73f4775</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-09%20%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0&amp;url=/archives/ec39f290-474c-4313-9be7-fcb0f73f4775" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fef03db07fae43710cd424be1f980a0b1.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 从前往后遍历链表，当当前节点的值与下一个节点值相等时，删除下一节点；否则向后移动一个节点，继续遍历</p>
<p>解题方法
 <br>
 while(p!=null\&amp;\&amp;p.next!=null){</p>
<p>if(p.next.val==p.val)p.next=p.next.next;//当前节点的值与下一个节点值相等
 <br>
 else p=p.next;//向后移动一个节点，继续遍历</p>
<p>}
 <br>
 注意：别忘记head为空的情况</p>
<p>时间复杂度： O(n)</p>
<p>空间复杂度： O(1)</p>
<p>Code</p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode deleteDuplicates(ListNode head) {

        if(head==null)return head;
        ListNode p=new ListNode(head.val,head.next);
        ListNode t=new ListNode();
        t=p;
        while(p!=null&amp;&amp;p.next!=null){

            if(p.next.val==p.val)p.next=p.next.next;
            else p=p.next;
        }
        return t;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135580298</p>]]></description><guid isPermaLink="false">/archives/ec39f290-474c-4313-9be7-fcb0f73f4775</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:07:44 GMT</pubDate></item><item><title><![CDATA[SpringMVC-04]]></title><link>https://wiki.dgfygk.com/archives/1ee9289b-bd76-4998-a4b3-f90da6e443bc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringMVC-04&amp;url=/archives/1ee9289b-bd76-4998-a4b3-f90da6e443bc" width="1" height="1" alt="" style="opacity:0;">
<p>RESTful是一种针对Web服务的软件架构风格，它基于HTTP协议和其他标准，用于构建可扩展、可维护和可拓展的网络应用程序。
 <br>
 RESTful风格的设计原则包括以下几点：</p>
<ol>
 <li>资源：将应用程序的所有内容都视为资源，每个资源都有唯一的标识符（URI）。</li>
 <li>统一接口：通过使用统一的HTTP方法（GET、POST、PUT、DELETE等）和标准的HTTP状态码来对资源进行操作。</li>
 <li>无状态：每个请求都应该包含足够的信息，以便服务器能够理解并 处理请求，而不需要依赖于之前的请求。</li>
 <li>可缓存性：服务器可以指定资源是否可以被缓存，以提高性能和可伸缩性。</li>
 <li>分层系统：客户端和服务器之间可以存在多个中间层，这些中间层可以提供负载均衡、缓存等功能，以提高系统的性能和可扩展性。
  <br>
  通过遵循RESTful风格，可以使得应用程序更具有灵活性和可拓展性，并且能够通过使用统一的接口和标准的HTTP协议进行跨平台和跨语言的通信。</li>
</ol>
<p>查询 GET
 <br>
 保存 POST
 <br>
 删除 DELETE
 <br>
 更新 PUT</p>
<p>选择传递参数：保存 修改 用json
 <br>
 查询 删除：如果参数是id用路径传递参数 如果参数不是id,是范围参数 用param传递参数</p>
<pre><code class="prism language-java">    @GetMapping
    public List&lt;User&gt; page(@RequestParam(required = false,defaultValue = "1") int page, 
                           @RequestParam(required = false,defaultValue = "10") int size){


        return null;
    }

    @PostMapping
    public User save(@RequestBody User user){

        return user;
    }

    @GetMapping({
   "id"})
    public User detail(@PathVariable Integer id){


        return null;
    }

    @PutMapping
    public User update(@RequestBody User user){


        return user;
    }

    @DeleteMapping({
   "id"})
    public User delete(@PathVariable Integer id){


        return null;
    }

    @GetMapping("seach")
    public List&lt;User&gt; search(String keyword,@RequestParam(required = false,defaultValue = "1") int page,
                             @RequestParam(required = false,defaultValue = "10") int size){


        return null;
    }
</code></pre>
<p>声明式异常：1声明一个全局异常处理类
 <br>
 2.自定义异常处理方法</p>
<pre><code class="prism language-java">//@ControllerAdvice        //可以返回逻辑视图 转发 重定向
@RestControllerAdvice    //相当于@ControllerAdvice 和 @ResponseBody 写了就可以不写那两个

public class GlobalExceptionHandler {

    @ExceptionHandler(ArithmeticException.class)
    public Object ArithmeticExceptionHandler(ArithmeticException e){

        //自定义处理异常
        String message=e.getMessage();
        System.out.println(message);
        return message;
    }

    @ExceptionHandler(Exception.class)  //全局异常处理    没找到匹配的异常则执行这个
    public Object ExceptionHandler(Exception e){

        //自定义处理异常
        String message=e.getMessage();
        System.out.println(message);
        return message;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135505073</p>]]></description><guid isPermaLink="false">/archives/1ee9289b-bd76-4998-a4b3-f90da6e443bc</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:07:33 GMT</pubDate></item><item><title><![CDATA[SpringMVC-05]]></title><link>https://wiki.dgfygk.com/archives/06d7467f-72cf-454e-ad2e-32f218d4883e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringMVC-05&amp;url=/archives/06d7467f-72cf-454e-ad2e-32f218d4883e" width="1" height="1" alt="" style="opacity:0;">
<p>Spring MVC拦截器是在请求到达处理器前或返回客户端前执行的组件，它可以用于拦截和处理请求，实现一些通用的功能。
 <br>
 Spring MVC拦截器可以实现的功能包括：</p>
<ol>
 <li>登录验证：拦截所有请求，检查用户是否已登录，未登录则跳转到登录页面。</li>
 <li>权限验证：拦截请求，检查用户是否拥有访问权限，没有权限则跳转到错误页面。</li>
 <li>日志记录：记录请求的参数、处理时间等信息，用于分析和排查问题。</li>
 <li>数据预处理：对请求参数进行一些预处理，比如格式化日期、转换数据类型等。</li>
 <li>异常处理：拦截处理过程中出现的异常，进行统一的异常处理。</li>
</ol>
<p>要使用Spring MVC拦截器，需要实现HandlerInterceptor接口，并注册到Spring的上下文中。拦截器可以通过配置匹配的URL路径、排除的URL路径等条件来决定对哪些请求进行拦截。</p>
<p>拦截器的执行顺序由配置的顺序决定，可以通过实现Ordered接口来指定拦截器的顺序。在拦截器的preHandle方法中，可以根据需要进行请求的拦截或处理；在postHandle方法中可以对处理结果做一些处理；在afterCompletion方法中可以进行一些清理工作。</p>
<p>拦截器是Spring MVC框架提供的一种灵活、可扩展的机制，可以在请求处理的各个阶段进行拦截和处理。它可以帮助我们实现一些通用的功能，代码重用性高，提高了开发效率。</p>
<p>拦截器 SpringMVC
 <br>
 过滤器 javaWeb</p>
<p>拦截器使用：1.创建拦截器类
 <br>
 2.添加拦截器
 <br>
 多个拦截器，先声明的优先级高</p>
<pre><code class="prism language-java">public class MyInterceptor implements HandlerInterceptor {

    //执行handler之前   调用的拦截方法 如登录保护 权限处理等
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("request:"+request+" response:"+response+" handler"+handler);
        return true;
    }

    //执行handler之后   触发的方法   如铭感词汇检查等
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("MyInterceptor.postHandle");
    }

    //整体处理完毕后   触发的方法
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("MyInterceptor.afterCompletion");
    }
}



    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //拦截全部请求
        registry.addInterceptor(new MyInterceptor());
    }


        //方案2.指定地址拦截
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/data");     // 模糊地址：/user/*     
                                                                                        //*代表任意一层     **代表任意多层
</code></pre>
<p>在Spring MVC中，我们可以使用JSR 303（Bean Validation）注解来进行参数校验。JSR 303定义了一组标准的注解，可以用于对Java Bean的属性进行验证。
 <br>
 常用的JSR 303注解包括：
 <br>
 @NotNull：表示属性不能为null。
 <br>
 @NotEmpty：表示字符串不能为空，集合不能为空。
 <br>
 @NotBlank：表示字符串不能为空，且长度必须大于0。
 <br>
 @Size：表示属性的大小必须在指定的范围内。
 <br>
 @Min：表示属性的最小值。
 <br>
 @Max：表示属性的最大值。
 <br>
 @Pattern：表示属性必须匹配指定的正则表达式。
 <br>
 @Email：表示属性必须是一个有效的电子邮件地址。</p>
<p>导入依赖</p>
<pre><code class="prism language-java">        &lt;dependency&gt;
            &lt;groupId&gt;org.hibernate&lt;/groupId&gt;
            &lt;artifactId&gt;hibernate-validator&lt;/artifactId&gt;
            &lt;version&gt;8.0.0.Final&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.hibernate&lt;/groupId&gt;
            &lt;artifactId&gt;hibernate-validator-annotation-processor&lt;/artifactId&gt;
            &lt;version&gt;8.0.0.Final&lt;/version&gt;
        &lt;/dependency&gt;
</code></pre>
<p>步骤：1.在实体类中添加注解</p>
<pre><code class="prism language-java">public class User1 {

    @NotBlank
    private String name;        //不为空
    @Length(min=6)
    private String password;    //长度大于6位
    @Min(1)
    private int age;            //&gt;=1
    @Email
    private String email;       //邮箱格式
    @Past
    private Date birthday;      //过去时间
}
</code></pre>
<p>2.handler(@Validated 实体类 实体类对象)</p>
<pre><code class="prism language-java">@RestController
@RequestMapping("user1")
public class UserController1 {

    @PostMapping("register")
    public Object register(@Validated @RequestBody User1 user1, BindingResult result){

        //BindingResult 获取错误，绑定错误，不会直接返回
        if(result.hasErrors()){

            Map data=new HashMap();
            data.put("code",400);
            data.put("msg","参数错误");
            return data;
        }
        return user1;
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135521333</p>]]></description><guid isPermaLink="false">/archives/06d7467f-72cf-454e-ad2e-32f218d4883e</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:07:10 GMT</pubDate></item><item><title><![CDATA[day-05 删除子串后的字符串最小长度]]></title><link>https://wiki.dgfygk.com/archives/dc675adc-75de-4e36-b5b0-c20389e04678</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-05%20%E5%88%A0%E9%99%A4%E5%AD%90%E4%B8%B2%E5%90%8E%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9C%80%E5%B0%8F%E9%95%BF%E5%BA%A6&amp;url=/archives/dc675adc-75de-4e36-b5b0-c20389e04678" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F37433835c1ebb905e292bb7545c47081.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 通过不断地检查是否含有"AB"或"CD”，如果有则将其从字符串中删除，直到"AB"或"CD"都不存在时，返回字符串的长度</p>
<p>解题方法
 <br>
 //检测是否有"AB”
 <br>
 for(int i=0;i<len-1;i++){< p> </len-1;i++){<></p>
<p>if(s.charAt(i)'A'\&amp;\&amp;s.charAt(i+1)'B'){</p>
<p>s=s.substring(0,i)+s.substring(i+2);
 <br>
 flag1=false;
 <br>
 len=s.length();
 <br>
 }
 <br>
 }</p>
<p>//检测是否有"CD”
 <br>
 for(int i=0;i<len-1;i++){< p> </len-1;i++){<></p>
<p>if(s.charAt(i)'C'\&amp;\&amp;s.charAt(i+1)'D'){</p>
<p>s=s.substring(0,i)+s.substring(i+2);
 <br>
 flag1=false;
 <br>
 len=s.length();
 <br>
 }
 <br>
 }</p>
<p>“AB” “CD"都不存在，则返回字符串长度
 <br>
 使用while(true)进行不断循环,直到得到最小字符串</p>
<p>时间复杂度: O(n)</p>
<p>空间复杂度: O(1)
 <br>
 Code</p>
<pre><code class="prism language-java">    public int minLength(String s) {

        int len=s.length();;
        boolean flag1,flag2;
        while(true){

            flag1=true;
            flag2=true;
            //检测是否有"AB"
            for(int i=0;i&lt;len-1;i++){

                if(s.charAt(i)=='A'&amp;&amp;s.charAt(i+1)=='B'){

                    s=s.substring(0,i)+s.substring(i+2);
                    flag1=false;
                    len=s.length();
                }
            }
            //检测是否有"CD"
            for(int i=0;i&lt;len-1;i++){

                if(s.charAt(i)=='C'&amp;&amp;s.charAt(i+1)=='D'){

                    s=s.substring(0,i)+s.substring(i+2);
                    flag1=false;
                    len=s.length();
                }
            }
            //"AB" "CD"都不存在，则返回字符串长度
            if(flag1==true&amp;&amp;flag2==true) break;
        }
        return s.length();
    }
</code></pre>
<p>官方答案：</p>
<pre><code class="prism language-java">class Solution {

    public int minLength(String s) {

        List&lt;Character&gt; stack = new ArrayList&lt;Character&gt;();
        for (int i = 0; i &lt; s.length(); i++) {

            char c = s.charAt(i);
            stack.add(c);
            int m = stack.size();
            if (m &gt;= 2 &amp;&amp;
                (stack.get(m - 2) == 'A' &amp;&amp; stack.get(m - 1) == 'B' ||
                stack.get(m - 2) == 'C' &amp;&amp; stack.get(m - 1) == 'D')) {

                stack.remove(m - 1);
                stack.remove(m - 2);
            }
        }
        return stack.size();
    }
}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135509885</p>]]></description><guid isPermaLink="false">/archives/dc675adc-75de-4e36-b5b0-c20389e04678</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:06:53 GMT</pubDate></item><item><title><![CDATA[MyBatis-06]]></title><link>https://wiki.dgfygk.com/archives/508048df-f4a8-4224-9df4-94dcf6e629fc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-06&amp;url=/archives/508048df-f4a8-4224-9df4-94dcf6e629fc" width="1" height="1" alt="" style="opacity:0;">
<p><strong>动态sql语句</strong>
 <br>
 标签 和 标签：</p>
<pre><code class="prism language-java">    &lt;select id="query" resultType="employee"&gt;
        select * from t_emp
        &lt;where&gt;
        &lt;if test="name!=null"&gt;        &lt;!--如果为true 则会把标签中的sql语句进行拼接，否则不拼接--&gt;
        emp_name=#{
   name}
        &lt;/if&gt;

        &lt;if test="salary!=null and salary &amp;gt; 100"&gt;    &lt;!--  &amp;gt; &gt;    &amp;lt;  &lt;  --&gt;
        and emp_salary=#{
   salary}
        &lt;/if&gt;
        &lt;/where&gt;
    &lt;/select&gt;
</code></pre>
<p>set标签：</p>
<pre><code class="prism language-java">    &lt;update id="update"&gt;
        update t_emp
        &lt;set&gt;
        &lt;if test="empName!=null"&gt;
        emp_name=#{
   empName},
        &lt;/if&gt;

        &lt;if test="empSalary!=null"&gt;
        emp_salary=#{
   empSalary}
        &lt;/if&gt;
        &lt;/set&gt;
        where emp_id=#{
   empId}
    &lt;/update&gt;
</code></pre>
<p>trim标签(了解)：可以替代 where标签 和 set标签</p>
<p>choose / when / otherwise 标签：有多个条件（如where id=1 and name=“haha”）的sql语句</p>
<p>foreach标签：用于批量操作（crud）时
 <br>
 如果一个标签设计多个语句，需要设置允许指定多语句！ allowMultiQueries=true</p>
<pre><code class="prism language-java">//批量查询
    &lt;select id="queryBatch" resultType="employee"&gt;
        select * from t_emp where emp_id in
        &lt;!--
            collection
            open            遍历之前要追加的字符串
            close           遍历结束要追加的字符串
            separator       分隔符号
            item            获取每个遍历项
        --&gt;
        &lt;foreach collection="ids" open="(" separator="," close=")" item="id"&gt;
            #{
   id}
        &lt;/foreach&gt;
    &lt;/select&gt;
</code></pre>
<p>sql片段：</p>
<pre><code class="prism language-java">    &lt;!--sql片段--&gt;
    &lt;sql id="selectSql"&gt;
        select * from t_emp
    &lt;/sql&gt;
    &lt;select id="" resultType=""&gt;
        &lt;!--引入--&gt;
        &lt;include refid="selectSql"&gt;&lt;/include&gt;
    &lt;/select&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135454987</p>]]></description><guid isPermaLink="false">/archives/508048df-f4a8-4224-9df4-94dcf6e629fc</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:06:42 GMT</pubDate></item><item><title><![CDATA[MyBatis-07]]></title><link>https://wiki.dgfygk.com/archives/afc471fb-c47c-4cbb-b6ce-14366fc8fdf3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-07&amp;url=/archives/afc471fb-c47c-4cbb-b6ce-14366fc8fdf3" width="1" height="1" alt="" style="opacity:0;">
<p><strong>MyBatis高级拓展</strong>
 <br>
 将Mapper接口和Mapper.xml文件打包到同一地址下 （在resources下创建相同的文件夹结构）
 <br>
 com/dc/mapper创建 多层结构
 <br>
 com.dc.mapper创建 一层结构</p>
<p>分页插件</p>
<pre><code class="prism language-java">//导入依赖
&lt;dependency&gt;
   &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt;
   &lt;artifactId&gt;pagehelper&lt;/artifactId&gt;
   &lt;version&gt;5.1.11&lt;/version&gt;
&lt;/dependency&gt;

//配置
    &lt;plugins&gt;
        &lt;plugin interceptor="com.github.pagehelper.PageInterceptor"&gt;
            &lt;property name="helperDialect" value="mysql"/&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;

        //使用分页插件
        PageHelper.startPage(1,2);
        List&lt;Employee&gt; list=mapper.queryList();
        PageInfo&lt;Employee&gt; pageInfo=new PageInfo&lt;&gt;(); 
        //获取相关数据
        List&lt;Employee&gt; list1=pageInfo.getList();
        int pages=pageInfo.getPages();
        long total=pageInfo.getTotal();
</code></pre>
<p>MyBatis逆向工程和MyBatisX插件 只生成单表的增删改查
 <br>
 MyBatis逆向工程是一个用于自动生成Java代码的工具，它可以根据数据库中的表结构自动生成实体类、Mapper接口以及XML配置文件。逆向工程可以帮助开发人员快速生成与数据库交互的代码，减少手动编写的工作量。使用逆向工程可以提高代码的开发效率，并且可以确保生成的代码与数据库表结构保持一致，减少出错的可能性。
 <br>
 MyBatis逆向工程主要有两个核心组件：</p>
<ol>
 <li>生成器配置文件：通过配置文件指定数据库信息、生成的代码位置、生成器的一些属性等。可以通过配置文件中的table标签配置需要生成代码的表，也可以通过sql标签配置自定义的SQL语句来生成代码。</li>
 <li>生成器类：通过生成器配置文件来创建生成器类对象，然后调用生成器类的方法生成代码。生成器类会根据配置文件中配置的信息连接数据库，读取数据库的表结构，然后根据表结构生成对应的Java代码。
  <br>
  使用MyBatis逆向工程需要按照以下步骤进行：</li>
 <li>创建生成器配置文件，配置数据库信息、生成代码的位置等。</li>
 <li>创建生成器类，通过读取配置文件来创建生成器对象。</li>
 <li>调用生成器对象的方法生成代码。</li>
 <li>查看生成的代码，对生成的代码进行适当的修改和调整。</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa17d652b8260204e04cbaa4cbbcd1931.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135457573</p>]]></description><guid isPermaLink="false">/archives/afc471fb-c47c-4cbb-b6ce-14366fc8fdf3</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:06:21 GMT</pubDate></item><item><title><![CDATA[SpringMVC-03]]></title><link>https://wiki.dgfygk.com/archives/18f78ef6-f570-42c0-aa91-31f880c7d1ba</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringMVC-03&amp;url=/archives/18f78ef6-f570-42c0-aa91-31f880c7d1ba" width="1" height="1" alt="" style="opacity:0;">
<p>开发模式：前后端分离 【重点】 可以在多个端打开
 <br>
 混合开发 一般只用浏览器打开</p>
<p>返回视图</p>
<pre><code class="prism language-java">//MvcConfig

@Configuration
@EnableWebMvc
@ComponentScan("com.dc.jsp")
public class MvcConfig implements WebMvcConfigurer {
       //视图解析器，配置前后缀，不使用@Bean
    @Override
    public void configureViewResolvers(ViewResolverRegistry resolverRegistry){

        resolverRegistry.jsp("/WEB-INF/views/",".jsp");
    }
}

//SpringMVCInit
public class SpringMVCInit extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class&lt;?&gt;[] getRootConfigClasses() {

        return new Class[0];
    }

    @Override
    protected Class&lt;?&gt;[] getServletConfigClasses() {

        return new Class[]{
   MvcConfig.class};
    }

    @Override
    protected String[] getServletMappings() {

        return new String[]{
   "/"};
    }
}

//JspController
@Controller
@RequestMapping("jsp")
public class JspController {

    /*
    *TODO:快速查找视图：1.方法返回值是String
    *                 2.不能添加@ResponseBody
    *                 3.返回值填？中的内容         /WEB-INF/views/？.jsp"
    * */
    @GetMapping("index")
    public String index(HttpServletRequest request){

        request.setAttribute("data","hello jsp");       //往request中放数据
        return "index";
    }
}
</code></pre>
<p>转发和重定向</p>
<pre><code class="prism language-java">    //转发
    @GetMapping("forward")
    public String forward(){

        return "forward:/jsp/index";        //转发地址
    }

    //重定向
    @GetMapping("redirect")
    public String redirect(){

        return "redirect:http://www.baidu.com";       //重定向地址
    }
</code></pre>
<p>返回json数据</p>
<pre><code class="prism language-java">    @GetMapping("data")
    @ResponseBody
    public User data(){

        User user=new User();
        user.setAge(18);
        user.setName("haha");
        return user;
    }

    @GetMapping("data1")
    @ResponseBody
    public List&lt;User&gt; data1(){

        User user=new User();
        user.setAge(18);
        user.setName("haha");
        List&lt;User&gt; users=new ArrayList&lt;&gt;();
        users.add(user);
        return users;
    }
</code></pre>
<p>//@RestController 作用相当于@Controller 和 @ResponseBody 写了这个就不用写这两个</p>
<p>返回静态资源处理</p>
<pre><code class="prism language-java">        //开启静态资源查找  先去handlerMapping找有没有对应的handler 没有再去找静态资源
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {

        configurer.enable();
    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135495655</p>]]></description><guid isPermaLink="false">/archives/18f78ef6-f570-42c0-aa91-31f880c7d1ba</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:05:48 GMT</pubDate></item><item><title><![CDATA[day-02 赎金信]]></title><link>https://wiki.dgfygk.com/archives/8095eb9c-61d1-4b77-8ddb-e45edfff6e92</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-02%20%E8%B5%8E%E9%87%91%E4%BF%A1&amp;url=/archives/8095eb9c-61d1-4b77-8ddb-e45edfff6e92" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2b636ef051289335807687600bede5cb.png&amp;size=m" alt="">
 <br>
 <strong>思路:</strong>
 <br>
 因为题目说明字符串中只含有小写英文字母，所以创建两个数组分别统计ransomNote和magazine中a-z的个数
 <br>
 若存在r[i]&gt;m[i] 返回false
 <br>
 否则返回true</p>
<p><strong>解题方法:</strong>
 <br>
 r[26]存储ransomNote中a-z字符个数
 <br>
 m[26]存储magazine中a-z字符个数
 <br>
 若存在r[i]&gt;m[i] 返回false
 <br>
 否则返回true</p>
<p>时间复杂度: O(n)
 <br>
 空间复杂度:O(1)</p>
<p><strong>code</strong></p>
<pre><code class="prism language-java">class Solution {

    public boolean canConstruct(String ransomNote, String magazine) {

        int r[]=new int[26];
        int m[]=new int[26];
        char ch;
        int po;
        for(int i=0;i&lt;ransomNote.length();i++){

            ch=ransomNote.charAt(i);
            po=ch-'a';
            r[po]=r[po]+1;
        }
        for(int i=0;i&lt;magazine.length();i++){

           ch=magazine.charAt(i);
            po=ch-'a';
            m[po]=m[po]+1;
        }
        boolean flag=true;
        int i=0;
        while(i&lt;26){

            if(r[i]&gt;m[i]){

                flag=false;
                break;
            }
            i++;
        }
        if(flag==true) return true;
        else return false;
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4f0bc8813e0cd7a88be50492b59313dd.png&amp;size=m" alt="">官方答案：</p>
<pre><code class="prism language-java">class Solution {

    public boolean canConstruct(String ransomNote, String magazine) {

        if (ransomNote.length() &gt; magazine.length()) {

            return false;
        }
        int[] cnt = new int[26];
        for (char c : magazine.toCharArray()) {

            cnt[c - 'a']++;
        }
        for (char c : ransomNote.toCharArray()) {

            cnt[c - 'a']--;
            if(cnt[c - 'a'] &lt; 0) {

                return false;
            }
        }
        return true;
    }
}

作者：力扣官方题解
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135444449</p>]]></description><guid isPermaLink="false">/archives/8095eb9c-61d1-4b77-8ddb-e45edfff6e92</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:05:16 GMT</pubDate></item><item><title><![CDATA[day-03 回旋镖的数量]]></title><link>https://wiki.dgfygk.com/archives/b532c536-0046-413a-93b6-f87bb157fe04</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-03%20%E5%9B%9E%E6%97%8B%E9%95%96%E7%9A%84%E6%95%B0%E9%87%8F&amp;url=/archives/b532c536-0046-413a-93b6-f87bb157fe04" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcb7ccdfdb00b6c70c3878097dc403163.png&amp;size=m" alt="">
 <br>
 思路：
 <br>
 用map集合记录相同距离的组合数,HashMap<integer,integer>，前一个值表示距离，后一个值表示组合数</integer,integer></p>
<p>解题方法：
 <br>
 用两次for循环，记录相同距离的组合数，统计出总的可行组合数，组合数*2即为答案</p>
<pre><code class="prism language-java">class Solution {

    public int numberOfBoomerangs(int[][] points) {

        int ans=0;
        for(int []p:points){

             HashMap&lt;Integer,Integer&gt; hashMap=new HashMap&lt;&gt;();
            for(int []q:points){

                if(p[0]==q[0]&amp;&amp;p[1]==q[1]) continue;
                int dis=(p[0]-q[0])*(p[0]-q[0])+(p[1]-q[1])*(p[1]-q[1]);
                int num=hashMap.getOrDefault(dis,0);
                ans=ans+num;
                hashMap.put(dis,num+1);
            }
        }
        return ans*2;
    }

}

注：本来暴力的，结果时间超时，看了答案，恍然大悟
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135466708</p>]]></description><guid isPermaLink="false">/archives/b532c536-0046-413a-93b6-f87bb157fe04</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:05:00 GMT</pubDate></item><item><title><![CDATA[day-04 字符串中的额外字符]]></title><link>https://wiki.dgfygk.com/archives/d43136da-fa67-4e72-9bab-eb9d500a7af4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-04%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E9%A2%9D%E5%A4%96%E5%AD%97%E7%AC%A6&amp;url=/archives/d43136da-fa67-4e72-9bab-eb9d500a7af4" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F51d7d5cb2692e2bd9948b40409b67832.png&amp;size=m" alt="">
 <br>
 <strong>思路</strong>
 <br>
 动态规划，每个字符要么额外要么不是额外</p>
<p><strong>解题方法</strong>
 <br>
 int[] dp = new int[n+1];
 <br>
 dp[i] 表示从字符串开头到字符串索引i位置的最少额外字符数
 <br>
 dp[i + 1] = Math.min(dp[i + 1], dp[j])
 <br>
 dp[j]表示假设s第i个字符不是额外的，可能等于dp[i + 1]，也可能等于dp[i + 1]-1</p>
<p><strong>code</strong></p>
<pre><code class="prism language-java">class Solution {

    public int minExtraChar(String s, String[] dictionary) {

        int n = s.length();
        int[] dp = new int[n+1];  // dp[i+1] 表示从字符串开头到字符串索引i位置的最少额外字符数
        Set&lt;String&gt; dictionarySet = new HashSet&lt;&gt;();
        for(String word: dictionary){

            dictionarySet.add(word);        //依次存入set中
        }
        for(int i = 0; i &lt; n; i++){

            dp[i + 1] = dp[i] + 1;  // 字符串i对应的位置字符是额外字符
            for(int j = 0; j &lt;= i; j++){
       // 假设字符串i对应的位置字符是额外字符
                if(dictionarySet.contains(s.substring(j, i + 1))){

                    dp[i + 1] = Math.min(dp[i + 1], dp[j]);
                }
            }
        }
        return dp[n];   // dp[n] 即为额外最少字符数
    }
}
</code></pre>
<p>substring(int beginIndex)：从指定索引位置 beginIndex（包括）开始截取字符串的剩余部分。</p>
<p>substring(int beginIndex, int endIndex)：从指定索引位置 beginIndex（包括）开始截取字符串，直到索引位置 endIndex（不包括）为止。</p>
<p>dictionarySet.contains(s.substring(j, i + 1)) set中是否有字符串 s[i]–s[j-1]</p>
<p>注：不会，看了题解，哈哈。。。。。。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135490237</p>]]></description><guid isPermaLink="false">/archives/d43136da-fa67-4e72-9bab-eb9d500a7af4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:04:16 GMT</pubDate></item><item><title><![CDATA[MyBatis-05]]></title><link>https://wiki.dgfygk.com/archives/47028013-cdac-4ff9-96b8-a31d97f5de5b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-05&amp;url=/archives/47028013-cdac-4ff9-96b8-a31d97f5de5b" width="1" height="1" alt="" style="opacity:0;">
<p><strong>多表查询</strong>
 <br>
 1.多表查询的sql语句需要我们自己编写 连接查询
 <br>
 2.自己设计存储数据的实体类 承接多个表查询的结果
 <br>
 3.自己定义结果集映射 resultMap</p>
<p>对一 属性中包含对方的对象 private Customer customer
 <br>
 对多 属性中包含对方的对象集合 private List orderList</p>
<pre><code class="prism language-java">    &lt;typeAliases&gt;
        &lt;package name="com.dc.pojo"/&gt;   &lt;!--对类起别名--&gt;
    &lt;/typeAliases&gt;

//起别名后
com.dc.mapper.Order     可以直接写为      order
</code></pre>
<p>自定义映射（对一查询） <strong>association</strong></p>
<pre><code class="prism language-java">    &lt;resultMap id="orderMap" type="order"&gt;
        &lt;!--主键--&gt;
        &lt;id column="order_id" property="orderId"&gt;&lt;/id&gt;

        &lt;!--普通列--&gt;
        &lt;result column="order_name" property="orderName"&gt;&lt;/result&gt;
        &lt;result column="order_id" property="customerId"&gt;&lt;/result&gt;

        &lt;!--
        对象属性:  property     对象属性名
                  javaType     对象类型
        --&gt;
        &lt;association property="customer" javaType="customer"&gt;
            &lt;id column="customer_id" property="customerId"&gt;&lt;/id&gt;
            &lt;result column="customer_name" property="customerName"&gt;&lt;/result&gt;
        &lt;/association&gt;
    &lt;/resultMap&gt;
    &lt;select id="queryOrderById" resultMap="orderMap"&gt;
        select * from t_order tor join t_customer tur
        on tor.customer_id=tur.customer_id
        where tor.order_id=#{
   id};
    &lt;/select&gt;
</code></pre>
<p>自定义映射（对多查询） <strong>collection</strong></p>
<pre><code class="prism language-java">&lt;resultMap id="customerMap" type="customer"&gt;
        &lt;id column="customer_id" property="customerId"&gt;&lt;/id&gt;
        &lt;result column="customer_name" property="customerName"&gt;&lt;/result&gt;

        &lt;!--
        collection:      给集合属性赋值
            property 集合属性名
            ofType   集合的泛型类型
        --&gt;
        &lt;collection property="orderList" ofType="order"&gt;
            &lt;id column="order_id" property="orderId"&gt;&lt;/id&gt;
            &lt;result column="order_name" property="orderName"&gt;&lt;/result&gt;
            &lt;result column="customer_id" property="customerId"&gt;&lt;/result&gt;
            &lt;!--不需要再给private Customer customer赋值，否则产生循环--&gt;
        &lt;/collection&gt;
    &lt;/resultMap&gt;
    &lt;select id="queryList" resultMap="customerMap"&gt;
        select * from t_order tor join t_customer tur
        on tor.customer_id=tur.customer_id
    &lt;/select&gt;
</code></pre>
<pre><code>```prism language-java
&lt;setting name="autoMappingBehavior" value="FULL"/&gt;          &lt;!--开启后，resultMap中的result标签可以不写，会自动映射--&gt;


    &lt;resultMap id="customerMap" type="customer"&gt;
        &lt;id column="customer_id" property="customerId"&gt;&lt;/id&gt;

        &lt;collection property="orderList" ofType="order"&gt;
            &lt;id column="order_id" property="orderId"&gt;&lt;/id&gt;
        &lt;/collection&gt;
    &lt;/resultMap&gt;
    &lt;select id="queryList" resultMap="customerMap"&gt;
        select * from t_order tor join t_customer tur
        on tor.customer_id=tur.customer_id
    &lt;/select&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135448831</p>]]></description><guid isPermaLink="false">/archives/47028013-cdac-4ff9-96b8-a31d97f5de5b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:04:05 GMT</pubDate></item><item><title><![CDATA[MyBatis-practice-04]]></title><link>https://wiki.dgfygk.com/archives/c1cf5014-e301-476e-9ec8-71cbb1f57d68</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-practice-04&amp;url=/archives/c1cf5014-e301-476e-9ec8-71cbb1f57d68" width="1" height="1" alt="" style="opacity:0;">
<p>lombook插件</p>
<pre><code class="prism language-java">@Data//lombook注解,实现get() set()方法 toString()方法
</code></pre>
<p>User:</p>
<pre><code class="prism language-java">@Data//lombook注解,实现get() set()方法 toString()方法
public class User {

    private Integer id;
    private String username;
    private String password;
}
</code></pre>
<p>UserMapper</p>
<pre><code class="prism language-java">public interface UserMapper {

    int insert(User user);

    int update(User user);

    int delete(Integer id);

    User selectById(Integer id);

    List&lt;User&gt; selectAll();
}
</code></pre>
<p>mybatis-config.xml</p>
<pre><code class="prism language-java">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd"&gt;
&lt;configuration&gt;
    &lt;settings&gt;
        &lt;setting name="mapUnderscoreToCamelCase" value="true"/&gt;      &lt;!--开启驼峰自动映射--&gt;
        &lt;setting name="logImpl" value="STDOUT_LOGGING"/&gt;        &lt;!--开启日志输出，选择使用system进行控制台输出--&gt;
    &lt;/settings&gt;

    &lt;typeAliases&gt;
        &lt;package name="com.dc.pojo"/&gt;
    &lt;/typeAliases&gt;

    &lt;environments default="development"&gt;
        &lt;environment id="development"&gt;
            &lt;transactionManager type="JDBC"&gt;&lt;/transactionManager&gt;
            &lt;dataSource type="POOLED"&gt;
                &lt;property name="driver" value="com.mysql.cj.jdbc.Driver"/&gt;
                &lt;property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/&gt;
                &lt;property name="username" value="root"/&gt;
                &lt;property name="password" value="root"/&gt;
            &lt;/dataSource&gt;
        &lt;/environment&gt;
    &lt;/environments&gt;

    &lt;mappers&gt;
        &lt;mapper resource="mappers/UserMapper.xml"&gt;&lt;/mapper&gt;
    &lt;/mappers&gt;

&lt;/configuration&gt;
</code></pre>
<p>UserMapper.xml</p>
<pre><code class="prism language-java">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;

&lt;mapper namespace="com.dc.mapper.UserMapper"&gt;
    &lt;insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id"&gt;
        insert into user(username,password) value(#{
   username},#{
   password})
    &lt;/insert&gt;
    &lt;update id="update"&gt;
        update user set username=#{
   username},password=#{
   password} where id=#{
   id}
    &lt;/update&gt;
    &lt;delete id="delete"&gt;
        delete from user where id=#{
   id}
    &lt;/delete&gt;
    &lt;select id="selectById" resultType="com.dc.pojo.User"&gt;
        select * from user where id=#{
   id}
    &lt;/select&gt;
    &lt;select id="selectAll" resultType="com.dc.pojo.User"&gt;
        select * from user
    &lt;/select&gt;
&lt;/mapper&gt;
</code></pre>
<p>测试</p>
<pre><code class="prism language-java">public class Test {

    @org.junit.jupiter.api.Test
    public void test() throws IOException {

        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);

        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user=new User();
        user.setUsername("lili");
        user.setPassword("3333");

        mapper.insert(user);
//        mapper.update(user);
//        mapper.delete(1);
//        User user1=mapper.selectById(2);
//        List&lt;User&gt; user1=mapper.selectAll();
//        System.out.println(user1);
        sqlSession.close();
    }


}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135444775</p>]]></description><guid isPermaLink="false">/archives/c1cf5014-e301-476e-9ec8-71cbb1f57d68</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:03:44 GMT</pubDate></item><item><title><![CDATA[SpringMVC-01]]></title><link>https://wiki.dgfygk.com/archives/13fe45d7-e2d4-443b-bf15-e85dd647f027</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringMVC-01&amp;url=/archives/13fe45d7-e2d4-443b-bf15-e85dd647f027" width="1" height="1" alt="" style="opacity:0;">
<pre><code>Spring MVC是一种基于Java的MVC（Model-View-Controller）架构的Web应用程序开发框架。它是Spring框架的一部分，用于构建灵活和高效的Web应用程序。
Spring MVC的核心思想是将应用程序的逻辑分成模型（Model）、视图（View）和控制器（Controller）。模型表示应用程序的数据和业务逻辑，视图负责呈现数据给用户，控制器接收用户的请求并根据请求选择合适的模型和视图。
Spring MVC提供了一组丰富的功能和特性，使开发人员可以轻松地构建可扩展的Web应用程序。它具有以下特点：
</code></pre>
<ol>
 <li>支持灵活的URL映射：Spring MVC通过使用控制器处理器映射URL到适当的控制器方法。</li>
 <li>强大的数据绑定和验证：Spring MVC提供了数据绑定功能，可以将用户提交的数据绑定到模型中。它还支持验证机制，可以验证并处理用户输入的数据。</li>
 <li>灵活的视图解析：Spring MVC支持多种视图技术，包括JSP、FreeMarker、Thymeleaf等。开发人员可以根据需求选择合适的视图技术。</li>
 <li>强大的拦截器支持：Spring MVC提供了拦截器机制，可以在请求处理的不同阶段添加拦截器来执行特定的操作，例如身份验证、日志记录等。</li>
 <li>易于测试：Spring MVC的松耦合架构使得对控制器进行单元测试变得简单。开发人员可以使用JUnit等测试框架来编写测试用例。
  <br>
  Spring MVC是一个功能强大且易于使用的Web应用程序开发框架，使开发人员能够高效地构建可扩展和可维护的Web应用程序。它是Java开发人员在构建Web应用程序时的首选框架之一。</li>
</ol>
<p>调用流程：（图片来自尚硅谷）
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff229347eebbd9c3148a7cea67f6cc950.png&amp;size=m" alt="">
 <br>
 @RequestMapping</p>
<pre><code class="prism language-java">    @RequestMapping(value = "/user/login",method = RequestMethod.GET)
    //1.进准地址[一个|多个]     {"地址1","地址2"}
    //2.模糊地址                  user/*
    //3.可以用与方法或类
    //4.method = RequestMethod.GET          指定访问方式  不符合会出现405异常
    //5.@RequestMapping(method = RequestMethod.GET) 等同于 @GetMapping
</code></pre>
<p>接收参数
 <br>
 1：param参数 param1=value1\&amp;param2=value2\&amp;param3=value3</p>
<pre><code class="prism language-java">    //1.直接接收 属性名要求对应相等
    @RequestMapping("/data")
    @ResponseBody
    public String data(String name,int age){

        return "name="+name+"age="+age;
    }

    //2.注解接收
    @RequestMapping("/data1")
    @ResponseBody
    public String data1(@RequestParam(value="account")String name,
                        @RequestParam(required = false,defaultValue = "18")int page){

        return "name="+name+"page="+page;
    }

    //3.实体对象接收          属性名需要等于参数名
    @RequestMapping("/data2")
    @ResponseBody
    public String data2(User user){

        return user.toString();
    }
</code></pre>
<p>2：路径传参</p>
<pre><code class="prism language-java">    @RequestMapping("{account}/{password}")     //动态路径
    public String login(@PathVariable String account,@PathVariable String password){

        return "account:"+account+" password:"+password;
    }
</code></pre>
<p>3：json参数 { “name”: “John”, “age”: 25, “email”: “john@example.com”}</p>
<pre><code class="prism language-java">@EnableWebMvc       //配置json转化器 （使用postman）

    @PostMapping("/data")
    public String data( Person person){

        return person.toString();
    }
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F28556953d8e4dc7e6742582e4e13d9cc.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135466927</p>]]></description><guid isPermaLink="false">/archives/13fe45d7-e2d4-443b-bf15-e85dd647f027</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:02:48 GMT</pubDate></item><item><title><![CDATA[SpringMVC-02]]></title><link>https://wiki.dgfygk.com/archives/b0f6d6f8-aa8e-4d40-95d3-5d7e2d0b63dc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringMVC-02&amp;url=/archives/b0f6d6f8-aa8e-4d40-95d3-5d7e2d0b63dc" width="1" height="1" alt="" style="opacity:0;">
<p>添加@EnableWebMvc //配置json转化器 （使用postman）
 <br>
 可以不用写下面两个方法了
 <br>
 @Bean
 <br>
 public RequestMappingHandlerMapping handlerMapping(){</p>
<p>return new RequestMappingHandlerMapping();
 <br>
 }
 <br>
 @Bean
 <br>
 public RequestMappingHandlerAdapter handlerAdapter(){</p>
<p>return new RequestMappingHandlerAdapter();
 <br>
 }</p>
<p><strong>接收数据</strong></p>
<p>接收cookie</p>
<pre><code class="prism language-java">    @RequestMapping("/data")
    public String cookie(@CookieValue(value="cookieName") String value){

        return value;
    }

    @GetMapping("/save")
    public String save(HttpServletResponse response){

        Cookie cookie=new Cookie("cookieName","root");
        response.addCookie(cookie);
        return "OK";
    }
</code></pre>
<p>获取请求头</p>
<pre><code class="prism language-java">    @RequestMapping("data")
    public String data(@RequestHeader("Host") String host){

        return "host:"+host;
    }
</code></pre>
<p>原生对象获取</p>
<pre><code class="prism language-java">    public String data(HttpServlet httpServlet, HttpServletRequest httpServletRequest,
                       HttpSession httpSession){

        return "HttpServlet"+httpServlet+" HttpServletRequest"+httpServletRequest+" HttpSession"+
                httpSession;
    }
</code></pre>
<p>共享域对象</p>
<pre><code class="prism language-java">//1.原生api方式 
    @Autowired
    private ServletContext servletContext;
    public void data(HttpServletRequest httpServletRequest, HttpSession httpSession){


    }

 //2.
    //spring提供的方法:request提供了几种   model   moaelMap    map    modelAndView
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135494614</p>]]></description><guid isPermaLink="false">/archives/b0f6d6f8-aa8e-4d40-95d3-5d7e2d0b63dc</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:02:32 GMT</pubDate></item><item><title><![CDATA[MyBatis-01]]></title><link>https://wiki.dgfygk.com/archives/b711dbb5-729d-461e-b732-521758b4f0f2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-01&amp;url=/archives/b711dbb5-729d-461e-b732-521758b4f0f2" width="1" height="1" alt="" style="opacity:0;">
<p>MyBatis 是一款优秀的持久层框架，它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息，将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。</p>
<p>1.导入依赖
 <br>
 2.准备Mapper接口和MapperXML文件</p>
<pre><code class="prism language-java">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;

&lt;!--namespace = mapper接口的权限定符--&gt;
&lt;mapper namespace="com.dc.mapper.EmployeeMapper"&gt;
    &lt;!--
    声明标签写sql语句
    mapper接口中方法不能重载
    --&gt;
    &lt;select id="queryById" resultType="com.dc.pojo.Employee"&gt;
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{
   id}
    &lt;/select&gt;

    &lt;delete id="deleteById"&gt;
        delete from t_emp where emp_id=#{
   id}
    &lt;/delete&gt;
&lt;/mapper&gt;
</code></pre>
<p>mybatis-config.xml</p>
<pre><code class="prism language-java">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd"&gt;
&lt;configuration&gt;
    &lt;environments default="development"&gt;
        &lt;environment id="development"&gt;
            &lt;transactionManager type="JDBC"&gt;&lt;/transactionManager&gt;
            &lt;dataSource type="POOLED"&gt;
                &lt;property name="driver" value="com.mysql.cj.jdbc.Driver"/&gt;
                &lt;property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/&gt;
                &lt;property name="username" value="root"/&gt;
                &lt;property name="password" value="root"/&gt;
            &lt;/dataSource&gt;
        &lt;/environment&gt;
    &lt;/environments&gt;

    &lt;!--5.扫描mapper文件--&gt;
    &lt;mappers&gt;
        &lt;mapper resource="mappers/EmployeeMapper.xml"&gt;&lt;/mapper&gt;
    &lt;/mappers&gt;

&lt;/configuration&gt;
</code></pre>
<pre><code>```prism language-java
        //1.读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");

        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);

        //3.根据sqlSessionFactory创建sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //4.获取接口的代理对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee employee=mapper.queryById(1);
        System.out.println(employee);

        //5.提交事务和释放资源
        sqlSession.commit();
        sqlSession.close();
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135429167</p>]]></description><guid isPermaLink="false">/archives/b711dbb5-729d-461e-b732-521758b4f0f2</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:02:13 GMT</pubDate></item><item><title><![CDATA[Spring-声明式事务-01]]></title><link>https://wiki.dgfygk.com/archives/6b015c04-9318-4c88-9738-9335b9b1ab20</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1-01&amp;url=/archives/6b015c04-9318-4c88-9738-9335b9b1ab20" width="1" height="1" alt="" style="opacity:0;">
<p>声明式事务(declarative transaction management)是Spring提供的对程序事务管理的方式之一。
 <br>
 Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明，就是指在配置文件中声明。用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。这样的好处是，事务管理不侵入开发的组件，具体来说，业务逻辑对象就不会意识到正在事务管理之中，事实上也应该如此，因为事务管理是属于系统层面的服务，而不是业务逻辑的一部分，如果想要改变事务管理策划的话，也只需要在定义文件中重新配置即可;在不需要事务管理的时候，只要在设定文件上修改一下，即可移去事务管理服务，无需改变代码重新编译，这样维护起来极其方便。
 <br>
 Spring使用AOP来完成声明式的事务管理，因而声明式事务是以方法为单位，Spring的事务属性自然就在于描述事务应用至方法上的策略，在Spring中事务属性有以下四个参数：
 <br>
 1.传播行为
 <br>
 2.隔离级别
 <br>
 3.只读提示
 <br>
 4.事务超时期间</p>
<p>事务具有四大特征,分别是<strong>原子性</strong>、一致性、隔离性和持久性。</p>
<p>Spring事务管理相当于对AOP的再次封装</p>
<p>添加事务步骤：
 <br>
 1…选择对应事务管理器实现加入到ioc容器
 <br>
 2.使用注解指定哪些方法需要添加事务</p>
<pre><code class="prism language-java">    //配置
    @Configuration
@ComponentScan("com.dc")
@PropertySource("classpath:jdbc.properties")
@EnableAspectJAutoProxy         //开启aspectj注解的支持
@EnableTransactionManagement    //开启事务注解的支持
public class JavaConfig {

    //druid连接池
    @Bean
    public DataSource dataSource(@Value("${jdbc.url}")String url,@Value("${jdbc.driver}")String driver,
                                 @Value("${jdbc.username}")String username,@Value("${jdbc.password}")String password){

        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    //jdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){

        JdbcTemplate jdbcTemplate=new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public TransactionManager transactionManager(DataSource dataSource){

        DataSourceTransactionManager dataSourceTransactionManager=new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

}


    @Transactional      //添加事务
    public void changInfo(){

        studentDao.updateAgeById(111,2001);
        System.out.println("-------------------");
        studentDao.updateNameById("test1",2002);
    }
</code></pre>
<p>@Transactional可以加到类(l类中所有方法皆有事务) 或 方法（只有该方法有事务）上</p>
<p><strong>事务属性</strong> （方法上的事务属性会覆盖类上的事务属性）
 <br>
 1.只读</p>
<pre><code class="prism language-java">    @Transactional(readOnly = true)      
</code></pre>
<p>2.超时时间 （超时回滚，释放资源）
 <br>
 默认不会超时 -1</p>
<pre><code class="prism language-java">    @Transactional(timeout = 3)     //超时时间设置为3秒
</code></pre>
<p>3.事务异常
 <br>
 指定异常回滚和指定异常不回滚
 <br>
 默认：只有发生运行异常才回滚
 <br>
 可以通过rollbackFor，指定任何异常都回滚 （回滚：相当于恢复之前的修改）</p>
<pre><code class="prism language-java">    @Transactional(rollbackFor = Exception.class)       //只要发生异常都会回滚
</code></pre>
<p>4.事务隔离级别 （多个事务并发执行时，为保持数据一致性所遵循的规定）</p>
<pre><code class="prism language-java">    @Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED)
</code></pre>
<p>5.事务传播行为 （一个事务是否加到另外一个事务，在子事务当中设置） 在同一个类中不会生效
 <br>
 默认值：Propagation.REQUIRED 如果父方法中有事务，则加入，没有就自己独立
 <br>
 Propagation.REQUIRES_NEW 不论父方法中是否有事务，都是独立的</p>
<pre><code class="prism language-java">//父事务
    @Transactional
    public void topService(){

        studentServices.changeAge();
        studentServices.changeName();
    }

//子事务1
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void changeAge(){

        studentDao.updateAgeById(55,2001);
    }
//子事务2
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void changeName(){

        studentDao.updateNameById("测试",2001);
    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135425700</p>]]></description><guid isPermaLink="false">/archives/6b015c04-9318-4c88-9738-9335b9b1ab20</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:01:55 GMT</pubDate></item><item><title><![CDATA[day-01 在链表中插入最大公约数]]></title><link>https://wiki.dgfygk.com/archives/e5467fc6-afbb-49fa-bd76-03fd72dd13d3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=day-01%20%E5%9C%A8%E9%93%BE%E8%A1%A8%E4%B8%AD%E6%8F%92%E5%85%A5%E6%9C%80%E5%A4%A7%E5%85%AC%E7%BA%A6%E6%95%B0&amp;url=/archives/e5467fc6-afbb-49fa-bd76-03fd72dd13d3" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb408d54a063658a3743760498418baed.png&amp;size=m" alt="">
 <br>
 思路
 <br>
 每两个数之间进行一次求最大公约数，并将其插入两个节点之间</p>
<p>解题方法
 <br>
 max（）函数用于求最大公约数，将其赋值给新的一个节点，并将其插入两个节点之间
 <br>
 注意：插入后，需要执行的是p.next.next,因为插入了一个节点，所以需要向后移动两次</p>
<p>复杂度
 <br>
 时间复杂度: O(n)</p>
<p>空间复杂度:O(n)</p>
<p>Code</p>
<pre><code class="prism language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode insertGreatestCommonDivisors(ListNode head) {

        int a,b;
        ListNode p=head;
        while(p.next!=null){

        a=p.val;
        b=p.next.val;
        ListNode l=new ListNode();
        if(a&gt;=b)
        l.val=max(a,b);
        else
        l.val=max(b,a);

        l.next=p.next;
        p.next=l;

        p=p.next.next;
        }
        return head;
    }

    public int  max(int a,int b){

         if (b == 0) {

            return a; 
        } else {

            return max(b, a % b); 
        }
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdf0fce7afb8515f869ac8986e9979082.png&amp;size=m" alt="">
 <br>
 官方答案</p>
<pre><code class="prism language-java">class Solution {

    public ListNode insertGreatestCommonDivisors(ListNode head) {

        ListNode node = head;
        while (node.next != null) {

            node.next = new ListNode(gcd(node.val, node.next.val), node.next);
            node = node.next.next;
        }
        return head;
    }

    public int gcd(int a, int b) {

        while (b != 0) {

            int tmp = a % b;
            a = b;
            b = tmp;
        }
        return a;
    }
}

作者：力扣官方题解
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135431939</p>]]></description><guid isPermaLink="false">/archives/e5467fc6-afbb-49fa-bd76-03fd72dd13d3</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 09:01:41 GMT</pubDate></item><item><title><![CDATA[MyBatis-02]]></title><link>https://wiki.dgfygk.com/archives/6b1e5815-8cce-4b23-b2ae-e0807cca76f9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-02&amp;url=/archives/6b1e5815-8cce-4b23-b2ae-e0807cca76f9" width="1" height="1" alt="" style="opacity:0;">
<p>type=“JDBC” 会自动开启事务，需要自己提交事务
 <br>
 type=“MANAGED” 不会开启事务</p>
<pre><code class="prism language-java">&lt;transactionManager type="JDBC"&gt;&lt;/transactionManager&gt;
</code></pre>
<p>配置数据源
 <br>
 type=“POOLED” 让mybatis帮我们维护连接池
 <br>
 type=“UNPOOLED” 每次都新建或释放连接</p>
<pre><code class="prism language-java">            &lt;dataSource type="POOLED"&gt;
                &lt;property name="driver" value="com.mysql.cj.jdbc.Driver"/&gt;
                &lt;property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/&gt;
                &lt;property name="username" value="root"/&gt;
                &lt;property name="password" value="root"/&gt;
            &lt;/dataSource&gt;
</code></pre>
<p>配置映射文件</p>
<pre><code class="prism language-java">&lt;mappers&gt;
     &lt;mapper resource="mappers/EmployeeMapper.xml"&gt;&lt;/mapper&gt;
&lt;/mappers&gt;
</code></pre>
<p>settings</p>
<pre><code class="prism language-java">&lt;settings&gt;
        &lt;setting name="logImpl" value="STDOUT_LOGGING"/&gt;        &lt;!--开启日志输出，选择使用system进行控制台输出--&gt;
    &lt;/settings&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4fa2c015ac814b748e35e68e616b6753.png&amp;size=m" alt=""></p>
<pre><code class="prism language-java">select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{
   id}

&lt;!--
#{
   key}：占位符+赋值   emp_id=?    ?=赋值           动态值时使用
${
   key}：字符串拼接    "emp_id"+id                动态的列名 容器名 关键字使用

//sqlSession.commit();      查询不需要提交事务
--&gt;
</code></pre>
<p><strong>数据传入：</strong></p>
<pre><code class="prism language-java">    &lt;select id="queryBySalary" resultType="com.dc.pojo.Employee"&gt;
        select emp_salary empSalary from t_emp where emp_salary=#{
   salary}
    &lt;/select&gt;

    &lt;insert id="insertEmp"&gt;
        insert into t_emp (emp_name,emp_salary) values(#{
   empName} #{
   empSalary})
    &lt;/insert&gt;
</code></pre>
<p>多个参数传入</p>
<pre><code class="prism language-java">    List&lt;Employee&gt; queryByNameAndSalary(@Param("a")String name, @Param("b")double salary);

    &lt;select id="queryByNameAndSalary" resultType="com.dc.pojo.Employee"&gt;
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
        where emp_name=#{
   a} and emp_salary=#{
   b}
    &lt;/select&gt;
</code></pre>
<p>map传入</p>
<pre><code class="prism language-java">     &lt;!--
    #{
   key}      key等于map的key
    --&gt;
    &lt;insert id="insertEmpMap"&gt;
        insert into t_emp (emp_name,emp_salary) values(#{
   name} #{
   salary})
    &lt;/insert&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135437008</p>]]></description><guid isPermaLink="false">/archives/6b1e5815-8cce-4b23-b2ae-e0807cca76f9</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:01:20 GMT</pubDate></item><item><title><![CDATA[MyBatis-03]]></title><link>https://wiki.dgfygk.com/archives/87ff67ca-b610-4c1c-ae8a-53ddc6c89f16</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis-03&amp;url=/archives/87ff67ca-b610-4c1c-ae8a-53ddc6c89f16" width="1" height="1" alt="" style="opacity:0;">
<p><strong>数据输出：</strong>
 <br>
 mapper接口插入 删除 修改时 返回值int就行 只有查询才有resultType</p>
<pre><code class="prism language-java">&lt;mapper namespace="com.dc.mapper.EmployeeMapper"&gt;
    &lt;delete id="deleteById"&gt;
        delete from t_emp where emp_id=#{
   id}
    &lt;/delete&gt;

    &lt;select id="queryNameByIdById" resultType="java.lang.String"&gt;
        select emp_name from t_emp where emp_id=#{
   id}
    &lt;/select&gt;

    &lt;select id="querySalaryById" resultType="java.lang.Double"&gt;
        select emp_salary from t_emp where emp_id=#{
   id}
    &lt;/select&gt;

    &lt;select id="queryById" resultType="com.dc.pojo.Employee"&gt;
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{
   id}
    &lt;/select&gt;

    &lt;select id="selectEmpAndMaxSalary" resultType="java.util.Map"&gt;
        SELECT
        emp_name 员工姓名,
        emp_salary 员工工资,
        (SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
        FROM t_emp WHERE emp_salary=(
        SELECT MAX(emp_salary)From t_emp
        )
    &lt;/select&gt;

        &lt;select id="queryNameBySalary" resultType="java.lang.String"&gt;
        select emp_name from t_emp where emp_salary=#{
   salary}
    &lt;/select&gt;

    &lt;select id="queryAll" resultType="com.dc.pojo.Employee"&gt;
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp 
    &lt;/select&gt;
&lt;/mapper&gt;
</code></pre>
<p>主键回显</p>
<pre><code class="prism language-java">//自增长主键
    &lt;insert id="insertEmp" useGeneratedKeys="true" keyColumn="emp_id" keyProperty="empId"&gt;
        insert into t_emp(emp_name,emp_salary) value(#{
   empName},#{
   empSalary})
    &lt;/insert&gt;

employee.getEmpId()


//非自增长主键
    &lt;insert id="insertTeacher"&gt;
        &lt;selectKey order="BEFORE" resultType="java.lang.String" keyProperty="tId"&gt;
            select replace(uuid(),'-','')           //uUId()随机生成,然后去掉-
        &lt;/selectKey&gt;
        insert into teacher(t_id,t_name) value(#{
   tId},#{
   tName})
    &lt;/insert&gt;
</code></pre>
<p>事务自动提交</p>
<pre><code class="prism language-java">SqlSession sqlSession = sqlSessionFactory.openSession(true);
</code></pre>
<p>列名和属性名不一致
 <br>
 1.select t_id tId,t_name tName from teacher where t_id=#{tId}
 <br>
 2.开启驼峰式映射
 <br>
 3.resultMap<em>自定义映射</em> （resultType<em>自动映射</em>）</p>
<pre><code class="prism language-java">    &lt;resultMap id="tMap" type="com.dc.pojo.Teacher"&gt;
        &lt;id column="t_id" property="tId"&gt;&lt;/id&gt;
        &lt;result column="t_name" property="tName"&gt;&lt;/result&gt;
    &lt;/resultMap&gt;
    &lt;select id="queryById" resultMap="tMap"&gt;
        select * from teacher where t_id=#{
   id}
    &lt;/select&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135439855</p>]]></description><guid isPermaLink="false">/archives/87ff67ca-b610-4c1c-ae8a-53ddc6c89f16</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:01:02 GMT</pubDate></item><item><title><![CDATA[Spring-05-配置类]]></title><link>https://wiki.dgfygk.com/archives/fef0b49d-bd98-413d-a785-826ae6f857a6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-05-%E9%85%8D%E7%BD%AE%E7%B1%BB&amp;url=/archives/fef0b49d-bd98-413d-a785-826ae6f857a6" width="1" height="1" alt="" style="opacity:0;">
<p>配置类替换的是xml文件，完全使用注解，配置类作用主要为：
 <br>
 1.包扫描注解配置
 <br>
 2.引入外部配置文件
 <br>
 3.生声明第三方依赖的bean组件</p>
<pre><code class="prism language-java">@ComponentScan({
   "com.dc.ioc_01"})                          //包扫描注解配置
@PropertySource(value="classpath:jdbc.properties")         //引入外部配置文件
@Configuration
public class JavaConfiguration {


}
</code></pre>
<p>步骤：
 <br>
 1.添加@Configuration，代表是配置类
 <br>
 2.实现上面的三个功能注解</p>
<p>创建IoC容器：
 <br>
 1.第一种</p>
<pre><code class="prism language-java">    //创建IoC容器
            //第一种
        ApplicationContext applicationContext=new AnnotationConfigApplicationContext(JavaConfiguration.class);
</code></pre>
<p>2.第二种</p>
<pre><code class="prism language-java">        //第二种
        AnnotationConfigApplicationContext applicationContext1=new AnnotationConfigApplicationContext();
        applicationContext1.register(JavaConfiguration.class);
        applicationContext1.refresh();
</code></pre>
<p>引入第三方依赖的bean组件 @Bean注解</p>
<pre><code class="prism language-java">@Controller
public class StudentController {

    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Autowired
    private StudentService studentService;

    //bean的name默认为方法名
    //@Bean(name="" initMethod="" destroyMethod="") 指定bean的name和周期方法
    //@Scope                                        指定作用域
    @Bean
    public DruidDataSource dataSource(){

        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
       //可用参数注入其他组件
        JdbcTemplate jdbcTemplate=new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
}
</code></pre>
<p>@import注解
 <br>
 在一个配置类中引入另一个配置类</p>
<pre><code class="prism language-java">@Configuration
@Import(value={
   JavaConfigurationB.class})     //引入配置类JavaConfigurationB.class
public class JavaConfigurationA {

}
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135369293</p>]]></description><guid isPermaLink="false">/archives/fef0b49d-bd98-413d-a785-826ae6f857a6</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:00:33 GMT</pubDate></item><item><title><![CDATA[Spring-AOP-01]]></title><link>https://wiki.dgfygk.com/archives/6e6536c8-94e7-408e-918c-a3b7de3abe6b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-AOP-01&amp;url=/archives/6e6536c8-94e7-408e-918c-a3b7de3abe6b" width="1" height="1" alt="" style="opacity:0;">
<p><strong>代理模式</strong></p>
<ol>
 <li>静态代理
  <br>
  自己创建代理类 【不推荐】</li>
</ol>
<p>2.动态代理
 <br>
 2.1 JDK动态代理 要求目标类有接口
 <br>
 2.2 cglib动态代理 不要求有接口</p>
<p>AOP：面向切面编程 将代码中重复的、非核心的业务提取到一个公共模块
 <br>
 OOP：面向对象编程 继承、多态等</p>
<p><strong>AOP核心概念</strong>
 <br>
 1、横切关注点：对哪些方法进行拦截，拦截后怎么处理，这些关注点称之为横切关注点</p>
<p>2、切面（aspect）：类是对物体特征的抽象，切面就是对横切关注点的抽象</p>
<p>3、连接点（joinpoint）：被拦截到的点，因为Spring只支持方法类型的连接点，所以在Spring中连接点指的就是被拦截到的方法，实际上连接点还可以是字段或者构造器</p>
<p>4、切入点（pointcut）：对连接点进行拦截的定义</p>
<p>5、通知（advice）：所谓通知指的就是指拦截到连接点之后要执行的代码，通知分为前置、后置、异常、最终、环绕通知五类</p>
<p>6、目标对象：代理的目标对象</p>
<p>7、织入（weave）：将切面应用到目标对象并导致代理对象创建的过程</p>
<p>8、引入（introduction）：在不修改代码的前提下，引入可以在运行期为类动态地添加一些方法或字段</p>
<p>添加依赖</p>
<pre><code class="prism language-java">&lt;dependency&gt;
   &lt;groupId&gt;org.springframework&lt;/groupId&gt;
   &lt;artifactId&gt;spring-aspects&lt;/artifactId&gt;
   &lt;version&gt;6.0.6&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>增强类</p>
<pre><code class="prism language-java">@Component
@Aspect
public class LogAdvice {

    //几个位置对应几个方法
    @Before("execution(* com.dc.service.impl.*.*(..))")
    public void start(){

        System.out.println("方法开始了");
    }

    @After("execution(* com.dc.service.impl.*.*(..))")
    public void end(){

        System.out.println("方法结束了");
    }

    @AfterThrowing("execution(* com.dc.service.impl.*.*(..))")
    public void error(){

        System.out.println("方发出错了");
    }
}
</code></pre>
<p>在配置类中开启aspectj</p>
<pre><code class="prism language-java">@EnableAspectJAutoProxy     //相当于在.xml文件中配置&lt;aop:aspectj-autoproxy /&gt;
</code></pre>
<p>获取切点详细信息：
 <br>
 1.获取目标方法的信息 参数(JoinPoint joinPoint) org.aspectj.lang.JoinPoint包下</p>
<pre><code class="prism language-java">@Before("execution(* com..impl.*.*(..))")
    public void before(JoinPoint joinPoint){

        String simpleName=joinPoint.getTarget().getClass().getSimpleName();     //获取方法属于的类的信息
        String name = joinPoint.getSignature().getName();       //获取方法名
        Object[] args = joinPoint.getArgs();            //获取目标方法参数
    }
</code></pre>
<p>2.获取返回的结果 @AfterReturning()</p>
<pre><code class="prism language-java">@AfterReturning(value = "execution(* com..impl.*.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){


    }
</code></pre>
<p>3.获取异常的信息</p>
<pre><code class="prism language-java">@AfterThrowing(value = "execution(* com..impl.*.*(..))",throwing = "throwable")
    public void afterThrowing(JoinPoint joinPoint,Throwable throwable){


    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135407764</p>]]></description><guid isPermaLink="false">/archives/6e6536c8-94e7-408e-918c-a3b7de3abe6b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:00:19 GMT</pubDate></item><item><title><![CDATA[Spring-AOP-02]]></title><link>https://wiki.dgfygk.com/archives/2ad984e9-8778-434e-be13-906001a4ba64</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-AOP-02&amp;url=/archives/2ad984e9-8778-434e-be13-906001a4ba64" width="1" height="1" alt="" style="opacity:0;">
<p>Spring中的AspectJ切点表达式函数 切点表达式函数就像我们的GPS导航软件。通过切点表达式函数，再配合通配符和逻辑运算符的灵活运用，我们能很好定位到我们需要织入增强的连接点上。</p>
<p><strong>切点表达式</strong>
 <br>
 固定语法：execution(切点表达式)
 <br>
 execution的语法表达式如下：execution(&lt;修饰符&gt; &lt;返回类型&gt; &lt;类路径&gt; &lt;方法名&gt;(&lt;参数列表&gt;) &lt;异常模式&gt; ) 其中，修饰符和异常是可选的,如果不加类路径，则默认对所有的类生效。</p>
<p><strong>execution详解：</strong></p>
<ol>
 <li>通过方法签名、返回值定义切点：
  <br>
  execution(public * *Service(…))：定位于所有类下返回值任意、方法入参类型、数量任意，public类型的方法
  <br>
  execution(public String *Service(…))：定位于所有类下返回值为String、方法入参类型、数量任意，public类型的方法</li>
 <li>通过类包定义切点：
  <br>
  execution(* com.yc.controller.BaseController+.<em>(…)):匹配任意返回类型，对应包下BaseController类及其子类等任意方法。 
   <br>execution(</em> com.<em>.(…)):匹配任意返回类型，com包下所有类的所有方法 
   <br>execution(</em> com…*.(…)):匹配任意返回类型，com包、子包下所有类的所有方法
  <br>
  注意.表示该包下所有类，…则涵括其子包。</li>
 <li>通过方法入参定义切点
  <br>
  这里”*“表示任意类型的一个参数，“…“表示任意类型任意数量的参数
  <br>
  execution(* speak(Integer,<em>)):匹配任意返回类型，所有类中只有两个入参，第一个入参为Integer，第二个入参任意的方法 
   <br>execution(</em> speak(…,Integer,…)):匹配任意返回类型，所有类中至少有一个Integer入参，但位置任意的方法。</li>
</ol>
<p><strong>annotation详解</strong>
 <br>
 此注解用于定位标注了某个注解的目标切点。下面我们来看一个模拟用户登录成功后日志记录用户名、时间和调用方法的示例,</p>
<p><strong>自定义注解</strong>
 <br>
 @Retention(RetentionPolicy.CLASS)//生命注释保留时长，这里无需反射使用，使用CLASS级别
 <br>
 @Target(ElementType.METHOD)//生命可以使用此注解的元素级别类型（如类、方法变量等）
 <br>
 public @interface NeedRecord {</p>
<p>}</p>
<p><strong>切点表达式的提取：</strong>
 <br>
 1.从当前类中提取
 <br>
 2.创建一个存储切点的类，单独维护切点表达式 【推荐】</p>
<pre><code class="prism language-java">//MypointCut专门用于维护切点
public class MypointCut {

    @Pointcut("execution(* com.dc.service.impl.*.*(..))")
    public void pc(){
   }

    @Pointcut("execution(* com..impl.*.*(..))")
    public void myPC(){
   }
}


//引入MypointCut下的pc()
@Before("com.dc.pointcut.MypointCut.pc()")
    public void start(){

        System.out.println("方法开始了");
    }
</code></pre>
<p><strong>环绕通知</strong></p>
<pre><code class="prism language-java">//普通方式
@Before("com.dc.pointcut.MypointCut.pc()")
    public void begin() {

        System.out.println("开启事务");
    }

    @AfterReturning("")
    public void commit() {

        System.out.println("事务提交");
    }

    @AfterThrowing()
    public void rollback() {

        System.out.println("事务回滚");
    }


//环绕通知
    @Around("com.dc.pointcut.MypointCut.pc()")
    public Object transaction(ProceedingJoinPoint joinPoint){

        Object[] args = joinPoint.getArgs();
        Object res= null;
        try {

            System.out.println("开启事务");
            res = joinPoint.proceed(args);
            System.out.println("结束事务");
        } catch (Throwable e) {

            System.out.println("事务回滚");
            throw new RuntimeException(e);
        }
        return res;
    }
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135422807</p>]]></description><guid isPermaLink="false">/archives/2ad984e9-8778-434e-be13-906001a4ba64</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 09:00:04 GMT</pubDate></item><item><title><![CDATA[Spring-AOP-03]]></title><link>https://wiki.dgfygk.com/archives/84c224d9-0bb4-404c-9667-180761779fe4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-AOP-03&amp;url=/archives/84c224d9-0bb4-404c-9667-180761779fe4" width="1" height="1" alt="" style="opacity:0;">
<p>切面优先级设置：@Order(10) 值越小，优先级越高，优先级高在外圈，优先级低在里圈
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbf6af89a93fb84dc2109c5a87192034c.png&amp;size=m" alt="">
 <br>
 基于XML的AOP实现（了解）
 <br>
 1.配置切面：在Spring的配置文件中，配置切面使用的是aop:aspect元素，该元素会将一个已定义好的Spring Bean转换成切面Bean，因此，在使用aop:aspect元素之前，要在配置文件中先定义一个普通的Spring Bean。Spring Bean定义完成后，通过aop:aspect元素的ref属性即可引用该Bean。配置aop:aspect元素时，通常会指定id和ref这两个属性。</p>
<p>2.配置切入点：在Spring的配置文件中，切入点是通过aop:pointcut元素来定义的，表示该切入点是全局切入点，它可被多个切面共享；当aop:pointcut元素作为aop:aspect元素的子元素时，表示该切入点只对当前切面有效。在定义aop:aspect元素时，通常会指定id和expression这两个属性。</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135425325</p>]]></description><guid isPermaLink="false">/archives/84c224d9-0bb4-404c-9667-180761779fe4</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:59:49 GMT</pubDate></item><item><title><![CDATA[Spring-03-practice]]></title><link>https://wiki.dgfygk.com/archives/9ef53738-26af-4276-bfc5-9808b7296b14</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-03-practice&amp;url=/archives/9ef53738-26af-4276-bfc5-9808b7296b14" width="1" height="1" alt="" style="opacity:0;">
<ol>
 <li>创建1个Table
  <br>
  <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fee34d853e7e34c1dced8808fde8977e7.png&amp;size=m" alt="">
  <br>
  2.导入相关依赖</li>
</ol>
<pre><code class="prism language-java">    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-context&lt;/artifactId&gt;
            &lt;version&gt;6.1.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
            &lt;artifactId&gt;junit-jupiter&lt;/artifactId&gt;
            &lt;version&gt;5.10.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
            &lt;version&gt;8.0.25&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
            &lt;artifactId&gt;druid&lt;/artifactId&gt;
            &lt;version&gt;1.2.8&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt;
            &lt;version&gt;6.0.6&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
</code></pre>
<p>3.JdbcTemplate
 <br>
 JdbcTemplate 是 Spring 对 JDBC 的封装，目的是使JDBC更加易于使用，JdbcTemplate是Spring的一部分。JdbcTemplate 处理了资源的建立和释放，它帮助我们避免一些常见的错误，比如忘了总要关闭连接。他运行核心的JDBC工作流，如Statement的建立和执行，而我们只需要提供SQL语句和提取结果即可。
 <br>
 Spring为了简化数据库访问，主要做了以下几点工作：
 <br>
 提供了简化的访问JDBC的模板类，不必手动释放资源；
 <br>
 提供了一个统一的 DAO 类以实现 Data Access Object 模式；
 <br>
 把SQLException封装为DataAccessException，这个异常是一个RuntimeException，并且让我们能区分SQL异常的原因，例如，DuplicateKeyException表示违反了一个唯一约束；
 <br>
 能方便地集成Hibernate、JPA和MyBatis这些数据库访问框架。
 <br>
 java代码</p>
<pre><code class="prism language-java">    /*
    public void testForJava(){
        //创建一个连接池对象
        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql:///spring_practice");
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("root");


        //实例化对象即可
        JdbcTemplate jdbctemplate=new JdbcTemplate();
        jdbctemplate.setDataSource(druidDataSource);

        //jdbcTemplate.update()             非查询语句
        //jdbcTemplate.queryForObject()     查询单个对象
        //jdbcTemplate.query()              查询集合
    }

     */
</code></pre>
<p>IoC方式</p>
<pre><code class="prism language-java">    &lt;context:property-placeholder location="jdbc.properties"&gt;&lt;/context:property-placeholder&gt;
    &lt;bean id="DataSource" class="com.alibaba.druid.pool.DruidDataSource"&gt;
        &lt;property name="url" value="${jdbc.url}"&gt;&lt;/property&gt;
        &lt;property name="driverClassName" value="${jdbc.driver}"&gt;&lt;/property&gt;
        &lt;property name="username" value="${jdbc.username}"&gt;&lt;/property&gt;
        &lt;property name="password" value="${jdbc.password}"&gt;&lt;/property&gt;
    &lt;/bean&gt;

    &lt;bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"&gt;
        &lt;property name="dataSource" ref="DataSource"&gt;&lt;/property&gt;
    &lt;/bean&gt;
</code></pre>
<p>properties文件</p>
<pre><code class="prism language-java">jdbc.url=jdbc:mysql://localhost:3306/spring_practice
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=root
</code></pre>
<p>1、spring 中的 RowMapper
 <br>
 sping中的RowMapper可以将数据中的每一行数据封装成用户定义的类。
 <br>
 我们在数据库查询中，如果返回的类型是用户自定义的类型(其实我们在数据库查询中大部分返回的都是自定义的类)则需要包装，如果是Java自定义的类型，如：String则不需要。
 <br>
 如果sping与hibernate 相结合了,基本上是用不到,大多数都是在spring单独使用时用到，常见的情况就是与JdbcTemplate一起使用。
 <br>
 可以通过建立内部类实现RowMapper接口,RowMapper中有一个mapRow方法,所以实现RowMapper接口一定要实现mapRow方法,而对自定义类的包装就在mapRow方法中实现。</p>
<pre><code class="prism language-java">        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-01.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
        String sql="select * from students where id=?";

        Student student = jdbcTemplate.queryForObject(sql, new RowMapper&lt;Student&gt;() {

            @Override
            public Student mapRow(ResultSet rs, int rowNum) {

                //rs 结果记
                //rowNum 行数

                Student stu = new Student();
                try {

                    stu.setId(rs.getInt("id"));
                    stu.setName(rs.getString("name"));
                    stu.setGender(rs.getString("gender"));
                    stu.setAge(rs.getInt("age"));
                    stu.setClasses(rs.getString("class"));
                } catch (SQLException e) {

                    throw new RuntimeException(e);
                }
                return stu;
            }
        }, 2002);
</code></pre>
<p>执行结果：
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3ba963de48c80ae8b587b38b9ec9268a.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135296332</p>]]></description><guid isPermaLink="false">/archives/9ef53738-26af-4276-bfc5-9808b7296b14</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:59:33 GMT</pubDate></item><item><title><![CDATA[Spring-配置类-practice]]></title><link>https://wiki.dgfygk.com/archives/085bafd4-a49b-447f-96b1-7c92b5e8d1ca</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-%E9%85%8D%E7%BD%AE%E7%B1%BB-practice&amp;url=/archives/085bafd4-a49b-447f-96b1-7c92b5e8d1ca" width="1" height="1" alt="" style="opacity:0;">
<p>配置类替代xml文件，<strong>完全注解开发</strong> ，不再需要xml文件
 <br>
 Config配置类</p>
<pre><code class="prism language-java">@ComponentScan(basePackages = "com.dc")
@PropertySource(value="classpath:jdbc.properties")
@Configuration
public class Config {


    @Bean
    public DruidDataSource dataSource(@Value("${jdbc.url}")String url,@Value("${jdbc.driver}")String driver,
                                      @Value("${jdbc.username}")String username,@Value("${jdbc.password}")String password){

        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
       //可用参数注入其他组件
        JdbcTemplate jdbcTemplate=new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

}
</code></pre>
<p>结果：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd571d64d6bae01bb40a756f2c8d67f08.png&amp;size=m" alt="">
 <br>
 三种方式
 <br>
 1、纯xml new ClassPathXmlApplicationContext(“spring.xml”);
 <br>
 2、xml+注解 new ClassPathXmlApplicationContext(“spring.xml”);
 <br>
 3、纯注解 【推荐】 new AnnotationConfigApplicationContext(Config.class);</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135403909</p>]]></description><guid isPermaLink="false">/archives/085bafd4-a49b-447f-96b1-7c92b5e8d1ca</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:59:08 GMT</pubDate></item><item><title><![CDATA[Spring-04-基于注解]]></title><link>https://wiki.dgfygk.com/archives/0a0e2588-ccf4-41d9-9712-0ce25f5c108a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-04-%E5%9F%BA%E4%BA%8E%E6%B3%A8%E8%A7%A3&amp;url=/archives/0a0e2588-ccf4-41d9-9712-0ce25f5c108a" width="1" height="1" alt="" style="opacity:0;">
<p>步骤：
 <br>
 1、在类上添加IoC注解
 <br>
 2、告诉Spring IoC容器，在哪些包下添加了IoC注解</p>
<p>@Component //通常描述Spring中的Bean
 <br>
 @Repository //通常访问层（Dao层）
 <br>
 @Controller //通常控制层
 <br>
 @Service //通常业务层</p>
<pre><code class="prism language-java">//@Scope(scopeName= ConfigurableBeanFactory.SCOPE_SINGLETON);    //单例 默认值
//@Scope(scopeName =ConfigurableBeanFactory.SCOPE_PROTOTYPE );    //多例
@Component  //相当于   &lt;bean id="commomComponent" class="CommonComponent"&gt;&lt;/bean&gt;

&lt;!--
  base-package指定IoC容器的去哪些类下找注解类
--&gt;
 &lt;context:component-scan base-package="org.example"&gt;&lt;/context:component-scan&gt;       //去org.example下找注解类

    //如果@Autowired下有多个组件会报错，可以使用@Qualifier(value="")获取bean的id
    @Autowired      //相当于&lt;propertyname="" value=""&gt;&lt;/property&gt;      不需要提供set方法
    @Qualifier(value="userServiceImpl2")
</code></pre>
<p>@Qualifier需要和@Autowired一起使用，如上面多个类继承同一个接口时</p>
<p>@Value注解,可以用于.properties文件下的内容</p>
<pre><code class="prism language-java">    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    //.properties文件
    jdbc.username=root
    jdbc.password=root

    //.xml配置引入
    &lt;context:component-scan base-package="org.example.ioc_04"&gt;&lt;/context:component-scan&gt;
    &lt;context:property-placeholder location="jdbc.properties"&gt;&lt;/context:property-placeholder&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135343026</p>]]></description><guid isPermaLink="false">/archives/0a0e2588-ccf4-41d9-9712-0ce25f5c108a</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:58:54 GMT</pubDate></item><item><title><![CDATA[Spring-基于注解-practice]]></title><link>https://wiki.dgfygk.com/archives/515e7a7d-cf21-4196-84f4-39c629411742</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-%E5%9F%BA%E4%BA%8E%E6%B3%A8%E8%A7%A3-practice&amp;url=/archives/515e7a7d-cf21-4196-84f4-39c629411742" width="1" height="1" alt="" style="opacity:0;">
<p>结构
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb747d59bc916ccb9370f171a20a95fc8.png&amp;size=m" alt="">
 <br>
 <strong>StudentController</strong></p>
<pre><code class="prism language-java">package com.dc.controller;

import com.dc.pojo.Student;
import com.dc.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Controller
public class StudentController {

    @Autowired
    private StudentService studentService;
    public void findAll(){

        List&lt;Student&gt; all=studentService.findAll();
        System.out.println("all:"+all);
    }
}
</code></pre>
<p><strong>StudentDaoImpl</strong>
 <br>
 package com.dc.dao;</p>
<p>import com.dc.pojo.Student;
 <br>
 import org.springframework.beans.factory.BeanFactory;
 <br>
 import org.springframework.beans.factory.annotation.Autowired;
 <br>
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 <br>
 import org.springframework.jdbc.core.JdbcTemplate;
 <br>
 import org.springframework.stereotype.Repository;</p>
<p>import java.util.List;</p>
<p>@Repository
 <br>
 public class StudentDaoImpl implements StudentDao{</p>
<p>@Autowired
 <br>
 private JdbcTemplate jdbcTemplate;</p>
<pre><code>@Override
public List&lt;Student&gt; queryAll(){
    String sql="select id,name,gender,age,class as classes from students";
    List&lt;Student&gt; studentList=jdbcTemplate.query(sql,new BeanPropertyRowMapper&lt;&gt;(Student.class));
    return studentList;
}
</code></pre>
<p>}</p>
<pre><code class="prism language-java">在这里插入代码片
</code></pre>
<p><strong>Student</strong></p>
<pre><code class="prism language-java">package com.dc.pojo;

public class Student {

    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private String classes;

    public Integer getId() {

        return id;
    }

    public String getName() {

        return name;
    }

    public String getGender() {

        return gender;
    }

    public Integer getAge() {

        return age;
    }

    public String getClasses() {

        return classes;
    }

    public void setId(Integer id) {

        this.id = id;
    }

    public void setName(String name) {

        this.name = name;
    }

    public void setGender(String gender) {

        this.gender = gender;
    }

    public void setAge(Integer age) {

        this.age = age;
    }

    public void setClasses(String classes) {

        this.classes = classes;
    }

    @Override
    public String toString() {

        return "Student:"+id+"  "+name+"  "+gender+"  "+age+"  "+classes;
    }
}
</code></pre>
<p><strong>StudentService</strong></p>
<pre><code class="prism language-java">package com.dc.service;

import com.dc.dao.StudentDao;
import com.dc.pojo.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class StudentServiceImpl implements StudentService{

    @Autowired
    private StudentDao studentDao;
    @Override
    public List&lt;Student&gt; findAll(){

        List&lt;Student&gt; studentList=studentDao.queryAll();
        return studentList;
    }
}
</code></pre>
<p>结果
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe298b8081fd9aecb6fa88c62f192b273.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135362942</p>]]></description><guid isPermaLink="false">/archives/515e7a7d-cf21-4196-84f4-39c629411742</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:58:41 GMT</pubDate></item><item><title><![CDATA[Maven01]]></title><link>https://wiki.dgfygk.com/archives/983e884e-f575-4416-af3e-848480198413</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Maven01&amp;url=/archives/983e884e-f575-4416-af3e-848480198413" width="1" height="1" alt="" style="opacity:0;">
<p>2023年12月25日，考完研，从零开始学。</p>
<p><strong>pom.xml内容：</strong></p>
<pre><code class="prism language-java">&lt;packaging&gt;war&lt;/packaging&gt; &lt;!--打包方式--&gt;

&lt;properties&gt;  &lt;!--统一依赖版本--&gt;
  &lt;jackson.version&gt;2.15.2&lt;/jackson.version&gt;
&lt;/properties&gt;

&lt;dependency&gt;
      &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
      &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
      &lt;version&gt;${
   jackson.version}&lt;/version&gt;
      &lt;scope&gt;test&lt;/scope&gt;&lt;!--依赖作用域--&gt;
 &lt;/dependency&gt;
</code></pre>
<p>依赖传递：导入依赖，会自动导入依赖的依赖
 <br>
 依赖冲突：有重复的依赖，发生冲突，冲突发生后，后面的依赖都不会引入</p>
<p>maven继承特性：可以直接在父工程设置版本</p>
<p>maven聚合：将多个项目组织到一个父项目中，方便管理和维护</p>
<pre><code class="prism language-java">&lt;modules&gt;
        &lt;module&gt;maven-child&lt;/module&gt;
&lt;/modules&gt;
</code></pre>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135200205</p>]]></description><guid isPermaLink="false">/archives/983e884e-f575-4416-af3e-848480198413</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:55:41 GMT</pubDate></item><item><title><![CDATA[Spring-01]]></title><link>https://wiki.dgfygk.com/archives/984bfbc3-9b60-4814-96c7-7e3f7fa094d1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-01&amp;url=/archives/984bfbc3-9b60-4814-96c7-7e3f7fa094d1" width="1" height="1" alt="" style="opacity:0;">
<p><strong>框架=jar包+配置文件</strong></p>
<p><strong>Spring：</strong>:技术栈"全家桶”,包括SpringFramework，SpringBoot，SpringMVC等…</p>
<p><strong>SpringFramework</strong>核心功能：(1)核心容器（2）面向切面编程（3）事务管理（4）SpringMVC</p>
<p><strong>ClassPathXmlApplicationContext</strong> :配置文件时XML格式；项目的类路径下resourses
 <br>
 <strong>WebApplicationContext</strong>:web项目对应的ioc容器</p>
<p>IoC 控制反转:创建对象的权利交给Spring
 <br>
 DI 依赖注入</p>
<p>IoC/DI实现步骤：
 <br>
 1、配置元数据
 <br>
 2、创建 Ioc容器
 <br>
 3、获取bean</p>
<p><strong>基于XML配置方式的组件管理</strong></p>
<pre><code class="prism language-java">    &lt;!--
    &lt;bean&gt;对应一个组件对象，id是组件唯一标识，class是组件的类限定符
    --&gt;
    &lt;bean id="HappyComponent" class="org.example.HappyComponent"&gt;&lt;/bean&gt;

     &lt;!--
    静态工厂ioc配置
    factory-method:静态工厂方法
    实例方法必须static
    --&gt;
    &lt;bean id="client" class="org.example.ClientService" factory-method="creatInstance"&gt;&lt;/bean&gt;


    &lt;!--
    实例工场方法实例化:1、配置工厂类的组件细信息
                    2、指定非静态工厂对象和方法名
    --&gt;
    &lt;bean id="defaultclent" class="org.example.DefaultServicesLocator" &gt;&lt;/bean&gt;
    &lt;bean id="client" factory-bean="defaultclent" factory-method="creatClientServicesInstance"&gt;&lt;/bean&gt;
</code></pre>
<p><strong>di配置</strong></p>
<pre><code class="prism language-java">    &lt;!--
    单个参数注入
    value：直接属性值
    ref:、引用其他的bean
    --&gt;
    &lt;bean id="userDao" class="ioc_02.UserDao"&gt;&lt;/bean&gt;

    &lt;bean  id="userServices" class="ioc_02.UserServices"&gt;
        &lt;constructor-arg ref="userDao"&gt;&lt;/constructor-arg&gt;
    &lt;/bean&gt;

    &lt;!--
   多个参数注入
   value：直接属性值
   ref:引用其他的bean
   --&gt;
    &lt;bean id="userService1" class="ioc_02.UserServices"&gt;
        &lt;constructor-arg name="age" value="18"&gt;&lt;/constructor-arg&gt;
        &lt;constructor-arg name="name" value="二狗子"&gt;&lt;/constructor-arg&gt;
        &lt;constructor-arg name="userDao" value="userDao"&gt;&lt;/constructor-arg&gt;
    &lt;/bean&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F91b818c99bb303545f76aab4d9e8cb47.png&amp;size=m" alt="">
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbac1aa47f1a34e6de7f4d5ec34f6b6fe.png&amp;size=m" alt=""></p>
<pre><code class="prism language-java">    &lt;!--setter方法注入--&gt;
    &lt;bean id="movieFinder" class="ioc_02.MovieFinder"&gt;&lt;/bean&gt;
    &lt;bean id="simpleMovieLister" class="ioc_02.SimpleMovieLister"&gt;
        &lt;property name="movieName" value="小明"&gt;&lt;/property&gt;
        &lt;property name="movieFinder " ref="movieFinder"&gt;&lt;/property&gt;
    &lt;/bean&gt;
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbdbf9f1fa44014594a39aabcd27a04ba.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135217794</p>]]></description><guid isPermaLink="false">/archives/984bfbc3-9b60-4814-96c7-7e3f7fa094d1</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:55:19 GMT</pubDate></item><item><title><![CDATA[Spring-02-基于xml的配置]]></title><link>https://wiki.dgfygk.com/archives/f47ff145-328c-4534-b049-d642871b325b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring-02-%E5%9F%BA%E4%BA%8Exml%E7%9A%84%E9%85%8D%E7%BD%AE&amp;url=/archives/f47ff145-328c-4534-b049-d642871b325b" width="1" height="1" alt="" style="opacity:0;">
<p><strong>CLassPathXmlApplicationContext //读取类路径下的xml配置方式 
  <br>FileSystemXmlApplicationContext //读取指定文件下的xml配置方式 
  <br>AnnotationConfigApplicationContext //读取配置类的ioc容器 
  <br>WebApplicationContext //web项目专属的配置的ioc容器</strong></p>
<p><strong>IoC容器创建</strong>：</p>
<pre><code class="prism language-java">    public void creatIoc(){

        //方式1：直接创建容器并指定配置文件 [推荐]
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-03.xml");
    }
</code></pre>
<p><strong>//读取IoC容器的组件</strong>
 <br>
 1、同一类型，在IoC容器中只能有一个bean，否则会出现不唯一异常</p>
<pre><code class="prism language-java">        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-03.xml");
        //读取IoC容器的组件
        //方案1，需要强转
        HappyComponent happyComponent=(HappyComponent)applicationContext.getBean("happyComponent");

        //方案2
        applicationContext.getBean("happyComponent", HappyComponent.class);

        //方案3
        HappyComponent happyComponent1=applicationContext.getBean(HappyComponent.class);
        happyComponent.doWork();
        happyComponent1.doWork();
</code></pre>
<p><strong>初始化方法和销毁方法</strong></p>
<pre><code class="prism language-java">    &lt;!--
    //通过init-method和destroy-method指定初始化方法，销毁方法
    --&gt;
    &lt;bean id="beanOne" class="ioc_04.BeanOne" init-method="init" destroy-method="destory"&gt;&lt;/bean&gt;
</code></pre>
<p><strong>组件作用域配置</strong>
 <br>
 scope 作用域：单例 【默认值】 【推荐】singleton
 <br>
 多例 prototype</p>
<pre><code class="prism language-java">    &lt;!--多例--&gt;
    &lt;bean id="beanTwo" class="ioc_04.BeanTwo" scope="prototype"&gt;&lt;/bean&gt;
</code></pre>
<p><strong>FactoryBean特性和使用</strong>
 <br>
 1、用于配置复杂的Bean对象
 <br>
 2、提供的三种方法 getObject()
 <br>
 getObjectType()
 <br>
 isSingleton()</p>
<pre><code class="prism language-java">public class JavaBeanFactoryBean implements FactoryBean&lt;JavaBean&gt; {

    /*
    *1、实现FactoryBean接口，实现&lt;返回值泛型&gt;
    *2、
     */
    private String name;
    public void setName(String name) {

        this.name = name;
    }
    @Override
    public JavaBean getObject() throws Exception{

        JavaBean javaBean=new JavaBean();
        javaBean.setName(name);
        return javaBean;
    }
    @Override
    public Class&lt;?&gt; getObjectType() {

        return JavaBean.class;
    }
}
</code></pre>
<p><strong>区别：</strong>
 <br>
 BeanFactory IoC容器中最大的接口 是IoC容器
 <br>
 FactoryBean 标准化组件工场的接口 是组件</p>
<p>原文链接: https://blog.csdn.net/qq_53568730/article/details/135262322</p>]]></description><guid isPermaLink="false">/archives/f47ff145-328c-4534-b049-d642871b325b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 08:53:54 GMT</pubDate></item><item><title><![CDATA[keep-alive原理]]></title><link>https://wiki.dgfygk.com/archives/5c192416-b851-4bbb-bdd4-3dad6dbc2690</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=keep-alive%E5%8E%9F%E7%90%86&amp;url=/archives/5c192416-b851-4bbb-bdd4-3dad6dbc2690" width="1" height="1" alt="" style="opacity:0;">
<p><code>keep-alive</code>在Web开发中有不同的应用上下文，但基于您提出的问题，我主要聚焦于<code>keep-alive</code>在Vue框架中的原理，因为这是最常见的应用场景。</p>
<h4>Vue中的<code>keep-alive</code>原理</h4>
<p>在Vue中，<code>keep-alive</code>是一个内置组件，主要用于缓存组件的实例，避免组件的重复创建和销毁，从而提升应用的性能和响应速度。其原理主要可以归纳如下：</p>
<ol>
 <li><strong>缓存机制</strong> ：</li>
 <li>当<code>keep-alive</code>包裹动态组件或<code>&lt;router-view&gt;</code>时，它会缓存不活动的组件实例，而不是销毁它们。这意味着，当用户再次访问这些组件时，可以直接从缓存中获取，而无需重新创建和渲染。</li>
 <li><code>keep-alive</code>通过内部机制（如LRU算法）来管理缓存的组件实例，确保在缓存大小超过限制时，能够释放最久未被访问的组件实例。</li>
 <li><strong>生命周期钩子</strong> ：</li>
 <li>当被<code>keep-alive</code>包裹的组件被激活（进入可视区域）时，会触发<code>activated</code>生命周期钩子。这通常用于执行一些数据初始化或异步操作。</li>
 <li>相反，当组件被停用（离开可视区域）时，会触发<code>deactivated</code>生命周期钩子。这可以用于执行一些清理工作，如停止定时器或取消网络请求。</li>
 <li><strong>条件控制</strong> ：</li>
 <li><code>keep-alive</code>提供了<code>include</code>和<code>exclude</code>属性，允许开发者指定哪些组件应该被缓存，哪些不应该。这通过组件的<code>name</code>属性进行匹配。</li>
 <li><code>max</code>属性用于限制缓存的组件实例数量，确保应用的内存使用不会无限制增长。</li>
 <li><strong>抽象组件</strong> ：</li>
 <li><code>keep-alive</code>是一个抽象组件，它自身不会渲染任何DOM元素，也不会出现在父组件的模板中。它只负责缓存和管理组件实例。</li>
 <li><strong>服务端渲染</strong> ：</li>
 <li>需要注意的是，在服务端渲染（SSR）环境下，<code>activated</code>和<code>deactivated</code>生命周期钩子不会被调用。</li>
</ol>
<h4>总结</h4>
<p>Vue中的<code>keep-alive</code>通过缓存组件实例，避免了组件的重复创建和销毁，从而提高了应用的性能和响应速度。它利用内部机制管理缓存，并通过生命周期钩子和条件控制提供了灵活的使用方式。然而，开发者也需要注意缓存的副作用，如内存使用和组件状态管理，以避免潜在的问题。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142368748</p>]]></description><guid isPermaLink="false">/archives/5c192416-b851-4bbb-bdd4-3dad6dbc2690</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:44:51 GMT</pubDate></item><item><title><![CDATA[es6中set和map的区别]]></title><link>https://wiki.dgfygk.com/archives/b85136c2-8e62-4011-bbcc-ef6719ace97d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=es6%E4%B8%ADset%E5%92%8Cmap%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/b85136c2-8e62-4011-bbcc-ef6719ace97d" width="1" height="1" alt="" style="opacity:0;">
<p>在ES6（ECMAScript 2015）中，<code>Set</code> 和 <code>Map</code> 是两种新的集合类型，它们提供了更高级的数据结构来存储唯一值或键值对集合。尽管它们在功能上有些相似，但它们在用途和内部机制上存在一些关键区别。</p>
<h4>1. 基本概念</h4>
<ul>
 <li>
  <p><strong>Set</strong>：一个集合（Set）是一种包含唯一值的数据结构。它的每个元素都是唯一的，没有重复的值。Set 类似于数组，但成员的值都是唯一的，没有重复的值。</p>
 </li>
 <li>
  <p><strong>Map</strong>：一个Map对象保存键值对，并且能够记住键的原始插入顺序。任何值（对象或者原始值）都可以作为一个键或一个值。</p>
 </li>
</ul>
<h4>2. 主要区别</h4>
<h5>2.1 存储的数据类型</h5>
<ul>
 <li><strong>Set</strong>：只存储唯一的值（无论是原始值还是对象引用）。</li>
 <li><strong>Map</strong>：存储键值对，键和值都可以是任何类型。</li>
</ul>
<h5>2.2 插入和访问</h5>
<ul>
 <li><strong>Set</strong> ：通过 <code>.add()</code> 方法添加新元素，<code>.has()</code> 方法检查元素是否存在，<code>.delete()</code> 方法删除元素，<code>.clear()</code> 方法清空集合。没有直接按索引访问元素的方式，但可以遍历集合。</li>
 <li><strong>Map</strong> ：通过 <code>.set(key, value)</code> 方法添加新的键值对，<code>.get(key)</code> 方法根据键获取值，<code>.has(key)</code> 方法检查键是否存在，<code>.delete(key)</code> 方法根据键删除键值对，<code>.clear()</code> 方法清空Map。</li>
</ul>
<h5>2.3 遍历</h5>
<ul>
 <li><strong>Set</strong> 和 <strong>Map</strong> 都提供了遍历方法，如 <code>.forEach()</code>、<code>.keys()</code>、<code>.values()</code> 和 <code>.entries()</code>。但是，由于Map存储的是键值对，所以<code>.entries()</code> 方法对于Map特别有用，因为它返回一个包含每个键值对的数组。</li>
</ul>
<h5>2.4 顺序</h5>
<ul>
 <li><strong>Set</strong> 和 <strong>Map</strong> 都保持了元素的插入顺序。这意味着，当你遍历它们时，元素将按照被添加到集合中的顺序返回。</li>
</ul>
<h5>2.5 默认值</h5>
<ul>
 <li><strong>Set</strong> 的默认值是 <code>undefined</code>（虽然你通常不会向Set中添加 <code>undefined</code>，因为它被视为唯一值）。</li>
 <li><strong>Map</strong> 的键和值都可以是 <code>undefined</code>，只要它们是唯一的或作为特定的键值对存在。</li>
</ul>
<h4>3. 应用场景</h4>
<ul>
 <li><strong>Set</strong>：适用于需要去重或者需要快速判断某个元素是否存在于集合中的场景。</li>
 <li><strong>Map</strong>：适用于需要存储键值对，并且需要保持键的插入顺序的场景，如实现字典、缓存等。</li>
</ul>
<h4>4. 示例</h4>
<pre><code class="hljs">// Set 示例  
let mySet = new Set();  
mySet.add(1);  
mySet.add(2);  
mySet.add(2); // 重复的值不会被添加  
console.log(mySet.has(2)); // true  

// Map 示例  
let myMap = new Map();  
myMap.set('a', 1);  
myMap.set('b', 2);  
console.log(myMap.get('a')); // 1  
console.log(myMap.has('b')); // true
</code></pre>
<p>总结，<code>Set</code> 和 <code>Map</code> 在ES6中提供了强大的集合操作能力，它们的主要区别在于存储的数据类型（唯一值 vs 键值对）和相应的操作方法。选择使用哪一个取决于你的具体需求。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142219513</p>]]></description><guid isPermaLink="false">/archives/b85136c2-8e62-4011-bbcc-ef6719ace97d</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:44:37 GMT</pubDate></item><item><title><![CDATA[nginx的作用是什么]]></title><link>https://wiki.dgfygk.com/archives/76a8fc89-2c82-46f3-bcdf-9e53cefd149c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=nginx%E7%9A%84%E4%BD%9C%E7%94%A8%E6%98%AF%E4%BB%80%E4%B9%88&amp;url=/archives/76a8fc89-2c82-46f3-bcdf-9e53cefd149c" width="1" height="1" alt="" style="opacity:0;">
<p>Nginx是一个轻量级、高性能的Web服务器/反向代理服务器及电子邮件（IMAP/POP3）代理服务器，它的作用广泛且重要。以下是Nginx的主要作用：</p>
<h4>1. 作为Web服务器</h4>
<ul>
 <li><strong>高效处理静态文件</strong>：Nginx对静态文件（如HTML、图片、CSS、JavaScript等）的处理非常高效，能够显著提升网站的性能和响应速度。</li>
 <li><strong>资源占用少</strong>：相比其他Web服务器，Nginx使用更少的内存和CPU资源，能够支持更多的并发连接。</li>
 <li><strong>高并发能力</strong>：Nginx可以处理高达数万的并发连接，这对于高流量的网站来说至关重要。</li>
</ul>
<h4>2. 作为反向代理服务器</h4>
<ul>
 <li><strong>负载均衡</strong>：Nginx可以将客户端的请求根据配置转发到后端的多台服务器上，实现负载均衡，从而提高网站的可用性和扩展性。</li>
 <li><strong>安全性增强</strong>：通过反向代理，Nginx可以隐藏后端服务器的真实IP地址，增加网站的安全性。</li>
 <li><strong>缓存功能</strong>：Nginx还可以配置缓存机制，将常用的静态文件缓存到本地，减少对后端服务器的请求，进一步提高网站性能。</li>
</ul>
<h4>3. 作为电子邮件代理服务器</h4>
<ul>
 <li><strong>IMAP/POP3代理</strong>：Nginx不仅限于Web服务，还可以作为电子邮件的IMAP/POP3代理服务器，提供邮件的接收和发送功能。</li>
</ul>
<h4>4. 优点概述</h4>
<ul>
 <li><strong>跨平台</strong>：Nginx支持多种操作系统，具有良好的跨平台性。</li>
 <li><strong>配置简单</strong>：Nginx的配置文件简洁明了，易于上手和管理。</li>
 <li><strong>稳定性高</strong>：Nginx采用了多种优化技术，保证了在高并发下的稳定性和可靠性。</li>
 <li><strong>支持热部署</strong>：Nginx支持在不中断服务的情况下进行软件升级和配置更改，提高了运维的灵活性。</li>
</ul>
<h4>5. 应用实例</h4>
<p>在中国，许多大型网站都采用了Nginx作为Web服务器或反向代理服务器，如新浪、网易、腾讯等。这些网站通过Nginx的高效性能和稳定性，为用户提供了优质的访问体验。</p>
<p>综上所述，Nginx在Web服务、反向代理、负载均衡以及电子邮件代理等方面都发挥着重要作用，是现代互联网架构中不可或缺的一部分。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142401829</p>]]></description><guid isPermaLink="false">/archives/76a8fc89-2c82-46f3-bcdf-9e53cefd149c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:44:20 GMT</pubDate></item><item><title><![CDATA[react中diff的选择性子树渲染]]></title><link>https://wiki.dgfygk.com/archives/de466091-fc56-4348-8385-273bde533400</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=react%E4%B8%ADdiff%E7%9A%84%E9%80%89%E6%8B%A9%E6%80%A7%E5%AD%90%E6%A0%91%E6%B8%B2%E6%9F%93&amp;url=/archives/de466091-fc56-4348-8385-273bde533400" width="1" height="1" alt="" style="opacity:0;">
<p>在React中，组件的渲染是高效的，这得益于React的虚拟DOM（Virtual DOM）和diff算法。React的diff算法主要用于比较旧虚拟DOM树和新虚拟DOM树之间的差异，并仅更新实际DOM中需要变化的部分，从而提高性能。</p>
<p>关于"选择性子树渲染”，虽然React本身并没有直接提供一个名为"选择性子树渲染"的API，但你可以通过几种方式来实现或优化这一行为：</p>
<h4>1. 使用<code>React.memo</code> 或 <code>PureComponent</code></h4>
<p>对于函数组件，你可以使用<code>React.memo</code>来包装你的组件。这样，React将仅在当前组件的props与前一次渲染的props不相同时，才会重新渲染该组件。这有助于防止不必要的子树渲染。</p>
<pre><code>```hljs
const MyComponent = React.memo(function MyComponent(props) {  
  /* render using props */  
});
</code></pre>
<p>对于类组件，可以继承自<code>React.PureComponent</code>。<code>PureComponent</code>会对props和state进行浅比较，如果都没有变化，则不会触发渲染。</p>
<h4>2. 使用<code>shouldComponentUpdate</code>（类组件）</h4>
<p>在类组件中，你可以实现<code>shouldComponentUpdate</code>生命周期方法来自定义何时应该更新组件。如果返回<code>false</code>，则组件不会更新，这同样可以阻止不必要的子树渲染。</p>
<pre><code class="hljs">class MyComponent extends React.Component {  
  shouldComponentUpdate(nextProps, nextState) {  
    // 自定义比较逻辑  
    return this.props.someKey !== nextProps.someKey;  
  }  

  render() {  
    /* render using this.props */  
  }  
}
</code></pre>
<p>
 <br>
</p>
<h4>3. 条件渲染</h4>
<p>在某些情况下，你可以通过条件渲染来避免渲染整个子树。比如，基于某些条件来决定是否渲染某个组件或组件的一部分。</p>
<pre><code class="hljs">{shouldRenderSubTree &amp;&amp; &lt;SubTreeComponent /&gt;}
</code></pre>
<p>
 <br>
</p>
<p><strong>4. 使用</strong> <code>React.lazy</code><strong>和</strong> <code>Suspense</code><strong>进行代码分割</strong></p>
<p>虽然这不是直接优化"选择性子树渲染"的方式，但使用<code>React.lazy</code>和<code>Suspense</code>可以让你根据需要懒加载组件，这有助于减少初始加载时间和减少不必要的代码下载。</p>
<h4>5. 合理使用Context</h4>
<p>Context API可以用来避免通过组件树手动传递props，但它也可以被滥用导致不必要的渲染。确保只在确实需要时才使用Context，并考虑使用<code>React.memo</code>或<code>shouldComponentUpdate</code>来优化接收Context的组件。</p>
<h4>结论</h4>
<p>虽然没有直接名为"选择性子树渲染"的API，但通过上述方法，你可以有效地控制React中的组件渲染，以减少不必要的DOM操作和性能开销。每种方法都有其适用场景，选择最适合你应用需求的方法是关键。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142423427</p>]]></description><guid isPermaLink="false">/archives/de466091-fc56-4348-8385-273bde533400</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:44:02 GMT</pubDate></item><item><title><![CDATA[vue中webpack的主要功能]]></title><link>https://wiki.dgfygk.com/archives/55650d06-e3bc-44da-98ce-4f135c19dad0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%E4%B8%ADwebpack%E7%9A%84%E4%B8%BB%E8%A6%81%E5%8A%9F%E8%83%BD&amp;url=/archives/55650d06-e3bc-44da-98ce-4f135c19dad0" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue项目中，Webpack扮演了非常重要的角色，它是现代JavaScript应用程序的静态模块打包器（module bundler）。Webpack的主要功能可以归纳为以下几点，这些功能对于Vue项目的开发和优化至关重要：</p>
<ol>
 <li>
  <p><strong>模块打包</strong>：Webpack能够将项目中的所有资源（JavaScript、CSS、图片、字体等）视为模块，并根据模块的依赖关系进行打包。这对于Vue项目尤为重要，因为Vue组件本质上也是模块，Webpack能够处理这些组件之间的依赖关系，将它们打包成一个或多个bundle文件。</p>
 </li>
 <li>
  <p><strong>加载器（Loaders）</strong> ：Webpack通过加载器（Loaders）来处理非JavaScript文件（如CSS、图片、字体等）。加载器可以将这些文件转换为有效的模块，以便它们可以被添加到依赖图中。例如，<code>css-loader</code>允许你将CSS导入为JavaScript模块，而<code>file-loader</code>则可以将文件发送到output目录并返回（或返回一个URL）。</p>
 </li>
 <li>
  <p><strong>插件（Plugins）</strong> ：Webpack的插件系统使其功能极其强大和灵活。插件可以执行范围广泛的任务，如打包优化、资源管理、环境变量注入等。对于Vue项目，一些常用的Webpack插件包括<code>vue-loader</code>（处理Vue单文件组件的加载器，实际上也是一个插件）、<code>html-webpack-plugin</code>（简化HTML文件的创建，以便为你的webpack包提供服务）、<code>clean-webpack-plugin</code>（在Webpack构建过程之前和/或之后清理/删除构建文件夹）等。</p>
 </li>
 <li>
  <p><strong>开发服务器</strong>：Webpack Dev Server提供了一个简单的web服务器，并且具有实时重新加载（live reloading）功能。这意味着当你在开发Vue应用时，你可以修改代码并立即在浏览器中看到更改，而无需手动刷新页面。这对于提高开发效率非常有帮助。</p>
 </li>
 <li>
  <p><strong>优化</strong>：Webpack提供了多种优化手段，如代码分割（Code Splitting）、懒加载（Lazy Loading）、树摇（Tree Shaking）等，这些优化手段可以帮助你减小bundle的体积，提高应用的加载速度。在Vue项目中，这些优化手段尤为重要，因为它们直接影响到用户的体验。</p>
 </li>
 <li>
  <p><strong>环境变量</strong>：Webpack允许你定义和使用环境变量，这有助于你根据不同的环境（如开发环境、测试环境、生产环境）来配置你的项目。这对于Vue项目来说非常有用，因为你可以根据环境的不同来启用或禁用某些功能，或者改变API的URL等。</p>
 </li>
</ol>
<p>总之，Webpack是Vue项目不可或缺的一部分，它为Vue项目提供了模块打包、资源管理、优化、开发服务等一系列强大的功能。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142440773</p>]]></description><guid isPermaLink="false">/archives/55650d06-e3bc-44da-98ce-4f135c19dad0</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 08:42:57 GMT</pubDate></item><item><title><![CDATA[window.onload什么时候执行]]></title><link>https://wiki.dgfygk.com/archives/53104b5f-cfe7-4f9e-87dd-04c9afc9b642</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=window.onload%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E6%89%A7%E8%A1%8C&amp;url=/archives/53104b5f-cfe7-4f9e-87dd-04c9afc9b642" width="1" height="1" alt="" style="opacity:0;">
<p><code>window.onload</code> 事件在页面的所有资源（包括样式表、脚本文件、图片等）都加载完成后立即执行。这意味着，在 <code>window.onload</code> 事件的处理函数中，你可以安全地引用页面上的所有元素，因为此时它们都已经加载到DOM中，并且所有的样式和脚本也都已经应用和执行完毕。</p>
<p>这个事件特别有用，当你需要在页面加载完毕后立即执行某些操作（比如初始化JavaScript插件、发送统计信息等）时。</p>
<p>下面是一个简单的 <code>window.onload</code> 使用示例：</p>
<pre><code class="hljs">window.onload = function() {  
    // 在这里编写你的代码  
    // 比如，你可以安全地访问DOM元素  
    var element = document.getElementById("myElement");  
    if (element) {  
        // 对element进行操作  
        element.style.backgroundColor = "red";  
    }  
};
</code></pre>
<p>需要注意的是，如果你在页面中有多个 <code>window.onload</code> 赋值操作，那么只有最后一个会被执行，因为前一个会被后一个覆盖。为了避免这种情况，你可以使用 <code>addEventListener</code> 方法来为 <code>window.onload</code> 事件添加多个监听器，如下所示：</p>
<pre><code class="hljs">window.addEventListener('load', function() {  
    // 第一个加载完成后要执行的函数  
    console.log('页面加载完成，执行第一个操作');  
});  

window.addEventListener('load', function() {  
    // 第二个加载完成后要执行的函数  
    console.log('页面加载完成，执行第二个操作');  
});
</code></pre>
<p>使用 <code>addEventListener</code> 方法可以让你为同一个事件添加多个监听器，而不会相互覆盖。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142501041</p>]]></description><guid isPermaLink="false">/archives/53104b5f-cfe7-4f9e-87dd-04c9afc9b642</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:42:36 GMT</pubDate></item><item><title><![CDATA[前端构造函数]]></title><link>https://wiki.dgfygk.com/archives/3420c684-f7c1-4f29-8573-ec0b095abebe</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%89%8D%E7%AB%AF%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0&amp;url=/archives/3420c684-f7c1-4f29-8573-ec0b095abebe" width="1" height="1" alt="" style="opacity:0;">
<p>在前端开发中，构造函数扮演着至关重要的角色，尤其是在使用JavaScript这类动态类型语言时。构造函数是一种特殊的函数，主要用于初始化对象，即为对象成员变量赋初始值。下面将详细介绍前端构造函数的相关内容：</p>
<h4>一、定义与特点</h4>
<ol>
 <li>
  <p><strong>定义</strong> ：构造函数是一种特殊的函数，其名称习惯上首字母大写，用于创建和初始化对象。它通常与<code>new</code>操作符一起使用，通过<code>new</code>操作符调用时，会执行一系列操作来创建一个新对象。</p>
 </li>
 <li>
  <p><strong>特点</strong>：</p>
 </li>
 <li>
  <p><strong>首字母大写</strong>：为了区分普通函数和构造函数，习惯上将构造函数的首字母大写。</p>
 </li>
 <li>
  <p><strong>与<code>new</code>操作符一起使用</strong> ：构造函数必须通过<code>new</code>操作符来调用，以创建对象的实例。</p>
 </li>
 <li>
  <p><strong>初始化对象</strong>：构造函数的主要目的是初始化对象，为对象成员变量赋初始值。</p>
 </li>
</ol>
<h4>二、执行过程</h4>
<p>当使用<code>new</code>操作符调用构造函数时，JavaScript会执行以下步骤来创建并初始化一个新对象：</p>
<ol>
 <li><strong>创建新对象</strong>：在内存中创建一个新的空对象。</li>
 <li><strong>设置原型</strong> ：将新对象的<code>__proto__</code>属性（内部属性，通常通过<code>Object.getPrototypeOf()</code>访问）指向构造函数的<code>prototype</code>属性所引用的对象。这样，新对象就可以继承构造函数原型上的属性和方法。</li>
 <li><strong>改变<code>this</code>指向</strong> ：将构造函数内部的<code>this</code>关键字指向新创建的对象。</li>
 <li><strong>执行构造函数代码</strong>：执行构造函数中的代码，为新对象添加属性和方法。</li>
 <li><strong>返回新对象</strong>：如果构造函数显式返回一个对象，则返回该对象；否则，返回步骤1中创建的新对象。</li>
</ol>
<h4>三、构造函数与原型</h4>
<ol>
 <li><strong>原型（prototype）</strong> ：每个构造函数都有一个<code>prototype</code>属性，它是一个对象，包含了可以由构造函数创建的所有对象实例共享的属性和方法。</li>
 <li><strong>constructor属性</strong> ：原型对象上有一个<code>constructor</code>属性，它指向关联的构造函数。这允许对象实例知道是由哪个构造函数创建的。</li>
 <li><strong>原型链</strong> ：当访问一个对象的属性或方法时，如果该对象本身没有该属性或方法，JavaScript会沿着该对象的原型链向上查找，直到找到该属性或方法或到达原型链的顶端（通常是<code>Object.prototype</code>）。</li>
</ol>
<h4>四、静态成员与实例成员</h4>
<ol>
 <li><strong>静态成员</strong>：在构造函数本身上添加的成员称为静态成员，只能由构造函数本身来访问，不能通过构造函数创建的实例来访问。</li>
 <li><strong>实例成员</strong> ：在构造函数内部通过<code>this</code>关键字添加的成员称为实例成员，只能由构造函数创建的实例来访问。</li>
</ol>
<h4>五、使用场景与优势</h4>
<p>构造函数在前端开发中广泛用于创建具有相似属性和方法的对象实例。通过使用构造函数，可以方便地管理对象的初始化过程，并实现对象的继承和复用。此外，构造函数还有助于区分不同类型的对象，提高代码的可读性和可维护性。</p>
<h4>六、示例</h4>
<pre><code class="hljs">function Person(name, age) {  
  this.name = name;  
  this.age = age;  
  this.greet = function() {  
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);  
  };  
}  

var person1 = new Person('Alice', 30);  
person1.greet(); // 输出: Hello, my name is Alice and I am 30 years old.  

var person2 = new Person('Bob', 25);  
person2.greet(); // 输出: Hello, my name is Bob and I am 25 years old.
</code></pre>
<p>在上面的示例中，<code>Person</code>是一个构造函数，用于创建<code>Person</code>类型的对象实例。<code>person1</code>和<code>person2</code>是通过<code>new Person()</code>调用构造函数创建的实例，它们具有相同的属性和方法，但属性值不同。这展示了构造函数在创建和管理对象实例方面的强大功能。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142185498</p>]]></description><guid isPermaLink="false">/archives/3420c684-f7c1-4f29-8573-ec0b095abebe</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:42:09 GMT</pubDate></item><item><title><![CDATA[事件冒泡和事件捕获一般用在什么场景]]></title><link>https://wiki.dgfygk.com/archives/19264652-1fb9-463f-bc66-f65ad8e014d0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1%E5%92%8C%E4%BA%8B%E4%BB%B6%E6%8D%95%E8%8E%B7%E4%B8%80%E8%88%AC%E7%94%A8%E5%9C%A8%E4%BB%80%E4%B9%88%E5%9C%BA%E6%99%AF&amp;url=/archives/19264652-1fb9-463f-bc66-f65ad8e014d0" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<p>事件冒泡和事件捕获是JavaScript中处理DOM事件时的两种不同传播方式，它们各自有特定的应用场景。</p>
<h4>事件冒泡（Event Bubbling）</h4>
<p><strong>定义</strong> ：事件冒泡是指当某个元素上的事件被触发时，这个事件会沿着DOM树向上传播，直到达到根节点（通常是<code>document</code>对象）。</p>
<p><strong>应用场景</strong>：</p>
<ol>
 <li><strong>事件委托</strong> ：</li>
 <li><strong>性能提升</strong>：通过将事件监听器添加到父元素上，并利用事件冒泡来处理子元素的事件，可以减少事件处理程序的数量，从而提高性能。这在处理大量子元素或动态添加子元素时尤为有用。</li>
 <li><strong>代码简化</strong>：避免为每个子元素单独绑定事件处理程序，使得代码更加简洁和易于维护。</li>
 <li><strong>组件间通信</strong> ：</li>
 <li>在复杂的页面中，组件之间可能需要通过事件进行通信。通过事件冒泡，子组件可以触发一个事件，并将需要传递的数据作为事件的参数，然后让父组件监听这个事件并处理。这样可以实现组件之间的解耦，提高代码的复用性和可维护性。</li>
</ol>
<h4>事件捕获（Event Capturing）</h4>
<p><strong>定义</strong>：事件捕获是指事件从DOM树的根节点开始，逐级向下传播到目标元素的过程。</p>
<p><strong>应用场景</strong>：</p>
<ol>
 <li><strong>前置操作</strong> ：</li>
 <li>如果你需要在事件到达目标元素之前执行一些操作，例如验证用户输入、获取上下文信息等，可以使用事件捕获。通过在父级元素上使用事件捕获，你可以在事件到达目标元素之前拦截事件并进行处理。</li>
 <li><strong>细粒度控制</strong> ：</li>
 <li>一些复杂的交互行为可能需要更细粒度的控制，而事件捕获提供了这种能力。例如，你可能需要在事件捕获阶段使用<code>stopPropagation</code>方法停止事件的传播，以防止事件继续向下冒泡或到达目标元素。</li>
 <li><strong>处理异步加载的元素</strong> ：</li>
 <li>当页面中的元素是异步加载时，可能会出现目标元素在页面加载完成后才被加载出来的情况。这时如果使用事件冒泡来处理事件，可能会出现无法触发目标元素的事件处理程序的情况。使用事件捕获可以在页面加载完成后就逐层向下传递事件，确保目标元素能够被正确地触发。</li>
</ol>
<h4>总结</h4>
<p>事件冒泡和事件捕获各有其应用场景，开发者应根据具体需求选择合适的传播方式。在实际开发中，事件冒泡通常用于实现事件委托和组件间通信，而事件捕获则更多用于前置操作和细粒度控制。同时，需要注意的是，大多数事件处理程序默认情况下在事件冒泡阶段触发，但可以通过<code>addEventListener</code>的第三个参数来指定事件是在捕获阶段还是冒泡阶段处理。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142342436</p>]]></description><guid isPermaLink="false">/archives/19264652-1fb9-463f-bc66-f65ad8e014d0</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:41:47 GMT</pubDate></item><item><title><![CDATA[网络七层协议]]></title><link>https://wiki.dgfygk.com/archives/06324f22-947e-447c-abff-c2adc7baa51b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%BB%9C%E4%B8%83%E5%B1%82%E5%8D%8F%E8%AE%AE&amp;url=/archives/06324f22-947e-447c-abff-c2adc7baa51b" width="1" height="1" alt="" style="opacity:0;">
<p>网络七层协议，也称为OSI（Open Systems Interconnection）参考模型，是由国际标准化组织（ISO）提出的一种网络通信的协议分层模型。该模型将网络通信过程划分为七个层次，从下到上依次为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每个层次都有其特定的功能和任务，通过各层的协同工作，实现了计算机网络的可靠通信和高效传输。</p>
<h4>1. 物理层（Physical Layer）</h4>
<ul>
 <li><strong>功能</strong>：负责数据传输的物理介质传输，如电信号、光信号等。定义了通信设备之间的物理接口标准及传输介质等细节，包括电压、连接器、线缆等。</li>
 <li><strong>示例</strong>：以太网、无线局域网（Wi-Fi）、光纤等。</li>
</ul>
<h4>2. 数据链路层（Data Link Layer）</h4>
<ul>
 <li><strong>功能</strong>：在通信的实体之间建立数据链路，并根据需要执行差错检测和纠正，确保数据传输的可靠性。将数据分割成帧（frame），并添加控制信息，如MAC地址，以便进行点对点的直接通信。</li>
 <li><strong>示例</strong>：以太网、Wi-Fi MAC、PPP（点对点协议）等。</li>
</ul>
<h4>3. 网络层（Network Layer）</h4>
<ul>
 <li><strong>功能</strong>：负责在不同网络之间的数据传输和路由选择。实现逻辑地址（IP地址）的寻址和路由选择，提供不同网络之间的互联。</li>
 <li><strong>示例</strong>：IP协议、ICMP协议、路由器等。</li>
</ul>
<h4>4. 传输层（Transport Layer）</h4>
<ul>
 <li><strong>功能</strong>：提供端到端的可靠数据传输。使用传输协议（如TCP和UDP）在网络上的两个节点之间建立、维护和结束通信连接。</li>
 <li><strong>示例</strong>：TCP协议（可靠连接）、UDP协议（无连接）。</li>
</ul>
<h4>5. 会话层（Session Layer）</h4>
<ul>
 <li><strong>功能</strong>：负责建立、管理和终止应用程序之间的通信会话。提供了对话控制和同步，以便在通信双方之间建立可靠的会话。</li>
 <li><strong>示例</strong>：RPC（远程过程调用）、NetBIOS等。</li>
</ul>
<h4>6. 表示层（Presentation Layer）</h4>
<ul>
 <li><strong>功能</strong>：处理数据的格式转换、加密和解密等。负责数据的压缩、加密、编码和解码，以确保不同系统上的应用程序能够相互交换数据。</li>
 <li><strong>示例</strong>：加密解密算法、ASCII编码、压缩算法等。</li>
</ul>
<h4>7. 应用层（Application Layer）</h4>
<ul>
 <li><strong>功能</strong>：提供通用的接口和服务，为用户提供特定的应用程序。它是七层协议的最高层，为最终用户提供一种服务，并处理特定的应用程序。</li>
 <li><strong>示例</strong>：HTTP协议（支持万维网应用）、FTP协议（支持文件传送）、SMTP协议（支持电子邮件）等。</li>
</ul>
<p>网络七层协议模型通过分层的方式，使得网络设计变得模块化、可扩展，并且不同层级的协议可以独立地进行开发和维护。这种分层结构有助于理解和解决网络通信中的复杂问题。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142467848</p>]]></description><guid isPermaLink="false">/archives/06324f22-947e-447c-abff-c2adc7baa51b</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 08:41:25 GMT</pubDate></item><item><title><![CDATA[CORS是什么，功能如何实现]]></title><link>https://wiki.dgfygk.com/archives/a51e996b-3d7d-4931-a4a5-f995ddee5f58</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=CORS%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%8C%E5%8A%9F%E8%83%BD%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0&amp;url=/archives/a51e996b-3d7d-4931-a4a5-f995ddee5f58" width="1" height="1" alt="" style="opacity:0;">
<p>CORS，全称为"跨域资源共享”（Cross-Origin Resource Sharing），是一种浏览器技术的规范，允许浏览器向跨源服务器发出XMLHttpRequest请求，从而克服了AJAX只能同源使用的限制。CORS是Web安全领域的一个重要概念，旨在确保浏览器端与服务器端之间安全地进行跨域通信。</p>
<h4>CORS的功能</h4>
<p>CORS的主要功能是：</p>
<ol>
 <li><strong>安全跨域通信</strong>：允许前端页面从与其不同源的服务器请求资源，如API接口数据，同时保持通信的安全性。</li>
 <li><strong>细粒度控制</strong>：通过配置服务器端的响应头，可以精确地控制哪些域名的请求被允许，哪些请求方法被支持，以及哪些头部信息可以暴露给前端。</li>
 <li><strong>兼容性强</strong>：CORS得到了所有现代浏览器的支持（IE浏览器需不低于IE10），这使得基于CORS的跨域通信解决方案具有很高的普适性。</li>
</ol>
<h4>CORS功能的实现</h4>
<p>CORS功能的实现依赖于浏览器和服务器之间的协作，主要通过HTTP请求和响应中的特定头部字段来完成。CORS分为两种类型的请求处理方式：简单请求和预检请求。</p>
<ol>
 <li><strong>简单请求</strong> ：</li>
 <li>简单请求是指那些使用GET、HEAD或POST方法，且POST请求的Content-Type仅为application/x-www-form-urlencoded、multipart/form-data或text/plain的请求。</li>
 <li>对于简单请求，浏览器会直接发送请求，并在响应中检查CORS头部，如<code>Access-Control-Allow-Origin</code>。</li>
 <li><strong>预检请求</strong> ：</li>
 <li>对于复杂请求（如使用PUT、DELETE方法，或包含自定义头部的POST请求），浏览器会首先发送一个OPTIONS请求，称为预检请求（Preflight Request）。</li>
 <li>预检请求包含请求的来源（Origin）、实际请求将使用的方法（Access-Control-Request-Method）以及实际请求将包含的自定义头部（Access-Control-Request-Headers）。</li>
 <li>服务器收到预检请求后，会返回一个响应，包含是否允许请求的CORS头部信息，如<code>Access-Control-Allow-Origin</code>、<code>Access-Control-Allow-Methods</code>和<code>Access-Control-Allow-Headers</code>等。</li>
 <li>如果预检请求通过，浏览器会继续发送实际请求。</li>
</ol>
<h4>CORS的配置</h4>
<p>在服务器端配置CORS，通常需要在响应头中添加相应的CORS头部字段。例如，在Express.js中，可以使用<code>cors</code>中间件来轻松实现CORS配置。具体配置方法可能因服务器和框架的不同而有所差异，但基本思路相同，都是在响应头中指定允许的源、方法、头部等信息。</p>
<h4>CORS的优势</h4>
<p>CORS的优势在于它提供了一种安全、灵活且兼容性强的跨域通信解决方案。通过CORS，开发者可以更加自由地构建跨域应用，而无需担心浏览器同源策略的限制。同时，CORS也提供了细粒度的控制机制，允许开发者根据实际需求来配置跨域通信的权限。</p>
<p>总的来说，CORS是现代Web开发中不可或缺的一部分，它为跨域通信提供了强大的支持和保障。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141902939</p>]]></description><guid isPermaLink="false">/archives/a51e996b-3d7d-4931-a4a5-f995ddee5f58</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:41:10 GMT</pubDate></item><item><title><![CDATA[ES6中try-catch]]></title><link>https://wiki.dgfygk.com/archives/e819f834-2fac-41a0-a31d-6cc2916c58e0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ES6%E4%B8%ADtry-catch&amp;url=/archives/e819f834-2fac-41a0-a31d-6cc2916c58e0" width="1" height="1" alt="" style="opacity:0;">
<p>在ES6（ECMAScript 2015）中，<code>try-catch</code> 语句的语法和使用方式与在之前的ECMAScript版本中是一样的。<code>try-catch</code> 语句用于处理代码中可能发生的错误，确保程序的健壮性和用户体验。</p>
<h4>基本语法</h4>
<pre><code class="hljs">try {  
  // 尝试执行的代码块  
  // 如果发生错误，则执行 catch 块中的代码  
} catch (error) {  
  // 处理错误  
  // error 对象包含了错误的详细信息  
}
</code></pre>
<h4>示例</h4>
<p>假设我们有一个函数，它尝试将一个字符串转换为数字，并返回该数字的两倍。如果转换失败（例如，因为字符串不是一个有效的数字），则应该捕获这个错误并返回一个默认值。</p>
<pre><code class="hljs">function doubleNumber(str) {  
  try {  
    const num = Number(str);  
    return num * 2;  
  } catch (error) {  
    // 如果转换失败，捕获错误并返回 0  
    console.error('Error converting string to number:', error);  
    return 0;  
  }  
}  

console.log(doubleNumber('10')); // 输出: 20  
console.log(doubleNumber('abc')); // 输出: 0，并在控制台显示错误信息
</code></pre>
<h4>注意事项</h4>
<ol>
 <li>
  <p><strong>错误对象</strong> ：在 <code>catch</code> 块中，你可以访问到一个 <code>error</code> 对象，它包含了错误的详细信息，如错误消息和堆栈跟踪。</p>
 </li>
 <li>
  <p><strong>finally 块</strong> ：虽然 <code>finally</code> 块不是 ES6 特有的，但它是 <code>try-catch</code> 语句的一个重要部分，用于指定无论是否发生错误都要执行的代码。然而，请注意，在 ES6 中没有引入关于 <code>try-catch</code> 的新特性，<code>finally</code> 块自ES3起就已存在。</p>
  <pre><code class="hljs">try {  
 // 尝试执行的代码  
} catch (error) {  
 // 错误处理  
} finally {  
 // 无论是否发生错误，都会执行的代码  
}
</code></pre>
  <p>
   <br>
  </p>
 </li>
 <li>
  <p><strong>错误传播</strong> ：如果你在一个函数内部使用了 <code>try-catch</code>，但你没有在 <code>catch</code> 块中处理错误（例如，通过返回一个值或抛出一个新的错误），那么这个错误将不会传播到函数的外部。如果你希望错误继续传播，可以在 <code>catch</code> 块中再次抛出错误。</p>
 </li>
 <li>
  <p><strong>性能考虑</strong> ：虽然 <code>try-catch</code> 语句对于错误处理非常有用，但频繁使用它们可能会对性能产生负面影响，因为 JavaScript 引擎在 <code>try</code> 块中需要维护额外的信息以便在发生错误时能够正确地执行 <code>catch</code> 块。因此，建议仅在确实需要捕获和处理错误的情况下使用 <code>try-catch</code>。</p>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141902874</p>]]></description><guid isPermaLink="false">/archives/e819f834-2fac-41a0-a31d-6cc2916c58e0</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 08:39:58 GMT</pubDate></item><item><title><![CDATA[es6中解构赋值]]></title><link>https://wiki.dgfygk.com/archives/a17a71d6-d6b1-4a94-b464-3cd7f60783fd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=es6%E4%B8%AD%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC&amp;url=/archives/a17a71d6-d6b1-4a94-b464-3cd7f60783fd" width="1" height="1" alt="" style="opacity:0;">
<p>ES6（ECMAScript 2015）引入了一种新的表达式——解构赋值（Destructuring assignment），允许你直接从数组或对象中提取数据，赋值给声明的变量。这种方式使得数据提取更加简洁明了。</p>
<h4>数组解构赋值</h4>
<p>数组解构赋值允许你从数组中提取值，然后按照你指定的模式对变量进行赋值。</p>
<p><strong>基本用法</strong>：</p>
<pre><code class="hljs">let [a, b, c] = [1, 2, 3];  
console.log(a); // 1  
console.log(b); // 2  
console.log(c); // 3
</code></pre>
<p><strong>默认值</strong>：</p>
<p>如果数组中的某些位置没有值，解构赋值允许你为变量指定默认值。</p>
<pre><code class="hljs">let [x, y = 'default', z] = [1];  
console.log(x); // 1  
console.log(y); // 'default'  
console.log(z); // undefined
</code></pre>
<p><strong>不完全解构</strong>：</p>
<p>如果不需要数组中的所有值，可以只解构你需要的部分。</p>
<pre><code class="hljs">let [, , z] = [1, 2, 3];  
console.log(z); // 3
</code></pre>
<p><strong>嵌套数组解构</strong>：</p>
<p>解构赋值也支持嵌套数组。</p>
<pre><code class="hljs">let [a, [b], c] = [1, [2, 3], 4];  
console.log(a); // 1  
console.log(b); // 2  
console.log(c); // 4
</code></pre>
<h4>对象解构赋值</h4>
<p>对象解构赋值允许你从对象中提取值，然后按照你指定的模式对变量进行赋值。</p>
<p><strong>基本用法</strong>：</p>
<pre><code class="hljs">let { foo, bar } = { foo: 'hello', bar: 'world' };  
console.log(foo); // 'hello'  
console.log(bar); // 'world'
</code></pre>
<p><strong>默认值</strong>：</p>
<p>同样地，你可以为变量指定默认值。</p>
<pre><code class="hljs">let { baz = 'default' } = {};  
console.log(baz); // 'default'
</code></pre>
<p><strong>重命名变量</strong>：</p>
<p>在解构时，你也可以将对象的属性赋值给与属性名不同的变量。</p>
<pre><code class="hljs">let { foo: baz } = { foo: 'hello' };  
console.log(baz); // 'hello'
</code></pre>
<p><strong>嵌套对象解构</strong>：</p>
<p>解构赋值也支持嵌套对象。</p>
<pre><code class="hljs">let { user: { name, age } } = { user: { name: 'John', age: 30 } };  
console.log(name); // 'John'  
console.log(age); // 30
</code></pre>
<p><strong>参数解构</strong>：</p>
<p>解构赋值在函数参数中也非常有用，可以使函数签名更加清晰。</p>
<pre><code class="hljs">function greet({ name, greeting = 'Hello' }) {  
  console.log(`${greeting}, ${name}!`);  
}  

greet({ name: 'Alice' }); // Hello, Alice!
</code></pre>
<p>解构赋值是ES6中非常强大且实用的特性，可以极大地简化代码，提高代码的可读性和可维护性。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141871676</p>]]></description><guid isPermaLink="false">/archives/a17a71d6-d6b1-4a94-b464-3cd7f60783fd</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:39:41 GMT</pubDate></item><item><title><![CDATA[rgb中的最大数值为什么是255]]></title><link>https://wiki.dgfygk.com/archives/fc8f7c3a-5dd4-4417-9bd5-af9eb3a84a6d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=rgb%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%80%BC%E4%B8%BA%E4%BB%80%E4%B9%88%E6%98%AF255&amp;url=/archives/fc8f7c3a-5dd4-4417-9bd5-af9eb3a84a6d" width="1" height="1" alt="" style="opacity:0;">
<p>在RGB（红绿蓝）颜色模型中，每种颜色的强度（或称为亮度、值）被量化为一个0到255之间的整数。这个范围的选择基于几个关键原因，主要与计算机系统的设计和历史背景有关。</p>
<ol>
 <li>
  <p><strong>二进制系统的便捷性</strong>：计算机系统是基于二进制（即0和1）的。使用8位（bit）二进制数可以表示的最大数值是2^8 - 1 = 255。因此，选择255作为最大值可以充分利用一个字节（8位）的存储能力，无需额外的存储或处理开销。</p>
 </li>
 <li>
  <p><strong>内存和存储效率</strong>：在计算机图形处理的早期，内存和存储资源都非常有限。使用8位（即一个字节）来表示颜色的强度是一个高效的选择，因为它允许在一个字节的范围内精确控制颜色的亮度，同时不会浪费太多内存。</p>
 </li>
 <li>
  <p><strong>人眼感知的限制</strong>：虽然理论上可以使用更多的位数来表示颜色的亮度（比如16位或32位），但人眼对颜色亮度的分辨能力有限。在大多数应用场景中，使用8位（即256级亮度）已经足够模拟人眼可以区分的颜色差异。</p>
 </li>
 <li>
  <p><strong>标准化和兼容性</strong>：随着时间的推移，使用8位RGB值来表示颜色成为了计算机图形和图像处理领域的标准。这个标准被广泛应用于各种软件和硬件中，确保了不同系统之间的兼容性和互操作性。</p>
 </li>
</ol>
<p>综上所述，RGB中的最大数值选择为255是基于二进制系统的便捷性、内存和存储效率、人眼感知的限制以及标准化和兼容性的综合考虑。这个选择在现代计算机图形和图像处理中仍然占据主导地位。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142019923</p>]]></description><guid isPermaLink="false">/archives/fc8f7c3a-5dd4-4417-9bd5-af9eb3a84a6d</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Thu, 26 Sep 2024 08:39:28 GMT</pubDate></item><item><title><![CDATA[如何来避免FOUC]]></title><link>https://wiki.dgfygk.com/archives/dae91f16-ab8b-44f7-baf1-f3e1036b5527</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E6%9D%A5%E9%81%BF%E5%85%8DFOUC&amp;url=/archives/dae91f16-ab8b-44f7-baf1-f3e1036b5527" width="1" height="1" alt="" style="opacity:0;">
<p>FOUC（Flash of Unstyled Content）是指在网页加载过程中，由于CSS样式加载延迟或加载顺序不当，导致页面出现短暂的无样式内容闪烁现象。为了避免FOUC，可以采取以下几种方法：</p>
<h4>1. 优化CSS加载</h4>
<ul>
 <li><strong>内联CSS</strong> ：将关键的CSS样式直接嵌入到HTML文档的<code>&lt;head&gt;</code>部分，这样可以确保在页面渲染之前样式就已经加载和应用。</li>
 <li><strong>提前引入CSS</strong> ：将CSS文件放在HTML文档的<code>&lt;head&gt;</code>标签内，确保在其他内容加载之前优先加载样式。</li>
 <li><strong>使用预加载</strong> ：利用<code>&lt;link rel="preload"&gt;</code>标签来预加载CSS文件，确保在渲染阶段之前提前加载样式。这可以明确告诉浏览器哪些资源是优先级较高的，应该被优先加载。</li>
</ul>
<h4>2. 控制资源加载顺序</h4>
<ul>
 <li><strong>避免阻塞脚本</strong> ：确保JavaScript脚本不会阻塞CSS样式的加载。可以使用<code>async</code>或<code>defer</code>属性来异步加载脚本，从而避免它们干扰CSS的加载过程。</li>
 <li><strong>使用JavaScript控制加载顺序</strong>：通过JavaScript代码控制CSS文件的加载顺序，确保CSS文件在页面加载时优先加载。</li>
</ul>
<h4>3. 优化CSS文件</h4>
<ul>
 <li><strong>压缩CSS文件</strong>：使用压缩工具减少CSS文件的体积，从而减少加载时间。</li>
 <li><strong>合并CSS文件</strong>：将多个CSS文件合并为一个，以减少HTTP请求的数量，加快加载速度。</li>
</ul>
<h4>4. 使用样式回退</h4>
<ul>
 <li>在CSS样式加载之前，可以使用基本的HTML样式来避免FOUC，确保页面在加载完成前依然有可用的样式。这可以通过在<code>&lt;head&gt;</code>中添加一些内联样式来实现。</li>
</ul>
<h4>5. 使用样式加载器</h4>
<ul>
 <li>使用样式加载器（如LoadCSS）来异步加载CSS文件，确保CSS文件在页面加载时立即生效，避免FOUC的发生。</li>
</ul>
<h4>6. 样式闪避技术</h4>
<ul>
 <li>通过设置页面的初始样式，使其与最终样式尽可能接近，以减少FOUC的显著性。这可以通过在<code>&lt;head&gt;</code>中添加一些基本的样式规则来实现。</li>
</ul>
<h4>7. 避免使用外部字体文件</h4>
<ul>
 <li>外部字体文件的加载也可能会导致FOUC。如果可能的话，可以考虑使用系统字体或将字体文件嵌入到CSS中，以减少加载时间和潜在的FOUC问题。</li>
</ul>
<p>综上所述，避免FOUC的关键在于优化CSS的加载和应用过程，确保在页面渲染之前样式就已经被加载和应用。通过采取上述方法，可以显著减少或消除FOUC现象，从而提升用户体验。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142021599</p>]]></description><guid isPermaLink="false">/archives/dae91f16-ab8b-44f7-baf1-f3e1036b5527</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:39:07 GMT</pubDate></item><item><title><![CDATA[如何区分html和html5]]></title><link>https://wiki.dgfygk.com/archives/d7d82dfb-70e2-4a50-aa36-13c1ec94b1ef</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%8C%BA%E5%88%86html%E5%92%8Chtml5&amp;url=/archives/d7d82dfb-70e2-4a50-aa36-13c1ec94b1ef" width="1" height="1" alt="" style="opacity:0;">
<p>HTML与HTML5在多个方面存在显著的区别。以下是对这些区别的详细分析：</p>
<h4>1. 概念区别</h4>
<ul>
 <li><strong>HTML</strong>：HTML是HyperText Markup Language（超文本标记语言）的缩写，是一种用于创建网页和网页应用程序的标准标记语言。它通过一系列的标签（tags）来描述网页的内容和结构。</li>
 <li><strong>HTML5</strong>：HTML5是HTML的第五个版本，它不仅包含了HTML 4.01的所有功能，还增加了许多新的元素和属性，以及改进了对旧元素的支持。HTML5被设计为互联网的核心技术之一，用于构建和呈现互联网内容。</li>
</ul>
<h4>2. 文档声明区别</h4>
<ul>
 <li><strong>HTML</strong> ：HTML文档的类型声明通常较长且复杂，如<code>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&gt;</code>。这样的声明对于程序员来说记忆和书写都较为困难。</li>
 <li><strong>HTML5</strong> ：HTML5的文档声明则非常精简，只需<code>&lt;!DOCTYPE html&gt;</code>即可。这样的声明方式有利于程序员快速地阅读和开发。</li>
</ul>
<h4>3. 结构语义区别</h4>
<ul>
 <li><strong>HTML</strong> ：HTML没有充分体现结构语义化的标签。例如，它常使用<code>&lt;div&gt;</code>标签来布局，但这些<code>&lt;div&gt;</code>标签并不直接说明它们所包含的内容是什么。</li>
 <li><strong>HTML5</strong> ：HTML5添加了许多具有语义化的标签，如<code>&lt;article&gt;</code>（表示独立的、可复用的内容块）、<code>&lt;aside&gt;</code>（表示与页面内容稍微独立的部分）、<code>&lt;audio&gt;</code>（表示音频内容）、<code>&lt;video&gt;</code>（表示视频内容）等。这些标签使得网页的结构更加清晰，有利于搜索引擎的抓取和用户的理解。</li>
</ul>
<h4>4. 多媒体支持区别</h4>
<ul>
 <li><strong>HTML</strong>：在不使用Flash播放器等插件的情况下，HTML本身不支持音频和视频的直接播放。</li>
 <li><strong>HTML5</strong> ：HTML5通过<code>&lt;audio&gt;</code>和<code>&lt;video&gt;</code>标签直接支持音频和视频内容的播放，无需额外的插件。这使得网页的多媒体展示更加便捷和高效。</li>
</ul>
<h4>5. 绘图能力区别</h4>
<ul>
 <li><strong>HTML</strong> ：HTML中的矢量图形主要通过<code>&lt;img&gt;</code>标签引入，或者使用SVG（可缩放矢量图形）等技术实现，但这些方式在网页上直接绘制图形的能力有限。</li>
 <li><strong>HTML5</strong> ：HTML5引入了<code>&lt;canvas&gt;</code>元素，它允许使用JavaScript等脚本语言在网页上绘制图形。<code>&lt;canvas&gt;</code>元素提供了丰富的绘图API，可以绘制出复杂的图形和动画效果。</li>
</ul>
<h4>6. 表单元素和API的区别</h4>
<ul>
 <li><strong>HTML5</strong> 相对于HTML在表单元素上进行了升级，新增了多种输入类型（如<code>email</code>、<code>number</code>、<code>date</code>等）和属性（如<code>placeholder</code>），使得表单的验证和用户体验更加优化。</li>
 <li><strong>HTML5</strong>还新增了许多API，如Web Storage（本地存储）、Geolocation（地理位置）、WebSocket等，这些API为网页应用提供了更多的功能和优化选择。</li>
</ul>
<h4>7. 面向对象区别</h4>
<ul>
 <li><strong>HTML</strong>主要面向的是传统的网页设计和开发。</li>
 <li><strong>HTML5</strong>则更加面向未来，它不仅适用于传统的网页开发，还广泛应用于移动应用、Web游戏、实时通信等领域。</li>
</ul>
<p>综上所述，HTML与HTML5在概念、文档声明、结构语义、多媒体支持、绘图能力、表单元素和API以及面向对象等方面都存在显著的区别。这些区别使得HTML5成为互联网技术的重要组成部分，为网页和网页应用的开发提供了更多的可能性和选择。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/142020428</p>]]></description><guid isPermaLink="false">/archives/d7d82dfb-70e2-4a50-aa36-13c1ec94b1ef</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:38:37 GMT</pubDate></item><item><title><![CDATA[微信小程序与H5的区别]]></title><link>https://wiki.dgfygk.com/archives/b643ecc6-7ca8-476d-956d-dc65f75beddd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%B8%8EH5%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/b643ecc6-7ca8-476d-956d-dc65f75beddd" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序与H5在多个方面存在显著的区别，这些区别主要体现在开发语言、运行环境、用户体验、功能和权限、开发成本以及使用方式等方面。以下是详细的比较：</p>
<h4>1. 开发语言</h4>
<ul>
 <li><strong>微信小程序</strong>：有自己的一套标签与样式语言，与标准的HTML5语言不同。它使用微信自定义的WXML（WeiXin Markup Language）作为标记语言，WXSS（WeiXin Style Sheets）作为样式表语言，以及JavaScript进行逻辑处理。开发者需要按照微信官方提供的开发文档进行开发。</li>
 <li><strong>H5</strong>：基于HTML5的标准，需要利用HTML、CSS、JavaScript等Web开发技术开发。这些技术是在Web开发中广泛使用的标准技术。</li>
</ul>
<h4>2. 运行环境</h4>
<ul>
 <li><strong>微信小程序</strong>：运行环境并非完整的浏览器，而是微信开发团队基于浏览器内核完全重构的一个内置解析器。这个解析器针对小程序做了优化，并配合微信自定义的开发语言标准，提升了小程序的性能。</li>
 <li><strong>H5</strong>：主要运行在浏览器环境中，包括移动设备的浏览器和PC端的浏览器。用户可以通过浏览器直接访问H5页面，无需特定的应用或平台。</li>
</ul>
<h4>3. 用户体验</h4>
<ul>
 <li><strong>微信小程序</strong>：无需下载安装，可以直接在微信中使用，占用空间较小，加载速度快，用户体验较好。此外，小程序还提供了丰富的API接口，可以实现接近原生APP的使用体验。</li>
 <li><strong>H5</strong>：虽然也无需安装即可使用，但通常需要通过浏览器打开，加载速度可能受到网络环境和浏览器性能的影响。在复杂的业务逻辑或丰富的页面交互中，可能会出现卡顿现象。</li>
</ul>
<h4>4. 功能和权限</h4>
<ul>
 <li><strong>微信小程序</strong>：能够调用更多的设备硬件功能，如摄像头、地理位置等，提供更多的功能。此外，小程序还可以获得更多的系统权限，如网络通信状态、数据缓存能力等。</li>
 <li><strong>H5</strong>：在获取系统级权限方面较为受限，大多应用场景被定位在业务逻辑简单、功能单一的情况。虽然H5也可以通过某些方式调用一些设备功能或系统权限，但通常不如小程序方便和强大。</li>
</ul>
<h4>5. 开发成本</h4>
<ul>
 <li><strong>微信小程序</strong>：由于微信团队提供了开发者工具并规范了开发标准，因此开发成本相对较低。开发者只需按照官方文档进行开发即可保证小程序在微信内稳定运行。</li>
 <li><strong>H5</strong>：开发需要考虑更多的因素，如开发工具、前端框架、模块管理工具、任务管理工具、UI库选择、接口调用工具以及浏览器兼容性等。这些因素可能会增加开发成本和时间。</li>
</ul>
<h4>6. 使用方式</h4>
<ul>
 <li><strong>微信小程序</strong>：只能在微信内使用，用户需要通过微信的搜索或推荐功能找到并打开小程序。小程序不支持在其他平台或应用中直接打开。</li>
 <li><strong>H5</strong>：页面可以通过任何支持HTML5的浏览器打开，无需特定的应用或平台。用户可以通过网页地址直接访问H5页面，也可以通过分享链接等方式将H5页面分享给其他人。</li>
</ul>
<p>综上所述，微信小程序与H5在开发语言、运行环境、用户体验、功能和权限、开发成本以及使用方式等方面存在明显的区别。这些区别使得两者在应用场景、用户体验和开发效率等方面有所不同。开发者在选择使用哪种技术时需要根据实际需求和应用场景进行综合考虑。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141685166</p>]]></description><guid isPermaLink="false">/archives/b643ecc6-7ca8-476d-956d-dc65f75beddd</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 08:37:24 GMT</pubDate></item><item><title><![CDATA[小程序的数据驱动和vue的双向绑定有何异同]]></title><link>https://wiki.dgfygk.com/archives/4e6f3f31-3699-485a-b74a-0e5648fc8c3a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E9%A9%B1%E5%8A%A8%E5%92%8Cvue%E7%9A%84%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9A%E6%9C%89%E4%BD%95%E5%BC%82%E5%90%8C&amp;url=/archives/4e6f3f31-3699-485a-b74a-0e5648fc8c3a" width="1" height="1" alt="" style="opacity:0;">
<p>小程序的数据驱动和Vue的双向绑定在实现数据与视图同步方面有着共同的目标，但它们在实现方式、绑定方向以及性能优化等方面存在明显的异同。</p>
<h4>相同点</h4>
<ol>
 <li><strong>数据驱动</strong> ：</li>
 <li>小程序和Vue都采用了数据驱动的方式来构建用户界面，即将数据作为中心，通过更新数据来驱动视图的渲染。</li>
 <li>当数据发生变化时，两者都能自动更新对应的视图，以实现数据与视图的同步。</li>
 <li><strong>自动同步</strong> ：</li>
 <li>小程序和Vue都提供了自动同步机制，确保数据的更新能够实时反映到视图上，同时用户对视图的操作也能被捕捉并反馈到数据中。</li>
</ol>
<h4>不同点</h4>
<ol>
 <li><strong>实现机制</strong> ：</li>
 <li><strong>小程序</strong> ：数据驱动主要通过WXML模板和JS脚本实现。开发者需要在JS中定义数据，并通过<code>setData</code>方法更新数据，进而触发视图的更新。这种机制是基于响应式原理的，但更侧重于手动触发更新。</li>
 <li><strong>Vue</strong> ：双向绑定则通过模板引擎和响应式数据等机制实现。Vue内部使用了虚拟DOM和依赖追踪系统，当数据变化时，能够精确地计算出需要更新的DOM部分，并高效地执行更新操作。同时，Vue的双向绑定（如<code>v-model</code>指令）允许数据在视图和模型之间双向流动。</li>
 <li><strong>绑定方向</strong> ：</li>
 <li><strong>小程序</strong>：数据绑定主要是单向的，即从数据到视图。开发者需要在JS中手动更新数据，然后视图才会根据更新后的数据进行渲染。</li>
 <li><strong>Vue</strong>：数据绑定是双向的，即支持从数据到视图和从视图到数据的双向同步。这种机制极大地简化了数据交互过程，提高了开发效率。</li>
 <li><strong>性能优化</strong> ：</li>
 <li><strong>小程序</strong>：由于小程序运行在特定的环境中（如微信、支付宝等），其数据驱动机制针对这些环境进行了优化，采用了轻量级的数据绑定机制，以提高页面渲染的效率和性能。</li>
 <li><strong>Vue</strong>：Vue的双向绑定机制虽然强大，但在处理大型数据集合时可能会带来一定的性能开销。因此，在必要时，Vue也提供了优化手段（如使用计算属性、观察者等）来减少不必要的计算和DOM操作。</li>
 <li><strong>开发体验</strong> ：</li>
 <li><strong>小程序</strong>：小程序的数据驱动方式相对简单直接，开发者需要手动管理数据的更新和视图的渲染，这在一定程度上增加了开发的复杂性和工作量。但另一方面，这种方式也给了开发者更多的控制权，可以更加灵活地处理数据和视图的同步问题。</li>
 <li><strong>Vue</strong>：Vue的双向绑定机制极大地简化了数据交互过程，提高了开发效率。开发者可以更加专注于业务逻辑的实现，而无需过多关注数据同步的细节。同时，Vue的生态系统也非常丰富，提供了大量的插件和工具来支持开发过程。</li>
</ol>
<p>综上所述，小程序的数据驱动和Vue的双向绑定在实现数据与视图同步方面各有特点。开发者在选择技术方案时，应根据具体的应用场景和需求来做出合理的选择。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141684919</p>]]></description><guid isPermaLink="false">/archives/4e6f3f31-3699-485a-b74a-0e5648fc8c3a</guid><dc:creator>黄健</dc:creator><category>小程序</category><category>前端</category><pubDate>Thu, 26 Sep 2024 08:37:04 GMT</pubDate></item><item><title><![CDATA[小程序页面路由传参方法]]></title><link>https://wiki.dgfygk.com/archives/b31b455c-690f-4353-99aa-7822cdb12d33</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%A1%B5%E9%9D%A2%E8%B7%AF%E7%94%B1%E4%BC%A0%E5%8F%82%E6%96%B9%E6%B3%95&amp;url=/archives/b31b455c-690f-4353-99aa-7822cdb12d33" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<p>小程序页面路由传参是小程序开发中常见的需求，它允许在不同页面之间传递和接收数据。以下是几种常见的小程序页面路由传参方法：</p>
<p>
 <br>
</p>
<h4>1. URL参数传递</h4>
<p>这是最直接和常用的传参方式。在跳转页面时，将需要传递的参数附加在URL后面，通过<code>?</code>和<code>&amp;</code>分隔不同的参数。在目标页面的<code>onLoad</code>方法中，可以通过<code>options</code>参数获取这些传递过来的数据。</p>
<p><strong>示例代码</strong>：</p>
<pre><code>// 跳转页面并传递参数  
wx.navigateTo({  
  url: '/pages/detail/detail?id=123&amp;name=test'  
});  

// 在目标页面的onLoad方法中接收参数  
onLoad(options) {  
  console.log(options.id); // 输出: 123  
  console.log(options.name); // 输出: test  
}
</code></pre>
<h4>2. 全局变量传递</h4>
<p>通过小程序的全局变量（通常定义在<code>app.js</code>的<code>globalData</code>对象中）来传递参数。在跳转前，将需要传递的数据存储在全局变量中，然后在目标页面中通过<code>getApp()</code>方法获取全局变量并读取数据。</p>
<p><strong>示例代码</strong>：</p>
<pre><code>// 在app.js中定义全局变量  
App({  
  globalData: {  
    key1: 'value1',  
    key2: 'value2'  
  }  
});  

// 在跳转前设置全局变量  
getApp().globalData.key1 = 'newValue1';  

// 跳转到目标页面  
wx.navigateTo({  
  url: '/pages/targetPage/targetPage'  
});  

// 在目标页面中获取全局变量  
const app = getApp();  
onLoad() {  
  console.log(app.globalData.key1); // 输出: newValue1  
}
</code></pre>
<h4>3. 本地存储传递</h4>
<p>使用小程序的本地存储API（如<code>wx.setStorageSync</code>和<code>wx.getStorageSync</code>）来传递参数。在跳转前，将需要传递的数据存储在本地存储中，然后在目标页面中读取这些数据。这种方式适用于需要跨页面、跨会话持久化存储的场景。</p>
<p><strong>示例代码</strong>：</p>
<pre><code>// 在跳转前存储数据到本地  
wx.setStorageSync('key1', 'value1');  

// 跳转到目标页面  
wx.navigateTo({  
  url: '/pages/targetPage/targetPage'  
});  

// 在目标页面中读取本地存储的数据  
onLoad() {  
  const value1 = wx.getStorageSync('key1');  
  console.log(value1); // 输出: value1  
}
</code></pre>
<h4>4. 通信通道（EventChannel）传递</h4>
<p><code>EventChannel</code>是<code>wx.navigateTo</code>接口独有的，它允许两个页面之间建立一个通信渠道，实现数据的双向传递。在跳转前，可以在当前页面通过<code>eventChannel.emit</code>发送数据，在目标页面通过<code>eventChannel.on</code>接收数据。</p>
<p><strong>示例代码</strong>（略去详细实现，因为涉及较多代码和逻辑，且非所有情况都适用）：</p>
<ul>
 <li>在当前页面通过<code>eventChannel.emit</code>发送数据。</li>
 <li>在目标页面通过<code>eventChannel.on</code>接收数据。</li>
</ul>
<h4>5. 页面栈操作</h4>
<p>虽然页面栈本身不直接用于传参，但你可以通过操作页面栈（如<code>wx.navigateBack</code>）来返回数据给上一个页面。然而，这通常不是直接传参的方式，而是结合其他机制（如全局变量、本地存储等）来实现数据的反向传递。</p>
<h4>总结</h4>
<p>小程序页面路由传参有多种方式，每种方式都有其适用场景。在实际开发中，应根据具体需求和场景选择合适的传参方式。URL参数传递是最简单直接的方式，适用于简单的数据传递；全局变量和本地存储适用于需要跨页面、跨会话持久化存储的场景；通信通道（EventChannel）适用于需要实现页面间双向通信的场景；而页面栈操作则更多地用于页面导航和返回逻辑的处理。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141685124</p>]]></description><guid isPermaLink="false">/archives/b31b455c-690f-4353-99aa-7822cdb12d33</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 08:36:49 GMT</pubDate></item><item><title><![CDATA[j9、vue、uni-app、小程序的页面传参方式]]></title><link>https://wiki.dgfygk.com/archives/4a9825be-f0ea-4ef8-8515-bc2c2e265b18</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=j9%E3%80%81vue%E3%80%81uni-app%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%9A%84%E9%A1%B5%E9%9D%A2%E4%BC%A0%E5%8F%82%E6%96%B9%E5%BC%8F&amp;url=/archives/4a9825be-f0ea-4ef8-8515-bc2c2e265b18" width="1" height="1" alt="" style="opacity:0;">
<p>jq（jQuery）、Vue、Uni-app以及小程序在页面传参方面各有其独特的方法和特点。下面将分别介绍这些框架或平台在页面传参上的主要方式。</p>
<h4>jQuery 页面传参</h4>
<p>在jQuery中，页面传参更多是指通过JavaScript（jQuery是JavaScript的一个库）在DOM元素之间传递数据或执行某些操作。虽然jQuery本身不直接涉及页面跳转时的参数传递（这是由浏览器的URL或HTML5的Web Storage等机制处理的），但jQuery可以通过以下几种方式在DOM元素间"传参”：</p>
<ol>
 <li>
  <p><strong>直接设置元素属性或内容</strong> ：通过<code>.attr()</code>, <code>.prop()</code>, <code>.val()</code>, <code>.text()</code>, <code>.html()</code>等方法直接设置目标元素的属性值、内容等。</p>
 </li>
 <li>
  <p><strong>事件处理</strong> ：利用jQuery的事件处理机制（如<code>.click()</code>, <code>.change()</code>等）在元素上绑定事件，并在事件处理函数中通过参数传递数据。此外，还可以使用<code>.trigger()</code>方法触发自定义事件，并在事件处理函数中接收传递的数据。</p>
 </li>
 <li>
  <p><strong>数据对象</strong>：在调用jQuery函数时，可以将数据对象作为参数传递，数据对象的键表示参数名，值表示参数值。</p>
 </li>
 <li>
  <p><strong>Ajax请求</strong>：在发起Ajax请求时，可以将数据以键值对的形式存储在一个对象中，并作为请求的参数传递给服务器端。</p>
 </li>
</ol>
<h4>Vue 页面传参</h4>
<p>Vue中页面传参的方式主要包括以下几种：</p>
<ol>
 <li><strong>路由传参</strong> ：</li>
 <li>利用路由的<code>name</code>属性传参，通过<code>$route.name</code>接收参数（虽然这种方式不直接用于传递参数，但可用于识别路由）。</li>
 <li>通过<code>router-link</code>的<code>to</code>属性传参，可以使用<code>params</code>（配合<code>name</code>）或<code>query</code>（用于<code>path</code>）来传递参数。</li>
 <li>使用<code>path</code>匹配路由组件时，参数可以通过URL的查询字符串（<code>query</code>）或动态片段（<code>params</code>，需配合<code>*</code>或<code>:</code>在路由路径中的使用）来传递。</li>
 <li><strong>组件间传参</strong> ：</li>
 <li>父组件通过<code>props</code>向子组件传递数据。</li>
 <li>子组件通过<code>$emit</code>触发事件向父组件传递数据。</li>
 <li>使用Vuex进行状态管理，实现跨组件的数据传递。</li>
</ol>
<h4>Uni-app 页面传参</h4>
<p>Uni-app中页面传参的方式多样，包括：</p>
<ol>
 <li>
  <p><strong>URL编程式传参</strong> ：使用<code>uni.navigateTo</code>、<code>uni.redirectTo</code>等API进行页面跳转时，可以在URL中携带参数。接收页面在<code>onLoad</code>或<code>onShow</code>生命周期函数中通过<code>options</code>参数接收传递的数据。</p>
 </li>
 <li>
  <p><strong>全局变量</strong>：通过定义全局变量（如Vuex、globalData等）来跨页面传递数据。</p>
 </li>
 <li>
  <p><strong>事件通信</strong> ：使用<code>uni.$emit</code>和<code>uni.$on</code>进行页面间的事件通信，实现数据的传递。</p>
 </li>
 <li>
  <p><strong>Storage</strong> ：利用本地存储（如<code>uni.setStorage</code>和<code>uni.getStorage</code>）跨会话传递数据。</p>
 </li>
</ol>
<h4>小程序 页面传参</h4>
<p>小程序页面传参的方式主要包括：</p>
<ol>
 <li>
  <p><strong>URL传值</strong> ：在页面跳转时，将参数附加在URL后，接收页面在<code>onLoad</code>函数中通过<code>options</code>参数接收。</p>
 </li>
 <li>
  <p><strong>全局变量</strong> ：使用小程序的全局变量（如<code>globalData</code>）来跨页面传递数据。</p>
 </li>
 <li>
  <p><strong>Storage</strong> ：利用小程序的本地存储（如<code>wx.setStorage</code>和<code>wx.getStorage</code>）进行数据的持久化存储和跨页面传递。</p>
 </li>
 <li>
  <p><strong>EventChannel</strong> ：在<code>wx.navigateTo</code>、<code>wx.redirectTo</code>等页面跳转API中，可以通过<code>events</code>对象创建<code>EventChannel</code>进行页面间的通信和数据传递。</p>
 </li>
 <li>
  <p><strong>页面栈</strong> ：通过小程序的页面栈（<code>getCurrentPages</code>）获取当前页面栈的实例数组，进而操作页面数据或函数。</p>
 </li>
</ol>
<p>综上所述，jq、Vue、Uni-app和小程序在页面传参上各有其独特的方法和适用场景，开发者可以根据具体需求选择合适的方式进行数据传递。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141684499</p>]]></description><guid isPermaLink="false">/archives/4a9825be-f0ea-4ef8-8515-bc2c2e265b18</guid><dc:creator>黄健</dc:creator><category>小程序</category><category>前端</category><pubDate>Thu, 26 Sep 2024 08:36:33 GMT</pubDate></item><item><title><![CDATA[vue,小程序,uni-app的生命周期]]></title><link>https://wiki.dgfygk.com/archives/4baf407c-df51-4a00-8a4f-351e324b1f67</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%2C%E5%B0%8F%E7%A8%8B%E5%BA%8F%2Cuni-app%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&amp;url=/archives/4baf407c-df51-4a00-8a4f-351e324b1f67" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue、小程序（以微信小程序为例）和Uni-app中，生命周期的概念非常关键，它们帮助开发者理解组件或页面从创建到销毁的整个过程，并在适当的时候执行特定的代码。下面分别介绍这三个框架/平台的生命周期：</p>
<h4>Vue 的生命周期</h4>
<p>Vue 组件的生命周期主要指的是组件从创建到销毁的一系列过程。Vue 2.x 和 Vue 3.x 在生命周期上有所不同，但大体上可以分为以下几个阶段：</p>
<ul>
 <li><strong>beforeCreate</strong>：在实例初始化之后，数据观测（data observer）和event/watcher 事件配置之前被调用。</li>
 <li><strong>created</strong> ：实例已经创建完成之后被调用。在这一步，实例已完成以下的配置：数据观测（data observer），属性和方法的运算，watch/event 事件回调。然而，挂载阶段还没开始，<code>$el</code> 属性目前不可见。</li>
 <li><strong>beforeMount</strong>：在挂载开始之前被调用：相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。</li>
 <li><strong>mounted</strong>：el 被新创建的 vm.el替换，并挂载到实例上去之后调用该钩子。如果根实例挂载了一个文档内元素，当mounted被调用时vm.el 也在文档内。</li>
 <li><strong>beforeUpdate</strong>：数据更新时调用，发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM，比如手动移除已添加的事件监听器。</li>
 <li><strong>updated</strong>：由于数据更改导致的虚拟 DOM 重新渲染和打补丁，在这之后会调用这个钩子。当这个钩子被调用时，组件 DOM 已经更新，所以现在可以执行依赖于 DOM 的操作。但是要避免更改状态，因为这可能会导致无限循环的更新。</li>
 <li><strong>beforeDestroy</strong>：实例销毁之前调用。在这一步，实例仍然完全可用。</li>
 <li><strong>destroyed</strong>：Vue 实例销毁后调用。调用后，Vue 实例指示的所有东西都会解绑定，所有的事件监听器会被移除，所有的子实例也会被销毁。</li>
</ul>
<p>Vue 3.x 中，<code>beforeDestroy</code> 和 <code>destroyed</code> 被重命名为 <code>beforeUnmount</code> 和 <code>unmounted</code>，以更好地反映它们的目的。</p>
<h4>小程序的生命周期</h4>
<p>小程序的生命周期主要包括页面生命周期和全局生命周期（如<code>App</code>的生命周期）。以微信小程序为例：</p>
<ul>
 <li><strong>页面生命周期</strong> ：</li>
 <li><code>onLoad</code>：页面加载时触发。一个页面只会调用一次，可以在 onLoad 的参数中获取打开当前页面路径中的参数。</li>
 <li><code>onShow</code>：页面显示/切入前台时触发。</li>
 <li><code>onReady</code>：页面初次渲染完成时触发。一个页面只会调用一次，代表页面已经准备妥当，可以和视图层进行交互。</li>
 <li><code>onHide</code>：页面隐藏/切入后台时触发。</li>
 <li><code>onUnload</code>：页面卸载时触发。如 redirectTo 或 navigateBack 到其他页面时。</li>
 <li><strong>全局生命周期</strong> （<code>App</code>）：</li>
 <li><code>onLaunch</code>：当小程序初始化完成时，会触发 onLaunch（全局只触发一次）。</li>
 <li><code>onShow</code>：当小程序启动，或从后台进入前台显示，会触发 onShow。</li>
 <li><code>onHide</code>：当小程序从前台进入后台，会触发 onHide。</li>
 <li><code>onError</code>：当小程序发生脚本错误，或者 api 调用失败时，会触发 onError 并带上错误信息。</li>
</ul>
<h4>Uni-app 的生命周期</h4>
<p>Uni-app 的生命周期同时包含了 Vue 和小程序的生命周期概念，因为它旨在编写一次代码，然后发布到多个平台（包括小程序、H5、App等）。Uni-app 的页面和组件生命周期与 Vue 非常相似，但请注意，Uni-app 还会根据目标平台（如微信小程序）调整或添加一些特定的生命周期钩子。</p>
<p>对于页面来说，Uni-app 支持 Vue 的生命周期钩子，如 <code>onLoad</code>、<code>onShow</code>、<code>onReady</code>、<code>onHide</code>、<code>onUnload</code> 等，并且在编译到小程序时，这些钩子会映射到对应小程序的生命周期方法上。</p>
<p>总的来说，虽然 Vue、小程序和 Uni-app 在生命周期的具体实现上有所不同，但它们都遵循了类似的模式，帮助开发者在组件或页面的不同阶段执行相应的代码。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141684727</p>]]></description><guid isPermaLink="false">/archives/4baf407c-df51-4a00-8a4f-351e324b1f67</guid><dc:creator>黄健</dc:creator><category>小程序</category><category>前端</category><pubDate>Thu, 26 Sep 2024 08:36:15 GMT</pubDate></item><item><title><![CDATA[深入认识微信小程序openid]]></title><link>https://wiki.dgfygk.com/archives/b4bc6003-45a5-4535-b87e-5c60ee2f2c2b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E8%AE%A4%E8%AF%86%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8Fopenid&amp;url=/archives/b4bc6003-45a5-4535-b87e-5c60ee2f2c2b" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序中的OpenID是一个重要的概念，它涉及到用户的身份验证、跨平台登录以及隐私保护等多个方面。以下是对微信小程序OpenID的深入认识：</p>
<h4>一、OpenID的定义与作用</h4>
<ul>
 <li><strong>定义</strong>：OpenID是一种开放标准的认证协议，在微信小程序中，它特指一个用户在微信平台上的唯一标识符。这个标识符是微信小程序用来区分不同用户的"身份证”。</li>
 <li><strong>作用</strong> ：</li>
 <li><strong>用户身份验证</strong>：通过OpenID，小程序可以验证用户的身份，确保用户的安全性和唯一性。</li>
 <li><strong>跨平台登录</strong>：用户可以使用OpenID在不同的微信小程序或相关应用中进行登录，无需重复创建新账户。</li>
 <li><strong>隐私保护</strong>：OpenID采用非实名制认证方式，有助于保护用户的隐私信息不被滥用。</li>
 <li><strong>提高用户体验</strong>：用户无需在小程序中重复输入个人信息，只需一次登录即可享受所有服务。</li>
 <li><strong>增强安全性</strong>：OpenID采用加密传输技术，保障用户信息的传输安全，防止被恶意攻击。</li>
</ul>
<h4>二、OpenID的获取方式</h4>
<p>在微信小程序中，OpenID的获取通常通过以下步骤进行：</p>
<ol>
 <li><strong>用户授权</strong>：用户在使用小程序时，需要先进行授权操作，同意小程序获取其基本信息。</li>
 <li><strong>调用wx.login接口</strong>：小程序开发者通过调用微信提供的wx.login接口，获取用户的临时登录凭证（code）。</li>
 <li><strong>服务器请求</strong>：开发者将获取的code发送到自己的服务器后台。</li>
 <li><strong>请求access_token</strong>：服务器后台再将code发送至微信服务器，请求换取用户的access_token。</li>
 <li><strong>获取OpenID</strong>：微信服务器验证access_token的有效性后，会返回用户的OpenID给开发者服务器。</li>
</ol>
<h4>三、OpenID的应用场景</h4>
<p>OpenID在微信小程序中有广泛的应用场景，包括但不限于：</p>
<ol>
 <li><strong>用户识别</strong>：作为用户在小程序中的唯一标识符，OpenID可用于区分不同的用户。</li>
 <li><strong>数据统计</strong>：开发者可以通过OpenID对用户行为进行统计，如访问页面、购买商品等，从而了解用户需求和行为习惯。</li>
 <li><strong>个性化推荐</strong>：根据用户的OpenID和行为数据，开发者可以为用户提供个性化的内容推荐，提高用户体验。</li>
 <li><strong>用户管理</strong>：开发者可以通过OpenID对用户进行管理，如发送通知、推送消息等，增强用户粘性和活跃度。</li>
 <li><strong>安全验证</strong>：在需要进行安全验证的操作中，如支付、数据签名等，OpenID可以作为用户身份的有效证明。</li>
</ol>
<h4>四、注意事项</h4>
<p>在使用OpenID时，开发者需要注意以下几点：</p>
<ol>
 <li><strong>用户授权</strong>：在获取用户信息和OpenID之前，必须先获得用户的明确授权。</li>
 <li><strong>隐私保护</strong>：OpenID是用户的敏感信息，需要进行妥善保护。在传输和存储过程中应采取加密等措施，防止信息泄露。</li>
 <li><strong>合规使用</strong>：开发者应遵循微信小程序平台的相关规定和法律法规要求，合规使用OpenID等用户数据。</li>
</ol>
<p>综上所述，微信小程序中的OpenID是一个重要的身份验证和用户管理工具。通过合理使用OpenID，开发者可以为用户提供更加便捷、安全和个性化的服务体验。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141434565</p>]]></description><guid isPermaLink="false">/archives/b4bc6003-45a5-4535-b87e-5c60ee2f2c2b</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 08:35:44 GMT</pubDate></item><item><title><![CDATA[微信小程序wxml与标准的html的异同]]></title><link>https://wiki.dgfygk.com/archives/d6db4ba4-0e62-4b6e-9445-e996acc96a9d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8Fwxml%E4%B8%8E%E6%A0%87%E5%87%86%E7%9A%84html%E7%9A%84%E5%BC%82%E5%90%8C&amp;url=/archives/d6db4ba4-0e62-4b6e-9445-e996acc96a9d" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序WXML（WeiXin Markup Language）与标准的HTML在构建前端界面时，既有相似之处，也存在明显的差异。以下是对两者异同点的详细分析：</p>
<h4>相似之处</h4>
<ol>
 <li><strong>基本语法结构</strong> ：</li>
 <li>两者都使用尖括号<code>&lt; &gt;</code>包裹标签，标签闭合通常使用尾部斜杠<code>/</code>或双标签闭合（如HTML中的<code>&lt;div&gt;&lt;/div&gt;</code>和WXML中的<code>&lt;view&gt;&lt;/view&gt;</code>）。</li>
 <li>两者都采用嵌套的标签结构来组织页面元素，形成页面的层次结构。</li>
 <li><strong>标签与元素</strong> ：</li>
 <li>两者都包含用于表示不同元素的标签，如文本、图片、链接等。虽然具体标签名称不同（如WXML中的<code>&lt;view&gt;</code>、<code>&lt;text&gt;</code>、<code>&lt;image&gt;</code>对应HTML中的<code>&lt;div&gt;</code>、<code>&lt;span&gt;</code>、<code>&lt;img&gt;</code>），但它们在页面中的功能和作用相似。</li>
 <li><strong>样式定义</strong> ：</li>
 <li>两者都支持通过样式表来定义页面的样式。WXML使用WXSS（微信小程序样式表），而HTML使用CSS。两者都支持类似的选择器和属性设置，用于控制元素的布局、颜色、字体等样式。</li>
</ol>
<h4>差异之处</h4>
<ol>
 <li><strong>标签与属性</strong> ：</li>
 <li>WXML的标签是为微信小程序定制的，包括视图容器、文本、图片、按钮等元素标签，其标签集合相对较小但可以通过自定义组件来扩展。而HTML的标签更加广泛，包含了丰富的元素标签如<code>div</code>、<code>p</code>、<code>span</code>、<code>img</code>、<code>a</code>等。</li>
 <li>WXML中多出了一些特定的属性及表达式，如<code>wx:if</code>、<code>wx:for</code>等，用于实现条件渲染和列表渲染等功能。这些属性在HTML中不存在。</li>
 <li><strong>开发环境与兼容性</strong> ：</li>
 <li>WXML是为微信小程序开发而设计的，只能在微信小程序开发工具中预览和调试，且具有良好的兼容性，可以在微信客户端上正常运行。</li>
 <li>HTML作为Web开发的标准语言，具有广泛的兼容性，可以在各种Web浏览器上正常运行，无需特定的开发环境。</li>
 <li><strong>数据绑定与MVVM模式</strong> ：</li>
 <li>WXML支持数据绑定和MVVM（Model-View-ViewModel）模式，通过数据绑定将界面与数据状态连接起来，实现界面与数据的同步更新。这种模式有助于减少DOM操作，提高开发效率。</li>
 <li>HTML本身不支持数据绑定和MVVM模式，通常需要通过JavaScript和DOM操作来实现界面与数据的交互。</li>
 <li><strong>性能优化</strong> ：</li>
 <li>WXML对组件进行了重新封装，为后续的性能优化提供了可能，同时避免开发者写出低质量的代码。此外，微信小程序还提供了丰富的性能优化工具和技术支持。</li>
 <li>HTML的性能优化则更多地依赖于开发者的经验和技能，包括减少HTTP请求、优化DOM操作、使用缓存等技术手段。</li>
</ol>
<p>综上所述，WXML与HTML在基本语法结构和标签与元素的使用上存在相似之处，但在标签与属性、开发环境与兼容性、数据绑定与MVVM模式以及性能优化等方面存在明显的差异。开发者需要根据具体的开发平台和需求选择使用适当的标记语言。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141434849</p>]]></description><guid isPermaLink="false">/archives/d6db4ba4-0e62-4b6e-9445-e996acc96a9d</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 08:35:21 GMT</pubDate></item><item><title><![CDATA[微信小程序详细登录流程]]></title><link>https://wiki.dgfygk.com/archives/5ca473d3-6353-423f-9ddc-5ec5077915f5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E8%AF%A6%E7%BB%86%E7%99%BB%E5%BD%95%E6%B5%81%E7%A8%8B&amp;url=/archives/5ca473d3-6353-423f-9ddc-5ec5077915f5" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序的详细登录流程主要包括以下几个步骤：</p>
<h4>一、准备工作</h4>
<ol>
 <li><strong>确保微信客户端已安装并登录</strong>：用户需要确保手机上已经安装了微信客户端，并且微信账号处于正常登录状态。</li>
 <li><strong>寻找小程序入口</strong>：用户可以通过微信聊天界面、公众号文章、朋友圈链接、小程序列表或搜索功能找到并点击进入目标小程序。</li>
</ol>
<h4>二、用户授权</h4>
<ol>
 <li><strong>扫码或点击使用</strong>：对于需要通过扫码登录的小程序，用户点击小程序内的登录按钮后，小程序会生成一个二维码。此时，用户需要使用已登录的微信账号扫描该二维码。如果是直接点击使用的小程序，可能会直接跳转到授权页面。</li>
 <li><strong>授权页面</strong>：用户扫描二维码或点击使用后，小程序会请求用户进行授权，授权内容通常包括昵称、头像等基本信息。用户需要点击"允许"或"确认"按钮进行授权。</li>
</ol>
<h4>三、登录流程</h4>
<ol>
 <li><strong>获取临时登录凭证（code）</strong> ：</li>
 <li>用户在小程序端进行登录操作时，小程序会调用微信提供的<code>wx.login</code>接口，获取一个临时登录凭证（code）。这个code是一次性的，并且有时效性。</li>
 <li><strong>发送code到开发者服务器</strong> ：</li>
 <li>小程序前端将获取到的code通过HTTP请求发送到开发者服务器。</li>
 <li><strong>服务器请求session_key和openid</strong> ：</li>
 <li>开发者服务器使用code、小程序的appid和appsecret（小程序的管理后台可获取）向微信服务器发送请求，通过调用微信提供的<code>auth.code2Session</code>接口，换取用户的session_key和openid。openid是用户在当前小程序的唯一标识。</li>
 <li><strong>生成自定义登录态</strong> ：</li>
 <li>开发者服务器根据获取到的session_key和openid，可以生成自定义的登录态（如JWT token），这个登录态将用于后续请求的身份验证。</li>
 <li><strong>返回登录态给前端</strong> ：</li>
 <li>开发者服务器将生成的自定义登录态返回给小程序前端。</li>
 <li><strong>前端保存登录态</strong> ：</li>
 <li>小程序前端接收到登录态后，将其保存在本地存储（如使用<code>wx.setStorageSync</code>方法），以便后续请求时进行身份验证。</li>
</ol>
<h4>四、登录成功</h4>
<ul>
 <li>完成以上步骤后，用户即成功登录小程序。此时，小程序可以根据用户的登录信息，为用户提供个性化的服务，如展示用户数据、记录用户行为等。</li>
</ul>
<h4>五、注意事项</h4>
<ul>
 <li>在整个登录流程中，确保数据传输的安全性是非常重要的。特别是code、appid、appsecret、session_key等敏感信息，在传输过程中应进行加密处理，防止被截获或篡改。</li>
 <li>微信小程序登录流程的实现涉及到前端和后端的配合，前端主要负责获取code和发送请求，后端则负责处理请求、生成登录态并返回给前端。</li>
 <li>用户在小程序中的登录状态通常是临时的，即当用户关闭小程序或长时间未使用时，登录态可能会失效。此时，用户再次进入小程序需要重新进行登录流程。</li>
</ul>
<p>以上即为微信小程序详细的登录流程。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141434607</p>]]></description><guid isPermaLink="false">/archives/5ca473d3-6353-423f-9ddc-5ec5077915f5</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 08:35:05 GMT</pubDate></item><item><title><![CDATA[微信小程序应用的生命周期：app.js]]></title><link>https://wiki.dgfygk.com/archives/c8397850-0a06-4483-a0ec-8a48958098b8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BA%94%E7%94%A8%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%EF%BC%9Aapp.js&amp;url=/archives/c8397850-0a06-4483-a0ec-8a48958098b8" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序的应用生命周期主要指的是小程序从启动到销毁的整个过程，以及在这个过程中小程序所经历的不同阶段。在微信小程序中，<code>app.js</code> 文件是全局的 JavaScript 脚本文件，用于定义小程序的生命周期函数、全局变量等。</p>
<h4>小程序的生命周期函数（在 <code>app.js</code> 中定义）</h4>
<ol>
 <li><strong>onLaunch(Object object)</strong></li>
 <li>小程序初始化完成时触发（全局只触发一次）。</li>
 <li>参数 <code>object</code> 可以获取到启动小程序的场景值，例如是从哪个页面进入小程序的。</li>
 <li><strong>onShow(Object object)</strong></li>
 <li>小程序启动，或从后台进入前台显示时触发。</li>
 <li>也可以使用 <code>getApp().onShow</code> 在 <code>App</code> 实例上监听小程序显示。</li>
 <li><strong>onHide()</strong></li>
 <li>小程序从前台进入后台时触发。</li>
 <li>也可以使用 <code>getApp().onHide</code> 在 <code>App</code> 实例上监听小程序隐藏。</li>
 <li><strong>onError(String error)</strong></li>
 <li>小程序发生脚本错误，或者 api 调用失败时触发。</li>
 <li>可以在这里捕获到小程序的错误信息，并进行上报或处理。</li>
 <li><strong>onPageNotFound(Object object)</strong></li>
 <li>小程序要打开的页面不存在时触发。</li>
 <li>可以在这里进行页面重定向、错误提示等操作。</li>
</ol>
<h4>示例 <code>app.js</code></h4>
<pre><code class="hljs">// app.js  
App({  
  onLaunch: function (options) {  
    // 小程序启动之后 触发  
    console.log('小程序启动', options.query);  
    // 可以在这里进行全局变量的初始化、登录状态的校验等  
  },  
  onShow: function (options) {  
    // 小程序显示之后触发  
    console.log('小程序显示');  
  },  
  onHide: function () {  
    // 小程序隐藏之后触发  
    console.log('小程序隐藏');  
  },  
  onError: function (msg) {  
    // 小程序发生错误时触发  
    console.error('小程序发生错误：', msg);  
  },  
  onPageNotFound: function (res) {  
    // 页面不存在时触发  
    wx.redirectTo({  
      url: '/pages/index/index' // 跳转到首页  
    })  
  }  
})
</code></pre>
<h4>注意事项</h4>
<ul>
 <li>小程序的生命周期函数在 <code>App()</code> 函数中定义，这些函数会在小程序的不同阶段被自动调用。</li>
 <li>开发者可以在这些生命周期函数中执行相应的逻辑，如初始化数据、监听页面变化、处理错误等。</li>
 <li>需要注意的是，<code>onLaunch</code> 只会在小程序初始化时调用一次，而 <code>onShow</code> 和 <code>onHide</code> 则会在小程序显示和隐藏时多次调用。</li>
 <li><code>onError</code> 函数可以捕获到小程序运行时的错误信息，这对于调试和错误监控非常有用。</li>
 <li><code>onPageNotFound</code> 函数可以在用户尝试访问不存在的页面时，进行页面重定向或显示错误提示，提升用户体验。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141434655</p>]]></description><guid isPermaLink="false">/archives/c8397850-0a06-4483-a0ec-8a48958098b8</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 08:34:25 GMT</pubDate></item><item><title><![CDATA[微信小程序优缺点]]></title><link>https://wiki.dgfygk.com/archives/8f707ecd-81e6-4e70-8780-339fb3d819cb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BC%98%E7%BC%BA%E7%82%B9&amp;url=/archives/8f707ecd-81e6-4e70-8780-339fb3d819cb" width="1" height="1" alt="" style="opacity:0;">
<p>微信小程序的优缺点可以归纳如下：</p>
<h4>优点</h4>
<ol>
 <li><strong>无需安装与卸载</strong>：用户无需下载和安装即可使用，节省了手机内存，同时也避免了因安装过多应用而导致的手机卡顿问题。</li>
 <li><strong>跨平台使用</strong>：微信小程序支持在iOS、Android等不同操作系统上运行，开发者可以更加灵活地覆盖不同的用户群体。</li>
 <li><strong>轻量化设计</strong>：小程序专注于提供核心服务，界面简洁，功能直接，使用户能够更快地找到所需内容，提高了使用效率。</li>
 <li><strong>社群化传播</strong>：与微信社交功能紧密结合，用户可以通过微信好友、群组等分享小程序，从而扩大其传播范围，增强社交互动性。</li>
 <li><strong>场景化服务</strong>：可以针对不同的场景提供相应的服务，如旅游、阅读、购物等，满足用户在各种场景下的需求。</li>
 <li><strong>低成本高效益</strong>：与原生APP相比，微信小程序的开发成本更低，周期更短，同时能够借助微信的巨大用户基础，实现快速推广和盈利。</li>
 <li><strong>用户粘性强</strong>：用户可以将常用的小程序添加到微信首页，方便随时访问，提高了用户粘性和活跃度。</li>
 <li><strong>数据分析能力强</strong>：企业可以通过小程序获取到用户的行为数据，帮助企业更好地了解用户需求和行为习惯，优化产品和服务。</li>
</ol>
<h4>缺点</h4>
<ol>
 <li><strong>功能限制</strong>：由于微信小程序的轻量化和跨平台特性，其功能相对较为限制。与原生应用程序相比，微信小程序在某些复杂功能和性能方面可能存在差距。</li>
 <li><strong>用户体验差异</strong>：虽然微信小程序在用户体验上有所优化，但部分用户可能会觉得其界面不够直观、操作不够流畅等，与原生应用程序相比存在一定差距。</li>
 <li><strong>信息泄露风险</strong>：由于微信小程序与社交功能紧密结合，用户在使用过程中可能会担心个人信息泄露的问题。虽然微信已经采取了一些措施来保护用户隐私，但仍然存在一定的风险。</li>
 <li><strong>竞争激烈</strong>：由于微信小程序的门槛相对较低，竞争异常激烈。许多开发者都在开发类似的小程序，导致市场上充斥着大量同质化的产品。因此，要想在众多竞争对手中脱颖而出，需要付出更多的努力和创意。</li>
 <li><strong>iOS端支付限制</strong>：iOS端不支持虚拟支付，这导致一些知识付费等应用无法上线，影响了小程序的全面应用。</li>
 <li><strong>审核主观性</strong>：小程序审核过程中主观因素占比较大，相同内容和功能的小程序可能因审核人员不同而得到不同的结果。</li>
 <li><strong>私域工具属性</strong>：小程序本质上还是私域工具，自身不带流量，需要开发者自行推广和引流。</li>
 <li><strong>用户留存和唤醒难题</strong>：小程序主张"用完即走”，但用户的留存和唤醒是两个难题。订阅消息的机制不允许小程序反复发送消息提醒，这增加了唤醒用户的难度。</li>
 <li><strong>技术支持限制</strong>：有些在H5中已经比较成熟的技术，在小程序中支持还不是太好。例如，小程序对WebGL的支持还不是很完善，限制了部分图形渲染和交互功能的应用。</li>
</ol>
<p>综上所述，微信小程序在提供便捷、高效服务的同时，也存在一些不容忽视的缺点。开发者在选择使用微信小程序时，需要根据自身的需求和实际情况进行权衡和选择。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141434530</p>]]></description><guid isPermaLink="false">/archives/8f707ecd-81e6-4e70-8780-339fb3d819cb</guid><dc:creator>黄健</dc:creator><category>小程序</category><pubDate>Thu, 26 Sep 2024 08:34:00 GMT</pubDate></item><item><title><![CDATA[对react模块和模块化理解]]></title><link>https://wiki.dgfygk.com/archives/efb73d67-53e2-4ccf-8d8e-5770abc00b01</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AF%B9react%E6%A8%A1%E5%9D%97%E5%92%8C%E6%A8%A1%E5%9D%97%E5%8C%96%E7%90%86%E8%A7%A3&amp;url=/archives/efb73d67-53e2-4ccf-8d8e-5770abc00b01" width="1" height="1" alt="" style="opacity:0;">
<p>在React开发中，模块化和React模块是两个紧密相关但又有区别的概念。理解它们对于构建高效、可维护的React应用至关重要。</p>
<h4>模块化</h4>
<p>模块化是一种将大型代码库拆分成更小、更易于管理的部分（即模块）的软件设计技术。每个模块都封装了特定的功能或数据，并且可以通过定义好的接口与其他模块进行交互。模块化有助于减少代码间的耦合，提高代码的重用性，同时也使得代码更加易于测试和维护。</p>
<p>在JavaScript中，模块化可以通过多种方式实现，包括但不限于：</p>
<ul>
 <li><strong>CommonJS</strong> ：主要用于Node.js环境，通过<code>require</code>和<code>module.exports</code>实现模块的导入和导出。</li>
 <li><strong>ES Modules（ESM）</strong> ：ECMAScript 2015（ES6）引入的官方模块化标准，使用<code>import</code>和<code>export</code>语法。</li>
 <li><strong>AMD（异步模块定义）</strong>：如RequireJS，允许异步加载模块。</li>
 <li><strong>UMD（通用模块定义）</strong>：旨在兼容CommonJS、AMD和全局变量（无模块环境）的模块定义方式。</li>
</ul>
<h4>React模块</h4>
<p>React模块通常指的是在React应用中定义的组件或库。React组件是React应用的基本构建块，它们可以是类组件或函数组件，用于封装UI的某个部分，并可能包含状态（state）和生命周期方法（对于类组件）或Hooks（对于函数组件）。</p>
<p>React模块化的好处包括：</p>
<ul>
 <li><strong>组件化</strong>：将UI拆分成独立的、可复用的组件，提高了代码的可维护性和可重用性。</li>
 <li><strong>关注点分离</strong>：每个组件只关注自己的逻辑和UI，使得代码更加清晰和易于理解。</li>
 <li><strong>易于测试</strong>：由于组件是独立的，因此可以单独对它们进行测试，而无需考虑整个应用的上下文。</li>
</ul>
<h4>React与模块化的结合</h4>
<p>在React应用中，通常会使用ES Modules（或CommonJS，取决于项目配置）来组织和管理React组件。每个组件都被视为一个模块，可以通过<code>import</code>语句从其他文件中导入，并通过<code>export</code>语句导出以便在其他地方使用。</p>
<p>此外，React社区还提供了许多第三方库和工具，如Redux、React Router等，它们也是以模块化的形式提供的，可以轻松地集成到React应用中，以提供额外的功能，如状态管理和路由管理。</p>
<p>总之，模块化和React模块是构建现代React应用不可或缺的部分。通过合理地组织和管理模块，可以创建出高效、可维护且可扩展的React应用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141232388</p>]]></description><guid isPermaLink="false">/archives/efb73d67-53e2-4ccf-8d8e-5770abc00b01</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:33:48 GMT</pubDate></item><item><title><![CDATA[详细介绍一下vuex]]></title><link>https://wiki.dgfygk.com/archives/8a0904aa-64f4-4a5b-9e7f-0a8063b254d3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E4%BB%8B%E7%BB%8D%E4%B8%80%E4%B8%8Bvuex&amp;url=/archives/8a0904aa-64f4-4a5b-9e7f-0a8063b254d3" width="1" height="1" alt="" style="opacity:0;">
<p>Vuex是一个专为Vue.js应用程序开发的状态管理模式和库。它采用集中式存储管理应用的所有组件的状态，并以相应的规则保证状态以一种可预测的方式发生变化。以下是关于Vuex的详细介绍：</p>
<h4>一、Vuex的基本概念</h4>
<p>Vuex核心概念包括五个部分：state、getters、mutations、actions和modules。</p>
<ol>
 <li>
  <p><strong>state</strong>：用于存储应用层级的状态，即数据。它是唯一的，类似于一个全局变量，但Vuex的状态是响应式的，当状态发生变化时，视图会自动更新。</p>
 </li>
 <li>
  <p><strong>getters</strong>：类似于Vue的计算属性，用于从state中派生出一些状态。getters的返回值会根据它的依赖被缓存起来，只有getter中的依赖值发生改变的时候才会被重新计算。</p>
 </li>
 <li>
  <p><strong>mutations</strong>：是更改Vuex的store中状态的唯一方法。每个mutation都有一个字符串类型的事件类型和一个回调函数，回调函数就是实际进行状态更新的地方，并且它会接收state作为第一个参数。</p>
 </li>
 <li>
  <p><strong>actions</strong>：类似于mutations，不同在于actions提交的是mutations，而不是直接变更状态。actions可以包含任意异步操作。</p>
 </li>
 <li>
  <p><strong>modules</strong>：由于使用单一状态树，应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时，store对象就有可能变得相当臃肿。为了解决这个问题，Vuex允许我们将store分割成模块（module）。每个模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。</p>
 </li>
</ol>
<h4>二、Vuex的优点</h4>
<ol>
 <li>
  <p><strong>集中管理状态</strong>：Vuex将应用的所有状态存储在一个全局的store中，便于管理和维护。</p>
 </li>
 <li>
  <p><strong>可预测的状态变更</strong>：由于状态的变更只能通过mutation进行，且mutation必须是同步函数，这使得状态的变更变得可预测。</p>
 </li>
 <li>
  <p><strong>组件解耦</strong>：Vuex使得组件之间的通信变得简单，组件不再需要直接通过props和$emit进行通信，而是通过访问全局的store来实现数据的共享和通信。</p>
 </li>
 <li>
  <p><strong>易于调试</strong>：Vuex提供了严格的模式（strict mode），在开发模式下，如果不是通过mutation来修改状态，则会抛出错误，这有助于开发者更容易地捕获状态管理中的错误。</p>
 </li>
 <li>
  <p><strong>插件化</strong>：Vuex支持插件化，开发者可以编写自己的插件来扩展Vuex的功能。</p>
 </li>
</ol>
<h4>三、Vuex的使用</h4>
<ol>
 <li>
  <p><strong>安装Vuex</strong>：如果项目是通过Vue CLI创建的，可以在创建项目时选择Vuex进行安装。如果项目已经存在，可以通过npm或yarn来安装Vuex。</p>
 </li>
 <li>
  <p><strong>创建store</strong>：在项目的src目录下创建一个store目录，并在其中创建index.js文件。在index.js文件中，引入Vue和Vuex，并使用Vue.use(Vuex)来安装Vuex。然后，创建一个新的Vuex.Store实例，并传入state、getters、mutations、actions和modules等选项。</p>
 </li>
 <li>
  <p><strong>在Vue组件中使用Vuex</strong>：在Vue组件中，可以通过this.$store来访问store实例，进而访问state、getters、提交mutations或分发actions。</p>
 </li>
</ol>
<h4>四、Vuex的适用场景</h4>
<p>Vuex适用于复杂的应用场景，特别是当应用中存在多个组件需要共享状态时。对于简单的应用，可能不需要使用Vuex，因为Vuex会增加项目的复杂度。但是，对于大型应用来说，Vuex可以极大地提高开发效率和可维护性。</p>
<p>总之，Vuex是Vue.js应用程序开发中不可或缺的状态管理库，它通过集中式存储和严格的规则来保证状态的可预测性和可维护性。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141285136</p>]]></description><guid isPermaLink="false">/archives/8a0904aa-64f4-4a5b-9e7f-0a8063b254d3</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:30:01 GMT</pubDate></item><item><title><![CDATA[axios学习]]></title><link>https://wiki.dgfygk.com/archives/13878937-d550-4a7f-bdd5-6c221cde603a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=axios%E5%AD%A6%E4%B9%A0&amp;url=/archives/13878937-d550-4a7f-bdd5-6c221cde603a" width="1" height="1" alt="" style="opacity:0;">
<p>Axios 是一个基于 Promise 的 HTTP 客户端，用于浏览器和 node.js。它提供了一种简单的方法来发送异步 HTTP 请求到 REST 端点并处理响应。以下是 Axios 学习的详细指南：</p>
<h4>一、Axios 的介绍</h4>
<h5>1. 定义与原理</h5>
<ul>
 <li><strong>定义</strong>：Axios 是一个基于 Promise 的 HTTP 客户端，用于浏览器和 node.js。</li>
 <li><strong>原理</strong>：Axios 在浏览器端使用 XMLHttpRequests，在 node.js 中使用 http 模块进行通信。它封装了这些底层技术，提供了更加易用和灵活的 API。</li>
</ul>
<h5>2. 特性</h5>
<ul>
 <li>支持 Promise API</li>
 <li>拦截请求和响应</li>
 <li>转换请求和响应数据</li>
 <li>取消请求</li>
 <li>自动转换 JSON 数据</li>
 <li>客户端支持防御 XSRF（跨站请求伪造）</li>
</ul>
<h5>3. 浏览器支持</h5>
<p>Axios 支持所有现代浏览器，包括 Firefox、Chrome、Safari、Opera、Edge 以及 IE8+。</p>
<h4>二、安装 Axios</h4>
<p>Axios 可以通过多种方式安装，包括 npm、bower、yarn 以及通过 CDN 引入。</p>
<ul>
 <li><strong>npm 安装</strong> ：<code>npm install axios</code></li>
 <li><strong>bower 安装</strong> ：<code>bower install axios</code></li>
 <li><strong>yarn 安装</strong> ：<code>yarn add axios</code></li>
 <li><strong>CDN 引入</strong> ：</li>
 <li><code>&lt;script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"&gt;&lt;/script&gt;</code></li>
 <li><code>&lt;script src="https://unpkg.com/axios/dist/axios.min.js"&gt;&lt;/script&gt;</code></li>
</ul>
<h4>三、Axios 的基本用法</h4>
<h5>1. 引入 Axios</h5>
<p>在需要使用 Axios 的文件中，通过 <code>import</code> 或 <code>require</code> 引入 Axios。</p>
<pre><code class="hljs">import axios from 'axios';  
// 或者  
const axios = require('axios');
</code></pre>
<h5>2. 发送 GET 请求</h5>
<pre><code class="hljs">axios.get('/user?ID=12345')  
  .then(function (response) {  
    // 处理成功情况  
    console.log(response);  
  })  
  .catch(function (error) {  
    // 处理错误情况  
    console.log(error);  
  })  
  .then(function () {  
    // 总是会执行  
  });  

// 或者使用 async/await  
async function getUser() {  
  try {  
    const response = await axios.get('/user?ID=12345');  
    console.log(response.data);  
  } catch (error) {  
    console.error(error);  
  }  
}  

getUser();
</code></pre>
<h5>3. 发送 POST 请求</h5>
<pre><code class="hljs">axios.post('/user', {  
    firstName: 'Fred',  
    lastName: 'Flintstone'  
  })  
  .then(function (response) {  
    console.log(response);  
  })  
  .catch(function (error) {  
    console.log(error);  
  });  

// 或者使用 async/await  
async function createUser() {  
  try {  
    const response = await axios.post('/user', {  
      firstName: 'Fred',  
      lastName: 'Flintstone'  
    });  
    console.log(response.data);  
  } catch (error) {  
    console.error(error);  
  }  
}  

createUser();
</code></pre>
<h5>4. 发送其他类型的请求（PUT、DELETE 等）</h5>
<pre><code class="hljs">axios.put('/user/12345', {  
  firstName: 'Fred',  
  lastName: 'Flintstone'  
})  
.then(function (response) {  
  console.log(response);  
})  
.catch(function (error) {  
  console.log(error);  
});  

axios.delete('/user/12345')  
.then(function (response) {  
  console.log(response);  
})  
.catch(function (error) {  
  console.log(error);  
});
</code></pre>
<h4>欢迎欢迎！！！</h4>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141071862</p>]]></description><guid isPermaLink="false">/archives/13878937-d550-4a7f-bdd5-6c221cde603a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:29:40 GMT</pubDate></item><item><title><![CDATA[Pinia 的使用场景有哪些]]></title><link>https://wiki.dgfygk.com/archives/5e6270de-a852-4131-b74b-06b844f3700e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Pinia%20%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E6%9C%89%E5%93%AA%E4%BA%9B&amp;url=/archives/5e6270de-a852-4131-b74b-06b844f3700e" width="1" height="1" alt="" style="opacity:0;">
<p>Pinia 的使用场景主要集中在 Vue.js 应用程序中，特别是那些需要有效管理复杂状态的大型或中型应用程序。以下是 Pinia 的几个主要使用场景：</p>
<h4>1. 状态共享</h4>
<ul>
 <li><strong>跨组件状态共享</strong> ：在 Vue 应用程序中，经常需要在多个组件之间共享状态。Pinia 提供了一个集中的位置来存储和管理这些状态，使得跨组件的状态共享变得简单直接。例如，用户信息、应用主题、当前登录状态等全局状态，都可以存储在 Pinia store 中，并在需要的地方通过 <code>useStore</code> 函数进行访问和修改。</li>
</ul>
<h4>2. 状态管理</h4>
<ul>
 <li><strong>状态集中管理</strong>：Pinia 允许开发者将应用程序的状态存储在一个集中的位置，并通过 Vue 组件进行访问和修改。这种集中管理的方式有助于减少组件之间的直接依赖，提高代码的可维护性和可扩展性。</li>
 <li><strong>模块化状态管理</strong>：Pinia 支持将状态划分为不同的模块（store），每个模块对应一个特定的功能或数据领域。这种模块化的设计方式使得状态管理更加清晰和有序，同时也便于代码的复用和维护。</li>
</ul>
<h4>3. 复杂逻辑处理</h4>
<ul>
 <li><strong>异步操作处理</strong>：Pinia 支持在 actions 中执行异步操作，如发送网络请求、处理副作用等。这使得 Pinia 能够处理更复杂的逻辑，如用户登录、数据加载等场景。</li>
 <li><strong>状态变更追踪</strong>：Pinia 使用 Vue 3 的响应性系统，确保状态的变化能够自动追踪和响应。这使得开发者能够更容易地追踪状态的变化，并在需要时做出响应。</li>
</ul>
<h4>4. 调试和监控</h4>
<ul>
 <li><strong>支持 Devtools</strong>：Pinia 支持 Vue Devtools，这是一个浏览器扩展，允许开发者在浏览器中直接查看和调试 Vue 应用程序的状态。通过 Vue Devtools，开发者可以方便地查看 Pinia store 中的状态、getters 和 actions，以及触发状态变更的操作。</li>
</ul>
<h4>5. 性能优化</h4>
<ul>
 <li><strong>缓存和持久化</strong>：Pinia 支持缓存状态数据，以提高性能。同时，也支持将状态数据持久化到本地存储或后端数据库中，以便在应用程序重启后恢复状态。</li>
</ul>
<h4>总结</h4>
<p>Pinia 的使用场景广泛，涵盖了 Vue.js 应用程序中的状态共享、状态管理、复杂逻辑处理、调试和监控以及性能优化等多个方面。通过 Pinia，开发者可以更加高效、有序地管理应用程序的状态，提高代码的可维护性和可扩展性。无论是在大型应用程序中处理复杂的业务逻辑，还是在小型应用程序中简化状态管理，Pinia 都是一个值得推荐的选择。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141106606</p>]]></description><guid isPermaLink="false">/archives/5e6270de-a852-4131-b74b-06b844f3700e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:29:05 GMT</pubDate></item><item><title><![CDATA[pinia简介]]></title><link>https://wiki.dgfygk.com/archives/c07b66c2-c4ed-497b-95e6-34266e9eb9f7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=pinia%E7%AE%80%E4%BB%8B&amp;url=/archives/c07b66c2-c4ed-497b-95e6-34266e9eb9f7" width="1" height="1" alt="" style="opacity:0;">
<p>Pinia 是一个专为 Vue 应用程序开发的状态管理库，特别是针对 Vue 3 设计，但也支持 Vue 2。以下是 Pinia 的详细简介：</p>
<h4>一、基本概念</h4>
<ul>
 <li><strong>定义</strong>：Pinia 提供了简单、直观且可扩展的方式来组织和访问 Vue 应用程序的状态。</li>
 <li><strong>目标</strong>：Pinia 旨在提供一种比 Vuex 更轻量级、更易于使用和更灵活的状态管理解决方案，同时保留 Vuex 的主要功能。</li>
</ul>
<h4>二、主要特点</h4>
<ol>
 <li><strong>轻量级与简洁性</strong> ：</li>
 <li>Pinia 的 API 设计简洁明了，抛弃了 Vuex 中的 mutations，只保留了 state、getters 和 actions，使得代码编写更加容易和直观。</li>
 <li>相较于 Vuex，Pinia 的体积更小，性能更好，适用于大型应用程序。</li>
 <li><strong>支持 Vue 2 和 Vue 3</strong> ：</li>
 <li>Pinia 同时支持 Vue 2 和 Vue 3，使得开发者可以在不同版本的 Vue 项目中使用相同的状态管理库。</li>
 <li><strong>TypeScript 支持</strong> ：</li>
 <li>Pinia 提供了完整的 TypeScript 支持，允许开发者在编写代码时获得类型提示和自动补全，提高代码的质量和可维护性。</li>
 <li><strong>与 Vue 3 的组合式 API 无缝集成</strong> ：</li>
 <li>Pinia 能够与 Vue 3 的组合式 API 无缝集成，使得状态管理代码更加清晰和模块化。</li>
 <li><strong>灵活的插件系统</strong> ：</li>
 <li>Pinia 提供了一个插件系统，允许开发者通过插件来扩展其功能或改变其行为，例如添加中间件、持久化存储、调试工具等。</li>
 <li><strong>易于调试</strong> ：</li>
 <li>Pinia 支持 Devtools 调试工具，便于开发者进行状态跟踪和调试。</li>
 <li><strong>懒加载</strong> ：</li>
 <li>Pinia 可以根据需要动态地加载状态，使得应用程序的初始加载速度更快。</li>
</ol>
<h4>三、核心概念</h4>
<ul>
 <li><strong>State</strong>：状态是你在 store 中存储的数据。每个 Pinia store 都有自己的状态，这个状态是一个 JavaScript 对象。</li>
 <li><strong>Getters</strong>：用于获取数据之前进行再次编译，从而得到新的数据，类似于 Vue 中的 computed 属性。</li>
 <li><strong>Actions</strong>：用于定义事件处理方法，可以进行同步或异步操作。</li>
</ul>
<h4>四、使用方式</h4>
<ol>
 <li><strong>安装</strong> ：</li>
 <li>使用 npm 或 yarn 安装 Pinia，例如：<code>npm install pinia</code> 或 <code>yarn add pinia</code>。</li>
 <li><strong>创建 Pinia 实例</strong> ：</li>
 <li>在 Vue 应用的入口文件中（如 <code>main.js</code> 或 <code>main.ts</code>），创建并挂载 Pinia 实例。</li>
 <li><strong>定义 Store</strong> ：</li>
 <li>使用 <code>defineStore</code> 函数来定义一个或多个 store，每个 store 包含状态、getters 和 actions。</li>
 <li><strong>在组件中使用 Store</strong> ：</li>
 <li>在 Vue 组件中，使用 <code>useStore</code> 函数来访问 store 的实例，并获取或修改状态。</li>
</ol>
<h4>五、总结</h4>
<p>Pinia 是一个强大而灵活的状态管理库，它为 Vue 应用程序提供了简单、直观且可扩展的状态管理解决方案。通过 Pinia，开发者可以更加轻松和高效地管理应用程序的状态，提高开发效率和代码质量。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141106305</p>]]></description><guid isPermaLink="false">/archives/c07b66c2-c4ed-497b-95e6-34266e9eb9f7</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:28:48 GMT</pubDate></item><item><title><![CDATA[react事件处理&表单]]></title><link>https://wiki.dgfygk.com/archives/d3eeeef5-7641-42e9-8129-93bb539f05d2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=react%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%26%E8%A1%A8%E5%8D%95&amp;url=/archives/d3eeeef5-7641-42e9-8129-93bb539f05d2" width="1" height="1" alt="" style="opacity:0;">
<p>在React中，事件处理和表单处理是两个核心概念，它们共同支持着React应用的交互性和动态性。以下是对这两个方面的详细阐述：</p>
<h4>React事件处理</h4>
<p>React中的事件处理与原生JavaScript中的事件处理相似，但有一些关键的区别和特定的实践方式。</p>
<h5>事件绑定</h5>
<p>在React中，事件绑定是通过在JSX元素上设置事件处理函数来实现的。事件处理函数的名称使用驼峰命名法（如<code>onClick</code>而不是<code>onclick</code>）。React并不将事件处理函数直接绑定到DOM元素上，而是采用了一种称为"事件委托"的技术，将事件监听器绑定到DOM树的顶层，然后根据事件的目标元素来触发相应的事件处理函数。</p>
<h5>事件处理函数</h5>
<p>事件处理函数可以是类组件中的方法，也可以是函数组件中定义的函数。在类组件中，如果事件处理函数需要访问组件的实例（如<code>this.state</code>），则必须确保<code>this</code>的指向正确。这通常通过在构造函数中使用<code>.bind(this)</code>来绑定，或者使用箭头函数来自动绑定<code>this</code>。</p>
<h5>事件对象</h5>
<p>React为事件处理函数提供了一个合成事件对象（SyntheticEvent），这个对象是对浏览器原生事件对象的封装，提供了跨浏览器的兼容性。与原生事件对象类似，合成事件对象也提供了<code>preventDefault</code>、<code>stopPropagation</code>等方法，以及<code>target</code>、<code>currentTarget</code>等属性。如果需要访问原生事件对象，可以通过<code>nativeEvent</code>属性来获取。</p>
<h5>事件绑定方式</h5>
<p>React中事件绑定的方式主要有以下几种：</p>
<ol>
 <li><strong>在render方法中使用箭头函数</strong>：这种方式简单直接，但每次组件渲染时都会创建一个新的函数实例，可能会导致性能问题。</li>
 <li><strong>在构造函数中使用<code>.bind(this)</code></strong>：这种方式在组件实例化时绑定事件处理函数，避免了在render方法中创建新函数的问题。</li>
 <li><strong>类属性箭头函数</strong> ：在类组件中，可以直接将事件处理函数定义为类属性，并使用箭头函数来自动绑定<code>this</code>。这种方式既简洁又避免了性能问题。</li>
 <li><strong>使用<code>bind</code>在JSX中直接绑定</strong> ：虽然不推荐，但也可以在JSX中直接使用<code>.bind(this)</code>来绑定事件处理函数。然而，这同样会导致每次渲染时都创建新的函数实例。</li>
</ol>
<h4>React表单处理</h4>
<p>在React中，表单处理通常涉及到状态（state）的更新和事件处理函数的编写。</p>
<h5>受控组件</h5>
<p>在React中，表单元素（如<code>&lt;input&gt;</code>、<code>&lt;textarea&gt;</code>、<code>&lt;select&gt;</code>）可以通过将它们的值设置为组件的state来创建受控组件。这意味着React将负责渲染表单元素的值，并在表单元素的值发生变化时更新state。这种方式使得表单元素的值始终与组件的state保持一致。</p>
<h5>非受控组件</h5>
<p>与受控组件相对的是非受控组件，它们通常用于简单的表单场景，其中表单元素的值不直接由React的state控制。然而，在React中，更推荐使用受控组件来处理表单，因为它们提供了更好的灵活性和可维护性。</p>
<h5>表单事件处理</h5>
<p>在React中，表单事件（如<code>onChange</code>、<code>onSubmit</code>）的处理方式与普通事件类似。你可以在表单元素上设置事件处理函数，并在函数中更新组件的state或执行其他逻辑。例如，在<code>&lt;input&gt;</code>元素上设置<code>onChange</code>事件处理函数，并在函数中更新存储输入值的state。</p>
<h5>表单提交</h5>
<p>对于表单提交（<code>onSubmit</code>事件），你可以在表单元素上设置<code>onSubmit</code>事件处理函数，并在函数中阻止表单的默认提交行为（使用<code>event.preventDefault()</code>），然后执行自定义的提交逻辑（如发送AJAX请求）。</p>
<p>综上所述，React中的事件处理和表单处理提供了丰富的功能和灵活性，使得开发者能够构建出交互性强、动态性好的Web应用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141201018</p>]]></description><guid isPermaLink="false">/archives/d3eeeef5-7641-42e9-8129-93bb539f05d2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 08:28:26 GMT</pubDate></item><item><title><![CDATA[react中State&生命周期]]></title><link>https://wiki.dgfygk.com/archives/5691af91-684b-49c1-8d6d-8efa67e35679</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=react%E4%B8%ADState%26%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&amp;url=/archives/5691af91-684b-49c1-8d6d-8efa67e35679" width="1" height="1" alt="" style="opacity:0;">
<p>在React中，State和生命周期是构建动态和交互式组件的关键概念。下面将分别详细介绍React中的State和生命周期。</p>
<h4>React中的State</h4>
<p>State是React组件内部的数据源，用于描述组件的状态。当组件的状态发生变化时，React会自动重新渲染组件，并将新的状态传递给组件的render方法，从而更新组件的UI。State具有以下几个重要特点：</p>
<ul>
 <li><strong>私有性</strong>：State属性是每个React组件的私有属性，只能在组件内部使用。</li>
 <li><strong>只读性</strong>：State应该始终视为只读属性。组件只能通过调用setState方法来更新它，而不能直接更改它的值。直接更改state属性的值可能会导致不可预期的结果，因为React可能会在其内部使用异步方式更新状态。</li>
 <li><strong>灵活性</strong>：React中的state属性不仅可以存储简单的数据类型，还可以存储对象和数组等复杂数据类型。这使得开发者能够更灵活地控制组件的状态，提高组件的复用性和可维护性。</li>
</ul>
<p>更新State的方法是通过调用<code>this.setState</code>方法，该方法接收一个对象，其中包含要更新的属性及其新值。React会自动合并这些变化，并只重新渲染需要更新的部分。</p>
<h4>React的生命周期</h4>
<p>React的生命周期指的是组件从被创建出来，到被使用，最后被销毁的整个过程。在这个过程中，React提供了会自动执行的不同的钩子函数，我们称之为生命周期函数。React的生命周期可以分为三个阶段：挂载、更新、卸载。</p>
<h5>挂载阶段（Mounting）</h5>
<p>当组件实例被创建并插入DOM中时，会依次调用以下生命周期方法：</p>
<ul>
 <li><strong>constructor()</strong>：在React组件挂载之前，会调用它的构造函数。主要用于初始化state或进行方法绑定。</li>
 <li><strong>static getDerivedStateFromProps(props, state)</strong>（React 16.3+）：在调用render方法之前调用，并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state，如果返回null则不更新任何内容。</li>
 <li><strong>render()</strong>：是class组件中唯一必须实现的方法。当render被调用时，它会检查this.props和this.state的变化并返回React元素、数组或fragments等。</li>
 <li><strong>componentDidMount()</strong>：在组件挂载后（插入DOM树中）立即调用。适合进行网络请求、添加订阅等操作。</li>
</ul>
<h5>更新阶段（Updating）</h5>
<p>当组件的props或state发生变化时，会触发更新，依次调用以下生命周期方法：</p>
<ul>
 <li><strong>static getDerivedStateFromProps(props, state)</strong>（同上）。</li>
 <li><strong>shouldComponentUpdate(nextProps, nextState)</strong>：用于判断组件是否需要更新。默认返回true，表示组件将重新渲染。</li>
 <li><strong>render()</strong>（同上）。</li>
 <li><strong>getSnapshotBeforeUpdate(prevProps, prevState)</strong>：在最近一次渲染输出（提交到DOM节点）之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息。</li>
 <li><strong>componentDidUpdate(prevProps, prevState, snapshot)</strong>：在更新后会被立即调用。可以在此处对DOM进行操作，但需要注意避免无限循环。</li>
</ul>
<h5>卸载阶段（Unmounting）</h5>
<p>当组件从DOM中移除时，会调用<code>componentWillUnmount()</code>方法。该方法会在组件卸载及销毁之前直接使用，用于执行必要的清理操作，如清除定时器、取消网络请求或清除订阅等。</p>
<h4>注意事项</h4>
<ul>
 <li>在React的新版本中，一些旧的生命周期方法（如<code>componentWillMount</code>、<code>componentWillReceiveProps</code>、<code>componentWillUpdate</code>）被标记为不安全（UNSAFE_前缀），并计划在未来版本中移除。建议在新开发的应用中使用新的生命周期方法或Hooks来替代这些旧方法。</li>
 <li>对于函数式组件，React 16.8 版本引入了Hooks，如<code>useState</code>和<code>useEffect</code>，使得函数式组件也能够拥有类似类组件的状态和生命周期功能。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141200974</p>]]></description><guid isPermaLink="false">/archives/5691af91-684b-49c1-8d6d-8efa67e35679</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:21:12 GMT</pubDate></item><item><title><![CDATA[对react组件和组件化理解]]></title><link>https://wiki.dgfygk.com/archives/2aebfef3-1de6-4238-a938-3b35e78aeebf</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AF%B9react%E7%BB%84%E4%BB%B6%E5%92%8C%E7%BB%84%E4%BB%B6%E5%8C%96%E7%90%86%E8%A7%A3&amp;url=/archives/2aebfef3-1de6-4238-a938-3b35e78aeebf" width="1" height="1" alt="" style="opacity:0;">
<p>React组件和组件化是React框架中核心概念，对于构建高效、可维护的Web应用至关重要。下面我将详细解释这两个概念。</p>
<h4>React组件</h4>
<p>React组件是React应用的基本构建块，它们是可复用的代码块，用于封装UI的某个部分及其逻辑。React组件可以接受输入（称为"props”），并返回要渲染的React元素。组件可以是类组件或函数组件，React 16.8及以后版本中引入的Hooks使得函数组件也能使用状态和其他React特性。</p>
<ul>
 <li>
  <p><strong>类组件</strong> ：通过继承<code>React.Component</code>类来创建，包含渲染方法（<code>render</code>）和其他生命周期方法（如<code>componentDidMount</code>、<code>componentDidUpdate</code>等）。类组件内部可以维护自己的状态（state）。</p>
 </li>
 <li>
  <p><strong>函数组件</strong>：更简单、更轻量的组件形式，仅接受props作为参数并返回React元素。在React 16.8之前，函数组件是无状态的，但Hooks的引入允许函数组件使用状态和其他React特性。</p>
 </li>
</ul>
<h4>组件化</h4>
<p>组件化是一种软件开发技术，它将大型软件应用分解成一组小的、松散耦合的组件。在React中，组件化意味着将UI拆分成多个独立的、可复用的组件，每个组件负责渲染UI的特定部分并处理相关的逻辑。</p>
<p>组件化的好处包括：</p>
<ol>
 <li>
  <p><strong>可重用性</strong>：组件可以在不同的地方重复使用，减少了代码的冗余。</p>
 </li>
 <li>
  <p><strong>可维护性</strong>：由于每个组件都是独立的，因此可以单独对其进行测试和维护。</p>
 </li>
 <li>
  <p><strong>清晰的代码结构</strong>：组件化使得代码结构更加清晰，易于理解和导航。</p>
 </li>
 <li>
  <p><strong>高效的团队协作</strong>：不同的团队成员可以专注于开发不同的组件，提高了开发效率。</p>
 </li>
</ol>
<h4>React中的组件化实践</h4>
<p>在React中实践组件化，通常需要遵循以下原则：</p>
<ul>
 <li>
  <p><strong>单一职责原则</strong>：每个组件应该只负责一个功能或UI部分。</p>
 </li>
 <li>
  <p><strong>高内聚低耦合</strong>：组件内部应该紧密协作，但组件之间应该尽量解耦。</p>
 </li>
 <li>
  <p><strong>可重用性</strong>：尽量设计可以复用的组件，避免为每个小功能创建单独的组件。</p>
 </li>
 <li>
  <p><strong>清晰的props接口</strong>：组件应该通过props接收必要的数据和回调，避免直接操作DOM或全局状态。</p>
 </li>
 <li>
  <p><strong>使用高阶组件和Hooks</strong>：高阶组件（HOC）和Hooks是React中用于实现代码复用和逻辑共享的强大工具。</p>
 </li>
</ul>
<p>通过遵循这些原则，你可以构建出高效、可维护且可扩展的React应用。组件化是React框架的核心优势之一，它使得开发者能够以声明式的方式构建复杂的用户界面。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141232404</p>]]></description><guid isPermaLink="false">/archives/2aebfef3-1de6-4238-a938-3b35e78aeebf</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:20:05 GMT</pubDate></item><item><title><![CDATA[Vuex是如何帮助我们管理状态的]]></title><link>https://wiki.dgfygk.com/archives/09877617-bd80-4a86-ad14-3421f78ddffd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vuex%E6%98%AF%E5%A6%82%E4%BD%95%E5%B8%AE%E5%8A%A9%E6%88%91%E4%BB%AC%E7%AE%A1%E7%90%86%E7%8A%B6%E6%80%81%E7%9A%84&amp;url=/archives/09877617-bd80-4a86-ad14-3421f78ddffd" width="1" height="1" alt="" style="opacity:0;">
<p>Vuex通过一系列核心概念和规则，帮助我们有效地管理和维护Vue.js应用程序中的状态。以下是Vuex如何帮助我们管理状态的详细解释：</p>
<h4>1. 集中式存储</h4>
<p>Vuex将应用的所有状态存储在一个全局的store中，这个store是唯一的，并且包含了应用中大部分的状态（state）。这使得状态的管理变得集中和统一，避免了在多个组件之间通过props和$emit进行复杂的通信。</p>
<h4>2. 预测性状态变更</h4>
<p>Vuex通过mutations来管理状态的变更，而mutations必须是同步函数。这意味着每次状态的变更都是可预测的，因为你可以清晰地知道状态是如何被修改的。此外，Vuex还提供了strict模式，在开发模式下，如果不是通过mutation来修改状态，则会抛出错误，这有助于开发者更容易地捕获状态管理中的错误。</p>
<h4>3. 组件解耦</h4>
<p>Vuex使得组件之间的通信变得简单，组件不再需要直接通过props和$emit进行通信，而是通过访问全局的store来实现数据的共享和通信。这有助于减少组件之间的耦合度，提高组件的可重用性和可维护性。</p>
<h4>4. 响应式更新</h4>
<p>Vuex的状态存储是响应式的，当状态发生变化时，依赖于这些状态的组件会自动更新。这使得开发者可以更加专注于业务逻辑的实现，而不需要担心状态的更新和组件的渲染。</p>
<h4>5. 模块化</h4>
<p>对于大型应用来说，Vuex支持将store分割成模块（module）。每个模块拥有自己的state、mutation、action、getter等，甚至还可以嵌套子模块。这使得状态的管理变得更加清晰和易于维护。</p>
<h4>6. 提供计算属性（Getters）</h4>
<p>Vuex中的getters类似于Vue组件中的计算属性，它们允许组件从store中派生出一些状态。这些派生的状态是基于store中的state的，并且是响应式的。当state发生变化时，依赖于这些getters的组件也会自动更新。</p>
<h4>7. 异步操作（Actions）</h4>
<p>Vuex中的actions用于处理异步操作，例如从服务器获取数据。actions可以包含任意异步操作，但它们不能直接修改state，而是需要提交mutations来修改state。这使得异步操作的管理变得清晰和有序。</p>
<h4>总结</h4>
<p>Vuex通过集中式存储、预测性状态变更、组件解耦、响应式更新、模块化、提供计算属性以及处理异步操作等方式，帮助我们有效地管理和维护Vue.js应用程序中的状态。这些特性和规则使得Vuex成为构建大型、复杂Vue.js应用的强大工具。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141285162</p>]]></description><guid isPermaLink="false">/archives/09877617-bd80-4a86-ad14-3421f78ddffd</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 07:19:54 GMT</pubDate></item><item><title><![CDATA[介绍一下react中虚拟dom和diff算法]]></title><link>https://wiki.dgfygk.com/archives/2b451ffd-7360-4201-9996-7725cb1f1dcb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%8B%E7%BB%8D%E4%B8%80%E4%B8%8Breact%E4%B8%AD%E8%99%9A%E6%8B%9Fdom%E5%92%8Cdiff%E7%AE%97%E6%B3%95&amp;url=/archives/2b451ffd-7360-4201-9996-7725cb1f1dcb" width="1" height="1" alt="" style="opacity:0;">
<p>在React中，虚拟DOM（Virtual DOM）和Diff算法是两个核心概念，它们共同工作以提高应用的性能和效率。下面分别介绍这两个概念。</p>
<h4>虚拟DOM（Virtual DOM）</h4>
<p>虚拟DOM是React中的一个核心概念，它是对真实DOM的抽象表示。React在内存中维护了一个轻量级的JavaScript对象树，这个对象树就是虚拟DOM。每当React组件的状态（state）或属性（props）发生变化时，React会重新渲染组件，但这里的"渲染"并不是直接操作真实的DOM，而是生成一个新的虚拟DOM树。</p>
<p>虚拟DOM的主要优势在于：</p>
<ol>
 <li><strong>性能优化</strong>：通过比较新旧虚拟DOM树的差异，React可以计算出需要更新的最小DOM集合，然后只更新这些部分，而不是重新渲染整个页面。</li>
 <li><strong>跨平台</strong>：虚拟DOM使得React能够轻松地移植到不同的平台（如Web、React Native等），因为React不需要直接操作原生DOM API。</li>
</ol>
<h4>Diff算法</h4>
<p>Diff算法是React用来比较新旧虚拟DOM树，并计算出差异的过程。这个算法的核心在于快速高效地找出两个树之间的差异，并只更新实际DOM中需要改变的部分。</p>
<p>React的Diff算法主要关注以下三个层面的差异比较：</p>
<ol>
 <li>
  <p><strong>树层级比较</strong> ：React首先会对比两个树的根节点，然后递归地比较子节点。如果节点的类型（如<code>&lt;div&gt;</code>、<code>&lt;span&gt;</code>或自定义组件）不同，React会销毁旧节点及其子节点，并创建新节点及其子节点。</p>
 </li>
 <li>
  <p><strong>同类型节点比较</strong>：如果两个节点是相同类型的，React会比较它们的属性。对于不同的属性，React会更新这些属性。对于子节点，React会采用一种高效的算法来比较子节点列表。</p>
 </li>
 <li>
  <p><strong>子节点列表比较</strong>：React使用了一种启发式算法来比较子节点列表，这种算法基于两个假设来优化性能：</p>
 </li>
 <li>
  <p><strong>两个相邻节点在DOM树中移动位置时，开发者通常会同时修改它们的key属性</strong>。因此，React会假设列表中的节点是稳定的，除非它们的key改变。</p>
 </li>
 <li>
  <p><strong>开发者通常不会添加、删除或移动列表中的大量节点</strong>。基于这个假设，React会优先进行同类型节点的比较，而不是立即重新排序所有子节点。</p>
 </li>
</ol>
<p>通过这些策略，React的Diff算法能够高效地计算出虚拟DOM树之间的差异，并只更新实际DOM中必要的部分，从而提高了应用的性能和响应速度。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141200938</p>]]></description><guid isPermaLink="false">/archives/2b451ffd-7360-4201-9996-7725cb1f1dcb</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:19:22 GMT</pubDate></item><item><title><![CDATA[Componsition API与Options API区别]]></title><link>https://wiki.dgfygk.com/archives/a2927b98-d6f8-4f33-8f8f-39e4681c2738</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Componsition%20API%E4%B8%8EOptions%20API%E5%8C%BA%E5%88%AB&amp;url=/archives/a2927b98-d6f8-4f33-8f8f-39e4681c2738" width="1" height="1" alt="" style="opacity:0;">
<p>Componsition API（Composition API）与Options API是Vue.js框架中用于编写组件的两种不同的方式，它们在Vue 2和Vue 3中分别作为主要的编程范式被引入。以下是两者之间的主要区别：</p>
<h4>1. 编程范式</h4>
<ul>
 <li><strong>Options API</strong> ：这是Vue 2.x中默认的组件编写方式，它基于对象，将组件的选项分离到不同的部分，如<code>data</code>、<code>methods</code>、<code>computed</code>、<code>watch</code>等。每个选项都有其特定的用途，如<code>data</code>用于定义响应式数据，<code>methods</code>用于定义方法。</li>
 <li><strong>Composition API</strong> ：这是Vue 3.x中引入的新特性，它提供了一种基于函数的组件编写方式。通过<code>setup</code>函数，可以将组件的逻辑按照功能或相关性进行组织，而不是按照选项进行划分。这种方式更加灵活，允许开发者以函数式编程的方式组织代码。</li>
</ul>
<h4>2. 代码组织和复用</h4>
<ul>
 <li><strong>Options API</strong>：在Options API中，组件的不同逻辑（如数据、方法、计算属性等）通常混杂在一起，这可能会导致关注点不清晰。复用逻辑通常意味着在多个组件之间复制和粘贴代码，或者使用mixins，但mixins可能会导致命名冲突和调试困难。</li>
 <li><strong>Composition API</strong>：Composition API允许开发者将逻辑相关的代码组织在一起，通过创建独立的函数（称为"组合函数"或"Composables”）来封装逻辑，并在多个组件中重复使用这些函数。这种方式使得代码更易于管理和维护，同时减少了冗余。</li>
</ul>
<h4>3. 响应式系统</h4>
<ul>
 <li><strong>Options API</strong> ：依赖于Vue的响应式系统，如<code>data</code>和<code>computed</code>，来追踪状态变化。</li>
 <li><strong>Composition API</strong> ：同样依赖于响应式系统，但通过<code>reactive</code>、<code>ref</code>、<code>computed</code>等函数来创建响应式引用，提供了更底层的控制。这种方式使得开发者可以更灵活地处理组件的响应式状态。</li>
</ul>
<h4>4. 生命周期钩子</h4>
<ul>
 <li><strong>Options API</strong> ：提供了完整的生命周期钩子，如<code>created</code>、<code>mounted</code>、<code>updated</code>等。</li>
 <li><strong>Composition API</strong> ：通过<code>onMounted</code>、<code>onUpdated</code>、<code>onUnmounted</code>等钩子来处理生命周期事件，这些钩子是Composition API的一部分。这些钩子以<code>on</code>开头的函数形式出现，使得生命周期的管理更加集中和明确。</li>
</ul>
<h4>5. TypeScript支持</h4>
<ul>
 <li><strong>Options API</strong>：虽然可以与TypeScript配合使用，但可能需要一些额外的配置和类型声明来确保类型安全。</li>
 <li><strong>Composition API</strong>：与TypeScript的集成更加自然和流畅。由于它基于函数式编程，开发者可以更容易地利用TypeScript的类型检查功能来确保代码的类型安全。</li>
</ul>
<h4>6. 适用场景</h4>
<ul>
 <li><strong>Options API</strong>：更适合于小型项目或团队中，结构更为传统，易于理解和上手。同时，在迁移旧项目或者需要与Vue 2保持兼容性时，Options API也是一个不错的选择。</li>
 <li><strong>Composition API</strong>：更适合于大型项目或需要高度复用和关注点分离的场景。它提供了一种更现代、更灵活的方式来构建Vue组件，鼓励开发者以函数式编程的方式思考，使得代码更加清晰和可维护。</li>
</ul>
<p>综上所述，Componsition API与Options API在编程范式、代码组织和复用、响应式系统、生命周期钩子、TypeScript支持以及适用场景等方面存在显著的差异。开发者在选择使用哪种API时，应根据项目的实际需求和团队的偏好来做出决策。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141071806</p>]]></description><guid isPermaLink="false">/archives/a2927b98-d6f8-4f33-8f8f-39e4681c2738</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:19:03 GMT</pubDate></item><item><title><![CDATA[TS中什么是函数重载？ 作用是什么？]]></title><link>https://wiki.dgfygk.com/archives/f0435943-eff2-40a8-b11e-d3172f0121d9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=TS%E4%B8%AD%E4%BB%80%E4%B9%88%E6%98%AF%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD%EF%BC%9F%20%E4%BD%9C%E7%94%A8%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;url=/archives/f0435943-eff2-40a8-b11e-d3172f0121d9" width="1" height="1" alt="" style="opacity:0;">
<p>在TypeScript（TS）中，函数重载（Function Overloading）是指通过为同一个函数提供多个不同的函数签名（即参数类型和数量），以便根据实际传入的参数类型和数量，在编译时选择正确的函数声明进行调用。这是TypeScript为了增强代码的可读性和可维护性而提供的一个特性。</p>
<h4>函数重载的作用</h4>
<ol>
 <li><strong>提高代码的可读性和可维护性</strong> ：</li>
 <li>通过为同一个功能定义多个重载函数，可以使函数名具有更强的表达力，让代码更加直观和易懂。</li>
 <li>开发者可以根据需要选择最适合当前场景的函数重载，而无需记忆多个具有相似功能的函数名。</li>
 <li><strong>提供更灵活的参数选择</strong> ：</li>
 <li>函数重载允许函数接受不同类型的参数或不同数量的参数，从而提供了更灵活的参数选择。</li>
 <li>这使得函数的使用更加方便，能够处理更加多样化的输入情况。</li>
 <li><strong>增强类型安全性</strong> ：</li>
 <li>TypeScript的静态类型检查可以在编译阶段捕获一些常见的错误，例如传入错误类型的参数。</li>
 <li>通过函数重载，可以确保在编译时就明确函数的参数类型和返回类型，从而避免运行时错误。</li>
</ol>
<h4>函数重载的实现方式</h4>
<p>在TypeScript中，函数重载是通过定义多个同名但参数类型或数量不同的函数签名来实现的。这些函数签名仅包含参数列表和返回类型（可选），而不包含函数体。最后一个函数签名后面会跟随函数的实际实现，即包含参数列表、返回类型和函数体的完整函数定义。</p>
<h4>示例</h4>
<p>|—|——————————————————–|
 <br>
 | | <code>// 函数重载声明</code> |
 <br>
 | | <code>function processData(data: string): void;</code> |
 <br>
 | | <code>function processData(data: number): void;</code> |
 <br>
 | | |
 <br>
 | | <code>// 函数的实际实现</code> |
 <br>
 | | <code>function processData(data: string | number): void {</code> |
 <br>
 | | <code>if (typeof data === 'string') {</code> |
 <br>
 | | <code>console.log(`Processing string: ${data}`);</code> |
 <br>
 | | <code>} else if (typeof data === 'number') {</code> |
 <br>
 | | <code>console.log(`Processing number: ${data}`);</code> |
 <br>
 | | <code>}</code> |
 <br>
 | | <code>}</code> |
 <br>
 | | |
 <br>
 | | <code>// 调用函数</code> |
 <br>
 | | <code>processData('Hello, TypeScript!'); // 调用第一个重载</code> |
 <br>
 | | <code>processData(123); // 调用第二个重载</code> |</p>
<p>在这个示例中，<code>processData</code> 函数被重载了两次，分别用于处理字符串和数字类型的输入。在函数的实际实现中，通过类型检查来判断传入的参数类型，并执行相应的逻辑。</p>
<h4>注意事项</h4>
<ul>
 <li>函数重载的声明顺序很重要，因为TypeScript编译器在解析函数重载时会根据定义的顺序从上到下依次匹配，直到找到第一个匹配的函数签名。</li>
 <li>函数重载只是一种编译时的机制，它并不会影响运行时的行为。在运行时，只会有一个函数被调用。</li>
 <li>最后一个函数声明是函数的实际实现，它必须能够处理所有可能的参数类型。如果函数的实现版本无法处理所有可能的参数类型，那么编译时将会出现错误。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140891501</p>]]></description><guid isPermaLink="false">/archives/f0435943-eff2-40a8-b11e-d3172f0121d9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:18:49 GMT</pubDate></item><item><title><![CDATA[Vue中mutation介绍]]></title><link>https://wiki.dgfygk.com/archives/e81db573-cee3-4bea-816c-8c5e153cc273</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue%E4%B8%ADmutation%E4%BB%8B%E7%BB%8D&amp;url=/archives/e81db573-cee3-4bea-816c-8c5e153cc273" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue中，mutation是Vuex状态管理库中的一个核心概念，用于同步地更改Vuex store中的状态。以下是关于Vue中mutation的详细介绍：</p>
<h4>一、定义与作用</h4>
<ul>
 <li><strong>定义</strong>：mutation是Vuex中改变store状态的唯一方法。Vuex的设计原则之一就是将处理数据逻辑的方法全部放在mutations中，以保持数据和视图的分离。</li>
 <li><strong>作用</strong>：mutations提供了一种同步地更新状态的机制，确保每次状态变更都可以被追踪和记录。</li>
</ul>
<h4>二、结构与用法</h4>
<ul>
 <li>
  <p><strong>结构</strong>：每个mutation都有一个字符串类型的事件类型（type）和一个回调函数（handler）。这个回调函数就是我们实际进行状态更改的地方，并且它会接受state作为第一个参数。</p>
 </li>
 <li>
  <p><strong>用法</strong> ：</p>
 </li>
 <li>
  <p><strong>定义mutation</strong> ：在Vuex的store中，通过mutations属性定义一系列改变状态的函数。</p>
  <pre><code class="hljs"> const store = new Vuex.Store({ 
 state: { 
 count: 0 
 }, 
 mutations: { 
 increment(state) { 
 state.count++ 
 } 
 } 
 })
</code></pre>
  <p>
   <br>
  </p>
 </li>
 <li>
  <p><strong>触发mutation</strong> ：通过调用store.commit方法并传入mutation的类型来触发mutation。如果需要传递额外的参数（payload），可以将其作为第二个参数传入。</p>
  <pre><code class="hljs">    store.commit('increment') // 不带参数的mutation 

    store.commit('incrementBy', 10) // 带参数的mutation
</code></pre>
  <p>
   <br>
  </p>
  <p>或者使用对象风格的提交方式：
   <br>
   *</p>
  <pre><code class="hljs">store.commit({ 
type: 'incrementBy', 
amount: 10 
})
</code></pre>
  <p>
   <br>
  </p>
 </li>
</ul>
<h4>三、注意事项</h4>
<ol>
 <li><strong>同步性</strong>：mutation必须是同步函数。这是因为Vuex通过提交mutation来追踪状态的变化，如果是异步操作，则无法追踪状态何时以及如何变化。</li>
 <li><strong>响应性</strong>：由于Vuex的store中的状态是响应式的，所以当通过mutation变更状态时，依赖这些状态的Vue组件会自动更新。</li>
 <li><strong>初始化属性</strong>：最好提前在store中初始化好所有所需属性，避免在对象上动态添加新属性时破坏响应性。</li>
 <li><strong>使用常量</strong>：在大型项目中，使用常量替代mutation的事件类型有助于团队协作和代码维护。</li>
</ol>
<h4>四、辅助函数</h4>
<p>Vuex提供了<code>mapMutations</code>辅助函数，可以将组件中的methods映射为store.commit调用，从而简化mutation的触发方式。</p>
<pre><code class="hljs">    import { mapMutations } from 'vuex' 




    export default { 

    methods: { 

    ...mapMutations([ 

    'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')` 

    // `mapMutations` 也支持载荷： 

    'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)` 

    ]), 

    ...mapMutations({ 

    add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')` 

    }) 

    } 

    }
</code></pre>
<h4>五、总结</h4>
<p>Vue中的mutation是Vuex状态管理库中的一个重要概念，它提供了一种同步地更改store状态的机制。通过mutations，我们可以确保每次状态变更都可以被追踪和记录，从而保持数据和视图的同步和一致性。同时，Vuex还提供了一系列辅助函数和最佳实践，以帮助我们更好地使用mutations来管理应用的状态。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141001576</p>]]></description><guid isPermaLink="false">/archives/e81db573-cee3-4bea-816c-8c5e153cc273</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 07:18:33 GMT</pubDate></item><item><title><![CDATA[vue中数据持久化]]></title><link>https://wiki.dgfygk.com/archives/4fe0d2cd-2958-4352-8d5a-cee624066e95</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%E4%B8%AD%E6%95%B0%E6%8D%AE%E6%8C%81%E4%B9%85%E5%8C%96&amp;url=/archives/4fe0d2cd-2958-4352-8d5a-cee624066e95" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue中，数据持久化通常指的是将Vue组件或应用中的数据保存到用户的浏览器或设备的存储中，以便在用户的会话（session）或跨会话（如重新加载页面或重新打开浏览器）中保持这些数据。Vue本身并不直接提供数据持久化的功能，但你可以通过结合使用浏览器的存储API（如localStorage、sessionStorage、IndexedDB或Cookies）来实现。</p>
<p>以下是几种在Vue中实现数据持久化的方法：</p>
<h4>1. 使用localStorage</h4>
<p><code>localStorage</code>是Web存储API的一部分，允许你在用户的浏览器中存储数据，并且没有过期时间（除非被手动清除）。</p>
<p><strong>保存数据</strong>：</p>
<pre><code class="hljs">// 在Vue组件中  
methods: {  
  saveData() {  
    const userData = { name: 'John Doe', age: 30 };  
    localStorage.setItem('userData', JSON.stringify(userData));  
  }  
}
</code></pre>
<p><strong>读取数据</strong>：</p>
<pre><code class="hljs">// 在Vue组件的created钩子或任何需要的地方  
created() {  
  const storedData = localStorage.getItem('userData');  
  if (storedData) {  
    this.userData = JSON.parse(storedData);  
  }  
}
</code></pre>
<h4>2. 使用sessionStorage</h4>
<p><code>sessionStorage</code>与<code>localStorage</code>类似，但存储的数据在页面会话结束时会被清除（例如，当页面被关闭时）。</p>
<p><strong>使用方式与localStorage相同</strong> ，只是API名称从<code>localStorage</code>改为<code>sessionStorage</code>。</p>
<h4>3. 使用IndexedDB</h4>
<p>IndexedDB是一个低级的API，用于客户端存储大量结构化数据，包括文件/二进制大对象（blobs）。它提供了比localStorage和sessionStorage更丰富的查询能力。</p>
<p>由于IndexedDB的复杂性，你可能需要使用库（如Dexie.js）来简化操作。</p>
<h4>4. 使用Cookies</h4>
<p>Cookies通常用于存储少量的数据，如用户偏好设置或身份验证令牌。它们会随每个HTTP请求发送到服务器。</p>
<p>在Vue中，你可以使用<code>document.cookie</code>来操作cookies，但更推荐使用第三方库（如js-cookie）来简化操作。</p>
<h4>5. Vuex与持久化插件</h4>
<p>如果你的Vue应用使用了Vuex来管理状态，那么你可以使用Vuex的持久化插件（如vuex-persist）来自动将Vuex的状态保存到localStorage、sessionStorage或IndexedDB中，并在应用启动时恢复它们。</p>
<h4>注意事项</h4>
<ul>
 <li>考虑到用户隐私和数据安全，请谨慎使用持久化存储，特别是当存储敏感信息时。</li>
 <li>持久化存储的数据量有限制（如localStorage和sessionStorage的存储限制通常为5MB左右），请确保不要超出这些限制。</li>
 <li>考虑到用户体验，当使用持久化存储时，请确保在适当的时候清理不再需要的数据。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141071821</p>]]></description><guid isPermaLink="false">/archives/4fe0d2cd-2958-4352-8d5a-cee624066e95</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 07:15:04 GMT</pubDate></item><item><title><![CDATA[ts中any和unknomn有什么区别]]></title><link>https://wiki.dgfygk.com/archives/00ecdfbe-7936-419e-b44e-089060031cf1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ts%E4%B8%ADany%E5%92%8Cunknomn%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB&amp;url=/archives/00ecdfbe-7936-419e-b44e-089060031cf1" width="1" height="1" alt="" style="opacity:0;">
<p>在TypeScript（TS）中，<code>any</code> 和 <code>unknown</code> 都是顶级类型（top types），但它们在设计目的和安全性方面存在显著差异。</p>
<h4>any</h4>
<ul>
 <li><strong>类型安全性</strong> ：<code>any</code> 类型是TypeScript中一种特殊的类型，它表示可以是任何JavaScript值的类型。当你将一个变量声明为 <code>any</code> 类型时，TypeScript编译器对该变量将不再进行类型检查。这意呀着你可以将任何类型的值赋给这个变量，并且可以在不进行任何显式类型转换的情况下访问该变量的任何属性或方法。</li>
 <li><strong>用途</strong> ：<code>any</code> 类型在TypeScript项目中通常用作一种"逃避类型检查"的手段，特别是当你需要逐步迁移一个JavaScript项目到TypeScript时，或者当你处理一些无法预先知道类型的动态内容时。然而，过度使用 <code>any</code> 会降低TypeScript提供的类型安全性。</li>
</ul>
<h4>unknown</h4>
<ul>
 <li><strong>类型安全性</strong> ：<code>unknown</code> 类型是TypeScript 3.0中引入的，用于表示未知或不确定的类型。与 <code>any</code> 不同的是，当你有一个 <code>unknown</code> 类型的值时，你不能直接在其上调用任何方法或访问任何属性，除非你先将它断言（或称为"类型转换”）到一个更具体的类型。这迫使开发者显式地处理不确定的类型，从而提高代码的安全性和健壮性。</li>
 <li><strong>用途</strong> ：<code>unknown</code> 类型非常适合用于那些确实不知道其类型的变量，或者当函数返回一个不确定类型的值时。通过使用 <code>unknown</code>，你可以确保在使用这些变量之前进行必要的类型检查和转换，从而避免运行时错误。</li>
</ul>
<h4>比较</h4>
<ul>
 <li><strong>安全性</strong> ：<code>unknown</code> 类型比 <code>any</code> 类型更安全，因为它强制要求开发者在使用之前进行类型断言或检查。</li>
 <li><strong>灵活性</strong> ：<code>any</code> 类型提供了最大的灵活性，因为它允许你绕过类型检查。然而，这种灵活性往往以牺牲安全性为代价。</li>
 <li><strong>推荐用法</strong> ：在TypeScript项目中，应尽量避免使用 <code>any</code> 类型，而是尽可能使用 <code>unknown</code> 或其他更具体的类型。当你确实需要使用不确定类型的值时，<code>unknown</code> 是一个更好的选择。</li>
</ul>
<p>总的来说，<code>any</code> 和 <code>unknown</code> 各有其用途，但在大多数情况下，推荐使用 <code>unknown</code> 而不是 <code>any</code>，以提高代码的类型安全性和可维护性。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140872999</p>]]></description><guid isPermaLink="false">/archives/00ecdfbe-7936-419e-b44e-089060031cf1</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:14:46 GMT</pubDate></item><item><title><![CDATA[TS中函数形状有几种定义方式？]]></title><link>https://wiki.dgfygk.com/archives/a4decf02-e802-4d7c-9f28-219ae3773072</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=TS%E4%B8%AD%E5%87%BD%E6%95%B0%E5%BD%A2%E7%8A%B6%E6%9C%89%E5%87%A0%E7%A7%8D%E5%AE%9A%E4%B9%89%E6%96%B9%E5%BC%8F%EF%BC%9F&amp;url=/archives/a4decf02-e802-4d7c-9f28-219ae3773072" width="1" height="1" alt="" style="opacity:0;">
<p>在TypeScript（TS）中，函数的形状（或称为函数签名）可以通过多种方式进行定义。这些定义方式有助于在编译时提供类型检查和更好的代码提示，从而提高代码的可读性和可维护性。以下是TypeScript中定义函数形状的几种主要方式：</p>
<ol>
 <li><strong>函数声明</strong> ：</li>
 <li>使用<code>function</code>关键字声明函数，并直接指定参数类型和返回值类型。</li>
 <li>示例：<code>function add(x: number, y: number): number { return x + y; }</code></li>
 <li><strong>函数表达式</strong> ：</li>
 <li>将函数赋值给一个变量或常量，并指定该变量或常量的类型（可选，但推荐）。</li>
 <li>示例：<code>const add = function(x: number, y: number): number { return x + y; };</code></li>
 <li><strong>箭头函数</strong> ：</li>
 <li>使用箭头语法声明函数，并指定参数类型和返回值类型（类型注解是可选的，但推荐明确指定以提高代码可读性）。</li>
 <li>示例：<code>const add = (x: number, y: number): number =&gt; { return x + y; };</code></li>
 <li><strong>类型别名定义函数</strong> ：</li>
 <li>使用<code>type</code>关键字为函数定义类型别名，然后使用该类型别名来声明函数变量。</li>
 <li>示例：<code>type AddFunction = (x: number, y: number) =&gt; number; const add: AddFunction = (x, y) =&gt; x + y;</code></li>
 <li><strong>接口定义函数形状</strong> ：</li>
 <li>虽然接口在TypeScript中主要用于描述对象的形状，但它们也可以用来描述函数的形状。然而，这通常不如使用类型别名那么常见，因为类型别名更简洁且易于阅读。</li>
 <li>示例：<code>interface AddFunction { (x: number, y: number): number; } const add: AddFunction = (x, y) =&gt; x + y;</code></li>
 <li><strong>函数重载</strong>： TypeScript支持函数重载，允许为同一个函数提供多个类型签名。这是通过先定义多个具有相同名称但参数列表不同的函数签名（仅包含参数和返回类型，不包含函数体），然后定义函数的实际实现来实现的。</li>
 <li><strong>其他相关特性</strong> ：</li>
 <li>TypeScript还提供了其他与函数定义相关的特性，如可选参数、默认参数、剩余参数等，这些特性可以进一步丰富函数的定义方式，但它们本身并不直接定义函数的"形状”，而是提供了额外的灵活性。</li>
</ol>
<p>综上所述，TypeScript中定义函数形状的方式主要包括函数声明、函数表达式、箭头函数、类型别名定义函数、接口定义函数形状以及函数重载等。开发者可以根据实际需求选择合适的方式来定义函数形状，以提高代码的可读性、可维护性和类型安全性。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140891635</p>]]></description><guid isPermaLink="false">/archives/a4decf02-e802-4d7c-9f28-219ae3773072</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:14:32 GMT</pubDate></item><item><title><![CDATA[ts中什么是交叉类型]]></title><link>https://wiki.dgfygk.com/archives/3e76f9b2-f557-4193-870e-9d73622f8610</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ts%E4%B8%AD%E4%BB%80%E4%B9%88%E6%98%AF%E4%BA%A4%E5%8F%89%E7%B1%BB%E5%9E%8B&amp;url=/archives/3e76f9b2-f557-4193-870e-9d73622f8610" width="1" height="1" alt="" style="opacity:0;">
<p>TypeScript中的交叉类型（Intersection Types）是一种强大的类型构造，它允许多个类型组合成一个新类型，新类型将具有所有原类型的特性。交叉类型使用<code>&amp;</code>运算符来定义。</p>
<h4>定义</h4>
<p>交叉类型通过<code>&amp;</code>符号将多个类型合并成一个类型，表示同时拥有这些类型的属性和方法。这种类型合并的方式允许开发者将不同的类型特性组合在一起，从而创建出更为复杂和灵活的类型定义。</p>
<h4>使用示例</h4>
<ol>
 <li>
  <p><strong>合并对象类型</strong>：</p>
  <p>假设我们有两个类型<code>Person</code>和<code>Address</code>，分别表示一个人和一个地址的信息。我们可以使用交叉类型将它们合并成一个新的类型<code>PersonWithAddress</code>，这个新类型将同时包含<code>Person</code>和<code>Address</code>的所有属性。</p>
  <p>
   <br>
  </p>
  <p>|—|—————————————————|
   <br>
   | | <code>type Person = {</code> |
   <br>
   | | <code>name: string;</code> |
   <br>
   | | <code>age: number;</code> |
   <br>
   | | <code>};</code> |
   <br>
   | | |
   <br>
   | | <code>type Address = {</code> |
   <br>
   | | <code>street: string;</code> |
   <br>
   | | <code>city: string;</code> |
   <br>
   | | <code>};</code> |
   <br>
   | | |
   <br>
   | | <code>type PersonWithAddress = Person &amp; Address;</code> |
   <br>
   | | |
   <br>
   | | <code>const personWithAddress: PersonWithAddress = {</code> |
   <br>
   | | <code>name: 'John',</code> |
   <br>
   | | <code>age: 30,</code> |
   <br>
   | | <code>street: '123 Main St',</code> |
   <br>
   | | <code>city: 'Example City'</code> |
   <br>
   | | <code>};</code> |</p>
 </li>
 <li>
  <p><strong>组合函数类型</strong>：</p>
  <p>交叉类型也可以用于函数类型，允许一个函数同时拥有多个函数类型的特性。</p>
  <p>
   <br>
  </p>
  <p>|—|———————————————————————————————————-|
   <br>
   | | <code>type MathFunction = (x: number) =&gt; number;</code> |
   <br>
   | | <code>type StringFunction = (s: string) =&gt; string;</code> |
   <br>
   | | |
   <br>
   | | <code>// 注意：这个交叉类型实际上是有问题的，因为(x: number) =&gt; number 和 (s: string) =&gt; string 参数类型不同，不能直接合并。</code> |
   <br>
   | | <code>// 这里仅为了说明交叉类型在函数类型上的潜在用途，实际使用中应避免这种情况。</code> |
   <br>
   | | <code>// type CombinedFunction = MathFunction &amp; StringFunction; // 错误示例</code> |
   <br>
   | | |
   <br>
   | | <code>// 正确的做法是定义一个可以接受多种类型参数或返回多种类型结果的函数类型</code> |
   <br>
   | | <code>type CombinedFunction = {(x: number): number} | {(s: string): string};</code> |
   <br>
   | | |
   <br>
   | | <code>const combinedFunc: CombinedFunction = (arg) =&gt; typeof arg === 'number' ? arg * 2 : arg.toUpperCase();</code> |</p>
 </li>
 <li>
  <p><strong>合并类类型</strong>（注意：TypeScript原生不支持类的交叉类型，但可以通过接口或其他方式模拟）：</p>
  <p>虽然TypeScript不直接支持类的交叉类型，但你可以通过接口或其他类型构造来模拟这种行为。</p>
  <p>
   <br>
  </p>
  <p>|—|————————————————-|
   <br>
   | | <code>class Dog {</code> |
   <br>
   | | <code>bark() {</code> |
   <br>
   | | <code>console.log('Woof! Woof!');</code> |
   <br>
   | | <code>}</code> |
   <br>
   | | <code>}</code> |
   <br>
   | | |
   <br>
   | | <code>class Robot {</code> |
   <br>
   | | <code>move() {</code> |
   <br>
   | | <code>console.log('Walking...');</code> |
   <br>
   | | <code>}</code> |
   <br>
   | | <code>}</code> |
   <br>
   | | |
   <br>
   | | <code>// 假设我们想要一个同时具有Dog和Robot行为的对象，可以通过接口或其他方式来实现</code> |
   <br>
   | | <code>interface PetRobot {</code> |
   <br>
   | | <code>bark(): void;</code> |
   <br>
   | | <code>move(): void;</code> |
   <br>
   | | <code>}</code> |
   <br>
   | | |
   <br>
   | | <code>// 然后我们可以实现这个接口，或者通过其他方式（如组合模式）来模拟这种行为</code> |
   <br>
   | | <code>class HybridPet implements PetRobot {</code> |
   <br>
   | | <code>bark() {</code> |
   <br>
   | | <code>console.log('Woof! Woof!');</code> |
   <br>
   | | <code>}</code> |
   <br>
   | | |
   <br>
   | | <code>move() {</code> |
   <br>
   | | <code>console.log('Walking...');</code> |
   <br>
   | | <code>}</code> |
   <br>
   | | <code>}</code> |</p>
 </li>
</ol>
<h4>注意事项</h4>
<ul>
 <li>当合并的类型中存在重名但类型相同的属性时，新类型中的该属性将保持原有的类型。</li>
 <li>如果合并的类型中存在重名但类型不同的属性，TypeScript编译器将报错，因为交叉类型要求所有合并的类型在相同属性上必须兼容。</li>
 <li>交叉类型不仅限于对象类型，还可以用于函数类型、接口、类型别名等TypeScript中的几乎所有类型构造。</li>
</ul>
<p>交叉类型是TypeScript中非常有用的一个特性，它允许开发者以灵活的方式组合不同的类型，从而创建出更为复杂和强大的类型定义。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140873061</p>]]></description><guid isPermaLink="false">/archives/3e76f9b2-f557-4193-870e-9d73622f8610</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:12:34 GMT</pubDate></item><item><title><![CDATA[TS中什么是字面量类型？]]></title><link>https://wiki.dgfygk.com/archives/089f1cca-b107-4864-94a0-6bd70bd1acf4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=TS%E4%B8%AD%E4%BB%80%E4%B9%88%E6%98%AF%E5%AD%97%E9%9D%A2%E9%87%8F%E7%B1%BB%E5%9E%8B%EF%BC%9F&amp;url=/archives/089f1cca-b107-4864-94a0-6bd70bd1acf4" width="1" height="1" alt="" style="opacity:0;">
<p>在TypeScript（TS）中，字面量类型（Literal Types）是指那些与特定字面量值严格对应的类型。这些字面量值可以是字符串、数字、布尔值或符号等。字面量类型提供了一种方式来定义更精确的类型，从而增强代码的可读性和类型安全性。以下是关于TS中字面量类型的详细解释：</p>
<h4>定义</h4>
<ul>
 <li>字面量类型直接使用字面量值来定义变量的类型，例如<code>'success'</code>、<code>42</code>、<code>true</code>等。</li>
 <li>它允许开发者为变量或函数参数指定一组有限的可取值，从而限制它们的取值范围。</li>
</ul>
<h4>特点</h4>
<ol>
 <li><strong>精确性</strong> ：相比于使用更广泛的类型（如<code>string</code>、<code>number</code>），字面量类型提供了更高的精确性。它确保了变量或函数参数只能取特定的值。</li>
 <li><strong>增强类型安全性</strong>：通过限制变量的取值范围，字面量类型有助于在编译阶段捕获潜在的错误，提高代码的类型安全性。</li>
 <li><strong>提高可读性</strong>：使用字面量类型可以使代码的意图更加清晰，其他开发者在阅读代码时可以更容易地理解变量的预期用途和取值范围。</li>
</ol>
<h4>使用场景</h4>
<ul>
 <li>用来表示一组明确的可选值列表，如状态码、错误码、配置选项等。</li>
 <li>在定义函数参数时，可以限制参数只能取特定的值，从而增强函数的健壮性。</li>
</ul>
<h4>示例</h4>
<p>|—|——————————————————–|
 <br>
 | | <code>// 字符串字面量类型</code> |
 <br>
 | | <code>let status: 'open' | 'closed' = 'open';</code> |
 <br>
 | | <code>status = 'closed'; // 合法</code> |
 <br>
 | | <code>status = 'in progress'; // 错误，只能取 'open' 或 'closed'</code> |
 <br>
 | | |
 <br>
 | | <code>// 数字字面量类型</code> |
 <br>
 | | <code>let age: 18 | 20 | 22 = 18;</code> |
 <br>
 | | <code>age = 20; // 合法</code> |
 <br>
 | | <code>age = 25; // 错误，只能取 18、20 或 22</code> |
 <br>
 | | |
 <br>
 | | <code>// 布尔字面量类型（虽然在实际中较少直接使用布尔字面量类型，因为布尔类型只有两个值）</code> |
 <br>
 | | <code>let isDone: true = true;</code> |
 <br>
 | | <code>isDone = false; // 错误，只能取 true</code> |
 <br>
 | | |
 <br>
 | | <code>// 联合类型与字面量类型结合使用</code> |
 <br>
 | | <code>type Direction = 'up' | 'down' | 'left' | 'right';</code> |
 <br>
 | | <code>function move(direction: Direction) {</code> |
 <br>
 | | <code>// ...</code> |
 <br>
 | | <code>}</code> |
 <br>
 | | <code>move('up'); // 合法</code> |
 <br>
 | | <code>move('forward'); // 错误，参数不能赋值给 'Direction' 类型</code> |</p>
<h4>注意事项</h4>
<ul>
 <li>字面量类型不易扩展。一旦定义了字面量类型，就只能使用其中的值。如果需要添加新的值，需要修改类型定义。</li>
 <li>在使用字面量类型时，需确保其能够有效地限制变量的取值范围，避免出现不必要的错误。</li>
 <li>字面量类型可以与联合类型、交叉类型等其他类型结合使用，以实现更复杂的类型约束。</li>
</ul>
<p>总之，字面量类型是TypeScript中一种强大的类型约束机制，它能够提高代码的可读性和类型安全性。然而，在使用时需要注意合理权衡其优缺点，并确保与其他类型相互配合以达到更好的效果。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140891573</p>]]></description><guid isPermaLink="false">/archives/089f1cca-b107-4864-94a0-6bd70bd1acf4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:12:20 GMT</pubDate></item><item><title><![CDATA[vue中nextTick的作用]]></title><link>https://wiki.dgfygk.com/archives/5925da76-b7f7-47a4-a854-ddda68fd4ff8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%E4%B8%ADnextTick%E7%9A%84%E4%BD%9C%E7%94%A8&amp;url=/archives/5925da76-b7f7-47a4-a854-ddda68fd4ff8" width="1" height="1" alt="" style="opacity:0;">
<p>nextTick是Vue.js提供的一个非常有用的方法，其主要作用是在DOM更新之后执行延迟回调函数。以下是nextTick的具体作用及其实现原理的详细解析：</p>
<h4>nextTick的作用</h4>
<ol>
 <li><strong>确保DOM更新完成</strong> ：</li>
 <li>当Vue实例的数据发生变化时，Vue会异步地更新DOM。nextTick方法允许你在DOM实际更新完成后执行某些操作，确保你能够访问到最新的DOM元素。</li>
 <li><strong>在updated钩子中操作DOM</strong> ：</li>
 <li>在组件的updated钩子函数中，虽然组件的DOM已经更新，但子组件的DOM可能还未完全更新。使用nextTick可以在整个组件树完全更新后再操作DOM。</li>
 <li><strong>减少不必要的DOM操作</strong> ：</li>
 <li>通过将DOM操作延迟到下一个更新周期执行，nextTick可以减少不必要的DOM操作，从而提高性能。</li>
 <li><strong>优化页面渲染效率</strong> ：</li>
 <li>合理使用nextTick可以优化页面渲染效率，避免在DOM更新过程中进行不必要的计算和操作。</li>
 <li><strong>简化操作</strong> ：</li>
 <li>使用nextTick可以简化DOM操作的管理，避免手动判断DOM更新时机，提高代码的可读性和可维护性。</li>
</ol>
<h4>nextTick的实现原理</h4>
<p>nextTick的实现原理主要依赖于JavaScript的事件循环和异步执行机制。Vue.js利用浏览器的异步队列机制，将DOM更新操作放入到一个队列中，然后通过异步任务的方式执行队列中的任务。</p>
<ol>
 <li><strong>判断异步方法</strong> ：</li>
 <li>Vue会判断当前环境优先支持的异步方法，如Promise、MutationObserver、setImmediate或setTimeout。这些异步方法的优先级通常是Promise &gt; MutationObserver &gt; setImmediate &gt; setTimeout。</li>
 <li><strong>存储回调函数</strong> ：</li>
 <li>当调用nextTick方法时，Vue会将传入的回调函数存储到一个队列中。</li>
 <li><strong>执行异步任务</strong> ：</li>
 <li>在当前事件循环内执行完所有的同步任务后，Vue会检查是否存在异步队列。如果存在，则将异步队列中的任务依次执行。这些任务包括更新DOM以及执行nextTick队列中的回调函数。</li>
 <li><strong>确保DOM更新完成</strong> ：</li>
 <li>由于异步任务是在下一个事件循环中执行的，因此在执行nextTick的回调函数时，DOM已经更新完成，可以安全地执行相关操作。</li>
</ol>
<p>综上所述，nextTick是Vue.js中一个非常实用的方法，它允许开发者在DOM更新完成后执行特定的操作，从而避免在DOM更新过程中进行不必要的计算和操作，提高应用的性能和可维护性。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/141001557</p>]]></description><guid isPermaLink="false">/archives/5925da76-b7f7-47a4-a854-ddda68fd4ff8</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 07:12:08 GMT</pubDate></item><item><title><![CDATA[列举出一些TS的优点]]></title><link>https://wiki.dgfygk.com/archives/8ad8a4eb-2788-4963-bfa8-880d73997ec2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%97%E4%B8%BE%E5%87%BA%E4%B8%80%E4%BA%9BTS%E7%9A%84%E4%BC%98%E7%82%B9&amp;url=/archives/8ad8a4eb-2788-4963-bfa8-880d73997ec2" width="1" height="1" alt="" style="opacity:0;">
<p>TypeScript（TS）作为JavaScript的一个超集，它增加了许多有用的特性，使得在开发大型应用时更加高效、安全和可维护。以下是TypeScript的一些主要优点：</p>
<ol>
 <li><strong>静态类型检查</strong> ：</li>
 <li>TypeScript提供了静态类型检查，这意味着在编译阶段就能发现很多类型相关的错误，而不是等到运行时。这可以显著提高代码的质量，减少调试时间。</li>
 <li><strong>代码可读性和可维护性</strong> ：</li>
 <li>通过明确的类型定义和接口，TypeScript代码更加易于理解和维护。它允许开发者通过类型签名快速了解函数、变量和类的用途和预期行为。</li>
 <li><strong>更好的重构支持</strong> ：</li>
 <li>静态类型系统使得重构代码变得更加容易和安全。TypeScript能够追踪变量和函数的类型，从而在重构过程中自动检测可能的类型错误。</li>
 <li><strong>IDE支持</strong> ：</li>
 <li>大多数现代IDE和编辑器都提供了对TypeScript的出色支持，包括代码自动完成、类型检查、错误提示和重构工具。这些工具能够显著提高开发效率。</li>
 <li><strong>大型项目支持</strong> ：</li>
 <li>TypeScript非常适合用于开发大型项目，特别是那些需要多个人协同工作的项目。它的模块化特性、类型系统和强大的编译选项使得管理复杂代码库变得更加容易。</li>
 <li><strong>兼容现有JavaScript代码</strong> ：</li>
 <li>TypeScript可以逐步引入到现有的JavaScript项目中，因为它与JavaScript完全兼容。这意味着开发者可以在不改变现有代码的情况下，逐渐将部分代码转换为TypeScript。</li>
 <li><strong>社区和生态系统</strong> ：</li>
 <li>TypeScript拥有庞大的社区和丰富的生态系统，包括许多流行的库和框架（如Angular、Vue.js和React的TypeScript支持）都提供了TypeScript的支持。这使得开发者可以更容易地找到所需的资源和支持。</li>
 <li><strong>强大的泛型系统</strong> ：</li>
 <li>TypeScript提供了强大的泛型系统，允许开发者编写更加灵活和可重用的代码。泛型可以在编译时保持类型安全，同时避免在运行时进行不必要的类型转换。</li>
 <li><strong>枚举和命名空间</strong> ：</li>
 <li>TypeScript支持枚举和命名空间等特性，这些特性有助于组织和封装相关的代码，提高代码的可读性和可维护性。</li>
 <li><strong>非空类型检查</strong> ：
  <ul>
   <li>TypeScript允许开发者将变量声明为非空类型（如<code>string</code>而不是<code>string | null | undefined</code>），这有助于避免在代码中出现空指针异常。</li>
  </ul></li>
</ol>
<p>综上所述，TypeScript通过提供静态类型检查、增强的代码可读性和可维护性、大型项目支持以及丰富的社区和生态系统等特性，成为了许多现代Web和Node.js项目的首选编程语言。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140891535</p>]]></description><guid isPermaLink="false">/archives/8ad8a4eb-2788-4963-bfa8-880d73997ec2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 07:11:47 GMT</pubDate></item><item><title><![CDATA[react的State&生命周期]]></title><link>https://wiki.dgfygk.com/archives/ea286023-995f-4d86-984b-4a322a7997f1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=react%E7%9A%84State%26%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&amp;url=/archives/ea286023-995f-4d86-984b-4a322a7997f1" width="1" height="1" alt="" style="opacity:0;">
<p>React的State和生命周期是React框架中非常重要的概念，它们共同协作以实现组件的动态更新和高效渲染。以下是对React的State和生命周期的详细解析：</p>
<h4>React的State</h4>
<p><strong>定义与作用</strong>：</p>
<ul>
 <li>状态（State）是React组件内部的数据源，用于描述组件的状态。当组件的状态发生变化时，React会自动重新渲染组件，并将新的状态传递给组件的<code>render</code>方法，从而更新组件的UI。</li>
 <li>State属性是每个React组件的私有属性，只能在组件内部使用。通过调用<code>this.setState</code>方法来更新state，<code>setState</code>接收一个对象，其中包含要更新的属性及其新值。React会自动合并这些变化，并只重新渲染需要更新的部分。</li>
</ul>
<p><strong>使用注意事项</strong>：</p>
<ul>
 <li>State应该始终视为只读属性。组件只能通过调用<code>setState</code>方法来更新它，而不能直接更改它的值。直接更改state属性的值可能会导致不可预期的结果，因为React可能会在其内部使用异步方式更新状态。</li>
 <li>React中的state属性不仅可以存储简单的数据类型，还可以存储对象和数组等复杂数据类型。这使得开发者能够更灵活地控制组件的状态，提高组件的复用性和可维护性。</li>
</ul>
<h4>React的生命周期</h4>
<p>React的生命周期指的是组件从被创建出来，到被使用，最后被销毁的整个过程。在这个过程中，React提供了会自动执行的不同的钩子函数，我们称之为生命周期函数。React的生命周期可以分为三个阶段：挂载、更新、卸载。</p>
<p><strong>挂载阶段（Mounting）</strong>：</p>
<ul>
 <li>当组件实例被创建并插入DOM中时，会依次调用以下生命周期方法：</li>
 <li><code>constructor()</code>：在React组件挂载之前，会调用它的构造函数。主要用于初始化state或进行方法绑定。</li>
 <li><code>static getDerivedStateFromProps()</code>（可选）：在调用<code>render</code>方法之前调用，并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state，如果返回null则不更新任何内容。</li>
 <li><code>render()</code>：是class组件中唯一必须实现的方法。当<code>render</code>被调用时，它会检查<code>this.props</code>和<code>this.state</code>的变化并返回React元素、数组或fragments等。</li>
 <li><code>componentDidMount()</code>：在组件挂载后（插入DOM树中）立即调用。适合进行网络请求、添加订阅等操作。</li>
</ul>
<p><strong>更新阶段（Updating）</strong>：</p>
<ul>
 <li>当组件的props或state发生变化时，会触发更新，依次调用以下生命周期方法：</li>
 <li><code>static getDerivedStateFromProps()</code>（同上）。</li>
 <li><code>shouldComponentUpdate(nextProps, nextState)</code>：用于判断组件是否需要更新。默认返回true，表示组件将重新渲染。</li>
 <li><code>render()</code>（同上）。</li>
 <li><code>getSnapshotBeforeUpdate(prevProps, prevState)</code>：在最近一次渲染输出（提交到DOM节点）之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息。</li>
 <li><code>componentDidUpdate(prevProps, prevState, snapshot)</code>：在更新后会被立即调用。可以在此处对DOM进行操作，但需要注意避免无限循环。</li>
</ul>
<p><strong>卸载阶段（Unmounting）</strong>：</p>
<ul>
 <li>当组件从DOM中移除时，会调用<code>componentWillUnmount()</code>方法。该方法会在组件卸载及销毁之前直接使用，用于执行必要的清理操作，如清除定时器、取消网络请求或清除订阅等。</li>
</ul>
<p><strong>注意</strong>：</p>
<ul>
 <li>在React的新版本中，一些旧的生命周期方法（如<code>componentWillMount</code>、<code>componentWillReceiveProps</code>、<code>componentWillUpdate</code>）被标记为不安全（UNSAFE_前缀），并计划在未来版本中移除。建议在新开发的应用中使用新的生命周期方法或Hooks来替代这些旧方法。</li>
 <li>对于函数式组件，React 16.8 版本引入了Hooks，如<code>useState</code>和<code>useEffect</code>，使得函数式组件也能够拥有类似类组件的状态和生命周期功能。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140738003</p>]]></description><guid isPermaLink="false">/archives/ea286023-995f-4d86-984b-4a322a7997f1</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:07:51 GMT</pubDate></item><item><title><![CDATA[react子组件向父组件传参]]></title><link>https://wiki.dgfygk.com/archives/a5fa3f41-1189-444a-9273-db4a2c676f3f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=react%E5%AD%90%E7%BB%84%E4%BB%B6%E5%90%91%E7%88%B6%E7%BB%84%E4%BB%B6%E4%BC%A0%E5%8F%82&amp;url=/archives/a5fa3f41-1189-444a-9273-db4a2c676f3f" width="1" height="1" alt="" style="opacity:0;">
<p>在React中，子组件向父组件传参（或称为"通信”）通常通过事件回调的方式实现。父组件向子组件传递一个函数作为props，子组件在需要时调用这个函数，并可以通过参数的形式向父组件传递数据。</p>
<p>下面是一个简单的例子来说明这个过程：</p>
<h4>父组件（ParentComponent）</h4>
<pre><code class="hljs">import React, { useState } from 'react';  
import ChildComponent from './ChildComponent';  

function ParentComponent() {  
  const [childData, setChildData] = useState('');  

  // 这是一个由父组件传递给子组件的函数，用于接收子组件的数据  
  const handleChildData = (data) =&gt; {  
    setChildData(data);  
  };  

  return (  
    &lt;div&gt;  
      &lt;h1&gt;Parent Component&lt;/h1&gt;  
      &lt;p&gt;Data from Child: {childData}&lt;/p&gt;  
      &lt;ChildComponent onReceiveData={handleChildData} /&gt;  
    &lt;/div&gt;  
  );  
}  

export default ParentComponent;
</code></pre>
<h4>子组件（ChildComponent）</h4>
<pre><code class="hljs">import React from 'react';  

function ChildComponent({ onReceiveData }) {  
  // 假设这里有一个按钮，点击时会触发数据传递  
  const sendDataToParent = () =&gt; {  
    const dataToSend = 'Hello from Child!';  
    onReceiveData(dataToSend); // 调用父组件传递的函数，并传递数据  
  };  

  return (  
    &lt;div&gt;  
      &lt;h2&gt;Child Component&lt;/h2&gt;  
      &lt;button onClick={sendDataToParent}&gt;Send Data to Parent&lt;/button&gt;  
    &lt;/div&gt;  
  );  
}  

export default ChildComponent;
</code></pre>
<p>在这个例子中，<code>ParentComponent</code> 通过props向<code>ChildComponent</code>传递了一个名为<code>onReceiveData</code>的函数。这个函数在<code>ParentComponent</code>中被定义，用于更新<code>ParentComponent</code>的state（即<code>childData</code>）。当<code>ChildComponent</code>中的按钮被点击时，它会调用<code>onReceiveData</code>函数，并传递一个字符串<code>'Hello from Child!'</code>作为参数。这样，<code>ParentComponent</code>就能够接收到来自<code>ChildComponent</code>的数据，并更新其状态，从而在UI上反映出这一变化。</p>
<p>这种方式是React中实现组件间通信的一种常用且有效的方式，特别适用于父子组件之间的数据传递。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140738079</p>]]></description><guid isPermaLink="false">/archives/a5fa3f41-1189-444a-9273-db4a2c676f3f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:07:28 GMT</pubDate></item><item><title><![CDATA[虚拟dom和diff算法]]></title><link>https://wiki.dgfygk.com/archives/1e96789a-b8c8-4b12-bb6a-84505a9fb87e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%99%9A%E6%8B%9Fdom%E5%92%8Cdiff%E7%AE%97%E6%B3%95&amp;url=/archives/1e96789a-b8c8-4b12-bb6a-84505a9fb87e" width="1" height="1" alt="" style="opacity:0;">
<p>React的虚拟DOM（Virtual DOM）和Diff算法是React框架中非常重要的两个概念，它们共同协作以实现高效的UI更新。以下是对React虚拟DOM和Diff算法的详细解析：</p>
<h4>React虚拟DOM</h4>
<p><strong>定义</strong>：</p>
<ul>
 <li>虚拟DOM是React中的一个核心概念，它是一个轻量级的JavaScript对象树，作为真实DOM的抽象表示。React使用虚拟DOM来跟踪组件的状态和结构，从而实现高效的UI更新。</li>
</ul>
<p><strong>基本原理</strong>：</p>
<ul>
 <li>当React组件的状态或属性发生变化时，React会生成一个新的虚拟DOM树。</li>
 <li>然后，React会使用Diff算法来比较新旧两个虚拟DOM树的差异。</li>
 <li>根据比较结果，React会最小化对真实DOM的操作，只更新实际发生变化的部分，从而提高性能。</li>
</ul>
<p><strong>优势</strong>：</p>
<ul>
 <li>虚拟DOM避免了直接操作真实DOM带来的性能问题，因为真实DOM的操作是昂贵的。</li>
 <li>虚拟DOM的更新是批量的，React会将多个更新合并为一个，然后统一应用到真实DOM上，从而进一步减少性能损耗。</li>
</ul>
<h4>React Diff算法</h4>
<p><strong>定义</strong>：</p>
<ul>
 <li>Diff算法是React用于比较两个虚拟DOM树的技术，以确定需要进行的最小DOM操作。</li>
</ul>
<p><strong>基本原理</strong>：</p>
<ol>
 <li><strong>树的比较</strong> ：</li>
 <li>React会逐层比较两个虚拟DOM树的节点。</li>
 <li>如果节点类型不同，React会直接替换整个节点及其子树。</li>
 <li><strong>同级比较</strong> ：</li>
 <li>React只会比较同一层级的节点，不会跨层级比较。</li>
 <li><strong>唯一标识</strong> ：</li>
 <li>React使用唯一的key属性来标识列表中的每个节点，以便更准确地比较和更新节点及其子树。</li>
 <li><strong>更新策略</strong> ：</li>
 <li>React会尽量复用已存在的DOM节点，而不是直接创建新的节点。</li>
 <li>只有在必要的情况下才会创建、移除或更新DOM节点。</li>
 <li><strong>批量更新</strong> ：</li>
 <li>React将多个DOM操作合并为一个批量更新，以提高性能。</li>
</ol>
<p><strong>实现细节</strong>：</p>
<ul>
 <li>React的Diff算法主要包括三个步骤：Tree Diff、Component Diff和Element Diff。</li>
 <li>Tree Diff：React会对树形结构的每一层进行比较，如果其中一个节点被删除，则不会比较该节点下的子节点，直接重新生成真实的DOM。</li>
 <li>Component Diff：React会对树形结构中的单个节点组件进行比较，如果组件类型发生变化，则直接替换整个组件。</li>
 <li>Element Diff：如果组件类型相同，React会进一步比较组件内部的元素，只更新发生变化的元素。</li>
</ul>
<p><strong>总结</strong>：</p>
<p>React的虚拟DOM和Diff算法是React实现高效UI更新的关键。通过虚拟DOM，React避免了直接操作真实DOM带来的性能问题；通过Diff算法，React能够最小化对真实DOM的操作，只更新实际发生变化的部分。这两个技术的结合使得React在构建复杂应用时能够保持高效的性能和良好的用户体验。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140738038</p>]]></description><guid isPermaLink="false">/archives/1e96789a-b8c8-4b12-bb6a-84505a9fb87e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:07:00 GMT</pubDate></item><item><title><![CDATA[react创建虚拟DOM的两种方式]]></title><link>https://wiki.dgfygk.com/archives/59a181b9-3894-4290-826c-d9e645671abc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=react%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9FDOM%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F&amp;url=/archives/59a181b9-3894-4290-826c-d9e645671abc" width="1" height="1" alt="" style="opacity:0;">
<p>React创建虚拟DOM的两种方式主要包括使用JSX和使用<code>React.createElement</code>函数。以下是这两种方式的详细说明：</p>
<h4>1. 使用JSX创建虚拟DOM</h4>
<p>JSX是React中推荐的方式，它允许你在JavaScript代码中编写类似于HTML的结构。这些结构最终会被Babel等编译工具转换成<code>React.createElement</code>函数调用，从而创建虚拟DOM节点。</p>
<p><strong>优点</strong>：</p>
<ul>
 <li><strong>直观性</strong>：JSX的语法类似于HTML，使得开发者能够更直观地编写组件结构。</li>
 <li><strong>易于维护</strong>：JSX使得代码更加清晰，易于理解和维护。</li>
</ul>
<p><strong>示例</strong>：</p>
<p>|—|——————————–|
 <br>
 | | <code>const element = (</code> |
 <br>
 | | <code>&lt;div className="container"&gt;</code> |
 <br>
 | | <code>&lt;h1&gt;Hello, React!&lt;/h1&gt;</code> |
 <br>
 | | <code>&lt;p&gt;This is a paragraph.&lt;/p&gt;</code> |
 <br>
 | | <code>&lt;/div&gt;</code> |
 <br>
 | | <code>);</code> |</p>
<h4>2. 使用<code>React.createElement</code>函数创建虚拟DOM</h4>
<p><code>React.createElement</code>是React提供的一个函数，用于动态创建虚拟DOM元素。这个函数接受三个参数：类型（标签名）、属性（props）、子元素（children）。</p>
<p><strong>优点</strong>：</p>
<ul>
 <li><strong>灵活性</strong> ：在无法使用JSX的环境（如某些服务器端渲染场景）中，<code>React.createElement</code>是创建虚拟DOM的唯一方式。</li>
 <li><strong>兼容性</strong> ：对于一些老旧的项目或环境，使用<code>React.createElement</code>可能更加兼容。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
 <li><strong>繁琐性</strong> ：当需要创建的元素较多时，使用<code>React.createElement</code>可能会使代码变得繁琐。</li>
</ul>
<p><strong>示例</strong>：</p>
<p>|—|———————————————————–|
 <br>
 | | <code>const element = React.createElement(</code> |
 <br>
 | | <code>'div',</code> |
 <br>
 | | <code>{ className: 'container' },</code> |
 <br>
 | | <code>React.createElement('h1', null, 'Hello, React!'),</code> |
 <br>
 | | <code>React.createElement('p', null, 'This is a paragraph.')</code> |
 <br>
 | | <code>);</code> |</p>
<h4>总结</h4>
<p>在实际开发中，由于JSX的直观性和易维护性，它通常是创建虚拟DOM的首选方式。然而，在一些特殊场景下，如服务器端渲染或无法使用JSX的环境中，<code>React.createElement</code>函数则成为了一种有效的替代方案。无论是使用JSX还是<code>React.createElement</code>，React都会通过创建虚拟DOM来提高应用的性能。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140737922</p>]]></description><guid isPermaLink="false">/archives/59a181b9-3894-4290-826c-d9e645671abc</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:06:45 GMT</pubDate></item><item><title><![CDATA[React特点]]></title><link>https://wiki.dgfygk.com/archives/8ce2d371-b8c5-4dc1-9698-2425f1b25ea4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=React%E7%89%B9%E7%82%B9&amp;url=/archives/8ce2d371-b8c5-4dc1-9698-2425f1b25ea4" width="1" height="1" alt="" style="opacity:0;">
<p>React 是一个用于构建用户界面的 JavaScript 库，由 Facebook 开发并维护。React 的特点主要体现在以下几个方面：</p>
<ol>
 <li>
  <p><strong>声明式（Declarative）</strong>：React 使你能够以一种声明的方式来描述你的 UI，这使得代码更加容易理解和维护。在 React 中，你只需要描述 UI 应该长什么样，React 会负责更新和渲染 DOM 来匹配你的描述。</p>
 </li>
 <li>
  <p><strong>组件化（Component-Based）</strong>：React 鼓励将 UI 拆分成可复用的组件。每个组件负责自己的 UI 和逻辑，这有助于构建大型应用时保持代码的清晰和可维护性。组件之间可以嵌套使用，形成完整的 UI 结构。</p>
 </li>
 <li>
  <p><strong>高效的性能</strong>：React 通过虚拟 DOM（Virtual DOM）技术来提高应用的性能。React 在内存中维护一个快速的轻量级 JavaScript 对象表示 DOM，在每次状态更新时，React 会比较新旧虚拟 DOM 的差异，并只将必要的更新应用到真实的 DOM 上，这大大减少了 DOM 操作的数量，提高了应用的性能。</p>
 </li>
 <li>
  <p><strong>灵活性</strong>：React 可以与各种库和框架配合使用，如 Redux、MobX 等状态管理库，以及 GraphQL、Apollo 等数据获取库。React 并不强制你使用特定的架构或设计模式，你可以根据自己的需求选择合适的工具和技术栈。</p>
 </li>
 <li>
  <p><strong>学习曲线</strong>：虽然 React 本身的核心概念相对简单，但构建一个高效、可维护的 React 应用需要掌握一些额外的概念和最佳实践，如 JSX、Props、State、组件生命周期、Hooks 等。因此，React 的学习曲线可能相对陡峭，但一旦掌握了这些核心概念，你将能够更高效地开发 React 应用。</p>
 </li>
 <li>
  <p><strong>强大的生态系统</strong>：React 拥有庞大的社区和丰富的生态系统，提供了大量的库、工具和框架来帮助你构建 React 应用。这些资源包括 UI 组件库（如 Material-UI、Ant Design）、路由库（如 React Router）、状态管理库（如 Redux、MobX）、构建工具（如 Webpack、Create React App）等。</p>
 </li>
 <li>
  <p><strong>跨平台</strong>：React Native 是 React 的一个分支，它允许你使用 React 的方式来开发原生移动应用（iOS 和 Android）。这意味着你可以使用相同的代码库来开发 Web 应用和移动应用，实现跨平台开发。</p>
 </li>
</ol>
<p>综上所述，React 因其声明式、组件化、高效性能、灵活性、强大的生态系统以及跨平台等特点而备受开发者青睐。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140737888</p>]]></description><guid isPermaLink="false">/archives/8ce2d371-b8c5-4dc1-9698-2425f1b25ea4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:06:22 GMT</pubDate></item><item><title><![CDATA[VUE3.0关于Performance(性能)]]></title><link>https://wiki.dgfygk.com/archives/ed10b8dd-49e3-43d4-8eed-056b670802e9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=VUE3.0%E5%85%B3%E4%BA%8EPerformance%28%E6%80%A7%E8%83%BD%29&amp;url=/archives/ed10b8dd-49e3-43d4-8eed-056b670802e9" width="1" height="1" alt="" style="opacity:0;">
<p>Vue 3.0 在性能方面进行了显著的改进和优化，这些改进使得 Vue 3.0 在处理大型应用时更加高效和快速。以下是 Vue 3.0 在性能方面的主要提升点：</p>
<h4>1. 响应式系统优化</h4>
<ul>
 <li><strong>Proxy 替代 Object.defineProperty</strong>：Vue 3.0 使用了基于 Proxy 的响应式系统，相比 Vue 2.x 中的 Object.defineProperty，Proxy 可以提供更高效的变更侦测和访问拦截。Proxy 不仅可以拦截属性的读取和设置，还可以拦截属性的添加、删除等操作，从而提供了更全面的响应式能力。</li>
 <li><strong>性能提升</strong>：Proxy 的使用使得 Vue 3.0 的响应式系统更加高效，特别是在处理复杂数据结构和嵌套对象时，能够显著减少性能开销。</li>
</ul>
<h4>2. 编译优化</h4>
<ul>
 <li><strong>静态模板提升（Static Template Hoisting）</strong>：Vue 3.0 引入了静态模板提升技术，将模板编译为更简洁、更高效的渲染函数。在编译过程中，Vue 会识别出模板中的静态部分（即不会随数据变化而变化的部分），并在渲染函数中将其提升为常量，避免在每次渲染时都重新创建这些静态节点。这种优化方式减少了不必要的运行时开销，提高了组件的渲染性能。</li>
</ul>
<h4>3. 虚拟 DOM 优化</h4>
<ul>
 <li><strong>静态标记（Static Marking）</strong>：Vue 3.0 在创建虚拟 DOM 时引入了静态标记，用于标识那些不会变化的节点。在后续的更新过程中，Vue 只会比较带有静态标记的节点，并跳过那些没有发生变化的节点，从而减少了不必要的 DOM 操作和渲染开销。</li>
 <li><strong>更快的 Diff 算法</strong>：Vue 3.0 采用了更高效的 Diff 算法来比较新旧虚拟 DOM 之间的差异。这种算法能够更快地计算出需要更新的节点，并生成相应的 DOM 操作指令。</li>
</ul>
<h4>4. 组件实例初始化优化</h4>
<ul>
 <li><strong>减少不必要的初始化工作</strong>：Vue 3.0 在组件实例初始化过程中进行了一系列优化，减少了不必要的初始化工作和内存开销。例如，通过将组件的配置项合并为单个对象，避免了 Vue 2.x 中的原型链查找操作。</li>
</ul>
<h4>5. Tree-shaking 支持</h4>
<ul>
 <li><strong>模块化代码结构</strong>：Vue 3.0 的代码结构更加模块化，支持更好的 Tree-shaking。这意味着在构建应用时，Webpack 等打包工具可以自动排除项目中没有使用的代码，从而减少最终打包体积。这对于提升应用的加载速度和运行性能非常有帮助。</li>
</ul>
<h4>6. 其他优化</h4>
<ul>
 <li><strong>事件侦听器缓存</strong>：Vue 3.0 对事件侦听器进行了缓存，避免在每次渲染时都重新绑定事件侦听器。这种优化方式在频繁操作事件的情况下尤为明显。</li>
 <li><strong>Fragment、Teleport 和 Suspense</strong>：Vue 3.0 引入了 Fragment、Teleport 和 Suspense 等新特性，这些特性提供了更灵活的组件结构和更平滑的用户体验。虽然它们本身并不直接提升性能，但通过减少不必要的 DOM 嵌套和提供更好的异步组件处理能力，间接地提升了应用的性能和用户体验。</li>
</ul>
<p>综上所述，Vue 3.0 在性能方面的提升主要体现在响应式系统优化、编译优化、虚拟 DOM 优化、组件实例初始化优化以及 Tree-shaking 支持等方面。这些优化使得 Vue 3.0 在处理大型应用时更加高效和快速。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140533495</p>]]></description><guid isPermaLink="false">/archives/ed10b8dd-49e3-43d4-8eed-056b670802e9</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 06:03:45 GMT</pubDate></item><item><title><![CDATA[VUE中setup（）]]></title><link>https://wiki.dgfygk.com/archives/4fa77dc5-ebc0-42a0-ac64-3fabe75c49a7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=VUE%E4%B8%ADsetup%EF%BC%88%EF%BC%89&amp;url=/archives/4fa77dc5-ebc0-42a0-ac64-3fabe75c49a7" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue中，<code>setup()</code> 函数是Vue 3.0及更高版本引入的一个重要特性，它是Composition API的入口点。<code>setup()</code> 函数用于初始化组件的状态和逻辑，包括定义响应式数据、方法和生命周期钩子。以下是关于<code>setup()</code> 函数的详细解释：</p>
<h4>1. 作用与特点</h4>
<ul>
 <li><strong>初始化组件</strong> ：<code>setup()</code> 函数用于初始化组件的状态和逻辑，替代了Vue 2.x中的<code>data()</code>、<code>methods()</code>、<code>computed()</code> 和 <code>watch()</code> 等选项。</li>
 <li><strong>响应式处理</strong> ：通过响应式API（如<code>ref</code>、<code>reactive</code>等）定义的数据在<code>setup()</code>函数中会被自动处理为响应式，从而能够在组件模板中自动更新。</li>
 <li><strong>性能优化</strong> ：<code>setup()</code> 函数的响应式性处理方式比Vue 2.x中的Options API更高效，因为它只计算变化的部分，从而提高了性能。</li>
 <li><strong>代码重用与组织</strong>：支持Composition API，使得逻辑可以在组件之间共享和重用，提高了代码的可维护性和可读性。</li>
 <li><strong>分离逻辑与模板</strong>：将组件的逻辑与模板的渲染过程分离，使得逻辑更易于测试和维护。</li>
</ul>
<h4>2. 使用方式</h4>
<ul>
 <li><strong>定义响应式数据</strong> ：通过<code>ref</code>或<code>reactive</code>等API定义响应式数据。</li>
 <li><strong>定义方法</strong> ：在<code>setup()</code>函数中定义的方法可以直接在模板中使用，但需要返回这些方法。</li>
 <li><strong>生命周期钩子</strong> ：虽然<code>setup()</code>函数自身不直接提供生命周期钩子，但可以通过<code>onMounted</code>、<code>onUpdated</code>等Composition API函数来访问生命周期钩子。</li>
 <li><strong>返回值</strong> ：<code>setup()</code> 函数需要返回一个对象，该对象中的属性和方法将被合并到组件的实例中，并可在模板中使用。</li>
</ul>
<h4>3. 注意事项</h4>
<ul>
 <li><strong>同步执行</strong> ：<code>setup()</code> 函数必须是同步的，不能是异步的。</li>
 <li><strong>无法访问<code>this</code></strong> ：在<code>setup()</code>函数中，由于它是在组件实例化之前执行的，因此无法访问组件实例（即<code>this</code>是<code>undefined</code>）。如果需要访问组件实例，可以通过<code>setup()</code>函数的第二个参数<code>context</code>来获取，但通常建议使用Composition API提供的函数。</li>
 <li><strong>使用场景</strong> ：<code>setup()</code> 函数通常用于定义复杂的组件逻辑，特别是当需要在多个组件之间共享逻辑时。对于简单的组件，可能不需要使用<code>setup()</code>函数。</li>
</ul>
<h4>4. 示例</h4>
<p>|—|————————————————–|
 <br>
 | | <code>&lt;template&gt;</code> |
 <br>
 | | <code>&lt;div&gt;{ `</code>{ count }} <code>| &lt;br /&gt;| |</code>
 <button @click="increment">Increment</button>
 <code>| &lt;br /&gt;| |</code> <code>| &lt;br /&gt;| | | &lt;br /&gt;| |</code>
 <script> <code>|
&lt;br /&gt;|   |</code>import { ref } from 'vue'; <code>|
&lt;br /&gt;|   |                                                  |
&lt;br /&gt;|   |</code>export default { <code>|
&lt;br /&gt;|   |</code>setup() { <code>|
&lt;br /&gt;|   |</code>const count = ref(0); <code>|
&lt;br /&gt;|   |</code>const increment = () => { <code>|
&lt;br /&gt;|   |</code>count.value++; <code>|
&lt;br /&gt;|   |</code>}; <code>|
&lt;br /&gt;|   |                                                  |
&lt;br /&gt;|   |</code>return { <code>|
&lt;br /&gt;|   |</code>count, <code>|
&lt;br /&gt;|   |</code>increment, <code>|
&lt;br /&gt;|   |</code>}; <code>|
&lt;br /&gt;|   |</code>}, <code>|
&lt;br /&gt;|   |</code>}; <code>|
&lt;br /&gt;|   |</code></script>
 ` |</p>
<p>在这个示例中，<code>setup()</code> 函数定义了一个响应式数据<code>count</code>和一个方法<code>increment</code>，然后通过返回值将它们暴露给模板。在模板中，可以直接使用<code>count</code>和<code>increment</code>。</p>
<p>综上所述，<code>setup()</code> 函数是Vue 3.0及更高版本中非常重要的一个特性，它提供了一种更灵活、更高效的方式来定义和管理组件的状态和逻辑。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140533543</p>]]></description><guid isPermaLink="false">/archives/4fa77dc5-ebc0-42a0-ac64-3fabe75c49a7</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 06:03:29 GMT</pubDate></item><item><title><![CDATA[模块与组件、模块化与组件化的理解]]></title><link>https://wiki.dgfygk.com/archives/e828b00f-2e98-4eaf-8139-4a1b4812e2a6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%A8%A1%E5%9D%97%E4%B8%8E%E7%BB%84%E4%BB%B6%E3%80%81%E6%A8%A1%E5%9D%97%E5%8C%96%E4%B8%8E%E7%BB%84%E4%BB%B6%E5%8C%96%E7%9A%84%E7%90%86%E8%A7%A3&amp;url=/archives/e828b00f-2e98-4eaf-8139-4a1b4812e2a6" width="1" height="1" alt="" style="opacity:0;">
<p>在React或其他现代JavaScript框架中，模块与组件、模块化与组件化是核心概念，它们对于提高代码的可维护性、复用性和开发效率具有重要意义。以下是对这些概念的理解：</p>
<h4>模块与组件</h4>
<p><strong>模块（Module）</strong></p>
<ul>
 <li><strong>定义</strong> ：模块是向外提供特定功能的JavaScript程序，通常是一个独立的文件（如<code>.js</code>文件）。它封装了数据、对数据的操作（函数）以及可能需要暴露的私有函数（通过对象的方式）。</li>
 <li><strong>目的</strong>：随着业务逻辑的增加，代码量会越来越多且变得复杂。模块化的目的是通过拆分代码到不同的模块中，来复用JavaScript代码、简化代码的编写，并提高JavaScript的运行效率。</li>
</ul>
<p><strong>组件（Component）</strong></p>
<ul>
 <li><strong>定义</strong>：组件是React应用程序的基本构建块，它代表了UI的一部分。组件是一个用于实现局部功能效果的代码和资源的集合，可以包含HTML、CSS、JavaScript以及图片等资源。</li>
 <li><strong>目的</strong>：一个界面的功能可能会变得非常复杂，通过使用组件，可以将复杂的界面拆分为多个简单、可复用的部分。这样做有助于复用编码、简化项目编码，并提高应用的运行效率。</li>
</ul>
<h4>模块化与组件化</h4>
<p><strong>模块化（Modularization）</strong></p>
<ul>
 <li><strong>定义</strong>：模块化是一种编码方式，它要求应用的JavaScript代码以模块的形式编写和组织。这意味着每个模块都负责提供特定的功能，并通过特定的接口（如exports和imports）与其他模块进行交互。</li>
 <li><strong>目的</strong>：模块化的主要目的是提高代码的可维护性、可读性和可测试性。通过将代码拆分为多个模块，可以更容易地理解和修改每个部分，同时也方便进行单元测试。</li>
</ul>
<p><strong>组件化（Componentization）</strong></p>
<ul>
 <li><strong>定义</strong>：组件化是一种将UI拆分为独立、可重用的部分的方法。在React中，这通常意味着将应用程序拆分为多个组件，每个组件都负责渲染UI的一部分，并可能包含自己的状态和逻辑。</li>
 <li><strong>目的</strong>：组件化的主要目的是提高代码的可维护性、复用性和开发效率。通过将UI拆分为多个组件，可以更容易地更新和维护界面，同时也可以在不同的地方重用相同的组件，从而节省开发时间。</li>
</ul>
<h4>总结</h4>
<ul>
 <li><strong>模块</strong>关注于功能的封装和复用，通常是一个独立的文件或代码块。</li>
 <li><strong>组件</strong>则更侧重于UI的呈现和交互，是React等框架中的基本概念。</li>
 <li><strong>模块化</strong>是项目编码的一种方式，要求代码以模块的形式编写和组织。</li>
 <li><strong>组件化</strong>则是一种将UI拆分为独立、可重用部分的方法，特别适用于React等面向组件编程的框架。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140737959</p>]]></description><guid isPermaLink="false">/archives/e828b00f-2e98-4eaf-8139-4a1b4812e2a6</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:03:14 GMT</pubDate></item><item><title><![CDATA[详细解读一下react的Nalink]]></title><link>https://wiki.dgfygk.com/archives/0ef921e2-80a5-4bb2-a1b2-d45dbdbf1ca9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E8%A7%A3%E8%AF%BB%E4%B8%80%E4%B8%8Breact%E7%9A%84Nalink&amp;url=/archives/0ef921e2-80a5-4bb2-a1b2-d45dbdbf1ca9" width="1" height="1" alt="" style="opacity:0;">
<p>首先，需要澄清一点，React本身并没有直接名为"Nalink"的组件或特性。我猜测这里可能是对React中某个概念或特性的误写，比如可能是指"Link”（在React Router中用于路由导航的组件）或者是与React生态系统中其他库或框架相关的某个特定功能。不过，为了提供一个有价值的回答，我将基于React事件处理的一般概念进行解释。</p>
<h4>React事件处理</h4>
<p>在React中，事件处理是构建交互式UI的重要组成部分。React通过事件系统允许你以声明式的方式来处理用户的输入，如点击、按键、鼠标移动等。以下是一些关于React事件处理的关键点：</p>
<h5>1. 事件绑定</h5>
<p>在React中，你不能像在传统HTML中那样直接在元素上添加事件监听器（如<code>onclick</code>）。相反，你需要在React组件的JSX中通过驼峰命名法（camelCase）来指定事件处理器，并将它们作为属性添加到元素上。例如，使用<code>onClick</code>而不是<code>onclick</code>。</p>
<pre><code class="hljs">function handleClick() {  
  console.log('Button was clicked!');  
}  

// 在JSX中绑定事件处理器  
&lt;button onClick={handleClick}&gt;Click me&lt;/button&gt;
</code></pre>
<h5>2. 事件对象</h5>
<p>在React的事件处理函数中，React会提供一个合成事件（SyntheticEvent）对象作为参数。这个对象与原生浏览器事件相似，但它在所有浏览器中都有相同的属性和方法，从而简化了跨浏览器兼容性处理。</p>
<pre><code class="hljs">function handleClick(event) {  
  event.preventDefault(); // 阻止默认行为  
  console.log(event.target); // 触发事件的元素  
}  

&lt;a href="#" onClick={handleClick}&gt;Click me&lt;/a&gt;
</code></pre>
<h5>3. 传递参数</h5>
<p>如果你在调用事件处理函数时需要传递额外的参数，你可以使用箭头函数来"包装"你的事件处理函数，并在其中传递这些参数。</p>
<pre><code class="hljs">function handleClick(id) {  
  console.log(`Button with ID ${id} was clicked!`);  
}  

// 使用箭头函数传递id参数  
&lt;button onClick={(e) =&gt; handleClick(1)}&gt;Click me&lt;/button&gt;
</code></pre>
<h5>4. 事件池</h5>
<p>React使用了一个名为事件池（Event Pooling）的优化技术来减少内存分配和垃圾收集的开销。这意味着React会重用合成事件对象，而不是为每个事件创建新的对象。这意呀着你不应该在异步代码（如setTimeout或请求回调）中访问事件对象的属性，因为那时事件对象可能已经被回收。</p>
<h5>5. 条件渲染中的事件处理器</h5>
<p>在条件渲染（如使用<code>if</code>语句或<code>&amp;&amp;</code>运算符）时，确保你的事件处理器不会在组件卸载后被调用。这通常不是问题，但如果你将事件处理器绑定到在渲染条件之外定义的元素上，则需要注意这一点。</p>
<h5>结论</h5>
<p>React的事件处理系统提供了一种声明式的方式来处理用户输入，并通过合成事件对象简化了跨浏览器兼容性处理。通过合理使用事件绑定、传递参数、理解事件池以及注意条件渲染中的事件处理器，你可以构建出响应用户操作的强大React应用。</p>
<p>至于"Nalink”，如果它确实是一个特定的库、组件或React生态系统中的概念，我建议查阅相关文档或搜索更多信息以获取准确的解答。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140738129</p>]]></description><guid isPermaLink="false">/archives/0ef921e2-80a5-4bb2-a1b2-d45dbdbf1ca9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:03:01 GMT</pubDate></item><item><title><![CDATA[vue路由的钩子函数]]></title><link>https://wiki.dgfygk.com/archives/b8a2cac0-933b-4645-83dd-bbb6f0724aa5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%E8%B7%AF%E7%94%B1%E7%9A%84%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0&amp;url=/archives/b8a2cac0-933b-4645-83dd-bbb6f0724aa5" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue.js中，当使用Vue Router进行页面路由管理时，你可以利用路由守卫（Route Guards）来实现路由的钩子函数。这些钩子函数允许你在路由发生变化时执行一些代码，比如权限验证、页面加载前的数据预获取等。Vue Router 提供了全局守卫、路由独享的守卫以及组件内的守卫。</p>
<h4>1. 全局守卫</h4>
<ul>
 <li><strong>beforeEach</strong>：全局前置守卫，在路由跳转前触发，每个路由跳转都会经过这个守卫。</li>
 <li><strong>beforeResolve</strong>：在解析守卫之后调用，即在所有嵌套路径的组件内守卫和异步组件被解析之后调用。</li>
 <li><strong>afterEach</strong>：全局后置钩子，路由跳转后触发，不接受 next 函数，也不会改变导航本身。</li>
</ul>
<h4>2. 路由独享的守卫</h4>
<ul>
 <li><strong>beforeEnter</strong>：在路由配置中直接定义，进入路由前调用，参数为 to, from, next。</li>
</ul>
<h4>3. 组件内的守卫</h4>
<ul>
 <li><strong>beforeRouteEnter</strong> ：在渲染该组件的对应路由被 confirm 前调用，此时组件实例还没被创建，不能获取组件实例 <code>this</code>。可以通过传一个回调给 <code>next</code> 来访问组件实例。</li>
 <li><strong>beforeRouteUpdate</strong> (2.2+)：在当前路由改变，但是该组件被复用时调用，例如，对于一个带有动态参数的路径 <code>/foo/:id</code>，在 <code>/foo/1</code> 和 <code>/foo/2</code> 之间跳转的时候，由于会渲染同样的 <code>Foo</code> 组件，因此这个守卫就被调用，而不是销毁后重新创建。</li>
 <li><strong>beforeRouteLeave</strong> ：导航离开该组件的对应路由时调用，可以访问组件实例 <code>this</code>。</li>
</ul>
<h4>示例</h4>
<h5>全局前置守卫 <code>beforeEach</code></h5>
<p>|—|————————————————————————-|
 <br>
 | | <code>router.beforeEach((to, from, next) =&gt; {</code> |
 <br>
 | | <code>// 权限验证等逻辑</code> |
 <br>
 | | <code>if (to.name !== 'Login' &amp;&amp; !isAuthenticated) next({ name: 'Login' })</code> |
 <br>
 | | <code>else next()</code> |
 <br>
 | | <code>})</code> |</p>
<h5>组件内的守卫 <code>beforeRouteEnter</code></h5>
<p>|—|—————————————-|
 <br>
 | | <code>export default {</code> |
 <br>
 | | <code>beforeRouteEnter (to, from, next) {</code> |
 <br>
 | | <code>// 在渲染该组件的对应路由被 confirm 前调用</code> |
 <br>
 | | <code>// 不！能！获取组件实例 `this`</code> |
 <br>
 | | <code>// 因为当守卫执行前，组件实例还没被创建</code> |
 <br>
 | | <code>next(vm =&gt; {</code> |
 <br>
 | | <code>// 通过 `vm` 访问组件实例</code> |
 <br>
 | | <code>})</code> |
 <br>
 | | <code>}</code> |
 <br>
 | | <code>}</code> |</p>
<h5>组件内的守卫 <code>beforeRouteLeave</code></h5>
<p>|—|——————————————————————————————–|
 <br>
 | | <code>export default {</code> |
 <br>
 | | <code>beforeRouteLeave (to, from, next) {</code> |
 <br>
 | | <code>// 导航离开该组件的对应路由时调用</code> |
 <br>
 | | <code>// 可以访问组件实例 `this`</code> |
 <br>
 | | <code>const answer = window.confirm('Do you really want to leave? you have unsaved changes!')</code> |
 <br>
 | | <code>if (answer) {</code> |
 <br>
 | | <code>next()</code> |
 <br>
 | | <code>} else {</code> |
 <br>
 | | <code>next(false)</code> |
 <br>
 | | <code>}</code> |
 <br>
 | | <code>}</code> |
 <br>
 | | <code>}</code> |</p>
<p>路由守卫是Vue Router中非常强大的功能，能够让你对路由的导航过程进行精细的控制。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140506468</p>]]></description><guid isPermaLink="false">/archives/b8a2cac0-933b-4645-83dd-bbb6f0724aa5</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 06:02:21 GMT</pubDate></item><item><title><![CDATA[请解释vue的单页面应用是什么及其优缺点]]></title><link>https://wiki.dgfygk.com/archives/765fc67b-9bcf-4b8f-b42b-148dd428cb4a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%B7%E8%A7%A3%E9%87%8Avue%E7%9A%84%E5%8D%95%E9%A1%B5%E9%9D%A2%E5%BA%94%E7%94%A8%E6%98%AF%E4%BB%80%E4%B9%88%E5%8F%8A%E5%85%B6%E4%BC%98%E7%BC%BA%E7%82%B9&amp;url=/archives/765fc67b-9bcf-4b8f-b42b-148dd428cb4a" width="1" height="1" alt="" style="opacity:0;">
<p>Vue的单页面应用（Single Page Application, SPA）是一种使用Vue.js框架构建的Web应用程序，它通过动态加载内容来更新页面，而不是每次用户导航时重新加载整个页面。以下是对Vue单页面应用的详细解释及其优缺点的归纳：</p>
<h4>Vue单页面应用是什么</h4>
<p>Vue单页面应用是一种Web应用程序的架构模式，它采用Vue.js框架来实现。在这种模式下，整个应用程序只有一个HTML页面，在加载时会将所有需要的资源（如CSS、JavaScript和图片等）下载到浏览器中。然后，通过使用前端路由（如Vue Router）来实现页面之间的导航，当用户点击链接或执行其他操作时，页面内容会以异步方式加载，并通过Vue.js来动态更新。这种方式使得用户可以在不刷新整个页面的情况下，流畅地在不同页面或视图之间切换，从而提供更好的用户体验。</p>
<h4>优点</h4>
<ol>
 <li><strong>用户体验好</strong> ：</li>
 <li><strong>更快的响应速度</strong>：由于只更新部分页面内容，不需要重新加载整个页面，因此用户操作时的响应速度更快。</li>
 <li><strong>流畅的页面切换</strong>：通过前端路由实现页面跳转，可以在不刷新整个页面的情况下切换视图，提供更好的用户交互体验。</li>
 <li><strong>性能高</strong> ：</li>
 <li><strong>减少服务器负载和网络传输量</strong>：由于只加载所需的组件和数据，减少了服务器负载和网络传输量。</li>
 <li><strong>资源缓存</strong>：页面资源如JavaScript、CSS等只需加载一次，之后可以缓存，提高后续访问速度。</li>
 <li><strong>开发效率高</strong> ：</li>
 <li><strong>组件化开发</strong>：Vue提供了组件化的开发模式，可以将页面拆分为多个可重用的组件，提高代码的复用性和可维护性。</li>
 <li><strong>状态管理</strong>：Vuex等状态管理库可以帮助开发者在全局范围内管理状态，使得状态管理更加集中和高效。</li>
 <li><strong>前后端分离</strong> ：</li>
 <li>前端使用Vue作为主要的开发工具，后端只需要提供RESTful API接口即可，简化了项目设计和开发流程。</li>
 <li><strong>跨平台开发</strong> ：</li>
 <li>由于SPA使用前端技术构建，可以跨平台运行在各种设备上，如桌面浏览器、移动浏览器和原生应用等。</li>
</ol>
<h4>缺点</h4>
<ol>
 <li><strong>SEO不友好</strong> ：</li>
 <li>由于大量的内容是通过JavaScript动态加载的，搜索引擎难以爬取有效信息，影响优化效果。不过，可以通过服务器端渲染（SSR）或预渲染等技术手段来解决这个问题。</li>
 <li><strong>首次加载较慢</strong> ：</li>
 <li>SPA需要加载整个应用的JavaScript和CSS，以及初始化相关的框架和运行环境，因此首次加载时间较长。可以通过代码分割、懒加载等技术手段来优化加载时间。</li>
 <li><strong>内存占用较高</strong> ：</li>
 <li>SPA需要在浏览器中保持整个应用的状态，因此占用的内存较多。对于内存较小的设备或浏览器，可能会影响性能。</li>
 <li><strong>对浏览器的前进后退功能依赖较大</strong> ：</li>
 <li>SPA的页面跳转是通过前端路由实现的，因此对浏览器的前进后退功能有较大的依赖。如果浏览器的前进后退功能出现问题，可能会影响SPA的正常使用。</li>
</ol>
<p>综上所述，Vue的单页面应用具有用户体验好、性能高、开发效率高等优点，但也存在SEO不友好、首次加载较慢等缺点。在选择使用SPA架构时，需要根据具体项目需求和场景权衡利弊。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140506514</p>]]></description><guid isPermaLink="false">/archives/765fc67b-9bcf-4b8f-b42b-148dd428cb4a</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 06:01:35 GMT</pubDate></item><item><title><![CDATA[什么是diff算法？]]></title><link>https://wiki.dgfygk.com/archives/45aae7bf-2440-4ae2-ad80-b3284a8c01bb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AFdiff%E7%AE%97%E6%B3%95%EF%BC%9F&amp;url=/archives/45aae7bf-2440-4ae2-ad80-b3284a8c01bb" width="1" height="1" alt="" style="opacity:0;">
<p>Diff算法，全称为Difference算法，是一种用于比较和查找两个对象（如文本、源代码、数据结构或任何形式的字符串）之间差异的算法。它在多个领域有着广泛的应用，包括但不限于前端开发、版本控制系统、协同编辑工具等。以下是Diff算法的详细解释：</p>
<h4>1. 定义与工作原理</h4>
<ul>
 <li><strong>定义</strong>：Diff算法通过比较两个对象（如两个文本文件、两个虚拟DOM树等）来找出它们之间的差异，并根据这些差异生成一个补丁文件或差异描述，以便能够将这些差异应用到另一个对象上。</li>
 <li><strong>工作原理</strong>：Diff算法的基本思想是比较两个对象的元素（如文本中的行、代码中的节点等），找出它们之间的不同之处，并记录下来。这些差异可能包括添加、删除或修改操作。</li>
</ul>
<h4>2. 在前端开发中的应用</h4>
<p>在前端开发领域，特别是在React、Vue等现代JavaScript框架中，Diff算法被广泛应用于虚拟DOM的更新过程中。具体来说：</p>
<ul>
 <li><strong>虚拟DOM对比</strong>：当应用的状态发生变化时，框架会首先根据新的状态构建出一个新的虚拟DOM树。然后，它会使用Diff算法来比较这个新的虚拟DOM树与旧的虚拟DOM树之间的差异。</li>
 <li><strong>生成Patch</strong>：通过Diff算法，框架能够识别出哪些部分的DOM需要被更新（例如，哪些节点被添加、删除或修改了）。然后，它会根据这些差异生成一个Patch对象，该对象包含了所有必要的DOM更新操作。</li>
 <li><strong>应用Patch</strong>：最后，框架会将这个Patch应用到真实的DOM上，只更新那些确实需要改变的部分，而不是重新渲染整个页面。这样做可以显著提高应用的性能。</li>
</ul>
<h4>3. Diff算法的特点</h4>
<ul>
 <li><strong>只比较同级元素</strong>：在虚拟DOM中，Diff算法通常只会在同层级节点之间进行比较，而不会跨层级比较。这意味着不同层级的节点只有创建和删除操作。</li>
 <li><strong>优化策略</strong>：为了提高效率，Diff算法采用了一些优化策略。例如，在比较子节点时，如果两个节点的子节点数量不同，则可能直接替换整个节点；如果节点有key属性，则可以使用key来优化子节点的比较过程。</li>
 <li><strong>性能优势</strong>：通过减少不必要的DOM操作，Diff算法能够显著提高应用的渲染性能。这是因为它只更新那些确实需要改变的部分，而不是重新渲染整个页面或组件。</li>
</ul>
<h4>4. 经典算法与改进</h4>
<ul>
 <li><strong>经典算法</strong>：经典的Diff算法包括贪婪算法和最长公共子序列（LCS）算法。这些算法通过比较两个文本之间的行或字符来查找差异，并为每个差异分配一个操作（插入、删除或修改）。</li>
 <li><strong>改进算法</strong>：随着技术的发展，出现了一些更快速、更高效的Diff算法改进版。例如，Myers算法通过使用动态规划的方式以线性时间复杂度计算出两个文本之间的差异；二进制算法将文本视为二进制序列进行处理；基于哈希的算法使用哈希函数将文本划分为块并比较哈希值以快速识别差异位置；基于语义的算法则考虑了语义上的差异而不仅仅是字符级别的差异。</li>
</ul>
<h4>5. 应用领域</h4>
<p>除了前端开发中的虚拟DOM更新外，Diff算法还广泛应用于版本控制系统（如Git）、协同编辑工具、图像比较、数据同步、文档比较等多个领域。在这些领域中，Diff算法都发挥着识别差异、生成补丁或差异文件以及应用这些差异的重要作用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140533463</p>]]></description><guid isPermaLink="false">/archives/45aae7bf-2440-4ae2-ad80-b3284a8c01bb</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:00:55 GMT</pubDate></item><item><title><![CDATA[说一下你对dom驱动和数据驱动的理解]]></title><link>https://wiki.dgfygk.com/archives/aca3771d-4737-40f6-9b7e-62432fdb56f7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%B4%E4%B8%80%E4%B8%8B%E4%BD%A0%E5%AF%B9dom%E9%A9%B1%E5%8A%A8%E5%92%8C%E6%95%B0%E6%8D%AE%E9%A9%B1%E5%8A%A8%E7%9A%84%E7%90%86%E8%A7%A3&amp;url=/archives/aca3771d-4737-40f6-9b7e-62432fdb56f7" width="1" height="1" alt="" style="opacity:0;">
<p>DOM驱动和数据驱动是在前端开发中常见的两种编程思想，它们主要指导着应用程序如何响应用户输入和更新界面。</p>
<ol>
 <li>
  <p>DOM驱动：</p>
 </li>
 <li>
  <p>定义：DOM（Document Object Model，文档对象模型）驱动的编程方式侧重于直接操作页面的元素和结构。</p>
 </li>
 <li>
  <p>特点：传统的JavaScript开发方式通常是DOM驱动的，通过直接操作DOM元素来改变页面的内容、样式和行为。例如，通过 `document.getElementById('someElement').innerHTML = 'New content';` 直接修改页面内容。</p>
 </li>
 <li>
  <p>优点：操作直观，适用于简单的交互和动态效果。</p>
 </li>
 <li>
  <p>数据驱动：</p>
 </li>
 <li>
  <p>定义：数据驱动的编程方式则更专注于维护数据和界面之间的关系，通过绑定数据到界面元素来自动更新视图。</p>
 </li>
 <li>
  <p>特点：现代的前端框架（如Vue.js、React等）通常采用数据驱动的方式。这种方式将应用的状态和视图分离，通过定义数据模型和声明式的模板来描述界面，框架负责根据数据的变化自动更新DOM。</p>
 </li>
 <li>
  <p>优点：提高了开发效率，减少了手动操作DOM的复杂性和可能的错误，使得代码更易于维护和扩展。</p>
 </li>
</ol>
<p>在数据驱动的框架中，开发者可以专注于管理数据的变化和业务逻辑，而不必过多关注DOM操作的细节。这种方式通过响应式的数据绑定机制，实现了更高效的页面更新和更好的用户体验。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140388901</p>]]></description><guid isPermaLink="false">/archives/aca3771d-4737-40f6-9b7e-62432fdb56f7</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:00:43 GMT</pubDate></item><item><title><![CDATA[详细说一下axios的特点]]></title><link>https://wiki.dgfygk.com/archives/99bfef68-b0b3-4e09-9d2e-37880eda3330</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E8%AF%B4%E4%B8%80%E4%B8%8Baxios%E7%9A%84%E7%89%B9%E7%82%B9&amp;url=/archives/99bfef68-b0b3-4e09-9d2e-37880eda3330" width="1" height="1" alt="" style="opacity:0;">
<p>Axios 是一个基于 Promise 的 HTTP 客户端，用于浏览器和 node.js 环境。它提供了一种简单的方式来发送 HTTP 请求，并处理响应。Axios 因其灵活性和易用性在前端开发中非常受欢迎，特别是在需要与 RESTful API 交互的 Web 应用中。以下是 Axios 的一些主要特点：</p>
<ol>
 <li>
  <p><strong>基于 Promise</strong> ：
   <br>
   Axios 返回的是一个 Promise 对象，这意味着你可以使用 <code>.then()</code> 和 <code>.catch()</code> 方法来处理异步请求的成功和失败情况，或者使用 async/await 语法来编写更加简洁的异步代码。</p>
 </li>
 <li>
  <p><strong>支持浏览器和 node.js</strong> ：
   <br>
   Axios 可以在浏览器中运行，也可以在 node.js 环境中使用，无需进行任何修改。这使得它成为了一个跨平台的 HTTP 客户端解决方案。</p>
 </li>
 <li>
  <p><strong>请求和响应的拦截</strong> ：
   <br>
   Axios 允许你在请求或响应被 <code>then</code> 或 <code>catch</code> 处理之前拦截它们。这可以用来在请求发送前修改请求数据，或在响应数据传递给处理程序之前进行预处理。</p>
 </li>
 <li>
  <p><strong>自动转换 JSON 数据</strong> ：
   <br>
   默认情况下，Axios 会自动将 JavaScript 对象序列化为 JSON 字符串，并作为请求体发送给服务器。同时，它也会自动将接收到的 JSON 字符串解析为 JavaScript 对象。</p>
 </li>
 <li>
  <p><strong>客户端支持防御 XSRF</strong> ：
   <br>
   在浏览器中，Axios 提供了自动添加 CSRF (跨站请求伪造) 令牌的功能，以保护你的 API 免受恶意攻击。这通常通过读取浏览器中的 cookie 来实现。</p>
 </li>
 <li>
  <p><strong>请求取消</strong> ：
   <br>
   Axios 提供了取消正在进行的 HTTP 请求的能力。这可以通过传递一个取消令牌（cancel token）到请求配置中来实现，然后使用该令牌来取消请求。</p>
 </li>
 <li>
  <p><strong>请求和响应的转换</strong> ：
   <br>
   除了自动的 JSON 转换外，Axios 还允许你自定义请求和响应的转换逻辑。这可以通过配置请求和响应的转换器来实现，允许你进行更复杂的数据处理。</p>
 </li>
 <li>
  <p><strong>HTTP 方法的别名</strong> ：
   <br>
   Axios 提供了 <code>get</code>、<code>post</code>、<code>put</code>、<code>delete</code> 等 HTTP 方法的别名，这使得发送不同类型的请求变得更加直观和方便。</p>
 </li>
 <li>
  <p><strong>请求配置</strong> ：
   <br>
   Axios 允许你通过配置对象来高度自定义请求，包括 URL、方法、头部、超时时间、认证信息等。这使得 Axios 非常灵活，可以满足各种复杂的 HTTP 请求需求。</p>
 </li>
 <li>
  <p><strong>响应数据</strong> ：
   <br>
   Axios 的响应对象包含了非常丰富的信息，如状态码、状态信息、响应头、响应数据等。这使得处理响应变得非常简单和直接。</p>
 </li>
</ol>
<p>综上所述，Axios 因其易用性、灵活性、跨平台性以及强大的功能集，在前端开发中得到了广泛的应用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140506371</p>]]></description><guid isPermaLink="false">/archives/99bfef68-b0b3-4e09-9d2e-37880eda3330</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 06:00:04 GMT</pubDate></item><item><title><![CDATA[详细说一下虚拟DOM的工作原理]]></title><link>https://wiki.dgfygk.com/archives/b750897a-f836-4559-b35e-66e7a387484c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E8%AF%B4%E4%B8%80%E4%B8%8B%E8%99%9A%E6%8B%9FDOM%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86&amp;url=/archives/b750897a-f836-4559-b35e-66e7a387484c" width="1" height="1" alt="" style="opacity:0;">
<p>虚拟DOM（Virtual DOM）是一种编程概念，主要用于Web开发中，特别是在React、Vue等前端框架中广泛应用。它是对真实DOM（文档对象模型）的一种抽象表示，以JavaScript对象的形式存在。虚拟DOM的工作原理可以详细分为以下几个步骤：</p>
<h4>1. 创建虚拟DOM</h4>
<ul>
 <li><strong>初始渲染</strong>：首先，通过JavaScript对象（即虚拟DOM）表示整个页面的结构。这个虚拟DOM是一个轻量级的映射，它保存着真实DOM的层次结构和信息。这个过程并不是直接操作真实的DOM，而是构建了一个JavaScript对象来模拟DOM的结构。</li>
 <li><strong>成本较低</strong>：与直接创建真实DOM相比，创建虚拟DOM的成本更低，因为它不需要进行实际的DOM操作，只是在内存中构建了一个JavaScript对象树。</li>
</ul>
<h4>2. 更新状态</h4>
<ul>
 <li><strong>状态变化</strong>：当应用程序的状态发生变化时，比如用户交互或数据更新，虚拟DOM会被修改。这个过程是在内存中进行的，不涉及真实的DOM操作，因此非常高效。</li>
</ul>
<h4>3. 生成新的虚拟DOM</h4>
<ul>
 <li><strong>反映新状态</strong>：在状态变化后，会生成一个新的虚拟DOM，这个新的虚拟DOM会反映出更新后的状态。新虚拟DOM的生成也是在内存中完成的，不会立即影响到页面上的真实DOM。</li>
</ul>
<h4>4. 对比和更新</h4>
<ul>
 <li><strong>Diffing过程</strong>：将新的虚拟DOM与旧的虚拟DOM进行对比，找出它们之间的差异（这个过程被称为"diffing”）。由于虚拟DOM只是一棵轻量级的JavaScript对象树，因此这个对比过程是非常高效的。</li>
 <li><strong>确定差异</strong>：通过对比，可以确定哪些部分的DOM需要被更新。这个过程中，虚拟DOM内部会使用高效的算法来找出差异，以最小化需要更新的DOM部分。</li>
</ul>
<h4>5. 生成变更操作</h4>
<ul>
 <li><strong>生成操作</strong>：在对比过程中，找出需要更新的部分后，会生成相应的DOM操作（如添加、删除、修改节点等）。这些操作是以JavaScript函数或命令的形式存在的，准备应用到真实的DOM上。</li>
</ul>
<h4>6. 应用变更</h4>
<ul>
 <li><strong>更新真实DOM</strong>：最后，将生成的DOM操作应用到真实的DOM上，只更新需要变更的部分，而不是整个页面重新渲染。这样可以减少DOM操作的次数和计算量，提高页面的渲染效率。</li>
</ul>
<h4>总结</h4>
<p>虚拟DOM的工作原理主要包括创建虚拟DOM、更新状态、生成新的虚拟DOM、对比和更新、生成变更操作以及应用变更等步骤。通过这个过程，虚拟DOM能够最小化对真实DOM的操作，减少不必要的重绘和回流，从而提高Web应用程序的性能和渲染效率。同时，虚拟DOM还提供了方便的抽象和操作接口，使得开发者可以更方便地管理和维护页面的状态和交互。</p>
<h4>虚拟DOM的优势</h4>
<ul>
 <li><strong>性能优化</strong>：通过只更新需要变更的部分，减少了不必要的DOM操作，提高了页面渲染效率。</li>
 <li><strong>跨平台兼容性</strong>：虚拟DOM不依赖于特定的浏览器或平台，因此可以在不同的环境中运行。</li>
 <li><strong>便于测试和调试</strong>：由于虚拟DOM是以JavaScript对象的形式存在的，因此可以很方便地进行测试和调试。</li>
</ul>
<h4>虚拟DOM的劣势</h4>
<ul>
 <li><strong>额外的内存开销</strong>：由于需要创建虚拟DOM对象，因此会占用一定的内存空间。</li>
 <li><strong>初次渲染可能较慢</strong>：由于需要进行虚拟DOM的创建和比较等操作，因此在初次渲染时可能会比直接操作真实DOM慢一些。然而，在后续的更新和渲染中，虚拟DOM的优势会逐渐显现出来。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140533424</p>]]></description><guid isPermaLink="false">/archives/b750897a-f836-4559-b35e-66e7a387484c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 05:59:35 GMT</pubDate></item><item><title><![CDATA[MVVM和MVC的原理以及它们的区别]]></title><link>https://wiki.dgfygk.com/archives/4d28bc4c-bdcc-4158-a2cb-3ca09ea9773d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MVVM%E5%92%8CMVC%E7%9A%84%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8A%E5%AE%83%E4%BB%AC%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/4d28bc4c-bdcc-4158-a2cb-3ca09ea9773d" width="1" height="1" alt="" style="opacity:0;">
<p>MVVM（Model-View-ViewModel）和 MVC（Model-View-Controller）是两种常见的前端架构模式，它们都旨在帮助组织和管理复杂的前端应用程序逻辑和视图层。</p>
<h4>MVC（Model-View-Controller）</h4>
<ol>
 <li>
  <p><strong>原理</strong>：</p>
 </li>
 <li>
  <p><strong>模型（Model）</strong>：表示应用程序的数据结构和业务逻辑。模型通过与数据库通信来获取数据。</p>
 </li>
 <li>
  <p><strong>视图（View）</strong>：负责将模型的数据呈现给用户，并接收用户的输入，然后将用户输入传递给控制器处理。</p>
 </li>
 <li>
  <p><strong>控制器（Controller）</strong>：处理用户的输入（例如点击、输入等），根据输入更新模型，并选择性地将视图更新为新的模型状态。</p>
 </li>
 <li>
  <p><strong>区别</strong>：</p>
 </li>
 <li>
  <p><strong>分工明确</strong>：MVC中的控制器负责处理用户输入和状态变化，更新模型，并告知视图更新。视图只负责数据的展示。模型则是应用程序的数据管理者。</p>
 </li>
 <li>
  <p><strong>传统 Web 应用</strong>：MVC最初是为传统的 Web 应用开发设计的，但随着前端框架和单页面应用的兴起，MVVM也逐渐流行起来。</p>
 </li>
</ol>
<h4>MVVM（Model-View-ViewModel）</h4>
<ol>
 <li>
  <p><strong>原理</strong>：</p>
 </li>
 <li>
  <p><strong>模型（Model）</strong>：与MVC中的模型相同，表示应用程序的数据和业务逻辑。</p>
 </li>
 <li>
  <p><strong>视图（View）</strong>：用户界面的结构和布局，负责将视图模型绑定的数据渲染到用户界面上。</p>
 </li>
 <li>
  <p><strong>视图模型（ViewModel）</strong>：连接视图和模型的中间件，管理视图的状态和行为，处理用户交互，并根据需要更新模型的数据。</p>
 </li>
 <li>
  <p><strong>区别</strong>：</p>
 </li>
 <li>
  <p><strong>数据绑定</strong>：MVVM中视图和视图模型之间通过数据绑定实现自动同步，视图模型不需要显式控制视图的更新。</p>
 </li>
 <li>
  <p><strong>前端框架</strong>：MVVM更适合现代前端框架（如Vue.js、React等），这些框架提供了数据绑定和虚拟DOM等功能，可以更高效地管理复杂的用户界面。</p>
 </li>
</ol>
<h4>总结区别：</h4>
<ul>
 <li><strong>MVC</strong>强调控制器的作用，负责处理用户输入和应用逻辑，视图负责展示，模型负责数据管理。</li>
 <li><strong>MVVM</strong>则更加关注数据驱动视图的变化，通过视图模型处理用户交互和视图状态，模型则负责业务逻辑和数据管理。</li>
</ul>
<p>在选择使用MVC或者MVVM时，可以根据项目的需求、团队的熟悉程度以及前端框架的支持来决定。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140362592</p>]]></description><guid isPermaLink="false">/archives/4d28bc4c-bdcc-4158-a2cb-3ca09ea9773d</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 05:45:02 GMT</pubDate></item><item><title><![CDATA[promise中reject和catch处理上有什么区别]]></title><link>https://wiki.dgfygk.com/archives/f9ceacb1-b3ed-43e8-b697-1c342db7a17f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=promise%E4%B8%ADreject%E5%92%8Ccatch%E5%A4%84%E7%90%86%E4%B8%8A%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB&amp;url=/archives/f9ceacb1-b3ed-43e8-b697-1c342db7a17f" width="1" height="1" alt="" style="opacity:0;">
<p>在 Promise 中，<code>reject</code> 和 <code>catch</code> 是处理异步操作失败的两种方式，它们有一些重要的区别：</p>
<ol>
 <li>
  <p><strong><code>reject</code></strong>：</p>
 </li>
 <li>
  <p><code>reject</code> 是 Promise 实例的一个方法，用于显式地将 Promise 状态从 <code>pending</code> 变为 <code>rejected</code>，并传递一个拒绝的原因（通常是一个 Error 对象或者一个描述失败情况的值）。</p>
 </li>
 <li>
  <p>一旦 Promise 进入 <code>rejected</code> 状态，就会触发该 Promise 链中紧跟在 <code>reject</code> 后面的第一个 <code>catch</code> 方法或者接下来的 <code>then</code> 方法中的失败处理函数（如果有的话）。</p>
  <p>示例：</p>
  <p>let promise = new Promise((resolve, reject) =&gt; {</p>
  <pre><code>   reject(new Error('Something went wrong'));
</code></pre>
  <p>});</p>
  <p>promise.catch(error =&gt; {</p>
  <pre><code>   console.error(error);
</code></pre>
  <p>});</p>
 </li>
 <li>
  <p><strong><code>catch</code></strong>：</p>
 </li>
 <li>
  <p><code>catch</code> 是 Promise 实例的一个方法，用于注册在 Promise 链中捕获处理异常情况的函数。它只捕获 Promise 状态为 <code>rejected</code> 的情况。</p>
 </li>
 <li>
  <p><code>catch</code> 方法接收一个回调函数，该函数在 Promise 进入 <code>rejected</code> 状态时被调用，并且可以访问到被 <code>reject</code> 传递的拒绝原因。</p>
  <p>示例：</p>
  <p>let promise = new Promise((resolve, reject) =&gt; {</p>
  <pre><code>   throw new Error('Something went wrong');
</code></pre>
  <p>});</p>
  <p>promise.catch(error =&gt; {</p>
  <pre><code>   console.error(error);
</code></pre>
  <p>});</p>
 </li>
</ol>
<h4>区别总结：</h4>
<ul>
 <li>
  <p><strong>触发时机</strong>：</p>
 </li>
 <li>
  <p><code>reject</code> 在 Promise 执行过程中主动调用，显式地将 Promise 状态变为 <code>rejected</code>。</p>
 </li>
 <li>
  <p><code>catch</code> 是注册在 Promise 链中的方法，用于捕获到达 <code>rejected</code> 状态的 Promise。</p>
 </li>
 <li>
  <p><strong>用法</strong>：</p>
 </li>
 <li>
  <p><code>reject</code> 用于在 Promise 执行过程中处理错误并将其传递给后续处理程序。</p>
 </li>
 <li>
  <p><code>catch</code> 用于注册在 Promise 链中捕获错误的回调函数。</p>
 </li>
 <li>
  <p><strong>链式调用</strong>：</p>
 </li>
 <li>
  <p><code>reject</code> 是 Promise 执行器函数中的一个操作，不能像 <code>catch</code> 一样链式调用，因为它并不返回一个 Promise 对象。</p>
 </li>
 <li>
  <p><code>catch</code> 可以链式调用，允许在 Promise 链中处理多个步骤可能出现的错误。</p>
 </li>
</ul>
<p>在实际应用中，通常建议使用 <code>catch</code> 方法来处理 Promise 链中的异常，因为它能够更清晰地将错误处理与链式调用分离开来，使得代码更易读和维护。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140362610</p>]]></description><guid isPermaLink="false">/archives/f9ceacb1-b3ed-43e8-b697-1c342db7a17f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 05:44:44 GMT</pubDate></item><item><title><![CDATA[Vue的生命周期函数有哪些？详细说明]]></title><link>https://wiki.dgfygk.com/archives/102e9b07-5b2b-45cb-94a2-2ac8934d5b10</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%87%BD%E6%95%B0%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%9F%E8%AF%A6%E7%BB%86%E8%AF%B4%E6%98%8E&amp;url=/archives/102e9b07-5b2b-45cb-94a2-2ac8934d5b10" width="1" height="1" alt="" style="opacity:0;">
<p>Vue.js 的生命周期函数包括以下几个阶段，每个阶段都有相应的钩子函数可以用来在特定时机执行自定义的逻辑。这些生命周期钩子函数使得我们可以在组件的不同阶段进行操作，从而管理组件的状态和行为。</p>
<ol>
 <li>
  <p>beforeCreate：</p>
 </li>
 <li>
  <p>描述：在实例初始化之后，数据观测 (data observer) 和 event/watcher 事件配置之前被调用。</p>
 </li>
 <li>
  <p>使用场景：在此阶段无法访问 `data`、`methods`、`computed` 以及 `props`，适合执行一些初始化逻辑，如设置初始数据。</p>
 </li>
 <li>
  <p>created：</p>
 </li>
 <li>
  <p>描述：实例已经创建完成之后被调用。在这一步，实例已完成以下的配置：数据观测 (data observer)，属性和方法的运算，watch/event 事件回调。然而，挂载阶段还没开始，$el 属性目前不可见。</p>
 </li>
 <li>
  <p>使用场景：适合在这里做一些初始化的异步操作，如请求后端数据、订阅事件等。</p>
 </li>
 <li>
  <p>beforeMount：</p>
 </li>
 <li>
  <p>描述：在挂载开始之前被调用：相关的 `render` 函数首次被调用。</p>
 </li>
 <li>
  <p>使用场景：通常在这里可以访问组件的 `render` 函数中的虚拟 DOM，可以在渲染之前最后一次修改组件的数据。</p>
 </li>
 <li>
  <p>mounted：</p>
 </li>
 <li>
  <p>描述：el 被新创建的 vm.$el 替换，并挂载到实例上去之后调用该钩子函数。此时组件已经渲染完成，真实 DOM 已经插入文档中。</p>
 </li>
 <li>
  <p>使用场景：可以在这里执行操作，如 DOM 操作、初始化第三方库等，因为此时可以访问到真实的 DOM。</p>
 </li>
 <li>
  <p>beforeUpdate：</p>
 </li>
 <li>
  <p>描述：数据更新时调用，发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子函数中进一步地更改状态，不会触发附加的重渲染过程。</p>
 </li>
 <li>
  <p>使用场景：适合在更新之前访问现有的 DOM，比如手动移除已添加的事件监听器。</p>
 </li>
 <li>
  <p>updated：</p>
 </li>
 <li>
  <p>描述：由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。调用时，组件 DOM 已经更新，所以可以执行依赖于 DOM 的操作。</p>
 </li>
 <li>
  <p>使用场景：适合执行一些需要在 DOM 变化后进行的操作，如数据的同步更新。</p>
 </li>
 <li>
  <p>beforeUnmount (Vue 3.x 中新增，替代了 beforeDestroy)：</p>
 </li>
 <li>
  <p>描述：在实例销毁之前调用。在这一步，实例仍然完全可用。</p>
 </li>
 <li>
  <p>使用场景：适合在这里做一些清理操作，如清除定时器、取消订阅等。</p>
 </li>
 <li>
  <p>unmounted (Vue 3.x 中新增，替代了 destroyed)：</p>
 </li>
 <li>
  <p>描述：在 Vue 实例销毁后调用。此时，所有绑定的事件监听器和子实例都已被移除。</p>
 </li>
 <li>
  <p>使用场景：可以在这里进行最终的清理工作和资源释放，确保不会出现内存泄漏等问题。</p>
 </li>
</ol>
<p>这些生命周期函数提供了在组件生命周期不同阶段执行自定义逻辑的能力，帮助开发者更好地管理组件的状态、资源和行为。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140388937</p>]]></description><guid isPermaLink="false">/archives/102e9b07-5b2b-45cb-94a2-2ac8934d5b10</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 05:44:30 GMT</pubDate></item><item><title><![CDATA[vue3.0所采用的composition Api与vue2.x使用的Option Api有什么区别]]></title><link>https://wiki.dgfygk.com/archives/03d9a74b-c404-45c4-be0c-a6ab0bec5e81</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue3.0%E6%89%80%E9%87%87%E7%94%A8%E7%9A%84composition%20Api%E4%B8%8Evue2.x%E4%BD%BF%E7%94%A8%E7%9A%84Option%20Api%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB&amp;url=/archives/03d9a74b-c404-45c4-be0c-a6ab0bec5e81" width="1" height="1" alt="" style="opacity:0;">
<p>Vue 3.0 引入了 Composition API，与 Vue 2.x 使用的 Options API 相比，有几个重要的区别和优势：</p>
<ol>
 <li>
  <p><strong>代码组织方式：</strong></p>
 </li>
 <li>
  <p><strong>Options API（Vue 2.x）：</strong> 将相关功能的代码组织在一个对象中（如 <code>data</code>、<code>methods</code>、<code>computed</code>、<code>watch</code> 等），但随着组件逻辑增长，同一个功能的代码可能会分散在不同选项中。</p>
 </li>
 <li>
  <p><strong>Composition API（Vue 3.0）：</strong> 允许根据功能组织代码，而不是选项。你可以根据需要将相关代码封装在一个函数中，并通过 <code>setup()</code> 函数进行组合。这种方式更加灵活和可维护，特别是对于大型组件和复用逻辑。</p>
 </li>
 <li>
  <p><strong>逻辑复用和组件组合：</strong></p>
 </li>
 <li>
  <p><strong>Options API：</strong> 逻辑复用通过 mixins 和自定义指令等实现，但可能导致命名冲突和难以理解的代码。</p>
 </li>
 <li>
  <p><strong>Composition API：</strong> 通过函数组合和逻辑抽取更加直观和灵活，可以更好地实现逻辑复用，同时减少命名冲突的风险。</p>
 </li>
 <li>
  <p><strong>Typescript 支持：</strong></p>
 </li>
 <li>
  <p><strong>Composition API：</strong> 提供了更好的 Typescript 支持，可以更方便地推断类型和提供类型安全。</p>
 </li>
 <li>
  <p><strong>生命周期钩子：</strong></p>
 </li>
 <li>
  <p><strong>Options API：</strong> 生命周期钩子是通过特定的方法实现的，如 <code>created()</code>、<code>mounted()</code> 等。</p>
 </li>
 <li>
  <p><strong>Composition API：</strong> 生命周期钩子可以通过直接在 <code>setup()</code> 函数中使用特定的命名来定义，如 <code>onMounted()</code>、<code>onUpdated()</code> 等。</p>
 </li>
</ol>
<p>总体来说，Composition API 提供了更加灵活和清晰的代码组织方式，特别是在处理复杂逻辑和大型组件时尤为突出。它还能够更好地支持 Typescript，并且提供了更好的逻辑复用和组件组合能力。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140246203</p>]]></description><guid isPermaLink="false">/archives/03d9a74b-c404-45c4-be0c-a6ab0bec5e81</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 05:44:13 GMT</pubDate></item><item><title><![CDATA[子组件向父组件传参的方式]]></title><link>https://wiki.dgfygk.com/archives/6e15e9d5-25e2-4e23-bf74-33c6d1e9f9ff</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AD%90%E7%BB%84%E4%BB%B6%E5%90%91%E7%88%B6%E7%BB%84%E4%BB%B6%E4%BC%A0%E5%8F%82%E7%9A%84%E6%96%B9%E5%BC%8F&amp;url=/archives/6e15e9d5-25e2-4e23-bf74-33c6d1e9f9ff" width="1" height="1" alt="" style="opacity:0;">
<p>子组件向父组件传参的方式通常通过事件来实现。具体步骤如下：</p>
<ol>
 <li>
  <p><strong>在子组件中定义事件</strong> ：子组件可以通过 <code>$emit</code> 方法触发一个自定义事件，并传递参数。</p>
  <p>// 子组件 ChildComponent.vue
   <br>
   <template>
    <pre><code> &lt;button @click="sendDataToParent"&gt;向父组件传参&lt;/button&gt;
</code></pre>
    <p></p>
   </template></p>
  <p>
   <script>
<br  />export default {</p>
<pre><code> methods: {
   sendDataToParent() {
     let data = '这是子组件传递给父组件的数据';
     this.$emit('child-event', data);
   }
 }
</code></pre>
<p>}
<br  /></script>
  </p>
  <p>在上面的代码中，<code>sendDataToParent</code> 方法通过 <code>$emit</code> 发送了一个名为 <code>child-event</code> 的事件，并将 <code>data</code> 作为参数传递给父组件。</p>
 </li>
 <li>
  <p><strong>在父组件中监听事件</strong>：父组件需要在使用子组件的地方监听这个事件，并处理子组件传递过来的数据。</p>
  <p>// 父组件 ParentComponent.vue
   <br>
   <template>
    <pre><code> &lt;div&gt;
   &lt;p&gt;从子组件接收到的数据：{
   { receivedData }}&lt;/p&gt;
   &lt;child-component @child-event="handleChildEvent"&gt;&lt;/child-component&gt;
 &lt;/div&gt;
</code></pre>
    <p></p>
   </template></p>
  <p>
   <script>
<br  />import ChildComponent from './ChildComponent.vue';</p>
<p>export default {</p>
<pre><code> components: {
   ChildComponent
 },
 data() {
   return {
     receivedData: ''
   };
 },
 methods: {
   handleChildEvent(data) {
     this.receivedData = data;
   }
 }
</code></pre>
<p>}
<br  /></script>
  </p>
  <p>在父组件中，使用 <code>&lt;child-component&gt;</code> 标签引入子组件，并通过 <code>@child-event</code> 监听子组件触发的 <code>child-event</code> 事件。当事件被触发时，<code>handleChildEvent</code> 方法会被调用，并将子组件传递的 <code>data</code> 参数赋值给 <code>receivedData</code>。</p>
 </li>
</ol>
<p>通过以上方式，子组件就能够向父组件传递数据了。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140388883</p>]]></description><guid isPermaLink="false">/archives/6e15e9d5-25e2-4e23-bf74-33c6d1e9f9ff</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 05:43:59 GMT</pubDate></item><item><title><![CDATA[vue如何解决跨域？原理？]]></title><link>https://wiki.dgfygk.com/archives/8f05474d-7732-4f4d-ba1c-710a7f99cf4f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E8%B7%A8%E5%9F%9F%EF%BC%9F%E5%8E%9F%E7%90%86%EF%BC%9F&amp;url=/archives/8f05474d-7732-4f4d-ba1c-710a7f99cf4f" width="1" height="1" alt="" style="opacity:0;">
<p>Vue 通常并不直接解决跨域问题，而是依赖于浏览器的跨域策略和后端接口的设置。跨域是指浏览器限制在一个源（origin）发起的请求去访问另一个源的资源，源指的是协议、域名和端口号的组合。</p>
<h4>常见的跨域解决方案：</h4>
<ol>
 <li>
  <p><strong>后端设置 CORS（跨源资源共享）：</strong></p>
 </li>
 <li>
  <p>后端可以在响应中设置特定的 HTTP 头部来允许跨源请求。通常是通过设置 <code>Access-Control-Allow-Origin</code> 头部来指定允许访问的域名，例如可以设置为 <code>Access-Control-Allow-Origin: *</code> 允许所有来源访问，或指定具体的域名。</p>
 </li>
 <li>
  <p><strong>反向代理：</strong></p>
 </li>
 <li>
  <p>Vue 应用可以通过配置反向代理服务器来间接解决跨域问题。例如，可以配置 Vue 开发服务器（如 webpack-dev-server）或者使用 Nginx 等作为反向代理，将前端的请求转发到后端，使得请求不再跨域。</p>
 </li>
 <li>
  <p><strong>JSONP（仅限 GET 请求）：</strong></p>
 </li>
 <li>
  <p>如果接口支持 JSONP，可以利用 JSONP 的方式来进行跨域请求。但 JSONP 仅支持 GET 请求，并且需要后端支持 JSONP 的返回格式。</p>
 </li>
 <li>
  <p><strong>WebSocket：</strong></p>
 </li>
 <li>
  <p>使用 WebSocket 协议进行双向通信时，可以绕过浏览器的同源策略限制，不需要特殊处理跨域问题。</p>
 </li>
 <li>
  <p><strong>代理服务器：</strong></p>
 </li>
 <li>
  <p>在开发环境下，可以配置代理服务器（如 webpack-dev-server 的 proxy 配置），将 API 请求代理到与应用部署在同一域的服务器上，从而避免跨域问题。</p>
 </li>
</ol>
<h4>跨域解决原理：</h4>
<p>跨域问题的产生是由于浏览器的同源策略（Same-Origin Policy）。同源策略限制了一个源的脚本只能访问其来源相同的资源。Vue 的前端应用运行在浏览器中，当前端代码试图发起跨域请求时，浏览器会阻止这些请求。</p>
<p>解决跨域的核心是服务器端的设置。通过设置响应头 <code>Access-Control-Allow-Origin</code>，服务器告诉浏览器允许哪些源的请求能够访问该服务器的资源。Vue 本身提供的功能（如 axios 的拦截器）可以帮助开发者在前端项目中处理 HTTP 请求，但跨域问题的解决在大多数情况下需要后端与运维人员的协作来完成。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140246262</p>]]></description><guid isPermaLink="false">/archives/8f05474d-7732-4f4d-ba1c-710a7f99cf4f</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 05:40:37 GMT</pubDate></item><item><title><![CDATA[vue是如何进行监听数据变化的？vue2和vue3分别是什么，vue3为什么要更换]]></title><link>https://wiki.dgfygk.com/archives/56da7009-0e27-4b0e-93ad-9321c9188928</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=vue%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E7%9B%91%E5%90%AC%E6%95%B0%E6%8D%AE%E5%8F%98%E5%8C%96%E7%9A%84%EF%BC%9Fvue2%E5%92%8Cvue3%E5%88%86%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%8Cvue3%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%9B%B4%E6%8D%A2&amp;url=/archives/56da7009-0e27-4b0e-93ad-9321c9188928" width="1" height="1" alt="" style="opacity:0;">
<p>在 Vue 中，数据变化的监听是通过响应式系统来实现的。Vue 2.x 和 Vue 3 在这方面有一些区别。</p>
<h4>Vue 2.x 的数据监听</h4>
<p>Vue 2.x 使用的是 Object.defineProperty() 方法来实现数据的响应式。当你声明一个 Vue 实例的数据对象时，Vue 将遍历这个对象的属性，使用 Object.defineProperty() 将每个属性转换为 getter/setter。这样，当属性被访问或修改时，Vue 就能追踪到，并通知相关的 Watcher 来更新视图。</p>
<h4>Vue 3 的数据监听</h4>
<p>Vue 3 引入了 Proxy 对象来重写数据监听的实现。与 Object.defineProperty() 相比，Proxy 提供了更多的操作和拦截能力，使得 Vue 3 的响应式系统更加灵活和高效。使用 Proxy 的好处包括更好的性能、更直观的 API、更简洁的代码实现等。</p>
<h4>为什么 Vue 3 要更换数据监听实现方式？</h4>
<p>Vue 3 更换数据监听的实现方式从 Object.defineProperty() 到 Proxy，主要是出于几个考虑：</p>
<ol>
 <li>
  <p><strong>性能优化：</strong> Proxy 拥有更好的性能表现，特别是在初始化和更新大量数据时，相比于 Object.defineProperty() 有更高的效率。</p>
 </li>
 <li>
  <p><strong>更好的响应式能力：</strong> Proxy 提供了更多的拦截能力，能够监听更多类型的操作，如数组的变化等，相比之下，Object.defineProperty() 对数组的处理较为复杂和低效。</p>
 </li>
 <li>
  <p><strong>更清晰的代码和更好的扩展性：</strong> 使用 Proxy 可以减少 Vue 内部的复杂性，使得代码更加清晰，同时也为未来的功能扩展和优化提供了更好的基础。</p>
 </li>
</ol>
<p>总体来说，Vue 3 使用 Proxy 替代 Object.defineProperty() 是为了提升性能、增强响应式能力，并且能够更好地支持未来的开发和优化需求。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140246247</p>]]></description><guid isPermaLink="false">/archives/56da7009-0e27-4b0e-93ad-9321c9188928</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 05:40:21 GMT</pubDate></item><item><title><![CDATA[详细介绍TS中的枚举]]></title><link>https://wiki.dgfygk.com/archives/efca4340-5bfa-4702-80d4-391d7ac49649</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E4%BB%8B%E7%BB%8DTS%E4%B8%AD%E7%9A%84%E6%9E%9A%E4%B8%BE&amp;url=/archives/efca4340-5bfa-4702-80d4-391d7ac49649" width="1" height="1" alt="" style="opacity:0;">
<p>在 TypeScript 中，枚举（Enums）是一个用于表示常见常量集合的便利特性。枚举是一种特殊的类型，它们允许你为一组值定义一个固定的命名空间。枚举常用于模式匹配的 switch 语句和提供更可读的类型检查。</p>
<h4>枚举的基本语法：</h4>
<pre><code class="hljs">enum Direction {
  Up,
  Down,
  Left,
  Right
}

let direction: Direction = Direction.Up;

console.log(direction); // 输出：Up
</code></pre>
<p>在上面的例子中，<code>Direction</code> 是一个枚举类型，它定义了四个常量：<code>Up</code>、<code>Down</code>、<code>Left</code> 和 <code>Right</code>。你可以直接通过枚举成员的名字来访问其相关的值。</p>
<p>在 TypeScript 中，枚举成员的默认值是从 0 开始递增的数值。例如，<code>Up</code> 的值是 0，<code>Down</code> 是 1，依此类推。</p>
<h4>手动赋值枚举成员：</h4>
<p>如果你需要手动设置枚举成员的值，你可以给枚举中的第一个成员赋一个初始值，之后的其他成员都会从该值开始递增。</p>
<pre><code class="hljs">enum Direction {
  Up = 5,
  Down,
  Left,
  Right
}

console.log(Direction.Up); // 输出：5
console.log(Direction.Down); // 输出：6
console.log(Direction.Left); // 输出：7
console.log(Direction.Right); // 输出：8
</code></pre>
<p>在上面的例子中，我给 <code>Up</code> 设置了一个默认值为 5，之后 <code>Down</code> 的值为 6，<code>Left</code> 的为 7，<code>Right</code> 的为 8。</p>
<h4>字符串枚举（String Enums）：</h4>
<p>你也可以为枚举成员赋字符串类型的值。这种类型的枚举通常用于定义接口，更有效的处理枚举的字符串值。</p>
<pre><code class="hljs">enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

let direction: Direction = Direction.Up;

console.log(direction); // 输出：Up
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140335095</p>]]></description><guid isPermaLink="false">/archives/efca4340-5bfa-4702-80d4-391d7ac49649</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 05:40:07 GMT</pubDate></item><item><title><![CDATA[详细介绍一下TypeScript]]></title><link>https://wiki.dgfygk.com/archives/e435dde2-b2f3-4c77-b4a6-2985ae16ca87</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E4%BB%8B%E7%BB%8D%E4%B8%80%E4%B8%8BTypeScript&amp;url=/archives/e435dde2-b2f3-4c77-b4a6-2985ae16ca87" width="1" height="1" alt="" style="opacity:0;">
<p>TypeScript (TS) 是一种由微软开发和维护的开源编程语言。它是 JavaScript 的一个超集，意味着所有的 JavaScript 代码都可以在 TypeScript 中无需修改地运行。TypeScript 添加了可选的静态类型系统，这使得开发者能够在开发大型应用程序时更早地检测和修复错误，提高代码的可维护性和可读性。</p>
<h4>主要特点和优势包括：</h4>
<ol>
 <li>
  <p><strong>静态类型系统</strong>: TypeScript 允许开发者在声明变量、函数、参数和返回值时加入类型注解。这些类型信息在编译时会进行静态类型检查，从而捕获大部分常见的错误，如类型不匹配、不存在的属性等。</p>
 </li>
 <li>
  <p><strong>增强的编辑器支持</strong>: TypeScript 提供了强大的编辑器支持，尤其是与 Visual Studio Code 结合使用时效果显著。编辑器可以基于类型信息提供智能的代码补全、导航和重构功能，帮助开发者更高效地编写和重构代码。</p>
 </li>
 <li>
  <p><strong>ES6+ 支持</strong>: TypeScript 对最新的 ECMAScript 标准（如 ES6、ES7 等）有很好的支持，开发者可以使用最新的语言特性而无需等待所有目标环境都支持这些特性。</p>
 </li>
 <li>
  <p><strong>大型应用程序的可维护性</strong>: 随着项目规模的增长，JavaScript 本身的动态特性可能导致代码结构不清晰、难以维护。TypeScript 的类型系统和面向对象的特性（如接口、类等）可以帮助开发者更好地组织代码、重用组件，减少 bug 的引入和维护成本。</p>
 </li>
 <li>
  <p><strong>社区和生态系统</strong>: TypeScript 拥有一个庞大的社区和丰富的生态系统，许多流行的 JavaScript 框架和库（如 Angular、React、Vue 等）都提供了对 TypeScript 的官方支持或者社区支持。</p>
 </li>
</ol>
<p>总体来说，TypeScript 提供了一种更安全、更高效的方式来开发 JavaScript 应用程序，特别适合于大型和复杂的项目。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140335038</p>]]></description><guid isPermaLink="false">/archives/e435dde2-b2f3-4c77-b4a6-2985ae16ca87</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 05:39:56 GMT</pubDate></item><item><title><![CDATA[详细说一下vue中的路由拦截器的作用]]></title><link>https://wiki.dgfygk.com/archives/1c9d3e84-9bc6-4e01-bb9e-84380e2c15a5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E8%AF%B4%E4%B8%80%E4%B8%8Bvue%E4%B8%AD%E7%9A%84%E8%B7%AF%E7%94%B1%E6%8B%A6%E6%88%AA%E5%99%A8%E7%9A%84%E4%BD%9C%E7%94%A8&amp;url=/archives/1c9d3e84-9bc6-4e01-bb9e-84380e2c15a5" width="1" height="1" alt="" style="opacity:0;">
<p>在Vue中，路由拦截器主要用于在导航到某个路由前或者离开某个路由时进行拦截和处理。这种机制允许开发者在路由导航发生前或者发生后执行特定的逻辑，比如权限验证、数据加载、页面跳转等。</p>
<p>在Vue Router中，可以通过以下几种方式来实现路由拦截器：</p>
<ol>
 <li>
  <p><strong>全局前置守卫 (Global Before Guards)</strong>:</p>
 </li>
 <li>
  <p><code>router.beforeEach(to, from, next)</code>：注册一个全局前置守卫，当路由导航触发时，该守卫会在路由改变前被调用。</p>
 </li>
 <li>
  <p>用途：适合进行全局的权限验证、页面加载进度条控制等。</p>
  <pre><code class="hljs"> router.beforeEach((to, from, next) =&gt; {
     // 检查用户权限
     if (!userAuthenticated) {
         next('/login'); // 未认证跳转到登录页
     } else {
         next(); // 已认证则放行
     }
 });
</code></pre>
 </li>
</ol>
<p><strong>2.全局解析守卫 (Global Resolve Guards)</strong>:</p>
<ul>
 <li>
  <p><code>router.beforeResolve(to, from, next)</code>：注册一个全局解析守卫，在全局前置守卫之后被调用，在导航被确认之前调用。</p>
  <pre><code class="hljs">router.beforeResolve((to, from, next) =&gt; {
  // 在导航被确认之前，进行数据加载等操作
  fetchData().then(() =&gt; {
      next();
  });
});
</code></pre>
  <p>
   <br>
  </p>
 </li>
</ul>
<p><strong>3.路由独享守卫 (Per-Route Guard)</strong>:</p>
<ul>
 <li>在路由配置对象中直接添加 <code>beforeEnter</code> 字段，为单个路由添加守卫逻辑。</li>
</ul>
<pre><code class="hljs">const route = {
    path: '/profile',
    component: Profile,
    beforeEnter: (to, from, next) =&gt; {
        // 检查用户是否有权限访问该路由
        if (userHasAccess) {
            next();
        } else {
            next('/403'); // 没有权限跳转到403页面
        }
    }
};
</code></pre>
<p><strong>4.组件内的守卫 (In-Component Guards)</strong>:</p>
<ul>
 <li>在组件内部使用 <code>beforeRouteEnter</code>, <code>beforeRouteUpdate</code>, 和 <code>beforeRouteLeave</code> 钩子函数，这些函数会在路由导航到当前组件、在当前组件复用时、以及离开当前组件时被调用。</li>
</ul>
<pre><code class="hljs">export default {
    beforeRouteEnter (to, from, next) {
        // 在路由导航进入该组件前执行逻辑
        next(vm =&gt; {
            // 可以访问实例 `vm`
        });
    },
    beforeRouteUpdate (to, from, next) {
        // 在当前路由改变，但是该组件被复用时调用
        // 可以访问组件实例 `this`
        // 通常用于更新组件的数据
        next();
    },
    beforeRouteLeave (to, from, next) {
        // 在导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
        next();
    }
}
</code></pre>
<p>这些路由拦截器的机制可以帮助开发者在不同层级和场景下控制应用的导航流程，实现权限控制、数据预加载、页面跳转等功能，从而更好地管理和优化前端应用的交互体验。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140362544</p>]]></description><guid isPermaLink="false">/archives/1c9d3e84-9bc6-4e01-bb9e-84380e2c15a5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 05:39:33 GMT</pubDate></item><item><title><![CDATA[Vue.js中的计算属性]]></title><link>https://wiki.dgfygk.com/archives/cb01e766-9e75-47bf-a994-0f2a121a0093</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vue.js%E4%B8%AD%E7%9A%84%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7&amp;url=/archives/cb01e766-9e75-47bf-a994-0f2a121a0093" width="1" height="1" alt="" style="opacity:0;">
<p>Vue.js中的计算属性（computed properties）是用于声明响应式依赖的属性。它们会根据它们的依赖进行缓存，并且只有在相关依赖发生改变时才会重新求值。这使得它们非常适合用来处理复杂逻辑和数据处理。</p>
<h4>基本用法</h4>
<p>在Vue实例中，可以通过<code>computed</code>选项来定义计算属性：</p>
<pre><code class="hljs">var vm = new Vue({
  data: {
    message: 'Hello Vue!',
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('');
    }
  }
});

console.log(vm.reversedMessage); // 输出： "!euV olleH"
</code></pre>
<h4>计算属性的缓存</h4>
<p>计算属性默认是具有缓存功能的，只有相关依赖发生改变时才会重新求值。在上面的例子中，只有当<code>message</code>发生变化时，<code>reversedMessage</code>才会重新计算。</p>
<h4>计算属性 vs 方法</h4>
<p>与方法（methods）相比，计算属性是基于它们的响应式依赖进行缓存的。这意味着只要<code>message</code>没有发生变化，多次访问<code>reversedMessage</code>会立即返回之前的计算结果，而不必重新执行函数。</p>
<h4>计算属性的setter</h4>
<p>除了getter，计算属性还可以具有setter，用于处理对属性的更新：</p>
<pre><code class="hljs">var vm = new Vue({
  data: {
    firstName: 'John',
    lastName: 'Doe'
  },
  computed: {
    fullName: {
      // getter
      get: function () {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set: function (newValue) {
        var names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  }
});

console.log(vm.fullName); // 输出： "John Doe"

vm.fullName = 'Jane Smith';
console.log(vm.firstName); // 输出： "Jane"
console.log(vm.lastName); // 输出： "Smith"
</code></pre>
<h4>总结</h4>
<p>计算属性适合用于处理复杂逻辑或者对数据进行转换的场景，尤其是需要依赖多个响应式数据的情况。通过利用缓存，Vue能够高效地计算和更新这些属性，同时保持代码简洁和可读性。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140110401</p>]]></description><guid isPermaLink="false">/archives/cb01e766-9e75-47bf-a994-0f2a121a0093</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 03:28:14 GMT</pubDate></item><item><title><![CDATA[计算属性和侦听属性有什么区别？]]></title><link>https://wiki.dgfygk.com/archives/7a09ad5c-c9ff-4b7f-876b-a02ddbf63353</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E5%92%8C%E4%BE%A6%E5%90%AC%E5%B1%9E%E6%80%A7%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F&amp;url=/archives/7a09ad5c-c9ff-4b7f-876b-a02ddbf63353" width="1" height="1" alt="" style="opacity:0;">
<p>在 Vue.js 中，计算属性（computed properties）和侦听属性（watcher）是两种处理数据变化的方式，它们都允许开发者根据数据的变化来更新组件的状态。虽然它们的目标类似，但它们的工作方式和使用场景是有差异的。</p>
<h4>计算属性（Computed Properties）</h4>
<ol>
 <li>
  <p><strong>定义</strong>：</p>
 </li>
 <li>
  <p>计算属性是惰性计算的，即只有在需要计算的结果时才会进行计算。</p>
 </li>
 <li>
  <p>它们是基于它们的依赖项自动地计算出来。</p>
 </li>
 <li>
  <p><strong>特点</strong>：</p>
 </li>
 <li>
  <p><strong>性能优化</strong>：计算属性只在它依赖的响应式属性变化时才会重新计算。</p>
 </li>
 <li>
  <p><strong>封装逻辑</strong>：计算属性可以对一个或多个响应式属性进行计算，并将结果以新的属性形式暴露在外部。</p>
 </li>
 <li>
  <p><strong>组合<code>getter</code>和<code>setter</code></strong> ：计算属性可以用<code>getter</code>来计算值，可以用<code>setter</code>来改变依赖的响应式属性。</p>
 </li>
 <li>
  <p><strong>示例</strong>：</p>
  <p>computed: {</p>
  <pre><code> capitalizedName() {
   // 这个计算属性会返回一个新字符串，以响应 `name` 的变化
   return this.name &amp;&amp; this.name.charAt(0).toUpperCase() + this.name.slice(1);
 }
</code></pre>
  <p>}</p>
 </li>
</ol>
<h4>侦听属性（Watchers）</h4>
<ol>
 <li>
  <p><strong>定义</strong>：</p>
 </li>
 <li>
  <p>一个侦听器可以响应多个数据属性的变化，而计算属性仅能响应它自己的响应式依赖项的变化。</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>
  <p>watch: {</p>
  <pre><code> message(newValue, oldValue) {
   this.$emit('input', newValue);
 }
</code></pre>
  <p>}</p>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140161934</p>]]></description><guid isPermaLink="false">/archives/7a09ad5c-c9ff-4b7f-876b-a02ddbf63353</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 03:04:15 GMT</pubDate></item><item><title><![CDATA[什么是渐进式框架]]></title><link>https://wiki.dgfygk.com/archives/32186326-a6a9-4b16-ae17-a8a6d564d22d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%80%E4%B9%88%E6%98%AF%E6%B8%90%E8%BF%9B%E5%BC%8F%E6%A1%86%E6%9E%B6&amp;url=/archives/32186326-a6a9-4b16-ae17-a8a6d564d22d" width="1" height="1" alt="" style="opacity:0;">
<p>在软件开发中，框架（framework）和函数库（library）是两个常见的概念，它们在功能和使用方式上有着明显的区别。</p>
<h4>框架（Framework）：</h4>
<ol>
 <li>
  <p><strong>定义</strong>：</p>
 </li>
 <li>
  <p>框架是一个预先编写好的具有特定结构的软件，它提供了开发应用程序的基础架构。框架通常包括一系列预定义的类和函数，以及规定了这些类和函数如何交互的约定。</p>
 </li>
 <li>
  <p><strong>特点</strong>：</p>
 </li>
 <li>
  <p><strong>结构化</strong>：框架定义了整个应用程序的基本结构和流程，开发者需要按照框架提供的规则来编写代码。</p>
 </li>
 <li>
  <p><strong>反向控制</strong>：通常框架会通过反向控制（Inversion of Control，IoC）或者依赖注入（Dependency Injection，DI）来管理应用程序的控制流程。</p>
 </li>
 <li>
  <p><strong>全面性</strong>：框架通常提供了完整的解决方案，包括处理用户接口、数据库访问、安全性等方面的功能。</p>
 </li>
 <li>
  <p><strong>例子</strong>：</p>
 </li>
 <li>
  <p>常见的框架有Spring（Java）、Django（Python）、Ruby on Rails（Ruby）等。这些框架都提供了一个完整的开发环境和一系列的工具，使开发者能够快速构建复杂的应用程序。</p>
 </li>
</ol>
<h4>函数库（Library）：</h4>
<ol>
 <li>
  <p><strong>定义</strong>：</p>
 </li>
 <li>
  <p>函数库是一组通用的代码集合，提供了特定功能的函数或方法，供开发者在应用程序中调用使用。函数库通常不会对整个应用程序的结构和流程做出要求，而是为特定的任务提供工具。</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>常见的函数库包括jQuery（JavaScript DOM操作）、NumPy（Python科学计算）、Hibernate（Java数据库访问）等。这些函数库提供了一组函数或方法，用于执行特定的任务或操作，开发者可以根据需要灵活地使用它们。</p>
 </li>
</ol>
<h4>总结比较：</h4>
<ul>
 <li><strong>复杂度</strong>：框架通常比函数库更复杂，因为它们规定了应用程序的整体结构和控制流程。</li>
 <li><strong>依赖性</strong>：使用框架时，开发者更多地依赖于框架本身提供的规范和结构；而使用函数库时，开发者更多地根据自己的需要选择性地使用特定功能。</li>
 <li><strong>应用场景</strong>：框架适合构建整体架构复杂的应用程序，而函数库则更适合为特定的任务或功能增加支持和扩展。</li>
</ul>
<p>总的来说，框架和函数库在软件开发中各有其独特的作用和优势，开发者在选择使用时需要根据项目的需求和复杂度来进行权衡和决策。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140161869</p>]]></description><guid isPermaLink="false">/archives/32186326-a6a9-4b16-ae17-a8a6d564d22d</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:51:44 GMT</pubDate></item><item><title><![CDATA[JavaScript 中 callee 与 caller 的作用]]></title><link>https://wiki.dgfygk.com/archives/57361071-8332-450e-b723-7fa121442d20</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%20%E4%B8%AD%20callee%20%E4%B8%8E%20caller%20%E7%9A%84%E4%BD%9C%E7%94%A8&amp;url=/archives/57361071-8332-450e-b723-7fa121442d20" width="1" height="1" alt="" style="opacity:0;">
<p>JavaScript 中的 callee 和 caller 是两个常见的概念，它们在函数中起着重要的作用。在本篇博客中，我们将深入探讨这两个概念的作用以及如何在代码中使用它们。</p>
<p>
 <br>
</p>
<p>引言
 <br>
 在 JavaScript 中，callee 和 caller 是与函数调用和执行相关的重要属性和方法。它们可以帮助我们更好地理解和控制函数的执行过程，同时也可以用于一些特殊的编程场景。</p>
<p>callee 与 caller 的优缺点
 <br>
 callee 和 caller 是 JavaScript 中的两个内置属性，用于在函数内部引用函数本身或者调用函数的函数。它们各自有一些优点和缺点。</p>
<p>callee 的优点：</p>
<p>可以在函数内部引用函数本身，用于递归调用或者动态调用函数。
 <br>
 可以在匿名函数中使用，因为它不依赖函数名。
 <br>
 callee 的缺点：</p>
<p>在严格模式下不允许使用，会导致错误。
 <br>
 在性能上可能不如直接使用函数名调用函数。
 <br>
 caller 的优点：</p>
<p>可以在函数内部获取调用函数的引用，用于动态调用或者跟踪调用链。
 <br>
 可以在严格模式下使用。
 <br>
 caller 的缺点：</p>
<p>在性能上可能不如直接使用函数名调用函数。
 <br>
 在一些情况下可能会导致代码不易维护或者难以理解。
 <br>
 总的来说，callee 和 caller 都有各自的用途和局限性，需要根据具体的情况来选择使用哪个属性。</p>
<p>callee 的作用
 <br>
 在 JavaScript 的函数中，callee 是一个指向当前正在执行的函数的指针。它通常在递归函数或匿名函数中使用，因为它允许函数在执行过程中引用自身。这对于一些特定的算法和逻辑来说非常有用。</p>
<p>下面是一个简单的示例，演示了如何在递归函数中使用 callee：</p>
<pre><code class="hljs">function factorial(n) {
  if (n &lt;= 1) {
    return 1;
  } else {
    return n * arguments.callee(n - 1);
  }
}

console.log(factorial(5)); // 输出 120
</code></pre>
<p>在这个例子中，arguments.callee 指向了当前正在执行的 factorial 函数，这样就可以实现递归调用。</p>
<p>caller 的作用
 <br>
 与 callee 不同，caller 是一个函数对象的属性，它指向调用当前函数的函数。这在某些情况下可以用于调试和跟踪函数的调用链。</p>
<p>下面是一个简单的示例，演示了如何在函数内部获取调用它的函数：</p>
<pre><code class="hljs">function outerFunction() {
  innerFunction();
}

function innerFunction() {
  console.log(innerFunction.caller); // 输出 outerFunction 的源代码
}

outerFunction();
</code></pre>
<p>在这个例子中，innerFunction.caller 将输出调用 innerFunction 的函数的源代码，这对于调试和理解函数调用链非常有帮助。</p>
<p>结论
 <br>
 callee 和 caller 是 JavaScript 中的两个特殊属性，用于在函数内部获取调用函数的信息。</p>
<p>callee 属性是 arguments 对象的一个属性，它指向当前正在执行的函数对象，可以用于在函数内部引用自身，特别是在递归函数中。</p>
<p>caller 属性是函数对象的一个属性，它指向调用当前函数的函数对象，可以用于获取调用者的信息，但由于安全性和性能问题，现代 JavaScript 中已经不推荐使用 caller 属性。</p>
<p>总的来说，callee 和 caller 属性可以用于在函数内部获取函数调用的信息，但由于安全性和性能问题，最好避免使用 caller 属性。</p>
<p>在本篇博客中，我们深入探讨了 JavaScript 中 callee 和 caller 的作用及如何在代码中使用它们。callee 可以用于递归函数或匿名函数中引用自身，而 caller 则可以用于获取调用当前函数的函数。这些概念对于理解和控制函数的执行过程非常重要，同时也可以在特定的编程场景中发挥作用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139753512</p>]]></description><guid isPermaLink="false">/archives/57361071-8332-450e-b723-7fa121442d20</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:49:16 GMT</pubDate></item><item><title><![CDATA[详细说一下框架和函数库的区别]]></title><link>https://wiki.dgfygk.com/archives/f02d4efa-9300-47f8-86fd-d939caf0d55d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E8%AF%B4%E4%B8%80%E4%B8%8B%E6%A1%86%E6%9E%B6%E5%92%8C%E5%87%BD%E6%95%B0%E5%BA%93%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/f02d4efa-9300-47f8-86fd-d939caf0d55d" width="1" height="1" alt="" style="opacity:0;">
<p>在软件开发中，框架（framework）和函数库（library）是两个常见的概念，它们在功能和使用方式上有着明显的区别。</p>
<p>框架（Framework）：</p>
<ol>
 <li>
  <p>定义：</p>
 </li>
 <li>
  <p>框架是一个预先编写好的具有特定结构的软件，它提供了开发应用程序的基础架构。框架通常包括一系列预定义的类和函数，以及规定了这些类和函数如何交互的约定。</p>
 </li>
 <li>
  <p>特点：
   <br>
   结构化：框架定义了整个应用程序的基本结构和流程，开发者需要按照框架提供的规则来编写代码。
   <br>
   反向控制：通常框架会通过反向控制（Inversion of Control，IoC）或者依赖注入（Dependency Injection，DI）来管理应用程序的控制流程。
   <br>
   全面性：框架通常提供了完整的解决方案，包括处理用户接口、数据库访问、安全性等方面的功能。</p>
 </li>
 <li>
  <p>例子：
   <br>
   常见的框架有Spring（Java）、Django（Python）、Ruby on Rails（Ruby）等。这些框架都提供了一个完整的开发环境和一系列的工具，使开发者能够快速构建复杂的应用程序。</p>
 </li>
</ol>
<p>函数库（Library）：</p>
<ol>
 <li>
  <p>定义：
   <br>
   函数库是一组通用的代码集合，提供了特定功能的函数或方法，供开发者在应用程序中调用使用。函数库通常不会对整个应用程序的结构和流程做出要求，而是为特定的任务提供工具。</p>
 </li>
 <li>
  <p>特点：
   <br>
   独立性：函数库通常是独立的工具集，可以根据需要选择性地引入和使用。
   <br>
   功能性：函数库通常集中在特定领域或任务上，例如图形处理、数据解析、加密等。</p>
 </li>
 <li>
  <p>例子：
   <br>
   常见的函数库包括jQuery（JavaScript DOM操作）、NumPy（Python科学计算）、Hibernate（Java数据库访问）等。这些函数库提供了一组函数或方法，用于执行特定的任务或操作，开发者可以根据需要灵活地使用它们。</p>
 </li>
</ol>
<p>总结比较：</p>
<p>复杂度：框架通常比函数库更复杂，因为它们规定了应用程序的整体结构和控制流程。
 <br>
 依赖性：使用框架时，开发者更多地依赖于框架本身提供的规范和结构；而使用函数库时，开发者更多地根据自己的需要选择性地使用特定功能。
 <br>
 应用场景：框架适合构建整体架构复杂的应用程序，而函数库则更适合为特定的任务或功能增加支持和扩展。</p>
<p>总的来说，框架和函数库在软件开发中各有其独特的作用和优势，开发者在选择使用时需要根据项目的需求和复杂度来进行权衡和决策。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140161824</p>]]></description><guid isPermaLink="false">/archives/f02d4efa-9300-47f8-86fd-d939caf0d55d</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:48:44 GMT</pubDate></item><item><title><![CDATA[JavaScript自执行函数：用途、好处]]></title><link>https://wiki.dgfygk.com/archives/e77f6421-eb11-4c8b-8465-5da247e6d61d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%E8%87%AA%E6%89%A7%E8%A1%8C%E5%87%BD%E6%95%B0%EF%BC%9A%E7%94%A8%E9%80%94%E3%80%81%E5%A5%BD%E5%A4%84&amp;url=/archives/e77f6421-eb11-4c8b-8465-5da247e6d61d" width="1" height="1" alt="" style="opacity:0;">
<p>JavaScript中的自执行函数是一个常见的编程技巧，它可以在特定的场景中发挥重要作用。本文将介绍自执行函数的用途、好处，并提供代码示例进行说明。</p>
<p>引言
 <br>
 在JavaScript编程中，自执行函数是一种特殊的函数调用方式，它能够在定义后立即执行。这种技巧在许多场景中都能发挥作用，包括模块化开发、作用域控制和代码封装等方面。</p>
<p>用途</p>
<ol>
 <li>
  <p>模块化开发
   <br>
   自执行函数经常用于实现模块化开发。通过将模块的代码封装在自执行函数内部，可以避免污染全局命名空间，同时又能够提供对外部的接口。</p>
 </li>
 <li>
  <p>作用域控制
   <br>
   在需要控制变量作用域的情况下，自执行函数可以起到很好的作用。通过在函数内部定义变量，可以避免变量泄露到全局作用域中，从而提高代码的健壮性和可维护性。</p>
 </li>
</ol>
<p>好处</p>
<ol>
 <li>
  <p>避免命名冲突
   <br>
   自执行函数可以有效地避免命名冲突问题，因为其中定义的变量和函数不会污染全局作用域。</p>
 </li>
 <li>
  <p>封装代码
   <br>
   通过自执行函数，可以将一段代码封装起来，形成一个独立的作用域，这有助于提高代码的可读性和可维护性。</p>
 </li>
</ol>
<p>优缺点
 <br>
 自执行函数是一种在定义后立即执行的 JavaScript 函数。它有一些优点和缺点，如下所示：</p>
<p>优点：</p>
<p>避免全局污染：自执行函数可以将变量和函数封装在函数作用域内，避免了对全局作用域的污染，有助于保持代码的整洁和可维护性。
 <br>
 避免命名冲突：自执行函数中的变量和函数都是在函数作用域内，可以避免与全局作用域中的同名变量和函数发生冲突。
 <br>
 模块化开发：自执行函数可以用于创建模块化的代码结构，使得代码更易于组织和管理。
 <br>
 缺点：</p>
<p>可读性差：自执行函数的语法可能会让代码难以阅读和理解，特别是对于新手来说。
 <br>
 调试困难：由于自执行函数中的代码会立即执行，因此在调试时可能会遇到一些困难，特别是在需要单步调试时。
 <br>
 性能影响：自执行函数会增加函数调用的开销，可能会对性能产生一定的影响。
 <br>
 总的来说，自执行函数在一些情况下可以提供一些优势，但也需要谨慎使用，以避免引入不必要的复杂性和性能问题。</p>
<p>代码示例
 <br>
 下面是一个简单的自执行函数的代码示例：</p>
<pre><code class="hljs">(function() {
  // 在这里编写自执行函数的代码
})();
</code></pre>
<p>
 <br>
</p>
<p>在这个例子中，我们使用了一个匿名函数，并在函数定义后使用一对括号 <code>()</code> 来立即执行它。函数内部的任何变量都是局部的，不会影响到全局作用域。你可以在自执行函数内编写任何代码，包括声明变量、定义函数等。</p>
<p>你还可以传递参数给自执行函数，如下所示：</p>
<pre><code class="hljs">(function(param1, param2) {
    // 在这里编写你的代码，可以使用 param1 和 param2
    console.log(param1, param2);
})("参数1的值", "参数2的值");
</code></pre>
<p>这样，你可以将参数传递给自执行函数，并在函数内使用它们。</p>
<p>结论
 <br>
 JavaScript自执行函数是指在定义后立即执行的函数。它有以下特点：</p>
<p>语法：自执行函数的语法是将函数定义放在一对括号中，然后在后面再加上一对括号来立即执行该函数，例如：(function() { // code })();</p>
<p>作用：自执行函数可以用来创建一个独立的作用域，避免变量污染全局作用域。它还可以被用来初始化一些变量或执行一些初始化操作。</p>
<p>参数传递：自执行函数可以接受参数，并在定义后立即传入参数执行。</p>
<p>返回值：自执行函数可以返回一个值，并且可以被外部代码接收和使用。</p>
<p>适用场景：自执行函数通常用于一些需要立即执行的初始化操作，或者在模块化开发中用来创建私有作用域。</p>
<p>总之，自执行函数是一种灵活且常用的 JavaScript 编程技巧，可以帮助我们更好地管理作用域和代码执行顺序。</p>
<p>自执行函数是JavaScript中一个非常有用的编程技巧，它在模块化开发、作用域控制和代码封装等方面都能够发挥重要作用。合理地运用自执行函数，可以使代码更加健壮、可维护，同时也能提高开发效率。希望本文能够帮助读者更好地理解自执行函数的用途和好处，并在实际开发中加以运用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139753536</p>]]></description><guid isPermaLink="false">/archives/e77f6421-eb11-4c8b-8465-5da247e6d61d</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:44:00 GMT</pubDate></item><item><title><![CDATA[js replace替换文本中的多个字符]]></title><link>https://wiki.dgfygk.com/archives/0229fb67-945b-4c7a-a4e7-a263651da406</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=js%20replace%E6%9B%BF%E6%8D%A2%E6%96%87%E6%9C%AC%E4%B8%AD%E7%9A%84%E5%A4%9A%E4%B8%AA%E5%AD%97%E7%AC%A6&amp;url=/archives/0229fb67-945b-4c7a-a4e7-a263651da406" width="1" height="1" alt="" style="opacity:0;">
<p>JavaScript中的<a href="https://so.csdn.net/so/search?q=replace%E5%87%BD%E6%95%B0&amp;spm=1001.2101.3001.7020" title="replace函数">replace函数</a>默认只会替换第一个匹配的字符串。如果你想替换所有匹配的字符串，可以使用正则表达式配合g标志来实现。</p>
<p>以下是一段示例代码：</p>
<pre><code class="hljs">// 假设你要替换的字符串名为str
// 假设你要替换的模式为pattern
// 假设你要替换成的字符串名为replacement
// 使用正则表达式配合g标志实现替换所有匹配的字符串
var result = str.replace(new RegExp(pattern, 'g'), replacement);
// 打印结果
console.log(result);
</code></pre>
<p>请注意，上述代码中的变量名假设了你要替换的<a href="https://so.csdn.net/so/search?q=%E5%AD%97%E7%AC%A6%E4%B8%B2&amp;spm=1001.2101.3001.7020" title="字符串">字符串</a>、替换的模式以及替换成的字符串。你可以根据实际情况修改代码和变量名。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139767942</p>]]></description><guid isPermaLink="false">/archives/0229fb67-945b-4c7a-a4e7-a263651da406</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:43:45 GMT</pubDate></item><item><title><![CDATA[js的导入导出]]></title><link>https://wiki.dgfygk.com/archives/32028c57-e63e-4b05-bdb1-9554ed8547e2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=js%E7%9A%84%E5%AF%BC%E5%85%A5%E5%AF%BC%E5%87%BA&amp;url=/archives/32028c57-e63e-4b05-bdb1-9554ed8547e2" width="1" height="1" alt="" style="opacity:0;">
<h3>js 的导入导出</h3>
<p>在 JavaScript 中，可以使用 import <a href="https://so.csdn.net/so/search?q=%E5%85%B3%E9%94%AE%E5%AD%97&amp;spm=1001.2101.3001.7020" title="关键字">关键字</a>来导入其他模块或文件。而使用 export 关键字则可以将变量、函数等内容从当前模块导出供其他地方引用。</p>
<p>下面是一些<a href="https://so.csdn.net/so/search?q=%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81&amp;spm=1001.2101.3001.7020" title="示例代码">示例代码</a>：</p>
<p>导入模块：</p>
<pre><code class="hljs">// 导入名为 "module" 的模块
import module from './path/to/module';

// 导入多个模块
import { functionA, variableB } from './path/to/anotherModule';

// 重命名导入的模块
import { functionC as func } from './path/to/yetAnotherModule';

//以*as foo 的方式全部使用
import * as foo  from './foo.js'
</code></pre>
<p><a href="https://so.csdn.net/so/search?q=%E5%AF%BC%E5%87%BA%E5%8F%98%E9%87%8F&amp;spm=1001.2101.3001.7020" title="导出变量">导出变量</a>或函数：</p>
<pre><code class="hljs">// 导出单个变量
export const myVariable = 'Hello World!';

// 导出默认值（只能有一个）,且导出的是值
export default function() {
    console.log('This is a default exported function');
}

// 导出多个变量或函数
export function functionA() {}
export var variableB;

//在{}中做统一导出
//{}不是一个对象,其中放置要导出的变量的引用列表
export {name,age,hello}

//以别名的方式导出
export {
  name as BName,
  age as BAge,
  hello as Bhello
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139767989</p>]]></description><guid isPermaLink="false">/archives/32028c57-e63e-4b05-bdb1-9554ed8547e2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:42:39 GMT</pubDate></item><item><title><![CDATA[VUE的基础指令介绍]]></title><link>https://wiki.dgfygk.com/archives/6d3122c0-605d-484f-98af-a57d2a044eba</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=VUE%E7%9A%84%E5%9F%BA%E7%A1%80%E6%8C%87%E4%BB%A4%E4%BB%8B%E7%BB%8D&amp;url=/archives/6d3122c0-605d-484f-98af-a57d2a044eba" width="1" height="1" alt="" style="opacity:0;">
<p>Vue.js 是一个流行的 JavaScript 框架，用于构建用户界面。它包含了一系列的指令（Directives）来简化在 HTML 模板中操作和渲染数据的过程。以下是一些 Vue.js 中常用的基础指令：</p>
<ol>
 <li><code>v-model</code>: 用于在表单控件元素和应用状态之间创建双向数据绑定。例如，<code>&lt;input v-model="message"&gt;</code> 会将 input 元素的值绑定到 Vue 实例中的 <code>message</code> 属性。</li>
 <li><code>v-bind</code>: 用于动态地绑定 HTML 属性到 Vue 实例中的数据。例如，<code>&lt;a v-bind:href="url"&gt;</code> 会将该元素的 href 属性绑定到 Vue 实例中的 <code>url</code> 属性。</li>
 <li><code>v-if</code> / <code>v-else</code> / <code>v-else-if</code>: 用于根据条件来渲染或销毁元素。例如，<code>&lt;p v-if="isDisplayed"&gt;显示这个段落&lt;/p&gt;</code> 可以根据 <code>isDisplayed</code> 的值来决定是否显示该段落。</li>
 <li><code>v-for</code>: 用于循环渲染数组或对象。例如，<code>&lt;li v-for="(item, index) in items"&gt;{ &lt;br /&gt;`</code>{ index }} - {
  <br>
  `<code>{ item }}&lt;/li&gt;</code> 可以根据 <code>items</code> 数组中的内容来动态地生成列表项。</li>
 <li><code>v-on</code>: 用于监听 DOM 事件，并在触发时执行指定的方法。例如，<code>&lt;button v-on:click="handleClick"&gt;点击我&lt;/button&gt;</code> 会在按钮被点击时执行 <code>handleClick</code> 方法。</li>
</ol>
<p>以上是 Vue.js 中一些常用的基础指令，它们可以帮助你更方便地操作和渲染数据。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140077035</p>]]></description><guid isPermaLink="false">/archives/6d3122c0-605d-484f-98af-a57d2a044eba</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:42:23 GMT</pubDate></item><item><title><![CDATA[VUE实现简易购物车]]></title><link>https://wiki.dgfygk.com/archives/47c49938-c4dd-4d18-8de3-8d340ede114d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=VUE%E5%AE%9E%E7%8E%B0%E7%AE%80%E6%98%93%E8%B4%AD%E7%89%A9%E8%BD%A6&amp;url=/archives/47c49938-c4dd-4d18-8de3-8d340ede114d" width="1" height="1" alt="" style="opacity:0;">
<p>主要是对基础的指令的使用，直接上代码：</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;

&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Document&lt;/title&gt;
    &lt;style type="text/css"&gt;
        table {
            width: 500px;
            text-align: center;
            border-collapse: collapse;

        }

        table,
        th,
        td {
            border: 3px double black;
        }
        input{
            width: 120px;
        }
    &lt;/style&gt;
    &lt;script src="https://cdn.jsdelivr.net/npm/vue@3.2.31/dist/vue.global.js"&gt;&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;div id="falsebody"&gt;
        &lt;table id="tableBox" cellspacing="0" cellpadding="9"&gt;
            &lt;tr&gt;
                &lt;td&gt;名称&lt;/td&gt;
                &lt;td&gt;价格&lt;/td&gt;
                &lt;td&gt;数量&lt;/td&gt;
                &lt;td&gt;操作&lt;/td&gt;
            &lt;/tr&gt;


            &lt;tbody&gt;
                &lt;tr v-for="(item,index) in goods" @click="changeColor(index)"
                    :style="{backgroundColor: k != index ? '' : '#0078D4'}"&gt;
                    &lt;td&gt;{
  {item.name}}&lt;/td&gt;
                    &lt;td&gt;￥{
  {item.price}}&lt;/td&gt;
                    &lt;td&gt;
                        &lt;button type="button" @click="redruce(index)"&gt;-&lt;/button&gt;
                        {
  {item.num}}
                        &lt;button type="button" @click="plus(index)"&gt;+&lt;/button&gt;
                    &lt;/td&gt;
                    &lt;td&gt;
                        &lt;button type="button" @click="editBlock(item,index)"&gt;编辑&lt;/button&gt;
                        &lt;button type="button" @click="del(index)"&gt;删除&lt;/button&gt;
                    &lt;/td&gt;
                &lt;/tr&gt;
            &lt;/tbody&gt;

            &lt;tfoot&gt;
                &lt;tr&gt;
                    &lt;td&gt;&lt;/td&gt;
                    &lt;td&gt;总价:￥{
  {allPrice}}&lt;/td&gt;
                    &lt;td&gt;总数量:{
  {allNum}}&lt;/td&gt;
                    &lt;td&gt;&lt;/td&gt;
                &lt;/tr&gt;
            &lt;/tfoot&gt;
        &lt;/table&gt;



        &lt;button type="button" @click="addBlock()"&gt;添加商品&lt;/button&gt;


        &lt;div v-show="addShow"&gt;
            &lt;input v-model="name" type="text" name="" value="" placeholder="名称" /&gt;&amp;nbsp;
            &lt;input v-model="price" type="number" name="" value="" placeholder="价格" /&gt;&amp;nbsp;
            &lt;input v-model="num" type="number" name="" value="" placeholder="数量" /&gt;&amp;nbsp;
            &lt;button type="button" @click="addSure()"&gt;确定&lt;/button&gt;&amp;nbsp;
            &lt;button type="button" @click="addCancel()"&gt;取消&lt;/button&gt;
        &lt;/div&gt;


        &lt;div v-show="editShow"&gt;
            &lt;input v-model="editname" type="text" name="" /&gt;&amp;nbsp;
            &lt;input v-model="editprice" type="number" name="" /&gt;&amp;nbsp;
            &lt;input v-model="editnum" type="number" name="" /&gt;&amp;nbsp;
            &lt;button type="button" @click="editSure()"&gt;确定&lt;/button&gt;&amp;nbsp;
            &lt;button type="button" @click="editCancel()"&gt;取消&lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
        let { createApp, ref, reactive } = Vue;
        createApp({
            setup() {

                //假数据
                const goods = ref([
                    {
                        "name": "衣服",
                        "price": 39,
                        "num": 1
                    },
                    {
                        "name": "鞋子",
                        "price": 69,
                        "num": 1
                    }
                ])

                //减少商品数量
                function redruce(i) {
                    goods.value[i].num--;
                    // 不能减到负数
                    if (goods.value[i].num &lt;= 0) {
                        goods.value[i].num = 0;
                    }
                    calculation();
                }

                //增加商品数量
                function plus(i) {
                    goods.value[i].num++;
                    calculation();
                };


                // 声明一个变量计算总价
                let allPrice = ref(0);
                // 声明一个变量计算总数量
                let allNum = ref(0);


                calculation();
                //计算总价格和总数量
                function calculation() {
                    allPrice.value = 0;
                    allNum.value = 0;
                    for (let i in goods.value) {
                        allPrice.value += goods.value[i].num * goods.value[i].price;
                        allNum.value += goods.value[i].num;
                    };
                };


                // 默认隐藏添加输入框
                let addShow = ref(false);

                // v-model添加 获取输入框输入的新数据
                let name = ref('');
                let price = ref('');
                let num = ref('');

                //点击添加
                function addBlock() {
                    name.value = '';
                    price.value = '';
                    num.value = '';
                    addShow.value = true;
                };

                //点击确定添加
                function addSure() {
                    // 判断不为空
                    if (name.value != '' &amp;&amp; price.value != '' &amp;&amp; num.value != '') {
                        // 添加的新数据
                        let newData = {
                            name: name.value,
                            price: price.value,
                            num: num.value
                        };

                        goods.value.push(newData);
                        addShow.value = false;
                        calculation();
                    };
                };
                //点击取消添加
                function addCancel() {
                    name.value = '';
                    price.value = '';
                    num.value = '';
                    addShow.value = false;
                };

                // v-model编辑 更改元数据
                let editname = ref('');
                let editprice = ref('');
                let editnum = ref('');
                //编辑的默认数据
                let defaultData = reactive(
                    {
                        editname: '',
                        editprice: '',
                        editnum: ''
                    }
                );


                //默认隐藏编辑输入框
                let editShow = ref(false);

                let index;// 接收点击编辑时传的下标
                //点击编辑按钮
                function editBlock(item, i) {
                    Object.assign(defaultData, item);
                    editname.value = item.name;
                    editprice.value = item.price;
                    editnum.value = item.num;
                    index = i;
                    editShow.value = true;
                };

                //点击确定编辑
                function editSure() {
                    goods.value[index].name = editname.value;
                    goods.value[index].price = editprice.value;
                    goods.value[index].num = editnum.value;
                    calculation();
                    editShow.value = false;
                };

                //点击取消编辑
                function editCancel() {
                    editShow.value = false;
                };

                //点击删除
                function del(i) {
                    goods.value.splice(i, 1);
                    editShow.value = false;
                    calculation();
                };





                //点击商品操作时改变背景颜色
                let k = ref();

                //点击换色
                function changeColor(i) {
                    k.value = i;
                };

                return {
                    goods,
                    allNum,
                    allPrice,
                    redruce,
                    plus,
                    name,
                    num,
                    price,

                    addShow,
                    addBlock,
                    addSure,
                    addCancel,

                    editShow,
                    defaultData,
                    editname,
                    editprice,
                    editnum,
                    editBlock,
                    editSure,
                    editCancel,

                    del,

                    k,
                    changeColor
                }
            }


        }).mount('#falsebody');

    &lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140076954</p>]]></description><guid isPermaLink="false">/archives/47c49938-c4dd-4d18-8de3-8d340ede114d</guid><dc:creator>黄健</dc:creator><category>前端</category><pubDate>Thu, 26 Sep 2024 02:41:56 GMT</pubDate></item><item><title><![CDATA[箭头函数的应用场景]]></title><link>https://wiki.dgfygk.com/archives/f6488f2a-0689-4f7f-9dbd-2c3197d61576</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF&amp;url=/archives/f6488f2a-0689-4f7f-9dbd-2c3197d61576" width="1" height="1" alt="" style="opacity:0;">
<p>箭头函数是 ES6 中新增的一种函数书写方式，通常用于简洁地定义匿名函数。它的应用场景包括但不限于以下几个方面：</p>
<p>1.简化回调函数：箭头函数可以让回调函数的书写更加简洁，减少代码量。</p>
<pre><code class="hljs">// 传统函数形式
setTimeout(function() {
  console.log('Hello, world!');
}, 1000);

// 使用箭头函数
setTimeout(() =&gt; console.log('Hello, world!'), 1000);
</code></pre>
<p>2.简化匿名函数：在需要定义匿名函数的场景下，箭头函数可以提供更简洁的语法。</p>
<pre><code class="hljs">// 传统函数形式
const numbers = [1, 2, 3, 4];
const squareNumbers = numbers.map(function(num) {
  return num * num;
});

// 使用箭头函数
const squareNumbers = numbers.map(num =&gt; num * num);
</code></pre>
<p>3.this 指向：箭头函数没有自己的 this，会从定义时的外层作用域继承 this，避免了传统函数中 this 指向发生变化的问题。</p>
<pre><code class="hljs">// 传统函数形式
const obj = {
  value: 10,
  getValue: function() {
    console.log(this.value);
  }
};
obj.getValue(); // 输出 10

// 使用箭头函数
const obj = {
  value: 10,
  getValue: function() {
    setTimeout(() =&gt; {
      console.log(this.value);
    }, 1000);
  }
};
obj.getValue(); // 输出 10
</code></pre>
<p>4.简化数组操作：在使用数组的高阶函数（如 map、filter、reduce）时，箭头函数可以提供更简洁、易读的语法。</p>
<p>总的来说，箭头函数适合在简单的函数定义、回调函数或处理数组等场景下使用，能够提高代码的可读性和简洁性。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140024287</p>]]></description><guid isPermaLink="false">/archives/f6488f2a-0689-4f7f-9dbd-2c3197d61576</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:39:17 GMT</pubDate></item><item><title><![CDATA[去除数组重复成员的方法]]></title><link>https://wiki.dgfygk.com/archives/f106129f-c257-44ea-a9af-846309cc2da9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8E%BB%E9%99%A4%E6%95%B0%E7%BB%84%E9%87%8D%E5%A4%8D%E6%88%90%E5%91%98%E7%9A%84%E6%96%B9%E6%B3%95&amp;url=/archives/f106129f-c257-44ea-a9af-846309cc2da9" width="1" height="1" alt="" style="opacity:0;">
<p>方法 1 扩展运算符和 Set 结构相结合，就可以去除数组的重复成员</p>
<pre><code class="hljs">// 去除数组的重复成员
[...new Set([1, 2, 2, 3, 4, 5, 5])];
// [1, 2, 3, 4, 5]
</code></pre>
<p>方法 2</p>
<pre><code class="hljs">function dedupe(array) {
    return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]); // [1, 2, 3]
</code></pre>
<p>方法 3（ES5）</p>
<pre><code class="hljs">function unique(arry) {
    const temp = [];
    arry.forEach(e =&gt; {
        if (temp.indexOf(e) == -1) {
            temp.push(e);
        }
    });

    return temp;
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693898</p>]]></description><guid isPermaLink="false">/archives/f106129f-c257-44ea-a9af-846309cc2da9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:38:47 GMT</pubDate></item><item><title><![CDATA[如何阻止冒泡与默认行为]]></title><link>https://wiki.dgfygk.com/archives/1ded9a23-ecf6-4f1d-8dc4-a9651bb4420e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E9%98%BB%E6%AD%A2%E5%86%92%E6%B3%A1%E4%B8%8E%E9%BB%98%E8%AE%A4%E8%A1%8C%E4%B8%BA&amp;url=/archives/1ded9a23-ecf6-4f1d-8dc4-a9651bb4420e" width="1" height="1" alt="" style="opacity:0;">
<ul>
 <li>阻止冒泡行为：非 IE 浏览器 stopPropagation()，IE 浏览器 window.event.cancelBubble = true</li>
 <li>阻止默认行为：非 IE 浏览器 preventDefault()，IE 浏览器 window.event.returnValue = false</li>
</ul>
<p>
 <br>
</p>
<p>当需要阻止冒泡行为时，可以使用</p>
<pre><code class="hljs">function stopBubble(e) {
    //如果提供了事件对象，则这是一个非IE浏览器
    if (e &amp;&amp; e.stopPropagation)
        //因此它支持W3C的stopPropagation()方法
        e.stopPropagation();
    //否则，我们需要使用IE的方式来取消事件冒泡
    else window.event.cancelBubble = true;
}
</code></pre>
<p>当需要阻止默认行为时，可以使用</p>
<pre><code class="hljs">//阻止浏览器的默认行为
function stopDefault(e) {
    //阻止默认浏览器动作(W3C)
    if (e &amp;&amp; e.preventDefault) e.preventDefault();
    //IE中阻止函数器默认动作的方式
    else window.event.returnValue = false;
    return false;
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693882</p>]]></description><guid isPermaLink="false">/archives/1ded9a23-ecf6-4f1d-8dc4-a9651bb4420e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:38:08 GMT</pubDate></item><item><title><![CDATA[详细学习es6扩展运算符]]></title><link>https://wiki.dgfygk.com/archives/5258f778-962e-4259-bf53-494da7a2dd0c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%A6%E7%BB%86%E5%AD%A6%E4%B9%A0es6%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6&amp;url=/archives/5258f778-962e-4259-bf53-494da7a2dd0c" width="1" height="1" alt="" style="opacity:0;">
<p>ES6中的扩展运算符（Spread Operator）是一种非常方便的语法，主要用于将可迭代对象（比如数组、字符串等）展开成多个参数。以下是关于ES6扩展运算符的详细内容：</p>
<p>用法：</p>
<p>在数组字面量中展开数组：</p>
<pre><code class="javascript">const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArr = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
</code></pre>
<p>在函数调用中传递参数：</p>
<pre><code class="java">const numbers = [1, 2, 3];
const max = Math.max(...numbers); // 3
</code></pre>
<p>与解构赋值结合使用：</p>
<pre><code class="java">const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest);  // [2, 3, 4, 5]
</code></pre>
<ol>
 <li>
  <p>作用：</p>
 </li>
 <li>
  <p>方便地组合数组或对象，避免手动拼接。</p>
 </li>
 <li>
  <p>可以将函数的参数列表转换为数组，方便操作。</p>
 </li>
 <li>
  <p>可以快速复制数组或对象，避免引用传递。</p>
 </li>
 <li>
  <p>需要注意的地方：</p>
 </li>
 <li>
  <p>使用扩展运算符时，会逐个展开数组或类数组对象的元素。</p>
 </li>
 <li>
  <p>不能展开具有迭代特性的对象（如Map、Set等），需要转换为数组后才能使用扩展运算符。</p>
 </li>
</ol>
<p>需要注意的是，扩展运算符在ES6中引入，并得到了广泛应用。它能简化代码，提高代码可读性和可维护性。希望以上内容可以帮助你更好地理解和使用ES6的扩展运算符。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/140024037</p>]]></description><guid isPermaLink="false">/archives/5258f778-962e-4259-bf53-494da7a2dd0c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:37:47 GMT</pubDate></item><item><title><![CDATA[如何做到修改 url 参数页面不刷新]]></title><link>https://wiki.dgfygk.com/archives/2303eca0-8a57-4459-ac1d-a5c66e7e9f9f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%81%9A%E5%88%B0%E4%BF%AE%E6%94%B9%20url%20%E5%8F%82%E6%95%B0%E9%A1%B5%E9%9D%A2%E4%B8%8D%E5%88%B7%E6%96%B0&amp;url=/archives/2303eca0-8a57-4459-ac1d-a5c66e7e9f9f" width="1" height="1" alt="" style="opacity:0;">
<p>HTML5 引入了 <code>history.pushState()</code> 和 <code>history.replaceState()</code> 方法，它们分别可以添加和修改历史记录条目。</p>
<pre><code class="hljs">let stateObj = {
    foo: "bar"
};

history.pushState(stateObj, "page 2", "bar.html");
</code></pre>
<p>假设当前页面为 <code>foo.html</code> ，执行上述代码后会变为 <code>bar.html</code> ，点击浏览器后退，会变为 <code>foo.html</code> ，但浏览器并不会刷新。 <code>pushState()</code> 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和 (可选的) 一个 URL.让我们来解释下这三个参数详细内容：</p>
<ul>
 <li>
  <p>状态对象 — 状态对象 <code>state</code> 是一个 JavaScript 对象，通过 <code>pushState ()</code> 创建新的历史记录条目。无论什么时候用户导航到新的状态， <code>popstate</code> 事件就会被触发，且该事件的 <code>state</code> 属性包含该历史记录条目状态对象的副本。</p>
  <p>状态对象可以是能被序列化的任何东西。原因在于 Firefox 将状态对象保存在用户的磁盘上，以便在用户重启浏览器时使用，我们规定了状态对象在序列化表示后有 640k 的大小限制。如果你给 <code>pushState()</code> 方法传了一个序列化后大于 640k 的状态对象，该方法会抛出异常。如果你需要更大的空间，建议使用 <code>sessionStorage</code> 以及 <code>localStorage</code> .</p>
 </li>
 <li>
  <p>标题 — Firefox 目前忽略这个参数，但未来可能会用到。传递一个空字符串在这里是安全的，而在将来这是不安全的。二选一的话，你可以为跳转的 <code>state</code> 传递一个短标题。</p>
 </li>
 <li>
  <p>URL — 该参数定义了新的历史 URL 记录。注意，调用 <code>pushState()</code> 后浏览器并不会立即加载这个 URL，但可能会在稍后某些情况下加载这个 URL，比如在用户重新打开浏览器时。新 URL 不必须为绝对路径。如果新 URL 是相对路径，那么它将被作为相对于当前 URL 处理。新 URL 必须与当前 URL 同源，否则 <code>pushState()</code> 会抛出一个异常。该参数是可选的，缺省为当前 URL。</p>
 </li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693872</p>]]></description><guid isPermaLink="false">/archives/2303eca0-8a57-4459-ac1d-a5c66e7e9f9f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:37:29 GMT</pubDate></item><item><title><![CDATA[数组的常用方法]]></title><link>https://wiki.dgfygk.com/archives/c2bf382a-611a-4f04-bde7-8cee1a4ecfc4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E7%BB%84%E7%9A%84%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95&amp;url=/archives/c2bf382a-611a-4f04-bde7-8cee1a4ecfc4" width="1" height="1" alt="" style="opacity:0;">
<p>1.Array.map()</p>
<p>此方法是将数组中的每个元素调用一个提供的函数，结果作为一个新的数组返回，并没有改变原来的数组</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5];
let newArr = arr.map(x =&gt; x * 2);
//arr= [1, 2, 3, 4, 5]   原数组保持不变
//newArr = [2, 4, 6, 8, 10] 返回新数组
</code></pre>
<p>2.Array.forEach()</p>
<p>此方法是将数组中的每个元素执行传进提供的函数，没有返回值，直接改变原数组，注意和 map 方法区分</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5];
num.forEach(x =&gt; x * 2);
// arr = [2, 4, 6, 8, 10]  数组改变,注意和map区分
</code></pre>
<p>3.Array.filter()</p>
<p>此方法是将所有元素进行判断，将满足条件的元素作为一个新的数组返回</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5]
const isBigEnough =&gt; value =&gt; value &gt;= 3
let newArr = arr.filter(isBigEnough)
//newNum = [3, 4, 5] 满足条件的元素返回为一个新的数组
</code></pre>
<p>4.Array.every()</p>
<p>此方法是将所有元素进行判断返回一个布尔值，如果所有元素都满足判断条件，则返回 true，否则为 false：</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5]
const isLessThan4 =&gt; value =&gt; value &lt; 4
const isLessThan6 =&gt; value =&gt; value &lt; 6
arr.every(isLessThan4) //false
arr.every(isLessThan6) //true
</code></pre>
<p>5.Array.some()</p>
<p>此方法是将所有元素进行判断返回一个布尔值，如果存在元素都满足判断条件，则返回 true，若所有元素都不满足判断条件，则返回 false：</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5]
const isLessThan4 =&gt; value =&gt; value &lt; 4
const isLessThan6 =&gt; value =&gt; value &gt; 6
arr.some(isLessThan4) //true
arr.some(isLessThan6) //false
</code></pre>
<p>6.Array.reduce()</p>
<p>此方法是所有元素调用返回函数，返回值为最后结果, 传入的值必须是函数类型：</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5];
const add = (a, b) =&gt; a + b;
let sum = arr.reduce(add);
//sum = 15  相当于累加的效果
</code></pre>
<p>与之相对应的还有一个 Array.reduceRight() 方法，区别是这个是从右向左操作的</p>
<p>7.Array.push()</p>
<p>此方法是在数组的后面添加新加元素，此方法改变了数组的长度：</p>
<p>8.Array.pop()</p>
<p>此方法在数组后面删除最后一个元素，并返回数组，此方法改变了数组的长度：</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5];
arr.pop();
console.log(arr); //[1, 2, 3, 4]
console.log(arr.length); //4
</code></pre>
<p>9.Array.shift()</p>
<p>此方法在数组后面删除第一个元素，并返回数组，此方法改变了数组的长度：</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5];
arr.shift();
console.log(arr); //[2, 3, 4, 5]
console.log(arr.length); //4
</code></pre>
<p>10.Array.unshift()</p>
<p>此方法是将一个或多个元素添加到数组的开头，并返回新数组的长度：</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5];
arr.unshift(6, 7);
console.log(arr); //[6, 7, 2, 3, 4, 5]
console.log(arr.length); //7
</code></pre>
<p>11.Array.isArray()</p>
<p>判断一个对象是不是数组，返回的是布尔值</p>
<p>12.Array.concat()</p>
<p>此方法是一个可以将多个数组拼接成一个数组：</p>
<pre><code class="hljs">let arr1 = [1, 2, 3]
arr2 = [4, 5]
let arr = arr1.concat(arr2)
console.log(arr) //[1, 2, 3, 4, 5]
</code></pre>
<pre><code>```
13.Array.toString()

 此方法将数组转化为字符串：
</code></pre>
<pre><code>```hljs
`
``
js
let arr = [1, 2, 3, 4, 5];
let str = arr.toString()
console.log(str) // 1,2,3,4,5
``
`　
</code></pre>
<pre><code>```
14.Array.join()

  此方法也是将数组转化为字符串：
</code></pre>
<pre><code>```hljs
`
``
js
let arr = [1, 2, 3, 4, 5];
let str1 = arr.toString()
let str2 = arr.toString(',')
let str3 = arr.toString('##')
console.log(str1) // 12345
console.log(str2) // 1,2,3,4,5
console.log(str3) // 1##2##3##4##5
</code></pre>
<pre><code>```
</code></pre>
<p>通过例子可以看出和 toString 的区别，可以设置元素之间的间隔\~</p>
<p>15.Array.splice(开始位置， 删除的个数，元素)</p>
<p>万能方法，可以实现增删改：</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4, 5];
let arr1 = arr.splice(2, 0 'haha')
let arr2 = arr.splice(2, 3)
let arr1 = arr.splice(2, 1 'haha')
console.log(arr1) //[1, 2, 'haha', 3, 4, 5]新增一个元素
console.log(arr2) //[1, 2] 删除三个元素
console.log(arr3) //[1, 2, 'haha', 4, 5] 替换一个元素
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693845</p>]]></description><guid isPermaLink="false">/archives/c2bf382a-611a-4f04-bde7-8cee1a4ecfc4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:37:15 GMT</pubDate></item><item><title><![CDATA[call()与apply()的作用与区别？]]></title><link>https://wiki.dgfygk.com/archives/2140c306-9720-415b-8c47-7c4a1a6b7ea9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=call%28%29%E4%B8%8Eapply%28%29%E7%9A%84%E4%BD%9C%E7%94%A8%E4%B8%8E%E5%8C%BA%E5%88%AB%EF%BC%9F&amp;url=/archives/2140c306-9720-415b-8c47-7c4a1a6b7ea9" width="1" height="1" alt="" style="opacity:0;">
<ol>
 <li>概念</li>
</ol>
<p>每个函数都包含两个非继承而来的方法：apply()和call()。 call与apply都属于Function.prototype的一个方法，所以每个function实例都有call、apply属性；</p>
<ol>
 <li>作用</li>
</ol>
<p>call()方法和apply()方法的作用相同：改变this指向。</p>
<ol>
 <li>区别</li>
</ol>
<p>他们的区别在于接收参数的方式不同： call()：第一个参数是this值没有变化，变化的是其余参数都直接传递给函数。在使用call()方法时，传递给函数的参数必须逐个列举出来。 apply()：传递给函数的是参数数组</p>
<ol>
 <li>应用</li>
</ol>
<pre><code class="hljs">  var name = "Evan";
  var age = 20;

  var person = {
    name: "Hillary",
    age: 19,
    sayIntroduce: function () {
      return (
        "Hello, My name is " + this.name +" and I'm " +this.age +" years old."
      );
    },
    sayHobby: function (val1, val2) {
        return "I'm " + this.name + ", I like " + val1 + " and " + val2 + ".";
    }
  };


  var person1 = {
    name: "Coy",
  };


  console.log(person.sayIntroduce()); 
  // Hello, My name is Hillary and I'm 19 years old.

  // 当我们通过 call 和 apply 来this的指向时，不传任何参数，则默认为将this指向修改为 windows
  console.log(person.sayIntroduce.call()); 
  // Hello, My name is Evan and I'm 20 years old.
  console.log(person.sayIntroduce.apply()); 
  // Hello, My name is Evan and I'm 20 years old.

  // 有参数时，this 指向第一个参数：将this指向 person1，由于person1中没有age属性，因此为 undefined
  console.log(person.sayIntroduce.call(person1)); 
  // Hello, My name is Coy and I'm undefined years old.
  console.log(person.sayIntroduce.apply(person1)); 
  // Hello, My name is Coy and I'm undefined years old.

  // 当需要传递参数时，call可以直接写多个参数，apply需要用数组方式传递：
  console.log(person.sayHobby.call(person1, 'swimming', 'hiking')); 
  // I'm Coy, I like swimming and hiking. 
  console.log(person.sayHobby.apply(person1, ['swimming', 'hiking'])); 
  // I'm Coy, I like swimming and hiking. 
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693826</p>]]></description><guid isPermaLink="false">/archives/2140c306-9720-415b-8c47-7c4a1a6b7ea9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:36:47 GMT</pubDate></item><item><title><![CDATA[for in 和 for of的区别]]></title><link>https://wiki.dgfygk.com/archives/5f331cd4-8597-4860-a9c7-52069f9d404b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=for%20in%20%E5%92%8C%20for%20of%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/5f331cd4-8597-4860-a9c7-52069f9d404b" width="1" height="1" alt="" style="opacity:0;">
<p>1、for in</p>
<ul>
 <li>1.一般用于遍历对象的可枚举属性。以及对象从构造函数原型中继承的属性。对于每个不同的属性，语句都会被执行。</li>
 <li>2.不建议使用 for in 遍历数组，因为输出的顺序是不固定的。</li>
 <li>3.如果迭代的对象的变量值是 null 或者 undefined, for in 不执行循环体，建议在使用 for in 循环之前，先检查该对象的值是不是 null 或者 undefined。</li>
</ul>
<p>2、for of</p>
<ul>
 <li>1.for…of 语句在可迭代对象（包括 Array，Map，Set，String，TypedArray，arguments 对象等等）上创建一个迭代循环，调用自定义迭代钩子，并为每个不同属性的值执行语句。</li>
</ul>
<p>代码演示：</p>
<pre><code class="hljs">var s = {
    a: 1,
    b: 2,
    c: 3
};
var s1 = Object.create(s);
for (var prop in s1) {
    console.log(prop); //a b c
    console.log(s1[prop]); //1 2 3
}
for (let prop of s1) {
    console.log(prop); //报错如下 Uncaught TypeError: s1 is not iterable
}
for (let prop of Object.keys(s1)) {
    console.log(prop); // a b c
    console.log(s1[prop]); //1 2 3
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681447</p>]]></description><guid isPermaLink="false">/archives/5f331cd4-8597-4860-a9c7-52069f9d404b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:36:22 GMT</pubDate></item><item><title><![CDATA[JS 块级作用域、变量提升]]></title><link>https://wiki.dgfygk.com/archives/13a304e2-6877-40f5-ba86-23113a224ce3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%20%E5%9D%97%E7%BA%A7%E4%BD%9C%E7%94%A8%E5%9F%9F%E3%80%81%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87&amp;url=/archives/13a304e2-6877-40f5-ba86-23113a224ce3" width="1" height="1" alt="" style="opacity:0;">
<p>1.块级作用域</p>
<p>JS 中作用域有：全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称 ES6)中新增了块级作用域。块作用域由 { } 包括，if 语句和 for 语句里面的{ }也属于块作用域。</p>
<p>2.变量提升</p>
<ul>
 <li>如果变量声明在函数里面，则将变量声明提升到函数的开头</li>
 <li>如果变量声明是一个全局变量，则将变量声明提升到全局作用域的开头</li>
</ul>
<pre><code class="hljs">&lt;script type = "text/javascript"&gt; 
{
    var a = 1;
    console.log(a); // 1
}
console.log(a); // 1
// 可见，通过var定义的变量可以跨块作用域访问到。

(function A() {
    var b = 2;
    console.log(b); // 2
})();
// console.log(b); // 报错，
// 可见，通过var定义的变量不能跨函数作用域访问到

if (true) {
    var c = 3;
}
console.log(c); // 3
for (var i = 0; i &lt; 4; i++) {
    var d = 5;
};
console.log(i); // 4  (循环结束i已经是4，所以此处i为4)
console.log(d); // 5
// if语句和for语句中用var定义的变量可以在外面访问到，
// 可见，if语句和for语句属于块作用域，不属于函数作用域。

{
    var a = 1;
    let b = 2;
    const c = 3;
    {
        console.log(a); // 1    子作用域可以访问到父作用域的变量
        console.log(b); // 2    子作用域可以访问到父作用域的变量
        console.log(c); // 3    子作用域可以访问到父作用域的变量

        var aa = 11;
        let bb = 22;
        const cc = 33;
    }

    console.log(aa); // 11  // 可以跨块访问到子 块作用域 的变量
    console.log(bb); // 报错  bb is not defined
    console.log(cc); // 报错  cc is not defined
} 
&lt;/script&gt;
</code></pre>
<p>var、let、const 的区别</p>
<ul>
 <li>var 定义的变量，没有块的概念，可以跨块访问, 不能跨函数访问。</li>
 <li>let 定义的变量，只能在块作用域里访问，不能跨块访问，也不能跨函数访问。</li>
 <li>const 用来定义常量，使用时必须初始化(即必须赋值)，只能在块作用域里访问，而且不能修改。</li>
 <li>同一个变量只能使用一种方式声明，不然会报错</li>
</ul>
<pre><code class="hljs">&lt;script type = "text/javascript"&gt;
// 块作用域
{
    var a = 1;
    let b = 2;
    const c = 3;
    // c = 4; // 报错
    // let a = 'a'; // 报错  注：是上面 var a = 1; 那行报错
    // var b = 'b'; // 报错：本行报错
    // const a = 'a1';  // 报错  注：是上面 var a = 1; 那行报错
    // let c = 'c'; // 报错：本行报错
    var aa;
    let bb;
    // const cc; // 报错
    console.log(a); // 1
    console.log(b); // 2
    console.log(c); // 3
    console.log(aa); // undefined
    console.log(bb); // undefined
}
console.log(a); // 1
// console.log(b); // 报错
// console.log(c); // 报错

// 函数作用域
(function A() {
    var d = 5;
    let e = 6;
    const f = 7;
    console.log(d); // 5
    console.log(e); // 6  (在同一个{ }中,也属于同一个块，可以正常访问到)
    console.log(f); // 7  (在同一个{ }中,也属于同一个块，可以正常访问到)
})();
// console.log(d); // 报错
// console.log(e); // 报错
// console.log(f); // 报错
&lt;/script&gt;
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693759</p>]]></description><guid isPermaLink="false">/archives/13a304e2-6877-40f5-ba86-23113a224ce3</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:35:58 GMT</pubDate></item><item><title><![CDATA[JS 哪些操作会造成内存泄露]]></title><link>https://wiki.dgfygk.com/archives/daec376d-a5e8-405b-aa1a-29757195ce22</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%20%E5%93%AA%E4%BA%9B%E6%93%8D%E4%BD%9C%E4%BC%9A%E9%80%A0%E6%88%90%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2&amp;url=/archives/daec376d-a5e8-405b-aa1a-29757195ce22" width="1" height="1" alt="" style="opacity:0;">
<p><strong>1）意外的全局变量引起的内存泄露</strong></p>
<pre><code class="hljs">function leak() {
    leak = "xxx"; //leak成为一个全局变量，不会被回收
}
</code></pre>
<p><strong>2）闭包引起的内存泄露</strong></p>
<pre><code class="hljs">function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = function() {
        //Even if it's a empty function
    };
}
</code></pre>
<p>闭包可以维持函数内局部变量，使其得不到释放。 上例定义事件回调时，由于是函数内定义函数，并且内部函数–事件回调的引用外暴了，形成了闭包。 解决之道，将事件处理函数定义在外部，解除闭包, 或者在定义事件处理函数的外部函数中，删除对 dom 的引用。</p>
<pre><code class="hljs">//将事件处理函数定义在外部
function onclickHandler() {
    //do something
}

function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = onclickHandler;
}

//在定义事件处理函数的外部函数中，删除对dom的引用
function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = function() {
        //Even if it's a empty function
    };
    obj = null;
}
</code></pre>
<p><strong>3）没有清理的 DOM 元素引用</strong></p>
<pre><code class="hljs">var elements = {
    button: document.getElementById("button"),
    image: document.getElementById("image"),
    text: document.getElementById("text")
};

function doStuff() {
    image.src = "http://some.url/image";
    button.click():
        console.log(text.innerHTML)
}

function removeButton() {
    document.body.removeChild(document.getElementById('button'))
}
</code></pre>
<p><strong>4）被遗忘的定时器或者回调</strong></p>
<pre><code class="hljs">var someResouce = getData();
setInterval(function() {
    var node = document.getElementById("Node");
    if (node) {
        node.innerHTML = JSON.stringify(someResouce);
    }
}, 1000);
</code></pre>
<p>这样的代码很常见, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。</p>
<p><strong>5）子元素存在引起的内存泄露</strong></p>
<p>黄色是指直接被 js 变量所引用，在内存里，红色是指间接被 js 变量所引用，如上图，refB 被 refA 间接引用，导致即使 refB 变量被清空，也是不会被回收的子元素 refB 由于 parentNode 的间接引用，只要它不被删除，它所有的父元素（图中红色部分）都不会被删除。</p>
<p><strong>6）IE7/8 引用计数使用循环引用产生的问题</strong></p>
<pre><code class="hljs">function fn() {
    var a = {};
    var b = {};
    a.pro = b;
    b.pro = a;
}
fn();
</code></pre>
<p>fn()执行完毕后，两个对象都已经离开环境，在标记清除方式下是没有问题的，但是在引用计数策略下，因为 a 和 b 的引用次数不为 0，所以不会被垃圾回收器回收内存，如果 fn 函数被大量调用，就会造成内存泄漏。在 IE7 与 IE8 上，内存直线上升。 IE 中有一部分对象并不是原生 js 对象。例如，其内存泄漏 DOM 和 BOM 中的对象就是使用 C++以 COM 对象的形式实现的，而 COM 对象的垃圾回收机制采用的就是引用计数策略。因此，即使 IE 的 js 引擎采用标记清除策略来实现，但 js 访问的 COM 对象依然是基于引用计数策略的。换句话说，只要在 IE 中涉及 COM 对象，就会存在循环引用的问题。</p>
<pre><code class="hljs">var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;
</code></pre>
<p>上面的例子在一个 DOM 元素（element)与一个原生 js 对象（myObject)之间创建了循环引用。其中，变量 myObject 有一个名为 e 的属性指向 element 对象；而变量 element 也有一个属性名为 o 回指 myObject。由于存在这个循环引用，即使例子中的 DOM 从页面中移除，它也永远不会被回收。</p>
<p>看上面的例子，有人会觉得太弱了，谁会做这样无聊的事情，但是其实我们经常会这样做</p>
<pre><code class="hljs">window.οnlοad = function outerFunction() {
    var obj = document.getElementById("element"):
        obj.οnclick = function innerFunction() {};
};
</code></pre>
<p>这段代码看起来没什么问题，但是 obj 引用了 document.getElementById(“element”)，而 document.getElementById(“element”)的 onclick 方法会引用外部环境中的变量，自然也包括 obj，是不是很隐蔽啊。</p>
<p>最简单的解决方式就是自己手工解除循环引用，比如刚才的函数可以这样</p>
<pre><code class="hljs">myObject.element = null;
element.o = null;
window.οnlοad = function outerFunction() {
    var obj = document.getElementById("element"):
        obj.οnclick = function innerFunction() {};
    obj = null;
};
</code></pre>
<p>将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时，就会删除这些值并回收它们占用的内存。 要注意的是，IE9+并不存在循环引用导致 Dom 内存泄漏问题，可能是微软做了优化，或者 Dom 的回收方式已经改变</p>
<p>解析：</p>
<p><strong>1、JS 的回收机制</strong></p>
<p>JavaScript 垃圾回收的机制很简单：找出不再使用的变量，然后释放掉其占用的内存，但是这个过程不是实时的，因为其开销比较大，所以垃圾回收系统（GC）会按照固定的时间间隔, 周期性的执行。</p>
<p>到底哪个变量是没有用的？所以垃圾收集器必须跟踪到底哪个变量没用，对于不再有用的变量打上标记，以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别，通常情况下有两种实现方式：标记清除和引用计数。引用计数不太常用，标记清除较为常用。</p>
<p><strong>2、标记清除（mark and sweep）</strong></p>
<p>js 中最常用的垃圾回收方式就是标记清除。当变量进入环境时，例如，在函数中声明一个变量，就将这个变量标记为"进入环境”。从逻辑上讲，永远不能释放进入环境的变量所占用的内存，因为只要执行流进入相应的环境，就可能会用到它们。而当变量离开环境时，则将其标记为"离开环境”。</p>
<pre><code class="hljs">function test() {
    var a = 10; //被标记，进入环境
    var b = 20; //被标记，进入环境
}
test(); //执行完毕之后a、b又被标记离开环境，被回收
</code></pre>
<p><strong>3、引用计数(reference counting)</strong></p>
<p>引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值（function object array）赋给该变量时，则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量，则该值的引用次数加 1。相反，如果包含对这个值引用的变量又取得了另外一个值，则这个值的引用次数减 1。当这个值的引用次数变成 0 时，则说明没有办法再访问这个值了，因而就可以将其占用的内存空间回收回来。这样，当垃圾回收器下次再运行时，它就会释放那些引用次数为 0 的值所占用的内存。</p>
<pre><code class="hljs">function test() {
    var a = {}; //a的引用次数为0
    var b = a; //a的引用次数加1，为1
    var c = a; //a的引用次数加1，为2
    var b = {}; //a的引用次数减1，为1
}
</code></pre>
<p><strong>4、如何分析内存的使用情况</strong></p>
<p>Google Chrome 浏览器提供了非常强大的 JS 调试工具，Memory 视图 profiles 视图让你可以对 JavaScript 代码运行时的内存进行快照，并且可以比较这些内存快照。它还让你可以记录一段时间内的内存分配情况。在每一个结果视图中都可以展示不同类型的列表，但是对我们最有用的是 summary 列表和 comparison 列表。 summary 视图提供了不同类型的分配对象以及它们的合计大小：shallow size （一个特定类型的所有对象的总和）和 retained size （shallow size 加上保留此对象的其它对象的大小）。distance 显示了对象到达 GC 根（校者注：最初引用的那块内存，具体内容可自行搜索该术语）的最短距离。 comparison 视图提供了同样的信息但是允许对比不同的快照。这对于找到泄漏很有帮助。</p>
<p>5、怎样避免内存泄露</p>
<p>1）减少不必要的全局变量，或者生命周期较长的对象，及时对无用的数据进行垃圾回收；</p>
<p>2）注意程序逻辑，避免"死循环"之类的 ；</p>
<p>3）避免创建过多的对象 原则：不用了的东西要及时归还。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693775</p>]]></description><guid isPermaLink="false">/archives/daec376d-a5e8-405b-aa1a-29757195ce22</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:35:35 GMT</pubDate></item><item><title><![CDATA[JS 严格模式和正常模式的区别]]></title><link>https://wiki.dgfygk.com/archives/54afeafe-600a-4f15-b6d8-a13136b36da8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%20%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F%E5%92%8C%E6%AD%A3%E5%B8%B8%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/54afeafe-600a-4f15-b6d8-a13136b36da8" width="1" height="1" alt="" style="opacity:0;">
<p>严格模式使用"use strict”;</p>
<p>作用：</p>
<ul>
 <li>消除 Javascript 语法的一些不合理、不严谨之处，减少一些怪异行为;</li>
 <li>消除代码运行的一些不安全之处，保证代码运行的安全；</li>
 <li>提高编译器效率，增加运行速度；</li>
 <li>为未来新版本的 Javascript 做好铺垫。</li>
</ul>
<p>表现：</p>
<ul>
 <li>严格模式下, delete 运算符后跟随非法标识符(即 delete 不存在的标识符)，会抛出语法错误； 非严格模式下，会静默失败并返回 false</li>
 <li>严格模式中，对象直接量中定义同名属性会抛出语法错误； 非严格模式不会报错</li>
 <li>严格模式中，函数形参存在同名的，抛出错误； 非严格模式不会</li>
 <li>严格模式不允许八进制整数直接量（如：023）</li>
 <li>严格模式中，arguments 对象是传入函数内实参列表的静态副本；非严格模式下，arguments 对象里的元素和对应的实参是指向同一个值的引用</li>
 <li>严格模式中 eval 和 arguments 当做关键字，它们不能被赋值和用作变量声明</li>
 <li>严格模式会限制对调用栈的检测能力，访问 arguments.callee.caller 会抛出异常</li>
 <li>严格模式 变量必须先声明，直接给变量赋值，不会隐式创建全局变量，不能用 with,</li>
 <li>严格模式中 call apply 传入 null undefined 保持原样不被转换为 window</li>
</ul>
<p>解析：</p>
<p>一、概述</p>
<p>除了正常运行模式，ECMAscript 5 添加了第二种运行模式：“严格模式”（strict mode）。顾名思义，这种模式使得 Javascript 在更严格的条件下运行。</p>
<p>设立"严格模式"的目的，主要有以下几个：</p>
<ul>
 <li>
  <p>消除 Javascript 语法的一些不合理、不严谨之处，减少一些怪异行为;</p>
 </li>
 <li>
  <p>消除代码运行的一些不安全之处，保证代码运行的安全；</p>
 </li>
 <li>
  <p>提高编译器效率，增加运行速度；</p>
 </li>
 <li>
  <p>为未来新版本的 Javascript 做好铺垫。</p>
 </li>
</ul>
<p>“严格模式"体现了 Javascript 更合理、更安全、更严谨的发展方向，包括 IE 10 在内的主流浏览器，都已经支持它，许多大项目已经开始全面拥抱它。</p>
<p>另一方面，同样的代码，在"严格模式"中，可能会有不一样的运行结果；一些在"正常模式"下可以运行的语句，在"严格模式"下将不能运行。掌握这些内容，有助于更细致深入地理解 Javascript，让你变成一个更好的程序员。</p>
<p>本文将对"严格模式"做详细介绍。</p>
<p>二、进入标志</p>
<p>进入"严格模式"的标志，是下面这行语句：</p>
<p>“use strict”;</p>
<p>老版本的浏览器会把它当作一行普通字符串，加以忽略。</p>
<p>三、如何调用</p>
<p>“严格模式"有两种调用方法，适用于不同的场合。</p>
<p>3.1 针对整个脚本文件</p>
<p>将"use strict"放在脚本文件的第一行，则整个脚本都将以"严格模式"运行。如果这行语句不在第一行，则无效，整个脚本以"正常模式"运行。如果不同模式的代码文件合并成一个文件，这一点需要特别注意。</p>
<p>(严格地说，只要前面不是产生实际运行结果的语句，“use strict"可以不在第一行，比如直接跟在一个空的分号后面。)</p>
<pre><code>　　&lt;script&gt;
　　　　"use strict";
　　　　console.log("这是严格模式。");
　　&lt;/script&gt;

　　&lt;script&gt;
　　　　console.log("这是正常模式。");kly, it's almost 2 years ago now.I can admit it now - I run it on my school's network that has about 50 computers.
　　&lt;/script&gt;
</code></pre>
<p>上面的代码表示，一个网页中依次有两段 Javascript 代码。前一个 script 标签是严格模式，后一个不是。</p>
<p>3.2 针对单个函数</p>
<p>将"use strict"放在函数体的第一行，则整个函数以"严格模式"运行。</p>
<pre><code class="hljs">function strict() {
    "use strict";
    return "这是严格模式。";
}

function notStrict() {
    return "这是正常模式。";
}
</code></pre>
<p>3.3 脚本文件的变通写法</p>
<p>因为第一种调用方法不利于文件合并，所以更好的做法是，借用第二种方法，将整个脚本文件放在一个立即执行的匿名函数之中。</p>
<pre><code class="hljs">(function() {
    "use strict"; // some code here

})();
</code></pre>
<p>四、语法和行为改变</p>
<p>严格模式对 Javascript 的语法和行为，都做了一些改变。</p>
<p>4.1 全局变量显式声明</p>
<p>在正常模式中，如果一个变量没有声明就赋值，默认是全局变量。严格模式禁止这种用法，全局变量必须显式声明。</p>
<pre><code class="hljs">"use strict";

v = 1; // 报错，v未声明

for (i = 0; i &lt; 2; i++) {
    // 报错，i未声明
}
</code></pre>
<p>因此，严格模式下，变量都必须先用 var 命令声明，然后再使用。</p>
<p>4.2 静态绑定</p>
<p>Javascript 语言的一个特点，就是允许"动态绑定”，即某些属性和方法到底属于哪一个对象，不是在编译时确定的，而是在运行时（runtime）确定的。</p>
<p>严格模式对动态绑定做了一些限制。某些情况下，只允许静态绑定。也就是说，属性和方法到底归属哪个对象，在编译阶段就确定。这样做有利于编译效率的提高，也使得代码更容易阅读，更少出现意外。</p>
<p>具体来说，涉及以下几个方面。</p>
<p>（1）禁止使用 with 语句</p>
<p>因为 with 语句无法在编译时就确定，属性到底归属哪个对象。</p>
<pre><code class="hljs">"use strict";

var v = 1;

with(o) { // 语法错误
    v = 2;
}
</code></pre>
<p>（2）创设 eval 作用域</p>
<p>正常模式下，Javascript 语言有两种变量作用域（scope）：全局作用域和函数作用域。严格模式创设了第三种作用域：eval 作用域。</p>
<p>正常模式下，eval 语句的作用域，取决于它处于全局作用域，还是处于函数作用域。严格模式下，eval 语句本身就是一个作用域，不再能够生成全局变量了，它所生成的变量只能用于 eval 内部。</p>
<pre><code class="hljs">"use strict";

var x = 2;

console.info(eval("var x = 5; x")); // 5

console.info(x); // 2
</code></pre>
<p>4.3 增强的安全措施</p>
<p>（1）禁止 this 关键字指向全局对象</p>
<pre><code class="hljs">function f() {
    return !this;
} // 返回false，因为"this"指向全局对象，"!this"就是false
function f() {
    "use strict";
    return !this;
} // 返回true，因为严格模式下，this的值为undefined，所以"!this"为true。
</code></pre>
<p>因此，使用构造函数时，如果忘了加 new，this 不再指向全局对象，而是报错。</p>
<pre><code class="hljs">function f() {
    "use strict";

    this.a = 1;
}

f(); // 报错，this未定义
</code></pre>
<p>（2）禁止在函数内部遍历调用栈</p>
<pre><code class="hljs">function f1() {
    "use strict";

    f1.caller; // 报错

    f1.arguments; // 报错
}

f1();
</code></pre>
<p>4.4 禁止删除变量</p>
<p>严格模式下无法删除变量。只有 configurable 设置为 true 的对象属性，才能被删除。</p>
<pre><code class="hljs">"use strict";

var x;

delete x; // 语法错误

var o = Object.create(null, {
    'x': {
        value: 1,
        configurable: true
    }
});

delete o.x; // 删除成功
</code></pre>
<p>4.5 显式报错</p>
<p>正常模式下，对一个对象的只读属性进行赋值，不会报错，只会默默地失败。严格模式下，将报错。</p>
<pre><code class="hljs">"use strict";

var o = {};

Object.defineProperty(o, "v", {
    value: 1,
    writable: false
});

o.v = 2; // 报错
</code></pre>
<p>严格模式下，对一个使用 getter 方法读取的属性进行赋值，会报错。</p>
<pre><code class="hljs">"use strict";

var o = {
    get v() {
        return 1;
    }
};

o.v = 2; // 报错
</code></pre>
<p>严格模式下，对禁止扩展的对象添加新属性，会报错。</p>
<pre><code class="hljs">"use strict";

var o = {};

Object.preventExtensions(o);

o.v = 1; // 报错
</code></pre>
<p>严格模式下，删除一个不可删除的属性，会报错。</p>
<pre><code class="hljs">"use strict";

delete Object.prototype; // 报错
</code></pre>
<p>4.6 重名错误</p>
<p>严格模式新增了一些语法错误。</p>
<p>（1）对象不能有重名的属性</p>
<p>正常模式下，如果对象有多个重名属性，最后赋值的那个属性会覆盖前面的值。严格模式下，这属于语法错误。</p>
<pre><code class="hljs">"use strict";

var o = {
    p: 1,
    p: 2
}; // 语法错误
</code></pre>
<p>（2）函数不能有重名的参数</p>
<p>正常模式下，如果函数有多个重名的参数，可以用 arguments[i]读取。严格模式下，这属于语法错误。</p>
<pre><code class="hljs">"use strict";

function f(a, a, b) { // 语法错误

    return;

}
</code></pre>
<p>4.7 禁止八进制表示法</p>
<p>正常模式下，整数的第一位如果是 0，表示这是八进制数，比如 0100 等于十进制的 64。严格模式禁止这种表示法，整数第一位为 0，将报错。</p>
<pre><code class="hljs">"use strict";

var n = 0100; // 语法错误
</code></pre>
<p>4.8 arguments 对象的限制</p>
<p>arguments 是函数的参数对象，严格模式对它的使用做了限制。</p>
<p>（1）不允许对 arguments 赋值</p>
<pre><code class="hljs">"use strict";

arguments++; // 语法错误

var obj = {
    set p(arguments) {}
}; // 语法错误

try {} catch (arguments) {} // 语法错误

function arguments() {} // 语法错误

var f = new Function("arguments", "'use strict'; return 17;"); // 语法错误
</code></pre>
<p>（2）arguments 不再追踪参数的变化</p>
<pre><code class="hljs">"use strict";

arguments++; // 语法错误

var obj = {
    set p(arguments) {}
}; // 语法错误

try {} catch (arguments) {} // 语法错误

function arguments() {} // 语法错误

var f = new Function("arguments", "'use strict'; return 17;"); // 语法错误
</code></pre>
<p>（3）禁止使用 arguments.callee</p>
<p>这意味着，你无法在匿名函数内部调用自身了。</p>
<pre><code class="hljs">"use strict";

var f = function() {
    return arguments.callee;
};

f(); // 报错
</code></pre>
<p>4.9 函数必须声明在顶层</p>
<p>将来 Javascript 的新版本会引入"块级作用域”。为了与新版本接轨，严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说，不允许在非函数的代码块内声明函数。</p>
<pre><code class="hljs">"use strict";

if (true) {
    function f() {} // 语法错误
}

for (var i = 0; i &lt; 5; i++) {
    function f2() {} // 语法错误
}
</code></pre>
<p>4.10 保留字</p>
<p>为了向将来 Javascript 的新版本过渡，严格模式新增了一些保留字：implements, interface, let, package, private, protected, public, static, yield。</p>
<p>使用这些词作为变量名将会报错。</p>
<pre><code class="hljs">function package(protected) { // 语法错误

    "use strict";

    var implements; // 语法错误

}
</code></pre>
<p>此外，ECMAscript 第五版本身还规定了另一些保留字（class, enum, export, extends, import, super），以及各大浏览器自行增加的 const 保留字，也是不能作为变量名的。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139693696</p>]]></description><guid isPermaLink="false">/archives/54afeafe-600a-4f15-b6d8-a13136b36da8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:35:21 GMT</pubDate></item><item><title><![CDATA[new 一个对象的过程中发生了什么]]></title><link>https://wiki.dgfygk.com/archives/b591b5d8-0cdd-4005-bd5e-92df15571068</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=new%20%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88&amp;url=/archives/b591b5d8-0cdd-4005-bd5e-92df15571068" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="hljs">function Person(name) {
    this.name = name;
}
var person = new Person("qilei");
</code></pre>
<p>new一个对象的四个过程：</p>
<pre><code class="hljs">// 1.创建空对象；
var obj = {};
// 2.设置原型链: 设置新对象的 constructor 属性为构造函数的名称，设置新对象的__proto__属性指向构造函数的 prototype 对象；
obj.constructor = Person;
obj.__proto__ = Person.prototype;
// 3.改变this指向：使用新对象调用函数，函数中的 this 指向新实例对象obj：
var result = Person.call(obj); //{}.构造函数();
// 4.返回值：如果无返回值或者返回一个非对象值，则将新对象返回；如果返回值是一个新对象的话那么直接返回该对象。
if (typeof(result) == "object") {
    person = result;
} else {
    person = obj;
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681423</p>]]></description><guid isPermaLink="false">/archives/b591b5d8-0cdd-4005-bd5e-92df15571068</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:32:58 GMT</pubDate></item><item><title><![CDATA[JavaScript 继承的方式和优缺点]]></title><link>https://wiki.dgfygk.com/archives/4e72ba99-54bd-4718-a354-e72beae827c5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%20%E7%BB%A7%E6%89%BF%E7%9A%84%E6%96%B9%E5%BC%8F%E5%92%8C%E4%BC%98%E7%BC%BA%E7%82%B9&amp;url=/archives/4e72ba99-54bd-4718-a354-e72beae827c5" width="1" height="1" alt="" style="opacity:0;">
<ul>
 <li>
  <p>一、原型链继承</p>
 </li>
 <li>
  <p>缺点：</p>
 </li>
 <li>
  <p>1.引用类型的属性被所有实例共享</p>
 </li>
 <li>
  <p>2.在创建 Child 的实例时，不能向 Parent 传参</p>
 </li>
 <li>
  <p>二、借用构造函数(经典继承)</p>
 </li>
 <li>
  <p>优点：</p>
 </li>
 <li>
  <p>1.避免了引用类型的属性被所有实例共享</p>
 </li>
 <li>
  <p>2.可以在 Child 中向 Parent 传参</p>
 </li>
 <li>
  <p>缺点：</p>
 </li>
 <li>
  <p>1.方法都在构造函数中定义，每次创建实例都会创建一遍方法。</p>
 </li>
 <li>
  <p>三、组合继承</p>
 </li>
 <li>
  <p>优点：</p>
 </li>
 <li>
  <p>1.融合原型链继承和构造函数的优点，是 JavaScript 中最常用的继承模式。</p>
 </li>
 <li>
  <p>四、原型式继承</p>
 </li>
 <li>
  <p>缺点：</p>
 </li>
 <li>
  <p>1.包含引用类型的属性值始终都会共享相应的值，这点跟原型链继承一样。</p>
 </li>
 <li>
  <p>五、寄生式继承</p>
 </li>
 <li>
  <p>缺点：</p>
 </li>
 <li>
  <p>1.跟借用构造函数模式一样，每次创建对象都会创建一遍方法。</p>
 </li>
 <li>
  <p>六、寄生组合式继承</p>
 </li>
 <li>
  <p>优点：</p>
 </li>
 <li>
  <p>1.这种方式的高效率体现它只调用了一次 Parent 构造函数，并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。</p>
 </li>
 <li>
  <p>2.与此同时，原型链还能保持不变；</p>
 </li>
 <li>
  <p>3.因此，还能够正常使用 instanceof 和 isPrototypeOf。</p>
 </li>
 <li>
  <p>开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式</p>
 </li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681142</p>]]></description><guid isPermaLink="false">/archives/4e72ba99-54bd-4718-a354-e72beae827c5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:32:40 GMT</pubDate></item><item><title><![CDATA[复杂数据类型如何转变为字符串]]></title><link>https://wiki.dgfygk.com/archives/e16ec5e3-f6fe-4aff-a708-4a84e1f39e0f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A4%8D%E6%9D%82%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%A6%82%E4%BD%95%E8%BD%AC%E5%8F%98%E4%B8%BA%E5%AD%97%E7%AC%A6%E4%B8%B2&amp;url=/archives/e16ec5e3-f6fe-4aff-a708-4a84e1f39e0f" width="1" height="1" alt="" style="opacity:0;">
<ul>
 <li>首先，会调用 valueOf 方法，如果方法的返回值是一个基本数据类型，就返回这个值，</li>
 <li>如果调用 valueOf 方法之后的返回值仍旧是一个复杂数据类型，就会调用该对象的 toString 方法，</li>
 <li>如果 toString 方法调用之后的返回值是一个基本数据类型，就返回这个值，</li>
 <li>如果 toString 方法调用之后的返回值是一个复杂数据类型，就报一个错误。</li>
</ul>
<p>解析：</p>
<pre><code class="hljs">// 1;
var obj = {
    valueOf: function() {
        return 1;
    }
};
console.log(obj + ""); //'1'
// 2;
var obj = {
    valueOf: function() {
        return [1, 2];
    }
};
console.log(obj + ""); //'[object Object]';
// 3;
var obj = {
    valueOf: function() {
        return [1, 2];
    },
    toString: function() {
        return 1;
    }
};
console.log(obj + ""); //'1';
// 4;
var obj = {
    valueOf: function() {
        return [1, 2];
    },
    toString: function() {
        return [1, 2, 3];
    }
};
console.log(obj + ""); // 报错 Uncaught TypeError: Cannot convert object to primitive value
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681233</p>]]></description><guid isPermaLink="false">/archives/e16ec5e3-f6fe-4aff-a708-4a84e1f39e0f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:32:14 GMT</pubDate></item><item><title><![CDATA[你对闭包的理解？优缺点？]]></title><link>https://wiki.dgfygk.com/archives/6b7cf79f-e839-469b-a28d-e768d898ed70</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%A0%E5%AF%B9%E9%97%AD%E5%8C%85%E7%9A%84%E7%90%86%E8%A7%A3%EF%BC%9F%E4%BC%98%E7%BC%BA%E7%82%B9%EF%BC%9F&amp;url=/archives/6b7cf79f-e839-469b-a28d-e768d898ed70" width="1" height="1" alt="" style="opacity:0;">
<p>闭包就是能够读取其他函数内部变量的函数</p>
<p>三大特性：</p>
<ul>
 <li>函数嵌套函数。</li>
 <li>函数内部可以引用外部的参数和变量。</li>
 <li>参数和变量不会被垃圾回收机制回收。</li>
</ul>
<p>优点：</p>
<ul>
 <li>希望一个变量长期存储在内存中。</li>
 <li>避免全局变量的污染。</li>
 <li>私有成员的存在。</li>
</ul>
<p>缺点：</p>
<ul>
 <li>常驻内存，增加内存使用量。</li>
 <li>使用不当会很容易造成内存泄露。</li>
</ul>
<p>示例：</p>
<pre><code class="hljs">function outer() {
    var name = "jack";

    function inner() {
        console.log(name);
    }
    return inner;
}
outer()(); // jack
function sayHi(name) {
    return () =&gt; {
        console.log(`Hi! ${name}`);
    };
}
const test = sayHi("xiaoming");
test(); // Hi! xiaoming
</code></pre>
<p>虽然 sayHi 函数已经执行完毕，但是其活动对象也不会被销毁，因为 test 函数仍然引用着 sayHi 函数中的变量 name，这就是闭包。
 <br>
 但也因为闭包引用着另一个函数的变量，导致另一个函数已经不使用了也无法销毁，所以闭包使用过多，会占用较多的内存，这也是一个副作用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681360</p>]]></description><guid isPermaLink="false">/archives/6b7cf79f-e839-469b-a28d-e768d898ed70</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:31:42 GMT</pubDate></item><item><title><![CDATA[列举 3 种强制类型转换和 2 种隐式类型转换]]></title><link>https://wiki.dgfygk.com/archives/015cc33c-aec6-433c-8d87-3cca1b2b2b92</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%88%97%E4%B8%BE%203%20%E7%A7%8D%E5%BC%BA%E5%88%B6%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E5%92%8C%202%20%E7%A7%8D%E9%9A%90%E5%BC%8F%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2&amp;url=/archives/015cc33c-aec6-433c-8d87-3cca1b2b2b92" width="1" height="1" alt="" style="opacity:0;">
<p>强制: parseInt(), parseFloat(), Number(), Boolean(), String()</p>
<p>隐式: +, -</p>
<p>
 <br>
</p>
<p>代码演示：</p>
<pre><code class="hljs">// 1.parseInt() 把值转换成整数
parseInt("1234blue"); // 1234
parseInt("0xA"); // 10
parseInt("22.5"); // 22
parseInt("blue"); // NaN
// parseInt()方法还有基模式，可以把二进制、八进制、十六进制或其他任何进制的字符串转换成整数。基是由parseInt()方法的第二个参数指定的，示例如下：
parseInt("AF", 16); // 175
parseInt("10", 2); // 2
parseInt("10", 8); // 8
parseInt("10", 10); // 10
// 如果十进制数包含前导0，那么最好采用基数10，这样才不会意外地得到八进制的值。例如：
parseInt("010"); // 8
parseInt("010", 8); // 8
parseInt("010", 10); // 10

// 2.parseFloat() 把值转换成浮点数,没有基模式
parseFloat("1234blue"); // 1234.0
parseFloat("0xA"); // NaN
parseFloat("22.5"); // 22.5
parseFloat("22.34.5"); // 22.34
parseFloat("0908"); // 908
parseFloat("blue"); // NaN

// 3.Number() 把给定的值转换成数字（可以是整数或浮点数）,Number()的强制类型转换与parseInt()和parseFloat()方法的处理方式相似，只是它转换的是整个值，而不是部分值。示例如下：
Number(false) // 0
Number(true) // 1
Number(undefined) // NaN
Number(null) // 0
Number("5.5") // 5.5
Number("56") // 56
Number("5.6.7") // NaN
Number(new Object()) // NaN
Number(100) // 100

// 4.Boolean() 把给定的值转换成Boolean型
Boolean(""); // false 
Boolean("hi"); // true
Boolean(100); // true
Boolean(null); // false
Boolean(0); // false
Boolean(new Object()); // true

// 5.String() 把给定的值转换成字符串
String(123) // "123"

// 6.+ -
console.log(0 + '1') // "01"
console.log(2 - '1') // 1
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681293</p>]]></description><guid isPermaLink="false">/archives/015cc33c-aec6-433c-8d87-3cca1b2b2b92</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:30:39 GMT</pubDate></item><item><title><![CDATA[事件委托是什么]]></title><link>https://wiki.dgfygk.com/archives/26c65417-67c7-4e81-84dc-edcde52ad99a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98%E6%98%AF%E4%BB%80%E4%B9%88&amp;url=/archives/26c65417-67c7-4e81-84dc-edcde52ad99a" width="1" height="1" alt="" style="opacity:0;">
<h4>利用事件冒泡的原理，让自己的所触发的事件，让他的父元素代替执行！</h4>
<p>
 <br>
</p>
<p>1、那什么样的事件可以用事件委托，什么样的事件不可以用呢？</p>
<ul>
 <li>适合用事件委托的事件：click，mousedown，mouseup，keydown，keyup，keypress。</li>
 <li>值得注意的是，mouseover 和 mouseout 虽然也有事件冒泡，但是处理它们的时候需要特别的注意，因为需要经常计算它们的位置，处理起来不太容易。</li>
 <li>不适合的就有很多了，举个例子，mousemove，每次都要计算它的位置，非常不好把控，在不如说 focus，blur 之类的，本身就没用冒泡的特性，自然就不用事件委托了。</li>
</ul>
<p>2、为什么要用事件委托</p>
<ul>
 <li>
  <p>1.提高性能</p>
  <ul>
   <li>苹果</li>
   <li>香蕉</li>
   <li>凤梨</li>
  </ul>
  <p>// good
   <br>
   document.querySelector('ul').onclick = (event) =&gt; {
   <br>
   let target = event.target
   <br>
   if (target.nodeName === 'LI') {</p>
  <pre><code>console.log(target.innerHTML)
</code></pre>
  <p>}
   <br>
   }</p>
  <p>// bad
   <br>
   document.querySelectorAll('li').forEach((e) =&gt; {
   <br>
   e.onclick = function() {</p>
  <pre><code>console.log(this.innerHTML)
</code></pre>
  <p>}
   <br>
   })</p>
 </li>
 <li>
  <p>2.新添加的元素还会有之前的事件。</p>
 </li>
</ul>
<p>3、事件冒泡与事件委托的对比</p>
<ul>
 <li>事件冒泡：box 内部无论是什么元素，点击后都会触发 box 的点击事件</li>
 <li>事件委托：可以对 box 内部的元素进行筛选</li>
</ul>
<p>4、事件委托怎么取索引？</p>
<pre><code class="hljs">    &lt;ul id="ul"&gt;
        &lt;li&gt; aaaaaaaa &lt;/li&gt;
        &lt;li&gt; 事件委托了 点击当前， 如何获取 这个点击的下标 &lt;/li&gt;
        &lt;li&gt; cccccccc &lt;/li&gt;
    &lt;/ul&gt;
    window.onload = function() {
        var oUl = document.getElementById("ul");
        var aLi = oUl.getElementsByTagName("li");
        oUl.onclick = function(ev) {
            var ev = ev || window.event;
            var target = ev.target || ev.srcElement;
            if (target.nodeName.toLowerCase() == "li") {
                var that = target;
                var index;
                for (var i = 0; i &lt; aLi.length; i++)
                    if (aLi[i] === target) index = i;
                if (index &gt;= 0) alert('我的下标是第' + index + '个');
                target.style.background = "red";
            }
        }
    }
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681111</p>]]></description><guid isPermaLink="false">/archives/26c65417-67c7-4e81-84dc-edcde52ad99a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:28:54 GMT</pubDate></item><item><title><![CDATA[document load 和 document ready 的区别]]></title><link>https://wiki.dgfygk.com/archives/c1e69429-632c-42b4-a097-a648f880937b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=document%20load%20%E5%92%8C%20document%20ready%20%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/c1e69429-632c-42b4-a097-a648f880937b" width="1" height="1" alt="" style="opacity:0;">
<p>文档解析过程中，ready在加载图片等外部资源前触发，load在之后触发。如果页面中要是没有图片之类的媒体文件的话ready与load是差不多的，但是页面中有文件就不一样了，所以还是推荐大家在工作中用ready</p>
<p>
 <br>
</p>
<p>DOM文档解析：</p>
<ol>
 <li>解析html结构</li>
 <li>加载脚本和样式文件</li>
 <li>解析并执行脚本</li>
 <li>构造html的DOM模型 // ready</li>
 <li>加载图片等外部资源文件</li>
 <li>页面加载完毕 // load</li>
</ol>
<p>页面加载完成有两种事件</p>
<p><strong>1.</strong>load是当页面所有资源全部加载完成后（包括DOM文档树，css文件，js文件，图片资源等），执行一个函数</p>
<p>缺点：如果图片资源较多，加载时间较长，onload后等待执行的函数需要等待较长时间，所以一些效果可能受到影响</p>
<p>代码形式：</p>
<pre><code class="hljs">//document load
$(document).load(function(){
    ...code...
})
</code></pre>
<p><strong>2.</strong>$(document).ready()是当DOM文档树加载完成后执行一个函数 （不包含图片，css等）所以会比load较快执行</p>
<p>在原生的jS中不包括ready()这个方法，Jquery才有，jquery中有 $().ready(function)。</p>
<p>代码形式为：</p>
<pre><code class="hljs">//document ready
$(document).ready(function(){
    ...code...
})
//document ready 简写
$(function(){
    ...code...
})
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139680954</p>]]></description><guid isPermaLink="false">/archives/c1e69429-632c-42b4-a097-a648f880937b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:28:34 GMT</pubDate></item><item><title><![CDATA[JavaScript 中如何检测一个变量是一个 String 类型？]]></title><link>https://wiki.dgfygk.com/archives/7a6b8cd2-202f-4bc2-8f08-766ee7e6b304</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%20%E4%B8%AD%E5%A6%82%E4%BD%95%E6%A3%80%E6%B5%8B%E4%B8%80%E4%B8%AA%E5%8F%98%E9%87%8F%E6%98%AF%E4%B8%80%E4%B8%AA%20String%20%E7%B1%BB%E5%9E%8B%EF%BC%9F&amp;url=/archives/7a6b8cd2-202f-4bc2-8f08-766ee7e6b304" width="1" height="1" alt="" style="opacity:0;">
<p>三种方法（typeof、constructor、Object.prototype.toString.call()）</p>
<pre><code class="hljs">①
typeof

typeof('123') === "string" // true

typeof '123' === "string" // true

②
constructor

    '123'.constructor === String // true

③ Object.prototype.toString.call()

Object.prototype.toString.call('123') === '[object String]' // true
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681007</p>]]></description><guid isPermaLink="false">/archives/7a6b8cd2-202f-4bc2-8f08-766ee7e6b304</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:27:41 GMT</pubDate></item><item><title><![CDATA[JS的监听事件]]></title><link>https://wiki.dgfygk.com/archives/5536753d-1bcd-41f7-94b3-e3079ac065c8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E7%9A%84%E7%9B%91%E5%90%AC%E4%BA%8B%E4%BB%B6&amp;url=/archives/5536753d-1bcd-41f7-94b3-e3079ac065c8" width="1" height="1" alt="" style="opacity:0;">
<p>在JavaScript中，你可以使用监听器来捕获和处理不同类型的事件。通过添加事件监听器，你可以指定当特定事件发生时要执行的函数。</p>
<p>以下是几种常见的监听事件的方法：</p>
<ol>
 <li>addEventListener()：用于在目标元素上添加事件监听器。它接受三个参数：事件类型、要执行的回调函数、和一个可选的布尔值参数，用于指定事件是否在捕获阶段触发（默认为false，即在冒泡阶段触发）。例如：</li>
</ol>
<pre><code class="hljs">document.addEventListener("click", function(event) { console.log("Click event occurred."); });
</code></pre>
<p>2.<code>onclick</code>：直接将函数赋值给一个元素的<code>onclick</code>属性，以响应点击事件。例如：</p>
<pre><code class="hljs">button.onclick = function() { console.log("Button clicked."); };
</code></pre>
<p>3.其他事件监听方法：除了以上两种方法，还有许多特定的事件监听方法，如<code>onmouseover</code>、<code>onkeydown</code>等。这些方法是DOM对象的属性，可以通过直接赋值函数来进行事件监听。例如：</p>
<pre><code class="hljs">element.onmouseover = function() { console.log("Mouse over event occurred."); };
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139664642</p>]]></description><guid isPermaLink="false">/archives/5536753d-1bcd-41f7-94b3-e3079ac065c8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:27:25 GMT</pubDate></item><item><title><![CDATA[JS字符串拼接的几种方式]]></title><link>https://wiki.dgfygk.com/archives/90a79e43-fcaf-4108-a0ae-fef08ac21680</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8B%BC%E6%8E%A5%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F&amp;url=/archives/90a79e43-fcaf-4108-a0ae-fef08ac21680" width="1" height="1" alt="" style="opacity:0;">
<p>在 JavaScript 中，使用<a href="https://so.csdn.net/so/search?q=%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BF%9E%E6%8E%A5&amp;spm=1001.2101.3001.7020" title="字符串连接">字符串连接</a>有 几 种方式：连接符（+）、反引号（`）、join()、concat()。
 <br>
 <strong>一、使用连接符 “+” 把要连接的字符串连起来</strong></p>
<pre><code class="hljs">let a = 'java'
let b = a + 'script'

//运行结果：javascript
</code></pre>
<p>二、可以使用模板字符串，以反引号（ ` `）标识</p>
<p>这是ES6新增的字符串方法，可以配合反单引号完成拼接字符串的功能</p>
<p>反单引号怎么打出来：将输入法调整为英文输入法，tab按键的上一个就是。</p>
<p>用法：</p>
<p>1： 定义需要拼接进去的字符串变量</p>
<p>2： 将字符串变量用 ${ } 包起来，再写到需要拼接的地方</p>
<p>注意：反单引号：` ；单引号：'</p>
<pre><code class="hljs">let a = 'java'
let b = `hello ${a}script`

//运行结果：hello javascript
</code></pre>
<p><strong>三、使用 JavaScript的 concat() 方法连接字符串</strong></p>
<p>定义和用法：</p>
<p>concat() 方法用于连接两个或多个字符串。</p>
<p>该方法没有改变原有字符串，但是会返回连接两个或多个字符串新字符串。</p>
<p><strong>语法如下：</strong></p>
<pre><code class="hljs">string.concat(string1, string2, ..., stringX)
</code></pre>
<pre><code>```hljs
let a = 'dys'
let b = 'lyw'

let str = a.concat(b)

//运行结果：dyslyw
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139664609</p>]]></description><guid isPermaLink="false">/archives/90a79e43-fcaf-4108-a0ae-fef08ac21680</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:26:21 GMT</pubDate></item><item><title><![CDATA[请用 js 去除字符串空格？]]></title><link>https://wiki.dgfygk.com/archives/305f286c-362c-414c-b26b-95d9ec2b291f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%B7%E7%94%A8%20js%20%E5%8E%BB%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%A9%BA%E6%A0%BC%EF%BC%9F&amp;url=/archives/305f286c-362c-414c-b26b-95d9ec2b291f" width="1" height="1" alt="" style="opacity:0;">
<p>replace 正则匹配方法、str.trim()方法、JQ 方法：$.trim(str)方法</p>
<p>方法一：replace 正则匹配方法</p>
<p>去除字符串内所有的空格：str = str.replace(/\s*/g, ““);</p>
<p>去除字符串内两头的空格：str = str.replace(/^\s*\|\s*$/g, ““);</p>
<p>去除字符串内左侧的空格：str = str.replace(/^\s*/, ““);</p>
<p>去除字符串内右侧的空格：str = str.replace(/(\s*$)/g, ““);</p>
<pre><code class="hljs">var str = " 6 6 ";
var str_1 = str.replace(/\s*/g, "");
console.log(str_1); //66

var str = " 6 6 ";
var str_1 = str.replace(/^\s*|\s*$/g, "");
console.log(str_1); //6 6//输出左右侧均无空格

var str = " 6 6 ";
var str_1 = str.replace(/^\s*/, "");
console.log(str_1); //6 6 //输出右侧有空格左侧无空格

var str = " 6 6 ";
var str_1 = str.replace(/(\s*$)/g, "");
console.log(str_1); // 6 6//输出左侧有空格右侧无空格
</code></pre>
<p>方法二：str.trim()方法</p>
<p>trim()方法是用来删除字符串两端的空白字符并返回，trim 方法并不影响原来的字符串本身，它返回的是一个新的字符串。</p>
<p>缺陷：只能去除字符串两端的空格，不能去除中间的空格</p>
<pre><code class="hljs">var str = " 6 6 ";
var str_1 = str.trim();
console.log(str_1); //6 6//输出左右侧均无空格
</code></pre>
<p>方法三：JQ 方法：$.trim(str)方法</p>
<p>$.trim() 函数用于去除字符串两端的空白字符。</p>
<p>注意：$.trim()函数会移除字符串开始和末尾处的所有换行符，空格(包括连续的空格)和制表符。如果这些空白字符在字符串中间时，它们将被保留，不会被移除。</p>
<pre><code class="hljs">var str = " 6 6 ";
var str_1 = $.trim(str);
console.log(str_1); //6 6//输出左右侧均无空格
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139681048</p>]]></description><guid isPermaLink="false">/archives/305f286c-362c-414c-b26b-95d9ec2b291f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:26:07 GMT</pubDate></item><item><title><![CDATA[javascript判断对象中是否存在某个字段]]></title><link>https://wiki.dgfygk.com/archives/029524e4-dd8f-43ed-985e-1c5f2a2d1f09</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=javascript%E5%88%A4%E6%96%AD%E5%AF%B9%E8%B1%A1%E4%B8%AD%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E6%9F%90%E4%B8%AA%E5%AD%97%E6%AE%B5&amp;url=/archives/029524e4-dd8f-43ed-985e-1c5f2a2d1f09" width="1" height="1" alt="" style="opacity:0;">
<h5>1. in</h5>
<p>如果指定的属性在指定的对象或其<a href="https://so.csdn.net/so/search?q=%E5%8E%9F%E5%9E%8B%E9%93%BE&amp;spm=1001.2101.3001.7020" title="原型链">原型链</a>中，则 <code>in</code> <a href="https://so.csdn.net/so/search?q=%E8%BF%90%E7%AE%97%E7%AC%A6&amp;spm=1001.2101.3001.7020" title="运算符">运算符</a>返回 <code>true</code>。</p>
<pre><code class="hljs">const car = { make: 'Honda', model: 'Accord', year: 1998 };

console.log('make' in car);
//  true

delete car.make;
if ('make' in car === false) {
  car.make = 'Suzuki';
}

console.log(car.make);
//  "Suzuki"
</code></pre>
<h5>2. Object.prototype.hasOwnProperty()</h5>
<p><code>hasOwnProperty()</code> 方法会返回一个<a href="https://so.csdn.net/so/search?q=%E5%B8%83%E5%B0%94%E5%80%BC&amp;spm=1001.2101.3001.7020" title="布尔值">布尔值</a>，指示对象自身属性中是否具有指定的属性（也就是，是否有指定的键）。</p>
<pre><code class="hljs">const object1 = {};
object1.property1 = 42;

console.log(object1.hasOwnProperty('property1'));
//  true

console.log(object1.hasOwnProperty('toString'));
//  false

console.log(object1.hasOwnProperty('hasOwnProperty'));
//  false
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139664573</p>]]></description><guid isPermaLink="false">/archives/029524e4-dd8f-43ed-985e-1c5f2a2d1f09</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:25:51 GMT</pubDate></item><item><title><![CDATA[JS实现照片预览]]></title><link>https://wiki.dgfygk.com/archives/575a94cc-f841-4174-9745-322c6256bd63</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E7%85%A7%E7%89%87%E9%A2%84%E8%A7%88&amp;url=/archives/575a94cc-f841-4174-9745-322c6256bd63" width="1" height="1" alt="" style="opacity:0;">
<p>以下是一个简单的JS代码示例，用于实现照片预览功能：</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Photo Preview&lt;/title&gt;
    &lt;script&gt;
        function previewPhoto(event) {
            var reader = new FileReader();
            reader.onload = function(){
                var preview = document.getElementById('preview');
                preview.src = reader.result;
            }
            reader.readAsDataURL(event.target.files[0]);
        }
    &lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;input type="file" onchange="previewPhoto(event)"&gt;
    &lt;br&gt;
    &lt;img id="preview" src="" alt="Preview"&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>使用以上代码，当用户选择图片文件后，该图片将在页面上实现预览。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139213346</p>]]></description><guid isPermaLink="false">/archives/575a94cc-f841-4174-9745-322c6256bd63</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:25:31 GMT</pubDate></item><item><title><![CDATA[装箱和拆箱（JS）]]></title><link>https://wiki.dgfygk.com/archives/15172450-b384-4ba7-ae5a-a8341fd11221</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%A3%85%E7%AE%B1%E5%92%8C%E6%8B%86%E7%AE%B1%EF%BC%88JS%EF%BC%89&amp;url=/archives/15172450-b384-4ba7-ae5a-a8341fd11221" width="1" height="1" alt="" style="opacity:0;">
<p>在JavaScript中，“装箱”（boxing）和"拆箱”（unboxing）不是特定的术语，但它们的概念在其他一些语言如Java或C++中更为常见。在JavaScript中，你可以找到类似的概念，但通常不使用这些术语。</p>
<p>装箱（Boxing）：这是指将基本类型的数据（如int, float等）转化为对应的对象。例如在Java中，当你试图将一个基本类型的值赋给一个装箱的类型时，会发生装箱。</p>
<p>拆箱（Unboxing）：这是与装箱相反的过程，即从包装对象中提取出基本类型的值。</p>
<p>在JavaScript中，所有的数据都是对象，所以没有装箱和拆箱的概念。但是，JavaScript有一些内置的包装对象，如Number, String, Boolean等，它们可以用来创建对应的原始值对象。例如，当你创建一个Number对象时，你可以使用Number构造函数：</p>
<pre><code class="hljs">var num = new Number(10); // num 是一个 Number 对象
</code></pre>
<p>虽然这不是标准的"装箱”，但可以看作是类似的行为。然而，你不需要进行任何"拆箱"操作，因为你可以直接使用该对象的值。</p>
<p>另外，当涉及到null和undefined时，你可能会遇到一些情况，这些情况在某种程度上与装箱和拆箱相似。例如，你可以使用Number或String构造函数将null或undefined转换为对象：</p>
<pre><code class="hljs">var num = new Number(null); // num 是一个 Number 对象，值为0  
var str = new String(undefined); // str 是一个 String 对象，值为"undefined"
</code></pre>
<p>在这些情况下，<code>null</code>和<code>undefined</code>被视为被"装箱"到相应的对象中。然后，你可以直接使用这些对象的值，这可以看作是某种形式的"拆箱”。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139664590</p>]]></description><guid isPermaLink="false">/archives/15172450-b384-4ba7-ae5a-a8341fd11221</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:25:11 GMT</pubDate></item><item><title><![CDATA[JS导出excel功能]]></title><link>https://wiki.dgfygk.com/archives/94702ccc-def0-448d-83d3-15e0956c7ca8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AF%BC%E5%87%BAexcel%E5%8A%9F%E8%83%BD&amp;url=/archives/94702ccc-def0-448d-83d3-15e0956c7ca8" width="1" height="1" alt="" style="opacity:0;">
<p>在JavaScript中可以使用第三方库来实现导出Excel的功能，比如使用<code>xlsx</code>库。以下是一个简单的示例：</p>
<ol>
 <li>
  <p>首先，在html文件中引入<code>xlsx</code>库：</p>
  <pre><code class="hljs">&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.9/xlsx.full.min.js"&gt;&lt;/script&gt;
</code></pre>
  <p>
   <br>
  </p>
 </li>
 <li>
  <p>创建一个导出Excel的函数：</p>
  <pre><code class="hljs">function exportExcel(data, fileName) {
 const ws = XLSX.utils.json_to_sheet(data);
 const wb = XLSX.utils.book_new();
 XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
 const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });

 const blob = new Blob([wbout], { type: 'application/octet-stream' });
 const url = URL.createObjectURL(blob);

 const a = document.createElement('a');
 a.href = url;
 a.download = fileName + '.xlsx';
 a.click();

 URL.revokeObjectURL(url);
}
</code></pre>
  <p>
   <br>
  </p>
 </li>
 <li>
  <p>调用导出Excel的函数，并传入需要导出的数据和文件名：</p>
  <pre><code class="hljs">const data = [
 { name: 'Alice', age: 30 },
 { name: 'Bob', age: 25 },
 { name: 'Charlie', age: 35 }
];

exportExcel(data, 'myData');
</code></pre>
  <p>以上代码实现了一个简单的导出Excel的功能。需要注意的是，浏览器可能会阻止自动下载文件，用户需要手动允许或者调整浏览器设置。</p>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139306458</p>]]></description><guid isPermaLink="false">/archives/94702ccc-def0-448d-83d3-15e0956c7ca8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:19:42 GMT</pubDate></item><item><title><![CDATA[JS实现select框实现模糊搜索]]></title><link>https://wiki.dgfygk.com/archives/6cee4b2c-e833-4987-aa66-8a19957861ed</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0select%E6%A1%86%E5%AE%9E%E7%8E%B0%E6%A8%A1%E7%B3%8A%E6%90%9C%E7%B4%A2&amp;url=/archives/6cee4b2c-e833-4987-aa66-8a19957861ed" width="1" height="1" alt="" style="opacity:0;">
<p>您可以使用JavaScript来实现模糊搜索的功能，以下是一个简单的示例代码：</p>
<p>HTML部分：</p>
<pre><code class="hljs">&lt;select id="select"&gt;
  &lt;option value="1"&gt;Apple&lt;/option&gt;
  &lt;option value="2"&gt;Banana&lt;/option&gt;
  &lt;option value="3"&gt;Cherry&lt;/option&gt;
  &lt;option value="4"&gt;Orange&lt;/option&gt;
&lt;/select&gt;
</code></pre>
<p>JavaScript部分：</p>
<pre><code class="hljs">document.getElementById('select').addEventListener('input', function() {
  var input, filter, option, i;
  input = this.value;
  filter = input.toUpperCase();
  options = this.getElementsByTagName('option');

  for (i = 0; i &lt; options.length; i++) {
    if (options[i].innerHTML.toUpperCase().indexOf(filter) &gt; -1) {
      options[i].style.display = '';
    } else {
      options[i].style.display = 'none';
    }
  }
});
</code></pre>
<p>以上代码会监听select框的input事件，当用户输入内容时，会根据用户输入的内容来过滤选项。只有当某个选项的内容包含了用户输入的内容时，该选项才会显示出来，否则会被隐藏。这样就实现了模糊搜索的功能。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139393277</p>]]></description><guid isPermaLink="false">/archives/6cee4b2c-e833-4987-aa66-8a19957861ed</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:19:15 GMT</pubDate></item><item><title><![CDATA[JS实现局部全屏]]></title><link>https://wiki.dgfygk.com/archives/ece6773b-ea80-40a8-9c16-fef0f3b23506</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E5%B1%80%E9%83%A8%E5%85%A8%E5%B1%8F&amp;url=/archives/ece6773b-ea80-40a8-9c16-fef0f3b23506" width="1" height="1" alt="" style="opacity:0;">
<pre><code>document.getElementsByClassName('RFull')[0].addEventListener('click', function() {
    var elem = document.getElementsByClassName('RBox')[0];
    if (!document.fullscreenElement) {
        elem.requestFullscreen().catch(err =&gt; {

        });
    } else {
        if (document.exitFullscreen) {
            document.exitFullscreen();
        }
    }
});
</code></pre>
<p>以上代码，可以实现局部全屏。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139359684</p>]]></description><guid isPermaLink="false">/archives/ece6773b-ea80-40a8-9c16-fef0f3b23506</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:18:56 GMT</pubDate></item><item><title><![CDATA[JS实现时间段过滤搜索]]></title><link>https://wiki.dgfygk.com/archives/39011c94-7b83-4e39-9b57-3c5e9cd68356</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E6%97%B6%E9%97%B4%E6%AE%B5%E8%BF%87%E6%BB%A4%E6%90%9C%E7%B4%A2&amp;url=/archives/39011c94-7b83-4e39-9b57-3c5e9cd68356" width="1" height="1" alt="" style="opacity:0;">
<p>你想要实现一个JavaScript函数，用于根据时间段过滤搜索结果吗？可以尝试以下的示例代码：</p>
<pre><code class="hljs">// 假设这是你的数据
const data = [
  { id: 1, name: 'Alice', timestamp: 1622382000000 }, // 2021-05-30 12:00:00 UTC
  { id: 2, name: 'Bob', timestamp: 1630335600000 }, // 2021-08-30 12:00:00 UTC
  { id: 3, name: 'Charlie', timestamp: 1640991600000 } // 2022-12-31 12:00:00 UTC
];

// 过滤函数，根据时间段搜索
function filterByTimeRange(data, startTime, endTime) {
  return data.filter(item =&gt; {
    return item.timestamp &gt;= startTime &amp;&amp; item.timestamp &lt;= endTime;
  });
}

// 定义搜索时间段的开始时间和结束时间
const startTime = 1630000000000; // 2021-08-27 12:00:00 UTC
const endTime = 1640000000000; // 2022-08-27 12:00:00 UTC

// 调用过滤函数
const filteredData = filterByTimeRange(data, startTime, endTime);

// 输出过滤后的结果
console.log(filteredData);
</code></pre>
<p>在这个示例中，<code>filterByTimeRange</code> 函数接受一个数据数组、开始时间和结束时间作为参数，并返回在这个时间段内的数据项。你可以根据具体的时间戳来调整开始时间和结束时间，以适应你的需求。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139335082</p>]]></description><guid isPermaLink="false">/archives/39011c94-7b83-4e39-9b57-3c5e9cd68356</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:18:18 GMT</pubDate></item><item><title><![CDATA[浅谈一下实例化]]></title><link>https://wiki.dgfygk.com/archives/77a1288b-6618-4928-983a-fd1e673c448f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%85%E8%B0%88%E4%B8%80%E4%B8%8B%E5%AE%9E%E4%BE%8B%E5%8C%96&amp;url=/archives/77a1288b-6618-4928-983a-fd1e673c448f" width="1" height="1" alt="" style="opacity:0;">
<p>实例化对象是面向对象编程中非常重要的概念，它允许我们根据类的定义创建具体的对象，并操作这些对象的属性和方法。下面具体谈一下实例化对象的一些特点和用途：</p>
<ol>
 <li>
  <p><strong>封装性和复用性</strong> ：实例化对象可以将数据和行为封装在一起，从而提高代码的可维护性和复用性。例如，在上面的例子中，<code>Person</code>类定义了一个通用的人的属性和行为，我们可以通过实例化不同的对象来代表不同的人。</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>
</ol>
<p>例如，在JavaScript中，可以定义一个类如下：</p>
<pre><code class="hljs">class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}
</code></pre>
<p>然后通过实例化该类来创建一个具体的对象：</p>
<pre><code class="hljs">const person1 = new Person('Alice', 25);
person1.sayHello(); // Output: Hello, my name is Alice and I am 25 years old.
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139510595</p>]]></description><guid isPermaLink="false">/archives/77a1288b-6618-4928-983a-fd1e673c448f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:17:57 GMT</pubDate></item><item><title><![CDATA[深入了解JavaScript中的indexOf()方法：实现数组元素的搜索和索引获取]]></title><link>https://wiki.dgfygk.com/archives/2ad87648-829f-4cb9-a026-9c5d223fe474</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E4%BA%86%E8%A7%A3JavaScript%E4%B8%AD%E7%9A%84indexOf%28%29%E6%96%B9%E6%B3%95%EF%BC%9A%E5%AE%9E%E7%8E%B0%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E6%90%9C%E7%B4%A2%E5%92%8C%E7%B4%A2%E5%BC%95%E8%8E%B7%E5%8F%96&amp;url=/archives/2ad87648-829f-4cb9-a026-9c5d223fe474" width="1" height="1" alt="" style="opacity:0;">
<p>在JavaScript中，indexOf()方法用于搜索数组中的元素，并返回匹配的第一个元素的索引。如果数组中没有匹配的元素，则返回-1。</p>
<p>indexOf()方法的语法如下所示：</p>
<p>array.indexOf(searchElement[, fromIndex])</p>
<p>其中searchElement是要搜索的元素，fromIndex是可选参数，表示从哪个索引位置开始搜索。如果不指定fromIndex，则默认从数组的第一个元素开始搜索。</p>
<p>接下来让我们看一个简单的例子来演示如何使用indexOf()方法：</p>
<pre><code class="hljs">var fruits = ["apple", "banana", "orange", "grape"];

var index1 = fruits.indexOf("banana");
console.log(index1); // 输出结果为1

var index2 = fruits.indexOf("cherry");
console.log(index2); // 输出结果为-1
</code></pre>
<p>在上面的例子中，我们首先定义了一个包含水果的数组fruits。然后使用indexOf()方法分别搜索了"banana"和"cherry"这两个元素。由于数组中包含"banana”，所以返回其索引1；而数组中没有包含"cherry”，所以返回-1。</p>
<p>此外，indexOf()方法还可以接受一个fromIndex参数，表示从指定的索引位置开始搜索。例如：</p>
<pre><code class="hljs">var numbers = [10, 20, 30, 40, 50, 60];

var index = numbers.indexOf(30, 2);
console.log(index); // 输出结果为2
</code></pre>
<p>在上面的例子中，我们定义了一个包含数字的数组numbers，然后使用indexOf()方法从索引2的位置开始搜索数字30。由于数字30在索引2处，所以返回其索引2。</p>
<p>总的来说，indexOf()方法是一个用于数组元素搜索和索引获取的常用方法，在处理数组时非常实用。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139482672</p>]]></description><guid isPermaLink="false">/archives/2ad87648-829f-4cb9-a026-9c5d223fe474</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:16:09 GMT</pubDate></item><item><title><![CDATA[for循环嵌套for循环与递归的区别]]></title><link>https://wiki.dgfygk.com/archives/49415ad3-fbf2-4a36-88cd-70a6ebfdfb6e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=for%E5%BE%AA%E7%8E%AF%E5%B5%8C%E5%A5%97for%E5%BE%AA%E7%8E%AF%E4%B8%8E%E9%80%92%E5%BD%92%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/49415ad3-fbf2-4a36-88cd-70a6ebfdfb6e" width="1" height="1" alt="" style="opacity:0;">
<p>or循环嵌套for循环和递归都是控制程序执行流程的方法，但它们之间有一些明显的区别。</p>
<ol>
 <li>
  <p>实现方式：</p>
 </li>
 <li>
  <p>for循环嵌套for循环是通过在循环体内再次使用循环结构来实现多层循环的执行。</p>
 </li>
 <li>
  <p>递归是通过函数自身调用来实现多层嵌套的执行。</p>
 </li>
 <li>
  <p>复杂度：</p>
 </li>
 <li>
  <p>for循环嵌套for循环的复杂度通常是固定的，取决于嵌套的层数和每层循环的次数。</p>
 </li>
 <li>
  <p>递归的复杂度取决于递归的深度和每次递归调用的开销，可能会导致栈溢出或性能问题。</p>
 </li>
 <li>
  <p>可读性：</p>
 </li>
 <li>
  <p>for循环嵌套for循环通常比较直观，易于理解和调试。</p>
 </li>
 <li>
  <p>递归可能比较难以理解，需要注意递归的终止条件和递归调用的顺序。</p>
 </li>
 <li>
  <p>空间占用：</p>
 </li>
 <li>
  <p>for循环嵌套for循环的空间占用通常比较小，只需要保存循环变量和局部变量。</p>
 </li>
 <li>
  <p>递归的空间占用可能较大，需要保存每次递归调用的参数和局部变量，可能会导致栈溢出。</p>
 </li>
</ol>
<p>总的来说，for循环嵌套for循环适用于固定层数的循环结构，而递归适用于需要动态确定嵌套深度的情况。在选择使用哪种方法时，需要根据具体的问题需求和程序性能做出合适的选择。</p>
<pre><code class="hljs"># Nested for loops to print a 2D array
rows = 3
cols = 3
matrix = [[0 for _ in range(cols)] for _ in range(rows)]

for i in range(rows):
    for j in range(cols):
        matrix[i][j] = i * cols + j

for row in matrix:
    print(row)






//递归
# Recursion to calculate the factorial of a number
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

num = 5
result = factorial(num)
print(f"The factorial of {num} is {result}")
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139029708</p>]]></description><guid isPermaLink="false">/archives/49415ad3-fbf2-4a36-88cd-70a6ebfdfb6e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:15:47 GMT</pubDate></item><item><title><![CDATA[HTML标签data-属性的作用]]></title><link>https://wiki.dgfygk.com/archives/2fe98498-f1f5-44e1-941e-a43030935066</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=HTML%E6%A0%87%E7%AD%BEdata-%E5%B1%9E%E6%80%A7%E7%9A%84%E4%BD%9C%E7%94%A8&amp;url=/archives/2fe98498-f1f5-44e1-941e-a43030935066" width="1" height="1" alt="" style="opacity:0;">
<p>HTML标签中的data-属性用于存储自定义数据，这些数据可以在JavaScript中通过DOM访问和操作。它们可以用于在页面中存储任意类型的数据，例如字符串、数字、对象等，以便在需要时进行使用。这些数据属性可以帮助开发者在不使用全局变量的情况下，将数据与特定的HTML元素关联起来，从而实现更加模块化和可维护的代码。</p>
<p>举个例子：</p>
<p>假设我们有一个HTML按钮元素，我们想要在点击按钮时显示一个提示消息。我们可以使用data-属性来存储这个提示消息，并在JavaScript中访问和显示它。</p>
<pre><code class="hljs">&lt;button id="myButton" data-message="Hello, World!"&gt;Click me&lt;/button&gt;
</code></pre>
<p>在上面的例子中，我们给按钮元素添加了一个data-message属性，值为"Hello, World!“。接下来，我们可以使用JavaScript来获取这个data-属性的值，并在点击按钮时显示提示消息。</p>
<pre><code class="hljs">const button = document.getElementById('myButton');
const message = button.dataset.message;

button.addEventListener('click', function() {
  alert(message);
});
</code></pre>
<p>当用户点击按钮时，会弹出一个提示框显示"Hello, World!“。这样，我们就成功地使用data-属性存储了自定义数据，并在JavaScript中访问和使用它。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139041499</p>]]></description><guid isPermaLink="false">/archives/2fe98498-f1f5-44e1-941e-a43030935066</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:13:23 GMT</pubDate></item><item><title><![CDATA[jQuery实现瀑布流]]></title><link>https://wiki.dgfygk.com/archives/e7550437-e328-4d54-af20-035446498e1c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=jQuery%E5%AE%9E%E7%8E%B0%E7%80%91%E5%B8%83%E6%B5%81&amp;url=/archives/e7550437-e328-4d54-af20-035446498e1c" width="1" height="1" alt="" style="opacity:0;">
<p>以下是使用jQuery实现瀑布流的示例代码：</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;瀑布流示例&lt;/title&gt;
&lt;style&gt;
  .grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
  }
  .item {
    width: 30%;
    margin-bottom: 20px;
    background-color: #f0f0f0;
    border: 1px solid #ccc;
    box-sizing: border-box;
    padding: 10px;
  }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class="grid" id="grid"&gt;
&lt;/div&gt;

&lt;script src="https://code.jquery.com/jquery-3.6.0.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
$(document).ready(function(){
  // 模拟数据
  let data = [
    {title: 'Item 1'},
    {title: 'Item 2'},
    {title: 'Item 3'},
    {title: 'Item 4'},
    {title: 'Item 5'},
    {title: 'Item 6'},
    {title: 'Item 7'},
    {title: 'Item 8'},
    {title: 'Item 9'},
    {title: 'Item 10'},
  ];

  const grid = $('#grid');

  // 添加初始数据
  data.forEach(item =&gt; {
    const newItem = $('&lt;div class="item"&gt;' + item.title + '&lt;/div&gt;');
    grid.append(newItem);
  });

  // 当滚动到底部时加载更多数据
  $(window).scroll(function() {
    if($(window).scrollTop() + $(window).height() == $(document).height()) {
      // 加载更多数据
      data.forEach(item =&gt; {
        const newItem = $('&lt;div class="item"&gt;' + item.title + '&lt;/div&gt;');
        grid.append(newItem);
      });
    }
  });
});
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>以上代码通过jQuery实现了一个简单的瀑布流效果，页面加载时显示初始数据，当滚动到页面底部时加载更多数据。通过不断向页面末尾添加数据，实现了瀑布流的效果。您可以根据实际需求更改样式和数据内容。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139203704</p>]]></description><guid isPermaLink="false">/archives/e7550437-e328-4d54-af20-035446498e1c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:12:58 GMT</pubDate></item><item><title><![CDATA[JSf中ilter的使用方法]]></title><link>https://wiki.dgfygk.com/archives/88ff6962-4b66-4387-8127-d6982aab8157</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JSf%E4%B8%ADilter%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95&amp;url=/archives/88ff6962-4b66-4387-8127-d6982aab8157" width="1" height="1" alt="" style="opacity:0;">
<p>在JavaScript中，filter()方法是用于过滤数组元素的方法，返回一个新数组，该数组只包含满足条件的元素。filter()方法接受一个回调函数作为参数，该回调函数会对数组中的每个元素进行操作，如果回调函数返回true，则将该元素加入到新数组中。</p>
<p>下面是使用filter()方法的示例：</p>
<pre><code class="hljs">const numbers = [1, 2, 3, 4, 5];

// 过滤出所有大于2的元素
const filteredNumbers = numbers.filter(number =&gt; number &gt; 2);

console.log(filteredNumbers); // [3, 4, 5]
</code></pre>
<p>在这个示例中，我们创建了一个名为numbers的数组，然后使用filter()方法过滤出所有大于2的元素，并将结果保存在filteredNumbers中。最终打印出filteredNumbers的结果为[3, 4, 5]。</p>
<p>当然，filter()方法还可以结合其他方法一起使用，例如map()方法。下面是一个结合filter()和map()方法的示例：</p>
<pre><code class="hljs">const numbers = [1, 2, 3, 4, 5];

// 过滤出所有大于2的元素并将它们加倍
const filteredAndDoubledNumbers = numbers.filter(number =&gt; number &gt; 2)
                                         .map(number =&gt; number * 2);

console.log(filteredAndDoubledNumbers); // [6, 8, 10]
</code></pre>
<p>在这个示例中，我们首先使用filter()方法过滤出所有大于2的元素，然后将结果传递给map()方法，将每个元素加倍。最终打印出filteredAndDoubledNumbers的结果为[6, 8, 10]。这展示了如何结合多个数组方法来处理数据。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139116734</p>]]></description><guid isPermaLink="false">/archives/88ff6962-4b66-4387-8127-d6982aab8157</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:12:20 GMT</pubDate></item><item><title><![CDATA[Js地图路线规划以及点击获取经纬度]]></title><link>https://wiki.dgfygk.com/archives/1e6c6a97-9ced-4a8c-8ae6-5582ec698e0e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Js%E5%9C%B0%E5%9B%BE%E8%B7%AF%E7%BA%BF%E8%A7%84%E5%88%92%E4%BB%A5%E5%8F%8A%E7%82%B9%E5%87%BB%E8%8E%B7%E5%8F%96%E7%BB%8F%E7%BA%AC%E5%BA%A6&amp;url=/archives/1e6c6a97-9ced-4a8c-8ae6-5582ec698e0e" width="1" height="1" alt="" style="opacity:0;">
<p>废话不多说，直接上代码。</p>
<p>代码script引用链接链接来自高德地图开放平台。（<a href="https://lbs.amap.com/" title="高德开放平台 | 高德地图API (amap.com)">高德开放平台 \| 高德地图API (amap.com)</a><a href="https://lbs.amap.com/" title="高德开放平台 | 高德地图API (amap.com)"></a>）</p>
<pre><code class="hljs">&lt;!doctype html&gt;
&lt;html&gt;

&lt;head&gt;
  &lt;meta charset="utf-8"&gt;
  &lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&gt;
  &lt;meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width"&gt;
  &lt;script type="text/javascript"&gt;
    window._AMapSecurityConfig = {
        securityJsCode: "你的安全密钥",
    };
  &lt;/script&gt;
  &lt;title&gt;位置经纬度 + 驾车规划路线&lt;/title&gt;
  &lt;style type="text/css"&gt;
    html,
    body,
    #container {
      width: 92%;
      height: 100%;
    }

    #panel {
      position: fixed;
      background-color: white;
      max-height: 90%;
      overflow-y: auto;
      top: 10px;
      right: 10px;
      width: 280px;
    }

    #panel .amap-call {
      background-color: #009cf9;
      border-top-left-radius: 4px;
      border-top-right-radius: 4px;
    }

    #panel .amap-lib-driving {
      border-bottom-left-radius: 4px;
      border-bottom-right-radius: 4px;
      overflow: hidden;
    }
  &lt;/style&gt;
  &lt;link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" /&gt;
  &lt;script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"&gt;&lt;/script&gt;

  &lt;script type="text/javascript" src="https://cache.amap.com/lbs/static/addToolbar.js"&gt;&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;div id="container"&gt;&lt;/div&gt;
  &lt;div id="panel"&gt;&lt;/div&gt;
  &lt;div class="input-card"&gt;
    &lt;h4 style="line-height: 16px;margin: 10px;"&gt;左击获取经纬度：&lt;/h4&gt;
    &lt;div class="input-item"&gt;
        &lt;input type="text" readonly="true" id="lnglat"&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&amp;key=你的key值&amp;plugin=AMap.Driving"&gt;&lt;/script&gt;
  &lt;script type="text/javascript"&gt;
    //基本地图加载
    var map = new AMap.Map("container", {
      resizeEnable: true,
      center: [116.397428, 39.90923],//地图中心点
      zoom: 13 //地图显示的缩放级别
    });
    //构造路线导航类
    var driving = new AMap.Driving({
      map: map,
      panel: "panel"
    });
    // 根据起终点经纬度规划驾车导航路线
    driving.search(new AMap.LngLat(116.379028, 39.865042), new AMap.LngLat(116.427281, 39.903719), function (status, result) {
      // result 即是对应的驾车导航信息，相关数据结构文档请参考  https://lbs.amap.com/api/javascript-api/reference/route-search#m_DrivingResult
      if (status === 'complete') {
        log.success('绘制驾车路线完成')
        map.setCenter([116.442581,39.882498])
      } else {
        log.error('获取驾车数据失败：' + result)
      }
    });
    //为地图注册click事件获取鼠标点击出的经纬度坐标
    map.on('click', function(e) {
        document.getElementById("lnglat").value = e.lnglat.getLng() + ',' + e.lnglat.getLat()
    });
  &lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>感谢观看！！！</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138566808</p>]]></description><guid isPermaLink="false">/archives/1e6c6a97-9ced-4a8c-8ae6-5582ec698e0e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:11:43 GMT</pubDate></item><item><title><![CDATA[JS经常碰见的报错问题]]></title><link>https://wiki.dgfygk.com/archives/6919270b-41c4-45ef-bcac-5b8ec70da4da</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E7%BB%8F%E5%B8%B8%E7%A2%B0%E8%A7%81%E7%9A%84%E6%8A%A5%E9%94%99%E9%97%AE%E9%A2%98&amp;url=/archives/6919270b-41c4-45ef-bcac-5b8ec70da4da" width="1" height="1" alt="" style="opacity:0;">
<ol>
 <li>
  <p>语法错误：由于 JavaScript 是一种动态语言，因此编写代码期间可能会出现语法错误。这可能包括拼写错误、漏掉分号或括号等问题。</p>
 </li>
 <li>
  <p>作用域问题：JavaScript 中存在全局作用域和局部作用域的概念，有时候可能会出现变量作用域混乱导致的问题。</p>
 </li>
 <li>
  <p>异步编程问题：JavaScript 是单线程语言，因此在处理异步任务时可能会遇到回调地狱、Promise 的使用不当等问题。</p>
 </li>
 <li>
  <p>变量类型问题：JavaScript 是一种弱类型语言，因此变量类型的隐式转换可能导致意外的结果。</p>
 </li>
 <li>
  <p>性能问题：JavaScript 是一种解释性语言，因此可能存在性能问题。不良的代码设计、循环引用、频繁的 DOM 操作等都可能导致性能下降。</p>
 </li>
 <li>
  <p>跨域问题：由于浏览器的同源策略，JavaScript 在跨域请求时可能会遇到权限相关的问题。</p>
 </li>
 <li>
  <p>安全问题：JavaScript 可能存在安全漏洞，如 XSS 攻击、CSRF 攻击等。</p>
 </li>
 <li>
  <p>兼容性问题：不同浏览器对 JavaScript 的实现不同，可能导致兼容性问题。需要注意代码在不同浏览器上的表现是否一致。</p>
 </li>
 <li>
  <p>内存泄漏问题：JavaScript 是垃圾回收语言，但仍可能出现内存泄漏问题，特别是在处理大量数据或长时间运行的任务时。</p>
 </li>
 <li>
  <p>回调地狱问题：在异步编程中，过度使用回调函数会导致代码难以理解、维护和调试的问题。可以通过 Promise、async/await 等方式解决这个问题。</p>
 </li>
</ol>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139202073</p>]]></description><guid isPermaLink="false">/archives/6919270b-41c4-45ef-bcac-5b8ec70da4da</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:11:28 GMT</pubDate></item><item><title><![CDATA[JS实现递归功能]]></title><link>https://wiki.dgfygk.com/archives/b6a267fa-bcd4-4caf-86fa-c42bf87cb888</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E9%80%92%E5%BD%92%E5%8A%9F%E8%83%BD&amp;url=/archives/b6a267fa-bcd4-4caf-86fa-c42bf87cb888" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="hljs">// 递归函数示例：计算阶乘
function factorial(n) {
  if (n === 0) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}

// 调用递归函数计算阶乘
const result = factorial(5);
console.log(result); // 输出 120
</code></pre>
<p>在上面的示例中，我们定义了一个递归函数<code>factorial</code>来计算一个数的阶乘。当输入的数为0时，返回1；否则递归调用自身并返回当前数与递归调用结果的乘积。最后，我们调用这个递归函数计算5的阶乘，并将结果输出到控制台。</p>
<p>这只是一个简单的递归功能示例，你可以根据自己的需求来编写更复杂的递归函数。希望对你有所帮助！</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138731543</p>]]></description><guid isPermaLink="false">/archives/b6a267fa-bcd4-4caf-86fa-c42bf87cb888</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:11:13 GMT</pubDate></item><item><title><![CDATA[JS实现分页功能]]></title><link>https://wiki.dgfygk.com/archives/9ff6bc85-fbf6-4902-b960-aefda4ad6986</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E5%88%86%E9%A1%B5%E5%8A%9F%E8%83%BD&amp;url=/archives/9ff6bc85-fbf6-4902-b960-aefda4ad6986" width="1" height="1" alt="" style="opacity:0;">
<p>废话不多说，直接上代码：</p>
<pre><code class="hljs">// 假设有一个包含所有数据的数组
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 每页显示的数据条数
const pageSize = 3;

// 当前页数
let currentPage = 1;

// 获取总页数
const totalPages = Math.ceil(data.length / pageSize);

// 根据当前页数和每页显示的数据条数来获取当前页的数据
function getCurrentPageData() {
  const startIndex = (currentPage - 1) * pageSize;
  const endIndex = startIndex + pageSize;
  return data.slice(startIndex, endIndex);
}

// 更新页面内容
function updatePageContent() {
  const currentPageData = getCurrentPageData();
  // 将当前页的数据渲染到页面上
  currentPageData.forEach(item =&gt; {
    // 渲染数据到页面上的代码
    console.log(item);
  });

  // 更新分页按钮
  // 这里可以根据当前页数和总页数来渲染分页按钮，比如上一页、下一页、页码等
  // 以及绑定相应的点击事件来切换页数
}

// 初始化页面内容
updatePageContent();

// 示例：点击下一页按钮时切换到下一页
document.getElementById('nextPageBtn').addEventListener('click', () =&gt; {
  if (currentPage &lt; totalPages) {
    currentPage++;
    updatePageContent();
  }
});

// 示例：点击上一页按钮时切换到上一页
document.getElementById('prevPageBtn').addEventListener('click', () =&gt; {
  if (currentPage &gt; 1) {
    currentPage--;
    updatePageContent();
  }
});
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138731508</p>]]></description><guid isPermaLink="false">/archives/9ff6bc85-fbf6-4902-b960-aefda4ad6986</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:10:26 GMT</pubDate></item><item><title><![CDATA[JS实现全屏和退出全屏]]></title><link>https://wiki.dgfygk.com/archives/83c00057-41c1-426c-afd6-62018aeb6033</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E5%85%A8%E5%B1%8F%E5%92%8C%E9%80%80%E5%87%BA%E5%85%A8%E5%B1%8F&amp;url=/archives/83c00057-41c1-426c-afd6-62018aeb6033" width="1" height="1" alt="" style="opacity:0;">
<p>全屏和退出全屏时pc端很常用的一个功能，我们直接来看：</p>
<p>在HTML中添加两个按钮：</p>
<pre><code class="hljs">&lt;button id="fullScreenBtn"&gt;进入全屏&lt;/button&gt;
&lt;button id="exitFullScreenBtn"&gt;退出全屏&lt;/button&gt;
</code></pre>
<p>JS的内容：</p>
<pre><code class="hljs">// 进入全屏
function requestFullScreen(element) {
    if (element.requestFullscreen) {
        element.requestFullscreen();
    } else if (element.mozRequestFullScreen) {
        element.mozRequestFullScreen();
    } else if (element.webkitRequestFullscreen) {
        element.webkitRequestFullscreen();
    } else if (element.msRequestFullscreen) {
        element.msRequestFullscreen();
    }
}

// 退出全屏
function exitFullScreen() {
    if (document.exitFullscreen) {
        document.exitFullscreen();
    } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
    } else if (document.msExitFullscreen) {
        document.msExitFullscreen();
    }
}

// 按钮点击事件
document.getElementById('fullScreenBtn').addEventListener('click', function() {
    requestFullScreen(document.documentElement);
});

document.getElementById('exitFullScreenBtn').addEventListener('click', function() {
    exitFullScreen();
});
</code></pre>
<p>这样就可以通过点击按钮来实现全屏和退出全屏的功能。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138849968</p>]]></description><guid isPermaLink="false">/archives/83c00057-41c1-426c-afd6-62018aeb6033</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:10:09 GMT</pubDate></item><item><title><![CDATA[原生JS实现全选、全不选]]></title><link>https://wiki.dgfygk.com/archives/ea49f2c2-d3a1-47da-8ea3-b0e8cc222ec2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%8E%9F%E7%94%9FJS%E5%AE%9E%E7%8E%B0%E5%85%A8%E9%80%89%E3%80%81%E5%85%A8%E4%B8%8D%E9%80%89&amp;url=/archives/ea49f2c2-d3a1-47da-8ea3-b0e8cc222ec2" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;Check All Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;input type="checkbox" id="checkAll"&gt; Check All
&lt;br&gt;
&lt;input type="checkbox" class="checkbox"&gt; Item 1
&lt;br&gt;
&lt;input type="checkbox" class="checkbox"&gt; Item 2
&lt;br&gt;
&lt;input type="checkbox" class="checkbox"&gt; Item 3
&lt;br&gt;

&lt;script&gt;
var checkAll = document.getElementById('checkAll');
var checkboxes = document.querySelectorAll('.checkbox');

// 全选/全不选功能
checkAll.addEventListener('change', function() {
    checkboxes.forEach(function(checkbox) {
        checkbox.checked = checkAll.checked;
    });
});

// 每个复选框改变时检查是否所有复选框都被选中
checkboxes.forEach(function(checkbox) {
    checkbox.addEventListener('change', function() {
        var allChecked = true;
        checkboxes.forEach(function(checkbox) {
            if (!checkbox.checked) {
                allChecked = false;
            }
        });
        checkAll.checked = allChecked;
    });
});
&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>在这个示例中，我们首先获取全选复选框和所有子复选框的元素，然后为全选复选框和每个子复选框添加事件监听器。当全选复选框的状态改变时，我们将所有子复选框的状态设置为与全选复选框相同。当任何一个子复选框的状态改变时，我们检查是否所有子复选框都被选中，如果是则将全选复选框设置为选中状态，否则设置为未选中状态。</p>
<p>这样就实现了简单的全选和全不选功能。你可以根据自己的需求对代码进行定制和扩展。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/139023297</p>]]></description><guid isPermaLink="false">/archives/ea49f2c2-d3a1-47da-8ea3-b0e8cc222ec2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:09:48 GMT</pubDate></item><item><title><![CDATA[css实现瀑布流布局]]></title><link>https://wiki.dgfygk.com/archives/edfc078a-96c7-4bd7-ab2c-0debfecf11c1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=css%E5%AE%9E%E7%8E%B0%E7%80%91%E5%B8%83%E6%B5%81%E5%B8%83%E5%B1%80&amp;url=/archives/edfc078a-96c7-4bd7-ab2c-0debfecf11c1" width="1" height="1" alt="" style="opacity:0;">
<p>瀑布流布局也可以通过纯CSS来实现，使用CSS的column属性可以实现多列布局。下面是一个使用纯CSS实现瀑布流布局的示例：</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;瀑布流布局&lt;/title&gt;
&lt;style&gt;
    .waterfall {
        column-count: 4;
        column-gap: 10px;
    }

    .item {
        break-inside: avoid-column;
        margin-bottom: 10px;
    }

    .item img {
        width: 100%;
        display: block;
    }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class="waterfall"&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/150" alt=""&gt;&lt;/div&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/200" alt=""&gt;&lt;/div&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/250" alt=""&gt;&lt;/div&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/300" alt=""&gt;&lt;/div&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/350" alt=""&gt;&lt;/div&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/400" alt=""&gt;&lt;/div&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/450" alt=""&gt;&lt;/div&gt;
    &lt;div class="item"&gt;&lt;img src="https://via.placeholder.com/500" alt=""&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>在这个示例中，我们使用CSS的<code>column-count</code>属性将<code>.waterfall</code>容器分为4列，使用<code>column-gap</code>属性设置列与列之间的间距。然后通过<code>break-inside: avoid-column</code>属性确保<code>.item</code>元素不会被分割到两列之间。最后，设置<code>.item img</code>元素的宽度为100%以确保图片占满整个列，并设置<code>display: block</code>以消除图片之间的空隙。</p>
<p>通过这种方式，我们可以实现一个简单的瀑布流布局，不需要额外的JavaScript代码。你可以根据实际需求调整列数、间距和元素样式等来实现不同的效果。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138426343</p>]]></description><guid isPermaLink="false">/archives/edfc078a-96c7-4bd7-ab2c-0debfecf11c1</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:07:38 GMT</pubDate></item><item><title><![CDATA[document.write和innerHTML、innerText 的区别？]]></title><link>https://wiki.dgfygk.com/archives/5c3f528d-c90a-4b48-b60f-20a628b65ef1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=document.write%E5%92%8CinnerHTML%E3%80%81innerText%20%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%9F&amp;url=/archives/5c3f528d-c90a-4b48-b60f-20a628b65ef1" width="1" height="1" alt="" style="opacity:0;">
<p>
 <br>
</p>
<ol>
 <li>
  <p>document.write:</p>
 </li>
 <li>
  <p>document.write 是一个方法，用于向文档中写入内容。</p>
 </li>
 <li>
  <p>它可以直接在 HTML 文档中使用，将内容直接输出到页面上。</p>
 </li>
 <li>
  <p>但是，如果在页面加载完成后再使用 document.write 方法，它会覆盖整个文档内容，可能会导致页面结构混乱。</p>
 </li>
 <li>
  <p>2.innerHTML:</p>
 </li>
 <li>
  <p>innerHTML 是一个属性，用于获取或设置元素的 HTML 内容。</p>
 </li>
 <li>
  <p>可以通过 innerHTML 属性来改变元素的内容，而不会影响元素本身。</p>
 </li>
 <li>
  <p>innerHTML 只能用于 HTML 元素，不能用于文档节点。</p>
 </li>
 <li>
  <p>3.innerText:</p>
 </li>
 <li>
  <p>innerText 是一个属性，用于获取或设置元素的文本内容。</p>
 </li>
 <li>
  <p>innerText 会忽略元素中的 HTML 标签，只返回文本内容。</p>
 </li>
 <li>
  <p>innerText 适用于需要处理纯文本内容的情况，但在某些情况下可能会受到浏览器的支持程度影响。</p>
  <p>
   <br>
   <br>
  </p>
  <pre><code>&lt;title&gt;Document.write vs innerHTML vs innerText&lt;/title&gt;
</code></pre>
  <p>
   <br>
  </p>
  <pre><code>&lt;div id="output"&gt;&lt;/div&gt;

&lt;script&gt;
    // 使用 document.write
    document.write("Hello, document.write!");

    // 使用 innerHTML
    document.getElementById("output").innerHTML = "&lt;p&gt;Hello, innerHTML!&lt;/p&gt;";

    // 使用 innerText
    document.getElementById("output").innerText = "Hello, innerText!";
&lt;/script&gt;
</code></pre>
  <p>
   <br>
  </p>
 </li>
</ol>
<p>document.write 用于向文档中写入内容，会直接输出到页面上，可能会导致页面结构混乱。 innerHTML 和 innerText 用于获取或设置元素的内容，innerHTML 可以处理 HTML 内容，而 innerText 只处理纯文本内容。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138215142</p>]]></description><guid isPermaLink="false">/archives/5c3f528d-c90a-4b48-b60f-20a628b65ef1</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:07:21 GMT</pubDate></item><item><title><![CDATA[for each和for of的区别]]></title><link>https://wiki.dgfygk.com/archives/b448a50e-5c5c-43da-9438-2c1e01a01b45</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=for%20each%E5%92%8Cfor%20of%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/b448a50e-5c5c-43da-9438-2c1e01a01b45" width="1" height="1" alt="" style="opacity:0;">
<p>forEach方法：
 <br>
 forEach是Array对象的方法，可以直接使用。
 <br>
 forEach会对数组中的每个元素执行指定的回调函数，但无法在回调函数中使用break或return来中止循环。
 <br>
 回调函数接受三个参数：当前遍历的元素值、当前遍历的索引和整个数组本身。
 <br>
 forEach不返回任何值，它只是用于遍历数组并执行操作。</p>
<pre><code class="hljs">let arr = [1, 2, 3, 4];

arr.forEach((element, index) =&gt; {
  console.log(`Element at index ${index}: ${element}`);
});

//输出结果
Element at index 0: 1
Element at index 1: 2
Element at index 2: 3
Element at index 3: 4
</code></pre>
<ol>
 <li><code>for...of</code>循环：</li>
 <li><code>for...of</code>是ES6引入的语法，用于遍历具有迭代器的可迭代对象（如数组、字符串、set ， map等）。</li>
 <li><code>for...of</code>支持使用<code>break</code>和<code>return</code>来中止循环。</li>
 <li>在每次迭代中，通过<code>of</code>关键字将当前元素的值赋给一个变量，可以直接访问该值。</li>
</ol>
<pre><code class="hljs">const arr = [1, 2, 3, 4];

for (const element of arr) {
  console.log(`Element: ${element}`);
}
//输出结果
Element: 1
Element: 2
Element: 3
Element: 4
</code></pre>
<ul>
 <li><code>forEach</code>适用于需要对每个数组元素执行相同操作的情况，而且不需要中止循环。</li>
 <li><code>for...of</code>适用于需要在遍历过程中使用<code>break</code>或<code>return</code>来中止循环的情况，或者需要直接访问当前元素的值。此外，<code>for...of</code>也适用于遍历其他可迭代对象，如字符串、Set和Map等。</li>
</ul>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138170022</p>]]></description><guid isPermaLink="false">/archives/b448a50e-5c5c-43da-9438-2c1e01a01b45</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:06:51 GMT</pubDate></item><item><title><![CDATA[JS遍历数组和对象的方法有哪些]]></title><link>https://wiki.dgfygk.com/archives/c7481e31-051e-4166-bda0-bdd2493013c8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%E5%92%8C%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%96%B9%E6%B3%95%E6%9C%89%E5%93%AA%E4%BA%9B&amp;url=/archives/c7481e31-051e-4166-bda0-bdd2493013c8" width="1" height="1" alt="" style="opacity:0;">
<p>在JavaScript中，遍历数组和对象的方法有以下几种：</p>
<ol>
 <li>
  <p>for循环：使用for循环可以遍历数组和对象的所有元素。</p>
  <pre><code class="hljs">// 遍历数组
var arr = [1, 2, 3];
for (var i = 0; i &lt; arr.length; i++) {
 console.log(arr[i]);
}

// 遍历对象
var obj = {a: 1, b: 2, c: 3};
for (var key in obj) {
 console.log(key + ": " + obj[key]);
}
</code></pre>
  <p>
   <br>
  </p>
 </li>
 <li>
  <p>forEach方法：数组的forEach方法可以遍历数组的每个元素。</p>
  <pre><code class="hljs">var arr = [1, 2, 3];
arr.forEach(function(item) {
 console.log(item);
});
</code></pre>
  <p>
   <br>
  </p>
 </li>
 <li>
  <p>map方法：数组的map方法可以遍历数组的每个元素并返回一个新的数组。</p>
 </li>
</ol>
<pre><code class="hljs">var arr = [1, 2, 3];
var newArr = arr.map(function(item) {
  return item * 2;
});
console.log(newArr);
</code></pre>
<p>4.Object.keys方法：Object.keys方法可以获取对象的所有属性名，并返回一个数组。</p>
<pre><code class="hljs">var obj = {a: 1, b: 2, c: 3};
var keys = Object.keys(obj);
keys.forEach(function(key) {
  console.log(key + ": " + obj[key]);
});
</code></pre>
<p>5.Object.entries方法：Object.entries方法可以将对象转换为一个包含键值对的数组。</p>
<pre><code class="hljs">var obj = {a: 1, b: 2, c: 3};
var entries = Object.entries(obj);
entries.forEach(function([key, value]) {
  console.log(key + ": " + value);
});
</code></pre>
<p>这些方法可以帮助我们更方便地遍历数组和对象的元素，根据具体的需求选择合适的方法进行遍历</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138453706</p>]]></description><guid isPermaLink="false">/archives/c7481e31-051e-4166-bda0-bdd2493013c8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:06:08 GMT</pubDate></item><item><title><![CDATA[JS三级联动]]></title><link>https://wiki.dgfygk.com/archives/70e9cf3f-99fc-448a-8faf-acf13b06c414</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E4%B8%89%E7%BA%A7%E8%81%94%E5%8A%A8&amp;url=/archives/70e9cf3f-99fc-448a-8faf-acf13b06c414" width="1" height="1" alt="" style="opacity:0;">
<p>三级联动是指页面上有三个下拉框，第一个下拉框选定某个选项后，第二个下拉框会根据第一个下拉框的选项进行筛选，第三个下拉框会根据第二个下拉框的选项进行筛选。这种交互方式可以帮助用户快速定位到需要的选项。</p>
<p>下面是一个简单的三级联动的代码实例：</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;三级联动&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;select id="province"&gt;
    &lt;option value=""&gt;请选择省份&lt;/option&gt;
    &lt;option value="1"&gt;广东省&lt;/option&gt;
    &lt;option value="2"&gt;浙江省&lt;/option&gt;
&lt;/select&gt;

&lt;select id="city"&gt;
    &lt;option value=""&gt;请选择城市&lt;/option&gt;
&lt;/select&gt;

&lt;select id="district"&gt;
    &lt;option value=""&gt;请选择区县&lt;/option&gt;
&lt;/select&gt;

&lt;script&gt;
const cityData = {
    1: ["广州市", "深圳市", "珠海市"],
    2: ["杭州市", "宁波市", "温州市"]
};

const districtData = {
    "广州市": ["天河区", "越秀区", "海珠区"],
    "深圳市": ["福田区", "罗湖区", "南山区"],
    "杭州市": ["西湖区", "拱墅区", "滨江区"],
    "宁波市": ["海曙区", "江东区", "江北区"]
};

const province = document.getElementById("province");
const city = document.getElementById("city");
const district = document.getElementById("district");

province.addEventListener("change", function() {
    city.innerHTML = "&lt;option value=''&gt;请选择城市&lt;/option&gt;";
    district.innerHTML = "&lt;option value=''&gt;请选择区县&lt;/option&gt;";

    const selectedProvince = province.value;
    const cities = cityData[selectedProvince];
    cities.forEach(function(item) {
        const option = document.createElement("option");
        option.value = item;
        option.textContent = item;
        city.appendChild(option);
    });
});

city.addEventListener("change", function() {
    district.innerHTML = "&lt;option value=''&gt;请选择区县&lt;/option&gt;";

    const selectedCity = city.value;
    const districts = districtData[selectedCity];
    districts.forEach(function(item) {
        const option = document.createElement("option");
        option.value = item;
        option.textContent = item;
        district.appendChild(option);
    });
});
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>在这个代码实例中，我们定义了两个对象<code>cityData</code>和<code>districtData</code>，分别存储了省份、城市和区县的数据。然后通过事件监听器，实现了省份、城市和区县三个下拉框的联动效果。当选择省份后，城市下拉框会根据省份的选择进行筛选，选择城市后，区县下拉框会根据城市的选择进行筛选。这样用户就可以方便快速地选择到需要的选项。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138229412</p>]]></description><guid isPermaLink="false">/archives/70e9cf3f-99fc-448a-8faf-acf13b06c414</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:05:48 GMT</pubDate></item><item><title><![CDATA[JS实现瀑布流布局]]></title><link>https://wiki.dgfygk.com/archives/2f2c9dc5-a097-4d94-9d8c-8895216ba97b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E7%80%91%E5%B8%83%E6%B5%81%E5%B8%83%E5%B1%80&amp;url=/archives/2f2c9dc5-a097-4d94-9d8c-8895216ba97b" width="1" height="1" alt="" style="opacity:0;">
<p>瀑布流布局是一种常见的网页布局方式，可以实现页面内容的动态排列，使页面看起来更加美观。下面是一个简单的JS实现瀑布流布局的示例：</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;瀑布流布局&lt;/title&gt;
&lt;style&gt;
    .waterfall {
        column-count: 4;
        column-gap: 10px;
    }

    .item {
        margin-bottom: 10px;
    }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class="waterfall" id="waterfall"&gt;
&lt;/div&gt;

&lt;script&gt;
    const waterfall = document.getElementById('waterfall');

    const data = [
        'https://via.placeholder.com/150',
        'https://via.placeholder.com/200',
        'https://via.placeholder.com/250',
        'https://via.placeholder.com/300',
        'https://via.placeholder.com/350',
        'https://via.placeholder.com/400',
        'https://via.placeholder.com/450',
        'https://via.placeholder.com/500',
    ];

    data.forEach((url) =&gt; {
        const item = document.createElement('div');
        item.className = 'item';
        const img = document.createElement('img');
        img.src = url;
        item.appendChild(img);
        waterfall.appendChild(item);
    });
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>在这个示例中，我们首先定义了一个包含图片链接的数组<code>data</code>，然后使用<code>forEach</code>方法遍历数组，创建<code>div</code>元素作为每个图片的容器，并设置其样式为<code>item</code>。然后创建<code>img</code>元素，设置其<code>src</code>属性为对应的图片链接，最后将<code>img</code>元素添加到<code>div</code>容器中，并将整个<code>div</code>容器添加到<code>waterfall</code>容器中。</p>
<p>通过这种方式，我们可以实现一个简单的瀑布流布局，使页面内容以多列的方式动态排列。你可以根据实际需求调整列数和间距等样式来实现不同的效果。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138426308</p>]]></description><guid isPermaLink="false">/archives/2f2c9dc5-a097-4d94-9d8c-8895216ba97b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:05:34 GMT</pubDate></item><item><title><![CDATA[JS图表生成以及点击修改图表样式]]></title><link>https://wiki.dgfygk.com/archives/32a1f745-bf12-4457-baec-60c271ccea7c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%9B%BE%E8%A1%A8%E7%94%9F%E6%88%90%E4%BB%A5%E5%8F%8A%E7%82%B9%E5%87%BB%E4%BF%AE%E6%94%B9%E5%9B%BE%E8%A1%A8%E6%A0%B7%E5%BC%8F&amp;url=/archives/32a1f745-bf12-4457-baec-60c271ccea7c" width="1" height="1" alt="" style="opacity:0;">
<p>在这个示例中，我们将使用Chart.js库来生成一个简单的柱状图，并且添加按钮来点击时修改图表的样式。</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8" /&gt;
        &lt;title&gt;图表练习&lt;/title&gt;
        &lt;script src="./js/jquery-3.7.1.min.js"&gt;&lt;/script&gt;
        &lt;script src="./js/echarts.min.js"&gt;&lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;!-- 为 ECharts 准备一个定义了宽高的 DOM --&gt;
        &lt;div id="main" style="width: 600px;height:400px;"&gt;&lt;/div&gt;
        &lt;button id="Bar"&gt;柱状图&lt;/button&gt;
        &lt;button id="Line"&gt;折线图&lt;/button&gt;
        &lt;div id="mains" style="width: 600px;height:400px;"&gt;&lt;/div&gt;
        &lt;div id="" style="width: 600px;height:400px;"&gt;&lt;/div&gt;
        &lt;script type="text/javascript"&gt;
            // 基于准备好的dom，初始化echarts实例
            var myChart = echarts.init(document.getElementById('main'));
            let arr = [{
                    id: 1,
                    name: '衬衫',
                    price: '119',
                    num: 109,
                    income: 152
                },
                {
                    id: 2,
                    name: '西装',
                    price: '159',
                    num: 59,
                    income: 256
                },
                {
                    id: 3,
                    name: '领带',
                    price: '99',
                    num: 200,
                    income: 147
                },
                {
                    id: 4,
                    name: '那不雷斯',
                    price: '169',
                    num: 123,
                    income: 258
                },
                {
                    id: 5,
                    name: '皮鞋',
                    price: '259',
                    num: 144,
                    income: 369
                }
            ];
            let goods = [];
            let quantity = [];
            let surplus = [];
            let price = [];
            for (let i in arr) {
                goods[i] = arr[i].name;
                quantity[i] = arr[i].num;
                surplus[i] = arr[i].income;
                price[i] = arr[i].price;
            }
            // 指定图表的配置项和数据
            let option = {
                title: {
                    text: '西装'
                },
                tooltip: {},
                legend: {
                    data: ['销量', '剩余', '价格']
                },
                xAxis: {
                    data: goods
                },
                yAxis: {},
                series: [{
                        name: '销量',
                        type: 'bar',
                        data: quantity,
                    },
                    {
                        name: '剩余',
                        type: 'bar',
                        data: surplus
                    },
                    {
                        name: '价格',
                        type: 'bar',
                        data: price
                    }
                ]
            };

            // 使用刚指定的配置项和数据显示图表。
            myChart.setOption(option);
            let Bar = document.getElementById('Bar');
            let Line = document.getElementById('Line');
            Line.onclick = function() {
                for(let i = 0 ; i &lt; option.series.length ; i++){
                    option.series[i].type = 'line';
                    myChart.setOption(option);
                }

            }
            Bar.onclick = function() {
                for(let i = 0 ; i &lt; option.series.length ; i++){
                option.series[i].type = 'bar';
                myChart.setOption(option);
                }

            }
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>我们可以通过点击按钮，切换柱状图和折线图。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138507140</p>]]></description><guid isPermaLink="false">/archives/32a1f745-bf12-4457-baec-60c271ccea7c</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:05:23 GMT</pubDate></item><item><title><![CDATA[js怎样获取浏览器窗口尺寸]]></title><link>https://wiki.dgfygk.com/archives/832a55a9-0bb6-457d-a4b3-6ecfd31a10ec</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=js%E6%80%8E%E6%A0%B7%E8%8E%B7%E5%8F%96%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AA%97%E5%8F%A3%E5%B0%BA%E5%AF%B8&amp;url=/archives/832a55a9-0bb6-457d-a4b3-6ecfd31a10ec" width="1" height="1" alt="" style="opacity:0;">
<p>在 JavaScript 中，我们可以使用以下方法来获取浏览器窗口尺寸：</p>
<ol>
 <li>`window.innerWidth` 和 `window.innerHeight` 属性：用于获取浏览器窗口的内部宽度和高度（也就是不包括<a href="https://so.csdn.net/so/search?q=%E6%BB%9A%E5%8A%A8%E6%9D%A1&amp;spm=1001.2101.3001.7020" title="滚动条">滚动条</a>和边框的尺寸）。例如：</li>
</ol>
<pre><code class="hljs">var width = window.innerWidth;
var height = window.innerHeight;
console.log('窗口宽度：' + width);
console.log('窗口高度：' + height);
</code></pre>
<ol>
 <li>`document.documentElement.clientWidth` 和 `document.documentElement.clientHeight` 属性：用于获取文档的根元素（即 `` 元素）的客户端宽度和高度。这些属性与 `window.innerWidth` 和 `window.innerHeight` 属性相同，但在一些情况下可能会有所不同。例如：</li>
</ol>
<pre><code class="hljs">var width = document.documentElement.clientWidth;
var height = document.documentElement.clientHeight;
console.log('窗口宽度：' + width);
console.log('窗口高度：' + height);
</code></pre>
<p>注意，这些方法获取的是浏览器窗口的尺寸，而不是整个屏幕的尺寸。如果你需要获取整个屏幕的尺寸，可以使用 `screen.width` 和 `screen.height` 属性。</p>
<pre><code class="hljs">var screenWidth = screen.width;
var screenHeight = screen.height;
console.log('屏幕宽度：' + screenWidth);
console.log('屏幕高度：' + screenHeight);
</code></pre>
<p>总结起来，我们可以使用 `window.innerWidth` 和 `window.innerHeight` 属性或 `document.documentElement.clientWidth` 和 `document.documentElement.clientHeight` 属性来获取浏览器窗口的尺寸，以及使用 `screen.width` 和 `screen.height` 属性来获取整个屏幕的尺寸</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137979284</p>]]></description><guid isPermaLink="false">/archives/832a55a9-0bb6-457d-a4b3-6ecfd31a10ec</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:04:46 GMT</pubDate></item><item><title><![CDATA[js中onchange的使用场景及如何使用]]></title><link>https://wiki.dgfygk.com/archives/1a08a24c-0fb0-4533-8203-03ec739ef164</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=js%E4%B8%ADonchange%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E5%8F%8A%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8&amp;url=/archives/1a08a24c-0fb0-4533-8203-03ec739ef164" width="1" height="1" alt="" style="opacity:0;">
<p>使用场景：</p>
<ul>
 <li>onchange 事件常用于表单元素（如 input、select、textarea 等）的值发生改变时触发相应的操作。</li>
 <li>可以用于实时监测用户输入的内容，进行验证或实时更新相关内容。</li>
</ul>
<p>示例代码：</p>
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;onchange Event Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;label for="inputText"&gt;Enter Text:&lt;/label&gt;
    &lt;input type="text" id="inputText" onchange="handleChange()"&gt;

    &lt;p id="output"&gt;&lt;/p&gt;

    &lt;script&gt;
        function handleChange() {
            var input = document.getElementById("inputText").value;
            document.getElementById("output").innerText = "You entered: " + input;
        }
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>在这个例子中，我们创建了一个文本输入框和一个段落元素，当用户在文本输入框中输入内容并且移开焦点时（值发生改变时），会触发 handleChange 函数，该函数获取输入框中的值并将其显示在段落元素中。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138215192</p>]]></description><guid isPermaLink="false">/archives/1a08a24c-0fb0-4533-8203-03ec739ef164</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:04:28 GMT</pubDate></item><item><title><![CDATA[深入了解JS作用域]]></title><link>https://wiki.dgfygk.com/archives/6f232cb0-5e7d-4dcb-b9a1-51ea3f2d7b06</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E4%BA%86%E8%A7%A3JS%E4%BD%9C%E7%94%A8%E5%9F%9F&amp;url=/archives/6f232cb0-5e7d-4dcb-b9a1-51ea3f2d7b06" width="1" height="1" alt="" style="opacity:0;">
<p>在JavaScript中，作用域（Scope）是指变量的可访问性或可见性的范围。了解作用域是非常重要的，因为它决定了变量在代码中的访问范围，以及变量的生命周期。</p>
<p>1.全局作用域（Global Scope）：全局作用域是指在代码中任何地方都可以访问的变量，它们在整个程序中都是可见的。</p>
<pre><code class="hljs">var globalVariable = "I am a global variable";

function globalFunction() {
  console.log(globalVariable);
}

globalFunction(); // 输出"I am a global variable"
</code></pre>
<p>2.函数作用域（Function Scope）：函数作用域是指在函数内部定义的变量只能在该函数内部访问，外部无法访问。</p>
<pre><code class="hljs">function localFunction() {
  var localVariable = "I am a local variable";
  console.log(localVariable);
}

localFunction(); // 输出"I am a local variable"
console.log(localVariable); // 报错，localVariable未定义
</code></pre>
<p>3.块级作用域（Block Scope）：在ES6之前，JavaScript没有块级作用域，只有函数作用域和全局作用域。但是在ES6中引入了let和const关键字，可以创建块级作用域。</p>
<pre><code class="hljs">if (true) {
  var x = 10; // var声明的变量为函数作用域，会污染外部作用域
  let y = 20; // let声明的变量为块级作用域
  const z = 30; // const声明的变量也为块级作用域
}
console.log(x); // 输出10
console.log(y); // 报错，y未定义
console.log(z); // 报错，z未定义
</code></pre>
<p>4.词法作用域（Lexical Scope）：词法作用域是指变量的作用域由代码中变量声明的位置决定，而不是代码执行的位置。</p>
<pre><code class="hljs">var outside = "I am outside";

function lexicalScope() {
  var inside = "I am inside";
  console.log(outside); // 可以访问函数外部的变量
  console.log(inside); // 可以访问函数内部的变量
}

lexicalScope();
</code></pre>
<p>深入了解JavaScript的作用域有助于避免变量命名冲突、理解闭包的工作原理以及提高代码的可维护性。在实际编程中，充分利用不同作用域的特性可以更好地组织和管理代码。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/138453802</p>]]></description><guid isPermaLink="false">/archives/6f232cb0-5e7d-4dcb-b9a1-51ea3f2d7b06</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:04:12 GMT</pubDate></item><item><title><![CDATA[JavaScript BOM 浏览器对象模型]]></title><link>https://wiki.dgfygk.com/archives/32586e9a-59f0-44f5-b91a-93e6823135c6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%20BOM%20%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B&amp;url=/archives/32586e9a-59f0-44f5-b91a-93e6823135c6" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>1、JavaScript BOM 的概念</strong></h3>
<p>浏览器对象模型（Browser object model）简称 BOM。Js 通过 BOM 和浏览器进行交互，可以获</p>
<p>取屏幕尺寸，窗口大小，页面地址，历史记录等浏览器相关信息，也可以控制浏览器执行某些行</p>
<p>为：弹出信息，页面跳转，打开窗口，关闭窗口，调整窗口大小等。</p>
<p>浏览器对象模型用 window 对象来表示，所有 JavaScript 全局变量、全局函数会自动成为</p>
<p>window 对象的成员。DOM 的 document 也是 window 对象的一个属性。</p>
<h3><strong>2、JavaScript 窗 口</strong></h3>
<p>window.open()：打开新窗口</p>
<p>window.close()：关闭当前窗口</p>
<p>window.moveTo()：移动当前窗口</p>
<p>window.resizeTo()：调整当前窗口的尺寸</p>
<p>eg:</p>
<pre><code class="javascript">&lt;button  onclick="isOpen()"&gt;打开百度&lt;/button&gt;

&lt;script&gt;

function  isOpen(){

myBaidu  =  window.open('http://www.baidu.com');

}

&lt;/script&gt;
</code></pre>
<h3><strong>3、JavaScript 地址信息与页面跳转</strong></h3>
<p><strong>跳转传参（重点）：</strong></p>
<p>跳转传参指在页面跳转时，将部分数据拼接到 URL 路径上，一并跳转到另一个页面上。</p>
<p>另一页面可以通过 <strong>window.location.search</strong> 接收此参数。并配合 <strong>slice</strong>将多余符号切割掉。</p>
<p>永久性定时器，每隔一段时间执行一次</p>
<h3>4、JavaScript 定时器</h3>
<p>setInterval(执行函数,毫秒)：永久性定时器，每隔一段时间执行一次。</p>
<p>clearInterval(永久性定时器)：清除永久性定时器。</p>
<p>setTimeout(执行函数,毫秒)：一次性定时器，一定时间后执行一次。</p>
<p>clearTimeout(一次性定时器)：清除一次性定时器。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137397572</p>]]></description><guid isPermaLink="false">/archives/32586e9a-59f0-44f5-b91a-93e6823135c6</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:03:48 GMT</pubDate></item><item><title><![CDATA[JavaScript事件]]></title><link>https://wiki.dgfygk.com/archives/a5ed362f-6099-4381-999e-422866e80f63</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%E4%BA%8B%E4%BB%B6&amp;url=/archives/a5ed362f-6099-4381-999e-422866e80f63" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>1、JavaScript 事件的概念</strong></h3>
<p><strong>事件就是发生在 HTML 元素上的"事情”</strong>。例如这个 div 标签被用户点击（click 事件），或者是浏览器已经完成页面加载（load 事件），又或者鼠标移动到某个 HTML 元素上（mouseover 事件）等等，当这些事件触发时，我们要让程序去做一些操作，这些操作被称为<strong>事件的响应</strong>。</p>
<h3><strong>2、JavaScript 事件的绑定</strong></h3>
<p>常用的绑定方式有三种：</p>
<p><strong>通过on 在DOM 元素中直接绑定</strong></p>
<pre><code class="javascript">&lt;button  onclick="fn()"&gt;按钮&lt;/button&gt;
&lt;script&gt;
function fn(){
 alert('事件响应');
}
&lt;/script&gt;
</code></pre>
<p><strong>在</strong> <strong>JavaScript</strong> <strong>代码中绑定</strong></p>
<pre><code class="javascript">&lt;button  id="isButton"&gt;按钮&lt;/button&gt;
&lt;script&gt;
let  isButton  =  document.getElementById('isButton'); 
isButton.onclick  =  function  ()  {
alert('事件响应');
}
&lt;/script&gt;
</code></pre>
<h4><strong>以上方式不能重复绑定相同事件，且事件前需要加上 on 关键字。</strong></h4>
<p><strong>绑定事件监听函数</strong></p>
<p>语法为：元素.addEventListener(事件名,执行程序,true/false)</p>
<pre><code class="javascript">&lt;button  id="isButton"&gt;按钮&lt;/button&gt;
&lt;script&gt;
let  isButton  =  document.getElementById('isButton'); 

isButton.addEventListener('click',function()  {
alert('事件响应');
})
&lt;/script&gt;
</code></pre>
<p>此方法可以重复绑定相同的事件，或是绑定多个事件，并且<strong>不需要加 on 前缀。</strong></p>
<h3><strong>3、JavaScript 事件流</strong></h3>
<p>页面触发一个事件时，会按照一定的顺序来响应事件，事件的响应过程为事件流。</p>
<p>浏览器规定了两种事件流模型：<strong>捕获型事件流</strong> 和<strong>冒泡型事件流</strong>。</p>
<p>而 DOM 标准规定事件流包括三个阶段：<strong>事件捕获阶段</strong> 、<strong>处于目标阶段</strong> 和<strong>事件冒泡阶段</strong>。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F90271d27bf1bc76f46c0b5d590385fd5.png&amp;size=m" alt=""></p>
<p>eg：</p>
<pre><code class="javascript">&lt;script&gt;
let  nameList  =  document.getElementById("list"); 

nameList.addEventListener('click',showName,false);

function  showName(e){

e  =  e  ||  window.event;

let  targetElement  =  e.target  ||  e.srcElement; 

if(targetElement.nodeName.toLowerCase()  ===  "li"){

alert(targetElement.innerHTML);
}
}
&lt;/script&gt;
</code></pre>
<p>e 表示 event 是事件对象模型，<strong>e \|\| window.event</strong> 与 <strong>e.target \|\| e.srcElement</strong> 是兼容 IE 浏览器的写法。<strong>toLowerCase()</strong>是将字符串全部转换成小写。<strong>toUpperCase()</strong>是转换为大写。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137289276</p>]]></description><guid isPermaLink="false">/archives/a5ed362f-6099-4381-999e-422866e80f63</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 02:03:29 GMT</pubDate></item><item><title><![CDATA[jQuery改变元素]]></title><link>https://wiki.dgfygk.com/archives/f9224fbc-a95e-413c-963e-9d83b66644fd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=jQuery%E6%94%B9%E5%8F%98%E5%85%83%E7%B4%A0&amp;url=/archives/f9224fbc-a95e-413c-963e-9d83b66644fd" width="1" height="1" alt="" style="opacity:0;">
<p>1、获取HTML文本</p>
<p><strong>$('标签').text()</strong>例：console.log($('p').text());</p>
<p>2、改变HTML文本</p>
<p><strong>$('选择器').text('文本')</strong> 例：console.log($('.c2').text('啦啦啦啦啦'));</p>
<p>新文本会覆盖原来的内容,如果设置的文本中包含标签,是不会把这个标签解析出来的。</p>
<p>3、获取CSS样式</p>
<p><strong>$('选择器').css('属性')</strong>例：console.log($('.c2').css('width'))；</p>
<p>获取包含多个dom元素的jQuery对象的样式,他只会获取到第一个dom对象的样式</p>
<p>4、改变CSS样式</p>
<p><strong>$(“选择器”).css(“属性”,“属性值”)</strong> 例$(“#test1”).css(“border”,“3px solid red”)</p>
<p><strong>$('.c2').css({ 'width':'300px'; 'height':'300px'; })</strong></p>
<p>设置的样式是行内样式</p>
<p>5、CSS方法返回值</p>
<p>css方法有返回值,返回值就是<strong>设置这个方法的元素本身</strong></p>
<p>例:$(“.c1”).css(“opacity”,1).siblings(“.c2”).css(“opacity”,0.4)</p>
<p>6、class类使用</p>
<p><strong>切换类 toggleClass(类名);</strong>例:$(“#div1”).toggleClass(“lei1”);注意:如果元素有某个类就移除这</p>
<p>个类,如果元素没有这个类就添加这个类</p>
<p><strong>判断类 hasClass(类名);</strong></p>
<p>例:console.log($(“#div1”).hasClass(“lei1”));注意:判断一个元素有没有某个类,有返回true,没有返回false
 <br>
 <strong>移除类 removeClass(类名1 类名2);</strong>例:$(“#div1”).removeClass(“lei1 lei2”); 注意：若不给参数</p>
<p>时，会移除所有类集/属性。 例：$(“#div1”).addClass()</p>
<p><strong>添加类: addClass(类名1 类名2);</strong>例:$(“#div1”).addClass(“lei1 lei2”);</p>
<p>7、获取或设置表单内容</p>
<p>val()方法<strong>不给参数就是获取</strong>例:console.log($(“#div”).val());</p>
<p>val()方法 <strong>给参数就是设置</strong>例：$(“#div”).val(“我是设置的值”);</p>
<p>8、jQuery操作属性
 <br>
 <strong>attr()方法</strong></p>
<p><strong>设置属性:</strong>选择器.attr(“属性”,“属性值”);$(“img”).attr(“src”,“992.gif”);</p>
<p>注:元素有这个属性时会修改当前的属性值,若没有会添加新属性和属性值</p>
<p>attr()方法<strong>可以设置多个属性</strong>例:$(“img”).attr({ src:“992.gif”, aaa:“hahaha”});</p>
<p>获取属性:选择器.attr(“属性名”);console.log($(“img”).attr(“src”));</p>
<p>注:若没有这个属性,获取到的是undefined;</p>
<p><strong>removeAttr()方法</strong></p>
<p>移除属性:选择器.removeAttr(“属性名”);例:$(“img”).removeAttr(“src”);</p>
<p>注:此方法<strong>也可以移除多个属性,</strong>例:$(“img”).removeAttr(“src aaa bbb”);</p>
<p>ヾ(￣▽￣)Bye\~Bye\~</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137916555</p>]]></description><guid isPermaLink="false">/archives/f9224fbc-a95e-413c-963e-9d83b66644fd</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:59:31 GMT</pubDate></item><item><title><![CDATA[jQuery实现淡入淡出]]></title><link>https://wiki.dgfygk.com/archives/92fd4a59-7deb-4a01-bd2f-c5ba7ac467b4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=jQuery%E5%AE%9E%E7%8E%B0%E6%B7%A1%E5%85%A5%E6%B7%A1%E5%87%BA&amp;url=/archives/92fd4a59-7deb-4a01-bd2f-c5ba7ac467b4" width="1" height="1" alt="" style="opacity:0;">
<p>淡入淡出可以让我们的网页更加美观，更加丰富，今天来看一下淡入淡出效果。</p>
<pre><code class="javascript">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;title&gt;&lt;/title&gt;
        &lt;script src="./js/jquery-3.4.1.js"&gt;&lt;/script&gt;
        &lt;style&gt;
            #one{
                width: 100px;
                height: 100px;
                background-color: lightblue;
                display: none;
                position: absolute;
                top: 50px;
            }
            #two{
                width: 100px;
                height: 100px;
                background-color: hotpink;
                display: none;
                position: absolute;
                top: 150px;
            }
            #three{
                width: 100px;
                height: 100px;
                background-color: yellow;
                display: none;
                position: absolute;
                top: 250px;
            }
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;button id="but1"&gt;点击淡入&lt;/button&gt;
        &lt;button id="but2"&gt;点击淡出&lt;/button&gt;
        &lt;div id="one"&gt;&lt;/div&gt;
        &lt;div id="two"&gt;&lt;/div&gt;
        &lt;div id="three"&gt;&lt;/div&gt;
        &lt;button id="but3" &gt;消失&lt;/button&gt;
        &lt;div id="four" style="margin-left: 140px;"&gt;拜拜啦~&lt;/div&gt;
    &lt;/body&gt;
    &lt;script type="text/javascript"&gt;
        $(document).ready(function(){
            $("#but1").click(function(){
                $("#one").fadeIn(1000);
                $("#two").fadeIn(1200);
                $("#three").fadeIn(1400);
                $("#one").animate({left:'250px'},1000);
                $("#two").animate({left:'150px'},800);
                $("#three").animate({left:'50px'},700);
            })
        });
        $(document).ready(function(){
            $("#but2").click(function(){
                $("#one").animate({left:'0px'},1000);
                $("#two").animate({left:'0px'},800);
                $("#three").animate({left:'0px'},700);
                $("#three").fadeOut(1000);
                $("#two").fadeOut(3000);
                $("#one").fadeOut(5000);

            })
        })
        $(document).ready(function(){
            $("#but3").click(function(){
                $("#four").hide(2000,function(){
                    alert("再见了，妈妈我今晚，就要远航")
                })
            })
        })

    &lt;/script&gt;
&lt;/html&gt;
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137649990</p>]]></description><guid isPermaLink="false">/archives/92fd4a59-7deb-4a01-bd2f-c5ba7ac467b4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:59:16 GMT</pubDate></item><item><title><![CDATA[JS九宫格抽奖]]></title><link>https://wiki.dgfygk.com/archives/fca86e47-87b4-4779-91e8-b5a445f6eb42</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E4%B9%9D%E5%AE%AB%E6%A0%BC%E6%8A%BD%E5%A5%96&amp;url=/archives/fca86e47-87b4-4779-91e8-b5a445f6eb42" width="1" height="1" alt="" style="opacity:0;">
<p>制作九宫格抽奖是一个很有意思的过程，废话不多说，直接上代码，看看你能抽到什么。</p>
<p>html部分</p>
<pre><code class="html">&lt;body&gt;
        &lt;div id="all"&gt;
            &lt;div class="one"&gt;M4A1-雷神&lt;/div&gt;
            &lt;div class="two" style="font-size: 12px;"&gt;SCAR Light-白虎&lt;/div&gt;
            &lt;div class="three"&gt;M200-幻神&lt;/div&gt;
            &lt;div class="four" style="font-size: 14px;"&gt;大礼包（一折）&lt;/div&gt;
            &lt;div class="five"&gt;98K-星神&lt;/div&gt;
            &lt;div class="six"&gt;谢谢惠顾&lt;/div&gt;
            &lt;div class="seven"&gt;王者-葵&lt;/div&gt;
            &lt;div class="eight"&gt;再来一次&lt;/div&gt;
            &lt;button id="button"&gt;开始抽奖&lt;/button&gt;
        &lt;/div&gt;
&lt;/body&gt;
</code></pre>
<p>css部分</p>
<pre><code class="html">&lt;style&gt;
            #all {
                width: 360px;
                height: 360px;
                margin: auto;
                position: relative;
                text-align: center;
                line-height: 100px;
            }

            .one {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 10px;
                top: 10px;
            }

            .two {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 120px;
                top: 10px;
            }

            .three {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 230px;
                top: 10px;
            }

            .four {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 230px;
                top: 120px;
            }

            .five {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 230px;
                top: 230px;
            }

            .six {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 120px;
                top: 230px;
            }

            .seven {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 10px;
                top: 230px;
            }

            .eight {
                background-color: aquamarine;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 10px;
                top: 120px;
            }

            #button {
                display: block;
                background-color: cadetblue;
                width: 100px;
                height: 100px;
                position: absolute;
                left: 120px;
                top: 120px;
                cursor: pointer;
            }
        &lt;/style&gt;
</code></pre>
<p>js部分</p>
<pre><code class="javascript">&lt;script&gt;
        let button = document.getElementById('button');//获取按钮
        let all = document.getElementById('all'); //获取所有盒子
        let prize = all.getElementsByTagName('div');//获取奖品
        console.log(prize);
        let i = 0;//声明一个变量 对应奖品
        let speed = 0;//声明一个变量对应圈数
        let time = 300;//声明时间变量
        let inter;// 声明一个变量，把定时器赋给这个变量
        let num;//声明一个变量，获得一个随机数组
        button.onclick = asb; //给转动函数赋一个名字，为了不能重复点击
        //点击
        function asb() {
            prize[i].style.background = 'blue';
            inter = setInterval(rotates, time);
            num = Math.floor(Math.random() * prize.length);
            console.log(num);
            button.onclick = null;
        }
        //转动颜色变化
        function rotates() {
            if (i &lt; prize.length - 1) {
                i++;
                prize[i].style.backgroundColor = 'blue';
                prize[i - 1].style.backgroundColor = 'aquamarine';
            } else {
                i = 0;
                prize[i].style.backgroundColor = 'blue';
                prize[prize.length - 1].style.backgroundColor = 'aquamarine';
                speed++;
            }
            //速度变化设置
            if (speed &lt; 2) {
                time -= 20;
                if (time &lt; 100) {
                    time = 100;
                }
            } else if (speed &gt; 4) {
                time += 50;
                if (time &gt; 500) {
                    time = 500;
                }
            }
            //判定停止
            if (i == num &amp;&amp; speed &gt; 7) {
                clearInterval(inter);
                //设置一次性定时器，用来设置弹窗和恢复数据
                setTimeout(function() {
                    alert("恭喜您获得" + prize[i].innerHTML);
                    //重置数据
                    prize[i].style.backgroundColor = 'aquamarine';
                    i = 0;
                    time = 100;
                    speed = 0;
                    num = 0;
                    button.onclick = asb;
                }, 400)
            } else {
                //重置定时器
                clearInterval(inter);
                inter = setInterval(rotates, time);
            }
        }
    &lt;/script&gt;
</code></pre>
<p>ヾ(￣▽￣)Bye\~Bye\~</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137832535</p>]]></description><guid isPermaLink="false">/archives/fca86e47-87b4-4779-91e8-b5a445f6eb42</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:58:31 GMT</pubDate></item><item><title><![CDATA[JS实现表格跨行变色]]></title><link>https://wiki.dgfygk.com/archives/8c6c6261-48b9-4963-8899-dd000a021425</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E5%AE%9E%E7%8E%B0%E8%A1%A8%E6%A0%BC%E8%B7%A8%E8%A1%8C%E5%8F%98%E8%89%B2&amp;url=/archives/8c6c6261-48b9-4963-8899-dd000a021425" width="1" height="1" alt="" style="opacity:0;">
<p>表格的跨行换色主要用到了JS的函数以及for循环，今天以表格的增加，删除，隔行换色来给大家演示一下。</p>
<pre><code class="javascript">    &lt;body&gt;
        &lt;div&gt;
            &lt;table border="1px" id="tabled"&gt;
                &lt;thead&gt;
                    &lt;tr&gt;
                        &lt;td&gt;序号&lt;/td&gt;
                        &lt;td&gt;内容&lt;/td&gt;
                        &lt;td&gt;操作&lt;/td&gt;
                    &lt;/tr&gt;
                &lt;/thead&gt;
                &lt;tbody&gt;
                    &lt;tr&gt;
                        &lt;td&gt;1&lt;/td&gt;
                        &lt;td&gt;DATA&lt;/td&gt;
                        &lt;td&gt;&lt;button id="button_One"&gt;删除&lt;/button&gt;&lt;/td&gt;
                    &lt;/tr&gt;
                &lt;/tbody&gt;
            &lt;/table&gt;
        &lt;/div&gt;

        &lt;button id="button_Two"&gt;添加&lt;/button&gt;
    &lt;/body&gt;

    &lt;script&gt;

        let thead_thead = document.getElementsByTagName('thead')[0];   //获取thead
        let tbody = document.getElementsByTagName('tbody')[0];   //获取tbody
        let but = document.getElementById('button_Two');    //获取'添加'按钮
        let content = 100;     //设定初始内容框为100
          //绑定一个函数
        but.onclick = function() {       
            let thead_tr = thead_thead.getElementsByTagName('tr')[0];   //获取tr
            let thsss = thead_tr.children;         //获取tr中得子元素
            let new_tr = document.createElement('tr');   //新建一个tr
            //利用for循环添加每个单元格得内容
            for (let i = 0; i &lt; thsss.length; i++) {
                let new_td = document.createElement('td');   //新建一个td
                let tbody_tr = tbody.children;       //获取tbody得子元素
                if (i == 0) {
                    new_td.innerHTML = tbody_tr.length + 1;
                }
                if (i == 1) {
                    new_td.innerHTML = content;   //新的td会+100
                    content += 100;             
                }
                if (i == thsss.length - 1) {
                    new_td.innerHTML = '&lt;button&gt;删除&lt;/button&gt;'
                }
                new_tr.appendChild(new_td);        //将新建的单元格输入到新的tr中
            }
            tbody.appendChild(new_tr);    //将新建的tr输入到tbody中完成添加
            color();
        }

        function color() {
            let tr_s = tbody.children;    //获取tbody的子元素
            //利用for循环更改序号的值 以及颜色
            for (let i = 0; i &lt; tr_s.length; i++) {
                tr_s[i].children[0].innerHTML = i + 1;
                if (i % 2 == 0) {          //区分单数行和双数行
                    tr_s[i].style.backgroundColor = "";
                } else{
                    tr_s[i].style.backgroundColor = "blue";
                }
            }
        }

        tbody.addEventListener('click', function(sr) {
            sr = sr || window.event; 
            let targetElement = sr.target || sr.srcElement;
            if (targetElement.innerHTML == "删除") {
                let capture = targetElement.parentElement.parentElement;   //获取得到的元素，寻找其父级的父级，在这时指的是tr
                tbody.removeChild(capture);   //进行删除
                color();
            }
        }
        )
&lt;/script&gt;
</code></pre>
<p>下面是效果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F725ad4e95da61d6cc67cc45fa3420a65.png&amp;size=m" alt=""></p>
<p>谢谢大家观看，欢迎评论区讨论。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137285549</p>]]></description><guid isPermaLink="false">/archives/8c6c6261-48b9-4963-8899-dd000a021425</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:46:12 GMT</pubDate></item><item><title><![CDATA[js中批量修改对象属性]]></title><link>https://wiki.dgfygk.com/archives/412cb25a-eeff-4317-8595-997d40695526</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=js%E4%B8%AD%E6%89%B9%E9%87%8F%E4%BF%AE%E6%94%B9%E5%AF%B9%E8%B1%A1%E5%B1%9E%E6%80%A7&amp;url=/archives/412cb25a-eeff-4317-8595-997d40695526" width="1" height="1" alt="" style="opacity:0;">
<p>首先，有这个对象</p>
<pre><code class="hljs">let a = {
    id: 1,
    name: '张三',
    age: 18,
    sex: 0
}
</code></pre>
<p>需求：同时修改<code>name</code>,<code>id</code>,并添加一个新属性<code>c</code></p>
<p>常规写法：</p>
<pre><code class="hljs">a.id = 2;
a.name = '李四';
a.c = 1;
</code></pre>
<h4>方法1：使用<code>Object.assign()</code></h4>
<p>Object.assign()常用来拷贝合并对象,相同属性替换，不同属性新增
 <br>
 写法：</p>
<pre><code class="hljs">a = Object.assign(a,{id:2,name:'李四',c:1})
</code></pre>
<h4>方法2：使用<code>Object.defineProperties()</code></h4>
<p>Object.defineProperties(object,descriptors)参数说明如下：</p>
<pre><code class="hljs">object：对其添加或修改属性的对象，可以是本地对象或 DOM 对象。
descriptors：包含一个或多个描述符对象，每个描述符对象描述一个数据属性或访问器属性。
</code></pre>
<pre><code>```hljs
Object.defineProperties(a, {
    id: {
        value: 2
    },
    name: {
        value: '李四'
    },
    c:{
        value: 1
    }
})
</code></pre>
<p>而且这两种写法也不会使<a href="https://so.csdn.net/so/search?q=vue&amp;spm=1001.2101.3001.7020" title="vue">vue</a>的reactive失去响应</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137979232</p>]]></description><guid isPermaLink="false">/archives/412cb25a-eeff-4317-8595-997d40695526</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:45:46 GMT</pubDate></item><item><title><![CDATA[快速了解AJAX]]></title><link>https://wiki.dgfygk.com/archives/db286876-91b6-4a85-af55-5f644cb8e1b8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BF%AB%E9%80%9F%E4%BA%86%E8%A7%A3AJAX&amp;url=/archives/db286876-91b6-4a85-af55-5f644cb8e1b8" width="1" height="1" alt="" style="opacity:0;">
<h3>1、AJAX的概念</h3>
<h6>ajax 的全称 Asynchronous JavaScript and XML (异步 JavaScript 和 XML)。</h6>
<p><strong>ajax</strong> <strong>是一种创建交互式网页应用的网页开发技术</strong>。其中最核心的依赖是浏览器提供的</p>
<p>XMLHttpRequest 对象，是这个对象使得浏览器可以发出 HTTP 请求与接收 HTTP 响应。实现了在</p>
<p>页面不刷新的情况下和服务器进行交互。</p>
<h3>2、AJAX的使用方法</h3>
<h6>1.创建 XMLHttpRequest 对象</h6>
<h6>2.使用 open 方法指定要请求的地址、类型和方式。</h6>
<h6>3.使用 send 方法发送请求，如果需要传参：</h6>
<h6>get 方式要将参数拼接到 URL 后边，参数间\&amp;分割。例：'index.php?name=张三\&amp;age=18'</h6>
<h6>post 方式可以将参数放到 send()里。例：send('name=张三\&amp;age=18'</h6>
<h6>4.绑定 onreadystatechange 事件，判断 readyState 和 status 的状态。</h6>
<h6>5.接收数据，通过 json 转换使用</h6>
<pre><code class="javascript">let  xhr  =  new  XMLHttpRequest(); 

xhr.open('get','js/index.json',true); 

xhr.send();

xhr.onreadystatechange  =  function()  {

if  (xhr.readyState  ==  4  &amp;&amp;  xhr.status  ==  200)  {

let  text  =  xhr.responseText; console.log(text);

let  data  =  JSON.parse(text); console.log(data);

}

};
</code></pre>
<h4>get 与post 的区别：</h4>
<p>1.get 是将参数包含在 URL 中明文传输，不安全。而 post 是通过 request body 传递参数， 对于用户来说是不可见的，所有<strong>更安全</strong>。</p>
<p>2.get 传输数据量较小，因为 URL 有长度限制，post 传输的数据量较大，一般被默认为不受限制。</p>
<p>3.对于参数的数据类型，get 只接受 ASCII 字符，而 post 没有限制。</p>
<p>4.get 请求时只会产生一个 TCP 数据包，get 比 post 更快。</p>
<p>5.get 请求参数会被完整保留在浏览器历史记录里，而 post 中的参数不会被保留。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137523263</p>]]></description><guid isPermaLink="false">/archives/db286876-91b6-4a85-af55-5f644cb8e1b8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:45:21 GMT</pubDate></item><item><title><![CDATA[认识jQuery]]></title><link>https://wiki.dgfygk.com/archives/f45b9099-1458-472b-8756-a4bbe0701e6a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%A4%E8%AF%86jQuery&amp;url=/archives/f45b9099-1458-472b-8756-a4bbe0701e6a" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>什么是jQuery</strong></h3>
<p>jQuery就是一个js库，使用jQuery的话，会比使用JavaScript更简单。</p>
<h4>1.1$和jQuery</h4>
<p>jQuery文件就是一个<strong>自执行函数</strong>，也就是自执行文件，这个自执行文件就是给window对象添加一个<strong>jQuery属性</strong>和<strong>$属性（</strong> $和jQuery是等价的，$是一个函数。<strong>）</strong>。</p>
<h5>1.1.1$参数传递不同，效果也不同：</h5>
<p>如果参数传递的是一个匿名函数，那$就是一个入口函数。 例：$(function() { });</p>
<p>如果参数传递的是一个字符串，那$就是一个选择器或是创建标签。</p>
<p>例：$(“#test1”)</p>
<p>例：$('</p>
<div>略略略</div>
')
<p></p>
<p>如果参数是一个dom对象，那$会把dom对象转化为jQuery对象。</p>
<h5>1.1.2jQuery使用方法</h5>
<p><strong>1.引入jQuery文件</strong></p>
<p><strong>本地引入：</strong>从官网下载jQuery文件，放到js文件中，再进行引用。</p>
<p>jquery.js：未压缩版本（开发版本），代码可读性高，推荐在开发和学习阶段使用，方便查看源代码。</p>
<p>jquery.min.js：压缩版本，去除了注释、换行、空格、并且将一些变量替换成了a,b,c之类的简单字符，基本没有可读性，推荐在项目生产环境使用，因为文件较小，减少网络压力。</p>
<p><strong>在线引用：</strong>引入在线jQuery文件。</p>
<p>例：
 <script src="https://wiki.dgfygk.com/%E2%80%9Cjquery-1.11.1.js%E2%80%9D"></script></p>
<h5>2.jQuery入口函数</h5>
<p><strong>方法一：</strong>$(document).ready(function () { });</p>
<p><strong>方法二：</strong>$(function() { });</p>
<p>jQuery入口函数可以<strong>有多个</strong>，并且不会发生覆盖的情况。jQuery的入口函数只会等待文档树加载完成就开始执行，并不会等待图片、文件的加载。</p>
<h5>3.功能实现</h5>
<p>例：$(“#btnShowDiv”).click(function () {</p>
<p>$(“div”).show(1000);</p>
<p>});</p>
<h5>4.jQuery特性</h5>
<p><strong>隐式迭代：</strong>jQuery的隐式迭代会对所有的DOM对象设置相同的值,但是如果我们需要给每一个对象</p>
<p>设置不同的值的时候,就需要自己进行迭代了。</p>
<p><strong>链式编程：</strong>在于一个方法返回的是一个jQuery对象,既然是jQuery对象就可以继续点出jQuery的方法</p>
<p>来(选择器-方法-选择器-方法)。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137651830</p>]]></description><guid isPermaLink="false">/archives/f45b9099-1458-472b-8756-a4bbe0701e6a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:45:10 GMT</pubDate></item><item><title><![CDATA[认识学习JSON]]></title><link>https://wiki.dgfygk.com/archives/8c8a8a7c-093b-4023-a9fd-276626804827</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%A4%E8%AF%86%E5%AD%A6%E4%B9%A0JSON&amp;url=/archives/8c8a8a7c-093b-4023-a9fd-276626804827" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>JSON：</strong></h3>
<p>json 是一种轻量级的数据交换格式。</p>
<p>简单来说：json 就是一种在各个编程语言中流通的数据格式，负责不同编程语言中的数据传递</p>
<p>和交互。</p>
<p>json 在 js 中提供了两种方法：</p>
<p>JSON.parse()：将 JSON 数据转换为对象或数组。</p>
<p>JSON.stringify()：将对象或数组转换为 JSON 数据。</p>
<pre><code class="javascript">let  data  =  {name:'张三',age:18};

 console.log(data);  //  对象

let  str  =  JSON.stringify(data);

 console.log(str);  //  json 数据

let  obj  =  JSON.parse(str) ；

console.log(obj);  //  对象
</code></pre>
<p><strong>JSON</strong> <strong>数据的格式：</strong></p>
<p><strong>{“name”:“</strong><strong>张三”,“age”:18} [“张三”,“李四”,“王五”,“赵六”]</strong></p>
<p><strong>JSON</strong> <strong>数据注意事项：</strong></p>
<p>1、json 是一种<strong>纯字符数据</strong>，不属于任何编程语言。</p>
<p>2、json 的数据可以用花括号 {} 或中括号 [] 包裹，对应 js 中的 object 和 array。</p>
<p>3、json 中键值对的键，必须用<strong>双引号”“</strong>包裹，单引号都不行。</p>
<p>4、json 中键值对的值，不允许出现 <strong>function、undefined、NaN，</strong>但可以有 null。</p>
<p>5、json 数据结束后，不允许出现没有意义的符号，如：<strong>{“name”:“张三”,“age”:18 , }</strong>,注意数据结尾部分 18 后面的逗号，不允许出现。</p>
<p>json 除了作为不同编程语言之间通用数据格式以外，还可以将我们的数据转为 json 格式存储</p>
<p>到 localStorage、sessionStorage 或 cookie 中。更方便我们对数据的管理。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137607563</p>]]></description><guid isPermaLink="false">/archives/8c8a8a7c-093b-4023-a9fd-276626804827</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:44:59 GMT</pubDate></item><item><title><![CDATA[JavaScript 函数]]></title><link>https://wiki.dgfygk.com/archives/eb5d7201-634b-40e9-a1a7-21f77867fe26</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%20%E5%87%BD%E6%95%B0&amp;url=/archives/eb5d7201-634b-40e9-a1a7-21f77867fe26" width="1" height="1" alt="" style="opacity:0;">
<h3><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faf5c382cc443334f44254bf91a899d97.jpeg&amp;size=m" alt=""></h3>
<h3><strong>1.JavaScript 函数的概念</strong></h3>
<p>函数：封装一段可被重复调用执行的代码块，通过函数可以实现大量代码的重复使用。函数也是</p>
<p>一种引用数据类型。</p>
<p>简单来说函数就是一个容器，可以封装一些代码， 在需要时再执行这些代码。</p>
<h3>2.JavaScript 函数的使用</h3>
<h4>创建函数（常用）：</h4>
<p><strong>通过 function 关键字声明函数，又叫做 命名函数。</strong></p>
<p>function 函数名() {</p>
<p>// 函数体代码</p>
<p>}</p>
<p>// function 是声明函数的关键字，必须小写</p>
<p>// 函数名 命名为动词形式 例: getSum</p>
<p><strong>通过函数表达式定义函数，又叫做 匿名函数。</strong></p>
<p>let fun = function() {</p>
<p>// 函数体代码</p>
<p>};</p>
<p>// fun 是变量名，不是函数名</p>
<p>// fun 是变量，只不过变量存储的是函数</p>
<p><strong>调用函数：</strong></p>
<p>函数内的代码一般不会自动执行，需要调用这个函数函数名();</p>
<p>函数表达式创建的函数可以通过变量名来调用变量名();</p>
<p><strong>函数的封装：</strong></p>
<p>函数的封装是把一个或者多个功能通过函数的方式封装起来，对外只提供一个简单的函数接口（函数名）。</p>
<p>//例用封装函数计算 1-100 累加和</p>
<p>function getSum() {</p>
<p>let num = 0; // 准备一个变量，保存累加和</p>
<p>for (let i = 1; i &lt;= 100; i++) {</p>
<p>num += i; // 把每个数值 都累加到变量中</p>
<p>}</p>
<p>console.log(num);</p>
<p>}</p>
<p>// 调用函数</p>
<p>getSum();</p>
<p>
 <br>
</p>
<h3>3.JavaScript 函数的参数</h3>
<p>形参：函数定义时候，传递的参数 (实参值会传递给形参,相当于不用声明的变量)。</p>
<p>实参：函数调用时候，传递的参数。</p>
<p>// 带参数的函数声明</p>
<p>function 函数名(形参 1,形参 2,形参 3…) {</p>
<p>//函数体</p>
<p>}</p>
<p>// 带参数的函数调用</p>
<p>函数名(实参 1,实参 2,实参 3…);</p>
<p><strong>函数的返回值：</strong></p>
<p>返回值：函数调用后返回的数据；函数执行完成后可以通过 return 语句将指定数据返回。</p>
<p>// 声明函数</p>
<p>function 函数名() {</p>
<p>// 函数体</p>
<p>return 需要返回的值;</p>
<p>// 函数遇到 return 会停止执行，并返回指定的值</p>
<p>// 如果函数没有 return 返回的值是 undefined</p>
<p>}</p>
<p>// 调用函数</p>
<p>函数名(); //此时调用函数就可以得到函数体内 return 的值</p>
<p><strong>break，continue，return 的区别：</strong></p>
<p>break: 结束当前的循环体 (如 for、while)</p>
<p>continue: 跳出本次循环 继续执行下次循环</p>
<p>return: 不仅可以终止当前函数体内的代码，还能返回 return 语句后面的值。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137051461</p>]]></description><guid isPermaLink="false">/archives/eb5d7201-634b-40e9-a1a7-21f77867fe26</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:44:37 GMT</pubDate></item><item><title><![CDATA[JavaScript变量的特殊情况]]></title><link>https://wiki.dgfygk.com/archives/d81da01a-5061-44a9-9e28-c63058ec9112</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%E5%8F%98%E9%87%8F%E7%9A%84%E7%89%B9%E6%AE%8A%E6%83%85%E5%86%B5&amp;url=/archives/d81da01a-5061-44a9-9e28-c63058ec9112" width="1" height="1" alt="" style="opacity:0;">
<p>let num= 20 ;</p>
<p>function fn(){</p>
<p>num= 10 ; //局部变量来看，强烈不允许的书写</p>
<p>}</p>
<p>fn ();</p>
<p>cosole. log(num);</p>
<p>
 <br>
</p>
<p>function fun(x,y){</p>
<p>//形参可以看做是函数的局部变量</p>
<p>cosole.log(x);</p>
<p>}</p>
<p>fun(1,2);</p>
<p>console.log(x) //错误的</p>
<h4><strong>有一种特殊情况是全局变量是那种?我们提倡吗?</strong></h4>
<p>➢局部变量或者块级变量没有let声明直接赋值的当全局变量看，我们强烈不提倡。</p>
<p>➢还有一种特殊情况，函数内部的形参可以当做局部变量看。</p>
<h4><strong>变量的访问原则</strong></h4>
<p>●只要是代码，就至少有一个作用域</p>
<p>●写在函数内部的局部作用域</p>
<p>●如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域</p>
<p>●访问原则:在能够访问到的情况下先局部，局部没有在找全局</p>
<h3><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0523d76594519940ff1feb8fb4bfce83.jpeg&amp;size=m" alt=""></h3>
<p><strong>案例一：</strong></p>
<p><strong>function f1() { 
  <br>let num= 123 
  <br>function f2() { 
  <br>console.log( num ) 
  <br>} 
  <br>f2() 
  <br>} 
  <br>let num = 456 
  <br>f1()</strong></p>
<p><strong>结果是多少呢？</strong></p>
<p><strong>结果是123。</strong></p>
<h3><strong>案例二：</strong></h3>
<p><strong>let a = 1;</strong></p>
<p><strong>function fn1(){</strong></p>
<p>let a = 2 ;</p>
<p>let b = '22'</p>
<p>fn2()</p>
<p>function fn2 () {</p>
<p>let a = 3;</p>
<p>fn3();</p>
<p>function fn3 (){</p>
<p>let a =4</p>
<p>console.log( ) //a的值</p>
<p>console.log() //b的值</p>
<p>}</p>
<p>}</p>
<p><strong>}</strong></p>
<p>a为4，b为22</p>
<p>谢谢大家阅读，欢迎评论区讨论。</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137121450</p>]]></description><guid isPermaLink="false">/archives/d81da01a-5061-44a9-9e28-c63058ec9112</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:44:10 GMT</pubDate></item><item><title><![CDATA[JavaScript数组]]></title><link>https://wiki.dgfygk.com/archives/21d5abd0-f0f8-429d-a533-36f16fe3fb48</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%E6%95%B0%E7%BB%84&amp;url=/archives/21d5abd0-f0f8-429d-a533-36f16fe3fb48" width="1" height="1" alt="" style="opacity:0;">
<h2><strong>1、JavaScript 数组的概念</strong></h2>
<p><strong>数组指一组数据的集合</strong> ，其中的每个数据被称作元素，在数组中可以存放<strong>任意类型</strong>的元素。数组是一种将一组数据存储在单个变量名下的优雅方式。</p>
<p>数组的优势在于，我们不用再一个数据一个数据进行提交，我们可以对数据进行分类，在数组中输入一组数据，方便快捷，例如，我们需要统计班级学生姓名，不再需要一个一个名字上传，而是采用数组就行提交。</p>
<p>let arr = ['张三','李四','王五','赵六'];</p>
<h2>2、JavaScript如何创建数组</h2>
<h4>1.利用关键词创建数组</h4>
<p>// [] 是可选的，key 表示要存储的值，值与值之间用 , 号隔开</p>
<p>let 数组名 = new Array(key,key,key …. );</p>
<p>// 示例：创建了一个名为 arr 的空数组</p>
<p>let arr = new Array();</p>
<h4>2.利用数组字面量创建数组</h4>
<p>
 <br>
</p>
<p>// 使用数组字面量方式创建数组</p>
<p>let 数组名 = [key,key,key ….. ];</p>
<p>// 数组中可以存放任意类型的数据</p>
<p>let arr = ['fan', true, 17.5];</p>
<h4><strong>3.JavaScript访问数组元素</strong></h4>
<p>访问数组元素要引入一个名词：索引（下标）。索引用来访问数组元素的序号，但是索引从 0 开始。也就是说，我们的第一个元素所对应的下标应该是0，最后一个元素所对应的是总元素个数-1。</p>
<p>// 定义数组</p>
<p>let arr = ['张三','李四','王珅琦','赵六'];</p>
<p>// 获取数组中的第 2 个元素</p>
<p>console.log(arr[1]); // 李四</p>
<p>// 如果访问数组时没有和索引值对应的元素(数组越界)，</p>
<p>console.log(arr[4]);</p>
<p>// 返回值为 undefined</p>
<p>诉述是数组最基本的知识，谢谢大家观看。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137010410</p>]]></description><guid isPermaLink="false">/archives/21d5abd0-f0f8-429d-a533-36f16fe3fb48</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:43:52 GMT</pubDate></item><item><title><![CDATA[JavaScript——作用域]]></title><link>https://wiki.dgfygk.com/archives/e2e25d9d-825f-4654-89af-78715fea8153</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JavaScript%E2%80%94%E2%80%94%E4%BD%9C%E7%94%A8%E5%9F%9F&amp;url=/archives/e2e25d9d-825f-4654-89af-78715fea8153" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>1.JavaScript 作用域的概念</strong></h3>
<p>在JavaScript中，作用域是一个<strong>控制变量、函数和类的作用范围的概念</strong>。 它决定了变量、函数和类的生命周期，以及它们在代码中的可见性。 理解JavaScript中的作用域对于编写高效、可维护的代码至关重要。</p>
<p>ES6 之前作用域有两种 <strong>全局作用域</strong> 和 <strong>局部作用域 (函数作用域)</strong>。</p>
<p><strong>全局作用域</strong>作用于所有代码执行的环境(整个 script 标签的内部)或者一个独立的 js 文件中。</p>
<p><strong>局部作用域</strong> 作用于函数内部的代码环境，就是局部作用域。因为跟函数有关系，所以也被称为<strong>函数作用域</strong>。</p>
<h5><strong>块级作用域：</strong></h5>
<p>块级作用域由 {} 包括</p>
<p>ES6 之前 JS 是没有块级作用域这一概念的，在 if 语句中，使用 var 声明的变量在外部依旧可以使用，如下</p>
<p>if(true){</p>
<p>var num = 155172444; console.log(num); // 155172444</p>
<p>}</p>
<p>console.log(num); // 155172444</p>
<p><strong>在 ES6 中新增块级作用域这一概念，使用 let 声明的变量在外部将不可使用。</strong></p>
<p>if(true){</p>
<p>let num = 10000; console.log(num); // 10000</p>
<p>}</p>
<p>console.log(num); // 报错</p>
<h3><strong>2.JavaScript 变量的作用域</strong></h3>
<p>在 JavaScript 中，根据作用域的不同，变量可以分为两种：<strong>全局变量</strong> 、<strong>局部变量</strong>。</p>
<p><strong>全局变量</strong>在全局作用域下声明的变量（var 在函数外部定义的变量）。</p>
<p><strong>局部变量</strong>在局部作用域下声明的变量（var 在函数内部或 let 在 {} 中定义的变量）。</p>
<p><strong>全局变量和局部变量的生命周期</strong></p>
<p>全局变量：在任何一个地方都可以使用，只有在浏览器关闭时才会销毁，因此比较占内存。</p>
<p>局部变量：旨在代码块内部使用，当其所在的代码块被执行时，才会被初始化；当代码块运 行结束后，就会被销毁，因此更节省内存空间。</p>
<h3><strong>3.JavaScript 作用域链</strong></h3>
<p><strong>作用域链：</strong> 通过上节作用域我们知道，当我们声明一个函数时，程序会生成一个独立的作用域，如果函数中还有函数，那么在这个作用域中就又可以诞生一个作用域；根据 <strong>内部函数可以访问外部函数变量</strong>的这种机制，用链式查找决定哪些数据能被内部函数访问，就称为作用域链。</p>
<p>function f1(){</p>
<p>let num = 123;</p>
<p>function f2() {</p>
<p>console.log( num); // 123</p>
<p>} f2();</p>
<p>}</p>
<p>let num = 456; f1();</p>
<p><strong>注意：</strong><strong>作用域链</strong>采取就近原则的方式来查找变量最终的值。</p>
<h3><strong>4.JavaScript 预解析</strong></h3>
<p><strong>预解析相关概念：</strong> JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步：<strong>预解析</strong>和<strong>代码执行</strong>。</p>
<p><strong>预解析 ：</strong> 在当前作用域下，JS 代码执行之前，浏览器会默认把带有 <strong>var</strong> 和 <strong>function</strong>声明的变量在内存中进行提前声明或定义。</p>
<p><strong>代码执行 ：</strong>从上往下执行 JS 代码。</p>
<p>预解析会把变量和函数的声明在代码执行之前完成，预解析也叫做变量、函数提升。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137075114</p>]]></description><guid isPermaLink="false">/archives/e2e25d9d-825f-4654-89af-78715fea8153</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:43:07 GMT</pubDate></item><item><title><![CDATA[JS-函数封装数组求和案例]]></title><link>https://wiki.dgfygk.com/archives/1f422b58-eea3-4f77-a5a0-d125195a34be</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS-%E5%87%BD%E6%95%B0%E5%B0%81%E8%A3%85%E6%95%B0%E7%BB%84%E6%B1%82%E5%92%8C%E6%A1%88%E4%BE%8B&amp;url=/archives/1f422b58-eea3-4f77-a5a0-d125195a34be" width="1" height="1" alt="" style="opacity:0;">
<p>命名函数的封装可以多次利用，简单便捷，那么如何封装函数呢？今天我们以数组求和案例来看一下如何封装函数。
 <br>
 function array (d){</p>
<p>let sum = 0 ;
 <br>
 for (let i = 0; i &lt; d.length; i++) {</p>
<p>sum += d[i];
 <br>
 }
 <br>
 return sum;
 <br>
 }</p>
<p>let arr2 = [1, 2, 6, 79, 23];</p>
<p>array(arr2);</p>
<p>console.log(array(arr2));</p>
<p>let arr100= [ 1,852,963,4,852,1,5];</p>
<p>array(arr100);</p>
<p>console.log(array(arr100));</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F878f04ca21139a97cb998c13fa84a29b.png&amp;size=m" alt=""></p>
<p>在分装过程中主要需要搞清楚实参、形参和return的用法及含义。</p>
<p>实参：实参：函数调用时候，传递的参数（如上即arr2、arr100）。</p>
<p>形参：函数定义时候，传递的参数 (实参值会传递给形参,相当于不用声明的变量)（如上d）。</p>
<p>return即是函数处理后返回的值。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137067382</p>]]></description><guid isPermaLink="false">/archives/1f422b58-eea3-4f77-a5a0-d125195a34be</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:42:14 GMT</pubDate></item><item><title><![CDATA[JS选项卡如何制作]]></title><link>https://wiki.dgfygk.com/archives/6491a68b-a4db-49c4-8b08-4a8f00b86468</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JS%E9%80%89%E9%A1%B9%E5%8D%A1%E5%A6%82%E4%BD%95%E5%88%B6%E4%BD%9C&amp;url=/archives/6491a68b-a4db-49c4-8b08-4a8f00b86468" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="hljs">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;title&gt;选项卡&lt;/title&gt;
        &lt;style&gt;
            * {
                margin: 0;
                padding: 0;
            }

            button {
                width: 50px;
                height: 50px;
            }

            .but {
                width: 200px;
                height: 80px;
                margin: auto;
            }

            #china {
                width: 500px;
                height: 300px;
                background-color: aquamarine;
                margin: auto;
                text-align: center;
            }

            #math {
                width: 500px;
                height: 300px;
                background-color: chocolate;
                margin: auto;
                text-align: center;
                display: none;
            }

            #english1 {
                width: 500px;
                height: 300px;
                background-color: cornflowerblue;
                margin: auto;
                text-align: center;
                display: none;
            }
        &lt;/style&gt;
    &lt;/head&gt;

    &lt;body&gt;
        &lt;div class="but"&gt;
            &lt;button class="meau-one" onclick="fun1()"&gt; 菜单一&lt;/button&gt;
            &lt;button class="meau-two" onclick="fun2()"&gt; 菜单二&lt;/button&gt;
            &lt;button class="meau-three" onclick="fun4()"&gt; 菜单三&lt;/button&gt;
        &lt;/div&gt;
        &lt;div id="china"&gt;
            语文
        &lt;/div&gt;
        &lt;div id="math"&gt;
            数学
        &lt;/div&gt;
        &lt;div id="english1"&gt;
            英语
        &lt;/div&gt;

    &lt;/body&gt;
    &lt;script&gt;
        let on1 = document.getElementById("china");
        let on2 = document.getElementById("math");
        let on3 = document.getElementById("english1");

        function fun1() {
            on1.style.display = 'block';
            on2.style.display = 'none';
            on3.style.display = 'none';

        }
        function fun2() {
            on1.style.display = 'none';
            on3.style.display = 'none';
            on2.style.display = 'block';
        }
        function fun4() {
            on1.style.display = 'none';
            on2.style.display = 'none';
            on3.style.display = 'block';
        }
    &lt;/script&gt;
&lt;/html&gt;
</code></pre>
<p>上面所述是实现JS选项卡的一种方式，如果大家有其他方法，欢迎评论区讨论。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/137247569</p>]]></description><guid isPermaLink="false">/archives/6491a68b-a4db-49c4-8b08-4a8f00b86468</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:41:56 GMT</pubDate></item><item><title><![CDATA[经典问题分析：JS如何解决猴子吃桃问题]]></title><link>https://wiki.dgfygk.com/archives/cf659f42-b2a3-4a90-b4b4-0be8b635238b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BB%8F%E5%85%B8%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90%EF%BC%9AJS%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E7%8C%B4%E5%AD%90%E5%90%83%E6%A1%83%E9%97%AE%E9%A2%98&amp;url=/archives/cf659f42-b2a3-4a90-b4b4-0be8b635238b" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F89d88bc7949118d653183ce64ed6d944.jpeg&amp;size=m" alt=""></p>
<p><strong>猴子吃桃：一个猴子有一天摘了很多桃子，当天吃了一半，还觉得不过瘾，又多吃了一个，后边每天都吃了剩下桃子的一半多加一个第十天还没有吃的时候发现只剩下一个桃子了，问一开始摘了多少桃子？</strong></p>
<p>首先从数学角度进行分析，我们设一共有x个桃子，第一天吃一半多一个，那么剩下x/2-1个桃子，此时剩下的桃子就是明天的桃子，那么现在我们逆向思维想一下，最后一天也就是第十天，剩下了一个桃子，那么前一天也就是第九天没吃的时候是多少呢？计算之前，我们把公式转换一下，前一天剩的也就是2（x+1），我们把第十天的1个代入，可得出第九天还剩4个，以此类推第八天10个......。</p>
<p>那么我们的代码应该如何书写呢，首先我们要从第十天开始设定，第十天为1个，则可设定 let today = 1；求值（第n天有多少个）：let n=0；共九天：let day=9;</p>
<p>let today = 1;
 <br>
 let n=0 ;
 <br>
 let day=9;
 <br>
 while( day&gt;0）{</p>
<p>n=2*(today+1 );
 <br>
 today = n;（进行赋值）
 <br>
 day--;
 <br>
 }
 <br>
 console.log(n);</p>
<p>结果可得<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4b0dfd7614d8baf251345d65a244d387.png&amp;size=m" alt=""></p>
<p>如果大家有其他看法，欢迎讨论！</p>
<p>原文链接: <a href="https://blog.csdn.net/hexadecimal_001/article/details/136991592">https://blog.csdn.net/hexadecimal_001/article/details/136991592</a></p>]]></description><guid isPermaLink="false">/archives/cf659f42-b2a3-4a90-b4b4-0be8b635238b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:41:33 GMT</pubDate></item><item><title><![CDATA[快速认识JavaScript]]></title><link>https://wiki.dgfygk.com/archives/8d1b0d1f-cc6a-4c91-93af-5298636c181b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BF%AB%E9%80%9F%E8%AE%A4%E8%AF%86JavaScript&amp;url=/archives/8d1b0d1f-cc6a-4c91-93af-5298636c181b" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0c280fb38fe99d3b324f3f61f02278f3.png&amp;size=m" alt=""></p>
<p><strong>1、什么是JavaScript?</strong></p>
<p>JavaScript是一种直译式脚本语言，是一种动态类型、弱类型，基于原型的高级语言。</p>
<p>脚本语言：在程序的运行过程中逐行进行解释执行，不需要预编译。</p>
<p>动态类型：在声明了一个变量之后，我们能够随时改变它的数据类型。</p>
<p>弱类型：变量在使用前不需要做类型声明，而是解释器在运行时检查它的数据类型。</p>
<p>直译式：运行的时候将程序直接编译成机器语言(浏览器中的js引擎)。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7c4d2ba38fd0783f1b4e18ab410655a4.jpeg&amp;size=m" alt=""></p>
<p>如图所示，如果把精美的网页比作是一辆车子，那么HTML主要是构成了车子的框架，CSS赋予了车子颜色和外观，让它更加的漂亮，JavaScript则是这辆车的引擎，离合器和刹车，能够让车子动起来，使其成为一辆动力强劲外观漂亮的车子。</p>
<p><strong>2、JavaScript主要用来做什么以及有怎样的特点？</strong></p>
<p><strong>数据可视化：</strong>Javascript 拥有 ECharts、Dygraphs.js、D3.js 等多种可实现数据可视化效果的框架。</p>
<p><strong>移动端 APP：</strong>JavaScript 通过PhoneGap 打包成APP 发布到各个移动应用市场。</p>
<p><strong>全平台应用：</strong>现在任何一个网页都离不开 JavaScript。并且可以通过响应式设计，还能够实现一份代码构建移动、桌面、Web 能够全平台应用。</p>
<p>此外，JavaScript还可以制作游戏、VR、AR、硬件、物联网、操作系统界面等等。</p>
<p>其特点主要有：</p>
<p><strong>简单性</strong> ：它的变量类型是采用弱类型，并未使用严格的数据类型。</p>
<p><strong>安全性</strong> ：js 不允许直接访问本地的硬盘，而且不能将数据存放到服务器上，不允许对网络文档进行修改和删除，只能通过浏览器实现信息浏览或动态交互，从而有效地防止数据的丢失。</p>
<p><strong>动态性</strong> ：js 可以直接对用户的输入做出响应而无需经过 Web 服务程序。它对用户的响应是采用以事件驱动的方式进行的。</p>
<p><strong>跨平台性</strong> ：js 仅依赖于浏览器本身，与操作系统无关，只要是能运行浏览器的计算机，并支持 js的浏览器就可以正确执行。</p>
<p><strong>3、JavaScript的组成部分</strong></p>
<p><strong><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faaa57f671672771edb26167665a5271f.png&amp;size=m" alt=""></strong></p>
<p>主要由三部分组成：</p>
<p>ECMAScript：简称 ES，它是 JS 的基本语法，也是 JS 的核心。</p>
<p>DOM：（文档对象模型），使 JS 对 HTML 元素具有操作的能力。</p>
<p>BOM：（浏览器对象模型），使 JS 能够获取浏览器信息或操作浏览器。</p>
<p>JS 是前端的核心，主要用来操作 DOM 修改 DOM 结构，从而达到修改页面的效果和目的。我们会先从语法学起，然后使用语法来操作 DOM 和 BOM 。</p>
<p>原文链接: https://blog.csdn.net/hexadecimal_001/article/details/136973921</p>]]></description><guid isPermaLink="false">/archives/8d1b0d1f-cc6a-4c91-93af-5298636c181b</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:39:41 GMT</pubDate></item><item><title><![CDATA[Java三表联查方法]]></title><link>https://wiki.dgfygk.com/archives/71e2cf14-e420-4673-9af2-bb2153de1f2d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%89%E8%A1%A8%E8%81%94%E6%9F%A5%E6%96%B9%E6%B3%95&amp;url=/archives/71e2cf14-e420-4673-9af2-bb2153de1f2d" width="1" height="1" alt="" style="opacity:0;">
<p>有些数据的查询需要我们查询三个表来获得总的数据。</p>
<h3><strong>一、准备工作：</strong></h3>
<p>我前面写过两表联查的方式，三表联查就是在两表联查的基础上进行的，下面是我写的两表联查博客</p>
<p>三表查询和两表查询的区别就只是在配置文件里的映射文件不一样，其余不管是调用方式还底层逻辑都是一样的。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F01dae0dabb784fe59f971b66901e0263.png&amp;size=m" alt=""></p>
<p>所以，我们就更改了三表联查的映射文件。</p>
<pre><code class="XML">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE mapper PUBLIC
        "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;
&lt;mapper namespace="org.dao.oneselMapper"&gt;
&lt;!-- 主表字段映射--&gt;
    &lt;resultMap id="sanbiaoMapper" type="org.Emp.students"&gt;
        &lt;id column="sno" property="sno"&gt;&lt;/id&gt;
        &lt;result column="sname" property="sname"&gt;&lt;/result&gt;
        &lt;result column="ssex" property="ssex"&gt;&lt;/result&gt;
        &lt;result column="sbirthday" property="sbirthday"&gt;&lt;/result&gt;
        &lt;result column="classZ" property="classZ"&gt;&lt;/result&gt;
&lt;!--副表字段映射--&gt;
        &lt;collection property="scores" ofType="org.Emp.scores"&gt;
            &lt;id column="sno" property="sno"&gt;&lt;/id&gt;
            &lt;id column="cno" property="cno"&gt;&lt;/id&gt;
            &lt;result column="degree" property="degree"&gt;&lt;/result&gt;
            &lt;!--副表字段映射--&gt;
            &lt;association property="courses" javaType="org.Emp.courses"&gt;
                &lt;id column="cno" property="cno"&gt;&lt;/id&gt;
                &lt;result column="cname" property="cname"&gt;&lt;/result&gt;
                &lt;result column="tno" property="tno"&gt;&lt;/result&gt;
            &lt;/association&gt;
        &lt;/collection&gt;
    &lt;/resultMap&gt;

    &lt;!--数据库的三表查询--&gt;
    &lt;select id="sanbiaoall" resultMap="sanbiaoMapper"&gt;
        select students.*,courses.*,scores.*  from students,courses,scores
                                              where courses.cno=scores.cno and scores.sno=students.sno
    &lt;/select&gt;

&lt;/mapper&gt;
</code></pre>
<p>只要对这个映射文件进行调用即可，写映射文件的接口</p>
<pre><code class="java">public interface oneselMapper {
    List&lt;students&gt; sanbiaoall();}
</code></pre>
<p>最后是在主方法里调用：</p>
<pre><code class="java">package org.example;

import org.Emp.order;
import org.Emp.students;
import org.Emp.user;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.PropertyConfigurator;
import org.dao.oneselMapper;

import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

public class testuser {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;

    @Test
    public void star1() throws IOException {
        //配置日志信息
        Properties properties = new Properties();
        FileInputStream fileInputStream = new FileInputStream("src/main/resources/config/log4j.properties");
        properties.load(fileInputStream);
        PropertyConfigurator.configure(properties);
        // 加载 MyBatis 配置文件
        //从类路径中获取 MyBatis 配置文件 mybatis-config.xml 的输入流。
        in = Resources.getResourceAsStream("config/mybatis-config.xml");
        //创建 SqlSessionFactory 对象，它是 MyBatis 的工厂类，用于创建 SqlSession。
        factory = new SqlSessionFactoryBuilder().build(in);
        //创建一个新的 SqlSession 实例，用于执行 SQL 语句。
        session = factory.openSession();
        //通过 SqlSession 获取 oneselMapper 接口的代理实现。
        oneselMapper mapper = session.getMapper(oneselMapper.class);
        //接口中定义了一个名为 sanbiaoall 的方法，该方法返回一个 List&lt;students&gt; 类型的列表。
        List&lt;students&gt; sanbiaoall = mapper.sanbiaoall();
        for (students a : sanbiaoall) {
            System.out.println(a);
        }
    }

    public static void main(String[] args) throws IOException {
        testuser testuser = new testuser();
        testuser.star1();
    }
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/141164558</p>]]></description><guid isPermaLink="false">/archives/71e2cf14-e420-4673-9af2-bb2153de1f2d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:26:46 GMT</pubDate></item><item><title><![CDATA[JAVA算法数据结构第一节稀疏矩阵]]></title><link>https://wiki.dgfygk.com/archives/ab1c24f6-fb1d-4ea8-8d6d-892dd7e229e1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JAVA%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AC%AC%E4%B8%80%E8%8A%82%E7%A8%80%E7%96%8F%E7%9F%A9%E9%98%B5&amp;url=/archives/ab1c24f6-fb1d-4ea8-8d6d-892dd7e229e1" width="1" height="1" alt="" style="opacity:0;">
<h3>一、稀疏矩阵介绍：</h3>
<p>稀疏矩阵是一种特殊类型的矩阵，其中大部分元素都是零。在处理这类矩阵时，如果仍然使用标准的矩阵存储方式（即传统的二维数组），则会浪费大量的存储空间来保存零值。为了提高存储效率以及在某些情况下提高计算效率，人们发明了稀疏矩阵的概念及其相应的存储方法。</p>
<h4>稀疏矩阵的特点</h4>
<ul>
 <li><strong>高零值比例</strong>：矩阵中的零元素远多于非零元素。</li>
 <li><strong>节省空间</strong>：只存储非零元素，减少不必要的内存占用。</li>
 <li><strong>优化运算</strong>：在进行矩阵运算时，可以跳过与零相关的计算，从而加快计算速度。</li>
</ul>
<h3>二、稀疏矩阵代码实现：</h3>
<h4>1）原始二维代码：</h4>
<pre><code class="java">  //创建一个原始的二维数组9*9
        //0：表示没有棋子，1：表示黑子，2：表示白子
        int[][] ints = new int[9][9];

        ints[1][2]=1;
        ints[2][4]=2;

        //输出原始二维数组
        for (int[] row:ints){
            for (int data:row){
                System.out.print(data+"\t");
            }
            System.out.println();
        }
</code></pre>
<p>输出结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F807083ec3f3c4b419f619bde2c19b621.png&amp;size=m" alt=""></p>
<p>创建如图9*9的二维矩阵。</p>
<h4>2）、将二维矩阵转化成稀疏矩阵：</h4>
<pre><code class="java">  //创建对应稀疏矩阵
        int[][] xishu = new int[sum + 1][3];
        //给稀疏矩阵赋值
        xishu[0][0]=9;
        xishu[0][1]=9;
        xishu[0][2]=sum;
      //遍历二维数组，将非零值存放到xishu中
        int count=0;//用于记录是第几个非零数据
        for (int i=0;i&lt;9;i++){
            for (int j=0;j&lt;9;j++){
                if (ints[i][j]!=0){
                   count++;
                   xishu[count][0]=i;
                    xishu[count][1]=j;
                    xishu[count][2]=ints[i][j];
                }
            }
        }
      //输出稀疏数组的形式
        System.out.println("----------稀疏数组为---------");
        for (int i=0;i &lt; xishu.length;i++){
            System.out.println(xishu[i][0]+"  "+xishu[i][1]+"  "+xishu[i][2]);
        }
</code></pre>
<p>输出结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2d05747caa654bfdb322e49ed658b60c.png&amp;size=m" alt=""></p>
<h4>3）、将稀疏矩阵转化为 二维矩阵：</h4>
<pre><code class="java">   //读取稀疏数组第一行，根据第一行创建原始的二维数组
        int[][] huifus = new int[xishu[0][0]][xishu[0][1]];
      //读取稀疏数组后几行的数据并赋给原始二维数组即可
        //这里是要遍历稀疏矩阵的值而不是恢复矩阵
        for (int k=1;k&lt; xishu.length;k++){
        huifus[xishu[k][0]][xishu[k][1]]=xishu[k][2];
        }
        System.out.println("---------恢复的二维矩阵-------");
        for (int[] row:huifus){
            for (int data:row){
                System.out.print(data+"\t");
            }
            System.out.println();
        }
</code></pre>
<p>输出结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff6ebe8d3d7f24c1d9812c82e3bb3438a.png&amp;size=m" alt=""></p>
<h3>三、完整代码：</h3>
<pre><code class="java">//TIP 要&lt;b&gt;运行&lt;/b&gt;代码，请按 &lt;shortcut actionId="Run"/&gt; 或
// 点击装订区域中的 &lt;icon src="AllIcons.Actions.Execute"/&gt; 图标。
public class Main {
    public static void main(String[] args) {
        //创建一个原始的二维数组9*9
        //0：表示没有棋子，1：表示黑子，2：表示白子
        int[][] ints = new int[9][9];

        ints[1][2]=1;
        ints[2][4]=2;

        //输出原始二维数组
        for (int[] row:ints){
            for (int data:row){
                System.out.print(data+"\t");
            }
            System.out.println();
        }
//将二维数组 转 稀疏数组的思想
        //1.先遍历二维数组 得到非零数据的个数
        int sum=0;
        for (int i=0;i&lt;9;i++){
            for (int j=0;j&lt;9;j++){
                if (ints[i][j]!=0){
                    sum++;
                }
            }
        }
       //创建对应稀疏矩阵
        int[][] xishu = new int[sum + 1][3];
        //给稀疏矩阵赋值
        xishu[0][0]=9;
        xishu[0][1]=9;
        xishu[0][2]=sum;
      //遍历二维数组，将非零值存放到xishu中
        int count=0;//用于记录是第几个非零数据
        for (int i=0;i&lt;9;i++){
            for (int j=0;j&lt;9;j++){
                if (ints[i][j]!=0){
                   count++;
                   xishu[count][0]=i;
                    xishu[count][1]=j;
                    xishu[count][2]=ints[i][j];
                }
            }
        }
      //输出稀疏数组的形式
        System.out.println("----------稀疏数组为---------");
        for (int i=0;i &lt; xishu.length;i++){
            System.out.println(xishu[i][0]+"  "+xishu[i][1]+"  "+xishu[i][2]);
        }



        //读取稀疏数组第一行，根据第一行创建原始的二维数组
        int[][] huifus = new int[xishu[0][0]][xishu[0][1]];
      //读取稀疏数组后几行的数据并赋给原始二维数组即可
        //这里是要遍历稀疏矩阵的值而不是恢复矩阵
        for (int k=1;k&lt; xishu.length;k++){
        huifus[xishu[k][0]][xishu[k][1]]=xishu[k][2];
        }
        System.out.println("---------恢复的二维矩阵-------");
        for (int[] row:huifus){
            for (int data:row){
                System.out.print(data+"\t");
            }
            System.out.println();
        }

    }
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/142307252</p>]]></description><guid isPermaLink="false">/archives/ab1c24f6-fb1d-4ea8-8d6d-892dd7e229e1</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:26:36 GMT</pubDate></item><item><title><![CDATA[Map 接口常用方法]]></title><link>https://wiki.dgfygk.com/archives/2207548c-d43b-4bee-bea3-3ff106e68287</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Map%20%E6%8E%A5%E5%8F%A3%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95&amp;url=/archives/2207548c-d43b-4bee-bea3-3ff106e68287" width="1" height="1" alt="" style="opacity:0;">
<p>1.</p>
<h3><strong><code>containsKey(Object key)</code></strong>:</h3>
<ul>
 <li>
  <p>描述: 检查给定的键是否存在于映射中。</p>
 </li>
 <li>
  <p>参数: <code>key</code> - 要检查的键。</p>
 </li>
 <li>
  <p>返回值: 如果映射中存在指定的键，则返回 <code>true</code>；否则返回 <code>false</code>。
   <br>
   2.</p>
  <h3><strong><code>containsValue(Object value)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 检查给定的值是否存在于映射中。</p>
 </li>
 <li>
  <p>参数: <code>value</code> - 要检查的值。</p>
 </li>
 <li>
  <p>返回值: 如果映射中存在指定的值，则返回 <code>true</code>；否则返回 <code>false</code>。
   <br>
   3.</p>
  <h3><strong><code>get(Object key)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 返回指定键所映射的值；如果映射中不存在此键，则返回 <code>null</code>。</p>
 </li>
 <li>
  <p>参数: <code>key</code> - 键。</p>
 </li>
 <li>
  <p>返回值: 映射中指定键对应的值，如果映射中不存在此键，则返回 <code>null</code>。
   <br>
   4.</p>
  <h3><strong><code>put(K key, V value)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 将指定的键值对添加到（或将指定值与指定键关联）此映射中。</p>
 </li>
 <li>
  <p>参数: <code>key</code> - 键，<code>value</code> - 值。</p>
 </li>
 <li>
  <p>返回值: 之前与此键关联的值，如果没有，则返回 <code>null</code>。
   <br>
   5.</p>
  <h3><strong><code>remove(Object key)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 从此映射中移除指定键的映射关系（如果存在）。</p>
 </li>
 <li>
  <p>参数: <code>key</code> - 键。</p>
 </li>
 <li>
  <p>返回值: 如果映射中存在此键，则返回与此键关联的值；如果不存在，则返回 <code>null</code>。
   <br>
   6.</p>
  <h3><strong><code>size()</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 返回映射中的键值对数量。</p>
 </li>
 <li>
  <p>参数: 无。</p>
 </li>
 <li>
  <p>返回值: 映射中的键值对数量。
   <br>
   7.</p>
  <h3><strong><code>isEmpty()</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 检查映射是否为空。</p>
 </li>
 <li>
  <p>参数: 无。</p>
 </li>
 <li>
  <p>返回值: 如果映射不包含键值对，则返回 <code>true</code>；否则返回 <code>false</code>。
   <br>
   8.</p>
  <h3><strong><code>clear()</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 清除映射中的所有键值对。</p>
 </li>
 <li>
  <p>参数: 无。</p>
 </li>
 <li>
  <p>返回值: 无。
   <br>
   9.</p>
  <h3><strong><code>keySet()</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 返回映射中所有键的集合视图。</p>
 </li>
 <li>
  <p>参数: 无。</p>
 </li>
 <li>
  <p>返回值: 映射中所有键的集合视图。
   <br>
   10.</p>
  <h3><strong><code>values()</code></strong>:</h3>
  <ul>
   <li>描述: 返回映射中所有值的集合视图。</li>
   <li>参数: 无。</li>
   <li>返回值: 映射中所有值的集合视图。
    <br>
    11.</li>
  </ul>
  <h3><strong><code>entrySet()</code></strong>:</h3>
  <ul>
   <li>描述: 返回映射中包含所有映射关系的集合视图。</li>
   <li>参数: 无。</li>
   <li>返回值: 映射中包含所有映射关系的集合视图。
    <br>
    12.</li>
  </ul>
  <h3><strong><code>putAll(Map&lt;? extends K, ? extends V&gt; m)</code></strong>:</h3>
  <ul>
   <li>描述: 将指定映射的所有映射关系复制到此映射中。</li>
   <li>参数: <code>m</code> - 包含键值对的映射。</li>
   <li>返回值: 无。</li>
  </ul>
 </li>
</ul>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/141311448</p>]]></description><guid isPermaLink="false">/archives/2207548c-d43b-4bee-bea3-3ff106e68287</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:26:25 GMT</pubDate></item><item><title><![CDATA[验证码功能在前端的直接应用2.0]]></title><link>https://wiki.dgfygk.com/archives/d6f5ec4c-abbc-4fbe-bb71-06cc08c1cc18</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%AA%8C%E8%AF%81%E7%A0%81%E5%8A%9F%E8%83%BD%E5%9C%A8%E5%89%8D%E7%AB%AF%E7%9A%84%E7%9B%B4%E6%8E%A5%E5%BA%94%E7%94%A82.0&amp;url=/archives/d6f5ec4c-abbc-4fbe-bb71-06cc08c1cc18" width="1" height="1" alt="" style="opacity:0;">
<p>成果图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa74ece0845c44be28045af2ef6a65dc3.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>要完成这个验证码功能需要有导入一个jar包，</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff2ab93c659b94d948468a5aa5df7ac57.png&amp;size=m" alt="">；</p>
<p>我们要新建一个后端类用来验证码的生成：</p>
<pre><code class="java">package fangwuzulin.yewu;

import cn.dsna.util.images.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/code")
public class code extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //创建了一个 ValidateCode 实例，传入四个参数：
        //第一个参数是验证码图像的宽度（500 像素）。
        //第二个参数是验证码图像的高度（200 像素）。
        //第三个参数是验证码字符的数量（4 个字符）。
        //第四个参数是干扰线的数量（100 条线）。
        ValidateCode validateCode = new ValidateCode(500,200,4,100);
        //拿到生成的验证码值
        String code = validateCode.getCode();
//将生成的验证码字符串存储到 session 中，键为 "code"
// 这样可以在后续的请求中验证用户输入的验证码是否正确
        HttpSession sessions = req.getSession();
        sessions.setAttribute("code",code);
        //将生成的验证码图像写入到响应的输出流中。
        // 验证码图像会被发送给客户端显示。
        validateCode.write(resp.getOutputStream());
    }
}
</code></pre>
<p>现在后端就生成了验证码的图片和数据，现在我们要把验证码放到前端显示：</p>
<p>这是前端的主体：</p>
<pre><code class="html">&lt;body&gt;
&lt;div&gt;
    &lt;h1&gt;欢迎登录&lt;/h1&gt;
    &lt;form action="denglu" method="get"&gt;
        &lt;label for="user"&gt;账号：&lt;/label&gt;
        &lt;input type="text" id="user" name="user" required&gt;
        &lt;label for="pwd"&gt;密码：&lt;/label&gt;
        &lt;input type="password" id="pwd" name="pwd" required&gt;
        &lt;img src="code" alt="加载失败" width="100" onclick="this.src=this.src+'?'+Math.random()"&gt;
        &lt;input type="text" ID="yz" name="yz"&gt;
        &lt;button type="button" onclick="denglu()"&gt;登录&lt;/button&gt;
        &lt;button type="button" &gt;注册&lt;/button&gt;
    &lt;/form&gt;
&lt;/div&gt;
&lt;/body&gt;
</code></pre>
<p>关于验证码最重要的就是这一行：</p>
<pre><code class="html"> &lt;img src="code" alt="加载失败" width="100" onclick="this.src=this.src+'?'+Math.random()"&gt;
</code></pre>
<ul>
 <li><code>&lt;img&gt;</code>: 这是 HTML 中的图像标签，用于在网页上展示图片。</li>
 <li><code>src="code"</code>: <code>src</code> 属性指定了图像的源文件位置。在这里，<code>"code"</code> 可能是指向服务器端的一个脚本（例如 PHP 或者 Java Servlet），该脚本负责生成验证码图片并返回给客户端。通常情况下，这会是一个动态生成的图片。</li>
 <li><code>alt="验证码加载失败"</code>: <code>alt</code> 属性提供了当图像无法显示时的一个替代文本。这里设置为 “验证码加载失败”，意味着当验证码图像无法加载时，浏览器会显示这句话。</li>
 <li><code>width="100"</code>: <code>width</code> 属性指定了图像的宽度，这里是 100 像素。</li>
 <li><code>onclick="this.src=this.src+'?'+Math.random()"</code>: <code>onclick</code> 属性定义了当用户点击该图像时触发的 JavaScript 代码。这段代码的作用是在点击图像时更新图像的 <code>src</code> 属性，从而刷新验证码。</li>
 <li><code>this.src = this.src + '?' + Math.random();</code>: 更新 <code>src</code> 属性，使其包含原来的 URL 加上随机数，这样就会请求一个新的验证码图像。</li>
</ul>
<p>这样我们的验证码就显示在前端页面了，最后我们再加上比对的逻辑让验证码在正确时进入正确的页面，在错误时弹框告知。</p>
<p>在js中：</p>
<pre><code class="html">    const yzInput = document.getElementById('yz');
</code></pre>
<p>通过这个方式拿到验证框里用户输入的值，</p>
<pre><code class="html">const user = {
                user: name,
                pwd: pwd,
                yz:yz
            };
</code></pre>
<p>将值放入对象里，再用post方式传到后端：</p>
<pre><code class="html">//将对象JSON序列化
let s = JSON.stringify(user);
            //配置向后端请求类型 get post 异步请求数据 传递数据
            xmlHttpRequest.open("post", "/ajax_war_exploded/denglu" , true);
            xmlHttpRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded")

//将值传回后端
 let xmlHttpRequest = new XMLHttpRequest();
    xmlHttpRequest.send(s);
</code></pre>
<p>这样我们就将验证码的值传回了后端，我们只要在后端将验证码比对就能知道是否正确。</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/141090124</p>]]></description><guid isPermaLink="false">/archives/d6f5ec4c-abbc-4fbe-bb71-06cc08c1cc18</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 01:26:16 GMT</pubDate></item><item><title><![CDATA[IDEA2024.1.4版怎么配置tomcat、web文件、maven文件]]></title><link>https://wiki.dgfygk.com/archives/5dcd2a5e-ab6d-44d2-9188-908a3ceda53d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IDEA2024.1.4%E7%89%88%E6%80%8E%E4%B9%88%E9%85%8D%E7%BD%AEtomcat%E3%80%81web%E6%96%87%E4%BB%B6%E3%80%81maven%E6%96%87%E4%BB%B6&amp;url=/archives/5dcd2a5e-ab6d-44d2-9188-908a3ceda53d" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>一、maven文件：</strong></h3>
<p>我们首先创建一个新的Java项目</p>
<h3><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F38b9068442cb4274ba37bd5ad310a7a6.png&amp;size=m" alt=""></h3>
<p>选择maven即可；这样在项目里就有了maven文件</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa3009d0c17bd46acb02bcaf53165f892.png&amp;size=m" alt=""></p>
<h3><strong>二、web模块 ：</strong></h3>
<p>在创建好的项目里点击菜单，弹出的列表找到项目结构</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe057e17e57984fd1958dba065ce6fff9.png&amp;size=m" alt=""></p>
<p>在项目结构里找模块里的web</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7d8be9ba426e487bb63d82ff7a0dcb88.png&amp;size=m" alt=""></p>
<p>这样我们的就有了web文件</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa8598701370f481b913512958350df7c.png&amp;size=m" alt=""></p>
<h3><strong>三、tomcat：</strong></h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F4531069cdef04207a4ea1a6f3ad73f19.png&amp;size=m" alt=""></p>
<p>点击编辑配置就跳转到下图，再点击+就打开了配置列表，找到如图的tomcat配置文件<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0638e601ab544c49b80b423248591f5c.png&amp;size=m" alt=""></p>
<p>选择成功就看到右边的 配置详情，找到部署，点击+选择工件。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc749d432f8b44fa5b1a37bb4e3283847.png&amp;size=m" alt=""></p>
<p>最后是配置路径，按照如图的方式就配置完tomcat了<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff9a82822005a4c9abdff6b2cca573200.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffc268ce4e72649b58433b6582c3245cd.png&amp;size=m" alt=""></p>
<p>运行过程<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1fb5f59c8822439fb3a4a7c802494bac.png&amp;size=m" alt=""></p>
<p>运行结果，因为我们还没写前端网页，所以显示的是404;网址是本地网址，这样就是成功配置<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F0c3ed08655b048c6aa17dd8de515b78c.png&amp;size=m" alt=""></p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/141216946</p>]]></description><guid isPermaLink="false">/archives/5dcd2a5e-ab6d-44d2-9188-908a3ceda53d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:25:49 GMT</pubDate></item><item><title><![CDATA[Servlet的三种写法]]></title><link>https://wiki.dgfygk.com/archives/57e4fca5-a9ee-4009-a373-74638745dc38</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Servlet%E7%9A%84%E4%B8%89%E7%A7%8D%E5%86%99%E6%B3%95&amp;url=/archives/57e4fca5-a9ee-4009-a373-74638745dc38" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>一、什么是Servlet：</strong></h3>
<p>Java Servlet（服务端小应用程序）是一种基于 Java 的技术，用于处理来自 Web 客户端的 HTTP 请求，并生成相应的响应。Servlet 可以看作是服务器端的 Java 应用程序组件，它们扩展了 Web 服务器的功能，使其能够处理复杂的业务逻辑和动态内容生成。</p>
<h3><strong>二、环境搭配：</strong></h3>
<p>首先我们需要将servlet依赖复制粘贴到pom.xml文件里：</p>
<pre><code class="XML">   &lt;!--        servletApi的依赖--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt;
            &lt;version&gt;4.0.1&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;
</code></pre>
<p>这样我们就能使用servlet接口。</p>
<h3><strong>三、写接口：</strong></h3>
<h4>1、直接实现 <code>Servlet</code> 接口</h4>
<p>直接实现 <code>Servlet</code> 接口是最基础的方式，需要实现 <code>init</code>、<code>service</code>、<code>destroy</code> 等方法。这种方式较为灵活，但同时也需要更多手动处理的代码。</p>
<pre><code class="java">import javax.servlet.*;
import java.io.IOException;

@WebServlet("/simple-servlet")
public class SimpleServlet implements Servlet {

    @Override
         //这个方法在 Servlet 被加载到内存中时调用，用于执行初始化任务。
    public void init(ServletConfig config) throws ServletException {
        // 初始化逻辑
    }

    @Override
       //返回当前 Servlet 的配置对象。
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
       //这个方法用于处理客户端的请求
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
       // 处理请求
        System.out.println("Received request");
        ((HttpServletResponse) res).sendRedirect("/response");
    }

    @Override
      //返回关于 Servlet 的信息字符串
    public String getServletInfo() {
        return "SimpleServlet";
    }

    @Override
//当 Servlet 被卸载或容器关闭时调用。
    public void destroy() {
        // 销毁逻辑
    }
}
</code></pre>
<h4>2、继承 <code>GenericServlet</code> 类</h4>
<p><code>GenericServlet</code> 类实现了 <code>Servlet</code> 接口，并提供了一些默认实现。这种方式减少了需要自己实现的方法数量，但仍然需要实现 <code>service</code> 方法来处理请求。</p>
<pre><code class="java">import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import java.io.IOException;

@WebServlet("/generic-servlet")
public class GenericServletExample extends GenericServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        // 处理请求
        System.out.println("Received request");
        ((HttpServletResponse) res).sendRedirect("/response");
    }

    @Override
    public void destroy() {
        // 销毁逻辑
    }
}
</code></pre>
<h4>3、继承 <code>HttpServlet</code> 类</h4>
<p><code>HttpServlet</code> 类继承自 <code>GenericServlet</code> 类，为处理 HTTP 请求提供了便利。这是最常用的方式，因为它为 HTTP 请求提供了默认的 <code>doGet</code> 和 <code>doPost</code> 方法。只需要重写这些方法即可处理特定的 HTTP 请求。</p>
<pre><code class="java">import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/http-servlet")
public class HttpExampleServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理 GET 请求
        System.out.println("Received GET request");
        resp.sendRedirect("/response");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理 POST 请求
        System.out.println("Received POST request");
        resp.sendRedirect("/response");
    }
}
</code></pre>
<p>这三类方法可以处理Http的访问请求并处理</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/141253747</p>]]></description><guid isPermaLink="false">/archives/57e4fca5-a9ee-4009-a373-74638745dc38</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:25:30 GMT</pubDate></item><item><title><![CDATA[用字节流和字符流读取写入数据]]></title><link>https://wiki.dgfygk.com/archives/5270e374-12d0-4c6a-9aff-76cec5df218a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%94%A8%E5%AD%97%E8%8A%82%E6%B5%81%E5%92%8C%E5%AD%97%E7%AC%A6%E6%B5%81%E8%AF%BB%E5%8F%96%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE&amp;url=/archives/5270e374-12d0-4c6a-9aff-76cec5df218a" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>一、字节流：</strong></h3>
<p>
 <br>
</p>
<ol>
 <li>
  <p><strong>处理单位</strong>:</p>
 </li>
 <li>
  <p>字节流以字节（8位二进制数据）为单位处理数据。</p>
 </li>
 <li>
  <p><strong>主要类</strong>:</p>
 </li>
 <li>
  <p>输入流：<code>InputStream</code> 是所有字节输入流的基类。</p>
 </li>
 <li>
  <p>输出流：<code>OutputStream</code> 是所有字节输出流的基类。</p>
 </li>
 <li>
  <p><strong>应用场景</strong>:</p>
 </li>
 <li>
  <p>适合处理二进制文件，如图像、音频、视频等。</p>
 </li>
 <li>
  <p>适合处理网络传输数据，因为网络通信通常基于字节流。</p>
 </li>
 <li>
  <p>适合处理任何非文本数据。</p>
 </li>
 <li>
  <p><strong>常用类</strong>:</p>
 </li>
 <li>
  <p><code>FileInputStream</code> 和 <code>FileOutputStream</code>：用于从文件中读取和写入字节数据。</p>
 </li>
 <li>
  <p><code>ByteArrayInputStream</code> 和 <code>ByteArrayOutputStream</code>：用于从字节数组中读取和写入字节数据。</p>
 </li>
 <li>
  <p><code>Socket.getInputStream()</code> 和 <code>Socket.getOutputStream()</code>：用于从网络连接中读取和写入字节数据</p>
 </li>
</ol>
<p>1、写入方式：</p>
<pre><code class="java">import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
//字节流写入
public class xieru {
    public static void main(String[] args) {
        //定义字符串变量，存入要写入的值
        String data="hello";
        try {
            //创建一个 FileOutputStream 对象，用于写入名为 "data.txt" 的文件。
            // 如果文件不存在，它将被创建；如果文件已存在，它的内容将被覆盖。
            FileOutputStream fileOutputStream = new FileOutputStream("data.txt");
            //这里使用 getBytes() 方法将字符串转换为字节数组，并通过 FileOutputStream 写入文件。
            fileOutputStream.write(data.getBytes());
            //这会在尝试打开不存在的文件时抛出。
        } catch (FileNotFoundException e) {
            //抛出一个新的 RuntimeException，并将原始异常作为原因传递给构造函数。
            throw new RuntimeException(e);
            //捕获 IOException，这会在 I/O 操作中出现错误时抛出。
        } catch (IOException e) {
            //同样抛出一个新的 RuntimeException，并将原始异常作为原因传递给构造函数。
            throw new RuntimeException(e);
        }

    }
}
</code></pre>
<p>2、读取方式：</p>
<pre><code class="java">import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

//从文件读取数据
public class duqu {
    public static void main(String[] args) throws IOException {
//创建一个 FileInputStream 对象，用于读取名为 "data.txt" 的文件。
            FileInputStream fileInputStream = new FileInputStream("data.txt");
            int data;

           //循环读取文件中的数据。read() 方法每次读取一个字节
        // 如果文件结束，则返回 -1。
        while ((data=fileInputStream.read())!=-1){
            //将读取到的字节转换为字符并打印出来。
                System.out.println((char)data);
            }


    }
}
</code></pre>
<h3><strong>二、字符流：</strong></h3>
<ol>
 <li>
  <p><strong>处理单位</strong>:</p>
 </li>
 <li>
  <p>字符流以字符（通常是 16 位 Unicode 字符）为单位处理数据。</p>
 </li>
 <li>
  <p><strong>主要类</strong>:</p>
 </li>
 <li>
  <p>输入流：<code>Reader</code> 是所有字符输入流的基类。</p>
 </li>
 <li>
  <p>输出流：<code>Writer</code> 是所有字符输出流的基类。</p>
 </li>
 <li>
  <p><strong>应用场景</strong>:</p>
 </li>
 <li>
  <p>适合处理文本文件，如纯文本、XML、HTML 等。</p>
 </li>
 <li>
  <p>适合处理需要编码/解码的数据，因为字符流可以指定字符集。</p>
 </li>
 <li>
  <p><strong>常用类</strong>:</p>
 </li>
 <li>
  <p><code>FileReader</code> 和 <code>FileWriter</code>：用于从文件中读取和写入字符数据。</p>
 </li>
 <li>
  <p><code>BufferedReader</code> 和 <code>BufferedWriter</code>：用于提高读写效率，通常与 <code>FileReader</code> 和 <code>FileWriter</code> 结合使用。</p>
 </li>
 <li>
  <p><code>InputStreamReader</code> 和 <code>OutputStreamWriter</code>：用于将字节流转换为字符流，允许指定字符集。</p>
 </li>
</ol>
<p>1、写入：</p>
<pre><code class="java">import java.io.FileWriter;
import java.io.IOException;

//字符流 写数据
public class xieru2 {
    public static void main(String[] args) throws IOException {
        //定义要写入的字符串
        String data="我是影片中";
        //找到data2.txt文件，如果没有就新建，有就覆盖
        FileWriter fileWriter = new FileWriter("data2.txt");
        //将字符串写入文件。
        fileWriter.write(data);
//这行代码用于关闭 FileWriter 对象。
//关闭 FileWriter 对象时，会自动调用 flush() 方法以确保所有缓冲区中的数据都被写入文件。
//此外，关闭 FileWriter 对象还会释放与之关联的系统资源。
fileWriter.close();
    }
}
</code></pre>
<p>2、读取：</p>
<pre><code class="java">import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

//读操作   文件=》内存=》控制台    字符流
public class duqu2 {
    public static void main(String[] args) throws IOException {
        //创建一个 FileReader 对象，用于读取名为 "data2.txt" 的文件。
        FileReader fileReader = new FileReader("data2.txt");
       // 定义一个 int 类型的变量 chars，用于存储从文件中读取的每个字符。
        int chars;
        //循环读取文件中的数据。read() 方法每次读取一个字符
        // 如果文件结束，则返回 -1。
        while ((chars= fileReader.read())!=-1){
         //将读取到的字符打印到控制台。   
            System.out.println((char) chars);
        }
    }
}
</code></pre>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/141057148</p>]]></description><guid isPermaLink="false">/archives/5270e374-12d0-4c6a-9aff-76cec5df218a</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:24:44 GMT</pubDate></item><item><title><![CDATA[Spring Boot 常用注解]]></title><link>https://wiki.dgfygk.com/archives/247b7833-1b64-4701-bd7e-30e85abe6cbe</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%20%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3&amp;url=/archives/247b7833-1b64-4701-bd7e-30e85abe6cbe" width="1" height="1" alt="" style="opacity:0;">
<h5>1. <strong>基础 Spring 注解</strong></h5>
<ul>
 <li><strong>@Component</strong></li>
 <li>标记一个类作为 Spring IoC 容器的一个组件。</li>
 <li><strong>@Repository</strong></li>
 <li>标记一个 DAO 类，同时提供了异常转换机制。</li>
 <li><strong>@Service</strong></li>
 <li>标记业务逻辑层的服务类。</li>
 <li><strong>@Controller</strong></li>
 <li>标记一个 Web 层的控制器类。</li>
 <li><strong>@RestController</strong></li>
 <li>结合了 <code>@Controller</code> 和 <code>@ResponseBody</code>，用于直接返回 JSON/XML 数据的 RESTful 控制器。</li>
 <li><strong>@Autowired</strong></li>
 <li>自动注入 Bean 的实例。</li>
 <li><strong>@Qualifier</strong></li>
 <li>当存在多个相同类型的 Bean 时，用于指定注入哪个 Bean。</li>
 <li><strong>@Configuration</strong></li>
 <li>标记类作为配置类，可以用来定义 Bean。</li>
 <li><strong>@Bean</strong></li>
 <li>在配置类中定义 Bean。</li>
 <li><strong>@Import</strong></li>
 <li>导入其他配置类。</li>
 <li><strong>@PropertySource</strong></li>
 <li>指定额外的属性源文件。</li>
 <li><strong>@Value</strong></li>
 <li>注入属性值。</li>
</ul>
<h5>2. <strong>Spring Boot 特性注解</strong></h5>
<ul>
 <li><strong>@SpringBootApplication</strong></li>
 <li>合并了 <code>@Configuration</code>, <code>@EnableAutoConfiguration</code>, <code>@ComponentScan</code>。是 Spring Boot 应用的主要入口点。</li>
 <li><strong>@EnableAutoConfiguration</strong></li>
 <li>启用 Spring Boot 的自动配置功能。</li>
 <li><strong>@Conditional</strong></li>
 <li>控制 Bean 的创建条件。</li>
 <li><strong>@Profile</strong></li>
 <li>标记特定环境下的配置。</li>
 <li><strong>@ConditionalOnClass</strong></li>
 <li>如果给定的类存在于类路径中，则创建 Bean。</li>
 <li><strong>@ConditionalOnMissingClass</strong></li>
 <li>如果给定的类不存在于类路径中，则创建 Bean。</li>
 <li><strong>@ConditionalOnProperty</strong></li>
 <li>根据配置属性的存在与否来决定是否创建 Bean。</li>
 <li><strong>@ConditionalOnExpression</strong></li>
 <li>根据 SpEL 表达式的计算结果来决定是否创建 Bean。</li>
 <li><strong>@ConditionalOnWebApplication</strong></li>
 <li>如果应用是 Web 应用，则创建 Bean。</li>
 <li><strong>@ConditionalOnNotWebApplication</strong></li>
 <li>如果应用不是 Web 应用，则创建 Bean。</li>
 <li><strong>@EnableConfigurationProperties</strong></li>
 <li>用于注册配置属性类的 Bean。</li>
 <li><strong>@ConfigurationProperties</strong></li>
 <li>将 Java 对象与配置属性绑定在一起。</li>
</ul>
<h5>3. <strong>测试相关的注解</strong></h5>
<ul>
 <li><strong>@RunWith(SpringRunner.class)</strong></li>
 <li>使用 Spring 测试框架。</li>
 <li><strong>@SpringBootTest</strong></li>
 <li>对整个 Spring Boot 应用进行集成测试。</li>
 <li><strong>@WebMvcTest</strong></li>
 <li>只加载 Spring MVC 组件进行测试。</li>
 <li><strong>@DataJpaTest</strong></li>
 <li>只加载 JPA 相关的组件进行测试。</li>
 <li><strong>@Transactional</strong></li>
 <li>标记测试方法为事务性的。</li>
</ul>
<h5>4. <strong>安全相关的注解</strong></h5>
<ul>
 <li><strong>@EnableWebSecurity</strong></li>
 <li>启用 Web 安全配置。</li>
 <li><strong>@EnableGlobalMethodSecurity</strong></li>
 <li>启用全局方法级别的安全控制。</li>
</ul>
<h5>5. <strong>RESTful 相关的注解</strong></h5>
<ul>
 <li><strong>@GetMapping</strong></li>
 <li>处理 HTTP GET 请求。</li>
 <li><strong>@PostMapping</strong></li>
 <li>处理 HTTP POST 请求。</li>
 <li><strong>@PutMapping</strong></li>
 <li>处理 HTTP PUT 请求。</li>
 <li><strong>@DeleteMapping</strong></li>
 <li>处理 HTTP DELETE 请求。</li>
 <li><strong>@PatchMapping</strong></li>
 <li>处理 HTTP PATCH 请求。</li>
 <li><strong>@RequestMapping</strong></li>
 <li>用于映射请求路径，可以替代以上特定请求类型的注解。</li>
 <li><strong>@PathVariable</strong></li>
 <li>从 URL 中获取参数。</li>
 <li><strong>@RequestParam</strong></li>
 <li>从请求的查询字符串中获取参数。</li>
 <li><strong>@RequestBody</strong></li>
 <li>获取请求体中的数据。</li>
 <li><strong>@ResponseBody</strong></li>
 <li>将方法的返回值直接写入响应体中。</li>
</ul>
<h5>6. <strong>异步相关的注解</strong></h5>
<ul>
 <li><strong>@Async</strong></li>
 <li>标记方法为异步执行。</li>
 <li><strong>@EnableAsync</strong></li>
 <li>启用异步支持。</li>
</ul>
<h5>7. <strong>国际化相关的注解</strong></h5>
<ul>
 <li><strong>@MessageSource</strong></li>
 <li>注入消息资源。</li>
 <li><strong>@EnableMessageSource</strong></li>
 <li>启用消息源。</li>
</ul>
<h5>8. <strong>日志相关的注解</strong></h5>
<ul>
 <li><strong>@Slf4j</strong></li>
 <li>自动生成 <code>Logger</code>。</li>
 <li><strong>@Log4j2</strong></li>
 <li>生成 Log4j2 的 <code>Logger</code>。</li>
</ul>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/142149279</p>]]></description><guid isPermaLink="false">/archives/247b7833-1b64-4701-bd7e-30e85abe6cbe</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:24:35 GMT</pubDate></item><item><title><![CDATA[Javaweb用过滤器写防跳墙功能和退出登录]]></title><link>https://wiki.dgfygk.com/archives/9051fa12-6eb8-4b32-91cc-ec44a2a9e6d0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Javaweb%E7%94%A8%E8%BF%87%E6%BB%A4%E5%99%A8%E5%86%99%E9%98%B2%E8%B7%B3%E5%A2%99%E5%8A%9F%E8%83%BD%E5%92%8C%E9%80%80%E5%87%BA%E7%99%BB%E5%BD%95&amp;url=/archives/9051fa12-6eb8-4b32-91cc-ec44a2a9e6d0" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>一、什么是防跳墙功能：</strong></h3>
<p>防跳墙功能通常指的是防止用户在未完成认证的情况下直接访问受保护资源的功能。在 Web 开发中，这种功能通常被称为"登录拦截"或"身份验证拦截”。</p>
<p>在 Spring MVC 中，实现这种功能通常使用的是"拦截器”（Interceptor）。拦截器可以用来执行预处理或后处理任务，比如身份验证、权限控制等。</p>
<h3>二、如何与过滤器结合：</h3>
<p>将防跳墙功能写进过滤器中，就相当于所有跳转都要经过过滤器这一层，这样我们就只需要写一次防跳墙就能控制整个项目的跳转。</p>
<h3>三、具体操作：</h3>
<p>首先我们要创建一个过滤器，具体的写法在这篇文章：<a href="https://blog.csdn.net/daibadetianshi/article/details/140869629?spm=1001.2014.3001.5501" title="https://blog.csdn.net/daibadetianshi/article/details/140869629?spm=1001.2014.3001.5501">https://blog.csdn.net/daibadetianshi/article/details/140869629?spm=1001.2014.3001.5501<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F003a2ce7eb50c2e24a8c624c260c5930.png&amp;size=m" alt="">https://blog.csdn.net/daibadetianshi/article/details/140869629?spm=1001.2014.3001.5501</a></p>
<p>创建完过滤器就要开始写防跳墙功能：</p>
<p>首先我们要在登录界面给session存好值，这个就是我们后端判断的凭证；</p>
<pre><code class="java">if (login!=null){
    //将键值对存入session中
    session.setAttribute("user","passok");
    writer.write("success");
}else {

    writer.write("err");

}
</code></pre>
<p>我们的防跳墙功能建立在session的基础上，先获取用户的session值再根据这个值进行判断；</p>
<pre><code class="java"> //获取已有的session会话，即使没有也不自动创建
        HttpSession session = ((HttpServletRequest) servletRequest).getSession(false);
        //获取浏览器导航的地址
        String requestURI = ((HttpServletRequest) servletRequest).getRequestURI();
        //对网址经行判断
</code></pre>
<p>上面的代码完成了过滤器获取session的值和浏览器跳转的网址；</p>
<p>拿到这两个值，我们先对网址进行区分把不需要权限的页面放出来，让用户不会被卡。</p>
<p>先判断是否为不用卡的跳转直接放行：</p>
<p>不会被卡的跳转一般都是登录跳转，验证码跳转，退出登录跳转，登陆失败跳转，登录界面等等。</p>
<pre><code class="java"> if (requestURI.endsWith("/huanying.jsp")||requestURI.endsWith("/code")||requestURI.endsWith("/usererror")||requestURI.endsWith("/showlist")
                ||requestURI.endsWith("/httpuser")
        ){
            //放行
            filterChain.doFilter(servletRequest,servletResponse);

        }
</code></pre>
<p>再判断session的值是否够访问的权限：</p>
<pre><code class="java">else if (session==null||session.getAttribute("user")==null){
            //初始化write
            PrintWriter writer = servletResponse.getWriter();
            //被拦截时的弹窗,将跳转到登陆界面
            writer.write("&lt;script&gt;"+
                    "alert('请登录');"+
                    "window.location.href='huanying.jsp'"+
                    "&lt;/script&gt;");
        }
</code></pre>
<p>如果session为空或不存在，则可以判断用户是非法跳转，没有访问权限。用弹窗告知并跳转到登陆界面。</p>
<p>当session存在且有值时，就说明有权限，是正常登录，过滤器放行。</p>
<pre><code class="java">else if (session !=null &amp;&amp;session.getAttribute("user")!=null){
            //放行
            filterChain.doFilter(servletRequest,servletResponse);
        }
</code></pre>
<h3><strong>四、退出登录：</strong></h3>
<p>退出登录就是取消相应的权限，也就是将session变为空。</p>
<pre><code class="java"> //获取session
        HttpSession session = req.getSession();
        //将session消除
        session.invalidate();
</code></pre>
<p>这样我们的session会话就被销毁了。</p>
<pre><code class="java"> PrintWriter writer = resp.getWriter();
        writer.write(
                "&lt;script&gt;"+
                        "alert('退出成功');"+
                        "window.location.href='huanying.jsp'"+
                        "&lt;/script&gt;"
        );
    }
</code></pre>
<p>这样就成功退出了。</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/140870293</p>]]></description><guid isPermaLink="false">/archives/9051fa12-6eb8-4b32-91cc-ec44a2a9e6d0</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:24:23 GMT</pubDate></item><item><title><![CDATA[总结String 类常用方法]]></title><link>https://wiki.dgfygk.com/archives/2e7c1569-1616-49a9-b2fd-b9afab11ffff</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%80%BB%E7%BB%93String%20%E7%B1%BB%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95&amp;url=/archives/2e7c1569-1616-49a9-b2fd-b9afab11ffff" width="1" height="1" alt="" style="opacity:0;">
<p>1.</p>
<h3><strong><code>length()</code></strong>:</h3>
<ul>
 <li>
  <p>描述: 返回字符串的长度。</p>
 </li>
 <li>
  <p>参数: 无。</p>
 </li>
 <li>
  <p>返回值: 字符串的长度。
   <br>
   2.</p>
  <h3><strong><code>charAt(int index)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 返回指定索引处的字符。</p>
 </li>
 <li>
  <p>参数: <code>index</code> - 索引位置。</p>
 </li>
 <li>
  <p>返回值: 指定索引处的字符。
   <br>
   3.</p>
  <h3><strong><code>concat(String str)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 将指定字符串连接到此字符串的末尾。</p>
 </li>
 <li>
  <p>参数: <code>str</code> - 要连接的字符串。</p>
 </li>
 <li>
  <p>返回值: 新的字符串。
   <br>
   4.</p>
  <h3><strong><code>toUpperCase()</code> 和 <code>toLowerCase()</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 分别将字符串转换为全部大写或全部小写。</p>
 </li>
 <li>
  <p>参数: 无。</p>
 </li>
 <li>
  <p>返回值: 转换后的字符串。
   <br>
   5.</p>
  <h3><strong><code>trim()</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 删除字符串两端的空白字符。</p>
 </li>
 <li>
  <p>参数: 无。</p>
 </li>
 <li>
  <p>返回值: 去除空白字符后的字符串。
   <br>
   6.</p>
  <h3><strong><code>startsWith(String prefix)</code> 和 <code>endsWith(String suffix)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 分别检查字符串是否以指定的前缀或后缀开头或结尾。</p>
 </li>
 <li>
  <p>参数: <code>prefix</code> 或 <code>suffix</code> - 指定的前缀或后缀。</p>
 </li>
 <li>
  <p>返回值: 如果字符串以指定的前缀或后缀开头或结尾，则返回 <code>true</code>；否则返回 <code>false</code>。
   <br>
   7.</p>
  <h3><strong><code>indexOf(String str)</code> 和 <code>lastIndexOf(String str)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 分别返回指定子字符串首次出现的索引或最后一次出现的索引。</p>
 </li>
 <li>
  <p>参数: <code>str</code> - 指定的子字符串。</p>
 </li>
 <li>
  <p>返回值: 子字符串首次或最后一次出现的索引，如果未找到则返回 <code>-1</code>。
   <br>
   8.</p>
  <h3><strong><code>replace(char oldChar, char newChar)</code> 和 <code>replaceAll(String regex, String replacement)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 分别替换字符串中的指定字符或符合正则表达式的子字符串。</p>
 </li>
 <li>
  <p>参数: <code>oldChar</code> 和 <code>newChar</code> - 替换的旧字符和新字符；<code>regex</code> 和 <code>replacement</code> - 正则表达式和替换字符串。</p>
 </li>
 <li>
  <p>返回值: 替换后的字符串。
   <br>
   9.</p>
  <h3><strong><code>split(String regex)</code></strong>:</h3>
 </li>
 <li>
  <p>描述: 根据给定的正则表达式拆分字符串。</p>
 </li>
 <li>
  <p>参数: <code>regex</code> - 用于拆分字符串的正则表达式。</p>
 </li>
 <li>
  <p>返回值: 拆分后的字符串数组。
   <br>
   10.</p>
  <h3><strong><code>equals(Object obj)</code> 和 <code>equalsIgnoreCase(String str)</code></strong>:</h3>
  <ul>
   <li>描述: 分别比较字符串是否相等（区分大小写）和是否相等（忽略大小写）。</li>
   <li>参数: <code>obj</code> - 要比较的对象；<code>str</code> - 要比较的字符串。</li>
   <li>返回值: 如果字符串相等，则返回 <code>true</code>；否则返回 <code>false</code>。
    <br>
    11.</li>
  </ul>
  <h3><strong><code>matches(String regex)</code></strong>:</h3>
  <ul>
   <li>描述: 检查字符串是否匹配给定的正则表达式。</li>
   <li>参数: <code>regex</code> - 正则表达式。</li>
   <li>返回值: 如果字符串匹配正则表达式，则返回 <code>true</code>；否则返回 <code>false</code>。</li>
  </ul>
 </li>
</ul>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/141311706</p>]]></description><guid isPermaLink="false">/archives/2e7c1569-1616-49a9-b2fd-b9afab11ffff</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:24:07 GMT</pubDate></item><item><title><![CDATA[Java用反射封装MySQL增删改查的工具]]></title><link>https://wiki.dgfygk.com/archives/7fc7a0ca-82a0-49d7-9a29-0b613fb54d10</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E7%94%A8%E5%8F%8D%E5%B0%84%E5%B0%81%E8%A3%85MySQL%E5%A2%9E%E5%88%A0%E6%94%B9%E6%9F%A5%E7%9A%84%E5%B7%A5%E5%85%B7&amp;url=/archives/7fc7a0ca-82a0-49d7-9a29-0b613fb54d10" width="1" height="1" alt="" style="opacity:0;">
<h3>首先，我们要搭建环境：</h3>
<h5>一、导入jar包</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F72470d428f2c467ab167a980764d6767.jpeg&amp;size=m" alt=""></p>
<p>这是我们要加入的jar包 。</p>
<h5>二、创建德鲁伊的配置文件</h5>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F608115a0726044e78598f26e6ef5f156.jpeg&amp;size=m" alt=""></p>
<h5><strong>三、获取链接：</strong></h5>
<p>这是整个的基础，有很多方法都要调用该链接用来给数据库传值；</p>
<pre><code class="java">public class sql_lianjie {
    //创建变量
    private static Connection connection;

    /**
     * 建立链接
     *
     * @return
     * @throws SQLException
     */
    //
    public static Connection getConnection() throws SQLException {
        //创建一个对象用来存储和加载配置文件
        Properties properties = new Properties();
        //try catch输出错误
        try {
            //使用FileInputStream读取名为dur.properties的文件
            // 使用load方法将文件内容加载到properties对象中
            properties.load(new FileInputStream("src/dur.properties"));
            //使用`DruidDataSourceFactory`的`createDataSource`静态方法，传入前面加载的`properties`对象。
            // 这个方法根据配置创建一个`DataSource`对象，`DataSource`是JDBC中用于获取数据库连接的对象。
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//调用`DataSource`对象的`getConnection`方法来获取一个数据库`Connection`。
// 这个`Connection`对象是与数据库交互的基础，所有的SQL操作都需要通过这个对象执行。
            connection = dataSource.getConnection();
            //打印获取到的`Connection`对象。这通常用于调试目的，显示连接是否成功获取。
         //   System.out.println(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //最后，方法返回获取的`Connection`对象。
        return connection;
    }
</code></pre>
<h5><strong>四、 关闭链接：</strong></h5>
<p>为了不占用电脑的资源，每次访问完成都要关闭链接；</p>
<pre><code class="java">//名为closeAll，它接受一个可变参数列表（varargs）类型为AutoCloseable。AutoCloseable是一个功能接口
// 用于自动管理资源的类。类型为AutoCloseable。AutoCloseable是一个功能接口,参数它指的是要关闭的资源。
    public static void closeAll(AutoCloseable... yaoguanbidemingcheng) {
        //首先检查传入的参数数组是否非空且长度大于零，以确保有资源需要处理。
        if (yaoguanbidemingcheng != null &amp;&amp; yaoguanbidemingcheng.length &gt; 0) {
            //使用Java 8的流（Stream）API来遍历AutoCloseable对象数组。
            // forEach方法接收一个Lambda表达式，对每个元素执行给定的操作。
            Arrays.stream(yaoguanbidemingcheng).forEach(x -&gt; {
                //将捕获该异常并打印堆栈跟踪信息。这样可以避免因为单个资源的关闭失败而导致整个程序的崩溃。
                if (x != null) {
                    try {
                        x.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
</code></pre>
<h5><strong>五、预编译：</strong></h5>
<pre><code>创建并返回一个预编译的SQL语句对象（PreparedStatement）它接受三个参数：一个SQL语句（String sql），一个数据库连接对象（Connection connection）， 以及一个可变参数列表（Object... parameters），用于传递SQL语句中的参数值。
</code></pre>
<pre><code>```java
public static PreparedStatement getPrepar(String sql, Connection connection, Object... parameters) throws SQLException {
        //使用Connection对象和提供的SQL语句创建一个PreparedStatement对象。
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //获取PreparedStatement对象的元数据，这提供了关于SQL语句中参数的信息，包括参数的数量和类型。
        ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
        //首先，获取参数的预期数量parameterCount
        int parameterCount = parameterMetaData.getParameterCount();
        //检查是否有参数需要设置，以及传入的参数列表是否非空，最后确认传入参数的数量是否与SQL语句中参数的数量相匹配。
        if (parameterCount != 0 &amp;&amp; parameters != null &amp;&amp; parameters.length == parameterCount) {
            //如果参数数量和传入的参数列表一致，则循环遍历所有参数。
            for (int i = 0; i &lt; parameterCount; i++) {
                // 并使用setObject方法将每个参数值设置到PreparedStatement对象中。这里i + 1是因为参数索引是从1开始的，而数组索引是从0开始的
                preparedStatement.setObject(i + 1, parameters[i]);
            }
        }
        //方法返回配置好的PreparedStatement对象，以便后续调用者可以执行具体的数据库操
        return preparedStatement;
    }
</code></pre>
<h3>其次、写方法：</h3>
<h5><strong>六、 写"增删改"的封装方法:</strong></h5>
<p>它用于执行SQL更新操作（如插入、更新或删除），并返回受影响的行数。</p>
<pre><code class="java">//它接受两个参数：一个SQL更新语句（String sql）和一个可变参数列表（Object... parametes）
// 用于传递SQL语句中的参数值。方法返回一个整型值（int），表示更新操作影响的行数，
    public static int update(String sql, Object... parametes) throws SQLException {
        //sql_lianjie的对象来获取数据库连接（Connection）。getConnection()是获取数据库连接的标准方法。
        Connection connection = sql_lianjie.getConnection();
        //将sql语句和参数返回给getPrepar（）方法送去预编译处理
        PreparedStatement prepar = getPrepar(sql, connection, parametes);
        //使用PreparedStatement对象的executeUpdate()方法来执行SQL更新操作。
        //此方法返回一个整数，表示数据库中受影响的行数。
        int i = prepar.executeUpdate();
        return i;
    }
</code></pre>
<p>七、写查的方法：</p>
<h5>查多个数据：</h5>
<h6>1、将数据存储在List中以集合保存：</h6>
<pre><code class="java">public static &lt;T&gt; List&lt;T&gt; selduo(String sql, Class&lt;T&gt; cls, Object... parameters) throws SQLException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //定义 ArrayList 用于存储结果对象
        ArrayList&lt;T&gt; objects = new ArrayList&lt;&gt;();
        //数据库连接 (connection)
        Connection connection = null;
        //预编译的 SQL 语句 (preparedStatement)
        PreparedStatement preparedStatement = null;
        //结果集 (resultSet)
        ResultSet resultSet = null;
        T t = null;
        //2,获取链接
        connection = sql_lianjie.getConnection();
        //3,获取sql预编译对象
        preparedStatement = sql_lianjie.getPrepar(sql, connection, parameters);
        //4,执行查询结果，得到结果集
        resultSet = preparedStatement.executeQuery();
        //5,反编译对象，从结果集中获取表的元素
        ResultSetMetaData metaData = resultSet.getMetaData();
        //     System.out.println(metaData);
        while (resultSet.next()) {
            //实例化空的bean对象，用来封装数据
            t = cls.newInstance();
            int count = metaData.getColumnCount();
            for (int i = 1; i &lt;= count; i++) {
               // 将结果集中的列值设置到对象的属性上。
                BeanUtils.setProperty(t, metaData.getColumnName(i), resultSet.getObject(i));
            }
            //然后将对象添加到结果列表中。
            objects.add(t);
        }
        sql_lianjie.closeAll(connection, preparedStatement, resultSet);
        return objects;


    }
</code></pre>
<h6>2、将数据存储在Map中以集合保存：</h6>
<pre><code class="java"> public static ArrayList&lt;Map&gt; chamap(String sql, Object... parameters) throws SQLException {
        //定义需要的变量
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        ArrayList&lt;Map&gt; map = new ArrayList&lt;&gt;();
//2.获取链接
        connection = sql_lianjie.getConnection();
//3.获取预编译对象
        PreparedStatement prepar = sql_lianjie.getPrepar(sql, connection, parameters);
        //4.执行查询，得到结果集
        resultSet = prepar.executeQuery();
        //5.反编译对象，从结果集中获取表的元数据
        ResultSetMetaData metaData = resultSet.getMetaData();
        int count = metaData.getColumnCount();
        while (resultSet.next()) {
            HashMap&lt;Object, Object&gt; objectObjectHashMap = new HashMap&lt;&gt;();
            for (int i = 1; i &lt;= count; i++) {
                objectObjectHashMap.put(metaData.getColumnName(i), resultSet.getObject(i));
            }
            map.add(objectObjectHashMap);
        }
        sql_lianjie.closeAll(connection, preparedStatement, resultSet);
        return map;
    }
</code></pre>
<h6>3、查询多条记录，以数组集合（集合中的数据是数组）的数据状态返回</h6>
<pre><code class="java">public static List&lt;Object[]&gt; queryArraylist(String sql, Object... parameters) throws SQLException {
        //定义需要的变量
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        ArrayList&lt;Object[]&gt; objects = new ArrayList&lt;&gt;();
        //2.获取链接
        connection = sql_lianjie.getConnection();
        //3.获取预编译对象
        preparedStatement = sql_lianjie.getPrepar(sql, connection, parameters);
        //4.执行查询，得到结果集
        resultSet = preparedStatement.executeQuery();
        //5.反编译对象，从结果集中获取表的元数据
        ResultSetMetaData metaData = resultSet.getMetaData();
        int count = metaData.getColumnCount();

        while (resultSet.next()) {
            Object[] o = new Object[count];
            for (int i = 0; i &lt; count; i++) {
                o[i] = resultSet.getObject(i + 1);
            }
            objects.add(o);
        }
        sql_lianjie.closeAll(connection, preparedStatement, resultSet);
        return objects;
    }
</code></pre>
<h5>查一条数据：</h5>
<pre><code>1、查询一条记录，以键值对形式返回：
</code></pre>
<pre><code>```java
public static Map&lt;String, Object&gt; yiMap(String sql, Object... parameters) throws SQLException {
        //定义需要的变量
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        //2.获取链接
        connection = sql_lianjie.getConnection();
        //3.获取预编译对象
        preparedStatement = sql_lianjie.getPrepar(sql, connection, parameters);
        //4.执行查询，得到结果集
        resultSet = preparedStatement.executeQuery();
        //5.反编译对象，从结果集中获取表的元数据
        ResultSetMetaData metaData = resultSet.getMetaData();
        int count = metaData.getColumnCount();
        HashMap&lt;String, Object&gt; map = new HashMap&lt;&gt;();
        resultSet.next();
        for (int i = 1; i &lt;= count; i++) {
            map.put(metaData.getColumnName(i), resultSet.getObject(i));
        }
        sql_lianjie.closeAll(connection, preparedStatement, resultSet);
        return map;
    }
</code></pre>
<p>2、 查询一条记录以数组方式输出：</p>
<pre><code class="java">public static Object[] queryArray(String sql, Object... parameters) throws SQLException {
        //定义需要的变量
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        //2.获取链接
        connection = sql_lianjie.getConnection();
        //3.获取预编译对象
        preparedStatement = sql_lianjie.getPrepar(sql, connection, parameters);
        //4.执行查询，得到结果集
        resultSet = preparedStatement.executeQuery();
        //5.反编译对象，从结果集中获取表的元数据
        ResultSetMetaData metaData = resultSet.getMetaData();
        int count = metaData.getColumnCount();
        Object[] o = new Object[count];
        resultSet.next();
        for (int i = 0; i &lt; count; i++) {
            o[i] = resultSet.getObject(i + 1);
        }

        sql_lianjie.closeAll(connection, preparedStatement, resultSet);
        mod stu = new mod();

        return o;
    }
</code></pre>
<p>3、 查一条数据以Javabean的方式返回</p>
<pre><code class="java">public static &lt;T&gt; T selyi(String sql, Class&lt;T&gt; cls, Object... parameters) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        T t = null;
        //2,获取链接
        try {
            connection = sql_lianjie.getConnection();
            //3,获取sql预编译对象
            preparedStatement = sql_lianjie.getPrepar(sql, connection, parameters);
            //4,执行查询结果，得到结果集
            resultSet = preparedStatement.executeQuery();
//5,反编译对象，从结果集中获取表的元素
            ResultSetMetaData metaData = resultSet.getMetaData();
            resultSet.next();//这个方法只会拿限定的需要的数据，不会有其他内容


            t = cls.newInstance();//实例化空的bean对象，用来封装数据
            int count = metaData.getColumnCount();
            for (int i = 1; i &lt;= count; i++) {
                BeanUtils.setProperty(t, metaData.getColumnName(i), resultSet.getObject(i));
            }
            sql_lianjie.closeAll(connection, preparedStatement, resultSet);
        } catch (Exception e) {
            return null;
        }
        return t;
    }
</code></pre>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/140573285</p>]]></description><guid isPermaLink="false">/archives/7fc7a0ca-82a0-49d7-9a29-0b613fb54d10</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:23:47 GMT</pubDate></item><item><title><![CDATA[Mybatis的写增删改查的方法]]></title><link>https://wiki.dgfygk.com/archives/7e611942-53ec-470d-a7b3-743ed07b26b1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Mybatis%E7%9A%84%E5%86%99%E5%A2%9E%E5%88%A0%E6%94%B9%E6%9F%A5%E7%9A%84%E6%96%B9%E6%B3%95&amp;url=/archives/7e611942-53ec-470d-a7b3-743ed07b26b1" width="1" height="1" alt="" style="opacity:0;">
<h3><strong>一、Mybatis环境配置：</strong></h3>
<p>环境的配置我在前面的文章里有详细写<a href="https://blog.csdn.net/daibadetianshi/article/details/140698718?spm=1001.2014.3001.5501" title="https://blog.csdn.net/daibadetianshi/article/details/140698718?spm=1001.2014.3001.5501">https://blog.csdn.net/daibadetianshi/article/details/140698718?spm=1001.2014.3001.5501<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F003a2ce7eb50c2e24a8c624c260c5930.png&amp;size=m" alt="">https://blog.csdn.net/daibadetianshi/article/details/140698718?spm=1001.2014.3001.5501</a></p>
<h3><strong>二、方法之用注解写业务：</strong></h3>
<p>逻辑结构：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F816a38a6600245e1a0e200c987fb5163.png&amp;size=m" alt=""></p>
<p>首先是我们要写UserDao这个接口类：</p>
<pre><code class="java">public interface UserDao {
    @Select("select * from stu")
    List&lt;stu&gt;findall();
    @Insert("INSERT INTO stu (name,age,sex) VALUES (#{name},#{age},#{sex})")
    int insertall(stu stu);

    @Delete("DELETE FROM stu WHERE ID=#{ID}")
    int delete (int ID);
@Update("UPDATE stu SET name=#{name},age=#{age},sex=#{sex} WHERE ID=#{ID}")
    int update(stu stu);
//模糊查询
@Select("select * from stu where name like '%${value}%'")
    List&lt;stu&gt; findbyname(String name);
    @Select("select count(*) from stu")
    int findcount();

@Select("select * from stu where ID=#{ID} and age=#{age}")
    stu login(@Param("ID")Integer ID,@Param("age")Integer age);
</code></pre>
<p>再将 UserDao这个接口类添加到核心配置文件：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fa2a4b865f0f6436b8dc0a91140bf86ce.png&amp;size=m" alt=""></p>
<p>开始写测试类需要的初始化变量和方法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F38a3e09a03394b078ed37cbfa9b9af12.png&amp;size=m" alt=""></p>
<p>1、首先加载MyBatis配置文件</p>
<p>2、用配置文件构建MyBatis的核心工厂</p>
<p>3、再用核心工厂创建一个可以执行SQL的会话</p>
<p>4、用getMapper的方法获取UserDao的接口</p>
<p>实现方法：</p>
<p>用UserDao接口将需要的业务方法调用</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F2b947c5ffcf84451890b829cf7692ef0.png&amp;size=m" alt=""></p>
<p>最后就是用main方法去实现：</p>
<p>1、调用初始化的配置对象</p>
<p>2、调用方法</p>
<pre><code class="java">public static void main(String[] args) throws IOException {
        TestUserDao testUserDao = new TestUserDao();
        testUserDao.star();
        testUserDao. findData();

    }
</code></pre>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>原文链接: https://blog.csdn.net/daibadetianshi/article/details/140799006</p>]]></description><guid isPermaLink="false">/archives/7e611942-53ec-470d-a7b3-743ed07b26b1</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:23:11 GMT</pubDate></item><item><title><![CDATA[EasyExcel批量读取Excel文件数据导入到MySQL表中]]></title><link>https://wiki.dgfygk.com/archives/cda15a82-8a3d-49af-98f6-2b16aaf060bf</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=EasyExcel%E6%89%B9%E9%87%8F%E8%AF%BB%E5%8F%96Excel%E6%96%87%E4%BB%B6%E6%95%B0%E6%8D%AE%E5%AF%BC%E5%85%A5%E5%88%B0MySQL%E8%A1%A8%E4%B8%AD&amp;url=/archives/cda15a82-8a3d-49af-98f6-2b16aaf060bf" width="1" height="1" alt="" style="opacity:0;">
<h3>1、EasyExcel简介</h3>
<p>官网：<a href="https://easyexcel.opensource.alibaba.com/" title="EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网">EasyExcel官方文档 - 基于Java的Excel处理工具 \| Easy Excel 官网</a></p>
<h3>2、代码实战</h3>
<p>首先引入jar包</p>
<pre><code class="html">   &lt;dependency&gt;
      &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
      &lt;artifactId&gt;easyexcel&lt;/artifactId&gt;
      &lt;version&gt;3.3.2&lt;/version&gt; 
   &lt;/dependency&gt;
</code></pre>
<h4>2.1 读取Excel</h4>
<h5>2.1.1 使用PageReadListener</h5>
<p>这种方式代码简洁，通俗易懂，直接看示例：</p>
<pre><code class="java"> public void readExcelFile2(MultipartFile file) {
    AtomicInteger count = new AtomicInteger(0);
    try {
          int batch = 10;
          EasyExcel.read(file.getInputStream(), SharePathApproveModule.class, new PageReadListener&lt;SharePathApproveModule&gt;(list -&gt; {
             System.out.println("已完成" + list.size() + "条数据读取...");
             for (SharePathApproveModule module : list) {
                  System.out.println("读取到" + count.incrementAndGet() + "条数据=&gt;" + JSONObject.toJSONString(module));

              }
              //批量写入
              //sharePathApproveMapper.insertBatch(list);
          }, batch)).sheet(0).doRead();
          //sheetNo参数不传默认0，读取第一个sheet；填0也是读取第1个sheet；填1即读取第2个sheet
      } catch (Exception e) {
          log.error("读取Excel数据异常，", e);
      }
  }
</code></pre>
<p>这种方式是直接使用了PageReadListener监听器，有兴趣的同学可以自行解读PageReadListener源码，这里我只把源码粘贴出来，本文主要讲实战，就不过多说PageReadListener。</p>
<pre><code class="java">import java.util.List;
import java.util.function.Consumer;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.util.ListUtils;

import org.apache.commons.collections4.CollectionUtils;

/**
 * page read listener
 *
 * @author Jiaju Zhuang
 */
public class PageReadListener&lt;T&gt; implements ReadListener&lt;T&gt; {
    /**
     * Default single handle the amount of data
     */
    public static int BATCH_COUNT = 100;
    /**
     * Temporary storage of data
     */
    private List&lt;T&gt; cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * consumer
     */
    private final Consumer&lt;List&lt;T&gt;&gt; consumer;

    /**
     * Single handle the amount of data
     */
    private final int batchCount;

    public PageReadListener(Consumer&lt;List&lt;T&gt;&gt; consumer) {
        this(consumer, BATCH_COUNT);
    }

    public PageReadListener(Consumer&lt;List&lt;T&gt;&gt; consumer, int batchCount) {
        this.consumer = consumer;
        this.batchCount = batchCount;
    }

    @Override
    public void invoke(T data, AnalysisContext context) {
        cachedDataList.add(data);
        if (cachedDataList.size() &gt;= batchCount) {
            consumer.accept(cachedDataList);
            cachedDataList = ListUtils.newArrayListWithExpectedSize(batchCount);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (CollectionUtils.isNotEmpty(cachedDataList)) {
            consumer.accept(cachedDataList);
        }
    }

}
</code></pre>
<p>简单的说一下PageReadListener监听器的两个方法：invoke和doAfterAllAnalysed；他们都是实现ReadListener接口里面定义的方法。</p>
<blockquote>
 <p>invoke：表示每解析完一条数据就会调用该初始方法，因此很多条件筛选或业务我们可以放在里面实现。</p>
 <p>doAfterAllAnalysed：表示每解析完一个sheet页后调用该方法。</p>
</blockquote>
<p>从源码中知道，invoke()中当数组的长度大于等于设置的长度时，则执Consumer，执行完成后在进行初始化集合；</p>
<p>doAfterAllAnalysed()中在获取完数据后，判断当前集合时候还有数据，有的话则执行Consumer。</p>
<h5>2.1.2 自定义监听器</h5>
<p>我们可以通过继承AnalysisEventListener类来自定义监听器，重新里面的invoke和doAfterAllAnalysed方法。</p>
<p>首先，新建一个Module</p>
<pre><code class="java">/**
 * @description
 * @date 2024-07-10 18:02
 **/
@Data
public class ShareModule {

    @ExcelProperty(value = "share路径", index = 0)
    @ColumnWidth(value = 10)
    private String sharePath;

    @ExcelProperty(value = "权限", index = 1)
    @ColumnWidth(value = 10)
    private String access;

    @ExcelProperty(value = "组名", index = 2)
    @ColumnWidth(value = 10)
    private String groupCn;

    @ExcelProperty(value = "历史申请人", index = 3)
    @ColumnWidth(value = 20)
    private String applicants;
}
</code></pre>
<p>接着，定义Mapper和Mapper.xml，这里我们只写一个批量插入的方法，使用</p>
<blockquote>
 <p>insert into table_name(column1,column2,column3) values(x,x,x),(x,x,x),…..</p>
 <p>这种方式减少了数据库的连接，提高插入效率，而mybatis这样执行批处理需要在数据库url配置上添加<code>rewriteBatchedStatements=true</code>，进行批处理开启。</p>
</blockquote>
<pre><code class="java">public interface ShareMapper {

    int insertBatch(@Param("shareModules")List&lt;ShareModule&gt; shareModules);

    int clearTableData();
}
</code></pre>
<pre><code>```XML
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;

&lt;mapper namespace="com.example.demo.mapper.ShareMapper"&gt;

    &lt;insert id="insertBatch"&gt;
        INSERT INTO share_path_approve (share_path, access, group_cn, applicants) VALUES
        &lt;foreach collection="shareModules" index="index" item="po" separator=","&gt;
            (#{po.sharePath}, #{po.access}, #{po.groupCn}, #{po.applicants})
        &lt;/foreach&gt;
    &lt;/insert&gt;

    &lt;update id="clearTableData"&gt;
        truncate table share_path_approve
    &lt;/update&gt;

&lt;/mapper&gt;
</code></pre>
<p>然后，就是自定义监听器，配将这个监听器交给Spring容器</p>
<pre><code class="java">import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.wangpeng.mapper.SharePathApproveMapper;
import com.example.wangpeng.po.module.SharePathApproveModule;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

/**
 * @description share auto
 * @date 2024-07-10 10:30
 */
@Slf4j
public class ShareListener extends AnalysisEventListener&lt;ShareModule&gt; {

    private List&lt;ShareModule&gt; cacheData = new ArrayList&lt;&gt;();
    private static final int BATCH_COUNT = 8;

    private final ShareMapper shareMapper;

    public SharePathApproveListener(ShareMapper shareMapper) {
        this.shareMapper= shareMapper;
    }


    @Override
    public void invoke(ShareModule shareModule, AnalysisContext analysisContext) {
        cacheData.add(shareModule);
        if (cacheData.size() &gt;= BATCH_COUNT) {
            log.info("保存数据--share auto-----{}条", cacheData.size());
            saveData();
            // 可以清理缓存数据
            cacheData = new ArrayList&lt;&gt;();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 处理最后未达到8条数据的插入
        if (!cacheData.isEmpty()) {
            log.info("保存数据--share auto-----{}条", cacheData.size());
            saveData();
        }
    }

    private void saveData() {
        // 这里可以使用MyBatis的批量插入方法
        shareMapper.insertBatch(cacheData);
    }
}
</code></pre>
<pre><code>```java
import com.example.wangpeng.excel.SharePathApproveListener;
import com.example.wangpeng.mapper.SharePathApproveMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @description
 * @date 2024-07-10 18:17
 */
@Configuration
public class EasyExcelConfig {

    @Bean
    public ShareListener shareListener(ShareMapper shareMapper) {
        return new shareListener(shareMapper);
    }
}
</code></pre>
<p>最后一步，就是编写Service和实现类、控制层接口代码</p>
<pre><code class="java">public interface ExcelService {

    ResponseResult&lt;?&gt; importExcel(MultipartFile file, Integer type);

}
</code></pre>
<pre><code>```java
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.excel.ShareListener;
import com.example.demo.mapper.ShareMapper;
import com.example.demo.po.module.ShareModule;
import com.example.demo.response.ResponseResult;
import com.example.demo.service.ExcelService;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;


/**
 * @description
 * @date 2024-06-03 15:57
 */
@Log4j
@Service
public class ExcelServiceImpl implements ExcelService {

    @Autowired
    private SharePathApproveMapper sharePathApproveMapper;


    @Override
    public ResponseResult&lt;?&gt; importExcel(MultipartFile file, Integer type) {

        try (InputStream inputStream = file.getInputStream()) {
            if (0 == type) {
                //全量覆盖
                sharePathApproveMapper.clearTableData();
            }  //增量插入

            EasyExcel.read(inputStream, ShareModule.class, new ShareListener(shareMapper))
                    .sheet()
                    .doRead();

            return ResponseResult.success();
        } catch (Exception e) {
            log.info("ShareExcel并解析 异常！", e);
            return ResponseResult.fail(e.getMessage());
        }
    }
}
</code></pre>
<pre><code>```java

@RestController
@RequestMapping(value = "/excel")
public class ExcelController {

    @Autowired
    private ExcelService excelService;

    @PostMapping(value = "/import")
    public ResponseResult&lt;String&gt; excelFile2(@RequestBody MultipartFile file, Integer type) {
        excelService.importExcel(file, type);
        return ResponseResult.success();
    }
}
</code></pre>
<p>运行如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1931c75e9ee54ddb8709ba1160ba8c89.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fed527981dc9b4f8f9439ed209fc637e0.png&amp;size=m" alt=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fd275470855e241d78c8b54fe129b8427.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/140332965</p>]]></description><guid isPermaLink="false">/archives/cda15a82-8a3d-49af-98f6-2b16aaf060bf</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Thu, 26 Sep 2024 01:22:58 GMT</pubDate></item><item><title><![CDATA[Kettle同步数据时如何借助Shell通过SSH连接MySQL数据库]]></title><link>https://wiki.dgfygk.com/archives/fa6d5657-6031-4672-a5e8-28e1009910a3</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Kettle%E5%90%8C%E6%AD%A5%E6%95%B0%E6%8D%AE%E6%97%B6%E5%A6%82%E4%BD%95%E5%80%9F%E5%8A%A9Shell%E9%80%9A%E8%BF%87SSH%E8%BF%9E%E6%8E%A5MySQL%E6%95%B0%E6%8D%AE%E5%BA%93&amp;url=/archives/fa6d5657-6031-4672-a5e8-28e1009910a3" width="1" height="1" alt="" style="opacity:0;">
<p>在实际开发中，经常会用到Kettle+Spoon来同步数据，比如：需要定时将MySQL库某张表前一天的数据同步到SQL Server（MySQL）库中等等。一般由于安全性都会提供基于秘钥的连接方式，这种情况下如何在Kettle中连接数据库呢？接下来这篇文章就介绍如何借助XShell通过SSH连接MySQL！！！</p>
<p><a href="https://download.csdn.net/download/zuihongyan518/89611352" title="下载Kettle+Spoon工具下载，MySQL、SQL Server 对应jar包、XShell绿色版下载">下载Kettle+Spoon工具下载，MySQL、SQL Server 对应jar包、XShell绿色版下载<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F003a2ce7eb50c2e24a8c624c260c5930.png&amp;size=m" alt="">https://download.csdn.net/download/zuihongyan518/89611352</a></p>
<h4>1、点击"连接”</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ffe8d79de07ff49ae8159e4e32d837bd4.png&amp;size=m" alt=""></p>
<h4>2、点击"用户身份验证”</h4>
<p>填写用户名：SSH隧道账号， 密码：SSH隧道密码</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F01f5b638cd4346a89e3c82165a4d58ea.png&amp;size=m" alt=""></p>
<h4>3、选择秘钥文件</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F19e5b01959544e319767a9fcf7ebdbf9.jpeg&amp;size=m" alt=""></p>
<p>点击浏览后出现下面框，点击导入 -&gt; <strong>选择</strong> 相对应的<strong>秘钥文件</strong> -&gt; 最后点击确定</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F1fd7f2be9fb941f38cc2f39196626959.png&amp;size=m" alt=""></p>
<h4>4、创建SSH隧道</h4>
<p>点击SSH下面的"隧道” -&gt; 点击"添加"按钮 -&gt; 添加相关的隧道信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F36589375a1fc4ceca02bc90bd8ad7213.jpeg&amp;size=m" alt=""></p>
<p>点击"添加"按钮出现如下圖所示：</p>
<blockquote>
 <p>填写源主机：localhost（一般是localhost）</p>
 <p>侦听端口：XXX（端口自定义，kettle连接数据库会用到）</p>
 <p>目标主机：数据库IP地址（需要连接的数据库IP地址）</p>
 <p>目标端口：3306 默认是mysql开放的端口（需要连接的数据库端口）</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F263d2817f642465988f289b686828825.png&amp;size=m" alt=""></p>
<h4>5、Kettle使用SSH隧道建立数据库连接</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fea7235b67c424fbea2201a97ddd176e7.png&amp;size=m" alt=""></p>
<p>到这一步就能成功连接Kettle做数据同步工作了！！！</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/140891577</p>]]></description><guid isPermaLink="false">/archives/fa6d5657-6031-4672-a5e8-28e1009910a3</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Thu, 26 Sep 2024 01:22:40 GMT</pubDate></item><item><title><![CDATA[Java将文件目录转成树结构]]></title><link>https://wiki.dgfygk.com/archives/56b6e4f2-a2f7-4817-999c-30730b29c093</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%B0%86%E6%96%87%E4%BB%B6%E7%9B%AE%E5%BD%95%E8%BD%AC%E6%88%90%E6%A0%91%E7%BB%93%E6%9E%84&amp;url=/archives/56b6e4f2-a2f7-4817-999c-30730b29c093" width="1" height="1" alt="" style="opacity:0;">
<p>在实际开发中经常会遇到返回树形结构的场景，特别是在处理文件系统或者是文件管理系统中。下面就介绍一下怎么将文件路径转成需要的树形结构。</p>
<p>在Java中，将<code>List&lt;String&gt;</code>转换成树状结构，需要定义一个树节点类（<code>TreeNode</code>），然后遍历列表中的每个路径，将每个路径分割成多个部分，并根据这些部分构建树。下面是一个简单的实现示例：</p>
<p>首先，定义一个树节点类<code>TreeNode</code>：</p>
<pre><code class="java">    public static class TreeNode {
        String val;
        Map&lt;String, TreeNode&gt; children;

        TreeNode(String val) {
            this.val = val;
            this.children = new HashMap&lt;&gt;();
        }

        void addChild(String val, TreeNode child) {
            children.put(val, child);
        }

        TreeNode getChild(String val) {
            return children.get(val);
        }
    }
</code></pre>
<p>然后，可以使用以下封装的代码将<code>List&lt;String&gt;</code>转换成树状结构：</p>
<pre><code class="java">public static Map&lt;String, TreeNode&gt; buildTree(List&lt;String&gt; paths, String split) {
        logger.info("[字符串转Tree结构]开始...params paths:{}", paths);
        if (paths == null || paths.isEmpty()) {
            return Collections.emptyMap();
        }

        Map&lt;String, TreeNode&gt; roots = new HashMap&lt;&gt;();

        for (String path : paths) {
            // 拆分路径
            String[] parts = path.split(split);
            if (parts.length == 0) {
                continue;
            }
            String rootVal = parts[0];
            TreeNode root = roots.get(rootVal);

            // 不存在，则创建它
            if (root == null) {
                root = new TreeNode(rootVal);
                roots.put(rootVal, root);
            }

            // 从根节点的子节点开始构建剩余路径
            TreeNode currentNode = root;
            for (int i = 1; i &lt; parts.length; i++) {
                String part = parts[i];
                TreeNode child = currentNode.getChild(part);

                // 子节点不存在，则创建它
                if (child == null) {
                    child = new TreeNode(part);
                    currentNode.addChild(part, child);
                }

                // 移动到子节点，继续构建路径
                currentNode = child;
            }
        }
        return roots;
    }
</code></pre>
<p>有时候我们需要将结果展示，这个时候就需要封装一个printTree方法，printTree方法使用一个prefix 参数来保持适当的缩进，从而清晰地显示树状结构。它首先打印当前节点的名称，然后递归地调用自身来打印每个子节点，每次递归调用时都增加缩进。</p>
<pre><code class="java">    public static void outTreeHtmlStyle(TreeNode root, String prefix, StringBuilder sb) {
        if (root == null) {
            return;
        }
        String emptyFlag = "&amp;nbsp;&amp;nbsp;";
        sb.append(emptyFlag).append(prefix).append(root.val).append("&lt;/br&gt;");
        for (TreeNode child : root.children.values()) {
            outTreeHtmlStyle(child, prefix + "|" + emptyFlag, sb);
        }
    }
</code></pre>
<p>最后在HTML中展示的效果如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0f3390bcfa0c339ce07e86a744860c11.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/138336501</p>]]></description><guid isPermaLink="false">/archives/56b6e4f2-a2f7-4817-999c-30730b29c093</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:21:19 GMT</pubDate></item><item><title><![CDATA[Spring Boot项目中使用MyBatis Generator (MBG) 自动生成Mapper文件]]></title><link>https://wiki.dgfygk.com/archives/9b88cb2c-a2cf-4a9b-b7e4-08cf78e6bbf7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%E9%A1%B9%E7%9B%AE%E4%B8%AD%E4%BD%BF%E7%94%A8MyBatis%20Generator%20%28MBG%29%20%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90Mapper%E6%96%87%E4%BB%B6&amp;url=/archives/9b88cb2c-a2cf-4a9b-b7e4-08cf78e6bbf7" width="1" height="1" alt="" style="opacity:0;">
<p>Spring Boot项目中使用MyBatis Generator (MBG) 自动生成Mapper文件可以很大程度上减少编码。本文着重介绍如何在实战中使用MGB自动生成Mapper文件</p>
<h3>1. 添加MyBatis Generator依赖</h3>
<p>在pom.xml中添加必要的依赖</p>
<pre><code class="html">&lt;dependency&gt;
    &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;2.3.1&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;mysql&lt;/groupId&gt;
    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
    &lt;scope&gt;runtime&lt;/scope&gt;
    &lt;version&gt;8.0.28&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;org.mybatis.generator&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-generator-core&lt;/artifactId&gt;
    &lt;version&gt;1.3.7&lt;/version&gt;
&lt;/dependency&gt;



&lt;plugin&gt;
    &lt;groupId&gt;org.mybatis.generator&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-generator-maven-plugin&lt;/artifactId&gt;
    &lt;version&gt;1.4.0&lt;/version&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
            &lt;version&gt;8.0.23&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/plugin&gt;
</code></pre>
<p>根据自需进行添加相关的依赖jar包。上面是基于MySQL，如果你是SQL Server或者别的数据库还需要自行引入相关依赖。如 SQL Server</p>
<pre><code class="html">&lt;dependency&gt;
    &lt;groupId&gt;com.microsoft.sqlserver&lt;/groupId&gt;
    &lt;artifactId&gt;mssql-jdbc&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h3>2. 创建自动生成代码配置</h3>
<p>在resource目录下创建generator.properties 和 generator-config.xml两个配置文件</p>
<blockquote>
 <p>① generator.properties：配置文件用来 添加数据库连接信息 (可选择性创建)</p>
 <p>② generator-config.xml：用于生成代码时 指定生成的 目录路径、代码函数</p>
</blockquote>
<h4>2.1 generator.properties</h4>
<pre><code class="java">jdbc.driver-class=com.mysql.cj.jdbc.Driver
jdbc.connection.url=jdbc:mysql://localhost:3306/test_db?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root123456
</code></pre>
<h4>2.2 generator-config.xml</h4>
<pre><code class="XML">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE generatorConfiguration
     PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
      "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"&gt;

&lt;generatorConfiguration&gt;
&lt;properties resource="generator.properties"/&gt;
&lt;context id="MySqlContext" targetRuntime="MyBatis3" defaultModelType="flat"&gt;
    &lt;property name="beginningDelimiter" value="`"/&gt;
    &lt;property name="endingDelimiter" value="`"/&gt;
    &lt;property name="javaFileEncoding" value="UTF-8"/&gt;
    &lt;!-- 为模型生成序列化方法--&gt;
    &lt;plugin type="org.mybatis.generator.plugins.SerializablePlugin"/&gt;
    &lt;!-- 为生成的Java模型创建一个toString方法 --&gt;
    &lt;plugin type="org.mybatis.generator.plugins.ToStringPlugin"/&gt;
    &lt;!--生成mapper.xml时覆盖原文件--&gt;
    &lt;plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/&gt;
    &lt;!--可以自定义生成代码注释--&gt;
    &lt;commentGenerator type="com.demo.example.utils.CommentGenerator"&gt;
        &lt;!-- 是否去除自动生成的注释 true：是 ： false:否 --&gt;
        &lt;property name="suppressAllComments" value="true"/&gt;
        &lt;property name="suppressDate" value="true"/&gt;
        &lt;property name="addRemarkComments" value="true"/&gt;
    &lt;/commentGenerator&gt;
    &lt;!--配置数据库连接--&gt;
    &lt;jdbcConnection driverClass="${jdbc.driver-class}"
                    connectionURL="${jdbc.connection.url}"
                    userId="${jdbc.username}"
                    password="${jdbc.password}"&gt;
        &lt;!--解决mysql驱动升级到8.0后不生成指定数据库代码的问题--&gt;
        &lt;property name="nullCatalogMeansCurrent" value="true"/&gt;
    &lt;/jdbcConnection&gt;
    &lt;!--指定生成model的路径--&gt;
    &lt;javaModelGenerator targetPackage="com.demo.example.entity" targetProject="src\main\java"/&gt;
    &lt;!--指定生成mapper.xml的路径--&gt;
    &lt;sqlMapGenerator targetPackage="mappers" targetProject="src\main\resources"/&gt;
    &lt;!--指定生成mapper接口的的路径--&gt;
    &lt;javaClientGenerator type="XMLMAPPER" targetPackage="com.demo.example.mapper"
                         targetProject="src\main\java"/&gt;
    &lt;!--生成全部表tableName设为%--&gt;
    &lt;table tableName="table_name" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"
           enableSelectByExample="false" selectByExampleQueryId="false"&gt;

        &lt;property name="useActualColumnNames" value="false"/&gt;
        &lt;property name="constructorBased" value="false"/&gt;
        &lt;generatedKey column="id" sqlStatement="JDBC" identity="true"/&gt;
    &lt;/table&gt;

&lt;/context&gt;
&lt;/generatorConfiguration&gt;
</code></pre>
<h3>3. 自定义备注添加器CommentGenerator</h3>
<pre><code class="java">import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.internal.DefaultCommentGenerator;
import org.mybatis.generator.internal.util.StringUtility;

import java.util.Properties;

/**
 * @author
 */
public class CommentGenerator extends DefaultCommentGenerator {
    private boolean addRemarkComments = false;


    @Override
    public void addConfigurationProperties(Properties properties) {
        super.addConfigurationProperties(properties);
        this.addRemarkComments = StringUtility.isTrue(properties.getProperty("addRemarkComments"));
    }


    @Override
    public void addFieldComment(Field field, IntrospectedTable introspectedTable,
                                IntrospectedColumn introspectedColumn) {
        String remarks = introspectedColumn.getRemarks();
        if (addRemarkComments &amp;&amp; StringUtility.stringHasValue(remarks)) {
            addJavaDoc2Field(field, remarks);
        }
    }


    private void addJavaDoc2Field(Field field, String remarks) {

        field.addJavaDocLine("/**");
        String[] remarkLines = remarks.split(System.getProperty("line.separator"));
        for (String remarkLine : remarkLines) {
            field.addJavaDocLine(" * " + remarkLine);
        }
        addJavadocTag(field, false);
        field.addJavaDocLine(" */");
    }

}
</code></pre>
<p>我的CommentGenerator是创建在utils包下的，随意创建，只需要把第2步的commentGenerator标签中的type属性值替换一下</p>
<pre><code class="XML">&lt;commentGenerator type="替换成自己的包路径"&gt;

&lt;/commentGenerator&gt;
</code></pre>
<h3>4. 创建代码生成器 Generator</h3>
<pre><code class="java">import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author
 * @description
 */
public class Generator {

    public static void main(String[] args) throws Exception {
        List&lt;String&gt; warnings = new ArrayList&lt;&gt;();
        boolean overwrite = true;
        //读配置文件
        InputStream is = Generator.class.getResourceAsStream("/generator-config.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(is);
        is.close();
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
        for (String warning : warnings) {
            System.out.println(warning);
        }
    }
}
</code></pre>
<p>以上关于Spring Boot项目中使用MBG自动生成Mapper文件相关配置就介绍完了，最后只需要修改成指定的表名，执行Generator类的main方法就可以了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fe9627701d11f46dd8a4196760bf1e855.png&amp;size=m" alt=""></p>
<p>可以看到执行的结果已经在相应的目录下创建了PO实体类、Mapper接口和*Mapper.xml文件</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F61987107b9464f18b330653134455ca7.png&amp;size=m" alt=""></p>
<p>几乎常用的SQL都已经生成了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fc696e82c458244598750695ece0334a8.png&amp;size=m" alt=""></p>
<p>总结一下：上面代码几乎可以直接粘贴复制使用，需要自己修改的地方有一下几处：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Ff8a15c7c67b042ccb33fd134f8a8cf7f.png&amp;size=m" alt=""></p>
<p>
 <br>
</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/140553999</p>]]></description><guid isPermaLink="false">/archives/9b88cb2c-a2cf-4a9b-b7e4-08cf78e6bbf7</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:21:02 GMT</pubDate></item><item><title><![CDATA[SpringBoot项目同时集成Mybatis和Mybatis-plus框架]]></title><link>https://wiki.dgfygk.com/archives/d5985a2d-d002-4a34-bec0-1c9b6e4600e9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E9%A1%B9%E7%9B%AE%E5%90%8C%E6%97%B6%E9%9B%86%E6%88%90Mybatis%E5%92%8CMybatis-plus%E6%A1%86%E6%9E%B6&amp;url=/archives/d5985a2d-d002-4a34-bec0-1c9b6e4600e9" width="1" height="1" alt="" style="opacity:0;">
<h4>1. 背景</h4>
<p>Mybatis-plus可以生成CRUD，减少开发中SQL编写量，但是某些情况下我们需要多表关联查询，这时候Mybatis可以手写SQL的优势就体现出来了，在实际开发中，项目里面一般都是Mybatis和Mybatis-Plus公用，但是公用有版本不兼容的问题。下面这篇文章主要介绍如何在项目中同时集成Mybatis和Mybatis-plus。</p>
<h4>2. 前期准备</h4>
<p>引入相关的jar包</p>
<pre><code class="XML">&lt;!--  mybatis依赖    --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;2.2.2&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;com.baomidou&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-plus-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;3.5.2&lt;/version&gt;
 &lt;/dependency&gt;

&lt;!--引入autoconfigure--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-spring-boot-autoconfigure&lt;/artifactId&gt;
    &lt;version&gt;2.1.4&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<h4>3. 不兼容问题</h4>
<p>如果是单纯的引入完两个jar包，就开始使用这样是会抛出下面的异常</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F7ba170a124c14d34946e7f786306c847.png&amp;size=m" alt=""></p>
<p>这个异常也是常见的，不少后端开发可能都遇到过，绑定异常，意思是识别不了对应的mapper.xml</p>
<p>有人说是版本不兼容问题，我把Mybatis（2.1.3）和Mybatis-plus（3.3.0）替换成他们说的版本依然无解。单单只是替换版本是解决不了的</p>
<p>如果你最初项目中是已经集成了Mybatis或者Mybatis-plus；先集成Mybatis的项目再集成Mybatis-plus，这个时候application.yml配置文件需要做出修改，即将原 mybatis 改成 mybatis-plus即可。</p>
<p><strong>原Mybatis配置</strong>：</p>
<pre><code class="html">mybatis:
  type-aliases-package: com.xxxx.shortchain.entity
  mapper-locations: classpath*:mappers/**/*.xml
  configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  map-underscore-to-camel-case: true
</code></pre>
<p><strong>改成Mybatis-plus配置</strong>：</p>
<pre><code class="html">mybatis-plus:
  type-aliases-package: com.xxxx.shortchain.entity
  mapper-locations: classpath*:/mappers/*.xml,classpath*:/mappers/**/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mapUnderscoreToCamelCase: true  #自动转驼峰 true开启
</code></pre>
<p>根据自己xml的实际路径修改。</p>
<h4>4. jar包冲突</h4>
<p>在集成Mybatis和Mybatis-plus时，也可能会遇到jar包冲突的情况，这时候我们可以排除一些jar包来解决冲突。</p>
<p><strong>4.1 Mybatis中</strong></p>
<pre><code class="html">        &lt;dependency&gt;
            &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
            &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;2.2.2&lt;/version&gt;
            &lt;!--原Mybatis中需排除下面2个依赖--&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;org.mybatis&lt;/groupId&gt;
                    &lt;artifactId&gt;mybatis&lt;/artifactId&gt;
                &lt;/exclusion&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;org.mybatis&lt;/groupId&gt;
                    &lt;artifactId&gt;mybatis-spring&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
        &lt;/dependency&gt;
</code></pre>
<p><strong>4.2 分页插件pageHelper中</strong></p>
<pre><code class="XML">        &lt;dependency&gt;
            &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt;
            &lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;1.2.3&lt;/version&gt;
            &lt;!--需排除下面包--&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
                    &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
        &lt;/dependency&gt;
</code></pre>
<p>至此，SpringBoot同时集成Mybatis和Mybatis-plus就已经完成了。可以正常启动项目进行测试了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F73ed589c14d546c8b03532dcf862752a.png&amp;size=m" alt=""></p>
<pre><code class="java">        DateTime dateTime = DateUtil.beginOfDay(new Date());
        QueryWrapper&lt;ShortChain&gt; queryWrapper = new QueryWrapper&lt;&gt;();
        LambdaQueryWrapper&lt;ShortChain&gt; lambda = queryWrapper.lambda();
        lambda.ge(ShortChain::getExpireDate, dateTime).eq(ShortChain::getDeleted, 0);
        List&lt;ShortChain&gt; shortChains = shortChainMapper.selectList(queryWrapper);
</code></pre>
<p>使用Mybatis-plus也能正常查询数据行</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb38478dcf7cd448baea50b820898862e.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/142377464</p>]]></description><guid isPermaLink="false">/archives/d5985a2d-d002-4a34-bec0-1c9b6e4600e9</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:20:19 GMT</pubDate></item><item><title><![CDATA[SpringBoot整合Mybatis配置多数据源]]></title><link>https://wiki.dgfygk.com/archives/0757a292-df77-419a-a69e-004343db61f0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E6%95%B4%E5%90%88Mybatis%E9%85%8D%E7%BD%AE%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90&amp;url=/archives/0757a292-df77-419a-a69e-004343db61f0" width="1" height="1" alt="" style="opacity:0;">
<p>实战项目中经常遇见需要配置多个数据源的情况，这篇文章主要介绍一下如何集成Mybatis来实现多数据源配置。</p>
<h3>1. 先导入需要的jar包</h3>
<p>需要注意的是MySQL、SQL Server不同数据库需要的驱动jar包不一样，自主选择引入</p>
<pre><code class="XML">    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
            &lt;optional&gt;true&lt;/optional&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
            &lt;artifactId&gt;fastjson&lt;/artifactId&gt;
            &lt;version&gt;2.0.32&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
            &lt;artifactId&gt;commons-lang3&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
            &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;2.2.2&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;com.microsoft.sqlserver&lt;/groupId&gt;
            &lt;artifactId&gt;mssql-jdbc&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
            &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;1.1.23&lt;/version&gt;
        &lt;/dependency&gt;


        &lt;dependency&gt;
            &lt;groupId&gt;mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
        &lt;/dependency&gt;

    &lt;/dependencies&gt;
</code></pre>
<h3>2. application.yml配置文件</h3>
<p><strong>注意</strong>：Spring Boot 2.X 版本不再支持配置继承，多数据源的话每个数据源的所有配置都需要单独配置，否则配置不会生效。</p>
<pre><code class="javascript">spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      rbk:
        driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
        # driver-class-name=com.mysql.jdbc.Driver
        # jdbc-url: jdbc:mysql://localhost:3306/database01?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;failOverReadOnly=false
        url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=rbk
        username: root
        password: root123456@
      pbi:
        driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
        # driver-class-name=com.mysql.jdbc.Driver
        # jdbc-url: #jdbc-url: jdbc:mysql://localhost:3306/database02?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;failOverReadOnly=false
        url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=pbi
        username: root
        password: root123456@
      # 初始连接数
      initial-size: 5
      # 最小连接数
      min-idle: 15
      # 最大连接数
      max-active: 30
      # 超时时间(以秒数为单位)
      remove-abandoned-timeout: 180
      # 获取连接超时时间
      max-wait: 3000
      # 连接有效性检测时间
      time-between-eviction-runs-millis: 60000
      # 连接在池中最小生存的时间
      min-evictable-idle-time-millis: 300000
      # 连接在池中最大生存的时间
      max-evictable-idle-time-millis: 900000
      # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
      test-while-idle: true
      # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个
      test-on-borrow: true
      # 是否在归还到池中前进行检验
      test-on-return: false
      # 检测连接是否有效
      validation-query: select 1
  servlet:
    multipart:
      enabled: true #默认支持文件上传
      max-file-size: 10MB  #最大支持文件大小
      max-request-size: 10MB  #最大支持请求大小，不可省略


# mybatis 配置
mybatis:
  type-aliases-package: com.study.framework.entity
  mapper-locations: classpath*:mappers/**/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
</code></pre>
<h3>3. 数据库配置类</h3>
<h4>3.1 默认库配置类</h4>
<pre><code class="java">package com.study.framework.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @description rbk数据源配置类
 *  //basePackages  自己mapper包路径
 */
@Configuration
@MapperScan(basePackages = {"com.study.framework.mapper.rbkmapper"}, sqlSessionFactoryRef = "rbkSqlSessionFactory")
public class rbkDataSourceConfiguration {

    @Primary
    @Bean(name = "rbkDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.druid.rbk")
    public DataSourceProperties rbkDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "rbkDataSource")
    public DataSource storyDataSource(@Qualifier("rbkDataSourceProperties") DataSourceProperties rbkDataSourceProperties) {
        return rbkDataSourceProperties.initializeDataSourceBuilder().build();
    }

    @Primary
    @Bean("rbkSqlSessionFactory")
    public SqlSessionFactory rbkSqlSessionFactory(@Qualifier("rbkDataSource") DataSource rbkDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(rbkDataSource);
        bean.setMapperLocations(
                // 设置mybatis的xml所在位置
                new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/rbkmapper/*.xml"));
        bean.setTypeAliasesPackage("com.study.framework.entity"); //自己entity包路径
        return bean.getObject();
    }

    @Primary
    @Bean(name = "rbkTransactionManager")
    public DataSourceTransactionManager rbkTransactionManager(@Qualifier("rbkDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Primary
    @Bean(name = "rbkSqlSessionTemplate")
    public SqlSessionTemplate rbkSqlSessionTemplate(@Qualifier("rbkSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}
</code></pre>
<h4>3.2 添加第二个数据源配置类</h4>
<pre><code class="java">package com.study.framework.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @description pbi数据源配置类
 */
@Configuration
@MapperScan(basePackages = {"com.study.framework.mapper.pbimapper"}, sqlSessionFactoryRef = "pbiSqlSessionFactory")
public class PbiDataSourceConfiguration {

    @Bean(name = "pbiDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.druid.pbi")
    public DataSourceProperties pbiDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "pbiDataSource")
    public DataSource storyDataSource(@Qualifier("pbiDataSourceProperties") DataSourceProperties pbiDataSourceProperties) {
        return pbiDataSourceProperties.initializeDataSourceBuilder().build();
    }

    @Bean("pbiSqlSessionFactory")
    public SqlSessionFactory pbiSqlSessionFactory(@Qualifier("pbiDataSource") DataSource pbiDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(pbiDataSource);
        bean.setMapperLocations(
                // 设置mybatis的xml所在位置
                new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/pbimapper/*.xml"));
        bean.setTypeAliasesPackage("com.study.framework.entity"); //自己的entity包路径
        return bean.getObject();
    }

    @Bean(name = "pbiTransactionManager")
    public DataSourceTransactionManager pbiTransactionManager(@Qualifier("pbiDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "pbiSqlSessionTemplate")
    public SqlSessionTemplate pbiSqlSessionTemplate(@Qualifier("pbiSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}
</code></pre>
<p>解释一下几个注解：</p>
<blockquote>
 <p>@Primary：可以理解成它标识了默认数据源</p>
 <p>@ConfigurationProperties(prefix = ““)：读取YML配置文件中的参数映射成一个对象，其中prefix属性表示参数的前缀</p>
 <p>@Qualifier(“pbiDataSource”)：表示查找Spring容器中名称=pbiDataSource的对象</p>
</blockquote>
<h3>4. SpringBoot启动类</h3>
<p>注意：</p>
<p>① 不需要使用自动配置，那么需要取消加载对应的自动配置类。在启动类关闭 Spring Boot 对数据源的自动化配置；</p>
<p>② 因为数据源配置类中已经使用了@MapperScan注解扫描了指定包，这时SpringBoot启动类中也不需要添加@MapperScan注解了，否则就会重复扫描，抛出警告：</p>
<pre><code class="java">WARN  o.m.s.mapper.ClassPathMapperScanner - Skipping MapperFactoryBean with name 'DemoXxxxMapper' and 'com.study.framework.mapper.pbimapper.DemoXxxxMapper' mapperInterface. Bean already defined with the same name!
</code></pre>
<p>由我们手动进行多数据源的配置：</p>
<pre><code class="java">package com.study.framework;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author 
 */
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@ComponentScan("com.study.framework.*")
//@MapperScan(value = {"com.study.framework.rbkmapper", "com.study.framework.pbimapper"})
public class FrameworkApplication {

    public static void main(String[] args) {
        SpringApplication.run(FrameworkApplication.class, args);
    }

}
</code></pre>
<p>到这一步多数据源基本就已经配置完成了，接下来就是调试阶段，自己在不同数据库下创建一张表，然后编写Mapper和xml就可以测试了。下面是我Demo项目的整体目录：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2Fb0f9207158bd4b909450241cb1ad04e5.png&amp;size=m" alt=""></p>
<h3>5. Demo测试一下</h3>
<h4>5.1 Dao层</h4>
<p>对应的数据源下编写一个Mapper接口和对应的xxMapper.xml文件</p>
<pre><code class="java">@Repository
public interface UserInfoMapper {

   UserInfoPO selectUserInfoByName(@Param("name") String name);
}


@Repository
public interface StudentInfoMapper {

   StudentInfoPO selectStudentInfoByName(@Param("name") String name);
}
</code></pre>
<p>对应的UserInfoMapper.xml 和 StudentInfoMapper.xml</p>
<pre><code class="XML">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;
&lt;mapper namespace="com.study.framework.mapper.rbkmapper.UserInfoMapper"&gt;
    &lt;resultMap id="BaseResultMap" type="com.study.framework.entity.UserInfoPO"&gt;
        &lt;id column="id" property="id" jdbcType="INTEGER"/&gt;
        &lt;result column="name" property="name" jdbcType="VARCHAR"/&gt;
        &lt;result column="work" property="workcode" jdbcType="VARCHAR"/&gt;
        &lt;result column="number" property="number" jdbcType="INTEGER"/&gt;
        &lt;result column="status" property="status" jdbcType="INTEGER"/&gt;
    &lt;/resultMap&gt;

    &lt;select id="selectUserInfoByName" resultMap="BaseResultMap"&gt;
        select id, name, work, number, status
        from user_info
        where name = #{name}
    &lt;/select&gt;

&lt;/mapper&gt;
</code></pre>
<pre><code>```XML
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;
&lt;mapper namespace="com.study.framework.mapper.rbkmapper.StudentInfoMapper"&gt;
    &lt;resultMap id="BaseResultMap" type="com.study.framework.entity.StudentInfoPO"&gt;
        &lt;id column="id" property="id" jdbcType="INTEGER"/&gt;
        &lt;result column="name" property="name" jdbcType="VARCHAR"/&gt;
        &lt;result column="student_code" property="workcode" jdbcType="VARCHAR"/&gt;
        &lt;result column="age" property="number" jdbcType="INTEGER"/&gt;
        &lt;result column="status" property="status" jdbcType="INTEGER"/&gt;
    &lt;/resultMap&gt;

    &lt;select id="selectStudentInfoByName" resultMap="BaseResultMap"&gt;
        select id, name, student_code, age, status
        from student_info
        where name = #{name}
    &lt;/select&gt;

&lt;/mapper&gt;
</code></pre>
<h4>5.2 Service层和Impl实现类</h4>
<pre><code class="java">public interface InformationService {

    UserInfoPO getUserInfo(String name);

    StudentInfoPO getStudentInfo(String name);
}


/**
 * @author 
 */
@Slf4j
@Service
public class InformationServiceImpl implements InformationService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private StudentInfoMapper studentInfoMapper;

    @Override
    public UserInfoPO getUserInfo(String userName) {
        return  userInfoMapper.selectUserInfoByName(userName);
    }

    @Override
    public StudentInfoPO getStudentInfo(String studentName) {
        return  userInfoMapper.selectStudentInfoByName(studentName);
    }
}
</code></pre>
<h4>5.3 Controller层</h4>
<pre><code class="java">/**
 * @author 
 * @description
 * @date 2024-07-15 20:14
 */
@RestController
@RequestMapping("/people")
public class PeopleController {

    @Autowired
    private InformationService informationService ;

    @RequestMapping(value = "/user/info", method = RequestMethod.GET)
    public RespResult&lt;?&gt; getUserInfo(@RequestParam("name") String name) {
        UserInfoPO userinfo= informationService.getUserInfo(name);
        return RespResult.successWithData(userinfo);
    }


    @RequestMapping(value = "/student/info", method = RequestMethod.GET)
    public RespResult&lt;?&gt; getUserInfo(@RequestParam("name") String name) {
        StudentInfoPO studentInfo= informationService.getStudentInfo(userName);
        return RespResult.successWithData(userinfo);
    }

}
</code></pre>
<h4>5.4 Postman请求结果</h4>
<pre><code class="java">//http://localhost:8008/framework/people/user/info?name=测评
{
    "code": 0,
    "message": "操作成功",
    "data": {
        "id": 2,
        "name": "测评",
        "work": "user001",
        "number": 10,
        "status": 1
    },
    "pageBase": null
}


//http://localhost:8008/framework/people/student/info?name=测评
{
    "code": 0,
    "message": "操作成功",
    "data": {
        "id": 2,
        "name": "测评",
        "student_code": "stu001",
        "age": 10,
        "status": 1
    },
    "pageBase": null
}
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/140465591</p>]]></description><guid isPermaLink="false">/archives/0757a292-df77-419a-a69e-004343db61f0</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:19:06 GMT</pubDate></item><item><title><![CDATA[解决SpringBoot集成Mybatis 配置多数据源后，打印SQL失效问题]]></title><link>https://wiki.dgfygk.com/archives/8f7d0337-2301-4a80-a78d-9e9a8dd97b2c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%A7%A3%E5%86%B3SpringBoot%E9%9B%86%E6%88%90Mybatis%20%E9%85%8D%E7%BD%AE%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90%E5%90%8E%EF%BC%8C%E6%89%93%E5%8D%B0SQL%E5%A4%B1%E6%95%88%E9%97%AE%E9%A2%98&amp;url=/archives/8f7d0337-2301-4a80-a78d-9e9a8dd97b2c" width="1" height="1" alt="" style="opacity:0;">
<p><a href="https://blog.csdn.net/zuihongyan518/article/details/140465591?spm=1001.2014.3001.5501" title="SpringBoot集成Mybati&nbsp;配置多数据源参考上一篇文章">SpringBoot集成Mybati 配置多数据源参考上一篇文章</a></p>
<p>在Spring框架中使用MyBatis时，如果配置了多数据源，可能会导致之前配置的打印SQL语句的设置失效。这通常是因为在多数据源配置中，MyBatis的SQL打印功能只对默认数据源生效，而对于其他数据源，需要手动开启SQL打印。</p>
<ol>
 <li>确保你已经在application.properties或application.yml中启用了MyBatis的SQL日志打印功能</li>
</ol>
<pre><code class="javascript"># mybatis 配置
mybatis:
  type-aliases-package: com.roborock.framework.entity
  mapper-locations: classpath*:mappers/**/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
</code></pre>
<p>单数据源直接增加上面的配置mybatis.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 即可打印SQL语句</p>
<ol>
 <li>多数据源时，只需要在其中的一个配置类中增加如下配置即可打印SQL语句，不需要在所有数据源配置中都增加</li>
</ol>
<pre><code class="java">@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration globalConfiguration() {
    return new org.apache.ibatis.session.Configuration();
}
</code></pre>
<p>然后在其中一个配置类的SqlSessionFactory方法中增加入参Configuration configuration，使用bean.setConfiguration(configuration)即可！</p>
<p>
 <br>
</p>
<p>完整代码如下：</p>
<pre><code class="java">package com.study.framework.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @description rbk数据源配置类
 *  //basePackages  自己mapper包路径
 */
@Configuration
@MapperScan(basePackages = {"com.study.framework.mapper.rbkmapper"}, sqlSessionFactoryRef = "rbkSqlSessionFactory")
public class rbkDataSourceConfiguration {

    //配置打印SQL语句configuration
    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration globalConfiguration() {
        return new org.apache.ibatis.session.Configuration();
    }


    @Primary
    @Bean(name = "rbkDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.druid.rbk")
    public DataSourceProperties rbkDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "rbkDataSource")
    public DataSource storyDataSource(@Qualifier("rbkDataSourceProperties") DataSourceProperties rbkDataSourceProperties) {
        return rbkDataSourceProperties.initializeDataSourceBuilder().build();
    }

    @Primary
    @Bean("rbkSqlSessionFactory")
    public SqlSessionFactory rbkSqlSessionFactory(@Qualifier("rbkDataSource") DataSource rbkDataSource, 
org.apache.ibatis.session.Configuration configuration) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(rbkDataSource);
        //其中一个数据源中增加如下代码即可打印SQL语句
        bean.setConfiguration(configuration);
        bean.setMapperLocations(
                // 设置mybatis的xml所在位置
                new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/rbkmapper/*.xml"));
        bean.setTypeAliasesPackage("com.study.framework.entity"); //自己entity包路径
        return bean.getObject();
    }

    @Primary
    @Bean(name = "rbkTransactionManager")
    public DataSourceTransactionManager rbkTransactionManager(@Qualifier("rbkDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Primary
    @Bean(name = "rbkSqlSessionTemplate")
    public SqlSessionTemplate rbkSqlSessionTemplate(@Qualifier("rbkSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/140469592</p>]]></description><guid isPermaLink="false">/archives/8f7d0337-2301-4a80-a78d-9e9a8dd97b2c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:18:34 GMT</pubDate></item><item><title><![CDATA[Java操作Ldap 添加域组，添加用户等实战操作]]></title><link>https://wiki.dgfygk.com/archives/9cee029e-21df-4ec4-b8c3-33ac1cbb6414</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E6%93%8D%E4%BD%9CLdap%20%E6%B7%BB%E5%8A%A0%E5%9F%9F%E7%BB%84%EF%BC%8C%E6%B7%BB%E5%8A%A0%E7%94%A8%E6%88%B7%E7%AD%89%E5%AE%9E%E6%88%98%E6%93%8D%E4%BD%9C&amp;url=/archives/9cee029e-21df-4ec4-b8c3-33ac1cbb6414" width="1" height="1" alt="" style="opacity:0;">
<h3>背景</h3>
<p>近期有一个需求是希望Share自动化，也就是说用户申请流程审批通过后自动调用Share相关操作为用户添加权限，将用户移动到指定域组等操作。</p>
<p>通过调研后发现可以使用Ldap来完成！</p>
<h3>Ldap概述</h3>
<p>LDAP是一种协议，Lightweight Directory Access Protocol，轻量级目录访问协议，用于提供目录信息查询服务，基于TCP/IP协议。</p>
<p>目录服务：是一种特殊的数据库系统，其专门针对读取，浏览和搜索操作进行特定的优化。目录一般用来包含描述性的，基于属性的信息并支持精细复杂的过滤能力。</p>
<p>LDAP目录中的信息是是按照树型结构组织，具体信息存储在条目(entry)的数据结构中。条目相当于关系数据库中表的记录；条目是具有区别名DN （Distinguished Name）的属性（Attribute），DN是用来引用条目的，DN相当于关系数据库表中的关键字（Primary Key）。</p>
<p>简称：
 <br>
 组织-公司 O：organization
 <br>
 组织单元-部门 OU：organization unit
 <br>
 国家 C：countryName
 <br>
 域名 DC：domainComponent
 <br>
 真实名称 SN：suer name
 <br>
 常用名称 CN：common name</p>
<p>有需要深入详解Ldap的同学可以自行查询，这里就不在过多介绍！</p>
<h3>Java操作Ldap实战</h3>
<p>首先，想要操作Ldap首先我们要引入两个jar包，如果是Spring项目可以直接在pom.xml中引入</p>
<pre><code class="java"> &lt;!-- https://mvnrepository.com/artifact/org.springframework.ldap/spring-ldap-core --&gt;
   &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.ldap&lt;/groupId&gt;
      &lt;artifactId&gt;spring-ldap-core&lt;/artifactId&gt;
      &lt;version&gt;3.1.0&lt;/version&gt;
   &lt;/dependency&gt;
</code></pre>
<p>还可以使用下面jar包进行操作</p>
<pre><code class="java">&lt;!-- https://mvnrepository.com/artifact/com.unboundid/unboundid-ldapsdk --&gt;
&lt;dependency&gt;
   &lt;groupId&gt;com.unboundid&lt;/groupId&gt;
   &lt;artifactId&gt;unboundid-ldapsdk&lt;/artifactId&gt;
   &lt;version&gt;6.0.9&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>下面代码都是以ldapsdk jar包为例</p>
<h4>1. 链接到LDAP服务器</h4>
<p>添加Ldap服务的相关配置，这里的用户一定要有修改权限，否则在add及modify的时候会报错</p>
<pre><code class="java">import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;


public class JndiLdapOperation {

    private static final String LDAP_URL = "ldap://localhost:389";
    private static final String USERNAME = "admin";
    private static final String PASSWORD = "your_password";
    private static final String DOMAIN_BASE_DN = "cn=test,dc=example,dc=com"

    /**
      *添加组到LDAP服务器的方法
      */
    private static Properties getLdapEnvironment() {
       Properties env = new Properties();
       env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
       env.put(Context.PROVIDER_URL, LDAP_URL ); // LDAP服务器地址
       env.put(Context.SECURITY_AUTHENTICATION, "simple");
       env.put(Context.SECURITY_PRINCIPAL, USERNAM
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/137231833</p>]]></description><guid isPermaLink="false">/archives/9cee029e-21df-4ec4-b8c3-33ac1cbb6414</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:17:52 GMT</pubDate></item><item><title><![CDATA[Java元组Tuple的使用]]></title><link>https://wiki.dgfygk.com/archives/4bff3461-bc1f-4dc8-acc5-247ba5d48f85</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%85%83%E7%BB%84Tuple%E7%9A%84%E4%BD%BF%E7%94%A8&amp;url=/archives/4bff3461-bc1f-4dc8-acc5-247ba5d48f85" width="1" height="1" alt="" style="opacity:0;">
<h4>WHY？</h4>
<p>通常情况下，一个方法返回值是一个，类型固定（当然你要封装成对象就另一说了）。如果想要在一个方法中返回多个类型不同的值（不要封装成对象）？这个时候就可以使用元组。典型的需求场景处理数据分页问题时。‌例如，‌在Web应用中，‌当需要查询分页数据时，‌通常需要返回当前页的数据记录（‌通常是一个列表）‌和总记录数（‌一个整数）‌。‌通过在一次数据库查询中同时获取这两个值，‌可以避免进行额外的数据库连接，‌从而提高效率。‌</p>
<h4>WHAT？</h4>
<p>Tuple通过泛型实现，‌允许存储不同数据类型的元素，‌并且这些元素的值之间没有关联。</p>
<h4>元组和列表list区别：</h4>
<p>二者都可能用于数据存储，包含多个数据</p>
<p>元组和列表不同的是：列表只能存储相同的数据类型；而元组可以存储不同的数据类型，比如同时存储Integer、String、List、Map、Object等，并且可以根据需求无限扩展。</p>
<p>总结一下：</p>
<blockquote>
 <p>① 元组不同于List，List存储相同的数据类型，虽然也可以存储不同的数据类型，那就不符合泛型了</p>
 <p>② 元组不同于数组，数组种存储的相同的数据类型</p>
 <p>③ 元组不同于Map，Map是Key-Value，而元组中的值与值之间没有关系</p>
 <p>④ 元组的值可以是不同数据类型，值与值之间没有关系</p>
</blockquote>
<h4>DO？</h4>
<h5>方式1：三方类库</h5>
<p>引入依赖jar包</p>
<pre><code class="html">&lt;dependency&gt;
     &lt;groupId&gt;com.github.phantomthief&lt;/groupId&gt;
     &lt;artifactId&gt;more-lambdas&lt;/artifactId&gt;
     &lt;version&gt;0.1.55&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>示例：</p>
<pre><code class="java">public static void main(String[] args) {

   ThreeTuple&lt;String, Integer, Integer&gt; threeTuple = Tuple.tuple("123", 1, 2);

   List&lt;String&gt; list = Arrays.asList("这是第1个字符串", "这是第2个字符串", "这是第3个字符串");
   TwoTuple&lt;List&lt;String&gt;, Integer&gt; twoTuple1 = Tuple.tuple(list, list.size());

   List&lt;String&gt; first = twoTuple1.first;
   Integer second = twoTuple1.second;

   System.out.println(first);
   System.out.println(second);
}
</code></pre>
<h5>方式2：自定义元组</h5>
<p>这种方式和定义对象一样，只不过需要使用泛型来保证接受各类数据。</p>
<p>二元组类</p>
<pre><code class="java">public class TwoTuple&lt;A, B&gt; {

    public final A first;
    public final B second;

    public TwoTuple(final A a, final B b) {
        this.first = a;
        this.second = b;
    }

    public A getFirst() {
        return first;
    }

    public B getSecond() {
        return second;
    }
}
</code></pre>
<p>三元组类</p>
<pre><code class="java">public class ThreeTuple&lt;A, B, C&gt; extends TwoTuple&lt;A, B&gt; {

    public final C third;

    public ThreeTuple(A a, B b, C c) {
        super(a, b);
        this.third = c;
    }

    public C getThird() {
        return third;
    }
}
</code></pre>
<p>工具类：</p>
<pre><code class="java">public class TupleUtil {

    public static &lt;A, B&gt; TwoTuple&lt;A, B&gt; tuple(final A a, final B b) {
        return new TwoTuple&lt;A, B&gt;(a, b);
    }

    public static &lt;A, B, C&gt; ThreeTuple&lt;A, B, C&gt; tuple(final A a, final B b, final C c) {
        return new ThreeTuple&lt;A, B, C&gt;(a, b, c);
    }


}
</code></pre>
<p>Demo方法</p>
<pre><code class="java">public static void main(String[] args) {

   ThreeTuple&lt;String, Double, Integer&gt; threeTuple = TupleUtil.tuple("自定义2元组", 123.456, 789);

   System.out.println("3元组first: " + threeTuple.first);
   System.out.println("3元组second: " + threeTuple.second);
   System.out.println("3元组third: " + threeTuple.third);

   List&lt;String&gt; list = Arrays.asList("这是第1个字符串", "这是第2个字符串", "这是第3个字符串");
   TwoTuple&lt;List&lt;String&gt;, Integer&gt; twoTuple = TupleUtil.tuple(list, list.size());

   System.out.println("2元组first: " + twoTuple.first);
   System.out.println("2元组second: " + twoTuple.second);
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fdirect%2F6aa12c8ae2aa4bccbf142bec839cf8d5.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/141138067</p>]]></description><guid isPermaLink="false">/archives/4bff3461-bc1f-4dc8-acc5-247ba5d48f85</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:17:04 GMT</pubDate></item><item><title><![CDATA[SQL Server分页查询]]></title><link>https://wiki.dgfygk.com/archives/d6aa744d-ef0d-4417-9f6f-3673efae59fa</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SQL%20Server%E5%88%86%E9%A1%B5%E6%9F%A5%E8%AF%A2&amp;url=/archives/d6aa744d-ef0d-4417-9f6f-3673efae59fa" width="1" height="1" alt="" style="opacity:0;">
<p>原生SQL语句，实现SQL Server分页查询</p>
<pre><code class="sql">DECLARE @pageCount INT 
DECLARE @pageIndex INT 
SET @pageCount = 10 -- #{page.pageSize}
SET @pageIndex = 2 -- #{page.pageNum}
;WITH ACTE AS (
    SELECT
        ROW_NUMBER () OVER (ORDER BY id) AS SN,
        id, table_id, name, workcode, create_time, update_time
    FROM
        lott_result_information
    ),
BCTE AS (SELECT CEILING(MAX(SN) * 1.0 / @pageCount) AS PageTotal FROM ACTE) 
SELECT *,
    (SELECT COUNT (*) FROM ACTE ) AS total_number,
    (SELECT PageTotal FROM BCTE ) AS total_page_size 
FROM
    ACTE 
WHERE
    ACTE.SN &gt; (@pageIndex - 1) * @pageCount 
    AND ACTE.SN &lt;= (@pageIndex * @pageCount)
</code></pre>
<p>实战</p>
<ol>
 <li>编写Mapper.java的接口</li>
</ol>
<pre><code class="java">List&lt;LottResultInformation&gt; selectLottResultByPage(@Param("name") String name, @Param("page") BasePage page);
</code></pre>
<ol>
 <li>编写Mapper.xml的SQL语句</li>
</ol>
<pre><code class="XML">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;
&lt;mapper namespace="com.demo.framework.mapper.LottMapper"&gt;
    &lt;resultMap id="BaseResultLottResultMap" type="com.demo.framework.entity.LottResultInformation"  extends="BasePageMap"&gt;
        &lt;id column="id" property="id" jdbcType="INTEGER"/&gt;
        &lt;result column="table_id" property="tableId" jdbcType="INTEGER"/&gt;
        &lt;result column="name" property="name" jdbcType="VARCHAR"/&gt;
        &lt;result column="workcode" property="workcode" jdbcType="VARCHAR"/&gt;
        &lt;result column="create_time" property="createTime" jdbcType="TIMESTAMP"/&gt;
        &lt;result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/&gt;
    &lt;/resultMap&gt;

    &lt;resultMap id="BasePageMap" type="com.demo.framework.entity.BasePage"&gt;
        &lt;result column="total_number" property="totalNumber" jdbcType="INTEGER"/&gt;
        &lt;result column="total_page_size" property="pageTotal" jdbcType="INTEGER"/&gt;
    &lt;/resultMap&gt;

    &lt;select id="selectLottResultByPage" resultMap="BaseResultLottResultMap"&gt;
        DECLARE @pageCount INT
        DECLARE @pageIndex INT
        SET @pageCount = #{page.pageSize}
        SET @pageIndex = #{page.pageNum}
        ;WITH ACTE AS (
            SELECT ROW_NUMBER() OVER (ORDER BY id) AS SN,
                   id, table_id, name, workcode, create_time, update_time
            FROM lott_result_information
            &lt;if test="name != null and name !=''"&gt;
                where name LIKE CONCAT('%',#{name},'%')
            &lt;/if&gt;
        ),
        BCTE AS (SELECT CEILING(MAX(SN) * 1.0 / @pageCount) AS PageTotal FROM ACTE)
        SELECT *,
               (SELECT COUNT(*) FROM ACTE)  AS total_number,
               (SELECT PageTotal FROM BCTE) AS total_page_size
        FROM ACTE
        WHERE ACTE.SN &gt; (@pageIndex - 1) * @pageCount
          AND ACTE.SN &amp;lt;= (@pageIndex * @pageCount)
    &lt;/select&gt;

&lt;/mapper&gt;
</code></pre>
<p>这样的话每次查询都会冗余两列total_number和total_page_size；这样也方便每次获取总页数和总条数。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/141852660</p>]]></description><guid isPermaLink="false">/archives/d6aa744d-ef0d-4417-9f6f-3673efae59fa</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Thu, 26 Sep 2024 01:13:19 GMT</pubDate></item><item><title><![CDATA[ELK之使用Filebeat插件收集日志到Logstash]]></title><link>https://wiki.dgfygk.com/archives/3dfdccb1-4015-4755-96ae-865ad39e61d8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ELK%E4%B9%8B%E4%BD%BF%E7%94%A8Filebeat%E6%8F%92%E4%BB%B6%E6%94%B6%E9%9B%86%E6%97%A5%E5%BF%97%E5%88%B0Logstash&amp;url=/archives/3dfdccb1-4015-4755-96ae-865ad39e61d8" width="1" height="1" alt="" style="opacity:0;">
<p>对于Springboot项目接入ELK非常方便，对于非maven，非Spring项目来说就比较复杂，这个时候我们就可以使用Filebeat插件还完成日志的收集发送工作。</p>
<h3>Filebeat介绍</h3>
<p>Filebeat是用于转发和收集数据的轻量级工具，Filebeat可以监视指定的日志文件或位置，收集日志事件，并将数据转发到Elasticsearch或Logstash进行存储。</p>
<h4>Filebeat的工作方式</h4>
<p>启动Filebeat时，它将启动一个或多个输入；这些输入将在指定的位置中查找数据，对于Filebeat所找到的每个日志，Filebeat都会启动收集器。每个收集器都读取单个日志以获取新内容，并将新日志数据发送到libbeat，libbeat将聚集事件，并将聚集的数据发送到位Filebeat配置的输出。</p>
<p>Filebeat由两个主要组成部分：Prospector（探勘者）和Harvesters（矿车）。</p>
<blockquote>
 <p>prospector： 负责找到所有需要进行读取的数据源。
  <br>
  harvesters ： 负责读取单个文件的内容，并将内容发送到output，负责文件的打开和关闭。</p>
 <p>prospector和harvesters 这些组件一起工作来读取文件并将时间数据发送到指定的output。</p>
</blockquote>
<h4>Filebeat的工作原理</h4>
<p>Filebeat可以记录每个日志文件的状态（可以理解成就是offset），并且把这个状态从注册表里更新到磁盘，offset是用来记录上一次Harvaster读取文件时，读取到的位置；这样来保证能把全部的日志都读取出来发给output。</p>
<p>如果，作为output的Elasticsearch或则Logstash变成了不可用，Filebeat将会把最后的文件读取位置保存下来，直到output重新可用的时候，快速地恢复文件数据的读取。</p>
<p>在Filebeat运行过程中，每个Prospector的状态信息都会保存在内存里，如果Filebeat出现了重启，完成重启后，会从注册表文件里恢复重启之间的状态信息，让Filebeat继续从之间已知的位置开始进行读取数据。</p>
<p>
 <br>
</p>
<h3>安装并配置Filebeat</h3>
<h4>1. 下载Filebeat</h4>
<blockquote>
 <p><strong>方式1：wget命令下载filebeat 
   <br>wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.6.2-linux-x86_64.tar.gz</strong></p>
 <p><strong>方式2：也可以直接下载到本地上传到服务器上</strong></p>
 <p><a href="https://www.elastic.co/downloads/past-releases/filebeat-7-6-2" title="下载地址：Filebeat 7.6.2 | Elastic">下载地址：Filebeat 7.6.2 \| Elastic</a></p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc2b6ce7c31fb676b0e970806b43e4f1b.png&amp;size=m" alt=""></p>
<p>下载完成后使用命令将其解压：</p>
<pre><code class="java">tar -zxvf filebeat-6.6.0-linux-x86_64.tar.gz
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff061f5dbb18f6153f9b47a95765fd0f3.png&amp;size=m" alt=""></p>
<h4>2. 配置Filebeat</h4>
<p>进入解压目录</p>
<blockquote>
 <p>[root@cenos7\~] cd filebeat-7.6.2-linux-x86_64</p>
 <h1>备份filebest主配置文件</h1>
 <p>[root@cenos7 filebeat-7.6.2-linux-x86_64] cp filebeat.yml filebeat.yml.bak</p>
 <h1>vim命令打开配置文件 进行配置</h1>
 <p>[root@cenos7 filebeat-7.6.2-linux-x86_64] vim filebeat.yml</p>
</blockquote>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff2d89b8016b68f95de1bc80f4bd944ba.png&amp;size=m" alt=""></p>
<p>配置日志采集</p>
<p>参看文档：https://www.<a href="https://so.csdn.net/so/search?q=elastic&amp;spm=1001.2101.3001.7020" title="elastic">elastic</a>.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html</p>
<pre><code class="bash">#=========================== Filebeat inputs =============================

filebeat.inputs:

# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.

- type: log

  # Change to true to enable this input configuration.
  enabled: true

  # Paths that should be crawled and fetched. Glob based paths.
  # 配置要收集日志文件路径
  paths:
    - /usr/local/test_app/log/test_app.log
  encoding: utf-8
    #- c:\programdata\elasticsearch\logs\*

  # Exclude lines. A list of regular expressions to match. It drops the lines that are
  # matching any regular expression from the list.
  exclude_lines: ['DEBUG','INFO'] #不收集 'DEBUG','INFO' 级别日志

  # Include lines. A list of regular expressions to match. It exports the lines that are
  # matching any regular expression from the list.
  include_lines: ['ERROR','Exception','^WARN'] #收集 'ERROR','Exception', 'WARN' 级别日志

  # Exclude files. A list of regular expressions to match. Filebeat drops the files that
  # are matching any regular expression from the list. By default, no files are dropped.
  #exclude_files: ['.gz$']

......


#================================ General =====================================

# The name of the shipper that publishes the network data. It can be used to group
# all the transactions sent by a single shipper in the web interface.
#name:

# The tags of the shipper are included in their own field with each
# transaction published.
#tags: ["service-X", "web-tier"]

# Optional fields that you can specify to add additional information to the
# output.
fields:
  appname: oa_test  # key-value 添加额外字段 收集到日志中
fields_under_root: true

......
#============================== Kibana =====================================

# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API.
# This requires a Kibana endpoint configuration.
setup.kibana:

  # Kibana Host
  # Scheme and port can be left out and will be set to the default (http and 5601)
  # In case you specify and additional path, the scheme is required: http://localhost:5601/path
  # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601
  #host: "localhost:5601"

  # Kibana Space ID
  # ID of the Kibana Space into which the dashboards should be loaded. By default,
  # the Default Space will be used.
  #space.id:

#============================= Elastic Cloud ==================================

# These settings simplify using Filebeat with the Elastic Cloud (https://cloud.elastic.co/).

# The cloud.id setting overwrites the `output.elasticsearch.hosts` and
# `setup.kibana.host` options.
# You can find the `cloud.id` in the Elastic Cloud web UI.
#cloud.id:

# The cloud.auth setting overwrites the `output.elasticsearch.username` and
# `output.elasticsearch.password` settings. The format is `&lt;user&gt;:&lt;pass&gt;`.
#cloud.auth:

#================================ Outputs =====================================

# Configure what output to use when sending the data collected by the beat.

#-------------------------- Elasticsearch output ------------------------------
#output.elasticsearch:
  # Array of hosts to connect to.
  # hosts: ["localhost:9200"]

  # Protocol - either `http` (default) or `https`.
  #protocol: "https"

  # Authentication credentials - either API key or username/password.
  #api_key: "id:api_key"
  #username: "elastic"
  #password: "changeme"

#----------------------------- Logstash output --------------------------------
output.logstash:
  # The Logstash hosts 配置你自己的Logstash  ip地址
  hosts: ["127.0.0.1:5044","127.0.0.1:5044"]

  # Optional SSL. By default is off.
  # List of root certificates for HTTPS server verifications
  #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]

  # Certificate for SSL client authentication
  #ssl.certificate: "/etc/pki/client/cert.pem"

  # Client Certificate Key
  #ssl.key: "/etc/pki/client/cert.key"





......


#================================= Migration ==================================

# This allows to enable 6.7 migration aliases
#migration.6_to_7.enabled: true
</code></pre>
<p>配置完成以后就可以启动filebeat。</p>
<blockquote>
 <h1>前台启动</h1>
 <p>[root@cenos7 filebeat-7.6.2-linux-x86_64] ./filebeat -e -c filebeat.yml</p>
 <p>
  <br>
 </p>
 <h1>后台启动</h1>
 <p>[root@cenos7 filebeat-7.6.2-linux-x86_64] nohup ./filebeat -e -c filebeat.yml &gt; logs/filebeat.log 2&gt;\&amp;1 \&amp;</p>
</blockquote>
<h4>3. Kibana检索</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F709cd908d017f0d23052aecd77ee5ba2.png&amp;size=m" alt=""></p>
<h3>其他重要配置</h3>
<p>一般java异常，堆栈信息比较多，换行展示的，但是Filebeat收集的时候按行收集的，这算是一个缺陷。不过我们可以配置进行全部收集。</p>
<p>例如，常见的Java堆栈日志：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa632a153c2b291c68fa744ffa7c61f06.png&amp;size=m" alt=""></p>
<p>上面文档表示很多行，在Kibana里也视为多个单独的文档，但实际这是一个异常日志，如果分开阅读会脱离上下文关系，不利于分析。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd16d5b25b961189f7a5421e8a223cfa5.png&amp;size=m" alt=""></p>
<h4>1. 配置合并日志</h4>
<pre><code class="bash">  ### Multiline options

  # Multiline can be used for log messages spanning multiple lines. This is common
  # for Java Stack Traces or C-Line Continuation

  # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
  multiline.pattern: '^\d{4}-\d{2}-\d{2}' # 匹配以 YYYY-MM-DD开头的行 

  # Defines if the pattern set under pattern should be negated or not. Default is false.
  multiline.negate: true # 是否匹配pattern的情况

  # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
  # that was (not) matched before or after or as long as a pattern is not matched based on negate.
  # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
  multiline.match: after   # 将其追加到上一行之后 pattern + negate + match 组合成一条语意为: 如果匹配 YYYY-MM-DD HH:mm:ss 开头的行，则将其合并到当前行的上一行
  multiline.max_lines: 200  # 最多匹配多少行，如果超出最大行数，则丢弃多余的行(默认500)
  multiline.timeout: 6s # 一次合并事件的超时时间，默认为 5s
</code></pre>
<p>添加上面的配置表明：非日期开头的日志 追加到 日期开头日志的后面 即：非匹配行追加到匹配行后面</p>
<p>核心参数是上述参数，以下是参数说明：</p>
<blockquote>
 <ol>
  <li>multiline.pattern：希望匹配到的结果（正则表达式）</li>
  <li>multiline.negate：值为 true 或 false。使用 false 代表匹配到的行合并到上一行；使用 true 代表不匹配的行合并到上一行</li>
  <li>multiline.match：值为 after 或 before。after 代表合并到上一行的末尾；before 代表合并到下一行的开头</li>
  <li>multiline.max_lines：合并的最大行数，默认 500</li>
  <li>multiline.timeout：一次合并事件的超时时间，默认为 5s，防止合并消耗太多时间导致 filebeat 进程卡死</li>
 </ol>
</blockquote>
<p>不好理解咱们整张表格，协助理解</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb2c7cedab26df87e02b0f0403ad33cbd.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9534249f15187d8ee796800fca5c1502.png&amp;size=m" alt=""></p>
<p>可以看到成功收集到java堆日志。</p>
<h4>2. 去除Filebeat自带的日志字段</h4>
<p>Filebeat自带了很多字段，都不是必须的，而且没有必要保存</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd4b53ac83b035d29f554924830b97fb7.png&amp;size=m" alt=""></p>
<p>添加去除Filebeat自带日志属性的配置</p>
<pre><code class="bash">#================================ Processors =====================================

# Configure processors to enhance or manipulate events generated by the beat.
processors:
  - drop_fields:
      fields: ["log","host","input","agent","ecs"]
      ignore_missing: false
  #- add_host_metadata: ~
  #- add_cloud_metadata: ~
  #- add_docker_metadata: ~
  #- add_kubernetes_metadata: ~
</code></pre>
<p>这个配置是 把"log”,“host”,“input”,“agent”,“ecs"等这些字段属性去除，不在日志中收集展示</p>
<p>添加上配置后，需要重启Filebeat。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff9354c121c4bd36bfaede3932d149ed4.png&amp;size=m" alt=""></p>
<p>Filebeat官方配置文档： <a href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-reference-yml.html" title="filebeat.reference.yml | Filebeat Reference [8.12] | Elastic">filebeat.reference.yml \| Filebeat Reference [8.12] \| Elastic</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/132278805</p>]]></description><guid isPermaLink="false">/archives/3dfdccb1-4015-4755-96ae-865ad39e61d8</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:13:07 GMT</pubDate></item><item><title><![CDATA[idea中Maven引入jar包失败，教你手动导入jar包到Maven仓库]]></title><link>https://wiki.dgfygk.com/archives/ccd7d15a-cd78-4e3e-9329-f517f4445002</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=idea%E4%B8%ADMaven%E5%BC%95%E5%85%A5jar%E5%8C%85%E5%A4%B1%E8%B4%A5%EF%BC%8C%E6%95%99%E4%BD%A0%E6%89%8B%E5%8A%A8%E5%AF%BC%E5%85%A5jar%E5%8C%85%E5%88%B0Maven%E4%BB%93%E5%BA%93&amp;url=/archives/ccd7d15a-cd78-4e3e-9329-f517f4445002" width="1" height="1" alt="" style="opacity:0;">
<p>本文主要介绍在Idea中引入jar包失败的情况下，如何手动将所需要的jar包有本地导入到Maven仓库。话不多说直接上步骤</p>
<h4>先简单了解一下产生的原因：</h4>
<p>引入包爆红的原因可能是jar包不完整，也可能是下载失败了。比如这样</p>
<ul>
 <li>如下图所示，里面没有jar包，就是一个空的指引，所以idea会有自动提示但是找不到jar包<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F41d2a1932262f76b80e343422325334c.png&amp;size=m" alt=""></li>
 <li>如下图所示，一个完整的commons-beanutils jar包是这样的</li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe126ecde88c0c8380f8efb44f2e4401d.png&amp;size=m" alt=""></p>
<h4>解决方法</h4>
<ol>
 <li>先将需要的jar包从官网中手动下载到本地 ，以hutool-all.jar 报为例</li>
</ol>
<p><a href="https://mvnrepository.com/artifact/cn.hutool/hutool-all/5.8.12" title="MVN Responsity官网地址">MVN Responsity官网地址</a>：https://mvnrepository.com/artifact/</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7800dec2ed2dd8d026dd4e2647618573.png&amp;size=m" alt=""></p>
<ol>
 <li>
  <p>将下载到本地的jar包导入到Maven仓库</p>
 </li>
 <li>
  <p>win 直接啥实用 cmd(win+R 输入cmd)格式进入到命令行窗口；Mac直接iTerm 或者自带终端进入</p>
 </li>
 <li>
  <p>使用cd 命令 切换到该jar包所在的目录下；如下图<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdc29808ad8284060f798ae19d3f03019.png&amp;size=m" alt=""></p>
 </li>
 <li>
  <p>使用mvn install:install-file 导入jar包； mvn install:install-file 命令行 主要是将本地自定义jar安装到maven仓库，然后在pom中可以直接通过dependency的方式来引用</p>
 </li>
</ol>
<pre><code class="javascript">&lt;!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;cn.hutool&lt;/groupId&gt;
    &lt;artifactId&gt;hutool-all&lt;/artifactId&gt;
    &lt;version&gt;5.8.12&lt;/version&gt;
&lt;/dependency&gt;

mvn命令行 参数说明
① -DgroupId=自定义groupId  设置的GroupId名
② -DartifactId=自定义artifactId    设置该包artifactId名
③ -Dversion=自定义版本1.0.0 设置版本号
④ -Dpackaging=jar  设置该包的类型，有如下值：pom、jar、war、maven-plugin。但是一般常用的是jar类型
⑤ -Dfile=文件路径  设置该jar包文件所在的路径与文件名

mvn install:install-file -Dfile=jar包的位置 -DgroupId=上面的groupId -DartifactId=上面的artifactId -Dversion=上面的version -Dpackaging=jar
</code></pre>
<p>示例：可以拷贝到本地编辑器 自行替换参数</p>
<p>mvn install:install-file -Dfile=\Users\username\hutool-all-5.8.12.jar -DgroupId=cn.hutool -DartifactId=hutool-all -Dversion=5.8.12 -Dpackaging=jar</p>
<ol>
 <li>在命令窗口 执行mvn命令即可</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8f7f2cd0d95e456c983bb02e2838cde9.png&amp;size=m" alt=""></p>
<p>出现 “BUILD SUCCESS” 即为执行成功，接下来就可以去pom.xml导入依赖了。</p>
<p>有需要可以关注公众号，每天更新面试题</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff7a000cb8865463fda5b74d26f9b5062.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/129874788</p>]]></description><guid isPermaLink="false">/archives/ccd7d15a-cd78-4e3e-9329-f517f4445002</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:12:32 GMT</pubDate></item><item><title><![CDATA[Java guava partition方法拆分集合&自定义集合拆分方法]]></title><link>https://wiki.dgfygk.com/archives/53f7c95d-a5b9-4998-8922-9b4a58eb4a50</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20guava%20partition%E6%96%B9%E6%B3%95%E6%8B%86%E5%88%86%E9%9B%86%E5%90%88%26%E8%87%AA%E5%AE%9A%E4%B9%89%E9%9B%86%E5%90%88%E6%8B%86%E5%88%86%E6%96%B9%E6%B3%95&amp;url=/archives/53f7c95d-a5b9-4998-8922-9b4a58eb4a50" width="1" height="1" alt="" style="opacity:0;">
<p>日常开发中，经常遇到拆分集合处理的场景，现在记录2中拆分集合的方法。</p>
<h5>1. 使用Guava包提供的集合操作工具栏 Lists.partition()方法拆分</h5>
<p>首先，引入maven依赖</p>
<pre><code class="java">&lt;dependency&gt;
    &lt;groupId&gt;com.google.guava&lt;/groupId&gt;
    &lt;artifactId&gt;guava&lt;/artifactId&gt;
    &lt;version&gt;21.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>部分源码</p>
<pre><code class="java">public static &lt;T&gt; List&lt;List&lt;T&gt;&gt; partition(List&lt;T&gt; list, int size) {
        Preconditions.checkNotNull(list);
        Preconditions.checkArgument(size &gt; 0);
        return (List)(list instanceof RandomAccess ? new Lists.RandomAccessPartition(list, size) : new Lists.Partition(list, size));
    }
</code></pre>
<p>Lists.partition方法，根据传入的size，对list进行拆分</p>
<p>使用Demo</p>
<pre><code class="java"> public static void main(String[] args) {
        List&lt;Integer&gt; list = new ArrayList&lt;&gt;();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        List&lt;List&lt;Integer&gt;&gt; partition = Lists.partition(list, 3);
        partition.forEach(l -&gt; System.out.println(JSONObject.toJSONString(l)));
}
</code></pre>
<h5>2. 自定义集合拆分方法partition</h5>
<p>使用List的 subList方法自定义集合拆分</p>
<pre><code class="java">   /**
     * 分割集合
     *
     * @param list  原集合
     * @param count 分割后，每个集合大小
     * @return java.util.List&lt;java.util.List&lt;T&gt;&gt;
     **/
    public static &lt;T&gt; List&lt;List&lt;T&gt;&gt; partition(List&lt;T&gt; list, int count) {
        List&lt;List&lt;T&gt;&gt; result = new ArrayList&lt;&gt;();

        int total = list.size();
        int pageSize = total % count == 0 ? total / count : total / count + 1;
        for (int i = 0; i &lt; pageSize; i++) {
            int start = i * count;
            int end = Math.min((start + count), total);
            List&lt;T&gt; ts = list.subList(start, end);
            result.add(ts);
        }
        return result;
    }
</code></pre>
<p>使用Demo</p>
<pre><code class="java">public static void main(String[] args) {

        List&lt;String&gt; strList = new ArrayList&lt;&gt;();
        strList.add("一月");
        strList.add("二月");
        strList.add("三月");
        strList.add("四月");
        strList.add("五月");
        strList.add("六月");
        strList.add("七月");
        strList.add("八月");
        strList.add("九月");
        strList.add("十月");
        strList.add("十一月");
        List&lt;List&lt;String&gt;&gt; listList = partition(strList, 3);
        listList.forEach(l -&gt; System.out.println(JSONObject.toJSONString(l)));
    }
</code></pre>
<p>输出结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F40cd1f5956aac83ce459b3e7605d4e5d.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/135203031</p>]]></description><guid isPermaLink="false">/archives/53f7c95d-a5b9-4998-8922-9b4a58eb4a50</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 01:12:23 GMT</pubDate></item><item><title><![CDATA[Java Stream流轻松遍历树形结构]]></title><link>https://wiki.dgfygk.com/archives/7288890f-1af9-4378-ad2f-abbc9d81bf46</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20Stream%E6%B5%81%E8%BD%BB%E6%9D%BE%E9%81%8D%E5%8E%86%E6%A0%91%E5%BD%A2%E7%BB%93%E6%9E%84&amp;url=/archives/7288890f-1af9-4378-ad2f-abbc9d81bf46" width="1" height="1" alt="" style="opacity:0;">
<p>在日常开发中，经常会遇到返回树形及遍历树形结构的需求，比如构建菜单，项目-任务树形结构，角色权限树形结构等。数据库一般使用parent_id来表示上一层级，遍历的时候一般使用递归或多层for循环，增加了数据库的查询压力。</p>
<p>为了降低数据库的查询压力，我们可以使用Java8中的Stream流一次性把数据查出来，然后通过流式处理。</p>
<p>话不多说，直接上Demo。</p>
<p>首先，创建一个Menu 菜单的实体类</p>
<pre><code class="java">@Data
@Builder
public class Menu {
    /**
     * 唯一id
     */
    public Integer id;
    /**
     * 菜单名称
     */
    public String name;
    /**
     * 上级id ，根节点为0
     */
    public Integer parentId;
    /**
     * 子节点信息
     */
    public List&lt;Menu&gt; childList;

    public Menu(Integer id, String name, Integer parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }

    public Menu(Integer id, String name, Integer parentId, List&lt;Menu&gt; childList) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
        this.childList = childList;
    }
}
</code></pre>
<p>接下来，模拟生成一颗树形结构List</p>
<menu>
 集合。
 <p></p>
 <pre><code class="java">private static List&lt;Menu&gt; createTree() {
        //模拟创建一棵树
        return  = Arrays.asList(
                new Menu(1, "目录", 0),
                new Menu(2, "第一章节", 1),
                new Menu(3, "1.1 大战光明顶", 2),
                new Menu(4, "1.2 偶遇赵敏", 2),
                new Menu(5, "1.3 迎娶周芷若", 2),
                new Menu(6, "第二章节", 1),
                new Menu(7, "2.1 挥泪斩狮王", 6),
                new Menu(8, "2.2 大意失街亭", 6),
                new Menu(9, "2.2.1 败走麦城", 7),
                new Menu(10, "2.2.2 火烧赤壁", 7),
                new Menu(11, "第三章节", 1),
                new Menu(12, "3.1 刺杀董卓", 11),
                new Menu(13, "3.1.1 貂蝉献艺", 12),
                new Menu(14, "结束", 0)
        );

    }
</code></pre>
 <p>然后，使用根据上级 parentId 获取所有子节点的方法，这里我们可以使用递归的方式去实现</p>
 <pre><code class="java">private static List&lt;Menu&gt; getAllChildrenByRoot(Menu root, List&lt;Menu&gt; all) {
        return all.stream().filter(m -&gt; Objects.equals(m.getParentId(), root.getId()))
                .peek(
                        (m) -&gt; m.setChildList(getAllChildrenByRoot(m, all))
                ).collect(Collectors.toList());
    }
</code></pre>
 <p>最后，使用main方法验证一下</p>
 <pre><code class="java">public static void main(String[] args) {
        List&lt;Menu&gt; menus = createTree();

        List&lt;Menu&gt; menuList = menus.stream().filter(m -&gt; m.getParentId() == 0)
                .peek(
                (m) -&gt; m.setChildList(getAllChildrenByRoot(m, menus))
        ).collect(Collectors.toList());
        System.out.println("/*****collect to json*****/");
        System.out.println(JSONObject.toJSON(menuList));
    }
</code></pre>
 <p>打印一下结果</p>
 <pre><code class="java">[
    {
        "name":"目录",
        "childList":[
            {
                "name":"第一章节",
                "childList":[
                    {
                        "name":"1.1 大战光明顶",
                        "childList":[

                        ],
                        "id":3,
                        "parentId":2
                    },
                    {
                        "name":"1.2 偶遇赵敏",
                        "childList":[

                        ],
                        "id":4,
                        "parentId":2
                    },
                    {
                        "name":"1.3 迎娶周芷若",
                        "childList":[

                        ],
                        "id":5,
                        "parentId":2
                    }
                ],
                "id":2,
                "parentId":1
            },
            {
                "name":"第二章节",
                "childList":[
                    {
                        "name":"2.1 挥泪斩狮王",
                        "childList":[
                            {
                                "name":"2.2.1 败走麦城",
                                "childList":[

                                ],
                                "id":9,
                                "parentId":7
                            },
                            {
                                "name":"2.2.2 火烧赤壁",
                                "childList":[

                                ],
                                "id":10,
                                "parentId":7
                            }
                        ],
                        "id":7,
                        "parentId":6
                    },
                    {
                        "name":"2.2 大意失街亭",
                        "childList":[

                        ],
                        "id":8,
                        "parentId":6
                    }
                ],
                "id":6,
                "parentId":1
            },
            {
                "name":"第三章节",
                "childList":[
                    {
                        "name":"3.1 刺杀董卓",
                        "childList":[
                            {
                                "name":"3.1.1 貂蝉献艺",
                                "childList":[

                                ],
                                "id":13,
                                "parentId":12
                            }
                        ],
                        "id":12,
                        "parentId":11
                    }
                ],
                "id":11,
                "parentId":1
            }
        ],
        "id":1,
        "parentId":0
    },
    {
        "name":"结束",
        "childList":[

        ],
        "id":14,
        "parentId":0
    }
]
</code></pre>
 <p>
  <br>
 </p>
 <p>原文链接: https://onlyou.blog.csdn.net//article/details/135277469</p>
</menu>]]></description><guid isPermaLink="false">/archives/7288890f-1af9-4378-ad2f-abbc9d81bf46</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:50:55 GMT</pubDate></item><item><title><![CDATA[Java 将 List 转换为 String常见方式]]></title><link>https://wiki.dgfygk.com/archives/b4ad5c20-549e-43ab-b613-98180addc83b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20%E5%B0%86%20List%20%E8%BD%AC%E6%8D%A2%E4%B8%BA%20String%E5%B8%B8%E8%A7%81%E6%96%B9%E5%BC%8F&amp;url=/archives/b4ad5c20-549e-43ab-b613-98180addc83b" width="1" height="1" alt="" style="opacity:0;">
<h4>将 List 转换为 String的几种方式</h4>
<ol>
 <li>使用 <strong>List的toString()方法</strong>将 List 转换为 String；结果前后会带有英文的中括号[]，如：[1, 2, 3, 4, 5]</li>
 <li>使用Java8 stream流中的<strong>Collections.joining()方法</strong>，带有逗号分隔符或自定义分隔符将集合转成String字符串</li>
 <li>使用<strong>String.join()方法</strong>将带有逗号分隔符或自定义分隔符的集合转换为字符串</li>
 <li>使用Apache commons包下的<strong>StringUtils.join()方法</strong>；转成的String结尾带有分隔符小尾巴，如：“张三,李四,”</li>
 <li>使用 StringBuilder、StringBuffer的append方法<strong>自定义join()方法</strong></li>
</ol>
<h5><strong>第一种：List.toString方法</strong></h5>
<p>List.toString()产生的结果会在开头和结尾添加英文中括号，每个字符串用逗号分隔符分隔（分隔符不可更变）。如果我们想去掉方括号的话，需要借助String.replaceAll方法。</p>
<pre><code class="java">/**
  * List的toString方法，结果前后会带有英文的中括号[]，如：[1, 2, 3, 4, 5]
  */
public static void main(String[] args) {
   List&lt;Integer&gt; asList = Arrays.asList(1, 2, 3, 4, 5);

   String list2String = asList.toString();
   System.out.println(list2String);
   //使用replaceAll方法替换 中括号
   list2String = list2String.replaceAll("\[|]", "");
   System.out.println(list2String);
}
</code></pre>
<h5><strong>第二种：Stream流转String</strong></h5>
<p>java8 stream中提供了Collectors.join()方法可用于将集合转成为具有指定分隔符、前缀和后缀的字符串。</p>
<pre><code class="java">public class Demo2 {

    /**
     * List 转 String
     * stream流中的Collections.joining方法
     **/
    public static void main(String[] args) {
        List&lt;String&gt; list = Arrays.asList("张三", "李四", "王武", null, "麻子");
        //java stream流中的Collections.joining方法
        //指定分隔符
        String collect = list.stream().collect(Collectors.joining(","));
        //没有分隔符，直接拼接
        String collect1 = list.stream().collect(Collectors.joining());
        //指定分隔符，前缀，后缀
        String collect2 = list.stream().collect(Collectors.joining(",", "{", "}"));
        System.out.println(collect);//张三,李四,王武,null,麻子
        System.out.println(collect1);//张三李四王武null麻子
        System.out.println(collect2);//{张三,李四,王武,null,麻子}
    }
}
</code></pre>
<h5>第三种：String.join()方法</h5>
<p>java8 String添加了一个特殊的Join方法，用于将集合转换为指定分隔符连接的字符串。</p>
<pre><code class="java">   /**
     * String.join()
     **/
    public static void main(String[] args) {
        List&lt;String&gt; list = Arrays.asList("张三", "李四", "王武", null, "麻子");
        String join = String.join(",", list);
        System.out.println(join);//张三,李四,王武,null,麻子
    }
</code></pre>
<h5>第四种：StringUtils.join()方法</h5>
<p>Apache commons包的外部库有一个方法StringUtils.join() 方法，它类似于 String.join() 方法，用于将集合转成指定分隔符链接的字符串；但是，如果集合中有null的话，会被转换成空串。</p>
<pre><code class="java">   /**
     * StringUtils.join()
     **/
    public static void main(String[] args) {
        List&lt;String&gt; list = Arrays.asList("张三", "李四", "王武", null, "麻子");
        //StringUtils.join方法，集合中有null的话，会转成空串""
        String join = StringUtils.join(list, ",");
        System.out.println(join); //张三,李四,王武,,麻子
    }
</code></pre>
<h5>第五种：自定义Join方法</h5>
<p>java中提供了StringBuilder、StringBuffer两个类，可以使用他们提供的append方法自定义join方法，将集合转成字符串。</p>
<pre><code class="java">   /**
     * StringBuilder、StringBuffer的append()方法
     **/
    public static void main(String[] args) {
        List&lt;String&gt; list = Arrays.asList("张三", "李四", "王武", null, "麻子");
        //3. StringBuilder、StringBuffer的append方法自定义join方法
        StringBuilder sb = new StringBuilder();
        for (String s : list) {
            sb.append(s).append(",");
        }
        //sb.insert(0, "join如下：");
        if (sb.length() &gt; 1) {
            sb.deleteCharAt(sb.length() - 1);
        }
        System.out.println(sb.toString()); //张三,李四,王武,null,麻子
    }
</code></pre>
<p>逻辑如上，上面逻辑可以封账成一个通用的join方法</p>
<pre><code class="java">public class Demo2 {

    public static class ListJoin&lt;E&gt; {

        public String join(String delimiter, Collection&lt;? extends E&gt; list) {
            Objects.requireNonNull(delimiter);
            Objects.requireNonNull(list);
            StringBuilder result = new StringBuilder();
            for (E obj : list) {
                //根据需要对空进行处理
                if (Objects.isNull(obj)) {
                    continue;
                }
                result.append(obj).append(delimiter);
            }
            if (result.length() &gt; 1) {
                result.deleteCharAt(result.length() - 1);
            }

            return result.toString();
        }
    }

    /**
     * StringBuilder、StringBuffer的append()方法
     **/
    public static void main(String[] args) {
        List&lt;String&gt; list = Arrays.asList("张三", "李四", "王武", null, "麻子");
        //上述join方法可以修改成通用的join方法
        ListJoin&lt;String&gt; strDemo = new ListJoin&lt;&gt;();
        String join = strDemo.join(",", list);
        System.out.println(join); //张三,李四,王武,麻子

        ListJoin&lt;Integer&gt; intDemo = new ListJoin&lt;&gt;();
        List&lt;Integer&gt; asList = Arrays.asList(1, 2, 3, 4, 5);
        String intJoin = intDemo.join("-", asList);
        System.out.println(intJoin); //1-2-3-4-5
    }
}
</code></pre>
<p>以上方式都可以将List集合转成字符串，具体根据业务需要使用不同方式。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/135244595</p>]]></description><guid isPermaLink="false">/archives/b4ad5c20-549e-43ab-b613-98180addc83b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:50:39 GMT</pubDate></item><item><title><![CDATA[JSqlParser-解析SQL工具类]]></title><link>https://wiki.dgfygk.com/archives/351efd2d-e810-4fc7-8c5c-55c5a004c4cd</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JSqlParser-%E8%A7%A3%E6%9E%90SQL%E5%B7%A5%E5%85%B7%E7%B1%BB&amp;url=/archives/351efd2d-e810-4fc7-8c5c-55c5a004c4cd" width="1" height="1" alt="" style="opacity:0;">
<p>一、介绍</p>
<p>JSqlParse是一款很精简的sql解析工具，将sql语句转成Java对象。</p>
<p>官网：<a href="http://jsqlparser.sourceforge.net/" title="JSqlParser - Home">JSqlParser - Home</a></p>
<p>用法：可以用于<a href="https://so.csdn.net/so/search?q=%E6%95%B0%E6%8D%AE%E6%9D%83%E9%99%90&amp;spm=1001.2101.3001.7020" title="数据权限">数据权限</a>处理，拦截sql解析改写sql等等。</p>
<ol>
 <li>首先引入jar包</li>
</ol>
<pre><code class="java">&lt;dependency&gt;
     &lt;groupId&gt;com.github.jsqlparser&lt;/groupId&gt;
     &lt;artifactId&gt;jsqlparser&lt;/artifactId&gt;
     &lt;version&gt;1.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>二、结构</p>
<ol>
 <li>expression：SQL构建相关类，比如ArrayExpression、CaseExpression、JsonExpression等表达式用于构建SQL。</li>
 <li>parser： SQL解析相关类，比如CCJSqlParserUtil主要类。</li>
 <li>schema：主要存放数据库schema相关的类 ，比如表、列等。</li>
 <li>statement：封装了数据库操作对象，create、insert、delete、select等</li>
 <li>util： 各种工具类、不同DB版本、SQL标准等处理类，如SelectUtils、DatabaseType、TablesNamesFinder等</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8740c4f6747b9fd7e287de40e78b5f8b.png&amp;size=m" alt=""></p>
<p>三、工具类封装</p>
<p>网上有相关讲解，但是没有进行整合的例子，我写了个工具类供参考！！！直接上代码</p>
<p>3.1 定义SQL枚举类型</p>
<pre><code class="java">package com.test.enums;

/**
 * @className: SqlType
 * @description: SQL 类型
 * @version: 1.0
 */
public enum SqlType {
    ALTER,
    CREATEINDEX,
    CREATETABLE,
    CREATEVIEW,
    DELETE,
    DROP,
    EXECUTE,
    INSERT,
    MERGE,
    REPLACE,
    SELECT,
    TRUNCATE,
    UPDATE,
    UPSERT,
    NONE
}
</code></pre>
<p>3.2 封装工具类</p>
<pre><code class="java">package com.test.utils;

import com.test.enums.SqlType;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.alter.Alter;
import net.sf.jsqlparser.statement.create.index.CreateIndex;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.view.CreateView;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.drop.Drop;
import net.sf.jsqlparser.statement.execute.Execute;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.merge.Merge;
import net.sf.jsqlparser.statement.replace.Replace;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.Limit;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.WithItem;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.upsert.Upsert;
import net.sf.jsqlparser.util.TablesNamesFinder;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * @projectName: springboot_demo
 * @packageName: com.test.utils
 * @description: jsqlparser解析SQL工具类
 * PlainSelect类不支持union、union all等请使用SetOperationList接口
 * @version: 1.0
 */
public class SqlParserTool {
    /**
     * 由于jsqlparser没有获取SQL类型的原始工具，并且在下面操作时需要知道SQL类型，所以编写此工具方法
     *
     * @param sql sql语句
     * @return sql类型，
     * @throws JSQLParserException
     */
    public static SqlType getSqlType(String sql) throws JSQLParserException {
        Statement sqlStmt = CCJSqlParserUtil.parse(new StringReader(sql));
        if (sqlStmt instanceof Alter) {
            return SqlType.ALTER;
        } else if (sqlStmt instanceof CreateIndex) {
            return SqlType.CREATEINDEX;
        } else if (sqlStmt instanceof CreateTable) {
            return SqlType.CREATETABLE;
        } else if (sqlStmt instanceof CreateView) {
            return SqlType.CREATEVIEW;
        } else if (sqlStmt instanceof Delete) {
            return SqlType.DELETE;
        } else if (sqlStmt instanceof Drop) {
            return SqlType.DROP;
        } else if (sqlStmt instanceof Execute) {
            return SqlType.EXECUTE;
        } else if (sqlStmt instanceof Insert) {
            return SqlType.INSERT;
        } else if (sqlStmt instanceof Merge) {
            return SqlType.MERGE;
        } else if (sqlStmt instanceof Replace) {
            return SqlType.REPLACE;
        } else if (sqlStmt instanceof Select) {
            return SqlType.SELECT;
        } else if (sqlStmt instanceof Truncate) {
            return SqlType.TRUNCATE;
        } else if (sqlStmt instanceof Update) {
            return SqlType.UPDATE;
        } else if (sqlStmt instanceof Upsert) {
            return SqlType.UPSERT;
        } else {
            return SqlType.NONE;
        }
    }

    /**
     * 获取sql操作接口,与上面类型判断结合使用
     * example:
     * String sql = "create table a(a string)";
     * SqlType sqlType = SqlParserTool.getSqlType(sql);
     * if(sqlType.equals(SqlType.SELECT)){
     * Select statement = (Select) SqlParserTool.getStatement(sql);
     * }
     *
     * @param sql
     * @return
     * @throws JSQLParserException
     */
    public static Statement getStatement(String sql) throws JSQLParserException {
        Statement sqlStmt = CCJSqlParserUtil.parse(new StringReader(sql));
        return sqlStmt;
    }

    /**
     * 获取tables的表名
     *
     * @param statement
     * @return
     */
    public static List&lt;String&gt; getTableList(Select statement) {
        TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
        List&lt;String&gt; tableList = tablesNamesFinder.getTableList(statement);
        return tableList;
    }

    /**
     * 获取join层级
     *
     * @param selectBody
     * @return
     */
    public static List&lt;Join&gt; getJoins(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            List&lt;Join&gt; joins = ((PlainSelect) selectBody).getJoins();
            return joins;
        }
        return new ArrayList&lt;Join&gt;();
    }

    /**
     * @param selectBody
     * @return
     */
    public static List&lt;Table&gt; getIntoTables(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            List&lt;Table&gt; tables = ((PlainSelect) selectBody).getIntoTables();
            return tables;
        }
        return new ArrayList&lt;Table&gt;();
    }

    /**
     * @param selectBody
     * @return
     */
    public static void setIntoTables(SelectBody selectBody, List&lt;Table&gt; tables) {
        if (selectBody instanceof PlainSelect) {
            ((PlainSelect) selectBody).setIntoTables(tables);
        }
    }

    /**
     * 获取limit值
     *
     * @param selectBody
     * @return
     */
    public static Limit getLimit(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            Limit limit = ((PlainSelect) selectBody).getLimit();
            return limit;
        }
        return null;
    }

    /**
     * 为SQL增加limit值
     *
     * @param selectBody
     * @param l
     */
    public static void setLimit(SelectBody selectBody, long l) {
        if (selectBody instanceof PlainSelect) {
            Limit limit = new Limit();
            limit.setRowCount(new LongValue(String.valueOf(l)));
            ((PlainSelect) selectBody).setLimit(limit);
        }
    }

    /**
     * 获取FromItem不支持子查询操作
     *
     * @param selectBody
     * @return
     */
    public static FromItem getFromItem(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            FromItem fromItem = ((PlainSelect) selectBody).getFromItem();
            return fromItem;
        } else if (selectBody instanceof WithItem) {
            SqlParserTool.getFromItem(((WithItem) selectBody).getSelectBody());
        }
        return null;
    }

    /**
     * 获取子查询
     *
     * @param selectBody
     * @return
     */
    public static SubSelect getSubSelect(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            FromItem fromItem = ((PlainSelect) selectBody).getFromItem();
            if (fromItem instanceof SubSelect) {
                return ((SubSelect) fromItem);
            }
        } else if (selectBody instanceof WithItem) {
            SqlParserTool.getSubSelect(((WithItem) selectBody).getSelectBody());
        }
        return null;
    }

    /**
     * 判断是否为多级子查询
     *
     * @param selectBody
     * @return
     */
    public static boolean isMultiSubSelect(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            FromItem fromItem = ((PlainSelect) selectBody).getFromItem();
            if (fromItem instanceof SubSelect) {
                SelectBody subBody = ((SubSelect) fromItem).getSelectBody();
                if (subBody instanceof PlainSelect) {
                    FromItem subFromItem = ((PlainSelect) subBody).getFromItem();
                    return subFromItem instanceof SubSelect;
                }
            }
        }
        return false;
    }

    /**
     * 获取查询字段
     *
     * @param selectBody
     * @return
     */
    public static List&lt;SelectItem&gt; getSelectItems(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            List&lt;SelectItem&gt; selectItems = ((PlainSelect) selectBody).getSelectItems();
            return selectItems;
        }
        return null;
    }

    public static void main(String[] args) throws JSQLParserException {
        Statement sqlStmt = CCJSqlParserUtil.parse(new StringReader("show databases"));
        //获取到子查询语句
        String sql = "select * from (select userid from a) a";
        SqlType sqlType = SqlParserTool.getSqlType(sql);
        if(sqlType.equals(SqlType.SELECT)){
            Select statement = (Select) SqlParserTool.getStatement(sql);
            SubSelect subSelect = SqlParserTool.getSubSelect(statement.getSelectBody());
            System.out.println(subSelect.getSelectBody());
        } 
    }
}
</code></pre>
<p>该文章基于原文链接：https://blog.csdn.net/ciqingloveless/article/details/82626304</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/130112983</p>]]></description><guid isPermaLink="false">/archives/351efd2d-e810-4fc7-8c5c-55c5a004c4cd</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:50:10 GMT</pubDate></item><item><title><![CDATA[ThreadLocal系列之ThreadLocal的内存泄漏问题]]></title><link>https://wiki.dgfygk.com/archives/07b4203a-c5cf-4f0a-b375-9b61a976b8e2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ThreadLocal%E7%B3%BB%E5%88%97%E4%B9%8BThreadLocal%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E9%97%AE%E9%A2%98&amp;url=/archives/07b4203a-c5cf-4f0a-b375-9b61a976b8e2" width="1" height="1" alt="" style="opacity:0;">
<p>开篇介绍：凡是使用过ThreadLocal的小伙伴，必须注意的的问题就是ThreadLocal的内存泄漏，这也是在面试中经常出现的一道题。下面就让我们来分析一下ThreadLocal的内存泄漏问题。</p>
<p>在上一篇博客<a href="https://blog.csdn.net/zuihongyan518/article/details/114794079?spm=1001.2014.3001.5501">由ThreadLocal引发的惨案</a>中我们分析了ThreadLocal的时候，我们知道对ThreadLocal的操作实际上是围绕ThreadLocalMap展开的。ThreadLocalMap的源码相对比较复杂, 我们从以下三个方面进行讨论。</p>
<p>其实，​ ThreadLocalMap是ThreadLocal的内部类，没有实现Map接口，用独立的方式实现了Map的功能，其内部的Entry也是独立实现。E-R图如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6726007e302ab85b214be4f4a8144ad0.png&amp;size=m" alt=""></p>
<p>下面咱们来看一下ThreadLocalMap的一部分源码：</p>
<pre><code class="java">static class ThreadLocalMap {

        /**
         * 此哈希映射中的条目使用其主引用字段作为键（始终是ThreadLocal对象）扩展了WeakReference。 请注意，空键（即entry.get（）== null）意味着不再引用该键，因此可以从表中删除该条目。 在下面的代码中，此类条目称为"陈旧条目"。
         */
        static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {
            /** 与此ThreadLocal关联的值 */
            Object value;

            Entry(ThreadLocal&lt;?&gt; k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * 初始容量-必须是2的幂。
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * 该表，根据需要调整大小。table.length必须始终为2的幂。
         */
        private Entry[] table;

        /**
         * 数组里面entrys的个数，可以用于判断table当前使用量是否超过阈值。
         */
        private int size = 0;

        /**
         * The next size value at which to resize. (扩容时 下次要调整大小的值)
         * 进行扩容的阈值，表使用量大于它的时候进行扩容
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         * 设置调整大小阈值以保持最坏的2/3负载系数。
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len. 以模为单位递增。
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 &lt; len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.  递减i模len。
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 &gt;= 0) ? i - 1 : len - 1);
        }

        /**
         * 构造一个最初包含（firstKey，firstValue）的map。
         * ThreadLocalMaps是延迟构造的，因此只有在至少有一个条目要放置时才创建一个。
         */
        ThreadLocalMap(ThreadLocal&lt;?&gt; firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode &amp; (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         * 翻译：从给定的ParentMap构造一个包含所有可继承ThreadLocals的新映射。仅由createInheritedMap调用。
         * @param parentMap the map associated with parent thread.
         * 翻译： parentMap与父线程关联的Map
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j &lt; len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal&lt;Object&gt; key = (ThreadLocal&lt;Object&gt;) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode &amp; (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         * 翻译：获取与键关联的条目。该方法本身仅处理快速路径：直接命中现有密钥。否则，它将中继到getEntryAfterMiss。这样做的目的是最大程度地提高直接打击的性能，部分原因是使此方法易于操作。
         * @param  key the thread local object key线程本地对象
         * @return the entry associated with key, or null if no such  返回一个Entry，没有则返回null
         */
        private Entry getEntry(ThreadLocal&lt;?&gt; key) {
            int i = key.threadLocalHashCode &amp; (table.length - 1);
            Entry e = table[i];
            if (e != null &amp;&amp; e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         * 翻译：在哈希槽中找不到密钥时使用getEntry方法的版本。
         * @param  key the thread local object  当前线程的本地对象
         * @param  i the table index for key's hash code hashcode值
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal&lt;?&gt; key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal&lt;?&gt; k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal&lt;?&gt; key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
            //翻译：我们不像get（）那样使用快速路径，因为使用set（）创建新条目与替换现有条目至少一样普遍，在这种情况下，快速路径经常会失败。

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode &amp; (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal&lt;?&gt; k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) &amp;&amp; sz &gt;= threshold)
                rehash();
        }

        /**
         * Remove the entry for key.
         * 翻译：删除方法
         */
        private void remove(ThreadLocal&lt;?&gt; key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode &amp; (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

        /**
         * Replace a stale entry encountered during a set operation
         * with an entry for the specified key.  The value passed in
         * the value parameter is stored in the entry, whether or not
         * an entry already exists for the specified key.
         * 翻译：将设置操作期间遇到的陈旧条目替换为指定键的条目。无论是否已存在指定键的条目，在value参数中传递的值都存储在该条目中。
         * As a side effect, this method expunges all stale entries in the
         * "run" containing the stale entry.  (A run is a sequence of entries
         * between two null slots.)
         * 翻译： 副作用是，此方法删除了包含过时条目的"运行"中的所有过时条目。 （运行是两个空槽之间的一系列输入。）
         * @param  key the key 
         * @param  value the value to be associated with key 与key关联的值
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key. 搜索密钥时遇到的第一个陈旧条目的索引。
         *
         */
        private void replaceStaleEntry(ThreadLocal&lt;?&gt; key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal&lt;?&gt; k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null &amp;&amp; slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        /**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal&lt;?&gt; k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode &amp; (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
         * Heuristically scan some cells looking for stale entries.
         * This is invoked when either a new element is added, or
         * another stale one has been expunged. It performs a
         * logarithmic number of scans, as a balance between no
         * scanning (fast but retains garbage) and a number of scans
         * proportional to number of elements, that would find all
         * garbage but would cause some insertions to take O(n) time.
         *
         * @param i a position known NOT to hold a stale entry. The
         * scan starts at the element after i.
         *
         * @param n scan control: {@code log2(n)} cells are scanned,
         * unless a stale entry is found, in which case
         * {@code log2(table.length)-1} additional cells are scanned.
         * When called from insertions, this parameter is the number
         * of elements, but when from replaceStaleEntry, it is the
         * table length. (Note: all this could be changed to be either
         * more or less aggressive by weighting n instead of just
         * using straight log n. But this version is simple, fast, and
         * seems to work well.)
         *
         * @return true if any stale entries have been removed.
         */
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null &amp;&amp; e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n &gt;&gt;&gt;= 1) != 0);
            return removed;
        }

        /**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
        private void rehash() {
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size &gt;= threshold - threshold / 4)
                resize();
        }

        /**
         * Double the capacity of the table.
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j &lt; oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal&lt;?&gt; k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode &amp; (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

        /**
         * Expunge all stale entries in the table.
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j &lt; len; j++) {
                Entry e = tab[j];
                if (e != null &amp;&amp; e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}
</code></pre>
<p>通过上面的源码我们可以看出：ThreadLocalMap的结构设计和HashMap类似，INITIAL_CAPACITY代表这个Map的初始容量；table 是一个Entry 类型的数组，用于存储数据；size 代表表中的存储数目； threshold 代表需要扩容时对应 size 的阈值。【在这里需要说明一下：阈 这个字读 yu，而非读 fa， 阀 字门中是 伐，阈 字 门中是 或】</p>
<p>而且在ThreadLocalMap中，也是用Entry来保存K-V结构数据的。不过Entry中的key只能是ThreadLocal对象，这点在构造方法中已经限定死了。另外，Entry继承WeakReference，也就是key（ThreadLocal）是弱引用，其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。</p>
<p>这里要着重说的就是这个弱引用，有时候在使用ThreadLocal的过程中有内存泄漏的情况发生，就猜测这个内存泄漏跟Entry中使用了弱引用的key有关系。这个理解其实是不全对的。下面就让我们来分析一下。</p>
<hr>
<p>都知道在Java中有四种引用：强，软，弱，虚；这里我们就先简单回顾强，弱两种；如果有人不知道这四种引用的可以去百度一下</p>
<blockquote>
 <p>强引用：如果一个对象具有强引用，它就不会被垃圾回收器回收。即使当前内存空间不足，JVM也不会回收它，而是抛出 OutOfMemoryError 错误，使程序异常终止。如果想中断强引用和某个对象之间的关联，可以显式地将引用赋值为null，这样一来的话，JVM在合适的时间就会回收该对象。</p>
 <pre><code class="java">String str = "hello";    // 强引用
str = null;              // 取消强引用
</code></pre>
 <p>弱引用：具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收，一旦发现弱引用对象，无论当前内存空间是否充足，都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程，所以并不一定能迅速发现弱引用对象。</p>
 <pre><code class="java">WeakReference&lt;String&gt; weakName = new WeakReference&lt;String&gt;("hello");
</code></pre>
 <p>相关知识点：
  <br>
  Memory overflow: 内存溢出，没有足够的内存提供申请者使用。
  <br>
  Memory leak: 内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放，造成系统内存的浪费，导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。</p>
</blockquote>
<hr>
<p>回顾完上面的概念，下面我们就详细的介绍一下ThreadLocal中存在的内存泄漏的问题吧。我们可以使用比较的方式去分析，这个比较呢其实就是强，软引用的比较</p>
<p>假设1：假设ThreadLocalMap 中的Entry使用强引用</p>
<p><strong><em>问题：ThreadLocalMap中的key使用了强引用，那么会出现内存泄漏吗？</em></strong></p>
<p>此时ThreadLocal的内存图（实线表示强引用）如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F05b041b7c0c431c7e2aadbc74ce4f92e.png&amp;size=m" alt=""></p>
<p>分析：</p>
<p>在此，我们假设在业务代码中已经使用完了ThreadLocal ，threadLocal Ref被回收了。</p>
<p>​ 但是因为threadLocalMap的Entry强引用了threadLocal，造成threadLocal无法被回收。</p>
<p>​ 在没有手动删除这个Entry以及CurrentThread依然运行的前提下，始终有强引用链 CurrentThread Ref —&gt; CurrentThread —&gt; ThreadLocalMap —&gt; Entry，Entry就不会被回收（Entry中包括了ThreadLocal实例和value），导致Entry内存泄漏。</p>
<p>​ 也就是说，ThreadLocalMap中的key使用了强引用， 是无法完全避免内存泄漏的。</p>
<hr>
<p>实际上 ThreadLocalMap中Entry是弱引用，我们就分析一下弱引用时，会不会发生内存泄漏。</p>
<p><strong><em>问题：ThreadLocalMap中的key使用了弱引用，会出现内存泄漏吗？</em></strong></p>
<p>此时ThreadLocal的内存图（实线表示强引用，虚线表示弱引用）如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F44abc9867f84b21d512355bd50fe0dac.png&amp;size=m" alt=""></p>
<p>分析：</p>
<p>同样假设我们在业务代码中使用完了ThreadLocal ，ThreadLocal Ref被回收了。</p>
<p>​由于ThreadLocalMap只持有ThreadLocal的弱引用，没有任何强引用指向Threadlocal实例, 所以Threadlocal就可以顺利被GC回收，此时Entry中的key=null。</p>
<p>​但是在没有手动删除这个Entry以及CurrentThread依然运行的前提下，也存在有强引用链 CurrentThread Ref —&gt; CurrentThread —&gt; ThreadLocalMap —&gt; Entry —&gt; value ，value不会被回收， 而这value永远不会被访问到了，导致value内存泄漏。也就是说，ThreadLocalMap中的key使用了弱引用， 也有可能内存泄漏。</p>
<hr>
<p><strong><em>问题：那么出现内存泄漏的真实原因是什么呢？</em></strong></p>
<p>通过上面两种情况的比较，内存泄漏的发生跟ThreadLocalMap中的key是否使用弱引用是没有关系的。强弱引用的不同只是发生内存泄漏的点不同，那么内存泄漏的的真正原因是什么呢？</p>
<p>其实不难发现只要使用完ThreadLocal，如果不删除Entry或者不终止当前线程CurrentThread的运行就会发生内存泄漏，显而易见造成内存泄漏的的真正原因有两点：</p>
<blockquote>
 <ol>
  <li>没有手动删除这个Entry</li>
  <li>CurrentThread依然运行</li>
 </ol>
</blockquote>
<p>第一点很好理解，只要在使用完ThreadLocal，调用其remove方法删除对应的Entry，就能避免内存泄漏。</p>
<p>​第二点稍微复杂一点， 由于ThreadLocalMap是Thread的一个属性，被当前线程所引用，所以它的生命周期跟Thread一样长。那么在使用完ThreadLocal之后，如果当前Thread也随之执行结束，ThreadLocalMap自然也会被gc回收，从根源上避免了内存泄漏。</p>
<p>总结：<strong>重点来了</strong>，ThreadLocal内存泄漏的根源是：由于ThreadLocalMap的生命周期跟Thread一样长，如果没有手动删除对应key就会导致内存泄漏。</p>
<hr>
<p><strong><em>问题：那么Entry中为什么使用弱引用，使用弱引用有什么好处吗？</em></strong></p>
<p>根据刚才的分析, 我们知道了： 无论ThreadLocalMap中的key使用哪种类型引用都无法完全避免内存泄漏，跟使用弱引用没有关系。</p>
<blockquote>
 <p>​ <strong>避免内存泄漏两种方式</strong>：</p>
 <ol>
  <li>使用完ThreadLocal，调用其remove方法删除对应的Entry</li>
  <li>使用完ThreadLocal，当前Thread也随之运行结束</li>
 </ol>
</blockquote>
<p>分析：相对第1种方式，第2种方式显然更不好控制，特别是使用线程池的时候，线程结束是不会销毁的。也就是说，只要记得在使用完ThreadLocal及时的调用remove，无论key是强引用还是弱引用都不会有问题。</p>
<p><em>那么为什么key要用弱引用呢？</em></p>
<p>事实上，在ThreadLocalMap中的set/getEntry方法中，会对key为null（也即是ThreadLocal为null）进行判断，如果为null的话，那么是会对value置为null的。</p>
<p>这说明只要使用完ThreadLocal，CurrentThread依然运行的前提下，就算忘记调用remove方法，弱引用比强引用可以多一层保障：弱引用的ThreadLocal会被回收，对应的value在下一次ThreadLocalMap调用set()，get()，remove() 中的任一方法的时候会被清除，从而避免内存泄漏。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/114870263</p>]]></description><guid isPermaLink="false">/archives/07b4203a-c5cf-4f0a-b375-9b61a976b8e2</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:49:50 GMT</pubDate></item><item><title><![CDATA[基于CentOS 7上使用Filebeat进行后台启动]]></title><link>https://wiki.dgfygk.com/archives/68098a2e-a3d9-4f3d-ba2e-bbc0260adbab</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8ECentOS%207%E4%B8%8A%E4%BD%BF%E7%94%A8Filebeat%E8%BF%9B%E8%A1%8C%E5%90%8E%E5%8F%B0%E5%90%AF%E5%8A%A8&amp;url=/archives/68098a2e-a3d9-4f3d-ba2e-bbc0260adbab" width="1" height="1" alt="" style="opacity:0;">
<h3>filebeat后台启动</h3>
<p>使用命令启动filebeat，窗口关闭或者连接中断后就回退出；当然也可以使用nohup命令后台启动，不过需要使用exit命令退出才行。</p>
<pre><code class="bash">filebeat启动
### 后台启动生成日志文件，进入file-*.*.*目录下
nohup ./filebeat -e -c filebeat.yml &gt; logs/filebeat.log 2&gt;&amp;1 &amp;

### 后台启动不生成日志
./filebeat -e -c filebeat.yml -d "Publish" &gt;/dev/null 2&gt;&amp;1 &amp;

### exit命令
exit
</code></pre>
<p>下面介绍一种一劳永逸的方式：基于CentOS的方式启动filebeat</p>
<h3>基于CentOS的方式启动filebeat</h3>
<p>在CentOS 7上使用Filebeat进行后台启动，你可以通过systemd来实现。以下是步骤和示例代码：</p>
<ul>
 <li>创建Filebeat的systemd服务文件：在进入 /etc/systemd/system目录，创建一个filebeat.service文件</li>
</ul>
<pre><code class="bash">sudo touch /etc/systemd/system/filebeat.service
</code></pre>
<ul>
 <li>编辑这个文件，并添加以下内容：</li>
</ul>
<pre><code class="bash">vim /etc/systemd/system/filebeat.service


[Unit]
Description=Filebeat
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/filebeat-7.6.2/filebeat -e -c /usr/local/filebeat-7.6.2/filebeat.yml

[Install]
WantedBy=multi-user.target
</code></pre>
<p>确保<code>ExecStart</code>指向Filebeat的正确路径，你可以通过<code>whereis filebeat</code>来查找Filebeat的安装路径。</p>
<ul>
 <li>重新加载systemd以读取新的服务文件：</li>
</ul>
<pre><code class="bash">sudo systemctl daemon-reload
</code></pre>
<ul>
 <li>启用Filebeat服务：</li>
</ul>
<pre><code class="bash">sudo systemctl enable filebeat
</code></pre>
<ul>
 <li>检查Filebeat服务状态：</li>
</ul>
<pre><code class="bash">sudo systemctl status filebeat
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2a25073dcfc36273cd90ad2f0f75bbd3.png&amp;size=m" alt=""></p>
<p>现在Filebeat应该在后台安静运行了。如果你需要停止Filebeat服务，可以使用以下命令：</p>
<pre><code class="bash">sudo systemctl stop filebeat
</code></pre>
<p>最后可设置 Filebeat开机自启，命令如下：</p>
<pre><code class="bash">sudo systemctl enable filebeat
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/136735162</p>]]></description><guid isPermaLink="false">/archives/68098a2e-a3d9-4f3d-ba2e-bbc0260adbab</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 00:49:37 GMT</pubDate></item><item><title><![CDATA[面试：HTTP请求错误状态码]]></title><link>https://wiki.dgfygk.com/archives/ab8fb191-d3ec-4425-9bf8-a1d967da6dce</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%9D%A2%E8%AF%95%EF%BC%9AHTTP%E8%AF%B7%E6%B1%82%E9%94%99%E8%AF%AF%E7%8A%B6%E6%80%81%E7%A0%81&amp;url=/archives/ab8fb191-d3ec-4425-9bf8-a1d967da6dce" width="1" height="1" alt="" style="opacity:0;">
<p>有些面试会涉及到这个问题，不知道是面试官S，还是面试官要求的严格。这TM经常出现的无非就是400，404，500，502，200等这几种。但是面试官问的几乎是偶尔才出现的。程序员中广为流传的 “面试造航母，工作拧螺丝"就是说方方面面不仅会，还要精。造航母的可以随时去拧螺丝，你螺丝的却造不了航母。社会毕竟很残酷啊！好了闲话不说了。下面就将整理的HTTP请求错误代码给大家分享一下。还是有必要熟记的。</p>
<p>|—————|——————|————————————————————-|
 <br>
 | | <strong>status</strong> | <strong>message</strong> |
 <br>
 | <strong>常见：</strong> | 200（成功） | 服务器成功返回网页 |
 <br>
 | | 404（未找到资源） | 请求网页不存在 |
 <br>
 | | 500（服务器内部错误） | 服务器遇到错误，无法完成请求。 |
 <br>
 | | 503（服务不可用） | 服务器目前无法使用（由于超载或停机维护）。通常，这只是暂时状态。 |
 <br>
 | | 502（错误网关） | 服务器作为网关或代理，从上游服务器收到无效响应。 |
 <br>
 | | | |
 <br>
 | <strong>1XX（临时响应）</strong> | 表示临时响应并需要请求者继续执行操作的状态代码。 ||
 <br>
 | | 100（继续） | 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分，正在等待其余部分。 |
 <br>
 | | 101（切换协议） | 请求者已要求服务器切换协议，服务器已确认并准备切换。 |
 <br>
 | | | |
 <br>
 | <strong>2XX（成功）</strong> | 表示成功处理了请求的状态代码。 ||
 <br>
 | | 200（成功） | 服务器已成功处理了请求。通常，这表示服务器提供了请求的网页。 |
 <br>
 | | 201（已创建） | 请求成功并且服务器创建了新的资源。 |
 <br>
 | | 202（已接受） | 服务器已接受请求，但尚未处理。 |
 <br>
 | | 203（非授权信息） | 服务器已成功处理了请求，但返回的信息可能来自另一来源。 |
 <br>
 | | 204（无内容） | 服务器成功处理了请求，但没有返回任何内容。 |
 <br>
 | | 205（重置内容） | 服务器成功处理了请求，但没有返回任何内容。 |
 <br>
 | | 206（部分内容） | 服务器成功处理了部分GET请求。 |
 <br>
 | | | |
 <br>
 | <strong>3XX（重定向）</strong> | 表示要完成请求，需要进一步操作。通常，这些状态代码用来重定向。 ||
 <br>
 | | 300（多种选择） | 针对请求，服务器可执行多种操作。服务器可根据请求者 (useragent) 选择一项操作，或提供操作列表供请求者选择。 |
 <br>
 | | 301（永久移动） | 请求的网页已永久移动到新位置。服务器返回此响应（对GET或HEAD请求的响应）时，会自动将请求者转到新位置。 |
 <br>
 | | 302（临时移动） | 服务器目前从不同位置的网页响应请求，但请求者应继续使用原有位置来进行以后的请求。 |
 <br>
 | | 303（查看其他位置） | 请求者应当对不同的位置使用单独的GET请求来检索响应时，服务器返回此代码。 |
 <br>
 | | 304（未修改） | 自从上次请求后，请求的网页未修改过。服务器返回此响应时，不会返回网页内容。 |
 <br>
 | | 305（使用代理） | 请求者只能使用代理访问请求的网页。如果服务器返回此响应，还表示请求者应使用代理。 |
 <br>
 | | 307（临时重定向） | 服务器目前从不同位置的网页响应请求，但请求者应继续使用原有位置来进行以后的请求。 |
 <br>
 | | | |
 <br>
 | 4XX（请求错误） | 这些状态代码表示请求可能出错，妨碍了服务器的处理。 ||
 <br>
 | | 400（错误请求） | 服务器不理解请求的语法。 |
 <br>
 | | 401（未授权） | 请求要求身份验证。 对于需要登录的网页，服务器可能返回此响应。 |
 <br>
 | | 403（禁止） | 服务器拒绝请求。 |
 <br>
 | | 404（未找到） | 服务器找不到请求的网页。 |
 <br>
 | | 405（方法禁用） | 禁用请求中指定的方法。 |
 <br>
 | | 406（不接受） | 无法使用请求的内容特性响应请求的网页。 |
 <br>
 | | 407（需要代理授权） | 此状态代码与 401（未授权）类似，但指定请求者应当授权使用代理。 |
 <br>
 | | 408（请求超时） | 服务器等候请求时发生超时。 |
 <br>
 | | 409（冲突） | 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。 |
 <br>
 | | 410（已删除） | 如果请求的资源已永久删除，服务器就会返回此响应。 |
 <br>
 | | 411（需要有效长度） | 服务器不接受不含有效内容长度标头字段的请求。 |
 <br>
 | | 412（为满足前提条件） | 服务器未满足请求者在请求中设置的其中一个前提条件。 |
 <br>
 | | 413（请求实体过大） | 服务器无法处理请求，因为请求实体过大，超出服务器的处理能力。 |
 <br>
 | | 414（请求的URI过长） | 请求的 URI（通常为网址）过长，服务器无法处理。 |
 <br>
 | | 415（不支持的媒体类型） | 请求的格式不受请求页面的支持。 |
 <br>
 | | 416（请求范围不符合要求） | 如果页面无法提供请求的范围，则服务器会返回此状态代码。 |
 <br>
 | | 417（未满足期望值） | 服务器未满足"期望"请求标头字段的要求。 |
 <br>
 | | | |
 <br>
 | 5XX（服务器错误） | 这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误，而不是请求出错。 ||
 <br>
 | | 500（服务器内部错误） | 服务器遇到错误，无法完成请求。 |
 <br>
 | | 501（尚未实施） | 服务器不具备完成请求的功能。 例如，服务器无法识别请求方法时可能会返回此代码。 |
 <br>
 | | 502（错误网关） | 服务器作为网关或代理，从上游服务器收到无效响应。 |
 <br>
 | | 503（服务不可用） | 服务器目前无法使用（由于超载或停机维护）。 通常，这只是暂时状态。 |
 <br>
 | | 504（网关超时） | 服务器作为网关或代理，但是没有及时从上游服务器收到请求。 |
 <br>
 | | 505（HTTP 版本不受支持） | 服务器不支持请求中所用的 HTTP 协议版本。 |</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/114881995</p>]]></description><guid isPermaLink="false">/archives/ab8fb191-d3ec-4425-9bf8-a1d967da6dce</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:49:24 GMT</pubDate></item><item><title><![CDATA[面试：Java有哪几种引用类型]]></title><link>https://wiki.dgfygk.com/archives/86c13c3a-b5fa-4d4c-8543-ba7ea777e85b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%9D%A2%E8%AF%95%EF%BC%9AJava%E6%9C%89%E5%93%AA%E5%87%A0%E7%A7%8D%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B&amp;url=/archives/86c13c3a-b5fa-4d4c-8543-ba7ea777e85b" width="1" height="1" alt="" style="opacity:0;">
<p>Interviewer：你好，请先做一下自我介绍吧</p>
<p>applicant ：interviewer 你好，俺叫小明，巴拉巴拉巴拉巴….</p>
<p>Interviewer ：小明啊，那你说一下 Java有哪几种引用类型吧？</p>
<p>applicant ： 嗯啊…enenenenaaaa ????</p>
<p>Interviewer : 今天就到这吧，回家等通知吧！</p>
<hr>
<p>下面我们就简单的回顾一下这个知识点。</p>
<p>Java在JDK1.2以后就将对象的引用分为：强、软、弱、虚四种引用类型，采用这几种引用方式，可以很灵活，便捷的控制对象的生命周期。下面就4中引用类型逐个介绍一下。</p>
<p>*</p>
<h4>1、强引用</h4>
<p>1.1、什么样的引用是强引用呢？</p>
<p>强引用声明格式：</p>
<pre><code class="java">User user = new User();
//或者
String str = new String("我是强引用");

String str="abc";

str = null;//中断（取消）引用
</code></pre>
<p>强引用是不是我们开发过程中使用最多的，哈哈！</p>
<blockquote>
 <p>强引用的特性：</p>
 <p>如果一个对象具有强引用，它就不会被垃圾回收器回收。即使当前内存空间不足，JVM也不会回收它，而是抛出 OutOfMemoryError 错误，使程序异常终止。</p>
 <p>如果想中断强引用和某个对象之间的关联，可以显式地将引用赋值为null，这样JVM在合适的时间就会回收该对象。</p>
</blockquote>
<hr>
<p>*</p>
<h4>2、软引用</h4>
<p>2.1 软引用的声明格式</p>
<p>在Java中使用 SoftReference 来声明软引用</p>
<pre><code class="java">SoftReference&lt;String&gt; str = new SoftReference&lt;&gt;("abc软引用");
</code></pre>
<blockquote>
 <p>软引用特性：</p>
 <p>在使用软引用时，如果内存的空间足够，软引用就能继续被使用，而不会被垃圾回收器回收；只有在内存空间不足时，软引用才会被垃圾回收器回收。</p>
</blockquote>
<p>示例：</p>
<pre><code class="java">class RuanYinYong {

    public static void main(String[] args) {

        SoftReference&lt;String&gt; str = new SoftReference&lt;&gt;("abc软引用");
        System.out.println(str.get());
        //通知JVM进行内存回收
        System.gc();
        System.out.println(str.get());

    }

}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc7b94d36f7c902c7b8bc99d518713eef.png&amp;size=m" alt=""></p>
<p>可以看得出来，此时JVM内存是充足，暂时还没有回收被软引用关联的对象。</p>
<p>利用软引用的特性，软引用是可以用来做缓存的。</p>
<p>软引用适合做缓存，在内存足够时，直接通过软引用取值，无需从真实来源中查询数据，可以显著地提升网站性能。当内存不足时，能让JVM进行内存回收，从而删除缓存，这时候只能从真实来源查询数据。</p>
<p>但是在实际开发中也不会使用软引用做缓存，因为现在Redis已经成成熟而且很强大了，用Redis来做缓存再好不过了。</p>
<hr>
<p>*</p>
<h4>3、弱引用</h4>
<p>3.1 弱引用声明格式</p>
<p>在Java中使用 WeakReference 来声明软引用</p>
<pre><code class="java"> WeakReference&lt;String&gt; str = new WeakReference&lt;&gt;(new String("a，我是弱引用"));
</code></pre>
<p>示例：</p>
<pre><code class="java">class RuoYinYong {

    public static void main(String[] args) {

        WeakReference&lt;String&gt; str = new WeakReference&lt;&gt;(new String("a，我是弱引用"));
        System.out.println(str.get());
        //通知JVM进行内存回收
        System.gc();
        System.out.println(str.get());

    }

}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F747f9bdc7d8641c9310032fa0b7b7f60.png&amp;size=m" alt=""></p>
<blockquote>
 <p>弱引用特性：</p>
 <p>具有弱引用的对象拥有的生命周期更短暂。</p>
 <p>因为当 JVM 进行垃圾回收，一旦发现弱引用对象，无论当前内存空间是否充足，都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程，所以并不一定能迅速发现弱引用对象。</p>
</blockquote>
<p>【注意】上述示例中的 System.out.println(str.get()); 代码块 有可能取不到str的值。上面我们已经说了GC线程是一个优先级很低的守护线程，有可能还没来的及扫描该对象所在的区域（即来不及对该对象的回收）。</p>
<p>在我们System.gc(); 之前有返回值因为我们声明了弱引用之后，立即对其输出；如果我们在声明之后的很长时间再get，有可能get不到值了</p>
<p>有兴趣的同学 可以声明弱引用后，使线程Sleep()一段时间试试看；在此就不再演示了。</p>
<hr>
<p>*</p>
<h4>4、虚引用</h4>
<p>4.1 虚引用声明格式</p>
<p>在Java中使用 PhantomReference 来声明虚引用</p>
<pre><code class="java">        ReferenceQueue&lt;String&gt; queue = new ReferenceQueue&lt;&gt;();
        PhantomReference&lt;String&gt; phantomRef = new PhantomReference&lt;&gt;("hello 虚引用", queue);
</code></pre>
<p>示例：</p>
<pre><code class="java">class Phantom {

    public static void main(String[] args) {
        ReferenceQueue&lt;String&gt; queue = new ReferenceQueue&lt;&gt;();
        PhantomReference&lt;String&gt; phantomRef = new PhantomReference&lt;&gt;("hello 虚引用", queue);
        System.out.println(phantomRef.get());
    }

}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F40ad02238c28fd5fd26d5788a3bb6463.png&amp;size=m" alt=""></p>
<p>结果是我们根本get不到值</p>
<blockquote>
 <p>虚引用特性：</p>
 <p>顾名思义，虚引用就是形同虚设，如果一个对象仅持有虚引用，那么它相当于没有引用，在任何时候都可能被垃圾回收器回收。虚引用不能单独使用，必须配合引用队列一起使用</p>
</blockquote>
<p>虚引用必须和引用队列关联使用，当垃圾回收器准备回收一个对象时，如果发现它还有虚引用，就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用，来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列，那么就可以在所引用的对象的内存被回收之前采取必要的行动。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/114891433</p>]]></description><guid isPermaLink="false">/archives/86c13c3a-b5fa-4d4c-8543-ba7ea777e85b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:48:44 GMT</pubDate></item><item><title><![CDATA[ip地址转换成long型]]></title><link>https://wiki.dgfygk.com/archives/81951078-908d-4678-b9f4-7ae2d024a629</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ip%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2%E6%88%90long%E5%9E%8B&amp;url=/archives/81951078-908d-4678-b9f4-7ae2d024a629" width="1" height="1" alt="" style="opacity:0;">
<p>前段时间有以需求你，为了实现高效率的地址查找以及比对，将ip地址转化为long型存入redis进行处理，并按照ip进行AB（根据ip的奇偶）对照。</p>
<p>然后先梳理一下转换的逻辑和一些基础知识。</p>
<pre><code class="java">/**
     * 之前存储ip都是存字符串，没想过ip地址竟然还能存Long，于是测试了一下确实可以。
     * 举个简单例子先，比如有个ip地址是169.254.85.85，
     * 对应的二进制是10101001.11111110.1010101.1010101，将小数点去掉后变为101010011111111010101011010101，
     * 可以看到除了最低的位置的整数位置没有发生变化，其他三个位置的整数分别相对原来的位置左移了8位，16位，24位，而在二进制中每左移一位，就相当于*2，
     * 也就是说对应的每段数字分别乘2的8次方，16次方以及24次方，总体来说思路就是去掉ip地址的小数点后重新计算，
     * 上面这个ip地址转化为long型整数是2852017493，
     * 当然如果你觉得这样计算比较麻烦也可以通过将每段数字转化为十六进制后拼接，之后在计算器中直接转化为十进制效果是一样的，对应的十六进制是A9FE5555
     *
     * 
     * 基础运算知识：
     * 运算符解释：
     * (1)、"|"符号的含义就是将两个数字的二进制按右端对齐，只要有数字1就换1，当两个数字都是0才是0例如
     * 示例：202（二进制：11001010）| 163（10100011）可以像下面这样看 == 235（11101011）
     *      11001010
     *      10100011
     *      将有一的换成一，全是零的是零：11101011
     *
     * (2)、&lt;&lt; 这个位运算符号(左移运算)，这两个都是位运算符号，实例如下：
     * 示例：202（11001010）&lt;&lt;2 == 808（1100101000） 表示202的二进制数字在右端添加两个零所表示的数字
     *
     * (3)、操作符为&lt;&lt;，向左移动，右边的低位补0，高位的就舍弃掉了，将二进制看做整数，左移1位就相当于乘以2。
     * (4)、无符号右移：操作符为&gt;&gt;&gt;，向右移动，右边的舍弃掉，左边补0。
     * (5)、有符号右移：操作符为&gt;&gt;，向右移动，右边的舍弃掉，左边补什么取决于原来最高位是什么，原来是1就补1，原来是0就补0，将二进制看做整数，右移1位相当于除以2。
     * 
     */
</code></pre>
<h4>1、ip地址转long型</h4>
<pre><code class="java">private static Long ipToLong(String ipStr) {

        long ipLong = 0;

        if (ipStr != null &amp;&amp; ipStr.length() &gt; 0) {
            //将ip(点分十进制的形式 a.b.c.d) 地址按.分割
            String[] ipSplit = ipStr.split("\\.");
            try {
                if (ipSplit.length != 4) {
                    throw new Exception("IP Format Error");
                }
                for (int i = 0; i &lt; ipSplit.length; i++) {
                    int temp = Integer.parseInt(ipSplit[i]);
                    ipLong += temp * (1L &lt;&lt; (ipSplit.length - i - 1) * 8);
                }
            } catch (Exception e) {
                log.error("ip address to long type Error :{}", e);
            }
        } else {
            log.info("ip address is null or is ''");
        }
        return ipLong;

    }
</code></pre>
<h4>2、long型ip转点分十进制格式</h4>
<pre><code class="java">/**
     * 将long型IP地址转换回点分十进制表示的字符串类型
     * @param ipLong
     * @return java.lang.String
     * @throws Exception
     */
    private static String ipLongToString(long ipLong) {
        StringBuffer ipStr = new StringBuffer();
        try {
            if (ipLong &lt; 0) {
                throw new Exception("Can not to IP...");
            }
            //最高8位，直接右移24位
            ipStr.append((ipLong &gt;&gt;&gt; 24));
            ipStr.append(".");
            //将高8位设置0，然后右移16位
            ipStr.append(((ipLong &amp; 0x00FFFFFF) &gt;&gt;&gt; 16));//获得高8位，6个f对应的是24个1，最高8位设置空为0，之后右移16位将前面多余的16位去掉，以下类推即可
            ipStr.append(".");
            //将高16位设置0，然后右移8位
            ipStr.append((ipLong &amp; 0x0000FFFF) &gt;&gt;&gt; 8); //前16位 设置0，移除低8位，16个1，高16位设置为0
            ipStr.append(".");
            //将高24位设置0
            ipStr.append(ipLong &amp; 0x000000FF); //前24位 设置0，留低8位，8个1，高24位设置为0
        } catch (Exception e) {
            log.error("Long type ip to point ten type error :{}", e);
        }
        return ipStr.toString();
    }
</code></pre>
<p>测试结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F02c4e523e6d234e7040d0ee5fbc3578f.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/107619097</p>]]></description><guid isPermaLink="false">/archives/81951078-908d-4678-b9f4-7ae2d024a629</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:47:06 GMT</pubDate></item><item><title><![CDATA[Linux下nginx 的常用命令]]></title><link>https://wiki.dgfygk.com/archives/6df02c89-df0d-413b-af93-53c392e956a1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E4%B8%8Bnginx%20%E7%9A%84%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4&amp;url=/archives/6df02c89-df0d-413b-af93-53c392e956a1" width="1" height="1" alt="" style="opacity:0;">
<h2>启动</h2>
<p>启动代码格式：nginx安装目录地址 -c nginx配置文件地址</p>
<p>例如：</p>
<pre><code class="java">[root@LinuxServer sbin]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
</code></pre>
<h2>停止</h2>
<p>nginx的停止有三种方式：</p>
<p>*</p>
<h3>从容停止</h3>
<p>　　1、查看进程号</p>
<pre><code class="java">[root@LinuxServer ~]# ps -ef|grep nginx
</code></pre>
<pre><code>```
</code></pre>
<p>2、杀死进程</p>
<pre><code class="java">[root@LinuxServer ~]# kill -QUIT 2072
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa973e05afa31402466f9a3ef256cade8.png&amp;size=m" alt=""></p>
<p>*</p>
<h3>快速停止</h3>
<p>1、查看进程号</p>
<pre><code class="java">[root@LinuxServer ~]# ps -ef|grep nginx
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb41c1bbede7d5dd43e164c8320cda862.png&amp;size=m" alt=""></p>
<p>2、杀死进程</p>
<pre><code class="java">[root@LinuxServer ~]# kill -TERM 2132
或 [root@LinuxServer ~]# kill -INT 2132
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2d7db102cedffd1e7f3e47a553803358.png&amp;size=m" alt=""></p>
<p>*</p>
<h3>强制停止</h3>
<pre><code class="java">[root@LinuxServer ~]# pkill -9 nginx
</code></pre>
<h2>重启</h2>
<h3>1、验证nginx配置文件是否正确</h3>
<h4>方法一：进入nginx安装目录sbin下，输入命令./nginx -t</h4>
<p>看到如下显示 nginx.conf syntax is ok</p>
<p>nginx.conf test is successful</p>
<p>说明配置文件正确！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fcc75bdf07e04d0e95ecbd96f622e2097.png&amp;size=m" alt=""></p>
<h4>方法二：在启动命令-c前加-t</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3041f781122225041e7102b474696f77.png&amp;size=m" alt=""></p>
<h3>2、重启Nginx服务</h3>
<h4>方法一：进入nginx可执行目录sbin下，输入命令<strong>./nginx -s reload</strong>即可</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8f7ffbf393f1b857ad4bb161bb1755be.png&amp;size=m" alt=""></p>
<h4>方法二：查找当前nginx进程号，然后输入命令：kill -HUP 进程号 实现重启nginx服务</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F702aeae847df0b5c9d9e410165b3f5cf.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/107210344</p>]]></description><guid isPermaLink="false">/archives/6df02c89-df0d-413b-af93-53c392e956a1</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 00:46:49 GMT</pubDate></item><item><title><![CDATA[mysql之模糊查询的方法]]></title><link>https://wiki.dgfygk.com/archives/9544c2db-a57b-412f-9ad0-16fb69d43b91</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=mysql%E4%B9%8B%E6%A8%A1%E7%B3%8A%E6%9F%A5%E8%AF%A2%E7%9A%84%E6%96%B9%E6%B3%95&amp;url=/archives/9544c2db-a57b-412f-9ad0-16fb69d43b91" width="1" height="1" alt="" style="opacity:0;">
<p>Mysql模糊查询正常情况下在数据量小的时候，速度还是可以的，但是不容易看出查询的效率，在数据量达到百万级，千万级的甚至亿级时 mysql查询的效率是很关键的，也是很重要的。</p>
<p>一、一般情况下 like 模糊查询的写法：前后模糊匹配</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F44ce3368683b719f5c78b7b396cc2a91.png&amp;size=m" alt=""></p>
<p>这个SQL语句，如果用explain解释的话，我们很容易就能发觉它是没有走索引搜索，而是对全表进行了扫描，这显然是很慢的，还有卡库的可能。</p>
<p>如果将上面的SQL语句改成下面的写法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbd63e8ec4030a0251788456711ec9d20.png&amp;size=m" alt="">就是把'keyword'前面的%去掉了，这样的写法用explain解释看到，SQL语句使用了索引，这样就可以大大的提高查询的效率。</p>
<p>有时候，我们在做模糊查询的时候，并非要想查询的关键词都在开头，所以如果不是特别的要求，“keywork%“并不合适所有的模糊查询。</p>
<p>二、模糊查询高效的方法：</p>
<p>1、LOCATE（'substr',str,pos）方法</p>
<p>解释：返回 substr 在 str 中第一次出现的位置，如果 substr 在 str 中不存在，返回值为 0 。如果pos存在，返回 substr 在 str 第pos个位置后第一次出现的位置，如果 substr 在 str 中不存在，返回值为0。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fae5e8a68595faf2ab482ee31fb75c7b6.png&amp;size=m" alt=""></p>
<p>实例：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbbe6d9ac36c5bb3b3c65ce9b76ad479a.png&amp;size=m" alt=""></p>
<p>备注：keyword是要搜索的内容，business为被匹配的字段，查询出所有存在keyword的数据</p>
<p>2、POSITION('substr' IN `field`)方法</p>
<p>其实我们就可以把这个方法当做是locate（）方法的别名，因为它和locate（）方法的作用是一样的。</p>
<p>实例：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6751aa68e0731359060745d7e80970c8.png&amp;size=m" alt=""></p>
<p>3、INSTR(`str`,'substr')方法</p>
<p>格式：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2b5933052208ffdd0f2a3c9685591a63.png&amp;size=m" alt=""></p>
<p>实例：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5d9622c8ed8d691848f2aab3b9d1a1ed.png&amp;size=m" alt=""></p>
<p>除了上述的方法外，还有一个函数FIND_IN_SET，这个方法比较特殊，他所查询的必须要是以”，“分隔开。</p>
<p>4、FIND_IN_SET(str1,str2):</p>
<p>返回str2中str1所在的位置索引，其中str2必须以”,“分割开。</p>
<p>格式：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb1a3014cf938593fa711ec08ab60f80d.png&amp;size=m" alt=""></p>
<p>实例：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3429cb2422e12f60723b414a01d15b1b.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb83015d2a0489e6dca6f893ef0c2ef50.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/81131042</p>]]></description><guid isPermaLink="false">/archives/9544c2db-a57b-412f-9ad0-16fb69d43b91</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Thu, 26 Sep 2024 00:46:30 GMT</pubDate></item><item><title><![CDATA[Redis 慢查询 命令 slowlog]]></title><link>https://wiki.dgfygk.com/archives/f73aceea-1d3e-4cd4-8575-5a955d7472d8</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%20%E6%85%A2%E6%9F%A5%E8%AF%A2%20%E5%91%BD%E4%BB%A4%20slowlog&amp;url=/archives/f73aceea-1d3e-4cd4-8575-5a955d7472d8" width="1" height="1" alt="" style="opacity:0;">
<p>slowlog 此命令用于读取和重置Redis慢查询日志。</p>
<h4>Redis slowlog 官方概述:</h4>
<p>Redis Slow Log是一个用于记录超过指定执行时间的查询的系统。执行时间不包括与客户端交谈，发送答复等I / O操作，而仅包括实际执行命令所需的时间（这是命令执行的唯一阶段，在该阶段线程被阻塞并且不能同时满足其他要求）。</p>
<p>通俗的讲使用这个命令可以读取或重置 Redis 慢速查询日志。就是 redis 可以把执行时间超过我们设定值的命令记录下来，slowlog 是记录到内存中的，所以查询非常快。这里的<strong>执行时间</strong>不包括 I/O 操作,比如与客户端,发送应答等,就是实际执行命令所需的时间。</p>
<h3>设置 Redis slowlog</h3>
<p>官方文档介绍：</p>
<p>（1）您可以使用以下两个参数配置慢速日志：slowlog-log-slower-than告诉Redis执行命令的时间（以微秒为单位）要超过多少秒才能记录命令。请注意，负数将禁用慢速日志记录，而零值将强制记录每个命令。 slowlog-max-len是慢日志的长度。最小值为零。当记录新命令并且慢速日志已经达到最大长度时，最旧的日志将从已记录命令队列中删除，以腾出空间。</p>
<p>（2）可以通过编辑redis.conf或在服务器运行时使用CONFIG GET和CONFIG SET命令来完成配置。</p>
<p>总结起来就是两种方式：</p>
<ol>
 <li>可以通过配置 redis.conf 来完成。</li>
 <li>运行时，使用 CONFIG GET 和 CONFIG SET 命令配置。</li>
</ol>
<p>这里我们主要说的是第二种方式，你可以使用两个参数来配置慢日志：slowlog-log-slow-than * ,告诉 Redis, 记录超过 * <strong>微秒</strong> 内的命令执行情况。</p>
<p>需要注意的是，设置负数表示禁用 slowlog ，而设置 0 则强制记录每个命令的执行情况。
 <br>
 以下是 slowlog 的设置使用说明：</p>
<pre><code>redis 127.0.0.1:6379&gt;config set slowlog-log-slower-than 10000
"OK"

redis 127.0.0.1:6379&gt;config get slowlog-log-slower-than
1)  "slowlog-log-slower-than"
2)  "10000"
</code></pre>
<h4>读取 SLOW LOG</h4>
<p>慢速日志存储在内存中，因此不会写入有关慢速命令执行信息的文件。 这样一来，慢速日志就非常快了，因为您可以启用所有命令的日志记录（将slowlog-log-slower-than-config参数设置为零），而对性能的影响不大。</p>
<p>要读取慢日志，请使用SLOWLOG GET命令，该命令返回慢日志中的每个条目。 可以仅返回N个最新条目，该条目将附加参数传递给命令（例如SLOWLOG GET 10）。</p>
<p>请注意，您需要最新版本（需要 2.2.12及以上版本的 redis 才能支持）的redis-cli才能读取慢速日志输出，因为它使用了协议的某些功能，这些功能以前在redis-cli中没有实现（深度嵌套的多批量回复）。</p>
<p>那么问题来了，slowlog 是记录再内存中的，如果记录所有的命令 log 会不会把内存不够用呢？</p>
<p>答案是当然不会。slowlog 记录的 log 数是有最大长度限制的，我们可以通过 slowlog-max-len 来查询 slowlog 的最大长度。最小值为零。当一个新的命令被记录下来，并且如果已经达到它的最大长度时，最旧的一个 log 将从队列中删除，FIFO 。</p>
<p>另外，使用 slowlog len 命令可以查看当前已记录的数量。
 <br>
 使用 slowlog reset 重置已记录的 slowlog 信息。</p>
<h4>Output format 输出格式</h4>
<pre><code class="java">127.0.0.1:6379&gt; slowlog get 2
1) 1) (integer) 10638      //slowlog 唯一标识
   2) (integer) 1594978677   //unix 时间戳
   3) (integer) 18467   //命令执行的时间，单位：微秒
   4) 1) "slowlog"   //具体执行的命令，最多记录128
      2) "help"
   5) "127.0.0.1:45902"
   6) ""
2) 1) (integer) 10637
   2) (integer) 1594977072
   3) (integer) 16983
   4) 1) "ZRANGE"
      2) "gx:oFNc6s4mKJ6goQvnUJP36ZMoFLMo"
      3) "0"
      4) "-1"
   5) "127.0.0.1:49334"
   6) ""
</code></pre>
<p>还有一些仅由Redis 4.0或更高版本发出的可选字段：</p>
<pre><code class="java">5) "127.0.0.1:58217"  //客户端IP:Port
6) "worker-123"  //客户端名称
</code></pre>
<p><strong>输出格式解释：</strong>每个条目都由四个（或从Redis 4.0开始的六个）字段组成：</p>
<p>1）每个慢日志条目的唯一累进标识符。
 <br>
 2）处理已记录命令的Unix时间戳。
 <br>
 3）它执行所需的时间（以微秒为单位）。
 <br>
 4）组成命令参数的数组。
 <br>
 5）客户端IP地址和端口（仅4.0）。
 <br>
 6）客户端名称（如果通过CLIENT SETNAME命令设置）（仅4.0）。
 <br>
 可以使用条目的唯一ID，以避免多次处理慢日志条目（例如，对于每个新的慢日志条目，您可能有一个脚本向您发送电子邮件警报）。</p>
<p>该ID不会在Redis服务器执行过程中重置，只有重新启动服务器才会重置它。</p>
<p>获取 slowlog 的当前长度
 <br>
 使用命令SLOWLOG LEN可以仅获取慢速日志的长度。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa281446468f8fe53851735242fd06a1d.png&amp;size=m" alt=""></p>
<h3>Resetting the slow log. 重置慢速日志。</h3>
<p>您可以使用SLOWLOG RESET命令重置慢速日志。 一旦删除，信息将永远丢失。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/107414060</p>]]></description><guid isPermaLink="false">/archives/f73aceea-1d3e-4cd4-8575-5a955d7472d8</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:46:11 GMT</pubDate></item><item><title><![CDATA[面试题：由ThreadLocal引发的惨案]]></title><link>https://wiki.dgfygk.com/archives/8ea22e22-b7ec-480e-8b4e-8034e57e4f00</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%9A%E7%94%B1ThreadLocal%E5%BC%95%E5%8F%91%E7%9A%84%E6%83%A8%E6%A1%88&amp;url=/archives/8ea22e22-b7ec-480e-8b4e-8034e57e4f00" width="1" height="1" alt="" style="opacity:0;">
<p>ThreadLocal在面试中经常被问到，下面我们就ThreadLocal进行一下认识和了解。</p>
<p>从以下几个问题去了解ThreadLocal：</p>
<p>1、ThreadLocal是什么？</p>
<p>2、ThreadLocal应用场景？</p>
<p>3、ThreadLocal怎么用，以及ThreadLocal和Synchronized关键词有啥区别？</p>
<p>4、ThreadLocal源码分析？</p>
<p>5、ThreadLocal内存泄漏问题？</p>
<p><strong>问题1：什么是ThreadLocal？</strong></p>
<p>官方介绍：ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问（通过get和set方法访问）时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的，用于关联线程和线程上下文。</p>
<p>简单来说：ThreadLocal就是线程变量，意思是ThreadLocal中填充的变量属于当前线程，该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本，那么每个线程可以访问自己内部的副本变量。</p>
<p>从上述中我们可以得出ThreadLocal的作用：提供线程内的局部变量，不同线程之间互不干扰。这种变量在线程的生命周期内起作用，减少同一线程内多个函数或组件之间一些公共变量传递的复杂度。</p>
<blockquote>
 <p>总结:</p>
 <ol>
  <li>线程并发: 在多线程并发的场景下</li>
  <li>传递数据: 我们可以通过ThreadLocal在同一线程，不同组件中传递公共变量</li>
  <li>线程隔离: 每个线程的变量都是独立的，不会互相影响</li>
 </ol>
</blockquote>
<p><strong>问题2、ThreadLocal应用场景？</strong></p>
<blockquote>
 <ul>
  <li>1、在进行对象跨层传递的时候，使用ThreadLocal可以避免多次传递，打破层次间的约束。</li>
  <li>2、线程间数据隔离</li>
  <li>3、进行事务操作，用于存储线程事务信息。</li>
  <li>4、数据库连接，Session会话管理。</li>
 </ul>
</blockquote>
<p><strong>问题3：ThreadLocal怎么用？</strong></p>
<ul>
 <li>3.1 ThreadLocal的常用方法</li>
</ul>
<p>在使用之前,我们先来认识几个ThreadLocal的常用方法</p>
<p>|—————————|—————–|
 <br>
 | 方法声明 | 描述 |
 <br>
 | ThreadLocal() | 创建ThreadLocal对象 |
 <br>
 | public void set( T value) | 设置当前线程绑定的局部变量 |
 <br>
 | public T get() | 获取当前线程绑定的局部变量 |
 <br>
 | public void remove() | 移除当前线程绑定的局部变量 |</p>
<ul>
 <li>3.2 应该示例</li>
</ul>
<pre><code class="java">package com.threadlocal;

import java.util.stream.IntStream;

/**
 * 需求线程隔离：
 * 再多线程并发场景下，每个线程中的变量都是隔离的
 * 线程0，设置变量（0），获取变量0；
 * 线程1，设置变量（1），获取变量1；
 * 线程2，设置变量（2），获取变量2；
 * ......
 */
public class MyDemo {

    private String content;

    public MyDemo() {
    }

    public MyDemo(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        MyDemo myDemo = new MyDemo();

        //使用java8的lambda stream新建5个线程
        IntStream.range(0, 5).forEach(
                thread -&gt; new Thread(() -&gt; {
                    //每个线程存一个变量，然后再取这个变量
                    myDemo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println(Thread.currentThread().getName() + "---&gt;" + myDemo.getContent());
                }).start()
        );
    }
}
</code></pre>
<p>运行结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe59d62fa7853034ba96f41335f9b2735.png&amp;size=m" alt=""> 执行结果是很明显线程之间没有实现数据的隔离，线程0获取到了线程1的数据。这时我们考虑到的就是使用ThreadLocal实现并发情况下数据的相互隔离。</p>
<p>其中上面讲的ThreadLocal中的set()方法就是'将变量绑定到当前线程中'；而get()方法就是'获取当前线程绑定的变量'</p>
<p>将上面的示例改写成ThreadLocal的形式：</p>
<pre><code class="java">class MyDemo02 {

    private static ThreadLocal&lt;String&gt; threadLocal = new ThreadLocal&lt;&gt;();

    private String content;

    public String getContent() {
        return threadLocal.get();
    }

    public void setContent(String content) {
        threadLocal.set(content);
    }

    public static void main(String[] args) {

        MyDemo02 myDemo02 = new MyDemo02();
        for (int i = 0; i &lt; 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    myDemo02.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("-------------------------");
                    System.out.println(Thread.currentThread().getName() + "---&gt;" + myDemo02.getContent());
                }
            }).start();
        }
    }
}
</code></pre>
<p>运行结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F59fd34c1a49d2e8b846808572ffc5bc4.png&amp;size=m" alt=""></p>
<p>从结果我们可以看到，每一个线程都有各自的local值，这就是TheadLocal的基本使用，是不是非常的简单。</p>
<p>那么为什么会在数据库连接的时候使用的比较多呢？</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F98338ed683981dd7b36236acca572b25.png&amp;size=m" alt=""></p>
<p>上面是一个数据库连接的管理类，我们使用数据库的时候首先就是建立数据库连接，然后用完了之后关闭就好了，这样做有一个很严重的问题，如果有1个客户端频繁的使用数据库，那么就需要建立多次链接和关闭，我们的服务器可能会吃不消，怎么办呢？如果有一万个客户端，那么服务器压力更大。</p>
<p>这时候最好使用ThreadLocal，因为ThreadLocal在每个线程中对连接会创建一个副本，且在线程内部任何地方都可以使用，线程之间互不影响，这样一来就不存在线程安全问题，也不会严重影响程序执行性能。是不是很好用。</p>
<ul>
 <li>3.3 ThreadLocal Synchronized的区别</li>
</ul>
<p>上面的程序可以改写成synchronized实现的方式，不过使用synchronized相当于加锁，在高并发情况下，只有获取锁的线程才能继续执行，未获取锁的线程只能循环等待尝试获取锁，导致程序失去了并发性</p>
<pre><code class="java">/**
 * 使用synchronized
 */
class MyDemo03 {

    private String content;


    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        MyDemo03 myDemo03 = new MyDemo03();

        for (int i = 0; i &lt; 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (MyDemo03.class) {
                        myDemo03.setContent(Thread.currentThread().getName() + "的数据");
                        System.out.println("-------------------------");
                        System.out.println(Thread.currentThread().getName() + "---&gt;" + myDemo03.getContent());
                    }

                }
            }).start();
        }
    }
}
</code></pre>
<p>执行结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2b1c65482a36fb76e4532302fd3173fc.png&amp;size=m" alt=""></p>
<p>从结果可以看出, 加锁确实可以解决这个问题，但是在这里我们强调的是多线程数据隔离的问题，并不是多线程共享数据的问题, 在这个案例中使用synchronized关键字是不合适的。</p>
<p>说白了，synchronized可以实现隔离线程的需求，由于处理中需要加锁才能实现资源的共享，导致线程只能排队执行，降低了程序在高并发环境下的执行效率。所以使用ThreadLocal在多线程环境下实现数据的隔离性具有更高的并发性能</p>
<p>总结：</p>
<p>虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同。</p>
<p>|—–|—————————————|———————————————————–|
 <br>
 | | synchronized | ThreadLocal |
 <br>
 | 原理 | 同步机制采用"以时间换空间"的方式，只提供了一份变量，让不同的线程排队访问 | ThreadLocal采用'以空间换时间'的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰 |
 <br>
 | 侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |</p>
<p>以时间换空间 -&gt; 即加锁方式，某个区域代码或变量只有一份节省了内存，但是会形成很多线程等待现象，因此浪费了时间而节省了空间。（牺牲时间访问冲突）
 <br>
 以空间换时间 -&gt; 为每一个线程提供一份变量，多开销一些内存，但是线程不用等待，可以一起执行而相互之间没有影响。（牺牲空间来解决冲突）</p>
<p>总之，ThreadLocal是解决线程安全问题一个很好的思路，它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下，ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单，更方便，且结果程序拥有更高的并发性。</p>
<p><strong>问题4、ThreadLocal源码分析？</strong></p>
<p>截止到现在相信ThreadLocal对于大家来说已经不再陌生了，接下来就让我深究ThreadLocal的源码是如何设计的。</p>
<p>在JDK8中 <code>ThreadLocal</code>的设计是：每个<code>Thread</code>维护一个<code>ThreadLocalMap</code>，这个Map的<code>key</code>是<code>ThreadLocal</code>实例本身，<code>value</code>才是真正要存储的值<code>Object</code>。</p>
<p>具体的过程是这样的：</p>
<ul>
 <li>每个Thread线程内部都有一个Map (ThreadLocalMap)</li>
 <li>Map里面存储ThreadLocal对象（key）和线程的变量副本（value）</li>
 <li>Thread内部的Map是由ThreadLocal维护的，由ThreadLocal负责向map获取和设置线程的变量值。</li>
 <li>对于不同的线程，每次获取副本值时，别的线程并不能获取到当前线程的副本值，形成了副本的隔离，互不干扰。</li>
</ul>
<p>结构如下图所示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4369be9b8d97fc4d67770f4e7f0dc44f.png&amp;size=m" alt=""></p>
<p>这样设计有如下两个优势：</p>
<ul>
 <li>这样设计之后每个Map存储的Entry数量就会变少。因为之前的存储数量由Thread的数量决定，现在是由ThreadLocal的数量决定。在实际运用当中，往往ThreadLocal的数量要少于Thread的数量。</li>
 <li>当Thread销毁之后，对应的ThreadLocalMap也会随之销毁，能减少内存的使用。</li>
</ul>
<p>☆ ThreadLocal常用的方法有这几个：</p>
<p>|—————————|—————–|
 <br>
 | 方法声明 | 描述 |
 <br>
 | ThreadLocal() | 创建ThreadLocal对象 |
 <br>
 | public void set( T value) | 设置当前线程绑定的局部变量 |
 <br>
 | public T get() | 获取当前线程绑定的局部变量 |
 <br>
 | public void remove() | 移除当前线程绑定的局部变量 |</p>
<ol>
 <li>set()方法源码</li>
</ol>
<pre><code class="java">  /**
     * 设置当前线程对应的ThreadLocal的值
     *
     * @param value 将要保存在当前线程对应的ThreadLocal的值
     */
    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1）当前线程Thread 不存在ThreadLocalMap对象
            // 2）则调用createMap进行ThreadLocalMap对象的初始化
            // 3）并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
    }

 /**
     * 获取当前线程Thread对应维护的ThreadLocalMap 
     * 
     * @param  t the current thread 当前线程
     * @return the map 对应维护的ThreadLocalMap 
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    /**
     *创建当前线程Thread对应维护的ThreadLocalMap 
     *
     * @param t 当前线程
     * @param firstValue 存放到map中第一个entry的值
     */
    void createMap(Thread t, T firstValue) {
        //这里的this是调用此方法的threadLocal
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
</code></pre>
<p>剖析：从源码中我们可以看到 首先获取当前线程t，然后调用getMap(t) 方法传入当前线程t 获取ThreadLocalMap；然后再判断ThreadLocalMap 存在，则将当前线程对象t作为key，要存储的对象作为value存到ThreadLocalMap里面；如果不存在就使用createMap(Thread t, T firstValue)方法初始化一个。</p>
<p>到这相信你会有几个疑惑了，ThreadLocalMap是什么，getMap方法又是如何实现的。带着这些问题，继续往下看。先来看ThreadLocalMap。</p>
<pre><code class="java">static class ThreadLocalMap {

    static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {//这个弱引用是关键点
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal&lt;?&gt; k, Object v) {
            super(k);
            value = v;
        }
    }
    .....//省略大部分代码
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
</code></pre>
<p>我们可以看到ThreadLocalMap其实就是ThreadLocal的一个静态内部类，里面定义了一个Entry来保存数据，而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key，使用我们设置的value作为value。</p>
<p>调用当期线程t，返回当前线程t中的成员变量threadLocals。而threadLocals其实就是ThreadLocalMap。</p>
<p><strong>2. get()方法源码：</strong></p>
<pre><code class="java">    /**
     * 返回当前线程中保存ThreadLocal的值
     * 如果当前线程没有此ThreadLocal变量，
     * 则它会通过调用{@link #initialValue} 方法进行初始化值
     *
     * @return 返回当前线程对应此ThreadLocal的值
     */
    public T get() {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null) {
            // 以当前的ThreadLocal 为 key，调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 对e进行判空 
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取存储实体 e 对应的 value值
                // 即为我们想要的当前线程对应此ThreadLocal的值
                T result = (T)e.value;
                return result;
            }
        }
        /*
            初始化 : 有两种情况有执行当前代码
            第一种情况: map不存在，表示此线程没有维护的ThreadLocalMap对象
            第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
         */
        return setInitialValue();
}
</code></pre>
<p>源码剖析：首先获取当前线程t，然后调用getMap()方法获取一个ThreadLocalMap，如果map不为null，则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e，然后在判断e 是否为空，如果e不为null，则返回e.value；</p>
<p>​ 如果Map为空或者e为空，则通过initialValue() 函数获取初始值value，然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map。</p>
<p>那么初始化setInitialValue()是什么样的呢？</p>
<pre><code class="java">   /**
     * 初始化
     *
     * @return the initial value 初始化后的值
     */
    private T setInitialValue() {
        // 调用initialValue获取初始化的值
        // 此方法可以被子类重写, 如果不重写默认返回null
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1）当前线程Thread 不存在ThreadLocalMap对象
            // 2）则调用createMap进行ThreadLocalMap对象的初始化
            // 3）并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
        // 返回设置的值value
        return value;
    }
</code></pre>
<p>源码剖析：首先是对Value初始化，然后获取当前线程t， 然后再调用getMap()方法获取ThreadLocalMap，如果map不为空，则将当前线程t作为key，初始化的value为值存储ThreadLocalMap中；如果为空则使用createMap()方法初始化一个。</p>
<p>在setInitialValue()方法中，有一个initialValue()方法进行值得初始化，那么initialValue()源码是什么样的呢？我们来看一下</p>
<pre><code class="java">/**
  * 返回当前线程对应的ThreadLocal的初始值

  * 此方法的第一次调用发生在，当线程通过get方法访问此线程的ThreadLocal值时
  * 除非线程先调用了set方法，在这种情况下，initialValue 才不会被这个线程调用。
  * 通常情况下，每个线程最多调用一次这个方法。
  *
  * &lt;p&gt;这个方法仅仅简单的返回null {@code null};
  * 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值，
  * 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
  * 通常, 可以通过匿名内部类的方式实现
  *
  * @return 当前ThreadLocal的初始值
  */
protected T initialValue() {
    return null;
}
</code></pre>
<p>其实很简单哈哈哈哈。此方法的作用是 返回该线程局部变量的初始值。</p>
<p>（1） 这个方法是一个延迟调用方法，从上面的代码我们得知，在set方法还未调用而先调用了get方法时才执行，并且仅执行1次。</p>
<p>（2）这个方法缺省实现直接返回一个null。</p>
<p>（3）如果想要一个除null之外的初始值，可以重写此方法。（备注： 该方法是一个protected的方法，显然是为了让子类覆盖而设计的）</p>
<p><strong>3.remove()方法</strong></p>
<pre><code class="java"> /**
     * 删除当前线程中保存的ThreadLocal对应的实体entry
     */
     public void remove() {
        // 获取当前线程对象中维护的ThreadLocalMap对象
         ThreadLocalMap m = getMap(Thread.currentThread());
        // 如果此map存在
         if (m != null)
            // 存在则调用map.remove
            // 以当前ThreadLocal为key删除对应的实体entry
             m.remove(this);
     }
</code></pre>
<p>剖析：方法很简单就是获取当前线程t， 然后调用getMap()方法获取ThreadLocalMap，并且判断map是否为空，如果不是空就直接调用ThreadLocalMap的remove()方法直接移除当前ThreadLocal对象对应的entry。</p>
<p>那么 下面看一下ThreadLocalMap中remove()方法的源码:</p>
<pre><code class="java">private void remove(ThreadLocal&lt;?&gt; key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode &amp; (len-1);
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}
</code></pre>
<p><strong>源码就介绍到这里。</strong></p>
<p><strong>问题5、ThreadLocal内存泄漏问题？</strong></p>
<p>这个问题是ThreadLocal最需要注意的问题，稍后会专门设计一篇博客讲述ThreadLocal内存泄漏的问题。详情见<a href="https://blog.csdn.net/zuihongyan518/article/details/114870263">ThreadLocal之内存泄漏</a></p>
<p>文章如有错误之处欢迎指正，如果该文章对你有帮助，麻烦给予关注，点赞！！！</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/114794079</p>]]></description><guid isPermaLink="false">/archives/8ea22e22-b7ec-480e-8b4e-8034e57e4f00</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:45:44 GMT</pubDate></item><item><title><![CDATA[Linux 根据某个时间段查看日志 {查看某时间段内的日志}]]></title><link>https://wiki.dgfygk.com/archives/3f8c7d0f-2fda-4442-9d38-83c82ccdaf44</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%20%E6%A0%B9%E6%8D%AE%E6%9F%90%E4%B8%AA%E6%97%B6%E9%97%B4%E6%AE%B5%E6%9F%A5%E7%9C%8B%E6%97%A5%E5%BF%97%20%7B%E6%9F%A5%E7%9C%8B%E6%9F%90%E6%97%B6%E9%97%B4%E6%AE%B5%E5%86%85%E7%9A%84%E6%97%A5%E5%BF%97%7D&amp;url=/archives/3f8c7d0f-2fda-4442-9d38-83c82ccdaf44" width="1" height="1" alt="" style="opacity:0;">
<p>在系统应用集中部署的时候，经常会查看一些日志，有时候因为日志文件太大，不能去vi打开文件，而难以定位，因此获取某段时间的日志是对维护人员是非常关键的事情。</p>
<blockquote>
 <p>Linux 命令：</p>
 <p>① sed -n '/开始时间/,/结束时间/p' logfile[日志文件名] //表示查询开始-结束时间之间的日志行</p>
 <p>② sed -n '/xxx/p' 表示输出 包含xxx的日志行</p>
 <p>③ sed -n '/xxx\\|yyy/p' 表示输出 包含xxx或包含yyy的日志行</p>
 <p>
  <br>
 </p>
 <p>注意：</p>
 <p>p表示，满足前面的条件就 <strong>打印输出</strong>
  <br>
  !p表示，满足前面的条件就 <strong>不打印输出</strong>
  <br>
  例如：sed -n '/2020-05-30 00:01:28/,/2020-05-30 00:05:50/p' log_file_catalina.out</p>
</blockquote>
<p>上面的命令就能筛选出指定的时间段内的日志。</p>
<pre><code>// 1. 查询2020-05-30 00:05:50这个时间点的日志
[root@oradb ~]# sed -n '/2020-05-30 00:05:50/p' log_file_catalina.log

// 2. 查询2020-05-30 00:05:50并且包含abc的日志，这里用到正则 .* 表示任意字符有0个或多个
[root@oradb ~]# sed -n '/2020-05-30 00:05:50.*abc/p' log_file_catalina.log

// 3. 查询2021-10-10 00:11 和 2021-10-10 00:13 两个时间点的日志行，
//    这里用到正则 | 表示或的意思，注意|为特殊字符需要转义 \|
[root@oradb ~]# sed -n '/2020-05-30 00:04:50\|2020-05-30 00:05:50/p' log_file_catalina.log
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/106438199</p>]]></description><guid isPermaLink="false">/archives/3f8c7d0f-2fda-4442-9d38-83c82ccdaf44</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 00:45:19 GMT</pubDate></item><item><title><![CDATA[Linux 如何在文件中查找指定内容]]></title><link>https://wiki.dgfygk.com/archives/29fc772f-ba70-473f-bbb0-41921e52ccd9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%20%E5%A6%82%E4%BD%95%E5%9C%A8%E6%96%87%E4%BB%B6%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%8C%87%E5%AE%9A%E5%86%85%E5%AE%B9&amp;url=/archives/29fc772f-ba70-473f-bbb0-41921e52ccd9" width="1" height="1" alt="" style="opacity:0;">
<p>一、前言：在Linux系统当中，查找文件里面的指定内容 应该是系统维护、管理当中遇到最常见的。那么下面如何搜索、查找文件当中的内容呢？下面就介绍一下如何在文件中查找指定的内容</p>
<p>查找文件当中的内容，一般最常用的是grep命令，另外还有egrep, vi命令也能搜索文件里面内容。</p>
<p><strong>1、搜索某个文件里面是否包含字符串，使用 grep “search-content” filename</strong></p>
<p><strong>命令：</strong></p>
<pre><code class="cs">[root@localhost logs]# grep '搜索内容' log_file_catalina.out

或者

[root@localhost logs]# grep 搜索内容 log_file_catalina.out
</code></pre>
<p>例如我们 查找 log_file_catalina.out 文件中包含 '总数：900' 的字符内容</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0ba328fd6d6b0f610959eb2cb2735f89.png&amp;size=m" alt=""><strong>2: 2、如果你想搜索多个文件是否包含某个字符串,可以使用下面方式</strong></p>
<pre><code class="cpp">[root@localhost logs]# grep "search-content" filename1 filename2.... filenamen

[root@localhost logs]# grep "search-content" *.out
</code></pre>
<p>例如 我想查看当前目录下,哪些日志文件含'\总数：900\' {注意：搜索的内容如果包含特殊字符时，必须进行转义处理}</p>
<pre><code class="cpp">[root@localhost logs]# grep "\\总数：900\\" *.out
</code></pre>
<p>2.1 如果需要显示搜索文本在文件中的行数，可以使用参数 -n</p>
<pre><code class="cpp">[root@localhost logs]# grep -n "\\总数：900\\" *.out
</code></pre>
<p>2.2 如果搜索时需要忽略大小写问题，可以使用参数 -i</p>
<pre><code class="cpp">[root@localhost logs]# grep -i "\\总数：900\\" *.out
</code></pre>
<p><strong>3、从文件内容查找不匹配指定字符串的行：</strong></p>
<pre><code class="cpp">[root@localhost logs]# grep --v "search-content" 文件名
</code></pre>
<p>例如查找某些进程时，我们不想显示包含命令 grep tomcat 的进程，如下所示</p>
<pre><code class="cpp">[root@localhost  admin]$ ps -ef | grep tomcat
root 16675 16220  0 00:09 pts/1    00:00:00 grep tomcat
root 21412     1  0 Aug22 ?        00:00:07 tomcat
[root@localhost  admin]$ ps -ef  | grep tomcat| grep -v grep
root 21412     1  0 Aug22 ?        00:00:07 tomcat
[root@localhost  admin]$ 
</code></pre>
<p><strong>4、搜索、查找匹配的行数：</strong></p>
<pre><code class="cpp">[root@localhost logs]# grep -c "search-content" 文件名
</code></pre>
<pre><code>```cpp
[root@localhost log]$ grep -c  "总数：900"  log_file_catalina.out
2
[root@localhost log]$ grep -c  "总数：900"  log_file_catalina.out
5
[root@localhost log]$ 
</code></pre>
<p><strong>5、如果只想获取和整个搜索字符匹配的内容，那么可以使用参数w</strong></p>
<pre><code class="cpp">[root@localhost logs]# grep -w  "search-content" 文件名
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/106438570</p>]]></description><guid isPermaLink="false">/archives/29fc772f-ba70-473f-bbb0-41921e52ccd9</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 00:45:07 GMT</pubDate></item><item><title><![CDATA[Linux下查看文件内容的命令]]></title><link>https://wiki.dgfygk.com/archives/b590f9e2-c862-4946-9773-fdee09f85368</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%E4%B8%8B%E6%9F%A5%E7%9C%8B%E6%96%87%E4%BB%B6%E5%86%85%E5%AE%B9%E7%9A%84%E5%91%BD%E4%BB%A4&amp;url=/archives/b590f9e2-c862-4946-9773-fdee09f85368" width="1" height="1" alt="" style="opacity:0;">
<h4>查看文件内容的命令：</h4>
<blockquote>
 <ol>
  <li>cat 由第一行开始显示内容，并将所有内容输出</li>
  <li>tac 从最后一行倒序显示内容，并将所有内容输出</li>
  <li>more 根据窗口大小，一页一页的现实文件内容</li>
  <li>less 和more类似，但其优点可以往前翻页，而且进行可以搜索字符</li>
  <li>head 只显示头几行</li>
  <li>tail 只显示最后几行</li>
  <li>nl 类似于cat -n，显示时输出行号</li>
  <li>tailf 类似于tail -f</li>
 </ol>
</blockquote>
<p><strong>1.cat 与 tac</strong></p>
<p>cat的功能是将文件从第一行开始连续的将内容输出在屏幕上。但是cat并不常用，原因是当文件大，行数比较多时，屏幕无法全部容下时，只能看到一部分内容。</p>
<blockquote>
 <p>cat语法：cat [-n] 文件名 （-n ： 显示时，连行号一起输出）</p>
</blockquote>
<p>tac的功能是将文件从最后一行开始倒过来将内容数据输出到屏幕上。我们可以发现，tac实际上是cat反过来写。这个命令也不常用。</p>
<blockquote>
 <p>tac语法：tac 文件名。</p>
</blockquote>
<p><strong>2.more和less（常用）</strong></p>
<p>more的功能是将文件从第一行开始，根据输出窗口的大小，适当的输出文件内容。当一页无法全部输出时，可以用"回车键"向下翻行，用"空格键"向下翻页。退出查看页面，请按"q"键。另外，more还可以配合管道符”\|“（pipe）使用，例如:ls -al \| more</p>
<blockquote>
 <p>more的语法：more 文件名</p>
 <ul>
  <li>Enter 向下n行，需要定义，默认为1行；</li>
  <li>Ctrl f 向下滚动一屏；</li>
  <li>空格键 向下滚动一屏；</li>
  <li>Ctrl b 返回上一屏；</li>
  <li>= 输出当前行的行号；</li>
  <li>:f 输出文件名和当前行的行号；</li>
  <li>v 调用vi编辑器；</li>
  <li>! 命令 调用Shell，并执行命令；</li>
  <li>q 退出more</li>
 </ul>
</blockquote>
<p>less的功能和more相似，但是使用more无法向前翻页，只能向后翻。</p>
<p>less可以使用【pageup】和【pagedown】键进行前翻页和后翻页，这样看起来更方便。</p>
<blockquote>
 <p>less的语法：less 文件名
  <br>
  less还有一个功能，可以在文件中进行搜索你想找的内容，假设你想在passwd文件中查找有没有weblogic字符串，那么你可以这样来做：</p>
 <ul>
  <li>[root@redhat etc]# less passwd</li>
  <li>然后输入：/weblogic</li>
  <li>回车</li>
 </ul>
</blockquote>
<p>此时如果有weblogic字符串，linux会把该字符已高亮方式显示。</p>
<p>退出查看页面，请按"q"键。</p>
<p><strong>3.head和tail</strong></p>
<p>head和tail通常使用在只需要读取文件的前几行或者后几行的情况下使用。head的功能是显示文件的前几行内容</p>
<blockquote>
 <p>head的语法：head [n number] 文件名 (number 显示行数)</p>
</blockquote>
<p>tail的功能恰好和head相反，只显示最后几行内容</p>
<blockquote>
 <p>tail的语法:tail [-n number] 文件名</p>
</blockquote>
<p><strong>4.nl</strong></p>
<p>nl的功能和cat -n一样，同样是从第一行输出全部内容，并且把行号显示出来</p>
<blockquote>
 <p>nl的语法：nl 文件名</p>
</blockquote>
<p><strong>5.tailf</strong></p>
<p>　tailf命令几乎等同于tail -f，严格说来应该与tail –follow=name更相似些。当文件改名之后它也能继续跟踪，特别适合于日志文件的跟踪（follow the growth of a log file）。与tail -f不同的是，如果文件不增长，它不会去访问磁盘文件（It is similar to tail -f but does not access the file when it is not growing. This has the side effect of not updating the access time for the file, so a filesystem flush does not occur periodically when no log activity is happening.）。tailf特别适合那些便携机上跟踪日志文件，因为它能省电，因为减少了磁盘访问嘛（tailf is extremely useful for monitoring log files on a laptop when logging is infrequent and the user desires that the hard disk spin down to conserve battery life.）。tailf命令不是个脚本，而是一个用C代码编译后的二进制执行文件，某些Linux安装之后没有这个命令，本文提供了怎么编译安装tailf命令的方法。</p>
<h4>下面就谈谈二者的区别：</h4>
<blockquote>
 <ol>
  <li>tailf 总是从文件开头一点一点的读， 而tail -f 则是从文件尾部开始读</li>
  <li>tailf check文件增长时，使用的是文件名， 用stat系统调用；而tail -f 则使用的是已打开的文件描述符； 注：tail 也可以做到类似跟踪文件名的效果； 但是tail总是使用fstat系统调用，而不是stat系统调用；结果就是：默认情况下，当tail的文件被偷偷删除时，tail是不知道的，而tailf是知道的。</li>
 </ol>
</blockquote>
<p>常用参数</p>
<blockquote>
 <p>格式：tail -f logfile</p>
 <p>例如：tail -f logfile.out 或者 tail -100f logfile.out</p>
</blockquote>
<p>动态跟踪日志文件logfile，最初的时候打印文件的最后10行内容。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/106438377</p>]]></description><guid isPermaLink="false">/archives/b590f9e2-c862-4946-9773-fdee09f85368</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 00:44:52 GMT</pubDate></item><item><title><![CDATA[MongoDB $in 和 Sort , find(), $add,$set(), $map, $addFields, $multiply, update(), updateMany()用法]]></title><link>https://wiki.dgfygk.com/archives/3c1c25ff-acbc-4f80-a470-9be442e76a76</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MongoDB%20%24in%20%E5%92%8C%20Sort%20%2C%20find%28%29%2C%20%24add%2C%24set%28%29%2C%20%24map%2C%20%24addFields%2C%20%24multiply%2C%20update%28%29%2C%20updateMany%28%29%E7%94%A8%E6%B3%95&amp;url=/archives/3c1c25ff-acbc-4f80-a470-9be442e76a76" width="1" height="1" alt="" style="opacity:0;">
<h4>$in</h4>
<p><em>Syntax</em> : <code>{ field: { $in: [&lt;value1&gt;, &lt;value2&gt;, ... &lt;valueN&gt; ] } }</code></p>
<p><code>$in</code> selects the documents where the <code>field</code> value equals any value in the specified array (e.g. <code>&lt;value1&gt;</code>, <code>&lt;value2&gt;</code>, etc.)</p>
<p>Consider the following example:</p>
<pre><code>db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
</code></pre>
<p>This query selects all documents in the <code>inventory</code> collection where the <code>qty</code> field value is either <code>5</code> or <code>15</code>. Although you can express this query using the <code>[$or</code>](or.html#<em>S_or) operator, choose the <code>$in</code> operator rather than the <code>[$or</code>](or.html#</em>S_or) operator when performing equality checks on the same field.</p>
<p>If the <code>field</code> holds an array, then the <code>$in</code> operator selects the documents whose <code>field</code> holds an array that contains at least one element that matches a value in the specified array (e.g. <code>&lt;value1&gt;</code>, <code>&lt;value2&gt;</code>, etc.)</p>
<p>Consider the following example:</p>
<pre><code>db.inventory.update( { tags: { $in: ["appliances", "school"] } }, { $set: { sale:true } } )
</code></pre>
<p>This <code>[update()</code>](db.collection.update.html#db.collection.update) operation will set the <code>sale</code> field value in the <code>inventory</code> collection where the <code>tags</code> field holds an array with at least one element matching an element in the array <code>["appliances", "school"]</code>.</p>
<p>也可以参考</p>
<p><code>[find()</code>](db.collection.find.html#db.collection.find), <code>[update()</code>](db.collection.update.html#db.collection.update), <code>[$or</code>]</p>
<p>(or.html#<em>S_or), <code>[$set</code>](set.html#</em>S_set).</p>
<p>以上是 MongoDB中文文档 给出的解释和语法</p>
<h3>MongoDB sort() 方法</h3>
<p>在 MongoDB 中使用 sort() 方法对数据进行排序，sort() 方法可以通过参数指定排序的字段，并使用 1 和 -1 来指定排序的方式，其中 1 为升序排列，而 -1 是用于降序排列。</p>
<h4>语法</h4>
<p>sort()方法基本语法如下所示：</p>
<blockquote>
 <pre><code>```html
&gt;db.COLLECTION_NAME.find().sort({KEY:1})
</code></pre>
 <p>例如：db.getCollection('mongo_test').find().sort({_id:-1})</p>
</blockquote>
<p>例如：查詢mongo_test集合 id在170735,19786503,170732,170764,19786435中的数据，并按id降序排列</p>
<pre><code class="java">db.getCollection('mongo_test').find({_id:{$in: [170735,19786503,170732,170764,19786435]}}).sort({_id:-1})
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F201731b1d60d4a581403794fd9d4acef.png&amp;size=m" alt=""></p>
<p>使用Shell中的<a href="https://mongodb.net.cn/manual/reference/method/db.collection.find/#db.collection.find"><code>db.collection.find()</code></a>方法 对数组字段进行查询操作的示例 <a href="https://mongodb.net.cn/manual/reference/program/mongo/#bin.mongo"><code>mongo</code></a>。</p>
<p>1.查询所有的结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5f87ff2eeb5641a76dc0104c2e8a3392.png&amp;size=m" alt=""> 2.查询符合条件的记录</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4207a0c0328e5513b1a82b2327497735.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faad00bf5a49b28a545c295151e5d77c7.png&amp;size=m" alt=""></p>
<p>3.查询符合条件的指定的列</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F681438699aeba4027699e1f36363a5c0.png&amp;size=m" alt=""></p>
<p>4.sql查询条件中的and和or在mongodb中的应用</p>
<p>mongodb中使用and查询的类比语法为：db.col.find({key1:value1, key2:value2}).pretty()，比较简单，只需将多个键值对传入即可</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F44f4bc73ef55219e45f011b300655069.png&amp;size=m" alt=""></p>
<p>mongodb中的or语法与and类似，只是是以$or为键传入一个集合的值，具体的语法为db.col.find({$or:[{key1: value1},{key2:value2}]}).pretty()<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff063c8afd6f567978d531bc4f1deb305.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fede917b5b2d00669002015cfff2a04cb.png&amp;size=m" alt=""></p>
<p>创建一个<code>temperatures</code>包含摄氏温度的示例集合（如果该集合当前不存在，则插入操作将创建该集合）：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5a7a570dfb99f6223b293baffec72a96.png&amp;size=m" alt=""></p>
<p>以下<a href="https://mongodb.net.cn/manual/reference/method/db.collection.updateMany/#db.collection.updateMany"><code>db.collection.updateMany()</code></a>操作使用聚合管道以华氏度的相应温度更新文档：</p>
<p>具体来说，管道包括一个<a href="https://mongodb.net.cn/manual/reference/operator/aggregation/addFields/#pipe._S_addFields"><code>$addFields</code></a> 阶段，以添加一个新的数组字段<code>tempsF</code>，该字段包含华氏温度。要将<code>tempsC</code>数组中的每个摄氏温度转换为华氏温度，该阶段使用 <a href="https://mongodb.net.cn/manual/reference/operator/aggregation/map/#exp._S_map"><code>$map</code></a>带有<a href="https://mongodb.net.cn/manual/reference/operator/aggregation/add/#exp._S_add"><code>$add</code></a>和的 <a href="https://mongodb.net.cn/manual/reference/operator/aggregation/multiply/#exp._S_multiply"><code>$multiply</code></a>表达式。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb861e53e227e727ae50130cc59311d5b.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6d500d83fd3186b48bc62d2baa888e3d.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F079747bec40ddd6dc3aa2122f8a661ec.png&amp;size=m" alt=""></p>
<p>以下<a href="https://mongodb.net.cn/manual/reference/method/db.collection.updateOne/#db.collection.updateOne"><code>db.collection.updateOne()</code></a>操作使用聚合管道通过以下方式向文档添加测验分数：<code>_id: 2</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F41ad0596b8c729222693058273e0622b.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/106498369</p>]]></description><guid isPermaLink="false">/archives/3c1c25ff-acbc-4f80-a470-9be442e76a76</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Thu, 26 Sep 2024 00:44:29 GMT</pubDate></item><item><title><![CDATA[虚拟机Linux(Centos)上用户密码忘记了如何修改？]]></title><link>https://wiki.dgfygk.com/archives/87ec4d92-cef6-499d-b221-820c271c237f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%99%9A%E6%8B%9F%E6%9C%BALinux%28Centos%29%E4%B8%8A%E7%94%A8%E6%88%B7%E5%AF%86%E7%A0%81%E5%BF%98%E8%AE%B0%E4%BA%86%E5%A6%82%E4%BD%95%E4%BF%AE%E6%94%B9%EF%BC%9F&amp;url=/archives/87ec4d92-cef6-499d-b221-820c271c237f" width="1" height="1" alt="" style="opacity:0;">
<p>今天突然想打开虚拟机，但是发现密码忘记了，尴尬！今天来说一下怎样修改我们的密码.此方法对大多数的linux版本都有帮助.</p>
<p>第1步: 启动虚拟机，出现下面的倒计时界面时，按键盘上的e键.<strong>（要确保光标此时在虚拟机内，不然按e键无效。e代表edit，启动前编辑）</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F26af6dfb156920b7cfcd036c7326b1dd.png&amp;size=m" alt=""></p>
<p>第2步: 进入如下后界面，再按e键</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6470c8755d9d5ad3fb529197710e0284.png&amp;size=m" alt=""></p>
<p>第3步：进入如下界面后，使用键盘上的上下键，选中第二项以kernel开头的项。选中后，再次按e键</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F231e567fd28c50b69a53fb62374c8d4d.png&amp;size=m" alt=""></p>
<p>第4步: 进入以下界面后，在最后面输入空格和s，然后回车。（注：有的系统显示的内容可能和我下面出现的这一行不一样，没关系，直接加空格和s就行，不影响的；也就说什么呢，只要进入到这个页面，直接加空格和s，不要管他显示的是什么；）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8c92d97e1aa9b762bcb99c0561460673.png&amp;size=m" alt=""></p>
<p>第5步: 接着会回到如下界面，然后按下b键（b代表boot启动）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fec854b206c7080ef1bcc7a4a8cfbd981.png&amp;size=m" alt=""></p>
<p>第6步: 进入如下界面</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9ab33f5af16fa03f267f801a275b65b9.png&amp;size=m" alt=""></p>
<p>第7步: 此时输入passwd root，然后 回车</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb2d857cbd2039d713d266d736d132a4b.png&amp;size=m" alt=""></p>
<p>第8步: 输入密码，回车，确认密码，回车，输入reboot，回车；(声明：输入的密码不会显示出来，所以要谨记输入的密码）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1899579cf88fe9b593fa323410c6a6f3.png&amp;size=m" alt=""></p>
<p>以上就完成了!</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/107142514</p>]]></description><guid isPermaLink="false">/archives/87ec4d92-cef6-499d-b221-820c271c237f</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Thu, 26 Sep 2024 00:43:50 GMT</pubDate></item><item><title><![CDATA[jquery 合并任意值相等的列单元格]]></title><link>https://wiki.dgfygk.com/archives/6d9ca911-dd8c-4d7b-b925-09a49f53bd38</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=jquery%20%E5%90%88%E5%B9%B6%E4%BB%BB%E6%84%8F%E5%80%BC%E7%9B%B8%E7%AD%89%E7%9A%84%E5%88%97%E5%8D%95%E5%85%83%E6%A0%BC&amp;url=/archives/6d9ca911-dd8c-4d7b-b925-09a49f53bd38" width="1" height="1" alt="" style="opacity:0;">
<p>今天在开发中遇到产品要把数据相同的表格合并到一起，在经过一番周折以后就实现了。现在把实现的代码做一下记录并分享！</p>
<pre><code class="html">&lt;body&gt;
    &lt;ul class="nav nav-tabs"&gt;
        &lt;li class="active"&gt;&lt;a href="${ctx}/activity/IncomeData/"&gt;收入数据查询&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;form:form id="searchForm" modelAttribute="IncomeData" action="${ctx}/activity/IncomeData/" method="get" class="breadcrumb form-search"&gt;
        &lt;ul class="ul-form"&gt;
            &lt;li&gt;&lt;label&gt;编号：&lt;/label&gt;
                &lt;form:input path="activityNo" htmlEscape="false" maxlength="16" class="input-medium"/&gt;
            &lt;/li&gt;
            &lt;li&gt;&lt;label&gt;时间：&lt;/label&gt;
                &lt;input name="beginTime" type="text" readonly="readonly" maxlength="20" class="input-medium Wdate"
                    value="&lt;fmt:formatDate value="${IncomeData.beginTime}" pattern="yyyy-MM-dd HH:mm:ss"/&gt;"
                    onclick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss',isShowClear:false});"/&gt;
            &lt;/li&gt;
            &lt;li&gt;&lt;label&gt;到：&lt;/label&gt;
                &lt;input name="endTime" type="text" readonly="readonly" maxlength="20" class="input-medium Wdate"
                       value="&lt;fmt:formatDate value="${IncomeData.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/&gt;"
                       onclick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss',isShowClear:false});"/&gt;
            &lt;/li&gt;
            &lt;li class="btns"&gt;&lt;input id="btnSubmit" class="btn btn-primary" type="submit" value="查询"/&gt;&lt;/li&gt;
            &lt;li class="clearfix"&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/form:form&gt;
    &lt;sys:message content="${message}"/&gt;
    &lt;table id="contentTable" class="table table-striped table-bordered table-condensed"&gt;
        &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;编号&lt;/th&gt;
                &lt;th&gt;日期&lt;/th&gt;
                &lt;th&gt;人数&lt;/th&gt;
                &lt;th&gt;付费人数&lt;/th&gt;
                &lt;th&gt;总金额&lt;/th&gt;
                &lt;th&gt;豪华大礼编号&lt;/th&gt;
                &lt;th&gt;豪华大礼单价&lt;/th&gt;
                &lt;th&gt;销售数量&lt;/th&gt;
                &lt;th&gt;销售金额&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
        &lt;c:forEach items="${page.list}" var="IncomeData"&gt;
            &lt;tr&gt;
                &lt;td&gt;
                    ${IncomeData.no}
                &lt;/td&gt;
                &lt;td&gt;
                    ${IncomeData.date}
                &lt;/td&gt;
                &lt;td&gt;
                    ${IncomeData.lookCount}
                &lt;/td&gt;
                &lt;td&gt;
                    ${IncomeData.payCount}
                &lt;/td&gt;
                &lt;td&gt;
                    ${IncomeData.totalFee}
                &lt;/td&gt;
                &lt;td&gt;
                                       ${IncomeData.packageNum}
                &lt;/td&gt;
                &lt;td&gt;
                    ${IncomeData.price}
                &lt;/td&gt;
                &lt;td&gt;
                    ${IncomeData.soldNum}
                &lt;/td&gt;
                &lt;td&gt;
                    ${IncomeData.soldFee}
                &lt;/td&gt;
            &lt;/tr&gt;
        &lt;/c:forEach&gt;
        &lt;/tbody&gt;
    &lt;/table&gt;
&lt;/body&gt;
</code></pre>
<p><strong>一、只合并第一列</strong></p>
<pre><code class="javascript">&lt;script type="text/javascript"&gt;

        $(document).ready(function() {
            mergeTimeCell();

        });

                //合并单元格
        function mergeTimeCell(){
            var sameDayArr=[];
            var timeArr=[];
            var itemArr=[];
            var countArr=[];
            var count = 0;
            $("tbody").find("tr").each(function(index,item){
                var time = $(item).children(":eq(0)").text().trim()
                timeArr.push(time)
            })
            //判断和最后一个tr相同的个数
            var lastItemArr =[]
            var last =timeArr[timeArr.length-1]
            for(var i = 0;i&lt;timeArr.length;i++){
                if(last==timeArr[i]){
                    lastItemArr.push(timeArr[i])
                }
            }
            $("tbody").find("tr").each(function(index,item){
                var time = $(item).children(":eq(0)").text().trim()
                if(sameDayArr.indexOf(time)==-1){
                    sameDayArr.push(time)
                    countArr.push(count+1)
                    itemArr.push(item)
                    count=0
                }else{
                    count++
                    $(item).find("td:eq(0)").remove()
                }
            })
            countArr.push(lastItemArr.length)
            for(var i = 0;i&lt;itemArr.length;i++){
                $(itemArr[i]).find("td:eq(0)").attr("rowspan",countArr[i+1])
            }
        }


&lt;/script&gt;
</code></pre>
<p><strong>二、合并多列单元格</strong></p>
<pre><code class="javascript">&lt;script type="text/javascript"&gt;

       $(document).ready(function() {
        //要合并哪些列（合并多列，列从大到小排列）
        var arr =[4,3,2,1,0];
        mergeRowCell("contentTable",1,0,arr);

    });

              /*
                  MergeTableCell("tableId", 1, 0, 1);
          param:tableId,table标签的id
          param:startRow,开始行的索引值
          param:endRow,结束行的索引值
          param:arr,需要处理的列的索引值[这是一个数组]
           */
        function mergeRowCell(tableId, startRow, endRow, arr) {
            for (var i = 0; i &lt; arr.length; i++) {
                MergeTableCell(tableId, startRow, endRow, arr[i]);
            }
        }

        /*
                   MergeTableCell("tableId", 1, 0, 1);
           param:tableId,table标签的id
           param:startRow,开始行的索引值
           param:endRow,结束行的索引值
           param:col,需要处理的列的索引值
             */
        function MergeTableCell(tableId, startRow, endRow, col) {
            var tb = document.getElementById(tableId);
            //设置为0时,检索所有行
            if (endRow == 0) {
                endRow = tb.rows.length - 1;
            }
            //指定数据行索引大于表格行数
            if (endRow &gt;= tb.rows.length) {
                return;
            }
            //检测指定的列索引是否超出表格列数
            if (col &gt;= tb.rows[0].cells.length) {
                return;
            }
            //循环需要判断的数据行
            for (var i = startRow; i &lt; endRow; i++) {
                //如果当前行与下一行数据值相同，则进行前面列的判断
                if (tb.rows[startRow].cells[col].innerHTML == tb.rows[i + 1].cells[col].innerHTML) {
                    var Same = true;
                    //循环跟前面的所有的同级数据行进行判断
                    for (var j = col; j &gt; 0; j--) {
                        if (tb.rows[startRow].cells[j - 1].innerHTML != tb.rows[i + 1].cells[j - 1].innerHTML) {
                            Same = false;
                            break;
                        }
                    }
                    //如果前面的同级数据行的值均相同，则进行单元格的合并
                    if (true == Same) {
                        //如果相同则删除下一行的第0列单元格
                        tb.rows[i + 1].cells[col].style.display = 'none';
                        //更新rowSpan属性
                        tb.rows[startRow].cells[col].rowSpan = (tb.rows[startRow].cells[col].rowSpan | 0) + 1;
                    } else {
                        //增加起始行
                        startRow = i + 1;
                    }
                } else {
                    //增加起始行
                    startRow = i + 1;
                }
            }
        }

&lt;/script&gt;
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/105839992</p>]]></description><guid isPermaLink="false">/archives/6d9ca911-dd8c-4d7b-b925-09a49f53bd38</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:43:35 GMT</pubDate></item><item><title><![CDATA[jquery选择下拉框触发事件的实现及值的获取]]></title><link>https://wiki.dgfygk.com/archives/c9d94ade-125c-4e0e-8f29-539646ab2920</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=jquery%E9%80%89%E6%8B%A9%E4%B8%8B%E6%8B%89%E6%A1%86%E8%A7%A6%E5%8F%91%E4%BA%8B%E4%BB%B6%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8F%8A%E5%80%BC%E7%9A%84%E8%8E%B7%E5%8F%96&amp;url=/archives/c9d94ade-125c-4e0e-8f29-539646ab2920" width="1" height="1" alt="" style="opacity:0;">
<p>选中下拉框，隐藏对应的输入框，以及获取每一个数据值。</p>
<pre><code class="html">&lt;%@ page contentType="text/html;charset=UTF-8" language="java" %&gt;
&lt;%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;下拉框菜单选择&lt;/title&gt;
    &lt;script type="text/javascript" src="js/jquery-1.3.1.min.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="js/jquery.easing.1.3.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="My97DatePicker/WdatePicker.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;style type="text/css"&gt;
    body{
        margin-left: 150px;
        float:left;
    }
    label, select, div{
        margin-top: 10px;
    }
    .control-label-coupon{
        margin-left: -18px;
    }
    #control-label-coupon-min{
        margin-left: -116px;
    }

    #control-label-coupon-reduce{
        margin-left: -50px;
    }
&lt;/style&gt;
&lt;script type="text/javascript"&gt;
    $(document).ready(function(){
        $("select option").each(function(){
            if($(this).val()=='1'){
                $(this).attr("selected","selected");
                $("#div_attachment").hide();
                $("#div_coupon").hide();
            }
        });

        $(".giftType").bind('change', function(){
            if(this.value == "1" || this.value == "2") {
                $("#div_attachment").hide();
                $("#div_coupon").hide();
            } else if (this.value == "3"){
                $("#div_attachment").show();
                $("#div_coupon").hide();
            } else if (this.value == "4"){
                $("#div_attachment").hide();
                $("#div_coupon").show();
            } else {
                $("#div_attachment").show();
                $("#div_coupon").show();
            }
        })
    });

    function getDate() {
        var giftPackageNum = $("#giftNum option:selected").val();
        var activityNo = $(" input[name='activityNo']").val();
        var giftType = $(".giftType option:selected").val();
        var areaType = $("#areaType option:selected").val();
        var duration = $(" input[name='duration']").val();
        var attType = $(" input[name='attType']").val();
        var attNum = $(" input[name='attNum']").val();
        var couponType = $(" input[name='couponType']").val();
        var couponMinAmount = $(" input[name='couponMinAmount']").val();
        var couponReduceAmount = $(" input[name='couponReduceAmount']").val();
        var couponDiscount = $(" input[name='couponDiscount']").val();
        var createTime = $(" input[name='createTime']").val();
        var endDate = document.getElementById("createTime").value;
        document.writeln("活动编号: " + activityNo + "&lt;br&gt;");
        document.writeln("礼包编号: " + giftPackageNum + "&lt;br&gt;");
        document.writeln("奖励类型: " + giftType + "&lt;br&gt;");
        document.writeln("地区类型: " + areaType + "&lt;br&gt;");
        document.writeln("赠送周期: " + duration + "&lt;br&gt;");
        document.writeln("大众会员类型: " + attType + "&lt;br&gt;");
        document.writeln("大众会员个数: " + attNum + "&lt;br&gt;");
        document.writeln("优惠券类型: " + couponType + "&lt;br&gt;");
        document.writeln("最低使用金额: " + couponMinAmount + "&lt;br&gt;");
        document.writeln("减少金额: " + couponReduceAmount + "&lt;br&gt;");
        document.writeln("优惠折扣: " + couponDiscount + "&lt;br&gt;");
        document.writeln("添加时间: " + createTime + "&lt;br&gt;");
        document.writeln("添加时间: " + endDate + "&lt;br&gt;");
    }


&lt;/script&gt;

&lt;body&gt;
&lt;form action="" method="post"&gt;
    &lt;div id="giftPackageNum"&gt;
        &lt;label class="control-label"&gt;礼包编号：&lt;/label&gt;
        &lt;select id="giftNum" style="width: 10em"&gt;
            &lt;option id="giftNum1" value=1&gt;礼包一&lt;/option&gt;
            &lt;option id="giftNum2" value=2&gt;礼包二&lt;/option&gt;
            &lt;option id="giftNum3" value=3&gt;礼包三&lt;/option&gt;
            &lt;option id="giftNum4" value=4&gt;礼包四&lt;/option&gt;
            &lt;option id="giftNum5" value=5&gt;礼包五&lt;/option&gt;
            &lt;option id="giftNum6" value=6&gt;礼包六&lt;/option&gt;
            &lt;option id="giftNum7" value=7&gt;礼包七&lt;/option&gt;
            &lt;option id="giftNum8" value=8&gt;礼包八&lt;/option&gt;
            &lt;option id="giftNum9" value=9&gt;礼包九&lt;/option&gt;
        &lt;/select&gt;
    &lt;/div&gt;
    &lt;div class="control-group"&gt;
        &lt;label class="control-label"&gt;活动编号：&lt;/label&gt;
        &lt;input id="activityNo" name="activityNo" type="text"&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;label class="control-label"&gt;添加奖励：&lt;/label&gt;
    &lt;select id="addReward" class="giftType" name="giftType" size="1"&gt;
        &lt;option id="other" value=0&gt;选择奖励类型&lt;/option&gt;
        &lt;option id="member" value=1&gt;个人会员&lt;/option&gt;
        &lt;option id="kumiaoMember" value=2&gt;酷喵会员&lt;/option&gt;
        &lt;option id="attachment" value=3&gt;大众会员&lt;/option&gt;
        &lt;option id="coupon" value=4&gt;优惠券&lt;/option&gt;
    &lt;/select&gt;
    &lt;div class="control-group"&gt;
        &lt;label class="control-label"&gt;地区类型：&lt;/label&gt;
        &lt;select id="areaType" name="areaType" size="1"&gt;
            &lt;option id="country" value=1&gt;全国&lt;/option&gt;
            &lt;option id="oneProvince" value=2&gt;单省&lt;/option&gt;
            &lt;option id="noArea" value=0&gt;无地区&lt;/option&gt;
        &lt;/select&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="control-group"&gt;
        &lt;label class="control-label"&gt;赠送周期：&lt;/label&gt;
        &lt;input id="duration" name="duration" type="text"&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;
            &lt;font color="silver"&gt;&amp;ensp;例如：单位:天。15天，30天(1月)&lt;/font&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div id="div_input"&gt;
        &lt;div id="div_attachment" class="div_attachment"&gt;
            &lt;div class="control-group-attachment"&gt;
                &lt;label class="control-label-attachment"&gt;大众会员类型：&lt;/label&gt;
                &lt;input id="attType" name="attType" type="text"&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;
                &lt;font color="silver"&gt;&amp;ensp;1&lt;/font&gt;&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="control-group-attachment"&gt;
                &lt;label class="control-label-attachment"&gt;大众会员个数：&lt;/label&gt;
                &lt;input id="attNum" name="attNum" type="text"&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div id="div_coupon" class="div_coupon"&gt;
            &lt;div class="control-group-coupon"&gt;
                &lt;label id="control-label-coupon-type" class="control-label-coupon"&gt;优惠券类型：&lt;/label&gt;
                &lt;input id="couponType" name="couponType" type="text"&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;
            &lt;/div&gt;
            &lt;div class="control-group-coupon"&gt;
                &lt;label id="control-label-coupon-min" class="control-label-coupon"&gt;最低使用消费金额：&lt;/label&gt;
                &lt;input id="couponMinAmount" name="couponMinAmount" type="text"&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;
                &lt;font color="silver"&gt;单位:元&lt;/font&gt;&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="control-group-coupon"&gt;
                &lt;label id="control-label-coupon-reduce" class="control-label-coupon"&gt;减少金额：&lt;/label&gt;
                &lt;input id="couponReduceAmount" name="couponReduceAmount" type="text"&gt;&lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;
                &lt;font color="silver"&gt;单位:元&lt;/font&gt;&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="control-group-coupon"&gt;
                &lt;label id="control-label-coupon-discount"  class="control-label-coupon"&gt;优惠折扣：&lt;/label&gt;
                &lt;input id="couponDiscount" name="couponDiscount" type="text"&gt;
                &lt;span&gt;&lt;font color="red"&gt;&amp;nbsp;*&lt;/font&gt;&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="control-group"&gt;
            &lt;label class="control-label"&gt;添加时间：&lt;/label&gt;
            &lt;input id="createTime" name="createTime" type="text" onclick="WdatePicker({skin:'whyGreen',dateFmt:'yyyy-MM-dd HH:mm:ss'})"/&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;button onclick="getDate()"&gt;提交&lt;/button&gt;
&lt;/form&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>效果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe77891717a4b6966d4508f970bf2d76d.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F795cfa6db6cad78db1eeb2bb9009d472.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/105993529</p>]]></description><guid isPermaLink="false">/archives/c9d94ade-125c-4e0e-8f29-539646ab2920</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:43:18 GMT</pubDate></item><item><title><![CDATA[Redis 分布式锁的正确实现方式（ Java 版 ）]]></title><link>https://wiki.dgfygk.com/archives/ed897de3-a43c-4547-afd8-34e128bf4adb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%EF%BC%88%20Java%20%E7%89%88%20%EF%BC%89&amp;url=/archives/ed897de3-a43c-4547-afd8-34e128bf4adb" width="1" height="1" alt="" style="opacity:0;">
<h4>原文链接：<a href="https://yq.aliyun.com/articles/307547">https://yq.aliyun.com/articles/307547</a></h4>
<p><a href="https://yq.aliyun.com/go/articleRenderRedirect?url=http%3A%2F%2Fwww.roncoo.com%2Farticle%2Findex%3Ftitle%3D%25E5%2588%2586%25E5%25B8%2583%25E5%25BC%258F%25E9%2594%2581">分布式锁</a>一般有三种实现方式：1. 数据库乐观锁；2. 基于Redis的分布式锁；3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式，基于Redis实现分布式锁。虽然网上已经有各种介绍Redis分布式锁实现的博客，然而他们的实现却有着各种各样的问题，为了避免误人子弟，本篇博客将详细介绍如何正确地实现Redis分布式锁。
 <br>
 可靠性
 <br>
 首先，为了确保分布式锁可用，我们至少要确保锁的实现同时满足以下四个条件：
 <br>
 互斥性。在任意时刻，只有一个客户端能持有锁。
 <br>
 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁，也能保证后续其他客户端能加锁。
 <br>
 具有容错性。只要大部分的Redis节点正常运行，客户端就可以加锁和解锁。
 <br>
 解铃还须系铃人。加锁和解锁必须是同一个客户端，客户端自己不能把别人加的锁给解了。
 <br>
 代码实现
 <br>
 组件依赖
 <br>
 首先我们要通过Maven引入Jedis开源组件，在pom.xml文件加入下面的代码：</p>
<pre><code class="java">&lt;dependency&gt;
    &lt;groupId&gt;redis.clients&lt;/groupId&gt;
    &lt;artifactId&gt;jedis&lt;/artifactId&gt;
    &lt;version&gt;2.9.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>加锁代码
 <br>
 正确姿势
 <br>
 Talk is cheap, show me the code。先展示代码，再解释为什么这样实现：</p>
<pre><code class="java">public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";


   static JedisPool jedisPool = null;

    static {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(20);
        poolConfig.setMinIdle(10);
        poolConfig.setMaxTotal(30);
        jedisPool = new JedisPool(poolConfig, '127.0.0.1', 6379);
    }


    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {

        Jedis jedis = jedisPool.getResource();

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
</code></pre>
<p>可以看到，我们加锁就一行代码：jedis.set(String key, String value, String nxxx, String expx, int time)，这个set()方法一共有五个形参：</p>
<p>第一个为key，我们使用key来当锁，因为key是唯一的。</p>
<p>第二个为value，我们传的是requestId，很多童鞋可能不明白，有key作为锁不就够了吗，为什么还要用到value？原因就是我们在上面讲到可靠性时，分布式锁要满足第四个条件解铃还须系铃人，通过给value赋值为requestId，我们就知道这把锁是哪个请求加的了，在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。</p>
<p>第三个为nxxx，这个参数我们填的是NX，意思是SET IF NOT EXIST，即当key不存在时，我们进行set操作；若key已经存在，则不做任何操作；</p>
<p>第四个为expx，这个参数我们传的是EX（秒），PX(毫秒)，意思是我们要给这个key加一个过期的设置，具体时间由第五个参数决定。</p>
<p>第五个为time，与第四个参数相呼应，代表key的过期时间。
 <br>
 总的来说，执行上面的set()方法就只会导致两种结果：1. 当前没有锁（key不存在），那么就进行加锁操作，并对锁设置个有效期，同时value表示加锁的客户端。2. 已有锁存在，不做任何操作。</p>
<p>心细的童鞋就会发现了，我们的加锁代码满足我们可靠性里描述的三个条件。首先，set()加入了NX参数，可以保证如果已有key存在，则函数不会调用成功，也就是只有一个客户端能持有锁，满足互斥性。其次，由于我们对锁设置了过期时间，即使锁的持有者后续发生崩溃而没有解锁，锁也会因为到了过期时间而自动解锁（即key被删除），不会发生死锁。最后，因为我们将value赋值为requestId，代表加锁的客户端请求标识，那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。由于我们只考虑Redis单机部署的场景，所以容错性我们暂不考虑。</p>
<p>错误示例1
 <br>
 比较常见的错误示例就是使用jedis.setnx()和jedis.expire()组合实现加锁，代码如下：</p>
<pre><code class="java">public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {

    Long result = jedis.setnx(lockKey, requestId);
    if (result == 1) {
        // 若在这里程序突然崩溃，则无法设置过期时间，将发生死锁
        jedis.expire(lockKey, expireTime);
    }

}
</code></pre>
<p>setnx()方法作用就是SET IF NOT EXIST，expire()方法就是给锁加一个过期时间。乍一看好像和前面的set()方法结果一样，然而由于这是两条Redis命令，不具有原子性，如果程序在执行完setnx()之后突然崩溃，导致锁没有设置过期时间。那么将会发生死锁。网上之所以有人这样实现，是因为低版本的jedis并不支持多参数的set()方法。</p>
<p>错误示例2
 <br>
 这一种错误示例就比较难以发现问题，而且实现也比较复杂。实现思路：使用jedis.setnx()命令实现加锁，其中key是锁，value是锁的过期时间。执行过程：1. 通过setnx()方法尝试加锁，如果当前锁不存在，返回加锁成功。2. 如果锁已经存在则获取锁的过期时间，和当前时间比较，如果锁已经过期，则设置新的过期时间，返回加锁成功。代码如下：</p>
<pre><code class="java">public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) {

    long expires = System.currentTimeMillis() + expireTime;
    String expiresStr = String.valueOf(expires);

    // 如果当前锁不存在，返回加锁成功
    if (jedis.setnx(lockKey, expiresStr) == 1) {
        return true;
    }

    // 如果锁存在，获取锁的过期时间
    String currentValueStr = jedis.get(lockKey);
    if (currentValueStr != null &amp;&amp; Long.parseLong(currentValueStr) &lt; System.currentTimeMillis()) {
        // 锁已过期，获取上一个锁的过期时间，并设置现在锁的过期时间
        String oldValueStr = jedis.getSet(lockKey, expiresStr);
        if (oldValueStr != null &amp;&amp; oldValueStr.equals(currentValueStr)) {
            // 考虑多线程并发的情况，只有一个线程的设置值和当前值相同，它才有权利加锁
            return true;
        }
    }

    // 其他情况，一律返回加锁失败
    return false;

}
</code></pre>
<p>那么这段代码问题在哪里？1. 由于是客户端自己生成过期时间，所以需要强制要求分布式下每个客户端的时间必须同步。 2. 当锁过期的时候，如果多个客户端同时执行jedis.getSet()方法，那么虽然最终只有一个客户端可以加锁，但是这个客户端的锁的过期时间可能被其他客户端覆盖。3. 锁不具备拥有者标识，即任何客户端都可以解锁。
 <br>
 解锁代码
 <br>
 正确姿势
 <br>
 还是先展示代码，再带大家慢慢解释为什么这样实现：</p>
<pre><code class="java">public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(String lockKey, String requestId) {

         // jedis Redis客户端
        Jedis jedis = jedisPool.getResource();
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
</code></pre>
<p>可以看到，我们解锁只需要两行代码就搞定了！第一行代码，我们写了一个简单的Lua脚本代码，上一次见到这个编程语言还是在《黑客与画家》里，没想到这次居然用上了。第二行代码，我们将Lua代码传到jedis.eval()方法里，并使参数KEYS[1]赋值为lockKey，ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。</p>
<p>那么这段Lua代码的功能是什么呢？其实很简单，首先获取锁对应的value值，检查是否与requestId相等，如果相等则删除锁（解锁）。</p>
<p>那么为什么要使用Lua语言来实现呢？因为要确保上述操作是原子性的。关于非原子性会带来什么问题，可以阅读【解锁代码-错误示例2】 。</p>
<p>那为什么执行eval()方法可以确保原子性，源于Redis的特性，下面是官网对eval命令的部分解释：
 <br>
 简单来说，就是在eval命令执行Lua代码的时候，Lua代码将被当成一个命令去执行，并且直到eval命令执行完成，Redis才会执行其他命令。</p>
<p>错误示例1</p>
<p>最常见的解锁代码就是直接使用jedis.del()方法删除锁，这种不先判断锁的拥有者而直接解锁的方式，会导致任何客户端都可以随时进行解锁，即使这把锁不是它的。</p>
<pre><code class="java">public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
    jedis.del(lockKey);
}
</code></pre>
<p>错误示例2
 <br>
 这种解锁代码乍一看也是没问题，甚至我之前也差点这样实现，与正确姿势差不多，唯一区别的是分成两条命令去执行，代码如下：</p>
<pre><code class="java">public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {

    // 判断加锁与解锁是不是同一个客户端
    if (requestId.equals(jedis.get(lockKey))) {
        // 若在此时，这把锁突然不是这个客户端的，则会误解锁
        jedis.del(lockKey);
    }

}
</code></pre>
<p>如代码注释，问题在于如果调用jedis.del()方法的时候，这把锁已经不属于当前客户端的时候会解除他人加的锁。那么是否真的有这种场景？答案是肯定的，比如客户端A加锁，一段时间之后客户端A解锁，在执行jedis.del()之前，锁突然过期了，此时客户端B尝试加锁成功，然后客户端A再执行del()方法，则将客户端B的锁给解除了。</p>
<p><strong>总结</strong>
 <br>
 本文主要介绍了如何使用Java代码正确实现Redis分布式锁，对于加锁和解锁也分别给出了两个比较经典的错误示例。其实想要通过Redis实现分布式锁并不难，只要保证能满足可靠性里的四个条件。</p>
<p>分布锁主要是用在什么场景？需要同步的地方，比如说插入一条数据，需要事先检查数据库是否有类似的数据，多个请求同时插入的时候，可能会判断到数据库都返回没有类似的数据，则都可以加入。这时候需要进行同步处理，但是直接数据库锁表太耗时间，所以采用redis分布式锁，同时只能有一个线程去进行插入数据这个操作，其他的线程都等待。</p>
<p>如果你的项目中Redis是多机部署的，那么可以尝试使用Redisson实现分布式锁，这是Redis官方提供的Java组件，链接在参考阅读章节已经给出。</p>
<p>文章来源：<a href="https://yq.aliyun.com/go/articleRenderRedirect?url=http%3A%2F%2Fgeek.csdn.net%2Fnews%2Fdetail%2F246676">http://geek.csdn.net/news/detail/246676</a>
 <br>
 更多技术内容请登录：<a href="https://yq.aliyun.com/go/articleRenderRedirect?url=http%3A%2F%2Fwww.roncoo.com%2Farticle%2Findex">http://www.roncoo.com/article/index</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103983450</p>]]></description><guid isPermaLink="false">/archives/ed897de3-a43c-4547-afd8-34e128bf4adb</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:42:20 GMT</pubDate></item><item><title><![CDATA[批量删除Redis数据库中的Key]]></title><link>https://wiki.dgfygk.com/archives/0f1025db-f8bb-4adf-93e5-da452da235f5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%89%B9%E9%87%8F%E5%88%A0%E9%99%A4Redis%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%AD%E7%9A%84Key&amp;url=/archives/0f1025db-f8bb-4adf-93e5-da452da235f5" width="1" height="1" alt="" style="opacity:0;">
<p>1、批量删除Key
 <br>
 Redis 中有删除单个 Key 的指令 DEL，但好像没有批量删除 Key 的指令，不过我们可以借助 Linux 的 xargs 指令来完成这个动作</p>
<pre><code>redis-cli keys "*" | xargs redis-cli del
//如果redis-cli没有设置成系统变量，需要指定redis-cli的完整路径
//如：/opt/redis/redis-cli keys "*" | xargs /opt/redis/redis-cli del
</code></pre>
<p>2、如果要指定 Redis 数据库访问密码，使用下面的命令</p>
<pre><code>redis-cli -a password keys "*" | xargs redis-cli -a password del
</code></pre>
<p>
 <br>
</p>
<p>3、如果要访问 Redis 中特定的数据库，使用下面的命令</p>
<pre><code>//下面的命令指定数据序号为0，即默认数据库
redis-cli -n 0 keys "*" | xargs redis-cli -n 0 del
</code></pre>
<p>4、删除所有Key
 <br>
 删除所有Key，可以使用Redis的flushdb和flushall命令</p>
<pre><code>//删除当前数据库中的所有Key
flushdb
//删除所有数据库中的key
flushall
</code></pre>
<p>注：keys 指令可以进行模糊匹配，但如果 Key 含空格，就匹配不到了，暂时还没发现好的解决办法。</p>
<p><a href="https://blog.csdn.net/spring21st/article/details/15771861?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-1&amp;depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-1">转载文章地址</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/105953131</p>]]></description><guid isPermaLink="false">/archives/0f1025db-f8bb-4adf-93e5-da452da235f5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:42:06 GMT</pubDate></item><item><title><![CDATA[异常：No validator could be found for constraint '.....' validating type 'java.lang.Integer'.]]></title><link>https://wiki.dgfygk.com/archives/df62ca18-8618-443e-8071-7e164b96396a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BC%82%E5%B8%B8%EF%BC%9ANo%20validator%20could%20be%20found%20for%20constraint%20%27.....%27%20validating%20type%20%27java.lang.Integer%27.&amp;url=/archives/df62ca18-8618-443e-8071-7e164b96396a" width="1" height="1" alt="" style="opacity:0;">
<p>异常信息如下：</p>
<pre><code class="java">No validator could be found for constraint 'org.hibernate.validator.constraints.Length' validating type 'java.lang.Integer'. Check configuration for 'xxxxxx'
</code></pre>
<p>错误原因字面翻译是：约束'org.hibernate.validator.constraints.Length'验证类型'java.lang.Integer'找不到验证器。 检查” XXXXXX"的配置。也就是 在类中使用的校验注解所支持的数据类型与实体中字段的类型不符合。</p>
<p>例如：</p>
<pre><code class="java">        private Integer status; 
        private String strdemo;

        @NotEmpty(message="状态：不能为空")
    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Length(message="长度必须介于 0 和 255 之间")
    public String getStrdemo() {
        return strdemo;
    }

    public void setStrdemo(String strdemo) {
        this.strdemo= strdemo;
    }
</code></pre>
<p>上面代码中在Integer status 的字段上使用@NotEmpty，@NotEmpty支持的是字符串类型字段，这样子使用肯定是会报错的。</p>
<p><strong>解决方法</strong>：换个校验注解- 将Integer类型的字段上使用的@NotEmpty换成@NotNull，或者不使用校验注解</p>
<pre><code class="java">        private Integer status; 
        private String strdemo;

        @NotNull(message="状态：不能为空")
    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Length(message="长度必须介于 0 和 255 之间")
    public String getStrdemo() {
        return strdemo;
    }

    public void setStrdemo(String strdemo) {
        this.strdemo= strdemo;
    }
</code></pre>
<p>或</p>
<pre><code class="java">        private Integer status; 

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/106037414</p>]]></description><guid isPermaLink="false">/archives/df62ca18-8618-443e-8071-7e164b96396a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:41:52 GMT</pubDate></item><item><title><![CDATA[idea开发过程中，项目中之前导入的包不能被识别，并出现红色下划线]]></title><link>https://wiki.dgfygk.com/archives/fe4ba396-c738-4b9e-8c2d-276df4a48666</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=idea%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B%E4%B8%AD%EF%BC%8C%E9%A1%B9%E7%9B%AE%E4%B8%AD%E4%B9%8B%E5%89%8D%E5%AF%BC%E5%85%A5%E7%9A%84%E5%8C%85%E4%B8%8D%E8%83%BD%E8%A2%AB%E8%AF%86%E5%88%AB%EF%BC%8C%E5%B9%B6%E5%87%BA%E7%8E%B0%E7%BA%A2%E8%89%B2%E4%B8%8B%E5%88%92%E7%BA%BF&amp;url=/archives/fe4ba396-c738-4b9e-8c2d-276df4a48666" width="1" height="1" alt="" style="opacity:0;">
<h2>解决办法如下：</h2>
<p>步骤一：点击File–&gt;选择Invalidate Caches / Restart…</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6718745ba4aa34bc33adad5ca3f88b5d.png&amp;size=m" alt=""></p>
<p>步骤二：点击Invalidate and Restart</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4bb1116e3353e9dbf2096aa0fee424be.png&amp;size=m" alt=""></p>
<p>等到IDEA重新启动后，便可以看到包都被识别了，红色下划线也没有了。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/104626947</p>]]></description><guid isPermaLink="false">/archives/fe4ba396-c738-4b9e-8c2d-276df4a48666</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:41:38 GMT</pubDate></item><item><title><![CDATA[redis 使用命令delete所有key]]></title><link>https://wiki.dgfygk.com/archives/db6d8912-ade3-4470-ad72-18aeb0f5a291</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=redis%20%E4%BD%BF%E7%94%A8%E5%91%BD%E4%BB%A4delete%E6%89%80%E6%9C%89key&amp;url=/archives/db6d8912-ade3-4470-ad72-18aeb0f5a291" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="java">private static final String MEMBER_ID_KEY = "member:id";

@Resource(name = "RedisTemplate")
private RedisTemplate&lt;String, String&gt; redisTemplate;

Date expireDate = Date.from(LocalDateTime.ofInstant(LocalDateTime.now().toInstant(), ZoneId.systemDefault()).plusDays(1).atZone(ZoneId.systemDefault()).toInstant());
for(int i = 101; i &lt;= 100001; i++) {
   String jsonStr = MEMBER_ID_KEY  + i;
   redisTemplate.opsForValue().set(jsonStr ,  String.valueOf(i));
   redisTemplate.expireAt(jsonStr , expireDate);
}
</code></pre>
<p>上面是用程序往redis中添加一些值，key的前缀是member:id。下面是用命令删除这些值。</p>
<pre><code class="java">[root@localhost~]# redis-cli
127.0.0.1:6379&gt; redis-cli EVAL "local keys = redis.call('keys', ARGV[1]) for i=1,#keys,5000 do redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) end return #keys" 0 'member:id:*'
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/105952620</p>]]></description><guid isPermaLink="false">/archives/db6d8912-ade3-4470-ad72-18aeb0f5a291</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Thu, 26 Sep 2024 00:41:01 GMT</pubDate></item><item><title><![CDATA[【微信自动续费】委托代扣接口常见问题Q&A]]></title><link>https://wiki.dgfygk.com/archives/65be0880-e330-4855-b8f5-b6c8f7a61fb9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E5%BE%AE%E4%BF%A1%E8%87%AA%E5%8A%A8%E7%BB%AD%E8%B4%B9%E3%80%91%E5%A7%94%E6%89%98%E4%BB%A3%E6%89%A3%E6%8E%A5%E5%8F%A3%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98Q%26A&amp;url=/archives/65be0880-e330-4855-b8f5-b6c8f7a61fb9" width="1" height="1" alt="" style="opacity:0;">
<p><strong>Q: 如何申请委托代扣？</strong></p>
<p>A：目前微信支付委托代扣还不支持在线申请，需要通过对接的微信业务同学帮忙申请。</p>
<p><strong>Q：模版内容是否可以修改？</strong></p>
<p>A：不能。</p>
<p><strong>Q：模版内容的扣款金额是否是实际扣款金额？</strong></p>
<p>A：不是，以申请扣款请求中的金额为准。</p>
<p><strong>Q: 申请扣款周期说明：</strong></p>
<p>A:</p>
<p>1）自动续费周期一般是以月、季度为周期，具体扣款发起时间不受模版内容的周期影响。</p>
<p>2）委托代扣额度初始额度：单笔500、单日2500、单月75000；</p>
<p>2.1）授权扣款和免密支付，在额度限制内可以每天扣款5次（同一个用户在一个签约协议下；扣款失败不计算次数）；自动续费模版需要按模版内容设定的周期扣款，且同一个用户在一个签约协议下每天仅可以扣款一次，如上线前需多次测试签约扣款，请申请测试模版（模版名称标明为测试，测试模版限额为每次0.01元，每天可扣款100次）。</p>
<p><strong>Q: 扣款金额是否存在限制？</strong></p>
<p>A: 扣款金额限制为商户配置为准，扣款额度范围在设置额度以内；例：单笔500，那么只能扣款500，不可以超出500范围。</p>
<p><strong>Q：对于AutoDebit，如果客户签约时选择的卡扣款失败，微信是否回去尝试从客户其他卡或余额种扣款？如果是，微信给商户返回的失败原因是第一次尝试的失败原因还是最后一次的？</strong></p>
<p>A：如果用户默认的扣款方式扣款失败，微信会依次按以下顺序扣费：零钱、信用卡、借记卡。微信给商户返回的失败原因是最后一次扣款的结果。</p>
<p><strong>Q：如果客户在扣款前删除了签约时选择的卡片，微信是否直接给商户侧返回失败（失败原因是什么），还是微信会去尝试其他卡和余额？</strong></p>
<p>A：用户正常签约后，会按照签约规则进行扣款，和是否删除卡片无关。</p>
<p><strong>Q：能否抓出每笔失败是信用卡还是借记卡，如果一次扣款中尝试了客户不同卡和余额，可以只统计第一次尝试的卡（也就是客户签约时选择的卡）的种类?</strong></p>
<p>A：这个对于微信这边是属于机密数据，不能提供给商家，十分抱歉。</p>
<p><strong>Q：委托代扣分为哪几种模式？</strong></p>
<p>A：委托代扣分为纯签约模式和支付中签约模式。</p>
<p>1）纯签约模式即先完成签约后发起扣款，适用的场景包含：公众号、APP、小程序、H5。（其中APP、H5需要在拥有委托代扣权限基础上再特殊申请）。</p>
<p>2）支付中签约模式即通过支付完成签约，适用的场景包含：公众号、APP、小程序、H5、扫码。</p>
<p><strong>Q：纯签约模式和支付中签约模式有什么不同？</strong></p>
<p>A：</p>
<p>1）纯签约模式是: 用户进行签约，签约成功后，商户再使用申请扣款接口进行扣款。</p>
<p>2）支付中签约模式是: 支付后完成代扣协议的签约。</p>
<p><strong>Q: 委托代扣接口是否支持服务商模式？</strong></p>
<p>A：委托代扣接口支持服务商模式，但服务商模式不支持支付中签约接口场景。</p>
<p><strong>Q: 委托代扣模版是否通用所有委托代扣接口？</strong></p>
<p>A：APP纯签约和H5纯签约需要单独申请并配置域名及跳转权限，具体可以咨询对接运营。</p>
<p><strong>Q：一个微信用户能否与商户多次签约？</strong></p>
<p>A：签约遵循同一个商户号+同一个模版ID+同一个微信号只能签约一次的原则。多次签约可采用多模版ID的方式，同一个商户号下的每个不同模版ID都可以与同一微信用户分别签约一次。</p>
<p><strong>Q：委托代扣解约回调地址修改后多久生效？</strong></p>
<p>A：实时生效。</p>
<p><strong>Q：解约回调地址在哪里设置？</strong></p>
<p>A：解约回调地址是在设置模板ID的时候添加的，可以前往商户平台-委托代扣页添加和修改。</p>
<p><strong>Q：解约回调地址是否可以配置多个？</strong></p>
<p>A：解约通知地址在创建模版ID时填写，每个模版ID只能填写一个，不同的模版ID可以配置不同的解约回调地址。</p>
<p><strong>Q：解约回调地址在什么场景下会收到通知？</strong></p>
<p>A：用户主动解约和商户发起申请解约请求成功后都会有解约回调通知信息。</p>
<p><strong>Q：申请扣款接口请求成功，为什么还会扣款失败？</strong></p>
<p>A：申请扣款接口返回成功仅代表申请受理成功，不代表最终的扣款结果，扣款结果应以支付结果通知接口通知的结果为准，无论扣款是否成功都会返回扣款结果及原因。</p>
<p><strong>Q：请求签约接口时返回提示"Invalid request”？</strong></p>
<p>A：没有当前签约接口权限，请联系对接运营申请。</p>
<p><strong>Q：商户调用申请扣款返回用户状态"账户开启安全锁，阻断签约，请联系银行"如何解决？</strong></p>
<p>A：该情况是用户对银行卡进行了限制设置，需要用户关闭安全锁。</p>
<p><strong>Q：如果发起代扣之后，扣款失败了。是否可以使用同一个"out_trade_no"再次发起扣款？</strong></p>
<p>A：这个问题建议先进行订单状态查询，再根据订单状态判断是否使用原订单号再次发起扣款。如果订单状态查询结果为"订单关闭”“订单已支付"则不能使用原订单号再次扣款，如果查询结果为"订单未支付”“订单不存在"则可以使用原订单号再次扣款。</p>
<p><strong>Q：APP发起签约报"报跳转失败"如何解决？</strong></p>
<p>A：</p>
<p>1），和微信侧商务同学确认外部APP跳转微信客户端的权限是否开通成功。</p>
<p>2），微信iOS和Android客户端6.2版本及以上才支持本功能。</p>
<p>3），当前调起签约接口里的APPID需要使用在开放平台注册的应用的APPID。</p>
<p>4），在开放平台检查APP的配置是否正确（IOS检查bundleid，安卓检查包名，包签名），当前调起签约APP的信息需与这些配置一致。</p>
<p>5），[WXApi sendReq:req];这句是否有写，详情查看SDK文档使用说明。</p>
<p>6），签约链接前后是否有空格，中间是否有特殊字符。</p>
<p><strong>Q：签约时报错"网络异常签约失败"如何解决？</strong></p>
<p>A：请确认模版ID和商户号匹配且模版ID已审核通过。</p>
<p><strong>Q:请求签约接口返回签名失败如何解决？</strong></p>
<p>A：</p>
<p>1），需要确认参数类型和大小写与文档一致。</p>
<p>2），核对接口文档介绍，是否限制签名类型，例如该接口仅支持HMAC-SHA256。</p>
<p>3），核对接口文档notify_url的介绍说明是否需要encode。</p>
<p>4），签名时参数需要使用原值。</p>
<p><strong>Q：请求签约接口返回"system erro"如何解决？</strong></p>
<p>A：</p>
<p>1），排查商户信息是否匹配，例如商户号和appid是否存在绑定关系，服务商商户号和子商户号信息及子商户号appid是否具有绑定关系。</p>
<p>2），排查plan_id模版是否审核通过。</p>
<p>3），检查openid和appid是否匹配。</p>
<p><strong>Q：请求签约接口返回"参数构造错误"如何解决？</strong></p>
<p>A：</p>
<p>1），排查timestamp是否符合参数规则10位。</p>
<p>2），排查参数值长度是否超过了文档标注的长度限制。</p>
<p>3），核对文档notify_url参数是否需要进行encode。</p>
<p>4）, 签名时需要使用原值，不可以使用encode后的值进行签名。</p>
<p>微信开放社区链接：<a href="https://developers.weixin.qq.com/community/pay/doc/0004aaa01e8908b165985d15e5bc08">https://developers.weixin.qq.com/community/pay/doc/0004aaa01e8908b165985d15e5bc08</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103597673</p>]]></description><guid isPermaLink="false">/archives/65be0880-e330-4855-b8f5-b6c8f7a61fb9</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:40:26 GMT</pubDate></item><item><title><![CDATA[115个Java面试题及回答]]></title><link>https://wiki.dgfygk.com/archives/94dc10ca-caab-4a2d-a136-9d89d517bc7c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=115%E4%B8%AAJava%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%8A%E5%9B%9E%E7%AD%94&amp;url=/archives/94dc10ca-caab-4a2d-a136-9d89d517bc7c" width="1" height="1" alt="" style="opacity:0;">
<p>在本教程中,我们将讨论在Java面试中,用人单位用来测试应聘者Java以及面向对象的能力的面试题目.以下章节我们将按照以下结构讨论面试问题，面向对象编程及其特性，Java及其特性的一般问题,集合,垃圾回收，异常处理,Java applets,Swing,JDBC,RMI, Servlet 和 JSP.</p>
<p>来，我们一起出发吧</p>
<p>目录</p>
<ul>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#1">Object Oriented Programming (OOP)</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#2">General Questions about Java</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#3">Java Threads</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#4">Java Collections</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#5">Garbage Collectors</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#6">Exception Handling</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#7">Java Applets</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#8">Swing</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#9">JDBC</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#10">Remote Method Invocation (RMI)</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#11">Servlets</a></p>
 </li>
 <li>
  <p><a href="http://www.oschina.net/translate/java-interview-questions-and-answers#12">JSP</a></p>
 </li>
</ul>
<h4>面向对象编程(OOP)</h4>
<p>Java是支持并发，基于类的以及面向对象的一种计算机编程语言. 以下列举了面向对象编程的优势:</p>
<ul>
 <li>
  <p>模块化编程，使维护和修改更加容易</p>
 </li>
 <li>
  <p>代码重用</p>
 </li>
 <li>
  <p>提高代码的可靠性以及灵活性</p>
 </li>
 <li>
  <p>提高代码可读性</p>
 </li>
</ul>
<p>面向对象编程有其非常明显的特性，比如说封装,继承,多态和抽象. 下面我们来分析一下每种特性.</p>
<p><strong>封装(Encapsulation)</strong></p>
<p>封装，提供的对象隐藏内部特性和行为的一种能力，每个对象提供了一些方法，其他的对象可以访问并改变其内部数据。在Java中，提供了三种访问修饰符: 公有的，私有的以及保护的。每个修饰符都设定了不同的访问权限，这个权限设置不会因为包的不同而有差异。</p>
<p>下面是使用封装的一些优点:</p>
<ul>
 <li>
  <p>通过隐藏属性来保护对象的内部信息</p>
 </li>
 <li>
  <p>因为可以独立的修改或者扩展对象的行为，从而提高代码的可用性以及维护性</p>
 </li>
 <li>
  <p>隔离，通过阻止对象使用不希望的互动方式来交互，以此来提高模块化程度。</p>
 </li>
</ul>
<p>你可以通过<a href="http://examples.javacodegeeks.com/java-basics/encapsulation-in-java/">链接</a>访问我们的教程查看关于封装更多的细节和实例.</p>
<p><strong>多态(Polymorphism)</strong></p>
<p>多态就是针对不同的基础数据类型呈现相同接口的一种能力，多态类型就是其操作可以适用于不同类型值的一种类型。</p>
<p><strong>继承(Inheritance)</strong></p>
<p>继承提供了一个对象从基类获取字段和方法的一种能力.继承提供了代码的重用性，并且在不更改现有类的情况下，对现有类增加额外的功能。</p>
<p><strong>抽象(Abstraction)</strong></p>
<p>抽象是从具体的实例中分离想法的过程，根据他们各自的功能而非具体的实现来开发类. Java中支持创建和存在暴露接口的抽象类， 而没有包括方法的具体实现。抽象方法的宗旨就是将类的行为和具体实现分离开。</p>
<p><strong>抽象和封装的异同</strong></p>
<p>抽象和封装是个互补的概念。一方面，抽象专注在对象的行为上，而另外一方面，封装专注于对象的行为的具体实现。封装是通过隐藏对象的内部信息来实现的，因此也可以被看做是抽象的一种策略。</p>
<h4>有关Java的一般问题</h4>
<p><strong>1. 什么是JVM? 为什么称Java为跨平台的编程语言?</strong></p>
<p>Java虚拟机(Java Virtual Machine)是可以执行Java字节码的<a href="http://www.javacodegeeks.com/2013/12/part-1-of-3-synopsis-of-articles-videos-on-performance-tuning-jvm-gc-in-java-mechanical-sympathy-et-al.html">虚拟机</a>，每个Java源文件将被编译成<a href="http://www.javacodegeeks.com/2013/12/mastering-java-bytecode.html">字节码</a>文件，然后在JVM中执行。Java之所以被设计成可以在任意的平台运行，而不需要重写或者在不同的平台下重新编译，这些都要归功于Java虚拟机(JVM),因为JVM非常了解特定的指令的长度以及底层硬件平台的特殊性。</p>
<p><strong>2. JDK和JRE之间的差异是什么？</strong></p>
<p>Java运行环境(Java Runtime Enviroment) 是运行Java程序的基本的Java虚拟机，包括执行applet的浏览器插件。JDK (Java Development Kit) 是为了开发，编译和执行Java应用程序，针对Java的全功能的软件开发包，包含了JRE，编译器和工具(比如说 <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html">JavaDoc</a> 和<a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/jdb.html">Java Debugger</a>)。</p>
<p><strong>3. “static” 关键字是什么意思？在Java里可以 override private 或 static 的方法吗？ keyword mean ? Can you override private or static method in Java ?</strong></p>
<p>static 关键字表示，访问这个成员变量或方法时，不必获取它属于的类的实例。</p>
<p><a href="http://www.javacodegeeks.com/2012/05/java-static-methods-can-be-code-smell.html">Java 里的 static 方法</a>不能被 override，因为 override 的机制是运行时(runtime)的动态绑定，而 static 方法是在编译时静态绑定的。static 方法并不与任何类的具体实例有关，因此无法应用继承的概念。</p>
<p><strong>4. 在静态方法里可以访问非静态变量吗?</strong></p>
<p>Java 中的 static 变量归相应的类所有，它的值对于类的所有实例都是相同的。static 变量是在 JVM 加载类的时候初始化的。如果代码试图访问非静态的变量，而且不是通过类的实例去访问，编译器会报错，因为这些非静态变量还没有被创建呢，并且它们没有与实例相关联。</p>
<p><strong>5. Java 支持哪些数据类型？什么是 Autoboxing 和 Unboxing？</strong>
 <br>
 Java语言支持的8个基本数据类型如下：</p>
<ul>
 <li>
  <p>byte</p>
 </li>
 <li>
  <p>short</p>
 </li>
 <li>
  <p>int</p>
 </li>
 <li>
  <p>long</p>
 </li>
 <li>
  <p>float</p>
 </li>
 <li>
  <p>double</p>
 </li>
 <li>
  <p>boolean</p>
 </li>
 <li>
  <p>char</p>
 </li>
</ul>
<p>Autoboxing 是指在基本数据类型和对应的包装(wrapper)类之间<a href="http://www.javacodegeeks.com/2013/07/java-generics-tutorial-example-class-interface-methods-wildcards-and-much-more.html">Java 编译器所做的自动转换</a>。例如，编译器将 int 转换为 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html?is-external=true">Integer</a>，将 double 转换为 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Double.html">Double</a> ，等等。逆向的转换称为 unboxing。</p>
<p><strong>6. 在Java中什么是方法的 Override(覆盖) 和 Overload(重载)?</strong>
 <br>
 Java中方法的 overload 发生的条件是，同一个类里，有两个或以上的方法名称完全相同，但参数列表不同。另一方面，方法的 override 是指，子类重定义了父类里的同一个方法。Override 的方法必须方法名、参数列表和返回类型都完全相同。Override 的方法不会限制原方法的访问权限。</p>
<p><strong>7.Java中构造函数、构造函数重载的概念和拷贝构造函数</strong></p>
<p>当类的对象被创建的时候，调用它的构造函数。每个类都有一个构造函数。如果程序员没有为类编写构造函数，Java编译器自动为类创建一个缺省的构造函数。</p>
<p>构造函数重载和Java中函数重载类似，可以为同一个类创建不同的构造函数，每个构造函数必须拥有唯一的参数列表。</p>
<p>Java与C++不同，它不支持拷贝构造函数，但是区别仅仅是，如果你没有编写类的拷贝构造函数，Java不会自动创建它。</p>
<p><strong>8.Java支持多继承吗？</strong></p>
<p>Java不支持多继承，每个类只允许继承一个类，但是可以实现多个接口。</p>
<p><strong>9.接口和抽象类有什么不同？</strong></p>
<p>Java同时提供和支持抽象类和接口，它们的实现有一些共同的特点，也有如下不同：</p>
<ul>
 <li>
  <p>接口中所有的方法默认都是抽象的，而抽象类可以同时包含抽象和非抽象的方法。</p>
 </li>
 <li>
  <p>一个类可以实现多个接口，但它只能继承一个抽象类。</p>
 </li>
 <li>
  <p>一个类要实现某个接口，必须实现这个接口声明的所有方法。而一个类不需要实现抽象父类中声明的所有方法，不过，这时候这个类也必须声明为抽象类。</p>
 </li>
 <li>
  <p>抽象类可以实现接口，而且不需要实现接口中的方法。</p>
 </li>
 <li>
  <p>接口中声明的变量默认是final的，而抽象类可以包含非final的变量。</p>
 </li>
 <li>
  <p>接口中的成员默认是public的，而抽象类的成员可以是private，protected，或public的。</p>
 </li>
 <li>
  <p>接口是绝对抽象的，不可实例化，抽象类也不可以实例化，但可以在main方法中触发实例化（注：通过匿名类实现）。</p>
 </li>
</ul>
<p>也可以查阅 <a href="http://www.oschina.net/translate/abstract-class-versus-interface-in-the-jdk-8-era.html">Abstract class and Interface differences for JDK 8</a> 。</p>
<p><strong>10.传引用和传值</strong></p>
<p>当对象通过传值调用时，传递的是这个对象的一个拷贝。因此，即使函数修改这个对象，也不会影响原对象的值。</p>
<p>当对象通过传引用调用时，对象本身没有被传递，而传递的是对象的一个引用。因此，外部函数对这个对象的修改，也会反映到任何出现这个对象的地方。</p>
<h3>Java 线程</h3>
<p><strong>11. 进程与线程的区别 ?</strong>
 <br>
 进程是一个程序的执行(即正在运行的程序), 然而线程是在进程中独立的执行序列. 一个进程可以包含很多线程. 线程有时被称为轻量级的进程.</p>
<p><strong>12. 说下创建线程的不同方式. 你倾向于哪种方式并说明原因 ?</strong>
 <br>
 有三种创建线程的方式:</p>
<ul>
 <li>
  <p>继承Thread类.</p>
 </li>
 <li>
  <p>实现Runnable接口.</p>
 </li>
 <li>
  <p>通过Executor框架创建线程池.</p>
 </li>
</ul>
<p>首选方式是实现Runnable接口, 因为它不需要继承Thread类. 当你的程序设计需要多继承时, 使用接口会有所帮助. 另外, 线程池效率是很高的, 并且实施起来也很简单.</p>
<p><strong>13. 解释下可用的线程状态.</strong>
 <br>
 在执行期间, 线程会处于以下状态中的一种:</p>
<ul>
 <li>
  <p><strong>Runnable</strong>: 线程已准备就绪, 但没有立即运行.</p>
 </li>
 <li>
  <p><strong>Running</strong>: 处理器正在执行的线程代码.</p>
 </li>
 <li>
  <p><strong>Waiting</strong>: 处于阻塞状态的线程, 等待外部某种处理的结束.</p>
 </li>
 <li>
  <p><strong>Sleeping</strong>: 被强制休眠的线程.</p>
 </li>
 <li>
  <p><strong>Blocked on I/O</strong>: 等待I/O操作的完成.</p>
 </li>
 <li>
  <p><strong>Blocked on Synchronization</strong>: 等待取得线程锁.</p>
 </li>
 <li>
  <p><strong>Dead</strong>: 线程已经执行结束.</p>
 </li>
</ul>
<p><strong>14. 同步方法与同步块的区别 ?</strong>
 <br>
 在Java程序中, 每个对象都拥有一个锁. 线程可以通过使用synchronized关键字来获取一个对象上的锁. synchronized关键字可以用于方法级别(粗粒度锁)或代码块级别(细粒度锁).</p>
<p><strong>15. 在监视器中的线程同步是怎样发生的? 你可以使用哪些级别的同步 ?</strong></p>
<p>JVM使用结合了监视器的锁. 监视器是一个守护者, 它看管一个同步代码的序列, 并且确保在一个时刻只能有一个线程执行同步代码片段. 每个监视器关联着一个对象引用. 只能得到锁的线程才可以执行同步代码.</p>
<p><strong>16. 什么是死锁 ?</strong></p>
<p>当<a href="http://www.javacodegeeks.com/2013/01/java-deadlock-example-how-to-analyze-deadlock-situation.html">两个进程相互等待对方执行完毕</a>时, 其结果是它们会永远等待下去.</p>
<p><strong>17. 怎样确保N个线程访问N个资源时不会发生死锁 ?</strong></p>
<p>使用N个线程时一个非常简单的避免死锁的方法是为所有的锁排序, 并强制每个线程也按那种方式排序. 这样, 如果所有线程以相同的顺序锁定和解锁互斥资源就不会发生死锁了.</p>
<h3>Java Collections</h3>
<p><strong>18. Java Collections框架的基本接口 ?</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/collections/overview.html">Java Collections 框架</a> 提供了一系列支持对象集合操作的设计良好的接口和类. Java Collections框架中最基础的接口是:</p>
<ul>
 <li>
  <p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html">Collection</a>, 表示一组对象(元素).</p>
 </li>
 <li>
  <p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Set.html">Set</a>, 包含非重复元素的集合.</p>
 </li>
 <li>
  <p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/List.html">List</a>, 包含非重复元素的有序集合.</p>
 </li>
 <li>
  <p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Map.html">Map</a>, 包含非重复键的键值对的对象.</p>
 </li>
</ul>
<p><strong>19. 为什么Collection没有继承Cloneable和Serializable接口 ?</strong></p>
<p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html">Collection</a>接口描述的是由元素组成的对象组. Collection的每一个具体实现可以选择自己的方式来管理元素. 一些集合允许存在重复键, 另一些则不允许.</p>
<p>当处理实际实现时复制和序列化的语义和效果才会起作用. 因此, 集合类的具体实现应该决定它们将怎样被复制和序列化.</p>
<p><strong>20. 什么是Iterator(叠代器) ?</strong></p>
<p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html">Iterator</a>接口提供了许多能够叠代集合的方法.每个java集合(<a href="http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html">Collection</a>)都含有一个返回Iterator实例的iterator方法. 叠代器在叠代过程中能够移除底层集合中的元素.</p>
<p><strong>21. Iterator 和ListIterator之间的不同 ?</strong>
 <br>
 它们之间的不同如下:</p>
<ul>
 <li>
  <p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html">Iterator</a>能够遍历Set和List集合, 而ListIterator只能用来遍历List.</p>
 </li>
 <li>
  <p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html">Iterator</a>只能正向遍历集合, 而ListIterator可以双向遍历List.</p>
 </li>
 <li>
  <p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/ListIterator.html">ListIterator</a>实现了<a href="http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html">Iterator</a>接口并提供了额外的功能, 例如添加元素, 替换元素, 取得上一个或上一个元素索引, 等等.</p>
 </li>
</ul>
<p><strong>22. fail-fast与fail-safe的区别?</strong></p>
<p>Iterator的fail_safe特性是对底层集合的拷贝进行操作, 因此对集合的任何改变都不会有影响. java.util包下的所有集合类是fail-fast的, 但java.util.concurrent包下的集合类是fail-safe的. fail-fast叠代器会抛出<a href="http://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html">ConcurrentModificationException</a>, 而fail-safe叠代器不会抛出这种异常.</p>
<p><strong>23. Java中的 HashMap 是怎么工作的?</strong>
 <br>
 <a href="http://www.javacodegeeks.com/2014/03/how-hashmap-works-in-java.html">Java 中的 HashMap 是用来存储键值对</a>的。 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html">HashMap</a> 需要一个 hash 函数，它使用 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Object#hashCode%28%29">hashCode</a> 和 equals 方法，来进行 collection 中元素的保存和查找。调用 put 方法时，<a href="http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html">HashMap</a> 会计算键(key)的 hash 值，然后将键值对存到 collection 的适当索引下。如果键已经存在，那么相应的值会更新。<a href="http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html">HashMap</a> 的重要特征主要有它的容量，装载因子(load factor)和容量扩充(threshold resizing)。</p>
<p><strong>24. hashCode() 和 equals() 方法重要性何在 ?</strong>
 <br>
 Java中的 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html">HashMap</a> 使用 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Object#hashCode%28%29">hashCode</a> 和 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Object#equals%28java.lang.Object%29">equals</a> 方法来确定键值对的索引。根据键去查询对应的值时，同样会用到这两个方法。如果这两个方法没有正确实现，两个不同的键可能会产生相同的 hash 值，因此会被 collection 认为是相同的。并且，这两个方法在检测重复时也会用到。因此，这两个方法都要正确实现，对 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html">HashMap</a> 的正确性和效率都至关重要。</p>
<p><strong>25.HashMap与HashTable之间有哪些不同？</strong></p>
<p>HashMap、HashTable这两个类都实现了Map接口，因此有些非常相似的特征，但他们在以下特性中又有所不同：</p>
<ul>
 <li>
  <p>HashMap的key与value都允许null值的存在，而HashTable则既不允许key为null，也不允许value为null。</p>
 </li>
 <li>
  <p>HashTable是线程同步的，而HashMap则不是。因此，在单线程环境下HashMap是首选，而HashTable更适合在多线程环境下使用。</p>
 </li>
 <li>
  <p>HashMap提供了它键的set集合，因此Java程序可以通过set进行迭代。因此，HashMap是快速失败的。另一方面，HashTable提供了它键的枚举。</p>
 </li>
 <li>
  <p>HashTable类被当做遗留类（译者注：<a href="http://stackoverflow.com/questions/21086307/what-are-the-legacy-classes-in-java">Java遗留类说明</a>）</p>
 </li>
</ul>
<p><strong>26.Array与ArrayList间有什么不同？与ArrayList相比你什么时候会用Array？</strong></p>
<p>Array与ArrayList类在以下特性中有所区别：</p>
<ul>
 <li>
  <p>Arrays可以包含基础数据类型或者对象，而ArrayList只能包含对象。</p>
 </li>
 <li>
  <p>Arrays有固定长度，而ArrayList长度则是动态的。</p>
 </li>
 <li>
  <p>ArrayList类提供了更多的方法和特性，比如addAll,removeAll,iterator,等等</p>
 </li>
 <li>
  <p>对于一个基础数据类型的list，集合框架使用了自动装箱去减少编码的工作。但针对固定长度的的基础数据类型，这种方法会使得它们变得更慢。</p>
 </li>
</ul>
<p><strong>27.ArrayList与LinkedList间有什么不同？</strong></p>
<p>ArrayList、LinkedList这两个类都实现了List接口，但他们以下特性中又有所不同：</p>
<ul>
 <li>
  <p>ArrayList是基于索引的数据结构，底层由Array支持实现。它提供了以时间复杂度为O(1)的性能随机访问它的元素，另一方面，LinkedList以元素列表的方式来存储它的数据，每一个元素与它前一个和后一个元素都是相连的。对元素查询操作的时间复杂度为O(n).</p>
 </li>
 <li>
  <p>对元素的插入、添加、移除操作，与ArrayList相比，LinkedList更快，因为，当一个元素被添加到集合内部的任意位置时，LinkedList不需要重新调整数组大小或者更新索引。</p>
 </li>
 <li>
  <p>LinkedList比ArrayList消耗更多的内存，因为LinkedList中每一个节点都存储了两个引用，一个是它前一个元素，一个是它后一个元素</p>
 </li>
</ul>
<p>也可以查看我们的文章<a href="http://www.javacodegeeks.com/2013/12/arraylist-vs-linkedlist.html">ArrayList vs. LinkedList</a></p>
<p><strong>28. Comparable 和 Comparator 接口分别是什么 ? 列出它们的区别。</strong>
 <br>
 Java 提供的 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html">Comparable</a> 接口，其中只包含一个方法，就是 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable#compareTo%28T%29">compareTo</a>。这个方法会比较两个对象，从而得出它们的顺序关系。具体来说，它会返回一个负整数，零，或一个正整数，分别表示传入的对象小于，等于或大于已有的对象。</p>
<p>Java 提供的 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html">Comparator</a> 接口，包含两个方法，<a href="http://docs.oracle.com/javase/7/docs/api/java/util/Comparator#compare%28T,%20T%29">compare</a> 和 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/Comparator#equals%28java.lang.Object%29">equals</a> 。compare 方法比较两个参数，得出它们的顺序关系。它会返回一个负整数，零，或一个正整数，分别表示第一个参数小于，等于或大于已有的对象。equals 方法有1个参数，它用来确定参数对象是否等于这个 camparator。这个方法仅在要比较的对象也是一个 comparator，同时它的序关系与这个 comparator 相同时，才会返回 true。</p>
<p><strong>29.Java Priority Queue是什么?</strong>
 <br>
 PriorityQueue是一个基于优先级堆的无界队列，它的元素都以他们的自然顺序有序排列。在它创建的时候，我们可以可以提供一个比较器(Comparator)来负责PriorityQueue中元素的排序。PriorityQueue不允许null元素，不允许不提供自然排序的对象，也不允许没有任何关联Comparator的对象。最后，PriorityQueue不是线程安全的，在执行入队和出队操作它需要O(log(n))的时间复杂度。</p>
<p><strong>30. 关于Big-O符号你了解些什么？你能针对不同数据结构举些例子吗？</strong>
 <br>
 Big-O符号简单描述了，在一种数据结构中随着元素的不断增加，在最坏的情况下，一个算法的扩展或者执行能有多好。Big-O符号还可以用来描述其他的行为，比如内存消耗。由于collection集合下的类实际上都是数据结构，我们通常用Big-O符号来选择基于时间、内存、性能前提下的最好实现去使用。Big-O符号能就大量数据的性能给出一个很好的指示。</p>
<p><strong>31. 如何权衡有序数组和无序数组 ?</strong>
 <br>
 有序数组最关键的优势在于搜索的时间复杂度为 O(log n)，而无序数组的时间复杂度是 O (n)。有序数组的劣势就在于插入操作的时间复杂度为 O(n)，因为较高值的元素需要挪动位置腾出空间给新元素。与之不同的是，无序数组的插入操作的时间复杂度为 O(1)。</p>
<p><strong>32. 有哪些关于 Java 集合框架的最佳实践？</strong></p>
<ul>
 <li>
  <p>基于应用的需求来选择使用正确类型的集合，这对性能来说是非常重要的。例如，如果元素的大小是固定的，并且知道优先级，我们将会使用一个 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Array.html">Array</a>，而不是 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html">ArrayList</a>。</p>
 </li>
 <li>
  <p>一些集合类允许我们指定他们的初始容量。因此，如果我们知道存储数据的大概数值，就可以避免重散列或者大小的调整。</p>
 </li>
 <li>
  <p>总是使用泛型来保证类型安全，可靠性和健壮性。同时，使用泛型还可以避免运行时的 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/ClassCastException.html">ClassCastException</a>。</p>
 </li>
 <li>
  <p>在 Map 中使用 Java Development Kit (JDK) 提供的不可变类作为一个 key，这样可以避免 <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Object#hashCode%28%29">hashCode</a> 的实现和我们自定义类的 equals 方法。</p>
 </li>
 <li>
  <p>应该依照接口而不是实现来编程。</p>
 </li>
 <li>
  <p>返回零长度的集合或者数组，而不是返回一个 null ，这样可以防止底层集合是空的。</p>
 </li>
</ul>
<p><strong>33. Enumeration 和 Iterator 接口有什么不同 ?</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/Enumeration.html">Enumeration</a> 跟 Iterator 相比较快两倍，而且占用更少的内存。但是，<a href="http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html">Iterator</a> 相对于 Enumeration 更安全，因为其他线程不能修改当前迭代器遍历的集合对象。同时，Iterators 允许调用者从底层集合中移除元素，这些 Enumerations 都没法完成。</p>
<p><strong>34. HashSet 和TreeSet 有什么不同?</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html">HashSet</a> 是用一个 hash 表来实现的，因此，它的元素是无序的。添加，删除和 HashSet 包括的方法的持续时间复杂度是 O(1)。</p>
<p>另一个方面，TreeSet 是用一个树形结构实现的，因此，它是有序的。添加，删除和 TreeSet 包含的方法的持续时间复杂度是 O(logn)。</p>
<h3>垃圾回收器</h3>
<p><strong>35. Java中垃圾回收的目的是什么, 它什么时候被使用 ?</strong></p>
<p>垃圾回收用于识别和丢弃程序不再需要的对象, 以便回收和复用资源.</p>
<p><strong>36. System.gc() 和Runtime.gc()方法用途?</strong></p>
<p>这些方法用于提醒JVM开始垃圾回收. 然而开始垃圾回收的时机是由JVM决定的.</p>
<p><strong>37. finalize()什么时候被调用 ? 它的目的是什么 ?</strong></p>
<p>finallize方法是在释放该对象内存前由gc(垃圾回收器)调用. 通常建议在这个方法中释放该对象持有的资源.</p>
<p><strong>38.如果一个对象的引用被设置为null, gc会立即释放该对象的内存么?</strong></p>
<p>不会, 这个对象将会在下一次gc循环中被回收.</p>
<p><strong>39. Java堆的结构是什么 ? 堆中的Perm Gen(全称是Permanent Generation)空间是什么 ?</strong></p>
<p>JVM有一个运行时数据区,即堆(<a href="http://www.javacodegeeks.com/2012/07/5-tips-for-proper-java-heap-size.html">heap</a>).所有的类实例和数组的内存都是从堆中分配的. 它在JVM启动时被创建. 对象所占用的堆内存会被一个称为垃圾回收器的自动内存管理系统收回.</p>
<p>堆内存中包含活的和死的对象. 活的对象可以被程序访问并且不会被垃圾回收. 死的对象是那些不会被程序访问的, 但还没有被垃圾回收器收回的对象. 这种对象会占用堆内存空间直到最终被垃圾回收器收回.</p>
<p><strong>40. Serial 垃圾回收器与 Throughput 垃圾回收器区别 ?</strong></p>
<p>Throughput垃圾回收器使用并行版本的新生代回收器, 它用于中到大型数据集的应用. 另一方面, Serial回收器通常足以应对大多数的小应用(在现代处理器上不会超过约100MB的堆内存).</p>
<p><strong>41. 什么时候对象会被回收 ?</strong>
 <br>
 当当前程序无法访问到某个对象时，该对象将被回收.</p>
<p><strong>42. 垃圾回收发生在指定的JVM区域 ?</strong>
 <br>
 垃圾回收在 PermGen 里发生。如果 PermGen满了或是到了瓶颈，就会触发完全回收。如果仔细 观察垃圾回收过程，会发现PermGen最后也被回收了. 这就是为什么要设置合理的PermGen大小以避免完全垃圾回收. 可以看看这篇文章 <a href="http://www.javacodegeeks.com/2013/02/java-8-from-permgen-to-metaspace.html">Java 8: PermGen to Metaspace</a>.</p>
<h3>异常处理</h3>
<p><strong>43. Java中的两种异常是什么?它们之间的区别?</strong>
 <br>
 Java有两种类型的异常:checked与unchecked(检查与未检查) 异常. 如果unchecked异常可能会在方法或构造函数的执行时被抛出从而蔓延到方法或构造函数的外部, 它们也不需要要在方法或构造函数中声明throws子句. 然而, checked异常必须通过方法或构造函数的throws子句声明. 关于java异常处理的建议请参考这里<a href="http://www.javacodegeeks.com/2013/07/java-exception-handling-tutorial-with-examples-and-best-practices.html">Java exception handling</a>.</p>
<p><strong>44. Java中异常与错误的区别?</strong></p>
<p><a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Exception.html">Exception</a>和<a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Error.html">Error</a>都是<a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html">Throwable</a>类的子类. <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Exception.html">Exception</a>用于用户程序需要捕获的异常条件. <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Error.html">Error</a>定义了用户程序不可预见的异常</p>
<p><strong>45. throw与throws的区别 ?</strong>
 <br>
 关键字throw用于在程序中显式地抛出一个异常. 相反, throws子句用于指出在该方法中没有处理的异常. 每个方法必须显式指明哪些异常没有处理, 以便该方法的调用者可以预防可能发生的异常. 最后, 多个异常用逗号分隔.</p>
<p><strong>45. 异常处理中finally语句块的重要性?</strong></p>
<p>不管程序是否发生了异常, finally语句块都会被执行. 甚至当没有catch声明但抛出了一个异常时, finally语句块也会被执行. 最后要说一点: finally语句块通常用于释放资源, 如I/O缓冲区, 数据库连接等等.</p>
<p><strong>46. 异常被处理后异常对象会发生什么?</strong>
 <br>
 异常对象会在下次gc执行时被回收.</p>
<p><strong>47. 怎样区分finally语句块与finalize()方法?</strong>
 <br>
 不管是否抛出异常finally语句块都会被执行, 它通常用于释放程序持有的资源. finalize是Object类中的一个protected方法, 当一个对象被gc回收前它会被jvm调用.</p>
<h3>Java Applets</h3>
<p><strong>48. 什么是 Applet ?</strong>
 <br>
 一个 Java Applet 可以包含在 HTML 页面中并且可以在启用 Java 客户端的浏览器中运行。Applets 可以用来创建动态和交互式的 web 应用程序。</p>
<p><strong>49. Applet 生命周期的说明</strong>
 <br>
 一个 Applet 可能会经历以下的几个状态：</p>
<ul>
 <li>
  <p><strong>Init</strong>: 每次加载的时候都进行初始化</p>
 </li>
 <li>
  <p><strong>Start</strong>: 开始执行一个 applet</p>
 </li>
 <li>
  <p><strong>Stop</strong>: 停止执行一个 applet</p>
 </li>
 <li>
  <p><strong>Destroy</strong>: 卸载 applet 之前执行最后的清理</p>
 </li>
</ul>
<p><strong>50. 当 applet 加载的时候会发生什么？</strong>
 <br>
 首先，会创建一个 applet 的控制类的实例。然后，applet 初始化，最后 applet 开始运行。</p>
<p><strong>51. Applet和Java应用程序有什么不同?</strong>
 <br>
 Applets需要一个支持Java的浏览器, 但是Java应用程序可以被单独执行。但是，他们都需要一个Java虚拟机，JVM。</p>
<p>还有, 一个Java应用程序需要一个main方法与特定的签名，来确保启动. Java applets并不需要这样一些东西。</p>
<p>最后, Java Applet通常使用严格的安全策略，而Java应用程序通常使用较宽松的安全策略。</p>
<p><strong>52.</strong> 什么是强加给Java Applet限制？
 <br>
 这主要是由于安全原因，以下限制强加在Java小程序：</p>
<ul>
 <li>
  <p>Applet无法加载库或定义本地方法。</p>
 </li>
 <li>
  <p>Applet 通常无法读取或执行主机的文件。</p>
 </li>
 <li>
  <p>Applet无法读取某些系统属性。</p>
 </li>
 <li>
  <p>除非连接来自主机否则不能进行网络连接。</p>
 </li>
 <li>
  <p>Applet程序不能启动主机上执行它的任何程序。</p>
 </li>
</ul>
<p><strong>53. 什么是不可信的Applet ?</strong>
 <br>
 不受信任的Applet是那些Java Applet不能访问或执行本地系统的文件的程序。默认情况下，所有下载的Applet被认为是不可信的。</p>
<p><strong>54. 通过文件系统加载的小程序在加载了互联网和Applet之间的区别是什么 ?</strong>
 <br>
 关于其中一个applet被加载在互联网的情况下，applet是由applet类加载器加载并须受该applet安全管理器执行的限制。</p>
<p>关于其中一个applet是从客户端的本地磁盘加载的情况下，applet是由文件系统加载器加载的。</p>
<p>通过文件系统加载的applet允许读取文件，写入文件并加载在客户端上。还有，通过文件系统加载applet允许执行，最后，通过文件系统加载的applet不管是否通过字节码验证都可加载。</p>
<p><strong>55. 什么是applet类加载器，它提供了什么？</strong>
 <br>
 当在互联网上加载applet，该applet是由applet的类加载器加载的。类加载器强制执行Java命名空间的层次结构。此外，类加载器保证这是在来自本地文件系统的类中唯一的命名空间，以及在每个网络源中唯一的命名空间。</p>
<p>当浏览器在网络上加载applet，applet的类被放置在私人的和applet起始地址有关的命名空间中。那么，那些类加载器加载的类就会通过校验。这个校验会检查类文件是否符合Java语言规范。除此之外，校验器会确保没有堆栈溢出或者向下溢出，参数的所有字节码指令也是正确的。</p>
<p><strong>56. 什么是applet安全管理，它提供什么？</strong>
 <br>
 applet安全管理是对Java applet做了限制的机制。浏览器只能有一个安全管理器。安全管理器在启动的时候建立，之后它不能被取代，重载，重写或者延长。</p>
<h3><a href="http://examples.javacodegeeks.com/desktop-java/swing/">Swing</a></h3>
<p><strong>57. Choice和List之间的区别是什么？</strong>
 <br>
 Choice是一种紧凑的方式展示，必须要拉下，是为了让用户能够看到所有的可选选项列表。Choice只能选中一个选项。<a href="http://examples.javacodegeeks.com/desktop-java/swing/jlist/create-jlist-example/">List</a>是以几个List选项是可见的方式展示的。List支持选中一个或多个List选项。</p>
<p><strong>58. 什么是布局管理器？</strong>
 <br>
 布局管理器是用来组织容器内的组件。</p>
<p><strong>59. Scrollbar和JScrollPane 的区别是什么？</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Scrollbar.html">Scrollbar</a>是一个<a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Component.html">组件</a>， 但不是一个<a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Container.html">容器</a>，而<a href="http://docs.oracle.com/javase/7/docs/api/javax/swing/JScrollPane.html">ScrollPane</a>是一个<a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Container.html">容器</a>。 <a href="http://docs.oracle.com/javase/7/docs/api/javax/swing/JScrollPane.html">ScrollPane</a>处理它自身的事件并执行它自己的滑动。</p>
<p><strong>60. 哪些Swing方法是线程安全？</strong>
 <br>
 只有3个方法是线程安全的：repaint, revalidate, 和invalidate。</p>
<p><strong>61. 说出3个支持绘图的子类。</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Canvas.html">Canvas</a>,<a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Frame.html">Frame</a>,<a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Panel.html">Panel</a>, 和Applet类都支持绘图。</p>
<p><strong>62. 什么是裁剪？</strong>
 <br>
 裁剪是指在有限的区域和图形类进行绘图操作。</p>
<p><strong>63. MenuItem和CheckboxMenuItem的区别是什么？</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/awt/CheckboxMenuItem.html">CheckboxMenuItem</a>类继承了<a href="http://docs.oracle.com/javase/7/docs/api/java/awt/MenuItem.html">MenuItem</a>类，并支持选中或者取消菜单选项。</p>
<p><strong>64. BorderLayout的元素是怎样组织的？</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/awt/BorderLayout.html">BorderLayout</a>的元素都是在有序地分布在边缘部分(东，南，西，北)和容器的中心。</p>
<p><strong>65. GridBagLayout的元素是怎样组织的？</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/awt/GridBagLayout.html">GridBagLayout</a>的元素根据网格组织的。元素具有不同的尺寸，并且可以占据一行或列的网格。因此，行和列可以有不同的尺寸。</p>
<p><strong>66. Window和Frame有什么区别？</strong>
 <br>
 <a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Frame.html">Frame</a>类是继承<a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Window.html">Window</a>类，并定义了一些主要的带菜单栏的应用程序窗口。</p>
<p><strong>67. 裁剪和重画之间的关系？</strong>
 <br>
 当窗口被AWT绘图线程重画，它设置了裁剪区域到窗口中请求重画的区域。</p>
<p><strong>68. 事件监听器接口和事件适配器类之间是什么关系？</strong>
 <br>
 事件监听器接口定义了一个特定事件的事件处理程序所必须实现的一些方法。事件适配器提供了一个事件侦听器接口的默认实现。</p>
<p><strong>69. 一个GUI组件怎么处理自己的事件？</strong>
 <br>
 GUI组件可以通过实现相应的事件监听器接口和添加它自己作为本身的事件侦听器来处理自己的事件。</p>
<p><strong>70. Java布局管理器提供超过传统的窗口系统的什么样的优势？</strong>
 <br>
 Java使用布局管理器以一致的方式来布局组件，跨所有窗口平台。由于布局管理器不绑定绝对化的尺寸和位置，所以它们能够容纳不同窗口系统的平台具体差异。</p>
<p><strong>71. Java为所有Swing组件使用的设计模式是什么？</strong>
 <br>
 Java为所有Swing组件使用的设计模式是是 模型视图控制器（MVC）模式。</p>
<h3>JDBC</h3>
<p><strong>72. 什么是 JDBC ？</strong>
 <br>
 JDBC是一个抽象层，允许用户在不同数据库间进行选择。<a href="http://www.javacodegeeks.com/2014/03/java-8-friday-java-8-will-revolutionize-database-access.html">JDBC使开发人员能够在Java中编写数据库应用程序</a>，而不必让自己关心一个特定的数据库的底层细节。</p>
<p><strong>73. 解释JDBC中驱动的作用。</strong>
 <br>
 JDBC驱动提供了 对 JDBC API 所提供的抽象类 的 数据库供应商的特定实现。每个驱动必须提供java.sql包的以下的类实现：<a href="http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html">Connection</a>,<a href="http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html">Statement</a>,<a href="http://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html">PreparedStatement</a>,<a href="http://docs.oracle.com/javase/7/docs/api/java/sql/CallableStatement.html">CallableStatement</a>,<a href="http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html">ResultSet</a> 和 <a href="http://docs.oracle.com/javase/7/docs/api/java/sql/Driver.html">Driver</a>。</p>
<p><strong>74.Class.forName 方法的目的是什么?</strong>
 <br>
 此方法用于加载驱动程序，以建立与数据库的连接。</p>
<p><strong>75.与Statement相比PreparedStatement的优点?</strong></p>
<p>PreparedStatement是预编译的，因此它有更好的性能。另外，PreparedStatement可以被不同输入值的查询重用。</p>
<p><strong>76. CallableStatement的用途 ? 指出用于创建CallableStatement的方法.</strong></p>
<p><a href="http://docs.oracle.com/javase/7/docs/api/java/sql/CallableStatement.html">CallableStatement</a>用于执行存储过程。存储过程由数据库保存并提供。存储过程可以根据用户的输入返回结果。强烈建议使用存储过程，因为它提供了安全性和模块化。准备CallableStatement的方法如下：</p>
<p>CallableStament.prepareCall();</p>
<p><strong>77. 连接池是什么 ?</strong></p>
<p>打开和关闭数据库连接时与数据库的交互需要付出很高的代价. 特别是当数据库客户端增长时，这个代价是相当高的，并且消耗了很多资源。数据库连接池中的连接在应用服务器启动时被创建并在池中进行管理。一个连接请求由池中的数据库连接提供。当连接结束后，请求会被放回池中以供以后重用。</p>
<h3>远程方法调用 (RMI)</h3>
<p><strong>78. 什么是RMI ?</strong>
 <br>
 Java远程方法调用（RMI)是一个Java API，它执行的面向对象的等价远程过程调用（RPC）的方法，包括了直接传输序列化的Java类和分布式垃圾收集的支持。 远程方法调用（RMI），也可以看作是一个远程运行的对象上激活的方法的过程。RMI提供位置透明性，因为用户认为一个方法是在本地运行的对象上执行。 <a href="http://www.javacodegeeks.com/2013/11/two-things-to-remember-when-using-java-rmi.html">RMI Tips here</a>.</p>
<p><strong>79. 什么是RMI的体系结构的基本原理？</strong>
 <br>
 RMI的架构最重要的原则是将行为的定义和行为的实施分别对待。 RMI允许定义的行为和实现行为保持独立，并在独立的JVM中运行的代码。</p>
<p><strong>80. RMI的体系结构层是什么？</strong>
 <br>
 RMI的结构主要分为以下几层：</p>
<ul>
 <li>
  <p><strong>桩(Stub)和框架(Skeleton)层：</strong>该层位于开发者视图的下面。该层是负责拦截客户端请求接口的方法并重定向这些请求到远程RMI服务上。</p>
 </li>
 <li>
  <p><strong>远程引用层：</strong>架构的第二层是处理从客户端到服务器的远程对象引用的解析。该层解析并管理从客户端到远程服务对象的引用。该连接是一对一(单播)连接的。</p>
 </li>
 <li>
  <p><strong>传输层：</strong>该层主要负责连接参与服务的两个JVM。它基于通过网络连接的机子的TCP/IP，提供了基本的连通性，以及一些防火墙的渗透策略。</p>
 </li>
</ul>
<p><strong>81. 在RMI中远程接口的作用是什么？</strong>
 <br>
 远程接口用于识别那些不是来自本地机子接口但可以被调用的方法。所有对象都是必须直接或间接实现该接口的远程对象。实现该远程接口之前应该声明其远程接口，为每个远程对象定义构造方法，并在所有远程接口中为每个远程方法提供实现。</p>
<p><strong>82. java.rmi.Naming 类扮演的角色 ?</strong></p>
<p>java.rmi.Naming类提供了存储和获取已注册的远程对象. Naming类中的每个方法都需要一个URL格式的String作为参数的名称.</p>
<p><strong>83. RMI中的绑定是什么意思 ?</strong></p>
<p>绑定是关联或注册一个远程对象的名字的过程, 这个名字可以在以后用到, 用于查找与它绑定的远程对象. 远程对象可以通过Naming类中的bind或rebind方法与一个名字相关联.</p>
<p><strong>84. Naming 类中的bind与rebind方法的区别 ?</strong></p>
<p>bind方法的绑定主要用于将特定的名字绑定到一个远程对象, 但rebind方法的绑定用于将特定的名字重新绑定到一个新的远程对象. 如果这个名字已经绑定过了, 使用rebind这个绑定会被替换.</p>
<p><strong>85. 运行RMI 程序的步骤?</strong></p>
<p>为了使RMI程序正常运行需要以下步骤:</p>
<ul>
 <li>
  <p>编译所有源文件.</p>
 </li>
 <li>
  <p>用rmic生成stub.</p>
 </li>
 <li>
  <p>启动rmiregistry.</p>
 </li>
 <li>
  <p>启动RMIServer.</p>
 </li>
 <li>
  <p>运行客户端程序.</p>
 </li>
</ul>
<p><strong>86.RMI中stub的角色 ?</strong></p>
<p>远程对象的stub作为远程对象在本地程序中的表示或代理. 调用者调用本地stub的一个方法, 这个方法会在远程对象上执行.当一个stub的方法被调用时, 它经历了以下步骤:</p>
<ul>
 <li>
  <p>初始化与运行远程对象的远程JVM的连接.</p>
 </li>
 <li>
  <p>将参数编码并传递给远程JVM.</p>
 </li>
 <li>
  <p>等待方法调用与执行的结果.</p>
 </li>
 <li>
  <p>解码返回值或异常(如果执行失败).</p>
 </li>
 <li>
  <p>将返回值返回给调用者.</p>
 </li>
</ul>
<p><strong>87. 什么是DGC？它是如何工作的？</strong>
 <br>
 DGC代表的是分布式垃圾收集。远程方法调用(RMI)使用的是DGC自动垃圾收集机制。由于RMI涉及到跨JVM的远程引用，垃圾回收就会相当困难。DGC使用相关的计数算法为远程对象提供自动存储管理。</p>
<p><strong>88. 在RMI中使用RMISecurityManager的目的是什么？</strong>
 <br>
 在RMI应用程序中可以使用RMISecurityManager提供安全管理器来下载代码。如果安全管理器没有设置好，RMI的类加载器不会从远程端下载任何类。</p>
<p><strong>89. 解释编组和解组。</strong>
 <br>
 当一个应用程序要通过网络来传送内存对象到另一台主机，或者保留它到存储器，内存表达法会将其转换到合适的格式。这个过程就叫做编组，而恢复操作就叫解组。</p>
<p><strong>90. 解释序列化和反序列化。</strong>
 <br>
 Java提供了一个机制，是指一个对象可以被表示为字节序列，包括对象的数据，以及对象类型的信息和存储在对象中的数据类型。因此，序列化可以看做是平面化对象为了存储到磁盘中，方便后面读取和重新配置的一种方式。反序列化是一种从平面化状态到活跃状态的一种转换对象的逆过程。</p>
<h3>Servlets</h3>
<p><strong>91. 什么是Servlet?</strong>
 <br>
 <a href="http://examples.javacodegeeks.com/enterprise-java/servlet/sample-java-servlet/">servlet</a>是用来处理客户端请求并生成动态web内容的Java程序语言类。Servlets大多是用来处理或者存储HTML表单提交的数据，提供动态内容和管理那些不在HTTP无状态协议中的状态信息。</p>
<p><strong>92. 解释一个Servlet的架构.</strong>
 <br>
 核心抽象概念肯定是所有servlet必须实现 javax.servlet.Servlet 接口。 每个 servlet 必须直接或者间接实现这个接口， 也可以继承于 javax.servlet.GenericServlet 或者javax.servlet.http.HTTPServlet。最后想提的是,每个 servlet 能够使用多线程服务多个请求。</p>
<p><strong>93. 一个 Applet 和 一个 Servlet 区别是什么 ?</strong>
 <br>
 一个 Applet 是一个跑在客户机器的 网页浏览器 里面的 客户端java程序。相反，一个 servlet 是跑在网页服务器的服务的容器。一个 applet 能使用用户界面class， 而一个 servlet 不能够有一个用户界面。相反，一个servlet 等待客户端的 HTTP 请求并为每一个请求生成一个响应。</p>
<p><strong>94. GenericServlet 和 HttpServlet 的区别是什么 ?</strong>
 <br>
 GenericServlet 是一个实现了Servlet 和 ServletConfig 接口的通用的协议无关的 servlet . 那些继承于GenericServlet 类的 servlet 将重写 service 方法 。最后想提的是，为了给Web用户开发一个使用HTTP协议服务的HTTP servlet， 你的 servlet 必须改为继承于 HttpServlet 。<a href="http://examples.javacodegeeks.com/tag/servlet/">查看Servlet的示例</a> 。</p>
<p><strong>95.解释一个Servlet的生命周期.</strong></p>
<p>对每一个客户端的请求，这个Servlet引擎加载servlet和调用它的init方法，以便在servlet初始化。然后， Servlet对象处理所有从客户端来的后续请求，通过为每个请求单独调用服务的方法。最后，该servlet调用服务器的destroy方法。</p>
<p><strong>96 .doGet()和doPost()之间的区别是什么？</strong></p>
<p>doGet ： GET方法附加请求的URL的名称 - 值对。因此，存在客户端的请求字符数量的限制。此外，该请求的参数值为可见，因此，如果有敏感信息不能采用这种方式。
 <br>
 doPost： POST方法克服了GET请求的限制，将发送请求的值置于body里。此外，发送值的数量没有限制。最后，通过POST请求传递的敏感信息是不可见的</p>
<p><strong>97. web应用是什么 ?</strong>
 <br>
 web应用是web服务的延伸. 主要有两种类型：面向视觉的和面向服务的. 面向视觉型的应用通过编辑语言来动态展示交互页面. 面向应用的则提供了后端的service.总的来说，就是一堆置于 server's URL明明空间下的servlets.</p>
<p><strong>98. 什么是服务端包含 (SSI) ?</strong>
 <br>
 服务端包含是服务端的一种简单脚本语言,主要应用在Web方面，置于servlet标签中. 最常用的地方就是在网页中引入一个或多个文件. 当浏览器加载页面时，使用servlet产生的超文本替换其标签.</p>
<p><strong>99. 什么是 Servlet 链?</strong>
 <br>
 Servlet 链是指将上一个servlet的结果传到下一个.第二个的结构又可以传到第三个. 最后的servlet负责将响应回复给客户端.</p>
<p><strong>100. 如何知道请求 servlet的客户端信息 ?</strong>
 <br>
 ServletRequest类可以获取客户端的IP地址或主机名. getRemoteAddr()获取IP getRemoteHost()获取主机名. 示例如下 <a href="http://examples.javacodegeeks.com/enterprise-java/servlet/get-client-s-address-and-hostname-in-servlet/">here</a>.</p>
<p><strong>101. Http response的结构是什么 ?</strong>
 <br>
 HTTP response 包括了三个部分:</p>
<ul>
 <li>
  <p><strong>Status Code</strong>: 描述了这次回应的状况. 它可以用来检查这次请求是否成功完成. 一旦请求失败了, 这个status code可以用来寻找原因. 如果你的 servlet 没有返回一个status code, 默认就会返回成功的status code, HttpServletResponse.SC_OK.</p>
 </li>
 <li>
  <p><strong>HTTP Headers</strong> : 它包含了response的更多信息.举个例子，headers可以反应response的访问date/time, 或者是用于将实体安全地传送到用户的编码形式。可以阅读 <a href="http://examples.javacodegeeks.com/enterprise-java/servlet/get-all-request-headers-in-servlet/">how to retrieve headers in Servlet here</a>.</p>
 </li>
 <li>
  <p><strong>Body</strong>: 它是response的具体内容. 可能包括HTML内容,比如图片。Body包括了紧接Header发送的HTTP事务消息数据字节。</p>
 </li>
</ul>
<p><strong>102. 什么是cookie？session和cookie之间的区别是什么？</strong>
 <br>
 Cookie是Web服务器发送到浏览器的一小块信息，浏览器为每个Web服务器在本地文件中存储cookie。 在以后的请求里, 浏览器对特定的Web服务器，将request和所有特定的Web服务器的cookie一起发送。Session和Cookie之间的区别如下：</p>
<ul>
 <li>
  <p>Session无论在客户端浏览器的设置都可以工作。客户端可以选择禁用cookies。然而，Session仍然可以工作，因为客户端没有能力在服务器端禁用Session。</p>
 </li>
 <li>
  <p>Session和cookie也t有不同的信息存储量。 HTTP会话能够存储任何Java对象，而一个cookie只能保存String对象。</p>
 </li>
</ul>
<p><strong>103.浏览器和servlet通过什么协议通信 ?</strong>
 <br>
 HTTP协议.</p>
<p><strong>104. 什么是HTTP通道 ?</strong>
 <br>
 通道是指使用 HTTP或 HTTPS 封装其它的网络协议. HTTP包装了其他的网络通信协议. 其它协议通过HTTP来发送请求的过程就使用了通道.</p>
<p><strong>105. sendRedirect和 forward 方法的区别?</strong>
 <br>
 sendRedirect创建一个新的请求, forward只是将请求转发.之前请求中包含的对象在redirect后将不可用, 因为产生了一个新的请求. 但是 forwarding不受此限制.通常来说,sendRedirect 比 forward 方法慢一些.</p>
<p><strong>106. 什么是URL编码和解码 ?</strong></p>
<p>URL编码就是替换其中的空格和特殊字符, 变成相应的Hex码.解码就是反向操作。</p>
<h3>JSP</h3>
<p><strong>107. JSP页面是什么 ?</strong>
 <br>
 一个JSP页面是一个文本文档，包含了两种类型的文本：静态数据和JSP元素。静态数据可以以任何一种基于文本的格式表达，比如HTML或者XML。JSP是一种混合了静态内容与动态生成内容的技术。查看<a href="http://examples.javacodegeeks.com/enterprise-java/jsp/sample-jsp-java-server-page/">JSP例子</a></p>
<p><strong>108. JSP请求是如何被处理的？</strong>
 <br>
 一个JSP请求的到来,浏览器首先以一个jsp扩展的文件名来请求一个页面。然后，Web Server读取到该请求，使用JSP编译器将JSP页面转换为一个servlet类。注意JSP文件只在该页面的第一次请求或者JSP文件已经改变时才编译。生成的servlet类被调用，去处理浏览器的请求。一旦请求处理完，servlet会向客户端返回一个相应。查看<a href="http://examples.javacodegeeks.com/enterprise-java/jsp/get-request-parameter-in-jsp-page/">如何获取JSP请求中的参数</a></p>
<h4>109. JSP的优势所在？</h4>
<p>使用JSP技术有以下优势：</p>
<ul>
 <li>
  <p>JSP网页被动态的编译，因此开发者很容易更新当前代码。</p>
 </li>
 <li>
  <p>JSP网页可预编译。</p>
 </li>
 <li>
  <p>JSP网页能容易的组合成静态模版，包括HTML XML片段，且代码可形成动态内容。</p>
 </li>
 <li>
  <p>开发者能提供自定义的JSP标签库，且使用类XML语法访问。</p>
 </li>
 <li>
  <p>开发者可以在组件级别做逻辑上的改变，但是不能使用程序逻辑编辑个别的网页。</p>
 </li>
</ul>
<h4>110. 什么是指令？在JSP中， 包括哪些不同类型的指令？</h4>
<p>指令是JSP引擎所处理的命令，当网页被编译成小程序时，指令用于去设置页面级别的命令，从外部文件插入数据，指定自定义的标签库。指令被包括在&lt;%@ 和%&gt;内。指令的类型分为：</p>
<ul>
 <li>
  <p>包含指令：用于包含文件，和当用页面合并文件内容。</p>
 </li>
 <li>
  <p>页面指令：用于定义JSP页面的具体属性，比如错误页面和缓冲池。</p>
 </li>
 <li>
  <p>标签库：用于声明页面中使用到的自定义标签库。</p>
 </li>
</ul>
<p><strong>111. 什么是JSP的actions？</strong>
 <br>
 JSP的actions是使用XML语法结构来控制Servlet引擎的行为。JSP的actions是在当JSP页面请求的时候才执行。它们会动态插入一个文件中，再利用JavaBeans的组件，转发给用户到另一个页面，或者生成带Java插件的HTML页面。以下是一些可以操作的actions：</p>
<ul>
 <li>
  <p>jsp:include- 当JSP页面被请求的时候，包含了一个文件。</p>
 </li>
 <li>
  <p>jsp:useBean- 寻找或者实例化一个JavaBean。</p>
 </li>
 <li>
  <p>jsp:setProperty- 设置JavaBean的属性。</p>
 </li>
 <li>
  <p>jsp:getProperty- 得到JavaBean的属性。</p>
 </li>
 <li>
  <p>jsp:forward- 转发请求到新的页面。</p>
 </li>
 <li>
  <p>jsp:plugin- 生成特定浏览器的代码。</p>
 </li>
</ul>
<p><strong>112. 什么是Scriptlet？</strong>
 <br>
 在Java Server Page(JSP)技术中，scriptlet是嵌入在JSP页面的其中一块Java代码。scriptlet是标签内的任何东西。在这些标签之间，用户可以添加任何有效的scriptlet。</p>
<p><strong>113. 什么是声明？</strong>
 <br>
 声明类似于Java中的变量声明。声明是为表达式或者scriptlets后续的使用而声明的变量。添加一个声明，你必须在你的声明中使用序列。</p>
<p><strong>114. 什么是表达式？</strong>
 <br>
 JSP表达式是把一个脚本语言表达式的值插入进来，转换成字符串，进入数据流之后再通过web服务器返回给客户端。表达式的定义是在&lt;% = 和 %&gt;标签之间。</p>
<p><strong>115. 什么是隐式对象？他们是什么？</strong>
 <br>
 JSP隐式对象是指JSP容器在每页中提供给开发人员的Java对象。开发人员可以直接调用他们，而不需要任何显示声明。JSP隐式对象也被称为预定义变量。以下对象都是在JSP页面中隐式存在的：</p>
<ul>
 <li>
  <p>application</p>
 </li>
 <li>
  <p>page</p>
 </li>
 <li>
  <p>request</p>
 </li>
 <li>
  <p>response</p>
 </li>
 <li>
  <p>session</p>
 </li>
 <li>
  <p>exception</p>
 </li>
 <li>
  <p>out</p>
 </li>
 <li>
  <p>config</p>
 </li>
 <li>
  <p>pageContext</p>
 </li>
</ul>
<p>
 <br>
</p>
<p>源文地址：<a href="https://www.oschina.net/translate/java-interview-questions-and-answers">https://www.oschina.net/translate/java-interview-questions-and-answers</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102954250</p>]]></description><guid isPermaLink="false">/archives/94dc10ca-caab-4a2d-a136-9d89d517bc7c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Thu, 26 Sep 2024 00:40:00 GMT</pubDate></item><item><title><![CDATA[Java 获取redis配置文件redis.properties的信息]]></title><link>https://wiki.dgfygk.com/archives/d05340a6-e6ed-459d-91dc-d0b1c46bb146</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20%E8%8E%B7%E5%8F%96redis%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6redis.properties%E7%9A%84%E4%BF%A1%E6%81%AF&amp;url=/archives/d05340a6-e6ed-459d-91dc-d0b1c46bb146" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <h4>所需要的jar 包</h4>
 <pre><code class="java">&lt;dependency&gt;
    &lt;groupId&gt;redis.clients&lt;/groupId&gt;
    &lt;artifactId&gt;jedis&lt;/artifactId&gt;
    &lt;version&gt;2.9.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
 <h4>redis.properties文件配置信息</h4>
 <pre><code class="java">redis.host=127.0.0.1
redis.port=6379
redis.pass=
#maxIdle
redis.maxIdle=10
#minIdle
redis.minIdle=5
#maxTotal
redis.maxTotal=10
#timeout
redis.timeout=5000
redis.testOnBorrow=false
redis.testOnReturn=false
redis.database=0
</code></pre>
 <h4>获取redis连接的地址，端口等信息工具类</h4>
 <pre><code class="java">public class JedisPoolUtils {

    public Map&lt;String, Object&gt; getRedisResource() {
        Map&lt;String, Object&gt; map = new HashMap&lt;&gt;();
        try {
            // 创建一个redis的连接池
            Properties prop = new Properties();
            prop.load(JedisPoolUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
            JedisPoolConfig poolConfig = new JedisPoolConfig();
            int maxIdle = new Integer(prop.getProperty("redis.maxIdle"));
            int minIdle = new Integer(prop.getProperty("redis.minIdle"));
            int maxTotal = new Integer(prop.getProperty("redis.maxTotal"));
            poolConfig.setMaxIdle(maxIdle);// 最大闲置个数
            poolConfig.setMinIdle(minIdle);// 最小闲置个数
            poolConfig.setMaxTotal(maxTotal);// 最大连接数
            String host = prop.getProperty("redis.host"); // ip地址
            int port = new Integer(prop.getProperty("redis.port")); // 端口号
            map.put("maxIdle", maxIdle);
            map.put("minIdle", minIdle);
            map.put("maxTotal", maxTotal);
            map.put("host", host);
            map.put("port", port);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return map;
    }
}
</code></pre>
</blockquote>
<p>测试方法</p>
<pre><code class="java">import com.alibaba.fastjson.JSON;
import com.mmall.concurrency.utils.JedisPoolUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Map;
import java.util.Random;

/**
 * @version 1.0
 */
public class TestRedis {

    private static final String REDIS_KEY = "redis:key";

    public static void main(String[] args) {
        Map&lt;String, Object&gt; redisResource = new JedisPoolUtils().getRedisResource();
        JedisPool jedisPool = new JedisPool((String) redisResource.get("host"), 6379);
        Jedis jedis = jedisPool.getResource();
        jedis.select(2); // 切换数据库
        String key = REDIS_KEY + ":" + new Random().nextInt(1000);
        String jsonString = JSON.toJSONString("deductionPO");
        jedis.set(key, jsonString); 
        // NX是不存在时才set， XX是存在时才set， EX是秒，PX是毫秒
        jedis.set(key, jsonString, "NX", "EX", 180); // 添加过期时间
    }
}
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103538135</p>]]></description><guid isPermaLink="false">/archives/d05340a6-e6ed-459d-91dc-d0b1c46bb146</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 10:04:21 GMT</pubDate></item><item><title><![CDATA[Java 延时常见的几种方法]]></title><link>https://wiki.dgfygk.com/archives/86d317cb-dcc8-4c80-9feb-f2798a65aba1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20%E5%BB%B6%E6%97%B6%E5%B8%B8%E8%A7%81%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E6%B3%95&amp;url=/archives/86d317cb-dcc8-4c80-9feb-f2798a65aba1" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="java">1、 用Thread就不会iu无法终止

new Thread(new Runnable() {
            public void run() {
                while (true) {
                    test();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
            private void test() {
                // TODO Auto-generated method stub
            }
            public Runnable start() {
                // TODO Auto-generated method stub
                return null;
            }
        }.start());
2、 或者用现成的

javax.swing.Timer timer = new javax.swing.Timer(500, new ActionListener() {   public void actionPerformed(ActionEvent e) {     repaint();   } }；

timer.start();

3、下面这个方法测试过可以用 java非线程延时

import java.awt.Robot;
import java.util.Date;

public class test {
     public   static   void   main(String[]   args)   throws   Exception{   
         Robot  r   =   new   Robot(); 
         System.out.println( "延时前:"+new Date().toString()  ); 
         r.delay(   2000   );   
         System.out.println(   "延时后:"+new Date().toString()   );   
   }   
}


  4、 用这下面的TimeTask类（指定延时）

java里面的sleep()并不能精确定时，TimeTask可以：例下面的小程序：

import java.util.*;

public class test {
    public static void main(String[] args) {
        Timer timer = new Timer();// 实例化Timer类
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.println("退出");
                this.cancel();
            }
        }, 5000);// 这里百毫秒
        System.out.println("本程序存在5秒后自动退出");
    }
}
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103237666</p>]]></description><guid isPermaLink="false">/archives/86d317cb-dcc8-4c80-9feb-f2798a65aba1</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 10:03:52 GMT</pubDate></item><item><title><![CDATA[Java工具类NumberUtils详解]]></title><link>https://wiki.dgfygk.com/archives/d7916c8b-30cc-4065-94e1-d1ce1d66b45c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%B7%A5%E5%85%B7%E7%B1%BBNumberUtils%E8%AF%A6%E8%A7%A3&amp;url=/archives/d7916c8b-30cc-4065-94e1-d1ce1d66b45c" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="java">/**
         * @方法： public static int toInt(String str)
         * @作用： 将一个字符串转换成int类型
         * @参数： str-将要转换的字符串，可能会是null
         * @返回值： 转换失败返回0，否则返回转换的结果值
         */
        System.out.println(NumberUtils.toInt("5")); //返回5
        System.out.println(NumberUtils.toInt("")); //返回0
        System.out.println(NumberUtils.toInt(null)); //返回0

        /**
         * @方法： public static int toInt(String str, int defaultValue)
         * @作用： 将一个字符串转换成int类型，如果转换失败就返回一个默认值defaultValue
         * @参数： str-将要转换的字符串，可能会是null;
         *        defaultValue-默认值，如果转换失败就显示默认值
         * @返回值： 如果转换失败就返回一个默认值defaultValue，否则返回转换的结果值
         */
        System.out.println(NumberUtils.toInt(null, 0)); // 返回 0
        System.out.println(NumberUtils.toInt("", 0)); // 返回 0
        System.out.println(NumberUtils.toInt("5", 0)); // 返回 5

        /**
         * @方法： public static long toLong(String str)
         * @作用： 将一个字符串数据转换成一个long类型数据，如果转换失败返回0
         * @参数： 转换的字符串，可能为null;
         * @返回值： 字符串代表的long数据类型，或者转换失败的默认值0
         */
        System.out.println(NumberUtils.toLong(null)); // 返回 0
        System.out.println(NumberUtils.toLong("6")); // 返回 6
        System.out.println(NumberUtils.toLong("")); // 返回 0

        /**
         * @方法： public static long toLong(String str, long defaultValue)
         * @作用： 将一个字符串数据转换成一个long类型数据，如果转换失败返回默认值
         * @参数： str-转换的字符串，可能为null;
         *        defaultValue-默认值
         * @返回值： 字符串代表的long数据类型，转换失败的时候返回默认值
         */
        System.out.println(NumberUtils.toLong(null, 0)); // 返回 0
        System.out.println(NumberUtils.toLong("6", 0)); // 返回 6
        System.out.println(NumberUtils.toLong("", 0)); // 返回 0

        //double（双精度浮点数）和float（单精度浮点数）:
        //在内存中占的字节数不同，单精度浮点数占4个字节，双精度浮点数占8个字节；
        //有效位数不同，单精度浮点数有效位数是8位，双精度浮点数有效位数是16位；
        //位数不同，单精度浮点的表示范围：-3.40E+38 ~ +3.40E+38，双精度浮点的表示范围：-1.79E+308 ~ +1.79E+308
        //处理速度不同，一般来说处理器处理单精度浮点数的速度比处理双精度的浮点数速度要快；

        /**
         * @方法： public static double toDouble(String str)
         * @作用： 将一个字符串转换成double类型
         * @参数： str-转换的字符串，可能为null
         * @返回值： 字符串代表的double值；如果转换失败返回0.0d，如果字符串是null返回0.0d
         */
        System.out.println(NumberUtils.toDouble(null)); // 返回 0.0
        System.out.println(NumberUtils.toDouble("")); // 返回 0.0
        System.out.println(NumberUtils.toDouble("12")); // 返回 12.0

        /**
         * @方法： public static double toDouble(String str, double defaultValue)
         *
         * @作用： 将一个字符串转换成double类型，如果转换失败将会把默认值defaultValue返回
         * @参数： str-转换的字符串，可能为null;
         *        defaultValue-默认值
         * @返回值： 转换成功返回字符串代表的double数据类型；如果转换失败返回0.0d;如果字符串值是null将会把默认值返回
         */
        System.out.println(NumberUtils.toDouble(null, 0.001d)); // 返回 0.001
        System.out.println(NumberUtils.toDouble("", 0.001d)); // 返回 0.001
        System.out.println(NumberUtils.toDouble("12", 0.001d)); // 返回 12.0

        /**
         * @方法： public static float toFloat(String str)
         * @作用： 将一个字符串转换成float数据类型，如果转型失败返回0.0f;如果传入参数字符串为null，返回默认值0
         * @参数： str-转换的字符串，可能为null
         * @返回值： 转换成功返回字符串代表的float数据类型，如果转型失败返回0
         */
        System.out.println(NumberUtils.toFloat(null)); // 返回 0
        System.out.println(NumberUtils.toFloat("")); // 返回 0
        System.out.println(NumberUtils.toFloat("1.6")); // 返回 1.6

        /**
         * @方法： public static float toFloat(String str, float defaultValue)
         * @作用： 将一个字符串转换成float数据类型
         * @参数： str-转换的字符串，可能为null
         *        defaultValue-默认值
         * @返回值： 转换成功返回字符串代表的float数据类型，否则返回默认值defaultValue
         *          (如果转型失败返回默认值defaultValue;如果传入参数字符串为null，返回默认值defaultValue)
         */
        System.out.println(NumberUtils.toFloat(null, 0.0001f)); // 返回 1.0E-4(1.0 * 10^﹣4)
        System.out.println(NumberUtils.toFloat("",0.001f)); // 返回 0.001
        System.out.println(NumberUtils.toFloat("1.6",0.0001f)); // 返回 1.6


        /**
         * @方法： public static boolean isDigits(String str)
         * @作用： 检查字符串是否是只包含数字字符，null和空将会返回false
         * @参数： str-检查的字符串 str可为null
         * @返回值： 只包含数字返回true，否则返回false
         */
        System.out.println(NumberUtils.isDigits("6666! Life is so short,do something to make yourself happy,such as coding! 6666")); // 返回 false
        System.out.println(NumberUtils.isDigits("6666！生活如此短暂，做一些使自己开心的事，比如编程！6666"));  // 返回 false
        System.out.println(NumberUtils.isDigits("666666"));  // 返回 true

        /**
         * @方法： public static boolean isParsable(String str)
         * @作用： 检验提供的字符串是否可以转换为number,
         *        可解析的number包括下面的方法 Integer.parseInt(String), Long.parseLong(String),
         *        Float.parseFloat(String) or Double.parseDouble(String)，
         *        当调用上面的方法时这个方法可以替代ParseException异常；十六进制和科学符号被认为是不可解析的；null和空字符串返回false；
         * @参数： str-检验的字符串 str可为null
         * @返回值： true-如果参数是可转换的字符串，否则返回false
         */
        System.out.println(NumberUtils.isParsable(""));  // 返回 false
        System.out.println(NumberUtils.isParsable(null));  // 返回 false
        System.out.println(NumberUtils.isParsable("12"));  // 返回 true
        System.out.println(NumberUtils.isParsable("12.0")); // 返回 true
        System.out.println(NumberUtils.isParsable("12.3")); // 返回 true
        System.out.println(NumberUtils.isParsable("7a"));  // 返回 false (十六进制7a对应十进制122)
        System.out.println(NumberUtils.isParsable("1.0E-4")); // 返回 false


        /**
         * @方法： public static boolean isCreatable(String str)
         * @作用： 检查字符串是否是一个有效的number；
         *        有效数字包括进制标有0x或0X预选项，八进制数、科学记数法和标有类型限定符的数字，
         *        以 前导零开头的非十六进制字符串被视为八进制值，因此字符串09将返回false,因为9不是有效的八进制，
         *        然而从0开始的数字，被视为十进制；null、空或者空串将返回false;
         * @参数： str-检查的字符串； str可为null
         * @返回值： true-如果字符串是一个正确格式化的数字，否则返回false
         */
        System.out.println(NumberUtils.isCreatable(null)); // 返回 false
        System.out.println(NumberUtils.isCreatable(" ")); // 返回 false
        System.out.println(NumberUtils.isCreatable("")); // 返回 false
        System.out.println(NumberUtils.isCreatable("09")); // 返回 false
        System.out.println(NumberUtils.isCreatable("9")); // 返回 true
        System.out.println(NumberUtils.isCreatable("0x56")); // 返回 true
        System.out.println(NumberUtils.isCreatable("0X20")); // 返回 true

        /**
         * @方法： public static int compare(int x, int y)
         * @作用： 比较两个int数值
         * @参数： x-第一个int比较值，y-第二个int比较值
         * @返回值： 如果 x==y 返回0；x&lt;y 返回负数(-1)；x&gt;y 返回正数(1)
         */
        System.out.println(NumberUtils.compare(1,1)); // 返回 0
        System.out.println(NumberUtils.compare(1,2)); // 返回 -1
        System.out.println(NumberUtils.compare(3,2)); // 返回 1

        /**
         * @方法： public static int compare(long x, long y)
         * @作用： 比较两个int数值
         * @参数： x-第一个long比较值，y-第二个long比较值
         * @返回值： 如果 x==y 返回0；x&lt;y 返回负数(-1)；x&gt;y 返回正数(1)
         */
        System.out.println(NumberUtils.compare(1L,1L)); // 返回 0
        System.out.println(NumberUtils.compare(3L,2L)); // 返回 1
        System.out.println(NumberUtils.compare(5L,6L)); // 返回 -1

        /**
         * @方法： public static BigDecimal createBigDecimal(String str)
         * @作用： 将一个字符串转换成BigDecimal类型，如果字符串是null将会返回null
         * @参数： str-转换的字符串，可能为null
         * @返回值： 被转换的BigDecimal;如果输入的字符串是null将会返回null
         * @抛出异常： 空串或不能解析将会抛出 NumberFormatException
         */
//        System.out.println(NumberUtils.createBigDecimal("abc")); // 报异常 java.lang.NumberFormatException
//        System.out.println(NumberUtils.createBigDecimal("")); // 报异常 java.lang.NumberFormatException: A blank string is not a valid number
        System.out.println(NumberUtils.createBigDecimal(null)); // 返回 null
        System.out.println(NumberUtils.createBigDecimal("1235")); // 返回 1235

        /**
         * @方法： public static Double createDouble(String str)
         * @作用： 将一个字符串转换成Double类型，如果输入字符串是null将会返回null
         * @参数： str-转换的字符串，可能为null;
         * @返回值： 被转换的Double值（如果输入字符串是null将会返回null）；
         * @抛出异常： 如果值不能解析将会抛出NumberFormatException ;
         */
        System.out.println(NumberUtils.createDouble(null)); // 返回 null
//        System.out.println(NumberUtils.createDouble("asc")); // 返回 java.lang.NumberFormatException: For input string: "asc"
        System.out.println(NumberUtils.createDouble("12")); // 返回 12.0

        /**
         * @方法： public static Long createLong(String str)
         * @作用： 将一个字符串转换为Long数据类型，注：前导零表示八进制；空间不剪裁。如果字符串值是null将会返回null；
         * @参数： str-转换的字符串，可能为null；
         * @返回值： 被转换的Long数据（如果输入的是null将返回null）；
         * @抛出异常： 如果值不能被转换将会抛出NumberFormatException异常；
         */
//        System.out.println(NumberUtils.createLong("")); // java.lang.NumberFormatException: Zero length string
        System.out.println(NumberUtils.createLong(null));  // 返回 null
        System.out.println(NumberUtils.createLong("123")); // 返回 123

        /**
         * @方法： public static BigInteger createBigInteger(String str)
         * @作用： 将一个字符串转换为BigDecimal类型，自3.2以来，它处理六（0x或#）和八进制（0）符号。如果字符串为null将会返回null
         * @参数： str-转换的字符串,可能为null
         * @返回参数： 被转换的BigDecimal[如果输入字符串为null将返回null]
         * @抛出异常： 如果值不能被转换将会抛出NumberFormatException异常
         */
//        System.out.println(NumberUtils.createBigInteger("")); // java.lang.NumberFormatException: Zero length BigInteger
        System.out.println(NumberUtils.createBigInteger(null)); // 返回 null
//        System.out.println(NumberUtils.createBigInteger("12s"));// java.lang.NumberFormatException: For input string: "12s"
        System.out.println(NumberUtils.createBigInteger("12")); // 返回 12

        /**
         * @方法： public static Number createNumber(String str)
         * @作用： 将一个字符串值转换为java.lang.Number类型
         * @参数： str-转换字符串,可能为null
         * @返回值： 从字符串创建的Number值;如果输入的字符串为null则返回null
         * @抛出异常： 如果值不能被转换将会抛出NumberFormatException异常
         */
        System.out.println(NumberUtils.createNumber("123")); // 返回 123
//        System.out.println(NumberUtils.createNumber("12s")); // 返回 java.lang.NumberFormatException: 12s is not a valid number
        System.out.println(NumberUtils.createNumber("123.0")); // 返回 123.0
//        System.out.println(NumberUtils.createNumber("")); // 返回 java.lang.NumberFormatException: A blank string is not a valid number
        System.out.println(NumberUtils.createNumber(null)); // 返回 null

        /**
         * @方法： public static int max(int a, int b, int c)
         * @作用： 获取三个int值中最大的一个
         * @参数： a-值1，b-值2，c-值3
         * @返回值： 最大的值
         * @抛出异常： 如果参数是空抛出java.lang.IllegalArgumentException: Array cannot be empty
         */
        System.out.println(NumberUtils.max(1,5,2,9,10)); // 返回 10
//        System.out.println(NumberUtils.max()); // 返回 java.lang.IllegalArgumentException: Array cannot be empty

        /**
         * @方法： public static int max(int... array)
         * @作用： 返回数组中最大的值
         * @参数： array-一定不能为空或者null
         * @返回值： 数组中最大的值
         * @抛出异常： 如果数组为空或者null将会抛出IllegalArgumentException异常
         */
        System.out.println(NumberUtils.max(1,2,3,4,5,9,1.0,2.0,2.5,9.8)); //  返回 9.8
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103681712</p>]]></description><guid isPermaLink="false">/archives/d7916c8b-30cc-4065-94e1-d1ce1d66b45c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 10:03:28 GMT</pubDate></item><item><title><![CDATA[Lists.partition(List＜T＞ list, int size用法]]></title><link>https://wiki.dgfygk.com/archives/ee94cd32-79c3-4e2e-ac7f-bb917943f48f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Lists.partition%28List%EF%BC%9CT%EF%BC%9E%20list%2C%20int%20size%E7%94%A8%E6%B3%95&amp;url=/archives/ee94cd32-79c3-4e2e-ac7f-bb917943f48f" width="1" height="1" alt="" style="opacity:0;">
<p>首先应该知道这个单词怎么读</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3698c25a0fc45c0be86d888744dfc5d8.png&amp;size=m" alt=""></p>
<p>使用partition（）方法先引入jar包</p>
<pre><code class="java">      &lt;dependency&gt;
          &lt;groupId&gt;com.google.guava&lt;/groupId&gt;
          &lt;artifactId&gt;guava&lt;/artifactId&gt;
          &lt;version&gt;25.0-jre&lt;/version&gt;
      &lt;/dependency&gt;
</code></pre>
<p>1、将list（当然，也可以是其他集合）拆分成多份，常见的场景，比如批量执行sql、分批推送消息等。</p>
<p>（1）guava 实现（按照固定大小分片）</p>
<pre><code class="java">public static void main(String[] args) {
        List&lt;Integer&gt; numLists = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 1, 2, 3, 4, 4, 5, 6, 7, 78, 8, 9, 0,45,67,87);
        Collections.shuffle(numLists);
        List&lt;List&lt;Integer&gt;&gt; lists = Lists.partition(numLists, 6);
        Random random = new Random();
        lists.parallelStream().forEach(x -&gt; {
            System.out.println(x.toString());
        });
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F38cfa7f49b3d6e591d4efb53a8d75117.png&amp;size=m" alt=""></p>
<p>（2）使用apache.commons.collection实现</p>
<pre><code class="java">import org.apache.commons.collections4.ListUtils;

public static void main(String[] args) {
        List&lt;Integer&gt; intList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);
        List&lt;List&lt;Integer&gt;&gt; lists = ListUtils.partition(intList, 3);
        lists.parallelStream().forEach(x-&gt; System.out.println(x.toString()));

    }
</code></pre>
<p>2、平均切分集合（借鉴文章地址 ：<a href="https://www.jianshu.com/p/d0b6d686677d" title="Java List操作1(分片 partition) - 简书">Java List操作1(分片 partition) - 简书</a>）</p>
<pre><code class="java">/**
     * 将一个list均分成n个list,主要通过偏移量来实现的
     *
     * @param source
     * @return
     */
    public static &lt;T&gt; List&lt;List&lt;T&gt;&gt; averageAssignList(List&lt;T&gt; source, int n) {
        List&lt;List&lt;T&gt;&gt; result = new ArrayList&lt;&gt;();
        int remaider = source.size() % n;  //(先计算出余数)
        int number = source.size() / n;  //然后是商
        int offset = 0;//偏移量
        for (int i = 0; i &lt; n; i++) {
            List&lt;T&gt; value = null;
            if (remaider &gt; 0) {
                value = source.subList(i * number + offset, (i + 1) * number + offset + 1);
                remaider--;
                offset++;
            } else {
                value = source.subList(i * number + offset, (i + 1) * number + offset);
            }
            result.add(value);
        }
        return result;
    }


pubic static void main(String[] args){
        System.out.println("平均分list集合....");
        List&lt;List&lt;Integer&gt;&gt; assignList = averageAssignList(numLists, 5);
        assignList.parallelStream().forEach(x-&gt; System.out.println(x.toString()));
} 
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd51f55afa5e138a4adef58439bd52735.png&amp;size=m" alt=""></p>
<p>3、使用Java8 stream流 partition by , partitioningBy是一种特殊的分组，只会分成两组</p>
<pre><code class="java">System.out.println("使用Java8 stream流 partition by , partitioningBy是一种特殊的分组，只会分成两组");
        List&lt;Integer&gt; nums = Lists.newArrayList(1,1,8,2,3,4,5,6,7,9,10);
        Map&lt;Boolean,List&lt;Integer&gt;&gt; numMap= nums.stream().collect(Collectors.partitioningBy(num -&gt; num &gt; 5));
        System.out.println(numMap);
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc74c6844ed312da56d0f44ba24be2a64.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103383135</p>]]></description><guid isPermaLink="false">/archives/ee94cd32-79c3-4e2e-ac7f-bb917943f48f</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 10:03:08 GMT</pubDate></item><item><title><![CDATA[redis监听过期key时间]]></title><link>https://wiki.dgfygk.com/archives/1302717e-cc66-4a11-aa6b-13ac2b725d92</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=redis%E7%9B%91%E5%90%AC%E8%BF%87%E6%9C%9Fkey%E6%97%B6%E9%97%B4&amp;url=/archives/1302717e-cc66-4a11-aa6b-13ac2b725d92" width="1" height="1" alt="" style="opacity:0;">
<p>说明
 <br>
 redis key过期监听通知一定要 开启key过期通知功能。</p>
<p>事件通过 Redis 的订阅与发布功能（pub/sub）来进行分发,故需要订阅 <strong>keyevent@0</strong>:expired 这个topic通道</p>
<blockquote>
 <p>解释：<strong>keyevent@2</strong> :expired
  <br>
  <strong>__keyevent</strong> 必须以此开头；
  <br>
  <strong>@2</strong> 表示监听第二个数据库；
  <br>
  <strong>:expired</strong> 表示过期事件</p>
</blockquote>
<h4>1、redis 开启key过期通知</h4>
<blockquote>
 <p>1、<strong>修改 redis.conf 文件</strong> ，编辑/etc/redis/redis.conf文件，添加或启用以下内容（过期通知）：
  <br>
  配置详解:</p>
 <p>| 字符 | 发送通知 |
  <br>
  |—-|—————————————-|
  <br>
  | K | 键空间通知，所有通知以 keyspace@ 为前缀，针对Key |
  <br>
  | E | 键事件通知，所有通知以 keyevent@ 为前缀，针对event |
  <br>
  | g | DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知 |
  <br>
  | $ | 字符串命令的通知 |
  <br>
  | l | 列表命令的通知 |
  <br>
  | s | 集合命令的通知 |
  <br>
  | h | 哈希命令的通知 |
  <br>
  | z | 有序集合命令的通知 |
  <br>
  | x | 过期事件：每当有过期键被删除时发送 |
  <br>
  | e | 驱逐(evict)事件：每当有键因为 maxmemory 政策而被删除时发送 |
  <br>
  | A | 参数 g$lshzxe 的别名，相当于是All |</p>
 <p><strong>将 <code>notify-keyspace-events ""</code>改为 <code>notify-keyspace-events "Ex"</code></strong></p>
 <p>然后、重启redis , 即可测试失效事件的触发, 监听获取的值为 key
  <br>
  2、或者登陆redis-cli之后，输入以下命令：
  <br>
  <strong>config set notify-keyspace-events Ex</strong></p>
</blockquote>
<p>需要的jar包</p>
<pre><code class="java">                &lt;dependency&gt;
            &lt;groupId&gt;redis.clients&lt;/groupId&gt;
            &lt;artifactId&gt;jedis&lt;/artifactId&gt;
            &lt;version&gt;2.9.0&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.data&lt;/groupId&gt;
            &lt;artifactId&gt;spring-data-redis&lt;/artifactId&gt;
            &lt;version&gt;1.8.4.RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
</code></pre>
<p>配置文件</p>
<pre><code class="java">&lt;beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       ......
       default-lazy-init="false"&gt;

    &lt;context:property-placeholder location="classpath:redis.properties"
                                  ignore-unresolvable="true"/&gt;
    &lt;bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"&gt;
        &lt;property name="maxIdle" value="1000"/&gt;
        &lt;property name="minIdle" value="1000"/&gt;
        &lt;property name="maxTotal" value="100"/&gt;
        &lt;property name="testOnBorrow" value="true"/&gt;
        &lt;property name="testOnReturn" value="true"/&gt;
    &lt;/bean&gt;

    &lt;bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:host-name="127.0.0.1"
          p:port="6379"
          p:pool-config-ref="jedisPoolConfig"
          p:use-pool="true"
          p:database="${redis.database}"/&gt;

    &lt;bean id="RedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnectionFactory"
          p:keySerializer-ref="stringRedisSerializer"
          p:valueSerializer-ref="JdkSerializationRedisSerializer"/&gt;

    &lt;bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/&gt;
    &lt;bean id="JdkSerializationRedisSerializer"
          class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/&gt;

    &lt;bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter"&gt;
        &lt;constructor-arg&gt;
            &lt;bean class="com.mmall.concurrency.listener.RedisKeyExpiredListener"/&gt;
        &lt;/constructor-arg&gt;
    &lt;/bean&gt;
    &lt;bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer"&gt;
        &lt;property name="connectionFactory" ref="jedisConnectionFactory" /&gt;
        &lt;property name="messageListeners"&gt;
            &lt;map&gt;
                &lt;entry key-ref="messageListener"&gt;
                    &lt;list&gt;
&lt;!--                        &lt;bean class="org.springframework.data.redis.listener.ChannelTopic"&gt;--&gt;
&lt;!--                            &lt;constructor-arg value="__keyevent@1__:expired" /&gt;--&gt;
&lt;!--                        &lt;/bean&gt;--&gt;
&lt;!--                        &lt;bean class="org.springframework.data.redis.listener.PatternTopic"&gt;--&gt;
&lt;!--                            &lt;constructor-arg value="*" /&gt;--&gt;
&lt;!--                        &lt;/bean&gt;--&gt;
&lt;!--                        &lt;bean class="org.springframework.data.redis.listener.PatternTopic"&gt;--&gt;
&lt;!--                            &lt;constructor-arg value="'__key*__:*" /&gt;--&gt;
&lt;!--                        &lt;/bean&gt;--&gt;
                        &lt;bean class="org.springframework.data.redis.listener.ChannelTopic"&gt;
                            &lt;constructor-arg value="__keyevent@2__:expired"/&gt;
                        &lt;/bean&gt;
                    &lt;/list&gt;
                &lt;/entry&gt;
            &lt;/map&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
&lt;/beans&gt;
</code></pre>
<pre><code>```java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;


/**
 * redis监听
 * 监听类需要实现MessageListener,监听到redis key过期的时候会自动执行到onMessage方法
 * @version 1.0
 */
@Slf4j
public class RedisKeyExpiredListener implements MessageListener {

    private static final String FAIL_KEY = "fail:key";

    @Autowired
    private TestService testService;

    @Override
    public void onMessage(Message message, byte[] bytes) {
        String expiredKey = message.toString();// 获取失效的key
        log.info("=========&gt;失效的key:{}", expiredKey);
        if (expiredKey.startsWith(FAIL_KEY )) {
            // 进行相应的业务处理....
        }
    }
}
</code></pre>
<p>上面就可以完成监听的功能！！！！</p>
<p>上面的代码还可以改写成下面的形式</p>
<pre><code class="java">@Component(value ="SERVICE_NAME")
@Slf4j
public class RedisKeyExpiredListener implements MessageListener {

    private static final String FAIL_KEY = "fail:key";

    @Autowired
    private TestService testService;

    @Override
    public void onMessage(Message message, byte[] bytes) {
        String expiredKey = message.toString();// 获取失效的key
        log.info("=========&gt;失效的key:{}", expiredKey);
        if (expiredKey.startsWith(FAIL_KEY )) {
            // 进行相应的业务处理....
        }
    }
}



import com.mmall.concurrency.listener.RedisKeyExpiredListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

/**
 * redis监听容器
 * @author 12706
 */
@Configuration
public class RedisConfig {

    @Autowired
    @Qualifier(value = "SERVICE_NAME")//指定的是自己写的RedisMessageListener
    private RedisKeyExpiredListener redisKeyExpiredListener;

    @Autowired
    private RedisTemplate redisTemplate;

    @Bean
    public RedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisTemplate.getConnectionFactory());
        container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@0__:expired"));
        //这里是监听redis第一个库里面key的过期
        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter() {
        return new MessageListenerAdapter(redisKeyExpiredListener);
    }
}
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103538926</p>]]></description><guid isPermaLink="false">/archives/1302717e-cc66-4a11-aa6b-13ac2b725d92</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 10:02:53 GMT</pubDate></item><item><title><![CDATA[Redis的启动、关闭、查看状态]]></title><link>https://wiki.dgfygk.com/archives/2b05cc5b-f15c-4367-b71b-384ab8033eef</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%E7%9A%84%E5%90%AF%E5%8A%A8%E3%80%81%E5%85%B3%E9%97%AD%E3%80%81%E6%9F%A5%E7%9C%8B%E7%8A%B6%E6%80%81&amp;url=/archives/2b05cc5b-f15c-4367-b71b-384ab8033eef" width="1" height="1" alt="" style="opacity:0;">
<h4>一、Redis的启动：</h4>
<p>（1）切换到redis根目录下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F92be2adefdd6e098c838eff1d606107f.png&amp;size=m" alt=""></p>
<p>（2） 然后使用命令：src/redis-service ./redis.conf 启动</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F61c71e8ddc9a061ce148811ef05aeaf5.png&amp;size=m" alt=""></p>
<p>如果不打印任何日志或者打印如下日志证明启动成功。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F95d9464326173d8546a303cdb6b35c11.png&amp;size=m" alt=""></p>
<p>最后使用redis-cli 进入redis操作界面，并使用info命令查看详细信息</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2b722e58b351d8aa5c2e56b1f2f0cacf.png&amp;size=m" alt=""></p>
<h4>二、Redis关闭：</h4>
<p>（1）快捷键Ctrl + C关闭</p>
<p>（2）quit命令关闭</p>
<p>（3）exit命令关闭</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3aae4b3ac912bb4f71604918775a2ebb.png&amp;size=m" alt=""></p>
<h4>三、查看Redis状态</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fba00b047e8a17ce6449599d8202b44fe.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F83ebb7563d9fba502cc487a7f15067d0.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103615994</p>]]></description><guid isPermaLink="false">/archives/2b05cc5b-f15c-4367-b71b-384ab8033eef</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 10:01:47 GMT</pubDate></item><item><title><![CDATA[使用joda_time计算两时间的月数差，天数差]]></title><link>https://wiki.dgfygk.com/archives/6ed58a17-2c23-4119-948c-c1e681e8722b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8joda_time%E8%AE%A1%E7%AE%97%E4%B8%A4%E6%97%B6%E9%97%B4%E7%9A%84%E6%9C%88%E6%95%B0%E5%B7%AE%EF%BC%8C%E5%A4%A9%E6%95%B0%E5%B7%AE&amp;url=/archives/6ed58a17-2c23-4119-948c-c1e681e8722b" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="java">private static final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");

    /**
     * 利用joda_time包计算
     *
     * @param dateTime1 时间1
     * @param dateTime2 时间2
     * @return 两个时间的月份差值
     */
    private static int getMonthDifferent(DateTime dateTime1, DateTime dateTime2) {
        return Months.monthsBetween(dateTime1, dateTime2).getMonths();
    }

    /**
     * 利用joda_time包计算
     *
     * @param dateTime1 时间1
     * @param dateTime2 时间2
     * @return 两个时间的自然天数差值
     */
    private static int getDaysDifferent(DateTime dateTime1, DateTime dateTime2) {
        return Days.daysBetween(dateTime1, dateTime2).getDays();
    }

    /**
     * 利用joda_time包计算两个时间的天数差
     *
     * @param dt1 时间1
     * @param dt2 时间2
     * @return 天数差
     */
    private static int getDaysDifferent(Date dt1, Date dt2) {
        DateTime dateTime1 = new DateTime(dt1);
        DateTime dateTime2 = new DateTime(dt2);
        return Days.daysBetween(dateTime1, dateTime2).getDays();
    }


    /**
     * 通过Calendar类得日期比较，在这需要考虑闰年和平年，也要考虑跨年份
     *
     * @param date1 时间1
     * @param date2 时间2
     * @return 两个时间的自然天数差值
     */
    public static int differentDays(Date date1, Date date2) {
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(date1);

        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
        int day1 = cal1.get(Calendar.DAY_OF_YEAR);
        int day2 = cal2.get(Calendar.DAY_OF_YEAR);

        int year1 = cal1.get(Calendar.YEAR);
        int year2 = cal2.get(Calendar.YEAR);
        if (year1 != year2) {  //不同一年
            int timeDistance = 0;
            for (int i = year1; i &lt; year2; i++) {
                if (i % 4 == 0 &amp;&amp; i % 100 != 0 || i % 400 == 0) { //闰年
                    timeDistance += 366;
                } else { //不是闰年
                    timeDistance += 365;
                }
            }
            return timeDistance + (day2 - day1);
        } else { //同一年
            return day2 - day1;
        }
    }

    /**
     * LocalDateTime 计算两个时间的时间差
     *
     * @param dt1 时间1
     * @param dt2 时间2
     * @return 两个时间的月份差值
     */
    private static int getMonthDifferent(LocalDateTime dt1, LocalDateTime dt2) {
        //获取第一个时间点的月份
        int month1 = dt1.getMonthValue();
        //获取第一个时间点的年份
        int year1 = dt1.getYear();
        //获取第一个时间点的月份
        int month2 = dt2.getMonthValue();
        //获取第一个时间点的年份
        int year2 = dt2.getYear();
        //返回两个时间点的月数差
        return (year2 - year1) * 12 + (month2 - month1);
    }

    /**
     * 计算月份差
     *
     * @param time1 时间1
     * @param time2 时间2
     * @return 两个时间的月份差值
     */
    private static int getMonthDifferent(Integer time1, Integer time2) {
        LocalDateTime startTime = LocalDateTime.ofEpochSecond(time1, 0, ZoneOffset.ofHours(8));
        LocalDateTime endTime = LocalDateTime.ofEpochSecond(time2, 0, ZoneOffset.ofHours(8));
        return getMonthDifferent(startTime, endTime);
    }

    public static void main(String[] args) {
        DateTime dateTime1 = formatter.parseDateTime("2018-01-12");
        DateTime dateTime2 = formatter.parseDateTime("2019-12-25");
        int months = getMonthDifferent(dateTime1, dateTime2);
        System.out.println("1、月份差：" + months);

        LocalDateTime of1 = LocalDateTime.of(2018, 1, 12, 1, 1);
        LocalDateTime of2 = LocalDateTime.of(2019, 12, 25, 23, 16);
        int monthDiff = getMonthDifferent(of1, of2);
        System.out.println("2、月份差：" + monthDiff);

        int daysDifferent = getDaysDifferent(dateTime1, dateTime2);
        System.out.println("1、天数差：" + daysDifferent);

        int days = differentDays(new Date(2018 , 1 , 12), new Date(2019 , 12 ,25));
        System.out.println("2、天数差：" + days);

        Date date = Date.from(LocalDate.of(2016, 2, 1).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
        Date date2 = Date.from(LocalDate.of(2018, 3, 1).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
        int different = getDaysDifferent(date, date2);
        System.out.println("3、天数差：" +different);
    }
</code></pre>
<p>运行结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F96f78ef4f5c7e9c75c738ee9f58d9f59.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/103768532</p>]]></description><guid isPermaLink="false">/archives/6ed58a17-2c23-4119-948c-c1e681e8722b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 10:01:13 GMT</pubDate></item><item><title><![CDATA[2019 JAVA面试题附答案]]></title><link>https://wiki.dgfygk.com/archives/ec7c8a8c-d2e6-4646-bbaa-c73a7447aa48</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=2019%20JAVA%E9%9D%A2%E8%AF%95%E9%A2%98%E9%99%84%E7%AD%94%E6%A1%88&amp;url=/archives/ec7c8a8c-d2e6-4646-bbaa-c73a7447aa48" width="1" height="1" alt="" style="opacity:0;">
<h2>JAVA基础</h2>
<h3>JAVA中的几种基本类型，各占用多少字节？</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F814b899abbbb9df356473c9c21c0108f.png&amp;size=m" alt="">
 <br>
 下图单位是bit,非字节 1B=8bit</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd4f3058bf5e4afadefa0a0db77bf7890.jpeg&amp;size=m" alt=""></p>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F08167419d6ab1796698c5a8bda1fdeac.jpeg&amp;size=m" alt=""></h4>
<p>String能被继承吗？为什么？</p>
<p>不可以，因为String类有final修饰符，而final修饰的类是不能被继承的，实现细节不允许改变。平常我们定义的String str=“a”;其实和String str=new String(“a”)还是有差异的。</p>
<p>前者默认调用的是String.valueOf来返回String实例对象，至于调用哪个则取决于你的赋值，比如String num=1,调用的是</p>
<pre><code class="java">public static String valueOf(int i) {
return Integer.toString(i);
}
</code></pre>
<p>后者则是调用如下部分：</p>
<pre><code class="java">public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
</code></pre>
<p>最后我们的变量都存储在一个char数组中</p>
<pre><code class="java">private final char value[];
</code></pre>
<blockquote>
 <h4>##String， Stringbuffer， StringBuilder 的区别。</h4>
 <p>String 字符串常量(final修饰，不可被继承)，String是常量，当创建之后即不能更改。(可以通过StringBuffer和StringBuilder创建String对象(常用的两个字符串操作类)。)</p>
 <p>StringBuffer 字符串变量（线程安全）,其也是final类别的，不允许被继承，其中的绝大多数方法都进行了同步处理，包括常用的Append方法也做了同步处理(synchronized修饰)。其自jdk1.0起就已经出现。其toString方法会进行对象缓存，以减少元素复制开销。</p>
</blockquote>
<pre><code class="java">public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
</code></pre>
<p>StringBuilder 字符串变量（非线程安全）其自jdk1.5起开始出现。与StringBuffer一样都继承和实现了同样的接口和类，方法除了没使用synch修饰以外基本一致，不同之处在于最后toString的时候，会直接返回一个新对象。</p>
<pre><code class="java">public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
</code></pre>
<blockquote>
 <h3>ArrayList 和 LinkedList 有什么区别。</h3>
 <p>ArrayList和LinkedList都实现了List接口，有以下的不同点：
  <br>
  1、ArrayList是基于索引的数据接口，它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应，LinkedList是以元素列表的形式存储它的数据，每一个元素都和它的前一个和后一个元素链接在一起，在这种情况下，查找某个元素的时间复杂度是O(n)。
  <br>
  2、相对于ArrayList，LinkedList的插入，添加，删除操作速度更快，因为当元素被添加到集合任意位置的时候，不需要像数组那样重新计算大小或者是更新索引。
  <br>
  3、LinkedList比ArrayList更占内存，因为LinkedList为每一个节点存储了两个引用，一个指向前一个元素，一个指向下一个元素。</p>
 <h3>讲讲类的实例化顺序，比如父类静态数据，构造函数，字段，子类静态数据，构造函数，字段，当 new 的时候， 他们的执行顺序。</h3>
 <p>此题考察的是类加载器实例化时进行的操作步骤（加载–&gt;连接-&gt;初始化）。
  <br>
  父类静态代变量、
  <br>
  父类静态代码块、
  <br>
  子类静态变量、
  <br>
  子类静态代码块、
  <br>
  父类非静态变量（父类实例成员变量）、
  <br>
  父类构造函数、
  <br>
  子类非静态变量（子类实例成员变量）、
  <br>
  子类构造函数。
  <br>
  测试demo：<a href="http://blog.csdn.net/u014042066/article/details/77574956">http://blog.csdn.net/u014042066/article/details/77574956</a>
  <br>
  参阅我的博客《深入理解类加载》：<a href="http://blog.csdn.net/u014042066/article/details/77394480">http://blog.csdn.net/u014042066/article/details/77394480</a></p>
 <h3>用过哪些 Map 类，都有什么区别，HashMap 是线程安全的吗,并发下使用的 Map 是什么，他们内部原理分别是什么，比如存储方式， hashcode，扩容， 默认容量等。</h3>
 <p>hashMap是线程不安全的，HashMap是数组+链表+红黑树（JDK1.8增加了红黑树部分）实现的，采用哈希表来存储的，
  <br>
  参照该链接：<a href="https://zhuanlan.zhihu.com/p/21673805">https://zhuanlan.zhihu.com/p/21673805</a>
  <br>
  JAVA8 的 ConcurrentHashMap 为什么放弃了分段锁，有什么问题吗，如果你来设计，你如何设计。
  <br>
  参照：<a href="https://yq.aliyun.com/articles/36781">https://yq.aliyun.com/articles/36781</a></p>
 <h3>有没有有顺序的 Map 实现类， 如果有， 他们是怎么保证有序的。</h3>
 <p>TreeMap和LinkedHashMap是有序的（TreeMap默认升序，LinkedHashMap则记录了插入顺序）。</p>
 <p>参照：<a href="http://uule.iteye.com/blog/1522291">http://uule.iteye.com/blog/1522291</a></p>
 <h3>抽象类和接口的区别，类可以继承多个类么，接口可以继承多个接口么,类可以实现多个接口么。</h3>
 <p>1、抽象类和接口都不能直接实例化，如果要实例化，抽象类变量必须指向实现所有抽象方法的子类对象，接口变量必须指向实现所有接口方法的类对象。
  <br>
  2、抽象类要被子类继承，接口要被类实现。
  <br>
  3、接口只能做方法申明，抽象类中可以做方法申明，也可以做方法实现
  <br>
  4、接口里定义的变量只能是公共的静态的常量，抽象类中的变量是普通变量。
  <br>
  5、抽象类里的抽象方法必须全部被子类所实现，如果子类不能全部实现父类抽象方法，那么该子类只能是抽象类。同样，一个实现接口的时候，如不能全部实现接口方法，那么该类也只能为抽象类。
  <br>
  6、抽象方法只能申明，不能实现。abstract void abc();不能写成abstract void abc(){}。
  <br>
  7、抽象类里可以没有抽象方法
  <br>
  8、如果一个类里有抽象方法，那么这个类只能是抽象类
  <br>
  9、抽象方法要被实现，所以不能是静态的，也不能是私有的。
  <br>
  10、接口可继承接口，并可多继承接口，但类只能单根继承。</p>
</blockquote>
<h3>继承和聚合的区别在哪。</h3>
<p>继承指的是一个类（称为子类、子接口）继承另外的一个类（称为父类、父接口）的功能，并可以增加它自己的新功能的能力，继承是类与类或者接口与接口之间最常见的关系；在Java中此类关系通过关键字extends明确标识，在设计时一般没有争议性；
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3ab25d5c2d4775cfe51cec1a085def2f.png&amp;size=m" alt="">
 <br>
 聚合是关联关系的一种特例，他体现的是整体与部分、拥有的关系，即has-a的关系，此时整体与部分之间是可分离的，他们可以具有各自的生命周期，部分可以属于多个整体对象，也可以为多个整体对象共享；比如计算机与CPU、公司与员工的关系等；表现在代码层面，和关联关系是一致的，只能从语义级别来区分；
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7c62c75d9ac6ffbdf36a4ca081ebe356.png&amp;size=m" alt=""></p>
<p>参考：<a href="http://www.cnblogs.com/jiqing9006/p/5915023.html">http://www.cnblogs.com/jiqing9006/p/5915023.html</a></p>
<blockquote>
 <h3>讲讲你理解的 nio和 bio 的区别是啥，谈谈 reactor 模型。</h3>
 <p>IO是面向流的，NIO是面向缓冲区的
  <br>
  参考：<a href="https://zhuanlan.zhihu.com/p/23488863">https://zhuanlan.zhihu.com/p/23488863</a>
  <br>
  <a href="http://developer.51cto.com/art/201103/252367.htm">http://developer.51cto.com/art/201103/252367.htm</a>
  <br>
  <a href="http://www.jianshu.com/p/3f703d3d804c">http://www.jianshu.com/p/3f703d3d804c</a></p>
 <h3>反射的原理，反射创建类实例的三种方式是什么</h3>
 <p>参照：<a href="http://www.jianshu.com/p/3ea4a6b57f87?amp">http://www.jianshu.com/p/3ea4a6b57f87?amp</a></p>
 <p><a href="http://blog.csdn.net/yongjian1092/article/details/7364451">http://blog.csdn.net/yongjian1092/article/details/7364451</a></p>
 <h3>反射中，Class.forName 和 ClassLoader 区别。</h3>
 <p><a href="https://my.oschina.net/gpzhang/blog/486743">https://my.oschina.net/gpzhang/blog/486743</a></p>
 <h3>描述动态代理的几种实现方式，分别说出相应的优缺点。</h3>
 <p>Jdk cglib jdk底层是利用反射机制，需要基于接口方式，这是由于
  <br>
  Proxy.newProxyInstance(target.getClass().getClassLoader(),
  <br>
  target.getClass().getInterfaces(), this);
  <br>
  Cglib则是基于asm框架，实现了无反射机制进行代理，利用空间来换取了时间，代理效率高于jdk
  <br>
  <a href="http://lrd.ele.me/2017/01/09/dynamic_proxy/">http://lrd.ele.me/2017/01/09/dynamic_proxy/</a></p>
 <h2>动态代理与 cglib 实现的区别</h2>
 <p>同上（基于invocationHandler和methodInterceptor）</p>
 <h3>为什么 CGlib 方式可以对接口实现代理。</h3>
 <p>同上</p>
 <h3>final 的用途</h3>
 <p>类、变量、方法
  <br>
  <a href="http://www.importnew.com/7553.html">http://www.importnew.com/7553.html</a></p>
 <h3>写出三种单例模式实现。</h3>
 <p>懒汉式单例，饿汉式单例，双重检查等
  <br>
  参考：<a href="https://my.oschina.net/dyyweb/blog/609021">https://my.oschina.net/dyyweb/blog/609021</a></p>
 <h3>如何在父类中为子类自动完成所有的 hashcode 和 equals 实现？这么做有何优劣。</h3>
 <p>同时复写hashcode和equals方法，优势可以添加自定义逻辑，且不必调用超类的实现。
  <br>
  参照：<a href="http://java-min.iteye.com/blog/1416727">http://java-min.iteye.com/blog/1416727</a></p>
 <h3>请结合 OO 设计理念，谈谈访问修饰符 public、private、protected、default 在应用设计中的作用。</h3>
 <p>访问修饰符，主要标示修饰块的作用域，方便隔离防护</p>
 <p>1.</p>
 <pre><code>   同一个类   同一个包    不同包的子类  不同包的非子类
</code></pre>
 <p>Private √</p>
 <p>Default √ √
  <br>
  Protected √ √ √
  <br>
  Public √ √ √ √
  <br>
  public： Java语言中访问限制最宽的修饰符，一般称之为"公共的”。被其修饰的类、属性以及方法不
  <br>
  　　　　　仅可以跨类访问，而且允许跨包（package）访问。
  <br>
  private: Java语言中对访问权限限制的最窄的修饰符，一般称之为"私有的”。被其修饰的类、属性以
  <br>
  　　　　　及方法只能被该类的对象访问，其子类不能访问，更不能允许跨包访问。
  <br>
  protect: 介于public 和 private 之间的一种访问修饰符，一般称之为"保护形”。被其修饰的类、
  <br>
  　　　　　属性以及方法只能被类本身的方法及子类访问，即使子类在不同的包中也可以访问。
  <br>
  default：即不加任何访问修饰符，通常称为"默认访问模式”。该模式下，只允许在同一个包中进行访
  <br>
  　　　　　问。</p>
 <h3>深拷贝和浅拷贝区别。</h3>
 <p><a href="http://www.oschina.net/translate/java-copy-shallow-vs-deep-in-which-you-will-swim">http://www.oschina.net/translate/java-copy-shallow-vs-deep-in-which-you-will-swim</a></p>
 <h3>数组和链表数据结构描述，各自的时间复杂度</h3>
 <p><a href="http://blog.csdn.net/snow_wu/article/details/53172721">http://blog.csdn.net/snow_wu/article/details/53172721</a></p>
 <h3>error 和 exception 的区别，CheckedException，RuntimeException 的区别</h3>
 <p><a href="http://blog.csdn.net/woshixuye/article/details/8230407">http://blog.csdn.net/woshixuye/article/details/8230407</a></p>
 <h3>请列出 5 个运行时异常。</h3>
 <p>同上</p>
 <h3>在自己的代码中，如果创建一个 java.lang.String 对象，这个对象是否可以被类加载器加载？为什么</h3>
 <p>类加载无须等到"首次使用该类"时加载，jvm允许预加载某些类。。。。
  <br>
  <a href="http://www.cnblogs.com/jasonstorm/p/5663864.html">http://www.cnblogs.com/jasonstorm/p/5663864.html</a></p>
 <h3>说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法。</h3>
 <p>参考上边试题</p>
 <h3>在 jdk1.5 中，引入了泛型，泛型的存在是用来解决什么问题。</h3>
 <p>泛型的本质是参数化类型，也就是说所操作的数据类型被指定为一个参数，泛型的好处是在编译的时候检查类型安全，并且所有的强制转换都是自动和隐式的，以提高代码的重用率
  <br>
  <a href="http://baike.baidu.com/item/java%E6%B3%9B%E5%9E%8B">http://baike.baidu.com/item/java泛型</a></p>
 <h3>这样的 a.hashcode() 有什么用，与 a.equals(b)有什么关系。</h3>
 <p>hashcode
  <br>
  hashcode（）方法提供了对象的hashCode值，是一个native方法，返回的默认值与System.identityHashCode(obj)一致。</p>
 <p>通常这个值是对象头部的一部分二进制位组成的数字，具有一定的标识对象的意义存在，但绝不定于地址。</p>
 <p>作用是：用一个数字来标识对象。比如在HashMap、HashSet等类似的集合类中，如果用某个对象本身作为Key，即要基于这个对象实现Hash的写入和查找，那么对象本身如何实现这个呢？就是基于hashcode这样一个数字来完成的，只有数字才能完成计算和对比操作。</p>
 <p>hashcode是否唯一
  <br>
  hashcode只能说是标识对象，在hash算法中可以将对象相对离散开，这样就可以在查找数据的时候根据这个key快速缩小数据的范围，但hashcode不一定是唯一的，所以hash算法中定位到具体的链表后，需要循环链表，然后通过equals方法来对比Key是否是一样的。</p>
 <p>equals与hashcode的关系
  <br>
  equals相等两个对象，则hashcode一定要相等。但是hashcode相等的两个对象不一定equals相等。
  <br>
  <a href="https://segmentfault.com/a/1190000004520827">https://segmentfault.com/a/1190000004520827</a></p>
 <h3>有没有可能 2 个不相等的对象有相同的 hashcode。</h3>
 <p>有</p>
 <h3>Java 中的 HashSet 内部是如何工作的。</h3>
 <p>底层是基于hashmap实现的
  <br>
  <a href="http://wiki.jikexueyuan.com/project/java-collection/hashset.html">http://wiki.jikexueyuan.com/project/java-collection/hashset.html</a>
  <br>
  什么是序列化，怎么序列化，为什么序列化，反序列化会遇到什么问题，如何解决。
  <br>
  <a href="http://www.importnew.com/17964.html">http://www.importnew.com/17964.html</a></p>
</blockquote>
<h2>JVM 知识</h2>
<blockquote>
 <h3>什么情况下会发生栈内存溢出。</h3>
 <p>如果线程请求的栈深度大于虚拟机所允许的深度，将抛出StackOverflowError异常。 如果虚拟机在动态扩展栈时无法申请到足够的内存空间，则抛出OutOfMemoryError异常。
  <br>
  参照：<a href="http://wiki.jikexueyuan.com/project/java-vm/storage.html">http://wiki.jikexueyuan.com/project/java-vm/storage.html</a></p>
</blockquote>
<h3>JVM 的内存结构，Eden 和 Survivor 比例。</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc59bde04625eaff98eaad07ed5ce1380.png&amp;size=m" alt="">
 <br>
 eden 和 survior 是按8比1分配的
 <br>
 <a href="http://blog.csdn.net/lojze_ly/article/details/49456255">http://blog.csdn.net/lojze_ly/article/details/49456255</a></p>
<blockquote>
 <h3>jvm 中一次完整的 GC 流程是怎样的，对象如何晋升到老年代，说说你知道的几种主要的jvm 参数。</h3>
 <p>对象诞生即新生代-&gt;eden，在进行minor gc过程中，如果依旧存活，移动到from，变成Survivor，进行标记代数，如此检查一定次数后，晋升为老年代，
  <br>
  <a href="http://www.cnblogs.com/redcreen/archive/2011/05/04/2037056.html">http://www.cnblogs.com/redcreen/archive/2011/05/04/2037056.html</a>
  <br>
  <a href="http://ifeve.com/useful-jvm-flags/">http://ifeve.com/useful-jvm-flags/</a>
  <br>
  <a href="https://wangkang007.gitbooks.io/jvm/content/jvmcan_shu_xiang_jie.html">https://wangkang007.gitbooks.io/jvm/content/jvmcan_shu_xiang_jie.html</a></p>
 <h3>你知道哪几种垃圾收集器，各自的优缺点，重点讲下 cms，包括原理，流程，优缺点</h3>
 <p>Serial、parNew、ParallelScavenge、SerialOld、ParallelOld、CMS、G1
  <br>
  <a href="https://wangkang007.gitbooks.io/jvm/content/chapter1.html">https://wangkang007.gitbooks.io/jvm/content/chapter1.html</a></p>
 <h3>垃圾回收算法的实现原理。</h3>
 <p><a href="http://www.importnew.com/13493.html">http://www.importnew.com/13493.html</a></p>
 <h3>当出现了内存溢出，你怎么排错。</h3>
 <p>首先分析是什么类型的内存溢出，对应的调整参数或者优化代码。
  <br>
  <a href="https://wangkang007.gitbooks.io/jvm/content/4jvmdiao_you.html">https://wangkang007.gitbooks.io/jvm/content/4jvmdiao_you.html</a></p>
 <h3>JVM 内存模型的相关知识了解多少，比如重排序，内存屏障，happen-before，主内存，工作内存等。</h3>
 <p>内存屏障：为了保障执行顺序和可见性的一条cpu指令
  <br>
  重排序：为了提高性能，编译器和处理器会对执行进行重拍
  <br>
  happen-before：操作间执行的顺序关系。有些操作先发生。
  <br>
  主内存：共享变量存储的区域即是主内存
  <br>
  工作内存：每个线程copy的本地内存，存储了该线程以读/写共享变量的副本
  <br>
  <a href="http://ifeve.com/java-memory-model-1/">http://ifeve.com/java-memory-model-1/</a>
  <br>
  <a href="http://www.jianshu.com/p/d3fda02d4cae">http://www.jianshu.com/p/d3fda02d4cae</a>
  <br>
  <a href="http://blog.csdn.net/kenzyq/article/details/50918457">http://blog.csdn.net/kenzyq/article/details/50918457</a></p>
 <h3>简单说说你了解的类加载器。</h3>
 <p>类加载器的分类（bootstrap,ext,app,curstom），类加载的流程(load-link-init)
  <br>
  <a href="http://blog.csdn.net/gjanyanlig/article/details/6818655/">http://blog.csdn.net/gjanyanlig/article/details/6818655/</a></p>
 <h3>讲讲 JAVA 的反射机制。</h3>
 <p>Java程序在运行状态可以动态的获取类的所有属性和方法，并实例化该类，调用方法的功能
  <br>
  <a href="http://baike.baidu.com/link?url=C7p1PeLa3ploAgkfAOK-4XHE8HzQuOAB7K5GPcK_zpbAa_Aw-nO3997K1oir8N--1_wxXZfOThFrEcA0LjVP6wNOwidVTkLBzKlQVK6JvXYvVNhDWV9yF-NIOebtg1hwsnagsjUhOE2wxmiup20RRa#7">http://baike.baidu.com/link?url=C7p1PeLa3ploAgkfAOK-4XHE8HzQuOAB7K5GPcK_zpbAa_Aw-nO3997K1oir8N--1_wxXZfOThFrEcA0LjVP6wNOwidVTkLBzKlQVK6JvXYvVNhDWV9yF-NIOebtg1hwsnagsjUhOE2wxmiup20RRa#7</a></p>
 <h3>你们线上应用的 JVM 参数有哪些。</h3>
 <p>-server
  <br>
  Xms6000M
  <br>
  -Xmx6000M
  <br>
  -Xmn500M
  <br>
  -XX:PermSize=500M
  <br>
  -XX:MaxPermSize=500M
  <br>
  -XX:SurvivorRatio=65536
  <br>
  -XX:MaxTenuringThreshold=0
  <br>
  -Xnoclassgc
  <br>
  -XX:+DisableExplicitGC
  <br>
  -XX:+UseParNewGC
  <br>
  -XX:+UseConcMarkSweepGC
  <br>
  -XX:+UseCMSCompactAtFullCollection
  <br>
  -XX:CMSFullGCsBeforeCompaction=0
  <br>
  -XX:+CMSClassUnloadingEnabled
  <br>
  -XX:-CMSParallelRemarkEnabled
  <br>
  -XX:CMSInitiatingOccupancyFraction=90
  <br>
  -XX:SoftRefLRUPolicyMSPerMB=0
  <br>
  -XX:+PrintClassHistogram
  <br>
  -XX:+PrintGCDetails
  <br>
  -XX:+PrintGCTimeStamps
  <br>
  -XX:+PrintHeapAtGC
  <br>
  -Xloggc:log/gc.log</p>
</blockquote>
<h3>g1 和 cms 区别,吞吐量优先和响应优先的垃圾收集器选择。</h3>
<p>Cms是以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现。比较占用cpu资源，切易造成碎片。
 <br>
 G1是面向服务端的垃圾收集器，是jdk9默认的收集器，基于标记-整理算法实现。可利用多核、多cpu，保留分代，实现可预测停顿，可控。
 <br>
 <a href="http://blog.csdn.net/linhu007/article/details/48897597">http://blog.csdn.net/linhu007/article/details/48897597</a>
 <br>
 请解释如下 jvm 参数的含义：
 <br>
 -server -Xms512m -Xmx512m -Xss1024K
 <br>
 -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxTenuringThreshold=20
 <br>
 XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly。</p>
<p>Server模式启动
 <br>
 最小堆内存512m
 <br>
 最大512m
 <br>
 每个线程栈空间1m
 <br>
 永久代256
 <br>
 最大永久代256
 <br>
 最大转为老年代检查次数20
 <br>
 Cms回收开启时机：内存占用80%
 <br>
 命令JVM不基于运行时收集的数据来启动CMS垃圾收集周期</p>
<h1>开源框架知识</h1>
<h2>简单讲讲 tomcat 结构，以及其类加载器流程。</h2>
<p>Server- –多个service
 <br>
 Container级别的：–&gt;engine–》host–&gt;context
 <br>
 Listenter
 <br>
 Connector
 <br>
 Logging、Naming、Session、JMX等等
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe329c28089cd0fde9b0909d6ed0fdc54.jpeg&amp;size=m" alt="">
 <br>
 通过WebappClassLoader 加载class
 <br>
 <a href="http://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/">http://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/</a>
 <br>
 <a href="http://blog.csdn.net/dc_726/article/details/11873343">http://blog.csdn.net/dc_726/article/details/11873343</a>
 <br>
 <a href="http://www.cnblogs.com/xing901022/p/4574961.html">http://www.cnblogs.com/xing901022/p/4574961.html</a>
 <br>
 <a href="http://www.jianshu.com/p/62ec977996df">http://www.jianshu.com/p/62ec977996df</a></p>
<blockquote>
 <h3>tomcat 如何调优，涉及哪些参数。</h3>
 <p>硬件上选择，操作系统选择，版本选择，jdk选择，配置jvm参数，配置connector的线程数量，开启gzip压缩，trimSpaces，集群等
  <br>
  <a href="http://blog.csdn.net/lifetragedy/article/details/7708724">http://blog.csdn.net/lifetragedy/article/details/7708724</a></p>
 <h3>讲讲 Spring 加载流程。</h3>
 <p>通过listener入口，核心是在AbstractApplicationContext的refresh方法，在此处进行装载bean工厂，bean，创建bean实例，拦截器，后置处理器等。
  <br>
  <a href="https://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/">https://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/</a></p>
 <h3>讲讲 Spring 事务的传播属性。</h3>
 <p>七种传播属性。
  <br>
  事务传播行为
  <br>
  所谓事务的传播行为是指，如果在开始当前事务之前，一个事务上下文已经存在，此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量：
  <br>
  TransactionDefinition.PROPAGATION_REQUIRED：如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。
  <br>
  TransactionDefinition.PROPAGATION_REQUIRES_NEW：创建一个新的事务，如果当前存在事务，则把当前事务挂起。
  <br>
  TransactionDefinition.PROPAGATION_SUPPORTS：如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。
  <br>
  TransactionDefinition.PROPAGATION_NOT_SUPPORTED：以非事务方式运行，如果当前存在事务，则把当前事务挂起。
  <br>
  TransactionDefinition.PROPAGATION_NEVER：以非事务方式运行，如果当前存在事务，则抛出异常。
  <br>
  TransactionDefinition.PROPAGATION_MANDATORY：如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。
  <br>
  TransactionDefinition.PROPAGATION_NESTED：如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
  <br>
  <a href="https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/">https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/</a></p>
</blockquote>
<h3>Spring 如何管理事务的。</h3>
<p>编程式和声明式
 <br>
 同上</p>
<h3>Spring 怎么配置事务（具体说出一些关键的 xml 元素）。</h3>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p><beans&hellip;> 
  <br>…
 </beans&hellip;></p>
<p>
 <br>
</p>
<p>
 <br>
</p>
<p>
 <tx:advice id="“bankAdvice”" transaction-manager="“transactionManager”">
  <br>
  tx:attributes
  <br>
  <tx:method name="“transfer”" propagation="“REQUIRED”/"> 
   <br> 
   <br>
  </tx:method>
 </tx:advice>
</p>
<p>aop:config
 <br>
 <aop:pointcut id="“bankPointcut”" expression="“execution(*" *.transfer(&hellip;))&ldquo;>
  <br>
  <aop:advisor advice-ref="“bankAdvice”" pointcut-ref="“bankPointcut”/"> 
   <br> 
   <br>…
  </aop:advisor>
 </aop:pointcut></p>
<p>同上</p>
<blockquote>
 <h3>说说你对 Spring 的理解，非单例注入的原理？它的生命周期？循环注入的原理， aop 的实现原理，说说 aop 中的几个术语，它们是怎么相互工作的。</h3>
 <p>核心组件：bean，context，core，单例注入是通过单例beanFactory进行创建，生命周期是在创建的时候通过接口实现开启，循环注入是通过后置处理器，aop其实就是通过反射进行动态代理，pointcut，advice等。
  <br>
  Aop相关：<a href="http://blog.csdn.net/csh624366188/article/details/7651702/">http://blog.csdn.net/csh624366188/article/details/7651702/</a></p>
 <h3>Springmvc 中 DispatcherServlet 初始化过程。</h3>
 <p>入口是web.xml中配置的ds，ds继承了HttpServletBean，FrameworkServlet，通过其中的init方法进行初始化装载bean和实例，initServletBean是实际完成上下文工作和bean初始化的方法。
  <br>
  <a href="http://www.mamicode.com/info-detail-512105.html">http://www.mamicode.com/info-detail-512105.html</a></p>
</blockquote>
<h2>操作系统</h2>
<h3>Linux 系统下你关注过哪些内核参数，说说你知道的。</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4f71cd649573b424468ec62a65978ff5.jpeg&amp;size=m" alt="">
 <br>
 Tcp/ip io cpu memory
 <br>
 net.ipv4.tcp_syncookies = 1</p>
<h1>启用syncookies</h1>
<p>net.ipv4.tcp_max_syn_backlog = 8192</p>
<h1>SYN队列长度</h1>
<p>net.ipv4.tcp_synack_retries=2</p>
<h1>SYN ACK重试次数</h1>
<p>net.ipv4.tcp_fin_timeout = 30</p>
<h1>主动关闭方FIN-WAIT-2超时时间</h1>
<p>net.ipv4.tcp_keepalive_time = 1200</p>
<h1>TCP发送keepalive消息的频度</h1>
<p>net.ipv4.tcp_tw_reuse = 1</p>
<h1>开启TIME-WAIT重用</h1>
<p>net.ipv4.tcp_tw_recycle = 1</p>
<h1>开启TIME-WAIT快速回收</h1>
<p>net.ipv4.ip_local_port_range = 1024 65000</p>
<h1>向外连接的端口范围</h1>
<p>net.ipv4.tcp_max_tw_buckets = 5000</p>
<h1>最大TIME-WAIT数量，超过立即清除</h1>
<p>net.ipv4.tcp_syn_retries = 2</p>
<h1>SYN重试次数</h1>
<p>echo “fs.file-max=65535” &gt;&gt; /etc/sysctl.conf
 <br>
 sysctl -p</p>
<p><a href="http://www.haiyun.me/category/system/">http://www.haiyun.me/category/system/</a></p>
<blockquote>
 <h3>Linux 下 IO 模型有几种，各自的含义是什么。</h3>
 <p>阻塞式io，非阻塞io，io复用模型，信号驱动io模型，异步io模型。
  <br>
  <a href="https://yq.aliyun.com/articles/46404">https://yq.aliyun.com/articles/46404</a>
  <br>
  <a href="https://yq.aliyun.com/articles/46402">https://yq.aliyun.com/articles/46402</a></p>
 <h3>epoll 和 poll 有什么区别。</h3>
 <p>select的本质是采用32个整数的32位，即32<em>32= 1024来标识，fd值为1-1024。当fd的值超过1024限制时，就必须修改FD_SETSIZE的大小。这个时候就可以标识32</em> max值范围的fd。
  <br>
  对于单进程多线程，每个线程处理多个fd的情况，select是不适合的。
  <br>
  1.所有的线程均是从1-32*max进行扫描，每个线程处理的均是一段fd值，这样做有点浪费
  <br>
  2.1024上限问题，一个处理多个用户的进程，fd值远远大于1024
  <br>
  所以这个时候应该采用poll，
  <br>
  poll传递的是数组头指针和该数组的长度，只要数组的长度不是很长，性能还是很不错的，因为poll一次在内核中申请4K（一个页的大小来存放fd），尽量控制在4K以内
  <br>
  epoll还是poll的一种优化，返回后不需要对所有的fd进行遍历，在内核中维持了fd的列表。select和poll是将这个内核列表维持在用户态，然后传递到内核中。但是只有在2.6的内核才支持。
  <br>
  epoll更适合于处理大量的fd ，且活跃fd不是很多的情况，毕竟fd较多还是一个串行的操作
  <br>
  <a href="https://yq.aliyun.com/articles/10525">https://yq.aliyun.com/articles/10525</a></p>
 <h3>平时用到哪些 Linux 命令。</h3>
 <p>Ls,find,tar,tail,cp,rm,vi，grep,ps,pkill等等
  <br>
  <a href="https://yq.aliyun.com/articles/69417?spm=5176.100240.searchblog.18.Zrbh9R">https://yq.aliyun.com/articles/69417?spm=5176.100240.searchblog.18.Zrbh9R</a></p>
 <h3>用一行命令查看文件的最后五行。</h3>
 <p>Tail -n 5 filename</p>
 <h3>用一行命令输出正在运行的 java 进程。</h3>
 <p>ps -ef\|grep Java</p>
 <h3>介绍下你理解的操作系统中线程切换过程。</h3>
 <p>控制权的转换，根据优先级切换上下文（用户，寄存器，系统）
  <br>
  <a href="http://www.cnblogs.com/kkshaq/p/4544426.html">http://www.cnblogs.com/kkshaq/p/4544426.html</a></p>
 <h3>进程和线程的区别。</h3>
 <p>Linux 实现并没有区分这两个概念（进程和线程）</p>
 <ol>
  <li>进程：程序的一次执行</li>
  <li>线程：CPU的基本调度单位
   <br>
   一个进程可以包含多个线程。</li>
 </ol>
 <p><a href="http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html">http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html</a></p>
</blockquote>
<h2>多线程</h2>
<blockquote>
 <h3>多线程的几种实现方式，什么是线程安全。</h3>
 <p>实现runable接口，继承thread类。
  <br>
  <a href="http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/">http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/</a></p>
 <h3>volatile 的原理，作用，能代替锁么。</h3>
 <p>Volatile利用内存栅栏机制来保持变量的一致性。不能代替锁，其只具备数据可见性一致性，不具备原子性。
  <br>
  <a href="http://blog.csdn.net/gongzi2311/article/details/20715185">http://blog.csdn.net/gongzi2311/article/details/20715185</a></p>
</blockquote>
<h3>画一个线程的生命周期状态图。</h3>
<p>新建，可运行，运行中， 睡眠，阻塞，等待，死亡。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5dc5d1cb7fa9d12e0f03f81ca3dcf67a.png&amp;size=m" alt="">
 <br>
 <a href="http://ifeve.com/thread-status">http://ifeve.com/thread-status</a></p>
<blockquote>
 <h3>sleep 和 wait 的区别。</h3>
 <p>Sleep是休眠线程，wait是等待，sleep是thread的静态方法，wait则是object的方法。
  <br>
  Sleep依旧持有锁，并在指定时间自动唤醒。wait则释放锁。
  <br>
  <a href="http://www.jianshu.com/p/4ec3f4b3903d">http://www.jianshu.com/p/4ec3f4b3903d</a></p>
 <h3>Lock 与 Synchronized 的区别。</h3>
 <p>首先两者都保持了并发场景下的原子性和可见性，区别则是synchronized的释放锁机制是交由其自身控制，且互斥性在某些场景下不符合逻辑，无法进行干预，不可人为中断等。
  <br>
  而lock常用的则有ReentrantLock和readwritelock两者，添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外，它还提供了在激烈争用情况下更佳的性能。
  <br>
  <a href="http://blog.csdn.net/vking_wang/article/details/9952063">http://blog.csdn.net/vking_wang/article/details/9952063</a></p>
</blockquote>
<h3>synchronized 的原理是什么，解释以下名词：重排序，自旋锁，偏向锁，轻量级锁，可重入锁，公平锁，非公平锁，乐观锁，悲观锁。</h3>
<p>Synchronized底层是通过监视器的enter和exit实现
 <br>
 <a href="https://my.oschina.net/cnarthurs/blog/847801">https://my.oschina.net/cnarthurs/blog/847801</a>
 <br>
 <a href="http://blog.csdn.net/a314773862/article/details/54095819">http://blog.csdn.net/a314773862/article/details/54095819</a></p>
<h3>用过哪些原子类，他们的原理是什么。</h3>
<p>AtomicInteger； AtomicLong； AtomicReference； AtomicBoolean；基于CAS原语实现 ，比较并交换、加载链接/条件存储，最坏的情况下是旋转锁
 <br>
 <a href="https://www.ibm.com/developerworks/cn/java/j-jtp11234/index.html">https://www.ibm.com/developerworks/cn/java/j-jtp11234/index.html</a>
 <br>
 <a href="http://www.jmatrix.org/java/848.html">http://www.jmatrix.org/java/848.html</a></p>
<h3>用过线程池吗，newCache 和 newFixed 有什么区别，他们的原理简单概括下，构造函数的各个参数的含义是什么，比如 coreSize，maxsize 等。</h3>
<p>newSingleThreadExecutor返回以个包含单线程的Executor,将多个任务交给此Exector时，这个线程处理完一个任务后接着处理下一个任务，若该线程出现异常，将会有一个新的线程来替代。</p>
<p>newFixedThreadPool返回一个包含指定数目线程的线程池，如果任务数量多于线程数目，那么没有没有执行的任务必须等待，直到有任务完成为止。</p>
<p>newCachedThreadPool根据用户的任务数创建相应的线程来处理，该线程池不会对线程数目加以限制，完全依赖于JVM能创建线程的数量，可能引起内存不足。
 <br>
 底层是基于ThreadPoolExecutor实现，借助reentrantlock保证并发。
 <br>
 coreSize核心线程数，maxsize最大线程数。
 <br>
 <a href="http://ifeve.com/java-threadpoolexecutor/">http://ifeve.com/java-threadpoolexecutor/</a></p>
<h3>线程池的关闭方式有几种，各自的区别是什么。</h3>
<p>Shutdown shutdownNow tryTerminate 清空工作队列，终止线程池中各个线程，销毁线程池
 <br>
 <a href="http://blog.csdn.net/xxcupid/article/details/51993235">http://blog.csdn.net/xxcupid/article/details/51993235</a></p>
<h3>假如有一个第三方接口，有很多个线程去调用获取数据，现在规定每秒钟最多有 10 个线程同时调用它，如何做到。</h3>
<p>ScheduledThreadPoolExecutor 设置定时，进行调度。
 <br>
 public ScheduledThreadPoolExecutor(int corePoolSize,
 <br>
 ThreadFactory threadFactory) {</p>
<p>super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
 <br>
 new DelayedWorkQueue(), threadFactory);
 <br>
 }</p>
<p><a href="http://ifeve.com/java-scheduledthreadpoolexecutor/">http://ifeve.com/java-scheduledthreadpoolexecutor/</a></p>
<h3>spring 的 controller 是单例还是多例，怎么保证并发的安全。</h3>
<p>单例
 <br>
 通过单例工厂 DefaultSingletonBeanRegistry实现单例
 <br>
 通过保AsyncTaskExecutor持安全</p>
<h3>用三个线程按顺序循环打印 abc 三个字母，比如 abcabcabc。</h3>
<p>public static void main(String[] args) {</p>
<p>final String str=“abc”;
 <br>
 ExecutorService executorService= Executors.newFixedThreadPool(3);
 <br>
 executorService.execute(new Runnable() {</p>
<p>@Override
 <br>
 public void run() {</p>
<p>System.out.println(“1”+str);
 <br>
 }
 <br>
 });executorService.execute(new Runnable() {</p>
<p>@Override
 <br>
 public void run() {</p>
<p>System.out.println(“2”+str);
 <br>
 }
 <br>
 });executorService.execute(new Runnable() {</p>
<p>@Override
 <br>
 public void run() {</p>
<p>System.out.println(“2”+str);
 <br>
 }
 <br>
 });
 <br>
 }</p>
<h3>ThreadLocal 用过么，用途是什么，原理是什么，用的时候要注意什么。</h3>
<p>Threadlocal底层是通过threadlocalMap进行存储键值 每个ThreadLocal类创建一个Map，然后用线程的ID作为Map的key，实例对象作为Map的value，这样就能达到各个线程的值隔离的效果。
 <br>
 ThreadLocal的作用是提供线程内的局部变量，这种变量在线程的生命周期内起作用，减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
 <br>
 谁设置谁负责移除
 <br>
 <a href="http://qifuguang.me/2015/09/02/%5BJava%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83%5D%E8%A7%A3%E5%AF%86ThreadLocal/">http://qifuguang.me/2015/09/02/[Java并发包学习七]解密ThreadLocal/</a></p>
<h3>如果让你实现一个并发安全的链表，你会怎么做。</h3>
<p>Collections.synchronizedList() ConcurrentLinkedQueue
 <br>
 <a href="http://blog.csdn.net/xingjiarong/article/details/48046751">http://blog.csdn.net/xingjiarong/article/details/48046751</a></p>
<h3>有哪些无锁数据结构，他们实现的原理是什么。</h3>
<p>LockFree，CAS
 <br>
 基于jdk提供的原子类原语实现，例如AtomicReference
 <br>
 <a href="http://blog.csdn.net/b_h_l/article/details/8704480">http://blog.csdn.net/b_h_l/article/details/8704480</a></p>
<h3>讲讲 java 同步机制的 wait 和 notify。</h3>
<p>首先这两个方法只能在同步代码块中调用，wait会释放掉对象锁，等待notify唤醒。
 <br>
 <a href="http://blog.csdn.net/ithomer/article/details/7685594">http://blog.csdn.net/ithomer/article/details/7685594</a></p>
<h3>多线程如果线程挂住了怎么办。</h3>
<p>根据具体情况（sleep,wait,join等），酌情选择notifyAll，notify进行线程唤醒。
 <br>
 <a href="http://blog.chinaunix.net/uid-122937-id-215913.html">http://blog.chinaunix.net/uid-122937-id-215913.html</a></p>
<h3>countdowlatch 和 cyclicbarrier 的内部原理和用法，以及相互之间的差别。</h3>
<p>CountDownLatch是一个同步辅助类，在完成一组正在其他线程中执行的操作之前，它运行一个或者多个线程一直处于等待状态。
 <br>
 CyclicBarrier要做的事情是，让一组线程到达一个屏障（也可以叫同步点）时被阻塞，直到最后一个线程到达屏障时，屏障才会开门，所有被屏障拦截的线程才会继续运行。
 <br>
 CyclicBarrier初始化的时候，设置一个屏障数。线程调用await()方法的时候，这个线程就会被阻塞，当调用await()的线程数量到达屏障数的时候，主线程就会取消所有被阻塞线程的状态。
 <br>
 前者是递减，不可循环，后者是递加，可循环用
 <br>
 countdowlatch 基于abq cb基于ReentrantLock Condition
 <br>
 <a href="http://www.jianshu.com/p/a101ae9797e3">http://www.jianshu.com/p/a101ae9797e3</a>
 <br>
 <a href="http://blog.csdn.net/tolcf/article/details/50925145">http://blog.csdn.net/tolcf/article/details/50925145</a></p>
<h3>使用 synchronized 修饰静态方法和非静态方法有什么区别。</h3>
<p>对象锁和类锁
 <br>
 <a href="https://yq.aliyun.com/articles/24226">https://yq.aliyun.com/articles/24226</a></p>
<h3>简述 ConcurrentLinkedQueue LinkedBlockingQueue 的用处和不同之处。</h3>
<p>LinkedBlockingQueue 是一个基于单向链表的、范围任意的（其实是有界的）、FIFO 阻塞队列。
 <br>
 ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列，它采用先进先出的规则对节点进行排序，当我们添加一个元素的时候，它会添加到队列的尾部，当我们获取一个元素时，它会返回队列头部的元素。它采用了"wait－free"算法来实现，该算法在Michael \&amp; Scott算法上进行了一些修改, Michael \&amp; Scott算法的详细信息可以参见参考资料一。
 <br>
 <a href="http://ifeve.com/concurrentlinkedqueue/">http://ifeve.com/concurrentlinkedqueue/</a>
 <br>
 <a href="http://ifeve.com/juc-linkedblockingqueue/">http://ifeve.com/juc-linkedblockingqueue/</a>
 <br>
 <a href="http://blog.csdn.net/xiaohulunb/article/details/38932923">http://blog.csdn.net/xiaohulunb/article/details/38932923</a></p>
<h2>导致线程死锁的原因？怎么解除线程死锁。</h2>
<p>死锁问题是多线程特有的问题，它可以被认为是线程间切换消耗系统性能的一种极端情况。在死锁时，线程间相互等待资源，而又不释放自身的资源，导致无穷无尽的等待，其结果是系统任务永远无法执行完成。死锁问题是在多线程开发中应该坚决避免和杜绝的问题。
 <br>
 一般来说，要出现死锁问题需要满足以下条件：</p>
<ol>
 <li>互斥条件：一个资源每次只能被一个线程使用。</li>
 <li>请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。</li>
 <li>不剥夺条件：进程已获得的资源，在未使用完之前，不能强行剥夺。</li>
 <li>循环等待条件：若干进程之间形成一种头尾相接的循环等待资源关系。
  <br>
  只要破坏死锁 4 个必要条件之一中的任何一个，死锁问题就能被解决。
  <br>
  <a href="https://www.ibm.com/developerworks/cn/java/j-lo-deadlock/">https://www.ibm.com/developerworks/cn/java/j-lo-deadlock/</a></li>
</ol>
<h3>非常多个线程（可能是不同机器），相互之间需要等待协调，才能完成某种工作，问怎么设计这种协调方案。</h3>
<p>此问题的本质是保持顺序执行。可以使用executors</p>
<h1>TCP 与 HTTP</h1>
<h3>http1.0 和 http1.1 有什么区别。</h3>
<p>HTTP 1.0主要有以下几点变化：
 <br>
 请求和相应可以由于多行首部字段构成
 <br>
 响应对象前面添加了一个响应状态行
 <br>
 响应对象不局限于超文本
 <br>
 服务器与客户端之间的连接在每次请求之后都会关闭
 <br>
 实现了Expires等传输内容的缓存控制
 <br>
 内容编码Accept-Encoding、字符集Accept-Charset等协商内容的支持
 <br>
 这时候开始有了请求及返回首部的概念，开始传输不限于文本（其他二进制内容）</p>
<p>HTTP 1.1加入了很多重要的性能优化：持久连接、分块编码传输、字节范围请求、增强的缓存机制、传输编码及请求管道。
 <br>
 <a href="http://imweb.io/topic/554c5879718ba1240cc1dd8a">http://imweb.io/topic/554c5879718ba1240cc1dd8a</a></p>
<h3>TCP 三次握手和四次挥手的流程，为什么断开连接要 4 次,如果握手只有两次，会出现什么。</h3>
<ul>
 <li>
  <p>第一次握手(SYN=1, seq=x):</p>
  <p>客户端发送一个 TCP 的 SYN 标志位置1的包，指明客户端打算连接的服务器的端口，以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。</p>
  <p>发送完毕后，客户端进入 <code>SYN_SEND</code> 状态。</p>
 </li>
 <li>
  <p>第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):</p>
  <p>服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号，放到 Seq 域里，同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1，即X+1。
   <br>
   发送完毕后，服务器端进入 <code>SYN_RCVD</code> 状态。</p>
 </li>
 <li>
  <p>第三次握手(ACK=1，ACKnum=y+1)</p>
  <p>客户端再次发送确认包(ACK)，SYN 标志位为0，ACK 标志位为1，并且把服务器发来 ACK 的序号字段+1，放在确定字段中发送给对方，并且在数据段放写ISN的+1</p>
 </li>
</ul>
<p>发送完毕后，客户端进入 <code>ESTABLISHED</code> 状态，当服务器端接收到这个包时，也进入 <code>ESTABLISHED</code> 状态，TCP 握手结束。</p>
<p>第一次挥手(FIN=1，seq=x)</p>
<p>假设客户端想要关闭连接，客户端发送一个 FIN 标志位置为1的包，表示自己已经没有数据可以发送了，但是仍然可以接受数据。</p>
<p>发送完毕后，客户端进入 FIN_WAIT_1 状态。</p>
<p>第二次挥手(ACK=1，ACKnum=x+1)</p>
<p>服务器端确认客户端的 FIN 包，发送一个确认包，表明自己接受到了客户端关闭连接的请求，但还没有准备好关闭连接。</p>
<p>发送完毕后，服务器端进入 CLOSE_WAIT 状态，客户端接收到这个确认包之后，进入 FIN_WAIT_2 状态，等待服务器端关闭连接。</p>
<p>第三次挥手(FIN=1，seq=y)</p>
<p>服务器端准备好关闭连接时，向客户端发送结束连接请求，FIN 置为1。</p>
<p>发送完毕后，服务器端进入 LAST_ACK 状态，等待来自客户端的最后一个ACK。</p>
<p>第四次挥手(ACK=1，ACKnum=y+1)</p>
<p>客户端接收到来自服务器端的关闭请求，发送一个确认包，并进入 TIME_WAIT状态，等待可能出现的要求重传的 ACK 包。</p>
<p>服务器端接收到这个确认包之后，关闭连接，进入 CLOSED 状态。</p>
<p>客户端等待了某个固定时间（两个最大段生命周期，2MSL，2 Maximum Segment Lifetime）之后，没有收到服务器端的 ACK ，认为服务器端已经正常关闭连接，于是自己也关闭连接，进入 CLOSED 状态。
 <br>
 两次后会重传直到超时。如果多了会有大量半链接阻塞队列。
 <br>
 <a href="https://segmentfault.com/a/1190000006885287">https://segmentfault.com/a/1190000006885287</a>
 <br>
 <a href="https://hit-alibaba.github.io/interview/basic/network/TCP.html">https://hit-alibaba.github.io/interview/basic/network/TCP.html</a></p>
<h3>TIME_WAIT 和 CLOSE_WAIT 的区别。</h3>
<p>TIME_WAIT状态就是用来重发可能丢失的ACK报文。
 <br>
 TIME_WAIT 表示主动关闭，CLOSE_WAIT 表示被动关闭。</p>
<h3>说说你知道的几种 HTTP 响应码，比如 200, 302, 404。</h3>
<p>1xx：信息，请求收到，继续处理
 <br>
 2xx：成功，行为被成功地接受、理解和采纳
 <br>
 3xx：重定向，为了完成请求，必须进一步执行的动作
 <br>
 4xx：客户端错误，请求包含语法错误或者请求无法实现
 <br>
 5xx：服务器错误，服务器不能实现一种明显无效的请求
 <br>
 200 ok 一切正常
 <br>
 302 Moved Temporatily 文件临时移出
 <br>
 404 not found
 <br>
 <a href="https://my.oschina.net/gavinjin/blog/42856">https://my.oschina.net/gavinjin/blog/42856</a></p>
<h3>当你用浏览器打开一个链接的时候，计算机做了哪些工作步骤。</h3>
<p>Dns解析–&gt;端口分析–&gt;tcp请求–&gt;服务器处理请求–&gt;服务器响应–&gt;浏览器解析—&gt;链接关闭</p>
<h3>TCP/IP 如何保证可靠性，说说 TCP 头的结构。</h3>
<p>使用序号，对收到的TCP报文段进行排序以及检测重复的数据；使用校验和来检测报文段的错误；使用确认和计时器来检测和纠正丢包或延时。//TCP头部，总长度20字节
 <br>
 typedef struct _tcp_hdr
 <br>
 {</p>
<p>unsigned short src_port; //源端口号
 <br>
 unsigned short dst_port; //目的端口号
 <br>
 unsigned int seq_no; //序列号
 <br>
 unsigned int ack_no; //确认号</p>
<h1>if LITTLE_ENDIAN</h1>
<p>unsigned char reserved_1:4; //保留6位中的4位首部长度
 <br>
 unsigned char thl:4; //tcp头部长度
 <br>
 unsigned char flag:6; //6位标志
 <br>
 unsigned char reseverd_2:2; //保留6位中的2位</p>
<h1>else</h1>
<p>unsigned char thl:4; //tcp头部长度
 <br>
 unsigned char reserved_1:4; //保留6位中的4位首部长度
 <br>
 unsigned char reseverd_2:2; //保留6位中的2位
 <br>
 unsigned char flag:6; //6位标志</p>
<h1>endif</h1>
<p>unsigned short wnd_size; //16位窗口大小
 <br>
 unsigned short chk_sum; //16位TCP检验和
 <br>
 unsigned short urgt_p; //16为紧急指针
 <br>
 }tcp_hdr;</p>
<p><a href="https://zh.bywiki.com/zh-hans/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE">https://zh.bywiki.com/zh-hans/传输控制协议</a></p>
<h3>如何避免浏览器缓存。</h3>
<p>无法被浏览器缓存的请求：
 <br>
 HTTP信息头中包含Cache-Control:no-cache，pragma:no-cache，或Cache-Control:max-age=0等告诉浏览器不用缓存的请求
 <br>
 需要根据Cookie，认证信息等决定输入内容的动态请求是不能被缓存的
 <br>
 经过HTTPS安全加密的请求（有人也经过测试发现，ie其实在头部加入Cache-Control：max-age信息，firefox在头部加入Cache-Control:Public之后，能够对HTTPS的资源进行缓存，参考《HTTPS的七个误解》）
 <br>
 POST请求无法被缓存
 <br>
 HTTP响应头中不包含Last-Modified/Etag，也不包含Cache-Control/Expires的请求无法被缓存
 <br>
 <a href="http://www.alloyteam.com/2012/03/web-cache-2-browser-cache/">http://www.alloyteam.com/2012/03/web-cache-2-browser-cache/</a></p>
<h3>简述 Http 请求 get 和 post 的区别以及数据包格式。</h3>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F507f0ac0daa97ea8549feae8a2a668f4.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa3cad8828d529d643e6798a9a25f6d6e.jpeg&amp;size=m" alt="">
 <br>
 <a href="http://www.w3school.com.cn/tags/html_ref_httpmethods.asp">http://www.w3school.com.cn/tags/html_ref_httpmethods.asp</a>
 <br>
 <a href="http://www.360doc.com/content/12/0612/14/8093902_217673378.shtml">http://www.360doc.com/content/12/0612/14/8093902_217673378.shtml</a></p>
<h3>简述 HTTP 请求的报文格式。</h3>
<p>参考上面</p>
<h3>HTTPS 的加密方式是什么，讲讲整个加密解密流程。</h3>
<p>加密方式是tls/ssl，底层是通过对称算法，非对称，hash算法实现
 <br>
 客户端发起HTTPS请求 –》2. 服务端的配置 –》</p>
<ol>
 <li>传送证书 —》4. 客户端解析证书 5. 传送加密信息 6. 服务段解密信息 7. 传输加密后的信息 8. 客户端解密信息
  <br>
  <a href="http://www.cnblogs.com/zhuqil/archive/2012/07/23/2604572.html">http://www.cnblogs.com/zhuqil/archive/2012/07/23/2604572.html</a>
  <h1>架构设计与分布式</h1></li>
</ol>
<h3>常见的缓存策略有哪些，你们项目中用到了什么缓存系统，如何设计的。</h3>
<p>Cdn缓存，redis缓存，ehcache缓存等
 <br>
 Cdn 图片资源 js等， redis一主一从 echcache缓存数据</p>
<h3>用 java 自己实现一个 LRU。</h3>
<p>final int cacheSize = 100;
 <br>
 Map
 <string, string>
  map = new LinkedHashMap<string, string>((int) Math.ceil(cacheSize / 0.75f) + 1, 0.75f, true) {</string,>
 </string,></p>
<p>@Override
 <br>
 protected boolean removeEldestEntry(Map.Entry<string, string> eldest) {</string,></p>
<p>return size() &gt; cacheSize;
 <br>
 }
 <br>
 };
 <br>
 <a href="http://www.cnblogs.com/lzrabbit/p/3734850.html">http://www.cnblogs.com/lzrabbit/p/3734850.html</a></p>
<h3>分布式集群下如何做到唯一序列号。</h3>
<p>Redis生成，mongodb的objectId，zk生成
 <br>
 <a href="http://www.cnblogs.com/haoxinyue/p/5208136.html">http://www.cnblogs.com/haoxinyue/p/5208136.html</a></p>
<h3>设计一个秒杀系统，30 分钟没付款就自动关闭交易。</h3>
<p>分流 – 限流–异步–公平性（只能参加一次）–用户体验（第几位，多少分钟，一抢完）
 <br>
 容错处理
 <br>
 Redis 队列 mysql</p>
<p>30分钟关闭 可以借助redis的发布订阅机制 在失效时进行后续操作，其他mq也可以
 <br>
 <a href="http://www.infoq.com/cn/articles/yhd-11-11-queuing-system-design">http://www.infoq.com/cn/articles/yhd-11-11-queuing-system-design</a></p>
<h3>如何使用 redis 和 zookeeper 实现分布式锁？有什么区别优缺点，分别适用什么场景。</h3>
<p>首先分布式锁实现常见的有数据库锁(表记录)，缓存锁，基于zk（临时有序节点可以实现的）的三种</p>
<p>Redis适用于对性能要求特别高的场景。redis可以每秒执行10w次，内网延迟不超过1ms
 <br>
 缺点是数据存放于内存，宕机后锁丢失。</p>
<p>锁无法释放？使用Zookeeper可以有效的解决锁无法释放的问题，因为在创建锁的时候，客户端会在ZK中创建一个临时节点，一旦客户端获取到锁之后突然挂掉（Session连接断开），那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。</p>
<p>非阻塞锁？使用Zookeeper可以实现阻塞的锁，客户端可以通过在ZK中创建顺序节点，并且在节点上绑定监听器，一旦节点有变化，Zookeeper会通知客户端，客户端可以检查自己创建的节点是不是当前所有节点中序号最小的，如果是，那么自己就获取到锁，便可以执行业务逻辑了。</p>
<p>不可重入？使用Zookeeper也可以有效的解决不可重入的问题，客户端在创建节点的时候，把当前客户端的主机信息和线程信息直接写入到节点中，下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样，那么自己直接获取到锁，如果不一样就再创建一个临时的顺序节点，参与排队。</p>
<p>单点问题？使用Zookeeper可以有效的解决单点问题，ZK是集群部署的，只要集群中有半数以上的机器存活，就可以对外提供服务。</p>
<p><a href="http://www.hollischuang.com/archives/1716">http://www.hollischuang.com/archives/1716</a></p>
<h3>如果有人恶意创建非法连接，怎么解决。</h3>
<p>可以使用filter过滤处理</p>
<h3>分布式事务的原理，优缺点，如何使用分布式事务。</h3>
<p>Two Phase commit协议
 <br>
 优点是可以管理多机事务，拥有无线扩展性 确定是易用性难，承担延时风险
 <br>
 JTA，atomiks等
 <br>
 <a href="https://yq.aliyun.com/webinar/join/185?spm=5176.8067841.0.0.RL4GDa">https://yq.aliyun.com/webinar/join/185?spm=5176.8067841.0.0.RL4GDa</a></p>
<h3>什么是一致性 hash。</h3>
<p>一致性hash是一种分布式hash实现算法。满足平衡性 单调性 分散性 和负载。
 <br>
 <a href="http://blog.csdn.net/cywosp/article/details/23397179/">http://blog.csdn.net/cywosp/article/details/23397179/</a></p>
<h3>什么是 restful，讲讲你理解的 restful。</h3>
<p>REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
 <br>
 <a href="http://baike.baidu.com/link?url=fTSAdL-EyYvTp9z7mZsCOdS3kbs4VKKAnpBLg3WS_1Z4cmLMp3S-zrjcy5wakLTO5AIoPTopWVkG-IenloPKxq">http://baike.baidu.com/link?url=fTSAdL-EyYvTp9z7mZsCOdS3kbs4VKKAnpBLg3WS_1Z4cmLMp3S-zrjcy5wakLTO5AIoPTopWVkG-IenloPKxq</a></p>
<h3>如何设计建立和保持 100w 的长连接。</h3>
<p>服务器内核调优(tcp，文件数)，客户端调优，框架选择(netty)</p>
<h3>如何防止缓存雪崩。</h3>
<p>缓存雪崩可能是因为数据未加载到缓存中，或者缓存同一时间大面积的失效，从而导致所有请求都去查数据库，导致数据库CPU和内存负载过高，甚至宕机。
 <br>
 解决思路：
 <br>
 1，采用加锁计数，或者使用合理的队列数量来避免缓存失效时对数据库造成太大的压力。这种办法虽然能缓解数据库的压力，但是同时又降低了系统的吞吐量。
 <br>
 2，分析用户行为，尽量让失效时间点均匀分布。避免缓存雪崩的出现。
 <br>
 3，如果是因为某台缓存服务器宕机，可以考虑做主备，比如：redis主备，但是双缓存涉及到更新事务的问题，update可能读到脏数据，需要好好解决。</p>
<p><a href="http://www.cnblogs.com/jinjiangongzuoshi/archive/2016/03/03/5240280.html">http://www.cnblogs.com/jinjiangongzuoshi/archive/2016/03/03/5240280.html</a></p>
<h3>解释什么是 MESI 协议(缓存一致性)。</h3>
<p>MESI是四种缓存段状态的首字母缩写，任何多核系统中的缓存段都处于这四种状态之一。我将以相反的顺序逐个讲解，因为这个顺序更合理：</p>
<p>失效（Invalid）缓存段，要么已经不在缓存中，要么它的内容已经过时。为了达到缓存的目的，这种状态的段将会被忽略。一旦缓存段被标记为失效，那效果就等同于它从来没被加载到缓存中。
 <br>
 共享（Shared）缓存段，它是和主内存内容保持一致的一份拷贝，在这种状态下的缓存段只能被读取，不能被写入。多组缓存可以同时拥有针对同一内存地址的共享缓存段，这就是名称的由来。
 <br>
 独占（Exclusive）缓存段，和S状态一样，也是和主内存内容保持一致的一份拷贝。区别在于，如果一个处理器持有了某个E状态的缓存段，那其他处理器就不能同时持有它，所以叫"独占”。这意味着，如果其他处理器原本也持有同一缓存段，那么它会马上变成"失效"状态。
 <br>
 已修改（Modified）缓存段，属于脏段，它们已经被所属的处理器修改了。如果一个段处于已修改状态，那么它在其他处理器缓存中的拷贝马上会变成失效状态，这个规律和E状态一样。此外，已修改缓存段如果被丢弃或标记为失效，那么先要把它的内容回写到内存中——这和回写模式下常规的脏段处理方式一样。</p>
<h3>说说你知道的几种 HASH 算法，简单的也可以。</h3>
<p>哈希(Hash)算法,即散列函数。 它是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。 同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出
 <br>
 MD4 MD5 SHA
 <br>
 <a href="http://blog.jobbole.com/106733/">http://blog.jobbole.com/106733/</a></p>
<h3>什么是 paxos 算法。</h3>
<p>Paxos算法是莱斯利·兰伯特（Leslie Lamport，就是 LaTeX 中的"La”，此人现在在微软研究院）于1990年提出的一种基于消息传递的一致性算法。</p>
<p><a href="http://baike.baidu.com/item/Paxos%20%E7%AE%97%E6%B3%95">http://baike.baidu.com/item/Paxos 算法</a></p>
<h2>什么是 zab 协议。</h2>
<p>ZAB 是 Zookeeper 原子广播协议的简称</p>
<p>整个ZAB协议主要包括消息广播和崩溃恢复两个过程，进一步可以分为三个阶段，分别是：</p>
<p>发现 Discovery
 <br>
 同步 Synchronization
 <br>
 广播 Broadcast
 <br>
 组成ZAB协议的每一个分布式进程，都会循环执行这三个阶段，将这样一个循环称为一个主进程周期。
 <br>
 <a href="https://zzzvvvxxxd.github.io/2016/08/09/ZAB/">https://zzzvvvxxxd.github.io/2016/08/09/ZAB/</a></p>
<h2>一个在线文档系统，文档可以被编辑，如何防止多人同时对同一份文档进行编辑更新。</h2>
<p>点击编辑的时候，利用redis进行加锁setNX完了之后 expire 一下
 <br>
 也可以用版本号进行控制</p>
<h3>线上系统突然变得异常缓慢，你如何查找问题。</h3>
<p>逐级排查（网络，磁盘，内存，cpu），数据库，日志，中间件等也可通过监控工具排查。</p>
<h3>说说你平时用到的设计模式。</h3>
<p>单例， 代理，模板，策略，命令
 <br>
 <a href="http://www.jianshu.com/p/bdf65e4afbb0">http://www.jianshu.com/p/bdf65e4afbb0</a></p>
<h3>Dubbo 的原理，数据怎么流转的，怎么实现集群，负载均衡，服务注册和发现。重试转发，快速失败的策略是怎样的。</h3>
<p>Dubbo[]是一个分布式服务框架，致力于提供高性能和透明化的RPC远程服务调用方案，以及SOA服务治理方案。</p>
<h2>Cluster 实现集群</h2>
<p>在集群负载均衡时，Dubbo提供了多种均衡策略，缺省为random随机调用。
 <br>
 Random LoadBalance：随机，按权重比率设置随机概率。
 <br>
 RoundRobin LoadBalance：轮循，按公约后的权重比率设置轮循比率。
 <br>
 LeastActive LoadBalance：最少活跃调用数，相同活跃数的随机，活跃数指调用前后计数差。使慢的提供者收到更少请求，因为越慢的提供者的调用前后计数差会越大。
 <br>
 ConsistentHash LoadBalance：一致性Hash，相同参数的请求总是发到同一提供者。当某一台提供者挂时，原本发往该提供者的请求，基于虚拟节点，平摊到其它提供者，不会引起剧烈变动。
 <br>
 快速失败，只发起一次调用，失败立即报错。</p>
<p><a href="https://my.oschina.net/u/1378920/blog/693374">https://my.oschina.net/u/1378920/blog/693374</a></p>
<h3>一次 RPC 请求的流程是什么。</h3>
<p>1）服务消费方（client）调用以本地调用方式调用服务；
 <br>
 2）client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体；
 <br>
 3）client stub找到服务地址，并将消息发送到服务端；
 <br>
 4）server stub收到消息后进行解码；
 <br>
 5）server stub根据解码结果调用本地的服务；
 <br>
 6）本地服务执行并将结果返回给server stub；
 <br>
 7）server stub将返回结果打包成消息并发送至消费方；
 <br>
 8）client stub接收到消息，并进行解码；
 <br>
 9）服务消费方得到最终结果。</p>
<h3>异步模式的用途和意义。</h3>
<p>异步模式使用与服务器多核，并发严重的场景
 <br>
 可提高服务吞吐量大，不容易受到冲击，可以采用并发策略，提高响应时间
 <br>
 缓存数据过期后的更新如何设计。
 <br>
 失效：应用程序先从cache取数据，没有得到，则从数据库中取数据，成功后，放到缓存中。
 <br>
 命中：应用程序从cache中取数据，取到后返回。
 <br>
 更新：先把数据存到数据库中，成功后，再让缓存失效。</p>
<h3>编程中自己都怎么考虑一些设计原则的，比如开闭原则，以及在工作中的应用。</h3>
<p>开闭原则（Open Close Principle）
 <br>
 一个软件实体如类、模块和函数应该对扩展开放，对修改关闭。
 <br>
 里氏代换原则（Liskov Substitution Principle）
 <br>
 子类型必须能够替换掉它们的父类型。
 <br>
 依赖倒转原则（Dependence Inversion Principle）
 <br>
 高层模块不应该依赖低层模块，二者都应该依赖其抽象；抽象不应该依赖细节；细节应该依赖抽象。即针对接口编程，不要针对实现编程
 <br>
 接口隔离原则（Interface Segregation Principle）
 <br>
 建立单一接口，不要建立庞大臃肿的接口，尽量细化接口，接口中的方法尽量少
 <br>
 组合/聚合复用原则
 <br>
 说要尽量的使用合成和聚合，而不是继承关系达到复用的目的
 <br>
 迪米特法则（Law Of Demeter）
 <br>
 迪米特法则其根本思想，是强调了类之间的松耦合，类之间的耦合越弱,越有利于复用，一个处在弱耦合的类被修改，不会对有关系的类造成影响，也就是说，信息的隐藏促进了软件的复用。
 <br>
 单一职责原则（Single Responsibility Principle）
 <br>
 一个类只负责一项职责，应该仅有一个引起它变化的原因
 <br>
 <a href="http://www.banzg.com/archives/225.html">http://www.banzg.com/archives/225.html</a></p>
<h3>设计一个社交网站中的"私信"功能，要求高并发、可扩展等等。 画一下架构图。</h3>
<p>MVC 模式，即常见的 MVC 框架。
 <br>
 SSM SSH SSI等</p>
<h3>聊了下曾经参与设计的服务器架构。</h3>
<h3>应用服务器怎么监控性能，各种方式的区别。</h3>
<h3>如何设计一套高并发支付方案，架构如何设计。</h3>
<h3>如何实现负载均衡，有哪些算法可以实现。</h3>
<h3>Zookeeper 的用途，选举的原理是什么。</h3>
<h3>Mybatis 的底层实现原理。</h3>
<h3>请思考一个方案，设计一个可以控制缓存总体大小的自动适应的本地缓存。</h3>
<h2>请思考一个方案，实现分布式环境下的 countDownLatch。</h2>
<h3>后台系统怎么防止请求重复提交。</h3>
<p>可以通过token值进行防止重复提交，存放到redis中，在表单初始化的时候隐藏在表单中，添加的时候在移除。判断这个状态即可防止重复提交。
 <br>
 如何看待缓存的使用（本地缓存，集中式缓存），简述本地缓存和集中式缓存和优缺点。本地缓存在并发使用时的注意事项。</p>
<h3>描述一个服务从发布到被消费的详细过程。</h3>
<h2>讲讲你理解的服务治理。</h2>
<h3>如何做到接口的幂等性。</h3>
<h1>算法</h1>
<h3>10 亿个数字里里面找最小的 10 个。</h3>
<h2>有 1 亿个数字，其中有 2 个是重复的，快速找到它，时间和空间要最优。</h2>
<h3>2 亿个随机生成的无序整数,找出中间大小的值。</h3>
<h3>给一个不知道长度的（可能很大）输入字符串，设计一种方案，将重复的字符排重。</h3>
<h3>遍历二叉树。</h3>
<h3>有 3n+1 个数字，其中 3n 个中是重复的，只有 1 个是不重复的，怎么找出来。</h3>
<h3>写一个字符串反转函数。</h3>
<h2>常用的排序算法，快排，归并、冒泡。 快排的最优时间复杂度，最差复杂度。冒泡排序的优化方案。</h2>
<h2>二分查找的时间复杂度，优势。</h2>
<h2>一个已经构建好的 TreeSet，怎么完成倒排序。</h2>
<h3>什么是 B+树，B-树，列出实际的使用场景。</h3>
<h2>数据库知识</h2>
<h2>数据库隔离级别有哪些，各自的含义是什么，MYSQL 默认的隔离级别是是什么。</h2>
<p>·未提交读(Read Uncommitted)：允许脏读，也就是可能读取到其他会话中未提交事务修改的数据</p>
<p>·提交读(Read Committed)：只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)</p>
<p>·可重复读(Repeated Read)：可重复读。在同一个事务内的查询都是事务开始时刻一致的，InnoDB默认级别。在SQL标准中，该隔离级别消除了不可重复读，但是还存在幻象读</p>
<p>·串行读(Serializable)：完全串行化的读，每次读都需要获得表级共享锁，读写相互都会阻塞</p>
<p>MYSQL默认是RepeatedRead级别</p>
<h3>MYSQL 有哪些存储引擎，各自优缺点。</h3>
<p>MyISAM： 拥有较高的插入，查询速度，但不支持事务
 <br>
 InnoDB ：5.5版本后Mysql的默认数据库，事务型数据库的首选引擎，支持ACID事务，支持行级锁定
 <br>
 BDB： 源自Berkeley DB，事务型数据库的另一种选择，支持COMMIT和ROLLBACK等其他事务特性
 <br>
 Memory ：所有数据置于内存的存储引擎，拥有极高的插入，更新和查询效率。但是会占用和数据量成正比的内存空间。并且其内容会在Mysql重新启动时丢失
 <br>
 Merge ：将一定数量的MyISAM表联合而成一个整体，在超大规模数据存储时很有用
 <br>
 Archive ：非常适合存储大量的独立的，作为历史记录的数据。因为它们不经常被读取。Archive拥有高效的插入速度，但其对查询的支持相对较差
 <br>
 Federated： 将不同的Mysql服务器联合起来，逻辑上组成一个完整的数据库。非常适合分布式应用
 <br>
 Cluster/NDB ：高冗余的存储引擎，用多台数据机器联合提供服务以提高整体性能和安全性。适合数据量大，安全和性能要求高的应用
 <br>
 CSV： 逻辑上由逗号分割数据的存储引擎。它会在数据库子目录里为每个数据表创建一个.CSV文件。这是一种普通文本文件，每个数据行占用一个文本行。CSV存储引擎不支持索引。
 <br>
 BlackHole ：黑洞引擎，写入的任何数据都会消失，一般用于记录binlog做复制的中继
 <br>
 另外，Mysql的存储引擎接口定义良好。有兴趣的开发者通过阅读文档编写自己的存储引擎。
 <br>
 <a href="http://baike.baidu.com/item/%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E">http://baike.baidu.com/item/存储引擎</a></p>
<h3>高并发下，如何做到安全的修改同一行数据。</h3>
<p>使用悲观锁 悲观锁本质是当前只有一个线程执行操作，结束了唤醒其他线程进行处理。
 <br>
 也可以缓存队列中锁定主键。</p>
<h3>乐观锁和悲观锁是什么，INNODB 的行级锁有哪 2 种，解释其含义。</h3>
<p>乐观锁是设定每次修改都不会冲突，只在提交的时候去检查，悲观锁设定每次修改都会冲突，持有排他锁。
 <br>
 行级锁分为共享锁和排他锁两种 共享锁又称读锁 排他锁又称写锁
 <br>
 <a href="http://www.jianshu.com/p/f40ec03fd0e8">http://www.jianshu.com/p/f40ec03fd0e8</a></p>
<h3>SQL 优化的一般步骤是什么，怎么看执行计划，如何理解其中各个字段的含义。</h3>
<p>查看慢日志（show [session\|gobal] status ），定位慢查询，查看慢查询执行计划 根据执行计划确认优化方案
 <br>
 Explain sql
 <br>
 select_type:表示select类型。常见的取值有SIMPLE（简单表，即不使用连接或者子查询）、PRIMARY（主查询，即外层的查询）、UNION（union中的第二个或者后面的查询语句）、SUBQUERY（子查询中的第一个SELECT）等。
 <br>
 talbe：输出结果集的表。
 <br>
 type:表的连接类型。性能由高到底：system（表中仅有一行）、const（表中最多有一个匹配行）、eq_ref、ref、ref_null、index_merge、unique_subquery、index_subquery、range、idnex等
 <br>
 possible_keys:查询时，可能使用的索引
 <br>
 key:实际使用的索引
 <br>
 key_len:索引字段的长度
 <br>
 rows：扫描行的数量
 <br>
 Extra：执行情况的说明和描述
 <br>
 <a href="http://blog.csdn.net/hsd2012/article/details/51106285">http://blog.csdn.net/hsd2012/article/details/51106285</a></p>
<h3>数据库会死锁吗，举一个死锁的例子，mysql 怎么解决死锁。</h3>
<p>产生死锁的原因主要是：</p>
<p>（1）系统资源不足。
 <br>
 （2） 进程运行推进的顺序不合适。
 <br>
 （3）资源分配不当等。</p>
<p>如果系统资源充足，进程的资源请求都能够得到满足，死锁出现的可能性就很低，否则就会因争夺有限的资源而陷入死锁。其次，进程运行推进顺序与速度不同，也可能产生死锁。</p>
<p>产生死锁的四个必要条件：</p>
<p>（1） 互斥条件：一个资源每次只能被一个进程使用。
 <br>
 （2） 请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。
 <br>
 （3） 不剥夺条件:进程已获得的资源，在末使用完之前，不能强行剥夺。
 <br>
 （4） 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。</p>
<p>这四个条件是死锁的必要条件，只要系统发生死锁，这些条件必然成立，而只要上述条件之一不满足，就不会发生死锁。
 <br>
 这里提供两个解决数据库死锁的方法：</p>
<p>1）重启数据库（谁用谁知道）
 <br>
 2）杀掉抢资源的进程：
 <br>
 先查哪些进程在抢资源：SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
 <br>
 杀掉它们：Kill trx_mysql_thread_id；</p>
<h3>MYsql 的索引原理，索引的类型有哪些，如何创建合理的索引，索引如何优化。</h3>
<p>索引是通过复杂的算法，提高数据查询性能的手段。从磁盘io到内存io的转变
 <br>
 普通索引，主键，唯一，单列/多列索引建索引的几大原则
 <br>
 1.最左前缀匹配原则，非常重要的原则，mysql会一直向右匹配直到遇到范围查询(&gt;、&lt;、between、like)就停止匹配，比如a = 1 and b = 2 and c &gt; 3 and d = 4 如果建立(a,b,c,d)顺序的索引，d是用不到索引的，如果建立(a,b,d,c)的索引则都可以用到，a,b,d的顺序可以任意调整。
 <br>
 2.=和in可以乱序，比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序，mysql的查询优化器会帮你优化成索引可以识别的形式
 <br>
 3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*)，表示字段不重复的比例，比例越大我们扫描的记录数越少，唯一键的区分度是1，而一些状态、性别字段可能在大数据面前区分度就是0，那可能有人会问，这个比例有什么经验值吗？使用场景不同，这个值也很难确定，一般需要join的字段我们都要求是0.1以上，即平均1条扫描10条记录
 <br>
 4.索引列不能参与计算，保持列"干净”，比如from_unixtime(create_time) = '2014-05-29'就不能使用到索引，原因很简单，b+树中存的都是数据表中的字段值，但进行检索时，需要把所有元素都应用函数才能比较，显然成本太大。所以语句应该写成create_time = unix_timestamp('2014-05-29');
 <br>
 5.尽量的扩展索引，不要新建索引。比如表中已经有a的索引，现在要加(a,b)的索引，那么只需要修改原来的索引即可</p>
<p><a href="http://tech.meituan.com/mysql-index.html">http://tech.meituan.com/mysql-index.html</a>
 <br>
 <a href="http://www.cnblogs.com/cq-home/p/3482101.html">http://www.cnblogs.com/cq-home/p/3482101.html</a></p>
<h2>聚集索引和非聚集索引的区别。</h2>
<p>“聚簇"就是索引和记录紧密在一起。
 <br>
 非聚簇索引 索引文件和数据文件分开存放，索引文件的叶子页只保存了主键值，要定位记录还要去查找相应的数据块。</p>
<h3>数据库中 BTREE 和 B+tree 区别。</h3>
<p>B+是btree的变种，本质都是btree，btree+与B-Tree相比，B+Tree有以下不同点：
 <br>
 每个节点的指针上限为2d而不是2d+1。
 <br>
 内节点不存储data，只存储key；叶子节点不存储指针。</p>
<p><a href="http://lcbk.net/9602.html">http://lcbk.net/9602.html</a>
 <br>
 Btree 怎么分裂的，什么时候分裂，为什么是平衡的。
 <br>
 Key 超过1024才分裂B树为甚会分裂？ 因为随着数据的增多，一个结点的key满了，为了保持B树的特性，就会产生分裂，就向红黑树和AVL树为了保持树的性质需要进行旋转一样！</p>
<h3>ACID 是什么。</h3>
<p>A，atomic，原子性，要么都提交，要么都失败，不能一部分成功，一部分失败。
 <br>
 C，consistent，一致性，事物开始及结束后，数据的一致性约束没有被破坏
 <br>
 I，isolation，隔离性，并发事物间相互不影响，互不干扰。
 <br>
 D，durability,持久性，已经提交的事物对数据库所做的更新必须永久保存。即便发生崩溃，也不能被回滚或数据丢失。</p>
<h2>Mysql 怎么优化 table scan 的。</h2>
<p>避免在where子句中对字段进行is null判断
 <br>
 应尽量避免在where 子句中使用!=或&lt;&gt;操作符，否则将引擎放弃使用索引而进行全表扫描。
 <br>
 避免在where 子句中使用or 来连接条件
 <br>
 in 和not in 也要慎用
 <br>
 Like查询（非左开头）
 <br>
 使用NUM=@num参数这种
 <br>
 where 子句中对字段进行表达式操作num/2=XX
 <br>
 在where子句中对字段进行函数操作</p>
<h3>如何写 sql 能够有效的使用到复合索引。</h3>
<p>由于复合索引的组合索引，类似多个木板拼接在一起，如果中间断了就无法用了，所以要能用到复合索引，首先开头(第一列)要用上，比如index(a,b) 这种，我们可以select table tname where a=XX 用到第一列索引 如果想用第二列 可以 and b=XX 或者and b like'TTT%'</p>
<h3>mysql 中 in 和 exists 区别。</h3>
<p>mysql中的in语句是把外表和内表作hash 连接，而exists语句是对外表作loop循环，每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高，这种说法其实是不准确的。这个是要区分环境的。</p>
<p>如果查询的两个表大小相当，那么用in和exists差别不大。
 <br>
 如果两个表中一个较小，一个是大表，则子查询表大的用exists，子查询表小的用in：
 <br>
 not in 和not exists如果查询语句使用了not in 那么内外表都进行全表扫描，没有用到索引；而not extsts 的子查询依然能用到表上的索引。所以无论那个表大，用not exists都比not in要快。
 <br>
 1.EXISTS只返回TRUE或FALSE，不会返回UNKNOWN。</p>
<p>2.IN当遇到包含NULL的情况，那么就会返回UNKNOWN。</p>
<h3>数据库自增主键可能的问题。</h3>
<p>在分库分表时可能会生成重复主键 利用自增比例达到唯一 自增1 2,3 等
 <br>
 <a href="https://yq.aliyun.com/articles/38438">https://yq.aliyun.com/articles/38438</a></p>
<h1>消息队列</h1>
<h2>用过哪些 MQ，和其他 mq 比较有什么优缺点，MQ 的连接是线程安全的吗，你们公司的MQ 服务架构怎样的。</h2>
<p>根据实际情况说明
 <br>
 我们公司用activeMQ 因为业务比较简单 只有转码功能，而amq比较简单
 <br>
 如果是分布式的建议用kafka
 <br>
 <a href="http://blog.csdn.net/sunxinhere/article/details/7968886">http://blog.csdn.net/sunxinhere/article/details/7968886</a></p>
<h3>MQ 系统的数据如何保证不丢失。</h3>
<p>基本都是对数据进行持久化，多盘存储</p>
<h3>rabbitmq 如何实现集群高可用。</h3>
<p>集群是保证服务可靠性的一种方式，同时可以通过水平扩展以提升消息吞吐能力。RabbitMQ是用分布式程序设计语言erlang开发的，所以天生就支持集群。接下来，将介绍RabbitMQ分布式消息处理方式、集群模式、节点类型，并动手搭建一个高可用集群环境，最后通过java程序来验证集群的高可用性。</p>
<ol>
 <li>三种分布式消息处理方式</li>
</ol>
<p>RabbitMQ分布式的消息处理方式有以下三种：</p>
<p>1、Clustering：不支持跨网段，各节点需运行同版本的Erlang和RabbitMQ, 应用于同网段局域网。</p>
<p>2、Federation：允许单台服务器上的Exchange或Queue接收发布到另一台服务器上Exchange或Queue的消息, 应用于广域网，。</p>
<p>3、Shovel：与Federation类似，但工作在更低层次。</p>
<p>RabbitMQ对网络延迟很敏感，在LAN环境建议使用clustering方式;在WAN环境中，则使用Federation或Shovel。我们平时说的RabbitMQ集群，说的就是clustering方式，它是RabbitMQ内嵌的一种消息处理方式，而Federation或Shovel则是以plugin形式存在。
 <br>
 <a href="https://my.oschina.net/jiaoyanli/blog/822011">https://my.oschina.net/jiaoyanli/blog/822011</a>
 <br>
 <a href="https://www.ibm.com/developerworks/cn/opensource/os-cn-RabbitMQ/">https://www.ibm.com/developerworks/cn/opensource/os-cn-RabbitMQ/</a></p>
<h1>Redis，Memcached</h1>
<h3>redis 的 list 结构相关的操作。</h3>
<p>LPUSH LPUSHX RPUSH RPUSHX LPOP RPOP BLPOP BRPOP LLEN LRANGE
 <br>
 <a href="https://redis.readthedocs.io/en/2.4/list.html">https://redis.readthedocs.io/en/2.4/list.html</a></p>
<h3>Redis 的数据结构都有哪些。</h3>
<p>字符串(strings)：存储整数（比如计数器）和字符串（废话。。），有些公司也用来存储json/pb等序列化数据，并不推荐，浪费内存
 <br>
 哈希表(hashes)：存储配置，对象（比如用户、商品），优点是可以存取部分key，对于经常变化的或者部分key要求atom操作的适合
 <br>
 列表(lists)：可以用来存最新用户动态，时间轴，优点是有序，确定是元素可重复，不去重
 <br>
 集合(sets)：无序，唯一，对于要求严格唯一性的可以使用
 <br>
 有序集合(sorted sets)：集合的有序版，很好用，对于排名之类的复杂场景可以考虑https://redis.readthedocs.io/en/2.4/list.html</p>
<h2>Redis 的使用要注意什么，讲讲持久化方式，内存设置，集群的应用和优劣势，淘汰策略等。</h2>
<p>持久化方式：RDB时间点快照 AOF记录服务器执行的所有写操作命令，并在服务器启动时，通过重新执行这些命令来还原数据集。
 <br>
 内存设置 maxmemory used_memory
 <br>
 虚拟内存： vm-enabled yes
 <br>
 3.0采用Cluster方式，
 <br>
 Redis集群相对单机在功能上存在一些限制， 需要开发人员提前了解，
 <br>
 在使用时做好规避。 限制如下：
 <br>
 1） key批量操作支持有限。 如mset、 mget， 目前只支持具有相同slot值的
 <br>
 ke
 <br>
 y执
 <br>
 行批量操作。 对于映射为不同slot值的key由于执行mget、 mget等操作可
 <br>
 能存在于多个节点上因此不被支持。
 <br>
 2） key事务操作支持有限。 同理只支持多key在同一节点上的事务操
 <br>
 作， 当多个key分布在不同的节点上时无法使用事务功能。
 <br>
 3） key作为数据分区的最小粒度， 因此不能将一个大的键值对象如
 <br>
 ha
 <br>
 sh、 list等映射到不同的节点。
 <br>
 4） 不支持多数据库空间。 单机下的Redis可以支持16个数据库， 集群模
 <br>
 式下只能使用一个数据库空间， 即db0。
 <br>
 5） 复制结构只支持一层， 从节点只能复制主节点， 不支持嵌套树状复
 <br>
 制结构。
 <br>
 Redis Cluster是Redis的分布式解决方案， 在3.0版本正式推出， 有效地解
 <br>
 决了Redis分布式方面的需求。 当遇到单机内存、 并发、 流量等瓶颈时， 可
 <br>
 以采用Cluster架构方案达到负载均衡的目的。 之前， Redis分布式方案一般
 <br>
 有两种：
 <br>
 ·客户端分区方案， 优点是分区逻辑可控， 缺点是需要自己处理数据路
 <br>
 由、 高可用、 故障转移等问题。
 <br>
 ·代理方案， 优点是简化客户端分布式逻辑和升级维护便利， 缺点是加
 <br>
 重架构部署复杂度和性能损耗。
 <br>
 现在官方为我们提供了专有的集群方案： Redis Cluster， 它非常优雅地
 <br>
 解决了Redis集群方面的问题， 因此理解应用好Redis Cluster将极大地解放我
 <br>
 们使用分布式Redis的工作量， 同时它也是学习分布式存储的绝佳案例。</p>
<p>LRU(近期最少使用算法)TTL（超时算法） 去除ttl最大的键值
 <br>
 <a href="http://wiki.jikexueyuan.com/project/redis/data-elimination-mechanism.html">http://wiki.jikexueyuan.com/project/redis/data-elimination-mechanism.html</a>
 <br>
 <a href="http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage">http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage</a>
 <br>
 <a href="http://www.redis.cn/topics/cluster-tutorial.html">http://www.redis.cn/topics/cluster-tutorial.html</a></p>
<h3>redis2 和 redis3 的区别，redis3 内部通讯机制。</h3>
<p>集群方式的区别，3采用Cluster，2采用客户端分区方案和代理方案
 <br>
 通信过程说明：
 <br>
 1） 集群中的每个节点都会单独开辟一个TCP通道， 用于节点之间彼此
 <br>
 通信， 通信端口号在基础端口上加10000。
 <br>
 2） 每个节点在固定周期内通过特定规则选择几个节点发送ping消息。
 <br>
 3） 接收到ping消息的节点用pong消息作为响应。</p>
<h2>当前 redis 集群有哪些玩法，各自优缺点，场景。</h2>
<p>当缓存使用 持久化使用</p>
<h3>Memcache 的原理，哪些数据适合放在缓存中。</h3>
<p>基于libevent的事件处理
 <br>
 内置内存存储方式SLab Allocation机制
 <br>
 并不单一的数据删除机制
 <br>
 基于客户端的分布式系统</p>
<p>变化频繁，具有不稳定性的数据,不需要实时入库, (比如用户在线
 <br>
 状态、在线人数…)
 <br>
 门户网站的新闻等，觉得页面静态化仍不能满足要求，可以放入
 <br>
 到memcache中.(配合jquey的ajax请求)</p>
<h2>redis 和 memcached 的内存管理的区别。</h2>
<p>Memcached默认使用Slab Allocation机制管理内存，其主要思想是按照预先规定的大小，将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录，以完全解决内存碎片问题。
 <br>
 Redis的内存管理主要通过源码中zmalloc.h和zmalloc.c两个文件来实现的。
 <br>
 在Redis中，并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别。
 <br>
 <a href="http://lib.csdn.net/article/redis/55323">http://lib.csdn.net/article/redis/55323</a></p>
<h3>Redis 的并发竞争问题如何解决，了解 Redis 事务的 CAS 操作吗。</h3>
<p>Redis为单进程单线程模式，采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念，Redis对于多个客户端连接并不存在竞争，但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题，这些问题均是由于客户端连接混乱造成。对此有2种解决方法：</p>
<p>1.客户端角度，为保证每个客户端间正常有序与Redis进行通信，对连接进行池化，同时对客户端读写Redis操作采用内部锁synchronized。</p>
<p>2.服务器角度，利用setnx实现锁。</p>
<p>MULTI，EXEC，DISCARD，WATCH 四个命令是 Redis 事务的四个基础命令。其中：</p>
<p>MULTI，告诉 Redis 服务器开启一个事务。注意，只是开启，而不是执行
 <br>
 EXEC，告诉 Redis 开始执行事务
 <br>
 DISCARD，告诉 Redis 取消事务
 <br>
 WATCH，监视某一个键值对，它的作用是在事务执行之前如果监视的键值被修改，事务会被取消。
 <br>
 可以利用watch实现cas乐观锁
 <br>
 <a href="http://wiki.jikexueyuan.com/project/redis/transaction-mechanism.html">http://wiki.jikexueyuan.com/project/redis/transaction-mechanism.html</a>
 <br>
 <a href="http://www.jianshu.com/p/d777eb9f27df">http://www.jianshu.com/p/d777eb9f27df</a></p>
<h2>Redis 的选举算法和流程是怎样的</h2>
<p>Raft采用心跳机制触发Leader选举。系统启动后，全部节点初始化为Follower，term为0.节点如果收到了RequestVote或者AppendEntries，就会保持自己的Follower身份。如果一段时间内没收到AppendEntries消息直到选举超时，说明在该节点的超时时间内还没发现Leader，Follower就会转换成Candidate，自己开始竞选Leader。一旦转化为Candidate，该节点立即开始下面几件事情：</p>
<p>1、增加自己的term。
 <br>
 2、启动一个新的定时器。
 <br>
 3、给自己投一票。
 <br>
 4、向所有其他节点发送RequestVote，并等待其他节点的回复。
 <br>
 如果在这过程中收到了其他节点发送的AppendEntries，就说明已经有Leader产生，自己就转换成Follower，选举结束。</p>
<p>如果在计时器超时前，节点收到多数节点的同意投票，就转换成Leader。同时向所有其他节点发送AppendEntries，告知自己成为了Leader。</p>
<p>每个节点在一个term内只能投一票，采取先到先得的策略，Candidate前面说到已经投给了自己，Follower会投给第一个收到RequestVote的节点。每个Follower有一个计时器，在计时器超时时仍然没有接受到来自Leader的心跳RPC, 则自己转换为Candidate, 开始请求投票，就是上面的的竞选Leader步骤。</p>
<p>如果多个Candidate发起投票，每个Candidate都没拿到多数的投票（Split Vote），那么就会等到计时器超时后重新成为Candidate，重复前面竞选Leader步骤。</p>
<p>Raft协议的定时器采取随机超时时间，这是选举Leader的关键。每个节点定时器的超时时间随机设置，随机选取配置时间的1倍到2倍之间。由于随机配置，所以各个Follower同时转成Candidate的时间一般不一样，在同一个term内，先转为Candidate的节点会先发起投票，从而获得多数票。多个节点同时转换为Candidate的可能性很小。即使几个Candidate同时发起投票，在该term内有几个节点获得一样高的票数，只是这个term无法选出Leader。由于各个节点定时器的超时时间随机生成，那么最先进入下一个term的节点，将更有机会成为Leader。连续多次发生在一个term内节点获得一样高票数在理论上几率很小，实际上可以认为完全不可能发生。一般1-2个term类，Leader就会被选出来。</p>
<p>Sentinel的选举流程</p>
<p>Sentinel集群正常运行的时候每个节点epoch相同，当需要故障转移的时候会在集群中选出Leader执行故障转移操作。Sentinel采用了Raft协议实现了Sentinel间选举Leader的算法，不过也不完全跟论文描述的步骤一致。Sentinel集群运行过程中故障转移完成，所有Sentinel又会恢复平等。Leader仅仅是故障转移操作出现的角色。</p>
<p>选举流程</p>
<p>1、某个Sentinel认定master客观下线的节点后，该Sentinel会先看看自己有没有投过票，如果自己已经投过票给其他Sentinel了，在2倍故障转移的超时时间自己就不会成为Leader。相当于它是一个Follower。
 <br>
 2、如果该Sentinel还没投过票，那么它就成为Candidate。
 <br>
 3、和Raft协议描述的一样，成为Candidate，Sentinel需要完成几件事情
 <br>
 1）更新故障转移状态为start
 <br>
 2）当前epoch加1，相当于进入一个新term，在Sentinel中epoch就是Raft协议中的term。
 <br>
 3）更新自己的超时时间为当前时间随机加上一段时间，随机时间为1s内的随机毫秒数。
 <br>
 4）向其他节点发送is-master-down-by-addr命令请求投票。命令会带上自己的epoch。
 <br>
 5）给自己投一票，在Sentinel中，投票的方式是把自己master结构体里的leader和leader_epoch改成投给的Sentinel和它的epoch。
 <br>
 4、其他Sentinel会收到Candidate的is-master-down-by-addr命令。如果Sentinel当前epoch和Candidate传给他的epoch一样，说明他已经把自己master结构体里的leader和leader_epoch改成其他Candidate，相当于把票投给了其他Candidate。投过票给别的Sentinel后，在当前epoch内自己就只能成为Follower。
 <br>
 5、Candidate会不断的统计自己的票数，直到他发现认同他成为Leader的票数超过一半而且超过它配置的quorum（quorum可以参考《redis sentinel设计与实现》）。Sentinel比Raft协议增加了quorum，这样一个Sentinel能否当选Leader还取决于它配置的quorum。
 <br>
 6、如果在一个选举时间内，Candidate没有获得超过一半且超过它配置的quorum的票数，自己的这次选举就失败了。
 <br>
 7、如果在一个epoch内，没有一个Candidate获得更多的票数。那么等待超过2倍故障转移的超时时间后，Candidate增加epoch重新投票。
 <br>
 8、如果某个Candidate获得超过一半且超过它配置的quorum的票数，那么它就成为了Leader。
 <br>
 9、与Raft协议不同，Leader并不会把自己成为Leader的消息发给其他Sentinel。其他Sentinel等待Leader从slave选出master后，检测到新的master正常工作后，就会去掉客观下线的标识，从而不需要进入故障转移流程。
 <br>
 <a href="http://weizijun.cn/2015/04/30/Raft%E5%8D%8F%E8%AE%AE%E5%AE%9E%E6%88%98%E4%B9%8BRedis%20Sentinel%E7%9A%84%E9%80%89%E4%B8%BELeader%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/">http://weizijun.cn/2015/04/30/Raft协议实战之Redis Sentinel的选举Leader源码解析/</a></p>
<h3>redis 的持久化的机制，aof 和 rdb 的区别。</h3>
<p>RDB 定时快照方式(snapshot)： 定时备份，可能会丢失数据
 <br>
 AOF 基于语句追加方式 只追加写操作
 <br>
 AOF 持久化和 RDB 持久化的最主要区别在于，前者记录了数据的变更，而后者是保存了数据本身</p>
<h3>redis 的集群怎么同步的数据的。</h3>
<p>redis replication redis-migrate-tool等方式</p>
<h1>搜索</h1>
<h3>elasticsearch 了解多少，说说你们公司 es 的集群架构，索引数据大小，分片有多少，以及一些调优手段。elasticsearch 的倒排索引是什么。</h3>
<p>ElasticSearch（简称ES）是一个分布式、Restful的搜索及分析服务器，设计用于分布式计算；能够达到实时搜索，稳定，可靠，快速。和Apache Solr一样，它也是基于Lucence的索引服务器，而ElasticSearch对比Solr的优点在于：</p>
<blockquote>
 <ol>
  <li>轻量级：安装启动方便，下载文件之后一条命令就可以启动。</li>
  <li>Schema free：可以向服务器提交任意结构的JSON对象，Solr中使用schema.xml指定了索引结构。</li>
  <li>多索引文件支持：使用不同的index参数就能创建另一个索引文件，Solr中需要另行配置。</li>
  <li>分布式：Solr Cloud的配置比较复杂。</li>
 </ol>
</blockquote>
<p>倒排索引是实现"单词-文档矩阵"的一种具体存储形式，通过倒排索引，可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成：“单词词典"和"倒排文件”。</p>
<h2>elasticsearch 索引数据多了怎么办，如何调优，部署。</h2>
<p>使用bulk API
 <br>
 初次索引的时候，把 replica 设置为 0
 <br>
 增大 threadpool.index.queue_size
 <br>
 增大 indices.memory.index_buffer_size
 <br>
 增大 index.translog.flush_threshold_ops
 <br>
 增大 index.translog.sync_interval
 <br>
 增大 index.engine.robin.refresh_interval
 <br>
 <a href="http://www.jianshu.com/p/5eeeeb4375d4">http://www.jianshu.com/p/5eeeeb4375d4</a></p>
<h3>lucence 内部结构是什么</h3>
<p>索引(Index)：
 <br>
 在Lucene中一个索引是放在一个文件夹中的。
 <br>
 如上图，同一文件夹中的所有的文件构成一个Lucene索引。
 <br>
 段(Segment)：
 <br>
 一个索引可以包含多个段，段与段之间是独立的，添加新文档可以生成新的段，不同的段可以合并。
 <br>
 如上图，具有相同前缀文件的属同一个段，图中共三个段 “<em>0” 和 “</em>1"和”_2”。
 <br>
 segments.gen和segments_X是段的元数据文件，也即它们保存了段的属性信息。
 <br>
 文档(Document)：
 <br>
 文档是我们建索引的基本单位，不同的文档是保存在不同的段中的，一个段可以包含多篇文档。
 <br>
 新添加的文档是单独保存在一个新生成的段中，随着段的合并，不同的文档合并到同一个段中。
 <br>
 域(Field)：
 <br>
 一篇文档包含不同类型的信息，可以分开索引，比如标题，时间，正文，作者等，都可以保存在不同的域里。
 <br>
 不同域的索引方式可以不同，在真正解析域的存储的时候，我们会详细解读。
 <br>
 词(Term)：
 <br>
 词是索引的最小单位，是经过词法分析和语言处理后的字符串。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102954078</p>]]></description><guid isPermaLink="false">/archives/ec7c8a8c-d2e6-4646-bbaa-c73a7447aa48</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 10:00:45 GMT</pubDate></item><item><title><![CDATA[微信公众号——拉取前端上传到微信服务器的图片并保存到本地]]></title><link>https://wiki.dgfygk.com/archives/e3b07253-3bc0-4e0a-b58b-1ed2fac0fc8f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E2%80%94%E2%80%94%E6%8B%89%E5%8F%96%E5%89%8D%E7%AB%AF%E4%B8%8A%E4%BC%A0%E5%88%B0%E5%BE%AE%E4%BF%A1%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E5%9B%BE%E7%89%87%E5%B9%B6%E4%BF%9D%E5%AD%98%E5%88%B0%E6%9C%AC%E5%9C%B0&amp;url=/archives/e3b07253-3bc0-4e0a-b58b-1ed2fac0fc8f" width="1" height="1" alt="" style="opacity:0;">
<p>OpenID是此网站上或应用中唯一对应用户身份的标识，网站或应用可将此ID进行存储，便于用户下次登录时辨识其身份，或将其与用户在网站上或应用中的原有账号进行绑定。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faceb1eab22d7b94f8449e29c5ee594ac.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102978339</p>]]></description><guid isPermaLink="false">/archives/e3b07253-3bc0-4e0a-b58b-1ed2fac0fc8f</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 09:59:59 GMT</pubDate></item><item><title><![CDATA[Java ExecutorService四种线程池的使用]]></title><link>https://wiki.dgfygk.com/archives/71846286-32f0-47fd-b222-052447e34b98</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20ExecutorService%E5%9B%9B%E7%A7%8D%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E4%BD%BF%E7%94%A8&amp;url=/archives/71846286-32f0-47fd-b222-052447e34b98" width="1" height="1" alt="" style="opacity:0;">
<p><strong>一、Executors提供的四种线程</strong></p>
<blockquote>
 <ol>
  <li>newCachedThreadPool创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。</li>
  <li>newFixedThreadPool 创建一个定长线程池，可控制线程最大并发数，超出的线程会在队列中等待。</li>
  <li>newScheduledThreadPool 创建一个定长线程池，支持定时及周期性任务执行。</li>
  <li>newSingleThreadExecutor 创建一个单线程化的线程池，它只会用唯一的工作线程来执行任务，保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。</li>
 </ol>
</blockquote>
<p><strong>二、下面对四种线程池的使用进行简单的代码实现</strong></p>
<ol>
 <li>newCachedThreadPool() 创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。</li>
</ol>
<pre><code class="java">public static void newCachedThreadPoolTest() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i &lt; 200; i++) {
            final int index = i;
            try {
                Thread.sleep(index);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.execute(() -&gt; System.out.println(Thread.currentThread().getName() + "," + index));
        }
        //shutdown 的原理是只是将线程池的状态设置成SHUTDOWN状态，然后中断所有没有正在执行任务的线程
        //shutdownNow 会首先将线程池的状态设置成STOP，然后尝试停止所有的正在执行或暂停任务的线程，并返回等待执行任务的列表
        executorService.shutdown();
    }
</code></pre>
<p>控制台输出：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4aa7bd84b20c52bda49ecd9fda15f805.png&amp;size=m" alt=""></p>
<p>注：介绍一下shutdown()和shutdownNow()区别：</p>
<blockquote>
 <p>线程池的关闭 我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池，但是它们的实现原理不</p>
 <ol>
  <li>shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态，然后中断所有没有正在执行任务的线程。</li>
  <li>shutdownNow的原理是遍历线程池中的工作线程，然后逐个调用线程的interrupt方法来中断线程，所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP，然后尝试停止所有的正在执行或暂停任务的线程，并返回等待执行任务的列表。</li>
 </ol>
 <p>只要调用了这两个关闭方法的其中一个，isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功，这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池，应该由提交到线程池的任务特性决定，通常调用shutdown来关闭线程池，如果任务不一定要执行完，则可以调用shutdownNow。</p>
</blockquote>
<ol>
 <li>newFixedThreadPool() 创建一个定长的线程池，可控制线程最大并发数，超出的线程会在队列中等待</li>
</ol>
<pre><code class="java">public static void newFixedThreadPoolTest(){
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        for (int i = 0; i &lt; 200; i++) {
            final int index = i;
            fixedThreadPool.execute(() -&gt; {
                try {
                    System.out.println(Thread.currentThread().getName() + "," + index);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            // 以上代码可以改写成submit提交任务的lambda表达式的形式
            fixedThreadPool.submit(() -&gt; {
                try {
                    System.out.println(Thread.currentThread().getName() + ", " + index);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, true);
        }
    }
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7702041d4d89d06829d211e728186156.png&amp;size=m" alt=""></p>
<blockquote>
 <p>注：这里介绍一下使用execute()和submit()方法提交任务的区别：</p>
 <p>向线程池提交任务 ThreadPoolExecutor类中execute()和submit()区别</p>
 <ul>
  <li>execute()方法实际上是Executor中声明的方法，在ThreadPoolExecutor进行了具体的实现，这个方法是ThreadPoolExecutor的核心方法，通过这个方法可以向线程池提交一个任务，交由线程池去执行。</li>
  <li>submit()方法是在ExecutorService中声明的方法，在AbstractExecutorService就已经有了具体的实现，在ThreadPoolExecutor中并没有对其进行重写，这个方法也是用来向线程池提交任务的，但是它和execute()方法不同，它能够返回任务执行的结果，通过源码查看submit()方法的实现，会发现它实际上还是调用的execute()方法，只不过它利用了Future来获取任务执行结果。</li>
 </ul>
</blockquote>
<pre><code class="java">//submit()源码
/**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public &lt;T&gt; Future&lt;T&gt; submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture&lt;T&gt; ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
</code></pre>
<ol>
 <li>newScheduledThreadPool() 创建定长的线程池，支持周期和定时任务</li>
</ol>
<pre><code class="java">// 示例如下:
    public static void newScheduledThreadPoolTest() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        System.out.println("before:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟3秒执行一次：" + System.currentTimeMillis() / 1000);
            }
        }, 3, TimeUnit.SECONDS);
        System.out.println("after:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);

    }
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Feb794516d6554b2ad0162a801795bc28.png&amp;size=m" alt=""></p>
<pre><code class="java">    System.out.println("----------------------------华丽的分割线-------------------------");
    System.out.println("begin:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
    // initialDelay:延迟首次执行的时间，period：连续执行之间的时间间隔，unit：initialDelay 和 period的时间单位
    scheduledThreadPool.scheduleAtFixedRate(() -&gt; System.out.println("首次执行时间延迟1s,然后3s执行一次：" + System.currentTimeMillis() / 1000), 1, 3, TimeUnit.SECONDS);
    System.out.println("end:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4f1e4589a60ea5cdb5ba2fafa2940af0.png&amp;size=m" alt=""></p>
<ol>
 <li>newSingleThreadExecutor() 创建一个单线程化的线程池，只会用工作线程来执行任务，保证顺序</li>
</ol>
<pre><code class="java">// 示例如下:
    public static void newSingleThreadExecutorTest(){
        ExecutorService singleThread = Executors.newSingleThreadExecutor();
        for (int i = 0; i &lt; 100; i++) {
            final int index = i;
            singleThread.execute(() -&gt; {
                try {
                    System.out.println(Thread.currentThread().getName() + "," + index);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
</code></pre>
<p>三、<strong>线程池分析</strong></p>
<ol>
 <li>线程池的主要工作流程如下图：</li>
</ol>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd491f9b988e4e1f3f0330937864afc22.png&amp;size=m" alt=""></p>
<p>从上图我们可知，当提交一个新任务到线程池时，线程池的处理流程如下：</p>
<blockquote>
 <ol>
  <li>首先线程池判断核心线程池是否已满？没满，创建一个工作线程来执行任务。满了，则进入下个流程。</li>
  <li>其次线程池判断工作队列是否已满？没满，则将新提交的任务存储在工作队列里。满了，则进入下个流程。</li>
  <li>最后线程池判断整个线程池是否已满？没满，则创建一个新的工作线程来执行任务，满了，则交给饱和策略来处理这个任务。</li>
 </ol>
</blockquote>
<ol>
 <li>源码分析。</li>
</ol>
<p>上面的流程分析让我们很直观的了解的线程池的工作原理，让我们再通过源代码来看看是如何实现的。线程池执行任务的方法如下：</p>
<pre><code class="java">public void execute(Runnable command) {
  if (command == null)
    throw new NullPointerException();
  int c = ctl.get();
  if (workerCountOf(c) &lt; corePoolSize) {
    if (addWorker(command, true))
      return;
    c = ctl.get();
  }
  if (isRunning(c) &amp;&amp; workQueue.offer(command)) {
    int recheck = ctl.get();
    if (! isRunning(recheck) &amp;&amp; remove(command))
      reject(command);
    else if (workerCountOf(recheck) == 0)
      addWorker(null, false);
  }
  else if (!addWorker(command, false))
    reject(command);
}
</code></pre>
<p>工作线程。线程池创建线程时，会将线程封装成工作线程Worker，Worker在执行完任务后，还会无限循环获取工作队列里的任务来执行。</p>
<p>参考文章链接：<a href="https://www.jb51.net/article/134862.htm">https://www.jb51.net/article/134862.htm</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102569501</p>]]></description><guid isPermaLink="false">/archives/71846286-32f0-47fd-b222-052447e34b98</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:55:44 GMT</pubDate></item><item><title><![CDATA[JQuery实现上传图片并预览]]></title><link>https://wiki.dgfygk.com/archives/233438e1-6d22-4fe3-bc5f-a3b7dcf3040a</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JQuery%E5%AE%9E%E7%8E%B0%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87%E5%B9%B6%E9%A2%84%E8%A7%88&amp;url=/archives/233438e1-6d22-4fe3-bc5f-a3b7dcf3040a" width="1" height="1" alt="" style="opacity:0;">
<p><strong>前段页面代码：</strong></p>
<pre><code class="html">&lt;div id="popUps" class="control-group" style="display: block"&gt;
    &lt;label class="control-label"&gt;身份证正面：&lt;/label&gt;
        &lt;div class="controls"&gt;
            &lt;input id="popUpsUrl" type="file" name="files" accept="image/*" onchange="uploadImg(this)"&gt;
            &lt;br&gt;
            &lt;img id="popUpImg" src="" width="350px" height="623px"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div id="adBanner" class="control-group" style="display: block"&gt;
        &lt;label class="control-label"&gt;身份证背面：&lt;/label&gt;
        &lt;div class="controls"&gt;
            &lt;input id="adBannerUrl" type="file" name="files" accept="image/*" onchange="uploadImg(this)"&gt;
            &lt;br&gt;
            &lt;img id="adBannerImg" src="" width="350px" height="49px"&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<pre><code>```javascript
function uploadImg(file) {
    if (!file.files || !file.files[0]) {
        return;
    }
    var reader = new FileReader();
    reader.onload = function (evt) {
        if (file.id === 'popUpsUrl') {
            document.getElementById('popUpImg').src = evt.target.result;
            image = evt.target.result;
        } else if (file.id === 'popUpsUrl2') {
            document.getElementById('popUpImg2').src = evt.target.result;
            image = evt.target.result;
        }
    };
    reader.readAsDataURL(file.files[0]);
}
</code></pre>
<p>上面的方法是比较笨的一种，也是容易想到的一种方法。还可以用下一种方法实现：</p>
<pre><code class="javascript">function uploadImg(file) {
    if (!file.files || !file.files[0]) {
        return;
    }
    var reader = new FileReader();
    reader.onload = function (evt) {
        image = evt.target.result;
            $("#"+file.id).parent().find("img").attr("src",image);
    };
    reader.readAsDataURL(file.files[0]);
}
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102822059</p>]]></description><guid isPermaLink="false">/archives/233438e1-6d22-4fe3-bc5f-a3b7dcf3040a</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:55:27 GMT</pubDate></item><item><title><![CDATA[JQuery中的Ajax,请求后台接口返回指定数据的实例]]></title><link>https://wiki.dgfygk.com/archives/4eb20c97-66cb-48cd-a630-bca9a9e098b9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JQuery%E4%B8%AD%E7%9A%84Ajax%2C%E8%AF%B7%E6%B1%82%E5%90%8E%E5%8F%B0%E6%8E%A5%E5%8F%A3%E8%BF%94%E5%9B%9E%E6%8C%87%E5%AE%9A%E6%95%B0%E6%8D%AE%E7%9A%84%E5%AE%9E%E4%BE%8B&amp;url=/archives/4eb20c97-66cb-48cd-a630-bca9a9e098b9" width="1" height="1" alt="" style="opacity:0;">
<h3>Ajax基本概念</h3>
<blockquote>
 <p>Ajax(Asynchronous JavaScript And XML)，异步 JavaScript 和 XML，用于异步请求数据，在不刷新网页的情况下更新页面数据，提升用户体验</p>
</blockquote>
<h3>Ajax优缺点</h3>
<p><strong>优点</strong>：</p>
<blockquote>
 <ol>
  <li>Ajax最大的优点就是能在不刷新整个页面的情况下维持与服务器通信</li>
  <li>使用异步的方式与服务器通信，不打断用户的操作</li>
  <li>可将一些后端的工作移到前端，减少服务器与带宽的负担</li>
  <li>Ajax使得界面与应用分离，也就是数据与呈现分离</li>
 </ol>
</blockquote>
<p><strong>缺点</strong>：</p>
<blockquote>
 <ol>
  <li>Ajax干掉了Back与History功能，即对浏览器机制的破坏，在动态更新页面的情况下，用户无法回到前一页的页面状态，因为浏览器仅能记忆历史纪录中的静态页面</li>
  <li>AJAX技术给用户带来很好的用户体验的同时也对IT企业带来了新的安全威胁，Ajax技术就如同对企业数据建立了一个直接通道。这使得开发者在不经意间会暴露比以前更多的数据和服务器逻辑</li>
  <li>对搜索引擎支持较弱</li>
 </ol>
</blockquote>
<h3>JQuery中的Ajax使用实例</h3>
<blockquote>
 <h4><strong>$.ajax()方法</strong></h4>
 <pre><code class="javascript">$(function(){
    $('#send').click(function(){
         $.ajax({
             type: "GET",
             url: "test.json",
             data: {username:$("#username").val(), content:$("#content").val()},
             dataType: "json",
             contentType: "application/x-www-form-urlencoded"
             async: true,
             cache: true,
             timeout: 5000,
             success: function (data) {},
             error: function () {},
             complete: function () {}
         });
    });
});
</code></pre>
</blockquote>
<h4>参数解释</h4>
<ol>
 <li>url String类型，请求的地址</li>
 <li>type String类型，请求方式，一般为get或者post请求，但是http的其他请求当然也可以</li>
 <li>timeout Number类型，超时时间，单位为毫秒</li>
 <li>async Boolean类型，是否异步，默认设置为true，所有请求均为异步请求。如果需要发送同步请求，设置为false</li>
 <li>cache Boolean类型，是否从浏览器缓存中加载请求信息，默认为true，表示会从浏览器缓存中加载请求信息</li>
 <li>data Object或者String类型，发送到服务器的数据。如果已经不是字符串，将自动转换为字符串格式</li>
 <li>dataType String类型，希望服务器返回的数据类型，如果不是，就转化为指定的数据类型，如果不指定，JQuery将自动根据http包mime信息返回responseXML或responseText，并作为回调函数参数传递。可用的类型如下：</li>
</ol>
<blockquote>
 <ul>
  <li>xml：返回XML文档，可用JQuery处理</li>
  <li>html：返回纯文本HTML信息；包含的script标签会在插入DOM时执行</li>
  <li>script：返回纯文本JavaScript代码。不会自动缓存结果。除非设置了cache参数。注意在远程请求时（不在同一个域下），所有post请求都将转为get请求</li>
  <li>json：返回JSON数据</li>
  <li>jsonp：JSONP格式</li>
  <li>text：返回纯文本字符串</li>
 </ul>
</blockquote>
<ol>
 <li>beforeSend Function类型，表示发送数据之前，进行的操作，例如想在发送数据之前设置请求头</li>
</ol>
<pre><code class="javascript">beforeSend: function(request) {
     request.setRequestHeader("Header", "x-www-***");
},
</code></pre>
<ol>
 <li>
  <p>contentType String类型，发送信息至服务器时，内容编码类型默认为"application/x-www-form-urlencoded”</p>
 </li>
 <li>
  <p>dataFilter Function类型，过滤数据（预处理数据），例如时间格式的预处理等，2个参数，参数一：Ajax返回的原始数据，参数二：<code>dataType</code>参数</p>
 </li>
</ol>
<pre><code class="javascript">dataFilter: function (data, type) {
    return data
}
</code></pre>
<ol>
 <li>
  <p>global Boolean类型，默认为<code>true</code>，表示触发全局<code>ajax</code>事件，改为<code>false</code>则不会触发<code>ajax</code>全局事件</p>
 </li>
 <li>
  <p>success Function类型，请求数据成功回调，2个参数，参数一：必需，服务器返回后经过<code>dataType</code>参数处理后的数据，参数二：可选，描述状态的字符串</p>
 </li>
</ol>
<pre><code class="javascript">success: function (data, dataStatus) {}
</code></pre>
<ol>
 <li>error <strong>Function</strong>类型，请求数据成功回调，3个参数，参数一：必需，XMLHttpRequest对象，参数二：必需，错误信息，参数三：可选，捕获的错误对象</li>
</ol>
<pre><code class="javascript">error: function (XMLHttpRequest, status, error) {}
</code></pre>
<ol>
 <li>complete Function类型，只要请求数据完成就回调，不管成功不成功，2个参数，参数一：必需，XMLHttpRequest对象，参数二：可选，描述请求类型的字符串</li>
</ol>
<pre><code class="javascript">complete: function (XMLHttpRequest, status) {}
</code></pre>
<ol>
 <li>
  <p>jsonp String类型，在一个jsonp请求中重写回调函数的名字。该值用来替代在"callback=?“这种GET或POST请求中URL参数里的"callback"部分</p>
 </li>
 <li>
  <p>username <strong>String</strong>类型，用于响应HTTP访问认证请求的用户名</p>
 </li>
 <li>
  <p>password <strong>String</strong>类型，响应HTTP访问认证请求的密码</p>
 </li>
</ol>
<h4>实例：</h4>
<p><strong>1.向后台接口传两个参数activityTemplateNo, uiStyleNo ，后台返回一个对象</strong></p>
<pre><code class="javascript">function showRecentStyle(activityTemplateNo, uiStyleNo) {
    &lt;%--var activityTemplateNo = '${activityTemplateStyle.activityTemplateNo}';--%&gt;
    // alert("tempalte:" + activityTemplateNo);
    // var uiStyleNo = $('#uiStyleNo').val();
    // alert("style:" + uiStyleNo);
    $.ajax({
        type: "post",  //请求方式
        url: "/activity/activityTemplateStyle/getStyleInfo",   //后台接口url
        data: {
            templateNo: activityTemplateNo,    // 传递的参数
            uiStyleNo: uiStyleNo
        },
        dataType: "json",  // 返回数据类型
        success: function (data) { 
        // if 返回的是List集合
        /*
                if(data.length == 0 || data == null || data == ''){
                      return;
                 }
        for(int i = 0; i &lt; data.length; i++){
            var perTr= data[i];
            document.getElementById('popUpImg').src = perTr.popUpsUrl;   // 弹窗url
        }
                 */
            alert("data:" + data);
            if (data == null || data == '') {
                return;
            } else {
                var styleInfo = data;
                document.getElementById('popUpImg').src = styleInfo.popUpsUrl;
                document.getElementById('adBannerImg').src = styleInfo.adBannerUrl;
            }
        },
        error: function (date) {
            console.log(date);
        }
    })
}
</code></pre>
<p><strong>2.前段传递相关参数，后台保存</strong></p>
<pre><code class="javascript">&lt;link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/easyui/themes/bootstrap/easyui.css" /&gt;
    &lt;link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/easyui/themes/icon.css" /&gt;
    &lt;link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/easyui/demo/demo.css" /&gt;
    &lt;link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/easyui/themes/default/easyui.css"&gt;

    &lt;script type="text/javascript" src="${pageContext.request.contextPath}/easyui/jquery-1.11.3.min.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="${pageContext.request.contextPath}/script/Calendar3.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="${pageContext.request.contextPath}/easyui/jquery.easyui.min.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="${pageContext.request.contextPath}/easyui/layer/layer.js"&gt;&lt;/script&gt;
</code></pre>
<pre><code>```html
&lt;table class="easyui-datagrid" id="datagriddata" title="信息列表" style="width:100%;height:600px;"
               data-options="rownumbers:true,
                            singleSelect:true,
                            collapsible:false,
                            url:'${pageContext.request.contextPath}/purchaseverify/informationlist',
                            method:'get',
                            toolbar: '#tb',
                            pagination:true,
                            pageSize:20,
                            pageList:[10,20,30,50,80,100,200]"&gt;
            &lt;thead&gt;
            &lt;tr&gt;
                &lt;th data-options="field:'product'"&gt;product&lt;/th&gt;
                &lt;th data-options="field:'areaname'"&gt;areaname&lt;/th&gt;
                &lt;th data-options="field:'amount'"&gt;amount&lt;/th&gt;
                &lt;th data-options="field:'phone'"&gt;phone&lt;/th&gt;
                &lt;th data-options="field:'updateTime'" formatter="formatDatebox2"&gt;updateTime&lt;/th&gt;
                &lt;th data-options="field:'createTime'" formatter="formatDatebox2"&gt;createTime&lt;/th&gt; 
            &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;&lt;/tbody&gt;
        &lt;/table&gt;


&lt;div id="button"&gt;
            &lt;button type="button" class="btn btn-primary" style="margin-right:40px;" onclick="comeBack()"&gt;
                返回
            &lt;/button&gt;
            &lt;button type="button" class="btn btn-primary" style="margin-right:40px;" onclick="saveInformation()"&gt;保存更改&lt;/button&gt;
            &lt;button type="button" class="btn btn-primary" style="margin-right:40px;" onclick="removeRecord()" &gt;移除信息&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<pre><code>```javascript
function formatDatebox2(value) {
        if (value == null || value == '') {
            return '';
        }
        var dt;
        if (value instanceof Date) {
            dt = value;
        } else {
            dt = new Date(value);
        }
        return dt.format("yyyy-MM-dd hh:mm"); //扩展的Date的format方法(上述插件实现)
    }
</code></pre>
<pre><code>```javascript
function saveInformation(){
    var rows = $('#datagriddata').datagrid('getSelections');
    var item = rows[0];
    var id = item.id;
    var productList = [];
    var formatList = [];
    var amountList = [];

    var linker = $("input[name ='linker']").val();
    var phone = $("input[name ='phone']").val();        
    var area = item.areaname;        
    var province = $(".province&gt;option:selected").text();       
    var city = $("select[name = 'city' ]").val();       
    $("input[name='product']").each(function () {
        productList.push($(this).val());
    });               
    $("input[name='productFormat']").each(function () {
        formatList.push($(this).val());
    });
    console.log('formatList:'+formatList);
    $(" input[name='amount']").each(function () {
        amountList.push($(this).val());
    });       
    var purchasestyle = $("input[name='purchasestyle']:checked").val();       
    var btime = $("#begintime").val();
    var begintime ;
    if (btime = "" || btime.length == 0){
        begintime = new Date().format("yyyy-MM-dd ");
    } else {
        begintime = $("#begintime").val();
    }       
    var etime =$("#endtime").val();
    var endtime;
    if (etime = '' || etime.length == 0){
        endtime = '2090-12-31';
    } else {
        endtime = $("#endtime").val();
    }
    var dataParams=[]; dataParams.push({'id':id,'linker':linker,'phone':phone,'areaname':area,'product':productList,'productFormat':formatList,'amount':amountList,'purchaseStyle':purchasestyle,'beginTime':begintime,'endTime':endtime});
    dataParams = JSON.stringify(dataParams);
    $.messager.confirm('提示信息', "确定要保存吗？", function (r) {
        if(r){
            $.ajax({
                url: "${pageContext.request.contextPath}/demo/save",
                type: "post",
                dataType: "json",
                data:{"dataParams":dataParams} ,
                success: function (data) {
                    if (data.code == "0") {
                        $.messager.confirm('提示信息', '保存信息成功！');
                    } else {
                        $.messager.confirm('提示信息', '保存信息失败！');
                    }
                    $("#datagriddata").datagrid("reload", jQuery("#datagriddata").serializeObject());
                }
            });
        }
    });
}
</code></pre>
<pre><code>```javascript
function validateRecord() {
    var rows = $('#datagriddata').datagrid('getSelections');
    if(rows.length!=1){
        $.messager.alert('提示信息','请选择要核实的采购信息！');
        return;
    }
    var id = rows[0].id;
    var memberid = rows[0].memberid;
    var source = rows[0].source;
    $.messager.confirm('提示信息',"是否对该信息进行核实？",function(r){
        if (r){
            $.ajax({
                url:"${pageContext.request.contextPath}/demo/shenhe",
                type:"post",
                dataType:"json",
                data:{
                    "memberid":memberid,
                    "id":id,
                    "name":"&lt;%=name %&gt;",
                    "source":source
                },
                success:function(data){
                    if(data.code == "0"){
                        $.messager.alert('提示信息','变更成功');
                    }else{
                        $.messager.alert('提示信息','变更失败');
                    }
                    $("#datagriddata").datagrid("reload", jQuery("#datagriddata").serializeObject());
                },
                error:function(data){
                    console.log(data)
                }
            })

        }
    });
}
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102820326</p>]]></description><guid isPermaLink="false">/archives/4eb20c97-66cb-48cd-a630-bca9a9e098b9</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:52:45 GMT</pubDate></item><item><title><![CDATA[MongoDB分页查询]]></title><link>https://wiki.dgfygk.com/archives/570ca342-307e-4732-abbc-bc7d74a39a98</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MongoDB%E5%88%86%E9%A1%B5%E6%9F%A5%E8%AF%A2&amp;url=/archives/570ca342-307e-4732-abbc-bc7d74a39a98" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="java">// 分页查询方法
public static Query getQuery(long startId) {
        Query query = new Query()
                .addCriteria(Criteria.where("_id").gt(startId))
                .with(Sort.by(new Sort.Order(Sort.Direction.ASC, "_id")))
                .limit(5000);
        return query;
    }
</code></pre>
<pre><code>```java
private static final String REGEX = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$";
private static final AtomicInteger saveCount = new AtomicInteger();
private static final AtomicInteger phoneIsNullCount = new AtomicInteger();
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

@Autowired
MongoTemplate mongoTemplate;

@Autowired
SendMessageImMapper sendMessageImMapper;

public String selectUserByProperty() {
        ExecutorService serviceExecutor = Executors.newFixedThreadPool(50);
        List&lt;Future&lt;Boolean&gt;&gt; futureList = new ArrayList&lt;&gt;();
        long lastDataResultId = 0;
        Integer dealDataCount = 0;
        int i = (1676955 / 5000) + 1;
        for (int k = 0; k &lt; i; k++) {
            List&lt;CompanyInfoFromMongoDocument&gt; mongoDocuments = mongoTemplate.find(
                    getQuery(lastDataResultId), CompanyInfoFromMongoDocument.class);
            log.info("====&gt;数据信息分批处理-mongoDocuments数据大小：{}", mongoDocuments.size());
            dealDataCount = dealDataCount + mongoDocuments.size();
            log.info("====&gt;数据处理大小：{}", mongoDocuments.size());
            if (CollectionUtils.isNotEmpty(mongoDocuments)) {
                for (CompanyInfoFromMongoDocument po: mongoDocuments) {
                    Future&lt;Boolean&gt; future = serviceExecutor.submit(() -&gt; {
                        MongoDBPO mongodpo = new MongoDBPO();
                        String phoneNumber = po.getPhoneNumber();// 电话
                        if (StringUtils.isNotBlank(phoneNumber)) {
                            Pattern PATTERN_REGEX = Pattern.compile(REGEX);
                            Matcher matcher = PATTERN_REGEX.matcher(phoneNumber);
                            if (matcher.find()) {
                               sendMessageImMapper.saveMongoDBData(tianYanChaPO);
                                if (saveCount.get() % 2000 == 0) {
                                    log.info("====&gt;已保存用户数量:{}", saveCount.get());
                                }
                            }
                        } else {
                            phoneIsNullCount.incrementAndGet();
                        }
                    }, true);
                    lastDataResultId = mongoDocuments.get(mongoDocuments.size() - 1).getId();
                    futureList.add(future);
                }
            }
            futureList.forEach(f -&gt; {
                try {
                    f.get(10, TimeUnit.SECONDS);
                } catch (TimeoutException e) {
                    f.cancel(true);
                } catch (InterruptedException | ExecutionException e) {
                    log.info("处理异常:{}", e);
                }
            });
            log.info("时间：{}, 起始查询id：{}, phone是空的数量：{}", dateFormat.format(new Date()), lastDataResultId, phoneIsNullCount.get());
        }
        return "数据处理结束";
    }
</code></pre>
<pre><code>```
&nbsp;
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102505314</p>]]></description><guid isPermaLink="false">/archives/570ca342-307e-4732-abbc-bc7d74a39a98</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 09:43:58 GMT</pubDate></item><item><title><![CDATA[MyBatis动态SQL]]></title><link>https://wiki.dgfygk.com/archives/0551e9ae-2d14-4f2d-94e6-9b5762c57065</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis%E5%8A%A8%E6%80%81SQL&amp;url=/archives/0551e9ae-2d14-4f2d-94e6-9b5762c57065" width="1" height="1" alt="" style="opacity:0;">
<p>MyBatis 令人喜欢的一大特性就是动态 SQL。 在使用 JDBC 的过程中， 根据条件进行 SQL 的拼接是很麻烦且很容易出错的。 MyBatis 动态 SQL 的出现， 解决了这个麻烦。</p>
<p>MyBatis通过 OGNL 来进行动态 SQL 的使用的。目前， 动态 SQL 支持以下几种标签：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F691b6633ca9515f9acdb768f03da5eb9.png&amp;size=m" alt=""></p>
<h2>1 数据准备</h2>
<h3>1.1 创建了对应的数据库和表</h3>
<p>为了后面的演示， 创建了一个 Maven 项目 mybatis-dynamic, 创建了对应的数据库和表</p>
<pre><code class="sql">DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `student_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `phone` varchar(20) DEFAULT NULL COMMENT '电话',
  `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
  `sex` tinyint(4) DEFAULT NULL COMMENT '性别',
  `locked` tinyint(4) DEFAULT NULL COMMENT '状态(0:正常,1:锁定)',
  `gmt_created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '存入数据库的时间',
  `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改的时间',
  `delete` int(11) DEFAULT NULL,
  PRIMARY KEY (`student_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='学生表';
</code></pre>
<h3>1.2 创建了一个 Maven 项目</h3>
<p>对应的项目结构</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F768cec50a7630fb116d46680b06e72d1.png&amp;size=m" alt=""></p>
<h2><strong>2 if 标签</strong></h2>
<p>if 标签是我们最常使用的。 在查询、删除、更新的时候很可能会使用到。 必须结合 test 属性联合使用。</p>
<h3><strong>2.1 在 WHERE 条件中使用 if 标签</strong></h3>
<p>这是常见的一种现象， 我们在进行按条件查询的时候， 可能会有多种情况。</p>
<h4><strong>2.1.1 查询条件</strong></h4>
<p>根据输入的学生信息进行条件检索</p>
<blockquote>
 <ul>
  <li>
   <p>当只输入用户名时， 使用用户名进行模糊检索；</p>
  </li>
  <li>
   <p>当只输入性别时， 使用性别进行完全匹配</p>
  </li>
  <li>
   <p>当用户名和性别都存在时， 用这两个条件进行查询匹配查询</p>
  </li>
 </ul>
</blockquote>
<h4><strong>2.1.2 动态 SQL</strong></h4>
<p>接口函数</p>
<pre><code class="java"> /**
     * 根据输入的学生信息进行条件检索
     * 1. 当只输入用户名时， 使用用户名进行模糊检索；
     * 2. 当只输入邮箱时， 使用性别进行完全匹配
     * 3. 当用户名和性别都存在时， 用这两个条件进行查询匹配的用
     * @param student
     * @return
     */
     List&lt;Student&gt; selectByStudentSelective(Student student);
</code></pre>
<p>对应的动态 SQL</p>
<pre><code class="sql">&lt;select id="selectByStudentSelective" resultMap="BaseResultMap" parameterType="com.homejim.mybatis.entity.Student"&gt;
    select
    &lt;include refid="Base_Column_List" /&gt;
    from student
    where 1=1
    &lt;if test="name != null and name !=''"&gt;
      and name like concat('%', #{name}, '%')
    &lt;/if&gt;
    &lt;if test="sex != null"&gt;
      and sex=#{sex}
    &lt;/if&gt;
  &lt;/select&gt;
</code></pre>
<p>在此 SQL 语句中， where 1=1 是多条件拼接时的小技巧， 后面的条件查询就可以都用 and 了。</p>
<p>同时， 我们添加了 if 标签来处理动态 SQL</p>
<pre><code class="sql"> &lt;if test="name != null and name !=''"&gt;
      and name like concat('%', #{name}, '%')
    &lt;/if&gt;
    &lt;if test="sex != null"&gt;
      and sex=#{sex}
    &lt;/if&gt;
</code></pre>
<p>此 if 标签的 test 属性值是一个符合 OGNL 的表达式， 表达式可以是 true 或 false。 如果表达式返回的是数值， 则0为 false, 非 0 为 true;</p>
<h4><strong>2.1.3 测试</strong></h4>
<pre><code class="java">@Test
    public void selectByStudent() {
        SqlSession sqlSession = null;
        sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        Student search = new Student();
        search.setName("明");

        System.out.println("只有名字时的查询");
        List&lt;Student&gt; studentsByName = studentMapper.selectByStudentSelective(search);
        for (int i = 0; i &lt; studentsByName.size(); i++) {
            System.out.println(ToStringBuilder.reflectionToString(studentsByName.get(i), ToStringStyle.MULTI_LINE_STYLE));
        }

        search.setName(null);
        search.setSex((byte) 1);
        System.out.println("只有性别时的查询");
        List&lt;Student&gt; studentsBySex = studentMapper.selectByStudentSelective(search);
        for (int i = 0; i &lt; studentsBySex.size(); i++) {
            System.out.println(ToStringBuilder.reflectionToString(studentsBySex.get(i), ToStringStyle.MULTI_LINE_STYLE));
        }

        System.out.println("姓名和性别同时存在的查询");
        search.setName("明");
        List&lt;Student&gt; studentsByNameAndSex = studentMapper.selectByStudentSelective(search);
        for (int i = 0; i &lt; studentsByNameAndSex.size(); i++) {
            System.out.println(ToStringBuilder.reflectionToString(studentsByNameAndSex.get(i), ToStringStyle.MULTI_LINE_STYLE));
        }

        sqlSession.commit();
        sqlSession.close();
    }
</code></pre>
<p>只有名字时的查询， 发送的语句和结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F36eff5c91861bcb3138086045dc2b20e.png&amp;size=m" alt=""></p>
<p>查询的条件只发送了</p>
<pre><code class="sql">where 1=1 and name like concat('%', ?, '%') 
</code></pre>
<p>只有性别时的查询， 发送的语句和结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6a1ba4cf19d41495ccfed3180fc9585b.png&amp;size=m" alt=""></p>
<p>查询的条件只发送了</p>
<pre><code class="sql">where 1=1 and sex=? 
</code></pre>
<p>姓名和性别同时存在的查询， 发送的语句和结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F381edcc6e6088feb75fe6a251f32e1a3.png&amp;size=m" alt=""></p>
<p>查询条件</p>
<pre><code class="sql">where 1=1 and name like concat('%', ?, '%') and sex=? 
</code></pre>
<h3>2.2 在 UPDATE 更新列中使用 if 标签</h3>
<p>有时候我们不希望更新所有的字段， 只更新有变化的字段。</p>
<h3>2.2.1 更新条件</h3>
<p>只更新有变化的字段， 空值不更新。</p>
<h4>2.2.1 动态 SQL</h4>
<p>接口方法</p>
<pre><code class="java">/**
     * 更新非空属性
     */
    int updateByPrimaryKeySelective(Student record);
</code></pre>
<p>对应的 SQL</p>
<pre><code class="sql"> &lt;update id="updateByPrimaryKeySelective" parameterType="com.homejim.mybatis.entity.Student"&gt;
    update student
    &lt;set&gt;
      &lt;if test="name != null"&gt;
        `name` = #{name,jdbcType=VARCHAR},
      &lt;/if&gt;
      &lt;if test="phone != null"&gt;
        phone = #{phone,jdbcType=VARCHAR},
      &lt;/if&gt;
      &lt;if test="email != null"&gt;
        email = #{email,jdbcType=VARCHAR},
      &lt;/if&gt;
      &lt;if test="sex != null"&gt;
        sex = #{sex,jdbcType=TINYINT},
      &lt;/if&gt;
      &lt;if test="locked != null"&gt;
        locked = #{locked,jdbcType=TINYINT},
      &lt;/if&gt;
      &lt;if test="gmtCreated != null"&gt;
        gmt_created = #{gmtCreated,jdbcType=TIMESTAMP},
      &lt;/if&gt;
      &lt;if test="gmtModified != null"&gt;
        gmt_modified = #{gmtModified,jdbcType=TIMESTAMP},
      &lt;/if&gt;
    &lt;/set&gt;
    where student_id = #{studentId,jdbcType=INTEGER}
</code></pre>
<h4>2.2.3 测试</h4>
<pre><code class="java">@Test
    public void updateByStudentSelective() {
        SqlSession sqlSession = null;
        sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        Student student = new Student();
        student.setStudentId(1);
        student.setName("明明");
        student.setPhone("13838438888");
        System.out.println(studentMapper.updateByPrimaryKeySelective(student));

        sqlSession.commit();
        sqlSession.close();
    }
</code></pre>
<p>结果如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8465728a17f1357d2d8efe9a8fdda65d.png&amp;size=m" alt=""></p>
<h3>2.3 在 INSERT 动态插入中使用 if 标签</h3>
<p>我们插入数据库中的一条记录， 不是每一个字段都有值的， 而是动态变化的。 在这时候使用 if 标签， 可帮我们解决这个问题。</p>
<h4>2.3.1 插入条件</h4>
<p>只有非空属性才插入。</p>
<h4>2.3.2 动态SQL</h4>
<p>接口方法</p>
<pre><code class="java">/**
     * 非空字段才进行插入
     */
    int insertSelective(Student record);
</code></pre>
<p>对应的SQL</p>
<pre><code class="sql">&lt;insert id="insertSelective" parameterType="com.homejim.mybatis.entity.Student"&gt;
    insert into student
    &lt;trim prefix="(" suffix=")" suffixOverrides=","&gt;
      &lt;if test="studentId != null"&gt;
        student_id,
      &lt;/if&gt;
      &lt;if test="name != null"&gt;
        `name`,
      &lt;/if&gt;
      &lt;if test="phone != null"&gt;
        phone,
      &lt;/if&gt;
      &lt;if test="email != null"&gt;
        email,
      &lt;/if&gt;
      &lt;if test="sex != null"&gt;
        sex,
      &lt;/if&gt;
      &lt;if test="locked != null"&gt;
        locked,
      &lt;/if&gt;
      &lt;if test="gmtCreated != null"&gt;
        gmt_created,
      &lt;/if&gt;
      &lt;if test="gmtModified != null"&gt;
        gmt_modified,
      &lt;/if&gt;
    &lt;/trim&gt;
    &lt;trim prefix="values (" suffix=")" suffixOverrides=","&gt;
      &lt;if test="studentId != null"&gt;
        #{studentId,jdbcType=INTEGER},
      &lt;/if&gt;
      &lt;if test="name != null"&gt;
        #{name,jdbcType=VARCHAR},
      &lt;/if&gt;
      &lt;if test="phone != null"&gt;
        #{phone,jdbcType=VARCHAR},
      &lt;/if&gt;
      &lt;if test="email != null"&gt;
        #{email,jdbcType=VARCHAR},
      &lt;/if&gt;
      &lt;if test="sex != null"&gt;
        #{sex,jdbcType=TINYINT},
      &lt;/if&gt;
      &lt;if test="locked != null"&gt;
        #{locked,jdbcType=TINYINT},
      &lt;/if&gt;
      &lt;if test="gmtCreated != null"&gt;
        #{gmtCreated,jdbcType=TIMESTAMP},
      &lt;/if&gt;
      &lt;if test="gmtModified != null"&gt;
        #{gmtModified,jdbcType=TIMESTAMP},
      &lt;/if&gt;
    &lt;/trim&gt;
  &lt;/insert&gt;
</code></pre>
<p>这个 SQL 大家应该很熟悉， 毕竟是自动生成的。</p>
<h4><strong>2.3.3 测试</strong></h4>
<pre><code class="java">@Test
    public void insertByStudentSelective() {
        SqlSession sqlSession = null;
        sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        Student student = new Student();
        student.setName("小飞机");
        student.setPhone("13838438899");
        student.setEmail("xiaofeiji@qq.com");
        student.setLocked((byte) 0);
        System.out.println(studentMapper.insertSelective(student));
        sqlSession.commit();
        sqlSession.close();
    }
</code></pre>
<p>对应的结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3df21e0abd7688cb6f0b0059f756e9e6.png&amp;size=m" alt=""></p>
<p>SQL 中， 只有非空的字段才进行了插入。</p>
<h2>3 choose 标签</h2>
<p>choose when otherwise 标签可以帮我们实现 if else 的逻辑。一个 choose 标签至少有一个 when, 最多一个otherwise。</p>
<p>下面是一个查询的例子。</p>
<h3>3.1 查询条件</h3>
<p>假设 name 具有唯一性， 查询一个学生</p>
<blockquote>
 <ul>
  <li>当 studen_id 有值时， 使用 studen_id 进行查询；</li>
  <li>当 studen_id 没有值时， 使用 name 进行查询；</li>
  <li>否则返回空</li>
 </ul>
</blockquote>
<h3>3.2 动态SQL</h3>
<p>接口方法</p>
<pre><code class="java"> /**
     * - 当 studen_id 有值时， 使用 studen_id 进行查询；
     * - 当 studen_id 没有值时， 使用 name 进行查询；
     * - 否则返回空
     */
    Student selectByIdOrName(Student record);
</code></pre>
<p>对应的SQL</p>
<pre><code class="sql">&lt;select id="selectByIdOrName" resultMap="BaseResultMap" parameterType="com.homejim.mybatis.entity.Student"&gt;
    select
    &lt;include refid="Base_Column_List" /&gt;
    from student
    where 1=1
    &lt;choose&gt;
      &lt;when test="studentId != null"&gt;
        and student_id=#{studentId}
      &lt;/when&gt;
      &lt;when test="name != null and name != ''"&gt;
        and name=#{name}
      &lt;/when&gt;
      &lt;otherwise&gt;
        and 1=2
      &lt;/otherwise&gt;
    &lt;/choose&gt;
  &lt;/select&gt;
</code></pre>
<h3>3.3 测试</h3>
<pre><code class="java">@Test
    public void selectByIdOrName() {
        SqlSession sqlSession = null;
        sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        Student student = new Student();
        student.setName("小飞机");
        student.setStudentId(1);

        Student studentById = studentMapper.selectByIdOrName(student);
        System.out.println("有 ID 则根据 ID 获取");
        System.out.println(ToStringBuilder.reflectionToString(studentById, ToStringStyle.MULTI_LINE_STYLE));

        student.setStudentId(null);
        Student studentByName = studentMapper.selectByIdOrName(student);
        System.out.println("没有 ID 则根据 name 获取");
        System.out.println(ToStringBuilder.reflectionToString(studentByName, ToStringStyle.MULTI_LINE_STYLE));

        student.setName(null);
        Student studentNull = studentMapper.selectByIdOrName(student);
        System.out.println("没有 ID 和 name, 返回 null");
        Assert.assertNull(studentNull);

        sqlSession.commit();
        sqlSession.close();
    }
</code></pre>
<p>有 ID 则根据 ID 获取， 结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd079a5cefc6818fdcc9aebfd0bbd02e9.png&amp;size=m" alt=""></p>
<p>没有 ID 则根据 name 获取</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9404aa8417a25605081a58fe775f8adc.png&amp;size=m" alt=""></p>
<p>没有 ID 和 name, 返回 null</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2fc00db2ff492f94dc793b8faa45d615.png&amp;size=m" alt=""></p>
<h2>4 trim(set、where)</h2>
<p>这三个其实解决的是类似的问题。 如我们在写前面的[在 WHERE 条件中使用 if 标签] SQL 的时候， where 1=1 这个条件我们是不希望存在的。</p>
<h3>4.1 where</h3>
<h4>4.1.1 查询条件</h4>
<p>根据输入的学生信息进行条件检索。</p>
<blockquote>
 <ul>
  <li>
   <p>当只输入用户名时， 使用用户名进行模糊检索；</p>
  </li>
  <li>
   <p>当只输入性别时， 使用性别进行完全匹配</p>
  </li>
  <li>
   <p>当用户名和性别都存在时， 用这两个条件进行查询匹配查询</p>
  </li>
 </ul>
</blockquote>
<p>不使用 where 1=1。</p>
<h4>4.1.2 动态 SQL</h4>
<p>很显然， 我们要解决这几个问题</p>
<p>当条件都不满足时： 此时 SQL 中应该要不能有 where ， 否则导致出错 当 if 有条件满足时： SQL 中需要有 where， 且第一个成立的 if 标签下的 and \| or 等要去掉 这时候， 我们可以使用 where 标签。</p>
<p>接口方法</p>
<pre><code class="java">    /**
     * 根据输入的学生信息进行条件检索
     * 1. 当只输入用户名时， 使用用户名进行模糊检索；
     * 2. 当只输入邮箱时， 使用性别进行完全匹配
     * 3. 当用户名和性别都存在时， 用这两个条件进行查询匹配的用
     */
    List&lt;Student&gt; selectByStudentSelectiveWhereTag(Student student);
</code></pre>
<p>对应的 SQL</p>
<pre><code class="sql">&lt;select id="selectByStudentSelectiveWhereTag" resultMap="BaseResultMap" parameterType="com.homejim.mybatis.entity.Student"&gt;
    select
    &lt;include refid="Base_Column_List" /&gt;
    from student
   &lt;where&gt;
    &lt;if test="name != null and name !=''"&gt;
      and name like concat('%', #{name}, '%')
    &lt;/if&gt;
    &lt;if test="sex != null"&gt;
      and sex=#{sex}
    &lt;/if&gt;
   &lt;/where&gt;
  &lt;/select&gt;
</code></pre>
<h4>4.1.3 测试</h4>
<pre><code class="java">@Test
    public void selectByStudentWhereTag() {
        SqlSession sqlSession = null;
        sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        Student search = new Student();
        search.setName("明");
        System.out.println("只有名字时的查询");
        List&lt;Student&gt; studentsByName = studentMapper.selectByStudentSelectiveWhereTag(search);
        for (int i = 0; i &lt; studentsByName.size(); i++) {
            System.out.println(ToStringBuilder.reflectionToString(studentsByName.get(i), ToStringStyle.MULTI_LINE_STYLE));
        }
        search.setSex((byte) 1);
        System.out.println("姓名和性别同时存在的查询");
        List&lt;Student&gt; studentsBySex = studentMapper.selectByStudentSelectiveWhereTag(search);
        for (int i = 0; i &lt; studentsBySex.size(); i++) {
            System.out.println(ToStringBuilder.reflectionToString(studentsBySex.get(i), ToStringStyle.MULTI_LINE_STYLE));
        }
        System.out.println("姓名和性别都不存在时查询");
        search.setName(null);
        search.setSex(null);
        List&lt;Student&gt; studentsByNameAndSex = studentMapper.selectByStudentSelectiveWhereTag(search);
        for (int i = 0; i &lt; studentsByNameAndSex.size(); i++) {
            System.out.println(ToStringBuilder.reflectionToString(studentsByNameAndSex.get(i), ToStringStyle.MULTI_LINE_STYLE));
        }
        sqlSession.commit();
        sqlSession.close();
    }
</code></pre>
<p>只有名字时的查询, 有 where</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7ac02b00f32dd1d7b390f98f39408e86.png&amp;size=m" alt=""></p>
<p>姓名和性别同时存在的查询， 有 where</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8a34d1c84af9b755bbc988e286130132.png&amp;size=m" alt=""></p>
<p>姓名和性别都不存在时查询, 此时， where 不会再出现了。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77bc0c00f24fab4c3214a5e5e1dc3e7b.png&amp;size=m" alt=""></p>
<h3><strong>4.2 set</strong></h3>
<p>set 标签也类似， 在 [2.2 在 UPDATE 更新列中使用 if 标签] 中， 如果我们的方法 updateByPrimaryKeySelective 没有使用</p>
<h3>4.3 trim</h3>
<p>set 和 where 其实都是 trim 标签的一种类型， 该两种功能都可以使用 trim 标签进行实现。</p>
<h4><strong>4.3.1 trim 来表示 where</strong></h4>
<p>如以上的 where 标签， 我们也可以写成</p>
<pre><code class="sql">&lt;trim prefix="where" prefixOverrides="AND |OR"&gt;
&lt;/trim&gt;
</code></pre>
<p>表示当 trim 中含有内容时， 添加 where， 且第一个为 and 或 or 时， 会将其去掉。 而如果没有内容， 则不添加 where。</p>
<h4>4.3.2 trim 来表示 set</h4>
<p>相应的， set 标签可以如下表示</p>
<pre><code class="sql">&lt;trim prefix="SET" suffixOverrides=","&gt;
&lt;/trim&gt;
</code></pre>
<p>表示当 trim 中含有内容时， 添加 set， 且最后的内容为 , 时， 会将其去掉。 而没有内容， 不添加 set</p>
<h4>4.3.3 trim 的几个属性</h4>
<blockquote>
 <ul>
  <li>prefix: 当 trim 元素包含有内容时， 增加 prefix 所指定的前缀</li>
  <li>prefixOverrides: 当 trim 元素包含有内容时， 去除 prefixOverrides 指定的 前缀</li>
  <li>suffix: 当 trim 元素包含有内容时， 增加 suffix 所指定的后缀</li>
  <li>suffixOverrides： 当 trim 元素包含有内容时， 去除 suffixOverrides 指定的后缀</li>
 </ul>
</blockquote>
<h2>5 foreach 标签</h2>
<p>foreach 标签可以对数组， Map 或实现 Iterable 接口。</p>
<p>foreach 中有以下几个属性：</p>
<blockquote>
 <ul>
  <li>collection: 必填， 集合/数组/Map的名称.</li>
  <li>item: 变量名。 即从迭代的对象中取出的每一个值</li>
  <li>index: 索引的属性名。 当迭代的对象为 Map 时， 该值为 Map 中的 Key.</li>
  <li>open: 循环开头的字符串</li>
  <li>close: 循环结束的字符串</li>
  <li>separator: 每次循环的分隔符</li>
 </ul>
</blockquote>
<p>其他的比较好理解， collection 中的值应该怎么设定呢？</p>
<p>跟接口方法中的参数相关。</p>
<p><strong>（1）只有一个数组参数或集合参数</strong></p>
<p>默认情况： 集合collection=list， 数组是collection=array</p>
<p>推荐： 使用 @Param 来指定参数的名称， 如我们在参数前@Param(“ids”)， 则就填写 collection=ids</p>
<p><strong>（2） 多参数</strong></p>
<p>多参数请使用 @Param 来指定， 否则SQL中会很不方便</p>
<p><strong>（3）参数是Map</strong></p>
<p>指定为 Map 中的对应的 Key 即可。 其实上面的 @Param 最后也是转化为 Map 的。</p>
<p><strong>（4） 参数是对象</strong></p>
<p>使用属性.属性即可。</p>
<h3>5.1 在 where 中使用 foreach</h3>
<p>在 where条件中使用， 如按id集合查询， 按id集合删除等。</p>
<h4><strong>5.1.1 查询条件</strong></h4>
<p>我们希望查询用户 id 集合中的所有用户信息。</p>
<h4>5.1.2 动态 SQL</h4>
<p>函数接口</p>
<pre><code class="java">  /**
     * 获取 id 集合中的用户信息
     * @param ids
     * @return
     */
    List&lt;Student&gt; selectByStudentIdList(List&lt;Integer&gt; ids);
</code></pre>
<p>对应SQL</p>
<pre><code class="sql"> &lt;select id="selectByStudentIdList" resultMap="BaseResultMap"&gt;
    select
    &lt;include refid="Base_Column_List" /&gt;
    from student
    where student_id in
    &lt;foreach collection="list" item="id" open="(" close=")" separator="," index="i"&gt;
      #{id}
    &lt;/foreach&gt;
  &lt;/select&gt;
</code></pre>
<h4>5.1.3 测试</h4>
<pre><code class="java">@Test
    public void selectByStudentIdList() {
        SqlSession sqlSession = null;
        sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        List&lt;Integer&gt; ids = new LinkedList&lt;&gt;();
        ids.add(1);
        ids.add(3);

        List&lt;Student&gt; students = studentMapper.selectByStudentIdList(ids);
        for (int i = 0; i &lt; students.size(); i++) {
            System.out.println(ToStringBuilder.reflectionToString(students.get(i), ToStringStyle.MULTI_LINE_STYLE));
        }

        sqlSession.commit();
        sqlSession.close();
    }
</code></pre>
<p>结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc5daad0690c527fdafd3fe43fb7e9d20.png&amp;size=m" alt=""></p>
<h3>5.2 foreach 实现批量插入</h3>
<p>可以通过foreach来实现批量插入。</p>
<h4><strong>5.2.1 动态SQL</strong></h4>
<p>接口方法</p>
<pre><code class="java"> /**
     * 批量插入学生
     */
    int insertList(List&lt;Student&gt; students);
</code></pre>
<p>对应SQL</p>
<pre><code class="sql"> &lt;insert id="insertList"&gt;
    insert into student(name, phone, email, sex, locked)
    values
    &lt;foreach collection="list" item="student" separator=","&gt;
      (
      #{student.name}, #{student.phone},#{student.email},
      #{student.sex},#{student.locked}
      )
    &lt;/foreach&gt;
  &lt;/insert&gt;
</code></pre>
<h4>5.2.2 测试</h4>
<pre><code class="java">@Test
    public void insertList() {
        SqlSession sqlSession = null;
        sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        List&lt;Student&gt; students = new LinkedList&lt;&gt;();
        Student stu1 = new Student();
        stu1.setName("批量01");
        stu1.setPhone("13888888881");
        stu1.setLocked((byte) 0);
        stu1.setEmail("13888888881@138.com");
        stu1.setSex((byte) 1);
        students.add(stu1);

        Student stu2 = new Student();
        stu2.setName("批量02");
        stu2.setPhone("13888888882");
        stu2.setLocked((byte) 0);
        stu2.setEmail("13888888882@138.com");
        stu2.setSex((byte) 0);
        students.add(stu2);

        System.out.println(studentMapper.insertList(students));
        sqlSession.commit();
        sqlSession.close();
    }
</code></pre>
<p>结果</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F51976e32cacec59cf6f67004954574a3.png&amp;size=m" alt=""></p>
<h2><strong>6 bind 标签</strong></h2>
<p>bind 标签是通过 OGNL 表达式去定义一个上下文的变量， 这样方便我们使用。</p>
<p>如在 selectByStudentSelective 方法中， 有如下</p>
<pre><code class="sql">&lt;if test="name != null and name !=''"&gt;
      and name like concat('%', #{name}, '%')
    &lt;/if&gt;
</code></pre>
<p>在 <a href="https://cloud.tencent.com/product/cdb?from=10680">MySQL</a> 中， 该函数支持多参数， 但在 Oracle 中只支持两个参数。 那么我们可以使用 bind 来让该 SQL 达到支持两个数据库的作用</p>
<pre><code class="sql">&lt;if test="name != null and name !=''"&gt;
     &lt;bind name="nameLike" value="'%'+name+'%'"/&gt;
     and name like #{nameLike}
&lt;/if&gt;
</code></pre>
<p>更改后的查询结果如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa8a59b08a6677581cb6fd92ee9062c7f.png&amp;size=m" alt=""></p>
<h2>7 代码</h2>
<p>使用示例：</p>
<blockquote>
 <p>https://github.com/homejim/mybatis-examples</p>
</blockquote>
<p><a href="https://download.csdn.net/download/zuihongyan518/11961784">https://download.csdn.net/download/zuihongyan518/11961784</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102935745</p>]]></description><guid isPermaLink="false">/archives/0551e9ae-2d14-4f2d-94e6-9b5762c57065</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:43:43 GMT</pubDate></item><item><title><![CDATA[测试中遇到 kafka 报错：Connection to node -1 could not be established. Broker may not be available.]]></title><link>https://wiki.dgfygk.com/archives/4a8edc3e-3e27-4cde-93c3-5a2ac6df885f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E4%B8%AD%E9%81%87%E5%88%B0%20kafka%20%E6%8A%A5%E9%94%99%EF%BC%9AConnection%20to%20node%20-1%20could%20not%20be%20established.%20Broker%20may%20not%20be%20available.&amp;url=/archives/4a8edc3e-3e27-4cde-93c3-5a2ac6df885f" width="1" height="1" alt="" style="opacity:0;">
<p>原文链接：<a href="https://blog.csdn.net/q1177660557/article/details/79000524">https://blog.csdn.net/q1177660557/article/details/79000524</a></p>
<p>kafka报错内容：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F20643dbe31e53b11a1cbb91bd315d941.png&amp;size=m" alt=""></p>
<p>原因：因为你的配置文件中的PLAINTEXT跟你请求的内容不同。</p>
<p>举例来说，我在配置文件里配置的listeners=PLAINTEXT://10.127.96.151:9092，但是我想测试的时候请求的是./kafka-console-consumer.sh –bootstrap-server localhost:9092 –topic topic1 –from-beginning</p>
<p>正确的应该是./kafka-console-consumer.sh –bootstrap-server 10.127.96.151:9092 –topic topic1 –from-beginning</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102738201</p>]]></description><guid isPermaLink="false">/archives/4a8edc3e-3e27-4cde-93c3-5a2ac6df885f</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:42:32 GMT</pubDate></item><item><title><![CDATA[根据请求的真实IP地址，获取请求的位置信息]]></title><link>https://wiki.dgfygk.com/archives/e3e1a49b-be50-4fcd-bc72-d78002d16d4c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%A0%B9%E6%8D%AE%E8%AF%B7%E6%B1%82%E7%9A%84%E7%9C%9F%E5%AE%9EIP%E5%9C%B0%E5%9D%80%EF%BC%8C%E8%8E%B7%E5%8F%96%E8%AF%B7%E6%B1%82%E7%9A%84%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF&amp;url=/archives/e3e1a49b-be50-4fcd-bc72-d78002d16d4c" width="1" height="1" alt="" style="opacity:0;">
<p>首先，需要导入maven包</p>
<pre><code class="html">&lt;!-- https://mvnrepository.com/artifact/org.lionsoul/ip2region --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.lionsoul&lt;/groupId&gt;
    &lt;artifactId&gt;ip2region&lt;/artifactId&gt;
    &lt;version&gt;1.7.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>获取真实IP地址的方法：getRealIp.java</p>
<p>获取请求主机IP地址,如果通过代理进来，则透过防火墙获取真实IP地址</p>
<pre><code class="java">   /**
     * 获取请求主机IP地址,如果通过代理进来，则透过防火墙获取真实IP地址
     *
     * @param request
     * @version 1.0
     * @date 2019/5/14 14:51
     */
    public static String getRealIp(HttpServletRequest request) {
        // 获取请求主机IP地址,如果通过代理进来，则透过防火墙获取真实IP地址

        String ip = request.getHeader("X-Forwarded-For");
        if (log.isInfoEnabled()) {
            log.info("getIpAddress(HttpServletRequest) - X-Forwarded-For - String ip=" + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } else if (ip.length() &gt; 15) {
            String[] ips = ip.split(",");
            for (int index = 0; index &lt; ips.length; index++) {
                String strIp = ips[index];
                if (!("unknown".equalsIgnoreCase(strIp))) {
                    ip = strIp;
                    break;
                }
            }
        }
        return ip;
    }
</code></pre>
<p>根据IP获取位置信息的 getUserAreaIdFromRequest.java</p>
<pre><code class="java">    /**
     * 根据ip 获取用户的地区
     *
     * @param request
     * @date 2019/5/14 15:31
     */
    public static String getUserAreaIdFromRequest(HttpServletRequest request) {
        String realIp = GetRealIpUtil.getRealIp(request);
        log.info("==&gt; 从request中获取的真实Ip地址 :{}", realIp);
        String areaName = null;
        if (StringUtils.isBlank(realIp)) {
            return areaName;
        }
        String ipInfo = GetRealIpUtil.getIpInfo(realIp);
        log.info("ipInfo----&gt;:{}", ipInfo);
        if (StringUtils.isNotBlank(ipInfo)) {
            // 1:内网ip (0|0|0|0|内网IP|内网IP|16392)
            // 0|0|0|0|内网IP|内网IP|16392
            // 0|中国|0|北京|北京市|移动|183155
            // 0|中国|0|海南省|0|电信|153149
            // 0|中国|0|海南省|海口市|电信|153094
            String[] ipInfoArr = StringUtils.split(ipInfo, "|");
            if (ipInfoArr != null &amp;&amp; ipInfoArr.length == 7) {
                String countryName = ipInfoArr[1];
                String provinceName = ipInfoArr[3];
                String cityName = ipInfoArr[4];
                if ("0".equals(countryName) &amp;&amp; "内网IP".equals(provinceName) &amp;&amp; "内网IP".equals(cityName)) {
                    areaName = "中国";
                } else {
                    if ("中国".equals(countryName)) {
                        //省份不为空
                        if (StringUtils.isNotBlank(provinceName)) {
                            // 去掉"省"字
                            if (provinceName.contains("省")) {
                                provinceName = provinceName.substring(0, provinceName.length() - 1);
                            }
                        } else {
                            if (StringUtils.isNotBlank(cityName)) {
                                // 去掉"市"字
                                cityName = cityName.substring(0, cityName.length() - 1);
                                Areas.Area area = Areas.instance.getAreaByName(cityName);
                            } else {
                                areaName = "中国";
                            }
                        }

                    }
                }
                log.info("从ip中获取的地区名称---&gt; 国家：{}, 省份：{}, 城市：{}", countryName, provinceName, cityName);
            }
        }
        log.info("对应的地区id:{}", areaName);
        return areaName;
    }
</code></pre>
<pre><code>```java
private static String getIpInfo(String ip) {
        String ipInfo = null;
        try {
            DbConfig config = new DbConfig();
            InputStream inputStream = GetRealIpUtil.class.getClassLoader().getResourceAsStream("ip2region/ip2region.db");
            log.info("inputStream ----&gt;:{}", inputStream);
            byte[] mybyte = IOUtils.toByteArray(inputStream);
            DbSearcher searcher = new DbSearcher(config, mybyte);
            DataBlock dataBlock = searcher.memorySearch(ip);
            ipInfo = dataBlock.toString();
            if (StringUtils.isNotBlank(ipInfo)) {
                ipInfo = new String(ipInfo.getBytes("gbk"), StandardCharsets.UTF_8);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ipInfo;
    }
</code></pre>
<p>ip2region/ip2region.db 可以自己生成，生成方法自行百度，也可以去下载已经生成号的。下载地址：<a href="https://download.csdn.net/download/zuihongyan518/11258365">https://download.csdn.net/download/zuihongyan518/11258365</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/93647648</p>]]></description><guid isPermaLink="false">/archives/e3e1a49b-be50-4fcd-bc72-d78002d16d4c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:42:09 GMT</pubDate></item><item><title><![CDATA[微信公众号开发之获取access_token]]></title><link>https://wiki.dgfygk.com/archives/8a7e5d6d-2247-47fb-b819-5a5ca13c3e58</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E5%BC%80%E5%8F%91%E4%B9%8B%E8%8E%B7%E5%8F%96access_token&amp;url=/archives/8a7e5d6d-2247-47fb-b819-5a5ca13c3e58" width="1" height="1" alt="" style="opacity:0;">
<p><strong>1、access_token的重要性</strong></p>
<blockquote>
 <ul>
  <li><strong>access_token的作用：</strong></li>
 </ul>
 <p>官网给出的解释：access_token是公众号的全局唯一接口调用凭据，公众号调用各接口时都需使用access_token</p>
 <ul>
  <li><strong>开发者需要注意的是：</strong></li>
 </ul>
 <p>（1）需要进行妥善保存。</p>
 <p>（2）access_token的存储至少要保留512个字符空间。</p>
 <p>（3）access_token的有效期目前为2个小时，需定时刷新，重复获取将导致上次获取的access_token失效。</p>
</blockquote>
<p><strong>2、公众平台的API调用所需的access_token的使用及生成方式说明：</strong></p>
<p>（1）建议公众号开发者使用中控服务器统一获取和刷新access_token，其他业务逻辑服务器所使用的access_token均来自于该中控服务器，不应该各自去刷新，否则容易造成冲突，导致access_token覆盖而影响业务；</p>
<p>（2）目前access_token的有效期通过返回的expire_in来传达，目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中，中控服务器可对外继续输出的老access_token，此时公众平台后台会保证在5分钟内，新老access_token都可用，这保证了第三方业务的平滑过渡；</p>
<p>（3）access_token的有效时间可能会在未来有调整，所以中控服务器不仅需要内部定时主动刷新，还需要提供被动刷新access_token的接口，这样便于业务服务器在API调用获知access_token已超时的情况下，可以触发access_token的刷新流程。</p>
<p><strong>3、 access_token的生成说明</strong></p>
<p>公众号和小程序均可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在"微信公众平台-开发-基本配置"页中获得（需要已经成为开发者，且帐号没有异常状态）。</p>
<p>**调用接口时，请登录"微信公众平台-开发-基本配置"提前将服务器IP地址添加到IP白名单中，点击查看设置方法，否则将无法调用成功。</p>
<p>**小程序无需配置IP白名单。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa8ecfbf53715ebe37a0b7d8d2d1fa449.png&amp;size=m" alt=""></p>
<p><strong>4、接口调用请求说明</strong></p>
<blockquote>
 <p>https请求方式: GET</p>
 <p>https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential\&amp;appid=APPID\&amp;secret=APPSECRET</p>
</blockquote>
<p><strong>4.1、参数说明</strong></p>
<p>| 参数 | 是否必须 | 说明 |
 <br>
 |————|——|———————————–|
 <br>
 | grant_type | 是 | 获取access_token填写client_credential |
 <br>
 | appid | 是 | 第三方用户唯一凭证 |
 <br>
 | secret | 是 | 第三方用户唯一凭证密钥，即appsecret |</p>
<p><strong>4.2、返回说明</strong></p>
<p><strong>4.2.1、正常情况下，微信会返回下述JSON数据包给公众号：</strong></p>
<pre><code>{"access_token":"ACCESS_TOKEN","expires_in":7200}
</code></pre>
<p><strong>参数说明</strong></p>
<p>| 参数 | 说明 |
 <br>
 |————–|————-|
 <br>
 | access_token | 获取到的凭证 |
 <br>
 | expires_in | 凭证有效时间，单位：秒 |</p>
<p><strong>4.2.3、错误时微信会返回错误码等信息，JSON数据包示例如下（该示例为AppID无效错误）:</strong></p>
<pre><code>{"errcode":40013,"errmsg":"invalid appid"}
</code></pre>
<p><strong>返回码说明</strong></p>
<p>| 返回码 | 说明 |
 <br>
 |——-|———————————————————|
 <br>
 | -1 | 系统繁忙，此时请开发者稍候再试 |
 <br>
 | 0 | 请求成功 |
 <br>
 | 40001 | AppSecret错误或者AppSecret不属于这个公众号，请开发者确认AppSecret的正确性 |
 <br>
 | 40002 | 请确保grant_type字段值为client_credential |
 <br>
 | 40164 | 调用接口的IP地址不在白名单中，请在接口IP白名单中进行设置。（小程序及小游戏调用不要求IP地址在白名单内。） |</p>
<p><strong>5、java接口开发</strong></p>
<p><strong>5.1 AccessToken实体类</strong></p>
<pre><code class="java">public class AccessToken {
    private String accessToken;//令牌
    private Integer expires;//过期时间：秒

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public Integer getExpires() {
        return expires;
    }

    public void setExpires(Integer expires) {
        this.expires = expires;
    }
}
</code></pre>
<p><strong>5.2、获取AccessToken的工具方法：</strong></p>
<p>GetWxAccessTokenUtil类中静态常量</p>
<pre><code class="java">//获取ACCESS_TOKEN的URL
private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid=APPID&amp;secret=APPSECRET";
//appID
private static final String APPID = "wx503297a5bc732744";
//appsecret
private static final String APPSECRET = "ab7c6be47ffe47ef4b44d13502afc60a";
</code></pre>
<p>获取AccessToken的工具方法：</p>
<pre><code class="java">public static String getAccessToken(){
        String accessToken = "";
        // 创建HttpClientBuilder
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // HttpClient
        CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
        String url = ACCESS_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET",APPSECRET);
        HttpGet httpGet = new HttpGet(url);
//        HttpGet httpGet = new HttpGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid=" + APPID + "&amp;secret=" + APPSECRET);
        try {
            // 执行get请求
            HttpResponse httpResponse = closeableHttpClient.execute(httpGet);
            // 获取响应消息实体
            HttpEntity entity = httpResponse.getEntity();
            // 判断实体是否为空
            if (entity != null) {
                String entityString = EntityUtils.toString(entity);
                // AccessToken po = JSON.parseObject(entityString, AccessToken.class);
                JSON parse = (JSON) JSON.parse(entityString);
                AccessToken po = JSON.toJavaObject(parse, AccessToken.class);
                accessToken = po.getAccessToken();
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return accessToken;
    }
</code></pre>
<p>该方法需要导入在pom.xml中导入"fastjson"的jar包：</p>
<blockquote>
 <p>
  <dependency>
   <br>
   <groupid>com.alibaba</groupid>
   <br>
   <artifactid>fastjson</artifactid>
   <br>
   <version>1.2.24</version>
   <br>
  </dependency>
 </p>
</blockquote>
<p>编写一个main方法进行测试一下：</p>
<pre><code class="java">public static void main(String[] args) {
        String accessToken = getAccessToken();
        tokenMapp.saveAccessToken(accessToken);
    }
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102591597</p>]]></description><guid isPermaLink="false">/archives/8a7e5d6d-2247-47fb-b819-5a5ca13c3e58</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 09:41:30 GMT</pubDate></item><item><title><![CDATA[微信公众号——自定义菜单的创建]]></title><link>https://wiki.dgfygk.com/archives/23208390-2cae-4314-abca-1732698fe7a9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E2%80%94%E2%80%94%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8F%9C%E5%8D%95%E7%9A%84%E5%88%9B%E5%BB%BA&amp;url=/archives/23208390-2cae-4314-abca-1732698fe7a9" width="1" height="1" alt="" style="opacity:0;">
<p><a href="https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html">自定义菜单创建微信公众号官方文档</a></p>
<p><strong>1、自定义菜单能够帮助公众号丰富界面，让用户更好更快地理解公众号的功能。</strong></p>
<p>开启自定义菜单后，公众号界面如图所示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F95549108b4d8eee08c1d71f298b82182.png&amp;size=m" alt=""></p>
<blockquote>
 <p>注意：</p>
 <ol>
  <li>
   <p>自定义菜单最多包括3个一级菜单，每个一级菜单最多包含5个二级菜单。</p>
  </li>
  <li>
   <p>一级菜单最多4个汉字，二级菜单最多7个汉字，多出来的部分将会以”…“代替。</p>
  </li>
  <li>
   <p>创建自定义菜单后，菜单的刷新策略是，在用户进入公众号会话页或公众号profile页时，如果发现上一次拉取菜单的请求在5分钟以前，就会拉取一下菜单，如果菜单有更新，就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注，则可以看到创建后的效果。
    <br>
    自定义菜单的按钮类型有很多，比如click、view、scancode_push、scancode_waitmsg等</p>
  </li>
  <li>
   <p>click：点击推事件用户点击click类型按钮后，微信服务器会通过消息接口推送消息类型为event的结构给开发者（参考消息接口指南），并且带上按钮中开发者填写的key值，开发者可以通过自定义的key值与用户进行交互；</p>
  </li>
  <li>
   <p>view：跳转URL用户点击view类型按钮后，微信客户端将会打开开发者在按钮中填写的网页URL，可与网页授权获取用户基本信息接口结合，获得用户基本信息。</p>
  </li>
  <li>
   <p>scancode_push：扫码推事件用户点击按钮后，微信客户端将调起扫一扫工具，完成扫码操作后显示扫描结果（如果是URL，将进入URL），且会将扫码的结果传给开发者，开发者可以下发消息。</p>
  </li>
  <li>
   <p>scancode_waitmsg：扫码推事件且弹出"消息接收中"提示框用户点击按钮后，微信客户端将调起扫一扫工具，完成扫码操作后，将扫码的结果传给开发者，同时收起扫一扫工具，然后弹出"消息接收中"提示框，随后可能会收到开发者下发的消息。</p>
  </li>
  <li>
   <p>pic_sysphoto：弹出系统拍照发图用户点击按钮后，微信客户端将调起系统相机，完成拍照操作后，会将拍摄的相片发送给开发者，并推送事件给开发者，同时收起系统相机，随后可能会收到开发者下发的消息。</p>
  </li>
  <li>
   <p>pic_photo_or_album：弹出拍照或者相册发图用户点击按钮后，微信客户端将弹出选择器供用户选择"拍照"或者"从手机相册选择”。用户选择后即走其他两种流程。</p>
  </li>
  <li>
   <p>pic_weixin：弹出微信相册发图器用户点击按钮后，微信客户端将调起微信相册，完成选择操作后，将选择的相片发送给开发者的服务器，并推送事件给开发者，同时收起相册，随后可能会收到开发者下发的消息。</p>
  </li>
  <li>
   <p>location_select：弹出地理位置选择器用户点击按钮后，微信客户端将调起地理位置选择工具，完成选择操作后，将选择的地理位置发送给开发者的服务器，同时收起位置选择工具，随后可能会收到开发者下发的消息。</p>
  </li>
  <li>
   <p>media_id：下发消息（除文本消息）用户点击media_id类型按钮后，微信服务器会将开发者填写的永久素材id对应的素材下发给用户，永久素材类型可以是图片、音频、视频、图文消息。请注意：永久素材id必须是在"素材管理/新增永久素材"接口上传后获得的合法id。</p>
  </li>
  <li>
   <p>view_limited：跳转图文消息URL用户点击view_limited类型按钮后，微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL，永久素材类型只支持图文消息。请注意：永久素材id必须是在"素材管理/新增永久素材"接口上传后获得的合法id。</p>
  </li>
 </ol>
</blockquote>
<p><strong>请注意，3到8的所有事件，仅支持微信iPhone5.4.1以上版本，和Android5.4以上版本的微信用户，旧版本微信用户点击后将没有回应，开发者也不能正常接收到事件推送。9和10，是专门给第三方平台旗下未微信认证（具体而言，是资质认证未通过）的订阅号准备的事件类型，它们是没有事件推送的，能力相对受限，其他类型的公众号不必使用。</strong></p>
<p><strong>接口调用请求说明</strong></p>
<blockquote>
 <p>http请求方式：POST（请使用https协议）</p>
 <p><a href="https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN">https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN</a></p>
</blockquote>
<p>菜单的创建以click、view两种进行示例，其他按钮方式所传入的json数据格式参考<a href="https://mp.weixin.qq.com/wiki?t=resource/res_main&amp;id=mp1421141013">自定义菜单创建微信公众号官方文档</a></p>
<p><strong>2、json字符串所对应的对象封装</strong></p>
<p>click和view的请求示例:</p>
<pre><code class="java">{
     "button":[
     {  
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
      },
      {
           "name":"菜单",
           "sub_button":[
           {    
               "type":"view",
               "name":"搜索",
               "url":"http://www.soso.com/"
            },
            {
                 "type":"miniprogram",
                 "name":"wxa",
                 "url":"http://mp.weixin.qq.com",
                 "appid":"wx286b93c14bbf93aa",
                 "pagepath":"pages/lunar/index"
             },
            {
               "type":"click",
               "name":"赞一下我们",
               "key":"V1001_GOOD"
            }]
       }]
 }
</code></pre>
<p>通过这段示例可以看出创建按钮需传入一段json字符串，而且一个button键对应按钮所形成的数组（一级菜单），在按钮数组中还存在sub_button键所对应的子按钮数组（二级菜单）；所以最外层可以创建为WxButton类，在类中只有一个属性，属性名为button，类型为List。观察发现每个按钮都有name字段，所以创建一个BaseButton抽象类，再类中只提供一个属性，属性名为name，类型为String。每个不同类型的按钮都继承自BaseButton类去构建不同类型的pojo类；如果菜单中包含二级菜单，就创建类继承自BaseButton并提供一个名为sub_button的List<basebutton>类型的属性，来代表一级菜单并包含子菜单这种情形。</basebutton></p>
<p><strong>参数说明</strong></p>
<p>| 参数 | 是否必须 | 说明 |
 <br>
 |————|—————————–|———————————————————————-|
 <br>
 | button | 是 | 一级菜单数组，个数应为1\~3个 |
 <br>
 | sub_button | 否 | 二级菜单数组，个数应为1\~5个 |
 <br>
 | type | 是 | 菜单的响应动作类型，view表示网页类型，click表示点击类型，miniprogram表示小程序类型 |
 <br>
 | name | 是 | 菜单标题，不超过16个字节，子菜单不超过60个字节 |
 <br>
 | key | click等点击类型必须 | 菜单KEY值，用于消息接口推送，不超过128字节 |
 <br>
 | url | view、miniprogram类型必须 | 网页 链接，用户点击菜单可打开链接，不超过1024字节。 type为miniprogram时，不支持小程序的老版本客户端将打开本url。 |
 <br>
 | media_id | media_id类型和view_limited类型必须 | 调用新增永久素材接口返回的合法media_id |
 <br>
 | appid | miniprogram类型必须 | 小程序的appid（仅认证公众号可配置） |
 <br>
 | pagepath | miniprogram类型必须 | 小程序的页面路径 |</p>
<p><strong>3、Java示例</strong></p>
<p>3.1、创建抽象类BaseButton</p>
<pre><code class="java">public class BaseButton {
    /**
     * 菜单名字
     */
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BaseButton(String name) {
        this.name = name;
    }
}
</code></pre>
<p>3.2、创建类型为Click的ClickButton类，并继承自BaseButton</p>
<pre><code class="java">public class ClickButton extends BaseButton{
    /**
     * 菜单类型
     */
    private String type = "click";
    /**
     * 唯一键
     */
    private String key;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public ClickButton(String name, String key) {
        super(name);
        this.key = key;
    }
}
</code></pre>
<p>3.3、创建按钮类型为View的ViewButton类，并继承自BaseButton</p>
<pre><code class="java">public class ViewButton extends BaseButton {
    /**
     * 菜单类型
     */
    private String type = "view";
    /**
     * 跳转地址
     */
    private String url;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public ViewButton(String name, String url) {
        super(name);
        this.url = url;
    }
}
</code></pre>
<p>3.4、创建有子菜单的菜单，类名为SubButton，继承自BaseButton</p>
<pre><code class="java">public class SubButton extends BaseButton{
    private List&lt;BaseButton&gt; sub_button = new ArrayList&lt;&gt;();

    public List&lt;BaseButton&gt; getSub_button() {
        return sub_button;
    }

    public void setSub_button(List&lt;BaseButton&gt; sub_button) {
        this.sub_button = sub_button;
    }

    public SubButton(String name) {
        super(name);
    }
}
</code></pre>
<p>3.5、创建名为WxButton的类用于生成最终json字符串的类</p>
<pre><code class="java">public class WxButton {

    private List&lt;BaseButton&gt; button = new ArrayList&lt;&gt;();

    public List&lt;BaseButton&gt; getButton() {
        return button;
    }

    public void setButton(List&lt;BaseButton&gt; button) {
        this.button = button;
    }
}
</code></pre>
<p>4、利用封装的pojo创建自定义菜单</p>
<p>常见名为CreatButtonUtil，在main方法中调用creatWxButton私有类方法进行菜单的创建</p>
<p>4.1、 将所需要用到的文字定义为静态常量</p>
<pre><code class="java">private static final String CREAT_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

    private static final String ONE_MENU_TITLE = "查找路线";
    public static final String ONE_MENU_TITLEKEY = "查找路线KEY";

    private static final String TWO_MENU_TITLE = "产品服务";
    private static final String TWO_MENU_SUBTITLE_ONE = "服务范围";
    private static final String TWO_MENU_SUBTITLE_ONEURL = "http://www.epochyoosure.com/fangan/";
    private static final String TWO_MENU_SUBOTITLE_TWO = "服务项目";
    private static final String TWO_MENU_SUBTITLE_TWOURL = "http://www.epochyoosure.com/a/youxuechanpin/";
    private static final String TWO_MENU_SUBTITLE_THREE = "新闻资讯";
    private static final String TWO_MENU_SUBTITLE_THREEURL = "http://www.epochyoosure.com/zixun/";

    private static final String THREE_MENU_TITLE = "艾派格";
    private static final String THREE_SUBTITLE_ONE = "私人订制";
    private static final String THREE_SUBTITLE_ONEKEY = "私人订制KEY";
    private static final String THREE_SUBTITLE_TWO = "关于我们";
    private static final String THREE_MENU_SUBTITLE_THREEURL = "http://www.epochyoosure.com/index.html";
</code></pre>
<p>4.2、创建creatWxButton私有类方法，在方法中创建菜单对象，并将对象转为json字符串， 调用NetManagerUtil所提供的post方法请求网络，传入所需要的参数，创建菜单</p>
<pre><code class="java">private static void creatWxButton() {
        //第一个菜单
        ClickButton oneButton = new ClickButton(ONE_MENU_TITLE,ONE_MENU_TITLEKEY);
        //第二个菜单
        SubButton twoButton = new SubButton(TWO_MENU_TITLE);
        //第二个菜单的子菜单
        ViewButton oneSubButton = new ViewButton(TWO_MENU_SUBTITLE_ONE,TWO_MENU_SUBTITLE_ONEURL);
        ViewButton twoSubButton = new ViewButton(TWO_MENU_SUBOTITLE_TWO,TWO_MENU_SUBTITLE_TWOURL);
        ViewButton threeSubButton = new ViewButton(TWO_MENU_SUBTITLE_THREE,TWO_MENU_SUBTITLE_THREEURL);
        twoButton.getSub_button().add(threeSubButton);
        twoButton.getSub_button().add(twoSubButton);
        twoButton.getSub_button().add(oneSubButton);
        //第三个菜单
        SubButton threeButton = new SubButton(THREE_MENU_TITLE);
        //第三个菜单的子菜单
        ClickButton subButtonOne = new ClickButton(THREE_SUBTITLE_ONE,THREE_SUBTITLE_ONEKEY);
        ViewButton subButtonTwo = new ViewButton(THREE_SUBTITLE_TWO,THREE_MENU_SUBTITLE_THREEURL);
        threeButton.getSub_button().add(subButtonOne);
        threeButton.getSub_button().add(subButtonTwo);
        //添加到button对象中
        WxButton wxButton = new WxButton();
        wxButton.getButton().add(oneButton);
        wxButton.getButton().add(twoButton);
        wxButton.getButton().add(threeButton);
        //转json
        String jsonStr = JSONObject.toJSONString(wxButton);
        //替换token
        String url = CREAT_MENU_URL.replace("ACCESS_TOKEN", GetWxAccessTokenUtil.getAccessToken2());
        //调用网络请求
        NetManagerUtil.post(url,jsonStr);
    }
</code></pre>
<p>4.3、在类中编写main方法进行测试：</p>
<pre><code class="java">public static void main(String[] args) {
        creatWxButton();
    }
</code></pre>
<p><strong>返回结果</strong></p>
<p>正确时的返回JSON数据包如下：</p>
<pre><code>{"errcode":0,"errmsg":"ok"}
</code></pre>
<p>错误时的返回JSON数据包如下（示例为无效菜单名长度）：</p>
<pre><code>{"errcode":40018,"errmsg":"invalid button name size"}
</code></pre>
<p>当然还可以在 <a href="https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&amp;type=%E5%9F%BA%E7%A1%80%E6%94%AF%E6%8C%81&amp;form=%E8%8E%B7%E5%8F%96access_token%E6%8E%A5%E5%8F%A3%20/token">微信公众平台接口调试工具</a> 里面手动添加，修改。后续再更新手动添加的文章。。。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102592980</p>]]></description><guid isPermaLink="false">/archives/23208390-2cae-4314-abca-1732698fe7a9</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 09:41:12 GMT</pubDate></item><item><title><![CDATA[JSON转对象，对象转JSON]]></title><link>https://wiki.dgfygk.com/archives/4573d1c5-18a4-4d4d-885d-ca046e19d83e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JSON%E8%BD%AC%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%AF%B9%E8%B1%A1%E8%BD%ACJSON&amp;url=/archives/4573d1c5-18a4-4d4d-885d-ca046e19d83e" width="1" height="1" alt="" style="opacity:0;">
<p>需要的jar包：</p>
<pre><code class="css">     工具包为阿里fastjson
     &lt;dependency&gt;
         &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
         &lt;artifactId&gt;fastjson&lt;/artifactId&gt;
         &lt;version&gt;1.2.44&lt;/version&gt;
     &lt;/dependency&gt;
</code></pre>
<p>具体实现DEMO：</p>
<pre><code class="java">        LocalDateTime now = LocalDate.now().atStartOfDay();
        // java.time.LocalDateTime --&gt; java.util.Date
        Date date = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
        Date date1 = Date.from(now.plusDays(1).atZone(ZoneId.systemDefault()).toInstant());

        ActivityTemplatePO templatePO = new ActivityTemplatePO(1,1,date,date1,"NO", date);
        // 对象 转 JSON对象
        Object toJSON = JSONObject.toJSON(templatePO);
        System.out.println(toJSON);

        // 对象 转 JSON串
        String jsonStr  = JSONObject.toJSONString(templatePO);
        System.out.println(jsonStr);

        // JSON串 转 对象
        ActivityTemplatePO templatePO1 = JSON.parseObject(jsonStr, ActivityTemplatePO.class);
        System.out.println(templatePO1);

        //JSON内容为{"activityTemplateNo":1,"createTime":1568131200,"endTime":1568217600,"id":724,"startTime":1568131200,"status":"NO"}
        JSONObject map=new JSONObject();
        map.put("activityTemplateNo", "1");
        map.put("createTime", "1568131200");
        map.put("startTime", "1568217600");
        map.put("endTime", "1568131200");
        map.put("id", "724");
        map.put("status", "NO");
        // JSON 转 对象
        ActivityTemplatePO toJavaObject = JSON.toJavaObject(map, ActivityTemplatePO.class);
        System.out.println(toJavaObject.toString());

        // JSON 转 数组
        String jsonStr2 = "[{\"activityTemplateNo\":\"1\",\"createTime\":\"1568131200\",\"endTime\":\"1568217600\",\"id\":\"1\",\"startTime\":\"1568131200\",\"status\":\"NO\"}" +
                ",{\"activityTemplateNo\":\"2\",\"createTime\":\"1568131200\",\"endTime\":\"1568217600\",\"id\":\"2\",\"startTime\":\"1568131200\",\"status\":\"NO\"}" +
                ",{\"activityTemplateNo\":\"3\",\"createTime\":\"1568131200\",\"endTime\":\"1568217600\",\"id\":\"3\",\"startTime\":\"1568131200\",\"status\":\"NO\"}" +
                ",{\"activityTemplateNo\":\"4\",\"createTime\":\"1568131200\",\"endTime\":\"1568217600\",\"id\":\"4\",\"startTime\":\"1568131200\",\"status\":\"NO\"}]";
        JSONArray templateDetails = JSONObject.parseArray(jsonStr2);
        //将JSON数组转换成数组对象
        List&lt;ActivityTemplatePO&gt; list = templateDetails.toJavaObject(templateDetails, List.class);
        List list2 = JSON.toJavaObject(templateDetails, List.class);
        if (list.size() &gt; 0) {
            for (int i = 0; i &lt; list.size(); i++) {
                System.out.println(list.get(i));
            }
        }
        System.out.println(list2);

    }
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100745450</p>]]></description><guid isPermaLink="false">/archives/4573d1c5-18a4-4d4d-885d-ca046e19d83e</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:38:53 GMT</pubDate></item><item><title><![CDATA[mybits 获取新增数据在数据库表中对应的id]]></title><link>https://wiki.dgfygk.com/archives/347b864a-1a88-4395-b7be-8bd31dcff7b7</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=mybits%20%E8%8E%B7%E5%8F%96%E6%96%B0%E5%A2%9E%E6%95%B0%E6%8D%AE%E5%9C%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E4%B8%AD%E5%AF%B9%E5%BA%94%E7%9A%84id&amp;url=/archives/347b864a-1a88-4395-b7be-8bd31dcff7b7" width="1" height="1" alt="" style="opacity:0;">
<p>首先int i = userMapper.insertSelective(user)，这里返回的并不是主键自增id，而是成功插入的条数。insert完成之后再去查询得到id，这样显然不行，很可能获取到的id不是自己想要的那条数据的id，只有在insert的过程中获取到id，再将其包装在结果集中一起返回，这样才能保证返回id的准确性。</p>
<p>如果想获取主键自增id，可以使用mybatis提供的两种方式：</p>
<ul>
 <li>
  <p>方式一：mybatis的selectKey标签配合sql语句就可以实现这一需求</p>
 </li>
 <li>
  <p>详解</p>
 </li>
 <li>
  <p>keyProperty属性表示要查询的主键的名字，就是主键字段对应实体类的属性。</p>
 </li>
 <li>
  <p>order属性有两个值，即after，before；after表示在insert之后执行SELECT LAST_INSERT_ID()，一般用于主键自增时，得到的就是自增长生成的id，而before表示在insert之前执行SELECT LAST_INSERT_ID()，一般用于非自增主键，得到的是传入的对象中主键的值，一般是用户生成的uuid。</p>
 </li>
 <li>
  <p>resultType属性表示主键的类型，一般要么是Integer，要么是String</p>
 </li>
</ul>
<p>将selectKey标签的内容放入insert语句中就好了</p>
<p>代码示例：</p>
<pre><code class="css">    &lt;insert id="insert"&gt;
        &lt;selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"&gt;
            SELECT LAST_INSERT_ID()
        &lt;/selectKey&gt;

        INSERT INTO activity_template_log(
            activity_template_no,
            start_time,
            end_time,
            status,
            create_time
        ) VALUES (
            #{activityTemplateNo},
            #{startTime},
            #{endTime},
            #{status},
            #{createTime}
        )
    &lt;/insert&gt;
</code></pre>
<p>这里数据库的表结构主键都是自增长，所以selectKey标签中order属性必须是大写AFTER。</p>
<p>如果将order属性改成BEFORE，查询的主键id是对象的id的值，而不是自增长生成的id的值，对象的属性未赋值，自动初始化的值是0，所以此处主键的值为0，改成BEFORE取的就是对象主键的值。</p>
<ul>
 <li>
  <p>方式二：在insert标签中加入useGeneratedKeys和keyProperty属性也可以(useGeneratedKeys是使用生成的主键的意思)</p>
 </li>
 <li>
  <p>详解：</p>
 </li>
 <li>
  <p>是否使用 useGeneratedKeys 开关为true</p>
 </li>
 <li>
  <p>keyProperty对应的就是要返回的字段名称</p>
 </li>
</ul>
<p>在insert标签中加入useGeneratedKeys和keyProperty属性即可，即：useGeneratedKeys=“true” keyProperty=“id”</p>
<pre><code class="css">   &lt;insert id="insert" useGeneratedKeys="true" keyProperty="id"&gt;
        INSERT INTO activity_template_log(
            activity_template_no,
            start_time,
            end_time,
            status,
            create_time
        ) VALUES (
            #{activityTemplateNo},
            #{startTime},
            #{endTime},
            #{status},
            #{createTime}
        )
    &lt;/insert&gt;
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100691555</p>]]></description><guid isPermaLink="false">/archives/347b864a-1a88-4395-b7be-8bd31dcff7b7</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:38:38 GMT</pubDate></item><item><title><![CDATA[Vmware虚拟机操作Redis]]></title><link>https://wiki.dgfygk.com/archives/9cf8d6e2-05e9-49df-b576-ee415576f7b1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Vmware%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%93%8D%E4%BD%9CRedis&amp;url=/archives/9cf8d6e2-05e9-49df-b576-ee415576f7b1" width="1" height="1" alt="" style="opacity:0;">
<p>首先切换到Redis安装目录：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77b98ef38e68589f1d849de230d36a7a.png&amp;size=m" alt=""></p>
<p>然后创建文件夹 myredis ,j讲安装目录中的redis-config文件拷贝到myredis中，并修改该文件。（可以选择不创建，直接修改Redis安装目录中的配置文件redis-config）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2d3bcd4d4ef4230a8143d8efa20b8002.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F18d71267aef4593e464b50a78c2f18ae.png&amp;size=m" alt=""></p>
<p>检查Redis是否启动：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5118823bcb9549d5cc44b7e41a7cb095.png&amp;size=m" alt=""></p>
<p>这里是没有启动Redis服务。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc69eca8e3d877c024b66da8c17b696c2.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbf4379e349c0745250196e96cab64524.png&amp;size=m" alt=""></p>
<p>最后在查看Redis服务是否启动：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2821652cbf134c894bb14a50945370c2.png&amp;size=m" alt=""></p>
<p>现在就可以愉快的玩耍Redis了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc22af04cf8581b9adaeb5bcba549bd37.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100199744</p>]]></description><guid isPermaLink="false">/archives/9cf8d6e2-05e9-49df-b576-ee415576f7b1</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Wed, 25 Sep 2024 09:37:45 GMT</pubDate></item><item><title><![CDATA[将MongoDB设置成系统服务，可以在后台启动]]></title><link>https://wiki.dgfygk.com/archives/e809bbb1-ecae-4974-8cd8-4b8e205a7979</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%86MongoDB%E8%AE%BE%E7%BD%AE%E6%88%90%E7%B3%BB%E7%BB%9F%E6%9C%8D%E5%8A%A1%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%9C%A8%E5%90%8E%E5%8F%B0%E5%90%AF%E5%8A%A8&amp;url=/archives/e809bbb1-ecae-4974-8cd8-4b8e205a7979" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1098e9f38cb31fe87b50bdc02cffafa2.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102504749</p>]]></description><guid isPermaLink="false">/archives/e809bbb1-ecae-4974-8cd8-4b8e205a7979</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:37:27 GMT</pubDate></item><item><title><![CDATA[线程池之ThreadPoolExecutor使用]]></title><link>https://wiki.dgfygk.com/archives/3353d0eb-aaae-4df9-82cc-4c028d353d4c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BA%BF%E7%A8%8B%E6%B1%A0%E4%B9%8BThreadPoolExecutor%E4%BD%BF%E7%94%A8&amp;url=/archives/3353d0eb-aaae-4df9-82cc-4c028d353d4c" width="1" height="1" alt="" style="opacity:0;">
<p>ThreadPoolExecutor提供四个构造方法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Faeee7f78f3d76fd7887b8b8cbd2e74ee.png&amp;size=m" alt=""></p>
<p>我们选一个参数最多的构造方法为参靠，并对其参数进行解释：</p>
<pre><code class="java">public ThreadPoolExecutor(int corePoolSize, // 参数1
                          int maximumPoolSize,  // 参数2
                          long keepAliveTime,  // 参数3
                          TimeUnit unit,  // 参数4
                          BlockingQueue&lt;Runnable&gt; workQueue, // 参数5
                          ThreadFactory threadFactory,  // 参数6
                          RejectedExecutionHandler handler ) { //参数7
        if (corePoolSize &lt; 0 ||
            maximumPoolSize &lt;= 0 ||
            maximumPoolSize &lt; corePoolSize ||
            keepAliveTime &lt; 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
</code></pre>
<p>|——|—————–|—————————|———-|
 <br>
 | 参数序号 | 参数名称 | 参数类型 | 参数含义 |
 <br>
 | 1 | corePoolSize | int | 核心线程池大小 |
 <br>
 | 2 | maximumPoolSize | int | 最大线程池大小 |
 <br>
 | 3 | keepAliveTime | long | 线程最大空闲时间 |
 <br>
 | 4 | unit | TimeUnit | 时间单位 |
 <br>
 | 5 | workQueue | BlockingQueue<runnable> | 线程等待队列 | 
  <br>| 6 | threadFactory | ThreadFactory | 线程创建工厂 | 
  <br>| 7 | handler | RejectedExecutionHandler | 拒绝策略 | 
  <br>[构造方法参书解析]</runnable></p>
<p>知道了各个参数的作用后，我们开始构造符合我们期待的线程池。首先看JDK给我们预定义的几种线程池：</p>
<p>一、预定义线程池</p>
<p><strong>1.FixedThreadPool</strong></p>
<pre><code class="java">public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue&lt;Runnable&gt;());
    }
</code></pre>
<blockquote>
 <ul>
  <li>corePoolSize 和 maximumPoolSize 相等，即其线程全为核心线程，是一个固定大小的线程池，是其优势；</li>
  <li>keepAliveTime = 0 该参数默认对核心线程无效，而FixedThreadPool全部为核心线程；</li>
  <li>workQueue 为LinkedBlockingQueue（无界阻塞队列），队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度，会造成队列大量阻塞。因为队列很大，很有可能在拒绝策略前，内存溢出。是其劣势；</li>
  <li>FixedThreadPool的任务执行是无序的；</li>
 </ul>
</blockquote>
<p>适用场景：可用于Web服务瞬时削峰，但需注意长时间持续高峰情况造成的队列阻塞。</p>
<p><strong>2.CachedThreadPool</strong></p>
<pre><code class="java">public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue&lt;Runnable&gt;());
    }
</code></pre>
<blockquote>
 <ul>
  <li>corePoolSize = 0，maximumPoolSize = Integer.MAX_VALUE，即线程数量几乎无限制；</li>
  <li>keepAliveTime = 60s，线程空闲60s后自动结束。</li>
  <li>workQueue 为 SynchronousQueue 同步队列，这个队列类似于一个接力棒，入队出队必须同时传递，因为CachedThreadPool线程创建无限制，不会有队列等待，所以使用SynchronousQueue；</li>
 </ul>
</blockquote>
<p>适用场景：快速处理大量耗时较短的任务，如Netty的NIO接受请求时，可使用CachedThreadPool。</p>
<p><strong>3.SingleThreadExecutor</strong></p>
<pre><code class="java">public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue&lt;Runnable&gt;()));
    }
</code></pre>
<p>咋一瞅，不就是newFixedThreadPool(1)吗？定眼一看，这里多了一层FinalizableDelegatedExecutorService包装，这一层有什么用呢，写个dome来解释一下：</p>
<pre><code class="java">public static void main(String[] args) {
        ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;
        System.out.println(threadPoolExecutor.getMaximumPoolSize());
        threadPoolExecutor.setCorePoolSize(8);

        ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
//      运行时异常 java.lang.ClassCastException
        ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) singleExecutorService;
    }
</code></pre>
<p>对比可以看出，FixedThreadPool可以向下转型为ThreadPoolExecutor，并对其线程池进行配置，而SingleThreadExecutor被包装后，无法成功向下转型。<strong>因此，SingleThreadExecutor被定以后，无法修改，做到了真正的Single。</strong></p>
<p><strong>4.ScheduledThreadPool</strong></p>
<pre><code class="java"> public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
</code></pre>
<p>newScheduledThreadPool调用的是ScheduledThreadPoolExecutor的构造方法，而ScheduledThreadPoolExecutor继承了ThreadPoolExecutor，构造是还是调用了其父类的构造方法。</p>
<pre><code class="java">  public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
</code></pre>
<p>对于ScheduledThreadPool本文不做描述。</p>
<p>二、自定义线程池</p>
<p>以下是自定义线程池，使用了有界队列，自定义ThreadFactory和拒绝策略的demo：</p>
<pre><code class="java">public class ThreadTest {

    public static void main(String[] args) throws InterruptedException, IOException {
        int corePoolSize = 2;
        int maximumPoolSize = 4;
        long keepAliveTime = 10;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue&lt;Runnable&gt; workQueue = new ArrayBlockingQueue&lt;&gt;(2);
        ThreadFactory threadFactory = new NameTreadFactory();
        RejectedExecutionHandler handler = new MyIgnorePolicy();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
                workQueue, threadFactory, handler);
        executor.prestartAllCoreThreads(); // 预启动所有核心线程

        for (int i = 1; i &lt;= 10; i++) {
            MyTask task = new MyTask(String.valueOf(i));
            executor.execute(task);
        }

        System.in.read(); //阻塞主线程
    }

    static class NameTreadFactory implements ThreadFactory {

        private final AtomicInteger mThreadNum = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
            System.out.println(t.getName() + " has been created");
            return t;
        }
    }

    public static class MyIgnorePolicy implements RejectedExecutionHandler {

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            doLog(r, e);
        }

        private void doLog(Runnable r, ThreadPoolExecutor e) {
            // 可做日志记录等
            System.err.println( r.toString() + " rejected");
//          System.out.println("completedTaskCount: " + e.getCompletedTaskCount());
        }
    }

    static class MyTask implements Runnable {
        private String name;

        public MyTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                System.out.println(this.toString() + " is running!");
                Thread.sleep(3000); //让任务执行慢点
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "MyTask [name=" + name + "]";
        }
    }
}
</code></pre>
<p>运行结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0e1b8e2c602478dbf8e71c9a95b8bacb.png&amp;size=m" alt=""></p>
<p>其中线程线程1-4先占满了核心线程和最大线程数量，然后4、5线程进入等待队列，7-10线程被直接忽略拒绝执行，等1-4线程中有线程执行完后通知4、5线程继续执行。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/102487963</p>]]></description><guid isPermaLink="false">/archives/3353d0eb-aaae-4df9-82cc-4c028d353d4c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:37:10 GMT</pubDate></item><item><title><![CDATA[Java 操作redis缓存]]></title><link>https://wiki.dgfygk.com/archives/2779e1b6-980b-47cb-851c-187184d69969</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20%E6%93%8D%E4%BD%9Credis%E7%BC%93%E5%AD%98&amp;url=/archives/2779e1b6-980b-47cb-851c-187184d69969" width="1" height="1" alt="" style="opacity:0;">
<p>*</p>
<h4>首先是Redis配置文件 applicationContext-redis.xml:</h4>
<pre><code>      &lt;beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd"
       default-lazy-init="false"&gt;

    &lt;context:property-placeholder location="classpath:redis.properties"
                                  ignore-unresolvable="true"/&gt;
    &lt;bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"&gt;
        &lt;property name="maxIdle" value="1000"/&gt;  &lt;!-- ${redis.maxIdle} --&gt;
        &lt;property name="minIdle" value="100"/&gt;  &lt;!-- ${redis.minIdle} --&gt;
        &lt;property name="maxTotal" value="100"/&gt; &lt;!-- ${redis.minIdle} --&gt;
        &lt;property name="testOnBorrow" value="true"/&gt;  &lt;!-- ${redis.testOnBorrow} --&gt;
        &lt;property name="testOnReturn" value="true"/&gt;  &lt;!-- ${redis.testOnReturn} --&gt;
    &lt;/bean&gt;

    &lt;bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:host-name="255.255.255.255"  &lt;!-- ${redis.host} --&gt;
          p:port="6379"  &lt;!-- ${redis.port} --&gt;
          p:pool-config-ref="jedisPoolConfig"
          p:use-pool="true"
          p:database="0"/&gt;  &lt;!-- ${redis.database} --&gt;

    &lt;bean id="RedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnectionFactory"
          p:keySerializer-ref="stringRedisSerializer"
          p:valueSerializer-ref="JdkSerializationRedisSerializer"/&gt;

    &lt;bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/&gt;
    &lt;bean id="JdkSerializationRedisSerializer"
          class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/&gt;
&lt;/beans&gt;
</code></pre>
<p>*</p>
<h4>java 实现层引入Redis</h4>
<pre><code class="java">// String类型 
@Autowired
private RedisTemplate&lt;String, String&gt; redisTemplate;
</code></pre>
<p>*</p>
<h4>操作Redis进行获取值，添加值</h4>
<pre><code class="java">private static final String REDIS_STRING_KEY = "activity:template";
// （1）获取值
String jsonStr = redisTemplate.boundValueOps(REDIS_STRING_KEY).get();

/*
如果引入  
@Autowired
private RedisTemplate&lt;String, Object&gt; redisTemplate;

获取redisTemplate.boundValueOps(REDIS_STRING_KEY).get() 返回的是一个对象，返回结果中包含一个@type键值*/

// (2) 添加值
String templateDetailJson = JSON.toJSONString(templateDetail);   //对象转json串
//添加1
redisTemplate.opsForValue().set(ACTIVITY_TEMPLATE_KEY, templateDetailJson);
//添加2
redisTemplate.boundValueOps(ACTIVITY_TEMPLATE_KEY).set(templateDetailJson);

// 设置键的过期时间
// 制定过期时间 date
redisTemplate.expireAt(ACTIVITY_TEMPLATE_KEY, templateDetail.getEndTime());
// 设置1天后过期
redisTemplate.expire(ACTIVITY_TEMPLATE_KEY, 1, TimeUnit.DAYS);
</code></pre>
<p>后续更新。。。。。。。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100763227</p>]]></description><guid isPermaLink="false">/archives/2779e1b6-980b-47cb-851c-187184d69969</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:36:55 GMT</pubDate></item><item><title><![CDATA[Linux 安装Redis 异常：gcc 命令未找到]]></title><link>https://wiki.dgfygk.com/archives/e49c7c88-b403-49f6-950d-ab37d010dcc5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%20%E5%AE%89%E8%A3%85Redis%20%E5%BC%82%E5%B8%B8%EF%BC%9Agcc%20%E5%91%BD%E4%BB%A4%E6%9C%AA%E6%89%BE%E5%88%B0&amp;url=/archives/e49c7c88-b403-49f6-950d-ab37d010dcc5" width="1" height="1" alt="" style="opacity:0;">
<p>*</p>
<h4>在执行make安装命令时候，报异常如下：</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F06537509efc742284511f63042ea3a02.png&amp;size=m" alt=""></p>
<h4>这就说明本机没有gcc环境，此时你要安装一下gcc环境。</h4>
<p>*</p>
<h4>什么是GCC？</h4>
<p><strong>GCC 是大多数类<a href="https://baike.baidu.com/item/Unix">Unix</a>操作系统（如<a href="https://baike.baidu.com/item/Linux">Linux</a>、<a href="https://baike.baidu.com/item/BSD">BSD</a>、<a href="https://baike.baidu.com/item/Mac%20OS%20X">Mac OS X</a>等）采纳为标准的<a href="https://baike.baidu.com/item/%E7%BC%96%E8%AF%91%E5%99%A8">编译器</a>，是C程序的编译工具。</strong></p>
<p><strong>GCC 原名为 GNU C 语言编译器（GNU C Compiler），因为它原本只能处理 <a href="https://baike.baidu.com/item/C%E8%AF%AD%E8%A8%80">C语言</a>。GCC 很快地扩展，变得可处理 <a href="https://baike.baidu.com/item/C%2B%2B">C++</a>。后来又扩展能够支持更多编程语言，如<a href="https://baike.baidu.com/item/Fortran">Fortran</a>、<a href="https://baike.baidu.com/item/Pascal">Pascal</a>、<a href="https://baike.baidu.com/item/Objective-C">Objective-C</a>、<a href="https://baike.baidu.com/item/Java">Java</a>、<a href="https://baike.baidu.com/item/Ada/5606819">Ada</a>、<a href="https://baike.baidu.com/item/Go/953521">Go</a>以及各类<a href="https://baike.baidu.com/item/%E5%A4%84%E7%90%86%E5%99%A8%E6%9E%B6%E6%9E%84">处理器架构</a>上的<a href="https://baike.baidu.com/item/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/61826">汇编语言</a>等，所以改名GNU编译器套件（GNU Compiler Collection）。</strong></p>
<p>*</p>
<h4>安裝GCC</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb317039d09746fada5096bf00f03be23.png&amp;size=m" alt=""></p>
<p><strong>（1）如果你的电脑可以联网，你可以使用命令安装GCC，命令如下：</strong></p>
<h4>yum install gcc-c++</h4>
<p><strong>此时可能会有 No package gcc-c+ 就是没有GCC包</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd6a85e11a7b5886eca8dd89527e13aa3.png&amp;size=m" alt=""></p>
<h4>此时命令： yum install gcc-c++ 就不好用了，</h4>
<h4>换成 命令：yum install gcc gcc-c++ 就OK了</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F28c7bca120f409d5de32baedf6066c42.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7f5ea04d7e74db98975548fb19ddb32d.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6cd050be9bba264f04a119d6192d22a7.png&amp;size=m" alt=""></p>
<p><strong>（2）如果不能联网 就可以在IOS镜像中安装：（详细介绍此安装方法）</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd2cde14865a422143530f2ba19c79da1.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7c9d124e6d6094375b7796a968273564.png&amp;size=m" alt=""></p>
<h4>在终端中输入： cd /切换到根目录 cd /media/CentOS_5.2_Final/CentOS 目录</h4>
<h4>接着在安装一下rpm包。</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F29a3aa1e9f6c9d0659a51c24f803c95d.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9d9ce50e8afb05862b0bf6bb4cf04647.png&amp;size=m" alt=""></p>
<h4>都安装完以后执行 gcc -v 命令</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffb630dfb08a3d0087b42166e9a631536.png&amp;size=m" alt=""></p>
<p><strong>到这 GCC就已经安装完了，然后在执行 make命令就可以安装成功啦！</strong></p>
<p><strong>再执行make命令可能</strong> <strong>出现 jemalloc/jemalloc.h：没有那个文件或目录 的错误。解决办法参考我的下一篇博客：</strong> <a href="https://blog.csdn.net/zuihongyan518/article/details/100174656">https://blog.csdn.net/zuihongyan518/article/details/100174656</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100173833</p>]]></description><guid isPermaLink="false">/archives/e49c7c88-b403-49f6-950d-ab37d010dcc5</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Wed, 25 Sep 2024 09:36:42 GMT</pubDate></item><item><title><![CDATA[redis 修复appendonly.aof文件]]></title><link>https://wiki.dgfygk.com/archives/dc285ea0-0f86-4b61-9342-75ad41f0b620</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=redis%20%E4%BF%AE%E5%A4%8Dappendonly.aof%E6%96%87%E4%BB%B6&amp;url=/archives/dc285ea0-0f86-4b61-9342-75ad41f0b620" width="1" height="1" alt="" style="opacity:0;">
<p>Redis持久化-恢复数据：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe0ec2134b0099d713645f18cce17480b.png&amp;size=m" alt=""></p>
<p>对于错误格式的AOF文件，在启动Redis时，Redis会启动不成功。如果appendonly.aof 和 dump.rdb文件同时，Redis 会优先加载 appendonly.aof 文件</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe3f9edec7c2cc473741341a64b8e3e03.png&amp;size=m" alt=""></p>
<h4>appendonly.aof数据文件损坏的时候如何修复</h4>
<p>先进行备份，然后采用redis-check-aof –fix appendonly.aof 命令进行修复，【同理，也可用 redis-check-rdb 工具来修复dump.rdb】修复后使用diff-u对比数据的差异，找出丢失的数据，有些可以人工修改补全。AOF文件可能存在结尾不完整的情况，比如机器突然掉电导致AOF尾部文件命令写入不全。Redis为我们提供了aof-load-truncated配置来兼容这种情况，默认开启。加载AOF时，当遇到此问题时会忽略并继续启动。
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5d5054f88b594ed9f52a383494d730cc.png&amp;size=m" alt=""></p>
<p>修复完成在重启Redis成功！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fee0e1513ed232045c4727359d4e715ee.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100519774</p>]]></description><guid isPermaLink="false">/archives/dc285ea0-0f86-4b61-9342-75ad41f0b620</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:36:24 GMT</pubDate></item><item><title><![CDATA[项目中如何引进Redis，在项目中配置Redis缓存]]></title><link>https://wiki.dgfygk.com/archives/7861dd53-a010-4dd9-a855-45249daa27c4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%A1%B9%E7%9B%AE%E4%B8%AD%E5%A6%82%E4%BD%95%E5%BC%95%E8%BF%9BRedis%EF%BC%8C%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E9%85%8D%E7%BD%AERedis%E7%BC%93%E5%AD%98&amp;url=/archives/7861dd53-a010-4dd9-a855-45249daa27c4" width="1" height="1" alt="" style="opacity:0;">
<p>首先，需要引入的包 Maven</p>
<pre><code class="java">&lt;!-- fastjson json --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
    &lt;artifactId&gt;fastjson&lt;/artifactId&gt;
    &lt;version&gt;1.2.52&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p><strong>然后配置 applicationContext-redis.xml 文件：</strong></p>
<pre><code class="css">&lt;beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd"
       default-lazy-init="false"&gt;



&lt;context:property-placeholder location="classpath:redis.properties"
                                  ignore-unresolvable="true"/&gt;
    &lt;bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"&gt;
        &lt;property name="maxIdle" value="1000"/&gt;
        &lt;property name="minIdle" value="100"/&gt;
        &lt;property name="maxTotal" value="100"/&gt;
        &lt;property name="testOnBorrow" value="true"/&gt;
        &lt;property name="testOnReturn" value="true"/&gt;
    &lt;/bean&gt;

&lt;bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:host-name="192.168.0.1"  // Redis缓存的服务器地址
          p:port="6379"
          p:pool-config-ref="jedisPoolConfig"
          p:use-pool="true"
          p:database="0"/&gt;  // ${redis.database}

&lt;bean id="RedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnectionFactory"
          p:keySerializer-ref="stringRedisSerializer"
          p:valueSerializer-ref="JdkSerializationRedisSerializer"/&gt;

&lt;bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/&gt;
    &lt;bean id="JdkSerializationRedisSerializer"
          class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/&gt;
&lt;/beans&gt;
</code></pre>
<p>然后 添加redis.properties文件（不是必须的）</p>
<pre><code class="css">redis.host=255.255.255.255  // redis缓存地址
redis.port=6379
redis.pass=
#maxIdle
redis.maxIdle=30
#minIdle
redis.minIdle=10
#maxTotal
redis.maxTotal=100
#timeout
redis.timeout=5000
redis.testOnBorrow=false
redis.testOnReturn=false
redis.database=0
</code></pre>
<p>这样配置就完成了。</p>
<p>在Java中如何使用引入配置的Redis缓存：</p>
<pre><code class="css">        @Resource(name = "RedisTemplate")
    private ListOperations&lt;String, TestLogPO&gt; listOperations;

    @Resource(name = "RedisTemplate")
    private RedisTemplate redisTemplate;

        listOperations.leftPush(TEST_TEMPLATE_KEY, TestLogPO);
        redisTemplate.expireAt(TEST_TEMPLATE_KEY, endTime);
</code></pre>
<p>当然这位只是引入一个Redis缓存，如果你的redis有多个，并且不在同一个服务器下，这时你需要再次引入redis</p>
<p>需要在<strong>applicationContext-redis.xml 文件中添加引入Redis配置：</strong></p>
<pre><code class="css">在配置文件中添加一个bean标签：

&lt;bean id="Test2JedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:host-name="255.255.255.255" // 随便写的
          p:port="6379" 
          p:pool-config-ref="jedisPoolConfig"
          p:use-pool="true" 
          p:database="2"/&gt;



还要添加一个:
&lt;bean id="Test2RedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="Test2JedisConnectionFactory"
          p:keySerializer-ref="Test2StringRedisSerializer"
          p:valueSerializer-ref="Test2JdkSerializationRedisSerializer"/&gt;

&lt;bean id="Test2StringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/&gt;

&lt;bean id="Test2JdkSerializationRedisSerializer"
          class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/&gt;
</code></pre>
<p>在Java中如何使用引入配置的Redis缓存：</p>
<pre><code class="css">       @Resource(name = "Test2RedisTemplate")
    private ListOperations&lt;String, TestLogPO&gt; test2ListOperations;

    @Resource(name = "Test2RedisTemplate")
    private RedisTemplate test2RedisTemplate;

        test2ListOperations.leftPush(TEST_TEMPLATE_KEY, TestLogPO);
        test2RedisTemplate.expireAt(TEST_TEMPLATE_KEY, endTime);
</code></pre>
<p>结束！作者是菜鸟，如有错误请指导改正</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100633012</p>]]></description><guid isPermaLink="false">/archives/7861dd53-a010-4dd9-a855-45249daa27c4</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:36:10 GMT</pubDate></item><item><title><![CDATA[CentOS 6.9 如何安装图形界面]]></title><link>https://wiki.dgfygk.com/archives/4d6d5a36-0dd2-42cb-a853-ae16a0895893</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=CentOS%206.9%20%E5%A6%82%E4%BD%95%E5%AE%89%E8%A3%85%E5%9B%BE%E5%BD%A2%E7%95%8C%E9%9D%A2&amp;url=/archives/4d6d5a36-0dd2-42cb-a853-ae16a0895893" width="1" height="1" alt="" style="opacity:0;">
<h3>一、进入 root 模式</h3>
<p>因为权限限制，所以我们需要进入 root 模式，开机使用 root 登陆或者系统运行中切换为 root 用户均可。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbd3e9406769756b2cb22ae5abdce7c01.png&amp;size=m" alt=""></p>
<h3>二、导入CentOS 6.x版本安装GNOME包</h3>
<h4>使用命令：[root@localhost\~] yum groupinstall “Desktop” -y</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffd772b2c30f7b72b24252225cd0e1b54.png&amp;size=m" alt=""></p>
<p><strong>在运行上述命令的时候可能会出现</strong>couldn't resolve host 'mirrorlist.centos.org 错误！网上查了半天,很多都说在/etc/resolv.conf文件末尾添加nameserver 8.8.8.8 nameserver 8.8.4.4 serchdomain localdomain.可我试了半天也没有用! 改/etc/resolv.conf是对的。为什么改成nameserver 8.8.8.8不对，原因 8.8.8.8是谷歌的dns 早就被封了 所以肯定不行。</p>
<h4>其实只要重启一下eth0网络适配器就可以了,它会自动连接网络的.以下命令都执行一下,yum命令好使了.</h4>
<h4>[root@localhost\~] <code>ifdown eth0</code></h4>
<h4>[root@localhost\~] <code>ifup eth0</code></h4>
<h4>[root@localhost\~] <code>yum update</code></h4>
<h4>三、3.1输入[root@localhost\~] <code>yum -y groupinstall chinese-support</code>，安装中文包</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8f79d898475e537c60c355aebeb5472d.png&amp;size=m" alt=""></p>
<h4>3.2输入 [root@localhost\~] <code>vi /etc/sysconfig/i18n</code>，查看配置文件</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb8084055c1368ccc7fa378fbe9dc46e7.png&amp;size=m" alt=""></p>
<h4>四、然后使用命令 [root@localhost\~] vim /etc/inittab 打开 inittab文件</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4e837df5250ec44ec089c4ecab5a14a2.png&amp;size=m" alt=""></p>
<p>将id:3:initdefault:修改为id:5:initdefault:保存文件并重启即可,这样启动虚拟机就默认是图形化界面了。</p>
<p>　　5表示图形化界面，设置为5后表示每次启动系统进入的是图形化界面
 <br>
 　　3表示命令行界面，设置为3后表示每次启动系统进入的是命令行界面</p>
<p>其他指定系统运行级别
 <br>
 　　0：关机
 <br>
 　　1：单用户模式
 <br>
 　　2：多用户模式但是没有网络
 <br>
 　　3：多用户模式并且有网络，即命令行界面
 <br>
 　　4：系统未使用保留给用户
 <br>
 　　5：X11模式即进入图形界面模式
 <br>
 　　6：重启</p>
<h4>五、最后，一切准备就绪以后在使用命令： [root@localhost\~] reboot 重启虚拟机</h4>
<h4>附件：linux虚拟机关机、重启命令</h4>
<p>一.shutdown
 <br>
 1.shutdown -h now ：立即关机
 <br>
 2.shutdown -h 10:53：到10:53关机
 <br>
 3.shutdown -h +10 ：10分钟后自动关机
 <br>
 4.shutdown -r ：重启（默认30秒）
 <br>
 5.shutdown -r now ：立即重启
 <br>
 6.shutdown -r +30 ：'The System Will Reboot 30 in Mins':30分钟后重启并发送通知给其他在线用户
 <br>
 7.shutdown -s -t 100 ：延迟100秒关机
 <br>
 8.shutdown -a ：取消上面的关机、重启</p>
<p>二、reboot
 <br>
 1.reboor ：重启，相当于shutdown -r now
 <br>
 2.reboot -d ：重新启动时不把数据写入记录文件 /var/tmp/wtmp
 <br>
 3.reboot -f ：强制重新开机
 <br>
 4.reboot -h：在系统关机之前，将所有的硬盘处于待机模式下
 <br>
 5.reboot -i ：关闭网络设置之后再重新启动系统
 <br>
 6.reboot -n ：保存数据后重启</p>
<p>三、halt
 <br>
 1.halt ： 关机
 <br>
 2.halt -d ：关闭系统，不把记录写到/var/log/wtmp文件里
 <br>
 3.halt -p ：关机并关闭电源
 <br>
 4.halt -f ：强制关机</p>
<p>四、init
 <br>
 1.init 0 ：关机
 <br>
 2.0-6：表示系统运行的7个级别
 <br>
 0：关机级别
 <br>
 1：单用户级别，运行rc.sysinit和rc1.d目录下的脚本
 <br>
 2：多用户，但系统不会启动NFS，字符模式
 <br>
 3：多用户，字符模式，系统启动具有网络功能
 <br>
 4：用户自定义级别
 <br>
 5：图形界面模式
 <br>
 6：重启级别</p>
<p>五、poweroff
 <br>
 1.poweroff ：关机并关闭电源
 <br>
 2.poweroff -p：关机并关闭电源
 <br>
 3.poweroff -f ：强制关机
 <br>
 4.poweroff -w：不是真正的关机，只是将数据写入到/var/log/wtmp文件里
 <br>
 六 、按Ctrl + Alt + Delete键—- 选择关机</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/98640922</p>]]></description><guid isPermaLink="false">/archives/4d6d5a36-0dd2-42cb-a853-ae16a0895893</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Wed, 25 Sep 2024 09:35:51 GMT</pubDate></item><item><title><![CDATA[Git 常用命令]]></title><link>https://wiki.dgfygk.com/archives/7553e379-2408-4751-b6ae-17b0be17d1c2</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Git%20%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4&amp;url=/archives/7553e379-2408-4751-b6ae-17b0be17d1c2" width="1" height="1" alt="" style="opacity:0;">
<h3>常用命令：</h3>
<pre><code class="java">$ git remote add origin git@github.com:yeszao/dofiler.git         # 配置远程git版本库
$ git pull origin master                                          # 下载代码及快速合并 
$ git push origin master                                          # 上传代码及快速合并
$ git fetch origin                                                # 从远程库获取代码

$ git branch                                                      # 显示所有分支
$ git checkout master                                             # 切换到master分支
$ git checkout -b dev                                             # 创建并切换到dev分支
$ git commit -m "first version"                                   # 提交

$ git status                                                      # 查看状态
$ git log                                                         # 查看提交历史

$ git config --global core.editor vim                             # 设置默认编辑器为vim（git默认用nano）
$ git config core.ignorecase false                                # 设置大小写敏感
$ git config --global user.name "YOUR NAME"                       # 设置用户名
$ git config --global user.email "YOUR EMAIL ADDRESS"             # 设置邮箱
</code></pre>
<h3>别名Alias</h3>
<pre><code class="html">$ git config --global alias.br="branch"                 # 创建/查看本地分支
$ git config --global alias.co="checkout"               # 切换分支
$ git config --global alias.cb="checkout -b"            # 创建并切换到新分支
$ git config --global alias.cm="commit -m"              # 提交
$ git config --global alias.st="status"                 # 查看状态
$ git config --global alias.pullm="pull origin master"  # 拉取分支
$ git config --global alias.pushm="push origin master"  # 提交分支
$ git config --global alias.log="git log --oneline --graph --decorate --color=always" # 单行、分颜色显示记录
$ git config --global alias.logg="git log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(bold white)--- %an%C(reset)%C(bold yellow)%d%C(reset)' --abbrev-commit --date=relative" # 复杂显示
</code></pre>
<h3>创建版本库</h3>
<pre><code class="css">$ git clone &lt;url&gt;                 # 克隆远程版本库
$ git init                        # 初始化本地版本库
</code></pre>
<h3>修改和提交</h3>
<pre><code class="cpp">$ git status                      # 查看状态
$ git diff                        # 查看变更内容
$ git add .                       # 跟踪所有改动过的文件
$ git add &lt;file&gt;                  # 跟踪指定的文件
$ git mv &lt;old&gt; &lt;new&gt;              # 文件改名
$ git rm &lt;file&gt;                   # 删除文件
$ git rm --cached &lt;file&gt;          # 停止跟踪文件但不删除
$ git commit -m "commit message"  # 提交所有更新过的文件
$ git commit --amend              # 修改最后一次提交
</code></pre>
<h3>查看提交历史</h3>
<pre><code class="sql">$ git log                         # 查看提交历史
$ git log -p &lt;file&gt;               # 查看指定文件的提交历史
$ git blame &lt;file&gt;                # 以列表方式查看指定文件的提交历史
</code></pre>
<h3>撤消</h3>
<pre><code class="cpp">$ git reset --hard HEAD           # 撤消工作目录中所有未提交文件的修改内容
$ git reset --hard &lt;version&gt;      # 撤销到某个特定版本
$ git checkout HEAD &lt;file&gt;        # 撤消指定的未提交文件的修改内容
$ git checkout -- &lt;file&gt;          # 同上一个命令
$ git revert &lt;commit&gt;             # 撤消指定的提交
</code></pre>
<h3>分支与标签</h3>
<pre><code class="css">$ git branch                      # 显示所有本地分支
$ git checkout &lt;branch/tag&gt;       # 切换到指定分支或标签
$ git branch &lt;new-branch&gt;         # 创建新分支
$ git branch -d &lt;branch&gt;          # 删除本地分支
$ git tag                         # 列出所有本地标签
$ git tag &lt;tagname&gt;               # 基于最新提交创建标签
$ git tag -a "v1.0" -m "一些说明"  # -a指定标签名称，-m指定标签说明
$ git tag -d &lt;tagname&gt;            # 删除标签

$ git checkout dev                # 合并特定的commit到dev分支上
$ git cherry-pick 62ecb3
</code></pre>
<h3>合并与衍合</h3>
<pre><code class="cpp">$ git merge &lt;branch&gt;              # 合并指定分支到当前分支
$ git merge --abort               # 取消当前合并，重建合并前状态
$ git merge dev -Xtheirs          # 以合并dev分支到当前分支，有冲突则以dev分支为准
$ git rebase &lt;branch&gt;             # 衍合指定分支到当前分支
</code></pre>
<h3>远程操作</h3>
<pre><code class="cpp">$ git remote -v                   # 查看远程版本库信息
$ git remote show &lt;remote&gt;        # 查看指定远程版本库信息
$ git remote add &lt;remote&gt; &lt;url&gt;   # 添加远程版本库
$ git remote remove &lt;remote&gt;      # 删除指定的远程版本库
$ git fetch &lt;remote&gt;              # 从远程库获取代码
$ git pull &lt;remote&gt; &lt;branch&gt;      # 下载代码及快速合并
$ git push &lt;remote&gt; &lt;branch&gt;      # 上传代码及快速合并
$ git push &lt;remote&gt; :&lt;branch/tag-name&gt; # 删除远程分支或标签
$ git push --tags                 # 上传所有标签
</code></pre>
<h3>打包</h3>
<pre><code class="cpp">$ git archive --format=zip --output ../file.zip master    # 将master分支打包成file.zip文件，保存在上一级目录
$ git archive --format=zip --output ../v1.2.zip v1.2      # 打包v1.2标签的文件，保存在上一级目录v1.2.zip文件中
$ git archive --format=zip v1.2 &gt; ../v1.2.zip             # 作用同上一条命令
</code></pre>
<p>git打包命令会自动忽略<code>.gitignore</code>中指定的目录和文件，以及<code>.git</code>目录。</p>
<h3>全局和局部配置</h3>
<pre><code class="cpp">全局配置保存在：$Home/.gitconfig
本地仓库配置保存在：.git/config
</code></pre>
<h3>远程与本地合并</h3>
<p>如果在远程创建了代码仓，而且已经初始化，本地是具体的源代码，那么工作流程应该是：</p>
<pre><code class="cpp">$ git init                              # 初始化本地代码仓
$ git add .                             # 添加本地代码
$ git commit -m "add local source"      # 提交本地代码
$ git pull origin master                # 下载远程代码
$ git merge master                      # 合并master分支
$ git push -u origin master             # 上传代码
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/100124695</p>]]></description><guid isPermaLink="false">/archives/7553e379-2408-4751-b6ae-17b0be17d1c2</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:35:38 GMT</pubDate></item><item><title><![CDATA[java微信退款接口]]></title><link>https://wiki.dgfygk.com/archives/6f3414c0-5490-4a97-8847-8997c95d0e5b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%E5%BE%AE%E4%BF%A1%E9%80%80%E6%AC%BE%E6%8E%A5%E5%8F%A3&amp;url=/archives/6f3414c0-5490-4a97-8847-8997c95d0e5b" width="1" height="1" alt="" style="opacity:0;">
<h2>原文链接：<a href="https://www.cnblogs.com/hongzm/p/7366510.html">https://www.cnblogs.com/hongzm/p/7366510.html</a></h2>
<h2><a href="https://www.cnblogs.com/hongzm/p/7366510.html">微信公众号退款开发</a></h2>
<p><strong>博主是小菜鸟，这篇文章仅是自己开发的随笔记录，不足博友可以指出来，一起进步</strong></p>
<p><strong>1、【微信支付】公众号支付开发者文档链接地址</strong></p>
<p><a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4</a></p>
<p>调用微信退款接口，需要发送特定的xml格式字符串到到微信退款接口；</p>
<p><strong>2、微信申请退款需要双向证书</strong>，</p>
<p>JAVA只需要使用apiclient_cert.p12即可，证书从</p>
<p><a href="https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F">https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F</a></p>
<p>微信商户平台-》账户设置-》 API安全 中下载的<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Febd0196c96c38865b6597418410f4e6a.jpeg&amp;size=m" alt="">，下载后解压到本地一个英文命名的文件夹下；</p>
<p><strong>3、证书解压之后</strong></p>
<p>如下图<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe0ade4049dd23f3ef40f41ac17c65e59.jpeg&amp;size=m" alt="">，安装证书，双击apiclient_cert.p12，一直下一步到如下页面<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0007a501e096b1002c6ceb608d88ab7f.jpeg&amp;size=m" alt=""></p>
<p>密码为商户号（mch_id），一直下一步，直至提示导入成功，至此证书安装成功。</p>
<p><strong>4、代码</strong></p>
<p><em></em>　4.1工具类（xml、map格式转换以及签名）**</p>
<pre><code class="java">import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.etom.itoilet.constants.WXPayConstants;
import com.etom.itoilet.constants.WXPayConstants.SignType;

/**
* 微信支付工具类
* 
* @author hongzm
* 
* @date 2017年7月17日 上午10:30:00
*/
public class WXPayUtil {
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map&lt;String, String&gt; xmlToMap(String strXML) throws Exception {
　　try {
　　　　Map&lt;String, String&gt; data = new HashMap&lt;String, String&gt;();
　　　　DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
　　　　DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
　　　　InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
　　　　org.w3c.dom.Document doc = documentBuilder.parse(stream);
　　　　doc.getDocumentElement().normalize();
　　　　NodeList nodeList = doc.getDocumentElement().getChildNodes();
　　　　for (int idx = 0; idx &lt; nodeList.getLength(); ++idx) {
　　　　　　Node node = nodeList.item(idx);
　　　　　　if (node.getNodeType() == Node.ELEMENT_NODE) {
　　　　　　　　org.w3c.dom.Element element = (org.w3c.dom.Element) node;
　　　　　　　　data.put(element.getNodeName(), element.getTextContent());
　　　　　　}
　　　　}
　　　　try {
　　　　　　stream.close();
　　　　} catch (Exception ex) {
　　　　　　// do nothing
　　　　}
　　　　return data;
　　} catch (Exception ex) {
　　　　WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
　　　　throw ex;
　　}

}

/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map&lt;String, String&gt; data) throws Exception {
　　DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
　　DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
　　org.w3c.dom.Document document = documentBuilder.newDocument();
　　org.w3c.dom.Element root = document.createElement("xml");
　　document.appendChild(root);
　　for (String key: data.keySet()) {
　　　　String value = data.get(key);
　　　　if (value == null) {
　　　　　　value = "";
　　　　}
　　　　value = value.trim();
　　　　org.w3c.dom.Element filed = document.createElement(key);
　　　　filed.appendChild(document.createTextNode(value));
　　　　root.appendChild(filed);
　　}
　　TransformerFactory tf = TransformerFactory.newInstance();
　　Transformer transformer = tf.newTransformer();
　　DOMSource source = new DOMSource(document);
　　transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
　　transformer.setOutputProperty(OutputKeys.INDENT, "yes");
　　StringWriter writer = new StringWriter();
　　StreamResult result = new StreamResult(writer);
　　transformer.transform(source, result);
　　String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
　　try {
　　　　writer.close();
　　}catch (Exception ex) {
　　}
　　return output;
}


/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map&lt;String, String&gt; data, String key) throws Exception {
　　return generateSignedXml(data, key, "MD5");
}

/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map&lt;String, String&gt; data, String key, SignType signType) throws Exception {
　　String sign = generateSignature(data, key, "MD5");
　　data.put("sign", sign);
　　return mapToXml(data);
}


/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
　　Map&lt;String, String&gt; data = xmlToMap(xmlStr);
　　if (!data.containsKey("sign") ) {
　　　　return false;
　　}
　　String sign = data.get("sign");
　　return generateSignature(data, key).equals(sign);
}

/**
* 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map&lt;String, String&gt; data, String key) throws Exception {
　　return isSignatureValid(data, key,"MD5");
}

/**
* 判断签名是否正确，必须包含sign字段，否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map&lt;String, String&gt; data, String key, SignType signType) throws Exception {
　　if (!data.containsKey("sign") ) {
　　　　return false;
　　}
　　String sign = data.get("sign");
　　return generateSignature(data, key, signType).equals(sign);
}

/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map&lt;String, String&gt; data, String key) throws Exception {
　　return generateSignature(data, key, "MD5");
}

/**
* 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map&lt;String, String&gt; data, String key, SignType signType) throws Exception {
　　Set&lt;String&gt; keySet = data.keySet();
　　String[] keyArray = keySet.toArray(new String[keySet.size()]);
　　Arrays.sort(keyArray);
　　StringBuilder sb = new StringBuilder();
　　for (String k : keyArray) {
　　　　if (k.equals("sign")) {
　　　　　　continue;
　　　　}
　　　　if (data.get(k).trim().length() &gt; 0) // 参数值为空，则不参与签名
　　　　sb.append(k).append("=").append(data.get(k).trim()).append("&amp;");
　　}
　　sb.append("key=").append(key);
　　return MD5(sb.toString()).toUpperCase();
}


/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
　　return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}


/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
　　java.security.MessageDigest md = MessageDigest.getInstance("MD5");
　　byte[] array = md.digest(data.getBytes("UTF-8"));
　　StringBuilder sb = new StringBuilder();
　　for (byte item : array) {
　　　　sb.append(Integer.toHexString((item &amp; 0xFF) | 0x100).substring(1, 3));
　　}
　　return sb.toString().toUpperCase();
}

/**
* 日志
* @return
*/
public static Logger getLogger() {
　　Logger logger = LoggerFactory.getLogger("wxpay java sdk");
　　return logger;
}

/**
* 获取当前时间戳，单位秒
* @return
*/
public static long getCurrentTimestamp() {
　　return System.currentTimeMillis()/1000;
}

/**
* 获取当前时间戳，单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
　　return System.currentTimeMillis();
}

/**
* 生成 uuid， 即用来标识一笔单，也用做 nonce_str
* @return
*/
public static String generateUUID() {
　　return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}

}
</code></pre>
<p><strong>4.2 微信退款(参数根据开发文档，代码里红色是必需)</strong></p>
<p>微信退款的，maven还有导入两个包</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0b0b979d1b446386c536306803ce58d8.jpeg&amp;size=m" alt=""></p>
<p>网址：http://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient</p>
<pre><code class="java">/***
* 提交退款处理
* 
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/submitrefund.json")
@ResponseBody
public Map&lt;String, Object&gt; applyForRefun(HttpServletRequest request, HttpServletResponse response) {
　　// 订单的主键
　　String pk_easyhouse_salelog = "";
　　// 退款处理原因(不是必须，若传入，则会在下发给用户的退款中显示)
　　String dispose_reason = "";
　　// 销售状态：同意退款：3，拒绝退款：4
　　Integer sale_type = "";
　　// 根据订单pk获取订单VO
　　EasyhouseSalelogVO sale = "";
　　String xmlStr = "";
　　String resultXml = "";
　　Map&lt;String, String&gt; resultMap = new HashMap&lt;String, String&gt;();
　　// 同意退款
　　if (sale_type == 3) {
　　　　// 公众账号ID：登陆微信公众号后台-开发-基本配置
　　　　String appid = "";
　　　　// 微信支付商户号： mch_id-登陆微信支付后台，即可看到
　　　　String mch_id = "";
　　　　// 随机字符串，长度要求在32位以内,调用工具类中的随机数生成方法
　　　　String nonce_str = WXPayUtil.generateNonceStr();
　　　　// 微信订单号 或者商户订单号，二选一，这里用微信订单号
　　　　String transaction_id = sale.getWx_order_num();
　　　　// 商户退款单号，同一单号多次请求，只退款一次
　　　　String out_refund_no = WXPayUtil.generateUUID();
　　　　String price = sale.getProduct_saleprice().toString();
　　　　Double total_price = Double.valueOf(price);
　　　　// 订单总金额
　　　　String total_fee = Integer.toString((int) (total_price * 100));
　　　　// 退款总金额
　　　　String refund_fee = Integer.toString((int) (total_price * 100));
　　　　// 退款原因，会在下发给用户的退款消息中体现(可不传入)
　　　　String refund_desc = sale.getRefund_reason();
　　　　// API密钥(设置路径：微信商户平台(pay.weixin.qq.com)--&gt;账户设置--&gt;API安全--&gt;密钥设置)
　　　　String key = WXPayConstants.KEY;
　　　　// 将获得的信息存入Map集合中
　　　　Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;();
　　　　map.put("appid", appid);
　　　　map.put("mch_id", mch_id);
　　　　map.put("nonce_str", nonce_str);
　　　　map.put("transaction_id", transaction_id);
　　　　map.put("out_refund_no", out_refund_no);
　　　　map.put("total_fee", total_fee);
　　　　map.put("refund_fee", refund_fee);
　　　　map.put("refund_desc", refund_desc);
　　　　try {
　　　　　　// 调用工具类，将Map集合转化为带签名sign的XML格式字符串
　　　　　　xmlStr = WXPayUtil.generateSignedXml(map, key);
　　　　　　// 调用微信退款接口地址
　　　　　　String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
　　　　　　// 调用双向证书，返回xml格式状态码
　　　　　　resultXml = ClientCustomSSL.doRefund(url, xmlStr);
　　　　　　// 将返回结果转换成Map集合
　　　　　　resultMap = WXPayUtil.xmlToMap(resultXml);
　　　　} catch (Exception e) {
　　　　　　logger.debug("调用退款接口失败");
　　　　}
　　// 微信端返回字符串为成功时，退款成功,更新数据
　　if (resultMap.get("return_code").equals(WXPayConstants.SUCCESS)&amp;&amp; resultMap.get("result_code").equals(WXPayConstants.SUCCESS)) {

　　　　// 退款成功时，在此处更改订单的状态，并更新数据库对应信息

　　　　sale.setSale_type(Integer.valueOf(BasicConstants.NUMBER_SALE_TYPE_REFUNDED));
　　　　logger.debug("退款成功");
　　　　//更新公众号粉丝表的退款总额、消费总额
　　　　FansInfoVO fansInfoVO = userCenterService.getUserInfo(sale.getOpenid());
　　　　fansInfoVO.setStatus(VOStatus.UPDATED);
　　　　fansInfoVO.setRefund_sum(fansInfoVO.getRefund_sum().add(new UFDouble(refund_fee.toString())));
　　　　fansInfoVO.setConsume_sum(fansInfoVO.getConsume_sum().sub(new UFDouble(refund_fee.toString())));
　　　　salelogService.saveOrUpdate(fansInfoVO);
　　} else {

　　　　// 退款失败时，在此处设置相应信息，更新相应记录
　　　　sale.setSale_type(Integer.valueOf(BasicConstants.NUMBER_SALE_TYPE_REFUNDFAIL));
　　　　logger.debug("退款失败");
　　　　// 记录退款失败原因
　　　　dispose_reason += "," + resultMap.get("err_code_des");
　　　　}
　　} else {
　　　　// 退款失败
　　　　sale.setSale_type(Integer.valueOf(BasicConstants.NUMBER_SALE_TYPE_REFUNDFAIL));
　　　　logger.debug("拒绝退款，退款失败");
}

　　//执行数据库更新动作
　　sale.setStatus(VOStatus.UPDATED);
　　sale.setDispose_reason(dispose_reason);
　　// 处理人
　　sale.setPk_user(WebUtilsFactory.getInstance().getLoginInfo().getPk_user());
　　// 保存退款处理时间
　　sale.setDispose_time(new UFDateTime(System.currentTimeMillis()));
　　int resultVO = salelogService.saveOrUpdate(sale);
　　if (resultVO == 0) {
　　　　return this.genAjaxResponse(false, "处理失败！", null);
　　}
　　return this.genAjaxResponse(true, "处理成功！", null);
}
</code></pre>
<p><strong>4.3 调用证书类，类里面需要指向证书安装的路径</strong></p>
<pre><code class="java">import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.nw.web.utils.WebUtilsFactory;

import com.etom.itoilet.constants.WXGlobal;


/**
* 微信退款
* 创建一个自定义的SSLContext安全连接
* 
*/
public class ClientCustomSSL {

public static String doRefund(String url,String data) throws Exception {
　　//指定读取证书格式为PKCS12(注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的)
　　KeyStore keyStore = KeyStore.getInstance("PKCS12"); 
　　String fileName = "/cert/apiclient_cert.p12"; //文件名
　　// 指定证书路径
　　String path = "";
　　//读取本机存放的PKCS12证书文件 
　　FileInputStream instream = new FileInputStream(new File(path));

　　//比如安装在D:/pkcs12/apiclient_cert.p12情况下，就可以写成如下语句
　　//FileInputStream instream = new FileInputStream(new File("D:/pkcs12/apiclient_cert.p12")); 
　　try { 
　　　　//指定PKCS12的密码(商户ID) 
　　　　keyStore.load(instream, WXGlobal.getMch_id().toCharArray()); 
　　} finally { 
　　　　instream.close(); 
　　} 
　　SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, WXGlobal.getMch_id().toCharArray()).build(); 
　　//指定TLS版本 
　　SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext,new String[] { "TLSv1"},null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); 
　　//设置httpclient的SSLSocketFactory 
　　CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
　　try {
　　　　HttpPost httpost = new HttpPost(url); // 设置响应头信息
　　　　httpost.addHeader("Connection", "keep-alive");
　　　　httpost.addHeader("Accept", "*/*");
　　　　httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
　　　　httpost.addHeader("Host", "api.mch.weixin.qq.com");
　　　　httpost.addHeader("X-Requested-With", "XMLHttpRequest");
　　　　httpost.addHeader("Cache-Control", "max-age=0");
　　　　httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
　　　　httpost.setEntity(new StringEntity(data, "UTF-8"));
　　　　CloseableHttpResponse response = httpclient.execute(httpost);
　　try {
　　　　HttpEntity entity = response.getEntity();
　　　　String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
　　　　EntityUtils.consume(entity);
　　　　return jsonStr;
　　} finally {
　　　　response.close();
　　}
　　} finally {
　　　　httpclient.close();
　　}
　　}

}
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/99856142</p>]]></description><guid isPermaLink="false">/archives/6f3414c0-5490-4a97-8847-8997c95d0e5b</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:35:19 GMT</pubDate></item><item><title><![CDATA[List集合的交集（retainAll）、并集（removeAll，addAll）、差集（removeAll）]]></title><link>https://wiki.dgfygk.com/archives/bfb379c8-eeba-4dea-ad62-a92b0a37114d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=List%E9%9B%86%E5%90%88%E7%9A%84%E4%BA%A4%E9%9B%86%EF%BC%88retainAll%EF%BC%89%E3%80%81%E5%B9%B6%E9%9B%86%EF%BC%88removeAll%EF%BC%8CaddAll%EF%BC%89%E3%80%81%E5%B7%AE%E9%9B%86%EF%BC%88removeAll%EF%BC%89&amp;url=/archives/bfb379c8-eeba-4dea-ad62-a92b0a37114d" width="1" height="1" alt="" style="opacity:0;">
<p>|—-|——————————————–|—————————–|———|
 <br>
 | 交集 | listA.retainAll(listB) | listA内容变为listA和listB都存在的对象 | listB不变 |
 <br>
 | 差集 | listA.removeAll(listB) | listA中存在的listB的内容去重 | listB不变 |
 <br>
 | 并集 | listA.removeAll(listB) listA.addAll(listB) | 为了去重，listA先取差集，然后追加全部的listB | listB不变 |</p>
<pre><code class="java">public static void main(String[] args) {
        List&lt;String&gt; listA = new ArrayList&lt;String&gt;(){
  {
            add("A");
            add("B");
            add("C");
            add("D");
            add("E");
            add("F");
        }};

        List&lt;String&gt; listB = new ArrayList&lt;String&gt;(){
  {
            add("A");
            add("G");
            add("Q");
            add("D");
            add("O");
            add("F");
            add("M");
            add("X");
        }};

        // 并集
        System.out.println("--并集---------------------");
        listA.removeAll(listB);
        listA.addAll(listB);
        System.out.println("listA: " + listA);
        System.out.println("listB:" + listB);


        List&lt;String&gt; listC = new ArrayList&lt;String&gt;(){
  {
            add("A");
            add("B");
            add("C");
            add("D");
        }};

        List&lt;String&gt; listD = new ArrayList&lt;String&gt;(){
  {
            add("A");
            add("G");
            add("Q");
            add("D");
            add("O");
        }};
        // 交集
        System.out.println("--交集---------------------");
        listC.retainAll(listD);
        System.out.println("listC: " + listC);
        System.out.println("listD:" + listD);


        List&lt;String&gt; listE = new ArrayList&lt;String&gt;(){
  {
            add("A");
            add("B");
            add("C");
            add("D");
            add("E");
        }};

        List&lt;String&gt; listF = new ArrayList&lt;String&gt;(){
  {
            add("A");
            add("G");
            add("Q");
            add("D");
        }};

        // 差集
        System.out.println("--差集---------------------");
        listE.removeAll(listF);
        System.out.println("listE:" + listE);
        System.out.println("listF:" + listF);
    }
</code></pre>
<p>运行结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff571ef04fff310456346df2eba9b2e5a.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/96479769</p>]]></description><guid isPermaLink="false">/archives/bfb379c8-eeba-4dea-ad62-a92b0a37114d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:35:05 GMT</pubDate></item><item><title><![CDATA[Linux 查找文件find命令]]></title><link>https://wiki.dgfygk.com/archives/bd18d8dd-94e5-4a22-95ce-0ff4ece4d90b</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Linux%20%E6%9F%A5%E6%89%BE%E6%96%87%E4%BB%B6find%E5%91%BD%E4%BB%A4&amp;url=/archives/bd18d8dd-94e5-4a22-95ce-0ff4ece4d90b" width="1" height="1" alt="" style="opacity:0;">
<p>find 命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。</p>
<p>如果使用该命令时，不设置任何参数，则 find命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。</p>
<pre><code class="java">find . -iname \*.txt(大小写都匹配)
find . -type d(当前目录下的所有子目录)
find /usr -type l(当前目录下所有的符号链接)
find /usr -type l -name "z*" -ls(符号链接的详细信息 eg:inode,目录)
find /home/test -size +250000k(超过250000k的文件，当然+改成-就是小于了)
find /home/test f -perm 777 -exec ls -l {} \; (按照权限查询文件)
find /home/test -atime -1  1天内访问过的文件
find /home/test -ctime -1  1天内状态改变过的文件    
find /home/test -mtime -1  1天内修改过的文件
find /home/test -amin -1  1分钟内访问过的文件
find /home/test -cmin -1  1分钟内状态改变过的文件    
find /home/test -mmin -1  1分钟内修改过的文件
</code></pre>
<p>该文章是作者在工作中遇到的薄弱环节，用此方式来做笔记，来加强记忆。如果内容有误还望指出并赐教！</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/97647549</p>]]></description><guid isPermaLink="false">/archives/bd18d8dd-94e5-4a22-95ce-0ff4ece4d90b</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Wed, 25 Sep 2024 09:34:51 GMT</pubDate></item><item><title><![CDATA[Math.round()，Math.ceil()，Math.floor()的区别]]></title><link>https://wiki.dgfygk.com/archives/e8db46ed-844a-4212-89a7-5cbd83b7d3bc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Math.round%28%29%EF%BC%8CMath.ceil%28%29%EF%BC%8CMath.floor%28%29%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/e8db46ed-844a-4212-89a7-5cbd83b7d3bc" width="1" height="1" alt="" style="opacity:0;">
<h4>1、Math.round() “四舍五入”， 该函数返回的是一个四舍五入后的的整数</h4>
<pre><code class="java">        double d = 3.1415926;
        double d2 = 18.58;
        double d3 = -15.23;
        double d4 = -16.85;
        long round1 = Math.round(d);    // 结果 3
        long round2 = Math.round(d2);   // 结果 19
        long round3 = Math.round(d3);   // 结果 -15
        long round4 = Math.round(d4);   // 结果 -17
</code></pre>
<p>当小数点第一位=5是，这时要注意正负数的结果。例如</p>
<pre><code class="java">        double d5 = -16.5;
        double d6 = 16.5;
        long round5 = Math.round(d5);   // 结果 -16
        long round6 = Math.round(d6);   // 结果 17


总结：负数，小数点第一位是5时，直接舍去，整数部分不 +1； 正数，小数点第一位是5时，往整数部分 +1
</code></pre>
<h4>2、Math.ceil() “向上取整”， 即小数部分直接舍去，并向正数部分进1</h4>
<pre><code class="java">        double d = 3.1415926;
        double d2 = 18.58;
        double d3 = -15.23;
        double d4 = -16.85;
        double d5 = -16.5;
        double d6 = 16.5;

        double ceil1 = Math.ceil(d);    // 结果 4.0
        double ceil2 = Math.ceil(d2);   // 结果 19.0
        double ceil3 = Math.ceil(d3);   // 结果 -15.0
        double ceil4 = Math.ceil(d4);   // 结果 -16.0
        double ceil5 = Math.ceil(d5);   // 结果 -16.0
        double ceil6 = Math.ceil(d6);   // 结果 17.0

【注】该数为小数时，小数部分直接舍去
</code></pre>
<h4>3、Math.floor() “向下取整” ，即小数部分直接舍去</h4>
<pre><code class="java">        double d = 3.1415926;
        double d2 = 18.58;
        double d3 = -15.23;
        double d4 = -16.85;
        double d5 = -16.5;
        double d6 = 16.5;

        double floor1 = Math.floor(d);    // 结果 3.0
        double floor2 = Math.floor(d2);   // 结果 18.0
        double floor3 = Math.floor(d3);   // 结果 -16.0
        double floor4 = Math.floor(d4);   // 结果 -17.0
        double floor5 = Math.floor(d5);   // 结果 -17.0
        double floor6 = Math.floor(d6);   // 结果 16.0

   【注】 Math.floor()容易出现精度问题，举个最简单例子:

         对小数 8.54 保留两位小数(虽然它已经保留了 2 位小数)：

         Math.floor(8.54*100)/100 // 输出结果为 8.53, 注意是 8.53 而不是 8.54。

         所以这种函数慎用。
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/96978200</p>]]></description><guid isPermaLink="false">/archives/e8db46ed-844a-4212-89a7-5cbd83b7d3bc</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:34:35 GMT</pubDate></item><item><title><![CDATA[Md5加密工具]]></title><link>https://wiki.dgfygk.com/archives/63ffa7f3-b187-4554-bf09-223c83030200</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Md5%E5%8A%A0%E5%AF%86%E5%B7%A5%E5%85%B7&amp;url=/archives/63ffa7f3-b187-4554-bf09-223c83030200" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7eec2397ff33f8ae45182316875e6a0f.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe1aa1a9f38731d777d3882d180a4307c.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1e1a1f49a54678687eec95dd1d8167c8.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/97917596</p>]]></description><guid isPermaLink="false">/archives/63ffa7f3-b187-4554-bf09-223c83030200</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:33:08 GMT</pubDate></item><item><title><![CDATA[MongoDB_可视化工具Robo 3T]]></title><link>https://wiki.dgfygk.com/archives/64756589-736d-4c00-abb4-3b19b7f2dbbb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MongoDB_%E5%8F%AF%E8%A7%86%E5%8C%96%E5%B7%A5%E5%85%B7Robo%203T&amp;url=/archives/64756589-736d-4c00-abb4-3b19b7f2dbbb" width="1" height="1" alt="" style="opacity:0;">
<h4>Robo 3T可以对MongoDB进行可视化操作。</h4>
<h3>　一、Robo 3T安装</h3>
<h4>　　官网下载地址：https://robomongo.org/</h4>
<p>　　进入官网，点击下载，Studio 3T功能更全面，基础功能是免费的，部分功能是收费的，这里选择Robo 3T就够基础学习使用了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fca65390e2c139ed055b6eda51495b7ff.png&amp;size=m" alt=""></p>
<p>点击 Download Robo 3T</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6220723a93a11cb5d310357c885474c6.png&amp;size=m" alt=""></p>
<p>选择操作系统和下载版本(可以选择.exe文件或者.zip压缩文件，本次安装选择的是.exe文件)</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6b84db24fe632d2fb353bbd617dd6c85.png&amp;size=m" alt=""></p>
<p>下载完成后，点击下载的.exe安装包，出现如下弹框</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F79018071fa0aa149b98c29720b701cd9.png&amp;size=m" alt=""></p>
<p>点击'下一步'',出现如下弹框</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F30e4bbb7d0de481fa8c88733ffd19699.png&amp;size=m" alt=""></p>
<p>点击'我接受'，出现如下弹框</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc04b55fa6091513ef29bdfb7a963ae71.png&amp;size=m" alt=""></p>
<p>选择安装路径，点击'下一步'，出现如下弹框</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0f9e12961700ecf706003b8622d8365a.png&amp;size=m" alt=""></p>
<p>点击'安装'，等待程序安装完成</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe710588b094ef505d23710a37e61161a.png&amp;size=m" alt=""></p>
<p>安装成功！！！</p>
<h4>二、使用Robo 3T</h4>
<p>1、打开Robo 3T,点击Create连接MongoDB服务器（确保MongoDB服务已启动）</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F77b289c5973bd80938c33b29cd333f44.png&amp;size=m" alt=""></p>
<p>2、在弹框中填写数据库信息，连接成功后如图</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F11c24b550819b6a6f91d08ec24b507ac.png&amp;size=m" alt=""></p>
<p>3、如下图 右键选择'Open Shell'打开一个shell窗口，可以对数据库进行基本操作</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbb557fe349dd4ee5fa3750cb96828c84.png&amp;size=m" alt=""></p>
<p>4、运行命令的时候，需要选中一行运行，否则从第一行开始运行全部命令</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F66c7b55c7576f0941d74f084cbef15f3.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/97632288</p>]]></description><guid isPermaLink="false">/archives/64756589-736d-4c00-abb4-3b19b7f2dbbb</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 09:32:45 GMT</pubDate></item><item><title><![CDATA[@Resource注解问题]]></title><link>https://wiki.dgfygk.com/archives/a5851956-c860-4fb5-b4d6-5ab22236fa1d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%40Resource%E6%B3%A8%E8%A7%A3%E9%97%AE%E9%A2%98&amp;url=/archives/a5851956-c860-4fb5-b4d6-5ab22236fa1d" width="1" height="1" alt="" style="opacity:0;">
<p>今天使用项目时候，有redis注解突然显红色，尽管没有什么影响但是还是看着不舒服，情况如下:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F307780290db39666331091198998e4b6.png&amp;size=m" alt=""></p>
<p>解决办法：</p>
<p>选中项目 然后按F4 进入</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9da55efaf3d021aa112c94ee3691df7f.png&amp;size=m" alt=""></p>
<p>然后选择Spring</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F70be6c176fe2fa9729ce1bbffb43f9b3.png&amp;size=m" alt=""></p>
<p>把Spring下的所有文件都删除， apply-&gt; ok ; 然后重新添加文件 再次Apply - OK 即可</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9ab2a0f209ac48d2cfc6199f37592c2a.png&amp;size=m" alt=""></p>
<p>结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3060b00fca2c191a87fff27fbf23de08.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/92806877</p>]]></description><guid isPermaLink="false">/archives/a5851956-c860-4fb5-b4d6-5ab22236fa1d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:31:20 GMT</pubDate></item><item><title><![CDATA[DecimalFormat 中 0 和 # 的区别]]></title><link>https://wiki.dgfygk.com/archives/c298da1e-730d-4bd7-bad0-5e7ff2734c09</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=DecimalFormat%20%E4%B8%AD%200%20%E5%92%8C%20%23%20%E7%9A%84%E5%8C%BA%E5%88%AB&amp;url=/archives/c298da1e-730d-4bd7-bad0-5e7ff2734c09" width="1" height="1" alt="" style="opacity:0;">
<p>先来介绍一下"0"和”#“的区别</p>
<h4>1. 以"0"补位时：</h4>
<p>如果数字少了，就会补"0”，小数和整数都会补；</p>
<p>如果数字多了，就切掉，但只切小数的末尾，整数不能切；</p>
<p>同时被切掉的小数位会进行四舍五入处理。</p>
<h4>2. 以”#“补位时：</h4>
<p>如果数字少了，则不处理，不会补"0”，也不会补”#“；</p>
<p>如果数字多了，就切掉，但只切小数的末尾，整数不能切；</p>
<p>同时被切掉的小数位会进行四舍五入处理。</p>
<p>上代码示例：</p>
<pre><code class="java">System.out.println("DecimalFormat 中 0 和 # 补位的区别：");
        System.out.println("0 补位：");
        // 0: 比实际数字的位数多，不足的地方用0补上。
        double a = 3.1415926535898;
        String format1 = new DecimalFormat("00.00").format(a);//结果：03.14
        String format2 = new DecimalFormat("0.000").format(a);//结果：3.140
        String format3 = new DecimalFormat("00.000").format(a);//结果：03.140
        System.out.println("0补位-比实际数字的位数多[原数：3.1415926535898, 格式：00.00]：" + format1);
        System.out.println("0补位-比实际数字的位数多[原数：3.1415926535898, 格式：0.000]：" + format2);
        System.out.println("0补位-比实际数字的位数多[原数：3.1415926535898, 格式：00.000]：" + format3);
        // 0: 比实际数字的位数少：整数部分不改动，小数部分，四舍五入
        String format4 = new DecimalFormat("0.000").format(13.146);//结果：13.146
        String format5 = new DecimalFormat("00.00").format(13.146);//结果：13.15
        String format6 = new DecimalFormat("0.00").format(13.146);//结果：13.15
        System.out.println("0补位-比实际数字的位数少[原数：13.146, 格式：0.000]：" + format4);
        System.out.println("0补位-比实际数字的位数少[原数：13.146, 格式：00.00]：" + format5);
        System.out.println("0补位-比实际数字的位数少[原数：13.146, 格式：0.00]：" + format6);


        System.out.println("# 补位：");
        // #: 比实际数字的位数多，不变。
        String format7 = new DecimalFormat("##.##").format(a);//结果：03.14
        String format8 = new DecimalFormat("#.###").format(a);//结果：3.140
        String format9 = new DecimalFormat("##.###").format(a);//结果：03.140
        System.out.println("#补位-比实际数字的位数多[原数：3.1415926535898, 格式：##.##]，" + "结果：" + format7);
        System.out.println("#补位-比实际数字的位数多[原数：3.1415926535898, 格式：#.###]，" + "结果：" + format8);
        System.out.println("#补位-比实际数字的位数多[原数：3.1415926535898, 格式：##.###]，" + "结果：" + format9);

        // #:  比实际数字的位数少：整数部分不改动，小数部分，四舍五入
        String format10 = new DecimalFormat("#.###").format(13.146);//结果：13.146
        String format11 = new DecimalFormat("##.##").format(13.146);//结果：13.15
        String format12 = new DecimalFormat("#.##").format(13.146);//结果：13.15
        System.out.println("#补位-比实际数字的位数少[原数：13.146, 格式：#.###]，" + "结果：" + format10);
        System.out.println("#补位-比实际数字的位数少[原数：13.146, 格式：##.##]，" + "结果：" + format11);
        System.out.println("#补位-比实际数字的位数少[原数：13.146, 格式：#.##]，" + "结果：" + format12);

        System.out.println("DecimalFormat 其他用法：");
        DecimalFormat format13 = new DecimalFormat("###,###.0000");//使用系统默认的格式
        System.out.println("使用系统默认的格式:"+format13.format(111111123456.12));

        Locale.setDefault(Locale.US);
        DecimalFormat format14 = new DecimalFormat("###,###.0000");//使用美国的格式
        System.out.println("使用美国的格式:" + format14.format(111111123456.12));

        DecimalFormat format15 = new DecimalFormat();
        format15.applyPattern("##,###.000");
        System.out.println("##,###.000 ：" + format15.format(11112345.12345));

        DecimalFormat format16 = new DecimalFormat();
        format16.applyPattern("0.000E0000");
        System.out.println("控制指数输出：" + format16.format(10000));
        System.out.println("控制指数输出：" + format16.format(12345678.345));

        /** DecimalFormat是NumberFormat的一个子类,其实例被指定为特定的地区。
         * 因此，你可以使用NumberFormat.getInstance 指定一个地区，
         * 然后将结构强制转换为一个DecimalFormat对象。
         * 文档中提到这个技术可以在大多情况下适用，但是你需要用try/catch 块包围强制转换以防转换不能正常工作
         * (大概在非常不明显得情况下使用一个奇异的地区)。
         */
        DecimalFormat percentInstance = (DecimalFormat) NumberFormat.getPercentInstance();
        percentInstance.applyPattern("00.0000%");
        System.out.println("百分数：" + percentInstance.format(0.34567));
        System.out.println("百分数：" + percentInstance.format(1.34567));
</code></pre>
<p>运行结果：</p>
<pre><code class="html">DecimalFormat 中 0 和 # 补位的区别：
0 补位：
0补位-比实际数字的位数多[原数：3.1415926535898, 格式：00.00]：03.14
0补位-比实际数字的位数多[原数：3.1415926535898, 格式：0.000]：3.142
0补位-比实际数字的位数多[原数：3.1415926535898, 格式：00.000]：03.142
0补位-比实际数字的位数少[原数：13.146, 格式：0.000]：13.146
0补位-比实际数字的位数少[原数：13.146, 格式：00.00]：13.15
0补位-比实际数字的位数少[原数：13.146, 格式：0.00]：13.15
# 补位：
#补位-比实际数字的位数多[原数：3.1415926535898, 格式：##.##]，结果：3.14
#补位-比实际数字的位数多[原数：3.1415926535898, 格式：#.###]，结果：3.142
#补位-比实际数字的位数多[原数：3.1415926535898, 格式：##.###]，结果：3.142
#补位-比实际数字的位数少[原数：13.146, 格式：#.###]，结果：13.146
#补位-比实际数字的位数少[原数：13.146, 格式：##.##]，结果：13.15
#补位-比实际数字的位数少[原数：13.146, 格式：#.##]，结果：13.15
DecimalFormat 其他用法：
使用系统默认的格式:111,111,123,456.1200
使用美国的格式:111,111,123,456.1200
##,###.000 ：11,112,345.123
控制指数输出：1.000E0004
控制指数输出：1.235E0007
百分数：34.5670%
百分数：134.5670%
</code></pre>
<p><strong>总结：</strong></p>
<ol>
 <li>
  <p>“0"强制按格式对齐，“#“最充足的情况下以这样的格式对齐；</p>
 </li>
 <li>
  <p>“#“适用的场景是当小数位超过两位时，只显示两位，但只有一位或没有，则不需要补"0”；</p>
 </li>
 <li>
  <p>整数位用多个”#“没有意义。</p>
 </li>
</ol>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/93506205</p>]]></description><guid isPermaLink="false">/archives/c298da1e-730d-4bd7-bad0-5e7ff2734c09</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:30:59 GMT</pubDate></item><item><title><![CDATA[java后台实现支付宝支付接口、支付宝订单查询接口 前端为APP]]></title><link>https://wiki.dgfygk.com/archives/89dc110c-26b7-4734-a5ca-309d03b8a6d5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%E5%90%8E%E5%8F%B0%E5%AE%9E%E7%8E%B0%E6%94%AF%E4%BB%98%E5%AE%9D%E6%94%AF%E4%BB%98%E6%8E%A5%E5%8F%A3%E3%80%81%E6%94%AF%E4%BB%98%E5%AE%9D%E8%AE%A2%E5%8D%95%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3%20%E5%89%8D%E7%AB%AF%E4%B8%BAAPP&amp;url=/archives/89dc110c-26b7-4734-a5ca-309d03b8a6d5" width="1" height="1" alt="" style="opacity:0;">
<p><a href="https://blog.csdn.net/Ouyzc/article/details/79551714">https://blog.csdn.net/Ouyzc/article/details/79551714</a></p>
<p>最近项目APP需要接入微信、支付宝支付功能，在分配开发任务时，听说微信支付接口比支付宝支付接口要难实现，由于我开发经验不是那么丰富（现工作经验1年半）且未接触过支付接口开发，组里刚好又有支付接口的老司机，所以很自然把简单的支付宝接口开发任务交给了我，看来开发组的组长还是很好人的嘛…..，废话就不多说了，我们开始吧！</p>
<p>实现支付宝接口详细过程
 <br>
 1.去支付宝官网申请公司企业账号并开通一个应用，在应用里签约APP支付功能</p>
<p>具体的申请截图步骤，在这里我就不详细说了，因为这不是文章的重点，可参考支付宝官网。</p>
<p>经过这一步，我们可以得过开发中需要用到的几个参数</p>
<p>①商户appid ②商户公钥、私钥 ③支付宝公钥 ④支付宝网关地址</p>
<p>解释一下这几个参数：</p>
<p>1.商户appid是识别商户的唯一ID，是让支付宝识别，我们到底是哪一个商户，这样支付宝就能识别商户对应的账号、用户号、收款账号…等等一系列信息。</p>
<p>2.商户公钥、私钥以及支付宝公钥这3个参数是对商户系统与支付宝进行信息交互的数字签名用的，相信各位大学里也有学过关于数字签名的一些知识，在这里，我就简单说一下我理解的过程：首先是商户系统需要给支付宝发送信息（支付、查询等等….），涉及钱方面，咱们当前要谨慎一点对吧，所以我们需要对发送之前的信息加把锁（用商户私钥进行签名），然后再发送给支付宝。支付宝收到商户发送的信息之后，发现上了把锁，那肯定得要一把钥匙（商户公钥）来解锁对吧，所以商户在跟支付宝签约APP支付功能的时候，就得把这把钥匙上传给支付宝了，支付宝就可以用商户的公钥进行解锁了。反过来也是一样，支付宝需要发送信息给商户信息，先用支付宝的私钥进行签名，再发送给商户系统，商户系统收到支付宝反馈过来的信息后，再用支付宝的公钥进行解密。在这里我们并没有用到支付宝的私钥，所以我们并不需要得到支付宝的私钥。这里放一个生成私钥公钥的支付宝官方工具</p>
<p>3.支付宝网关地址，是用来配置发送给支付宝的网关地址的。</p>
<p>2.将支付宝的SDK集成到项目系统里</p>
<p>支付宝的SDK指的就是支付宝提供的工具Jar包给我们开发者，SDK封装了大量的基础功能，使我们可以快速开发支付宝接口。这也是我在前面说的比微信支付接口更容易实现的原因。获取支付宝SDK地址：支付宝SDK下载地址，这里我选择JAVA的SDK。下载解压后的SDK得到：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F00a05b84a5761d89c4ccbabca113651f.png&amp;size=m" alt=""></p>
<p><strong>alipay-sdk-java20180122110032.jar、commons-logging-1.1.1.jar</strong>是我们需要导入到项目里的，因为项目后台的大致的架构是maven+springBoot+jpa,所以我们需要从maven里导入jar包，首先是导入commons-logging-1.1.1.jar，不用多说，咱直接在pom.xml里加上:</p>
<p><strong>
  <dependency>
   <br>
   <groupid>commons-logging</groupid>
   <br>
   <artifactid>commons-logging</artifactid>
   <br>
   <version>1.1.1</version>
   <br>
  </dependency>
 </strong></p>
<p>然后是 alipay-sdk-java20180122110032.jar，如果你也像上面一样直接加入到pom.xml，会发现，咦，怎么一直下载不下来。当然alipy的包在线上的maven仓库并没有，所以我们需要导入到本地的maven仓库。前提是配置好maven的环境变量，将包放在G:\alipay\sdk下，然后打开dos窗口,cd进入到G:\alipay\sdk下，执行maven如下命令：</p>
<p><strong>mvn install:install-file -DgroupId=com.alipay -DartifactId=sdk-java -Dversion=20180122110032 -Dpackaging=jar -Dfile=alipay-sdk-java20180122110032.jar</strong></p>
<p>导入成功后，在项目的pom.xml里继续添加</p>
<p><!--&ndash; 支付宝SDK &ndash;-->
 <br>
 <strong>
  <dependency>
   <br>
   <groupid>com.alipay</groupid>
   <br>
   <artifactid>sdk-java</artifactid>
   <br>
   <version>20180122110032</version>
   <br>
  </dependency>
 </strong></p>
<p>到此，我们就顺利把支付宝SDK集成到项目里，是不是有点小兴奋，我们很快可以开发了！</p>
<p>3.看支付宝提供的API和网上各位牛人总结的经验，后台使用支付宝的SDK与支付宝进行交互</p>
<p>先看一下支付宝支付流程的图：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8cf672951a4352039109fbd45e8387f1.png&amp;size=m" alt=""></p>
<p>首先，我们来理一理开发的思路，按照我当前项目的需求，关于支付这一块大概操作流程是：用户在APP上选好要购买的商品，点击"立即购买”，跳转到订单详细页面。选择支付方式，点击"确定支付"跳转到支付宝APP，付款完成后，跳转回APP，完成支付。这个过程，当用户点击"确定支付"时，APP需要调用商户后台接口。</p>
<p>这时候就是我们所需要做的事情：先是生成商户系统一笔未支付的订单，获得商户订单ID（商户系统生成）和订单的一些其他信息，然后再调用支付宝的SDK提供的数字签名方法，将需要传给支付宝的信息进行加签，然后把加签后的字符串返回给APP。APP拉起支付宝APP，再把这个加签的字符串传给支付宝，完成支付。APP接收到同步通知后，还需要再次调用商户后台的接口（虽然同步通知也有付款情况，但需要以后台通知为准），校验订单最终的付款情况。按照支付宝API上所说，当完成支付后，支付宝会做2个操作，一个是同步返回信息给APP，一个是异步通知商户后台返回支付状态等信息，并且最终的支付结果是以异步通知为准。所以我们还需要考虑到一点，就是当用户支付成功之后，商户系统暂时没有接收到支付宝的异步通知时。我们需要拿着这个商户订单ID主动调用SDK支付宝的查询接口，去获取该订单的支付情况，并最终返回给APP。这个查询的接口应该是给APP收到同步通知后，请求商户系统后台进行校验的时候调用的。</p>
<p>根据我们上面思考所得，后台只需要对外提供3个接口即可</p>
<p>1.用户点击"立即购买"时调用商户后台接口，后台返回加签后的订单信息字符串</p>
<p>2.在支付完成之后，支付宝异步通知商户后台订单的付款情况</p>
<p>3.在支付完成之后，跳转回APP时，APP调用商户后台进行最终付款校验</p>
<p>想通想明白之后，终于接一下我们要敲代码了，哈哈哈哈</p>
<p>首先，我们来准备一下需要传给支付宝SDK的公共基本参数，我把参数放到一个单独的类里，你也可以放到数据库里，代码如下：</p>
<pre><code class="java">public class AlipayConfig {
    // 1.商户appid
    //public static String APPID = "2017...";    

    //2.私钥 pkcs8格式的
    public static String RSA_PRIVATE_KEY ="MIIEwAIBADANBg.....";

    // 3.支付宝公钥
    public static String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkq.....";

    // 4.服务器异步通知页面路径 需http://或者https://格式的完整路径，不能加?id=123这类自定义参数，必须外网可以正常访问
    public static String notify_url = "http://www.xxx.com/alipay/notify_url.do";

     //5.页面跳转同步通知页面路径 需http://或者https://格式的完整路径，不能加?id=123这类自定义参数，必须外网可以正常访问 商户可以自定义同步跳转地址
    public static String return_url = "http://www.xxx.com/alipay/return_url.do";

    // 6.请求支付宝的网关地址
    public static String URL = "https://openapi.alipay.com/gateway.do";    

    // 7.编码
    public static String CHARSET = "UTF-8";

    // 8.返回格式
    public static String FORMAT = "json";

    // 9.加密类型
    public static String SIGNTYPE = "RSA2";

}
</code></pre>
<p>1.实现第一个接口：用户点击"立即购买"时调用商户后台接口，后台返回加签后的订单信息字符串。我把主要的处理逻辑写在Service层了，Controller层直接调用就可以，这里就不放Controller层的代码了</p>
<p>生成商户订单的代码，我就不放了，这个根据各自的业务需求来做，生成后订单之后，把订单信息传进来该方法进行处理，返回加签后的字符串，直接返回给APP即可，代码如下：</p>
<pre><code class="java">/**
     * 获取支付宝加签后台的订单信息字符串
     * 
     * @param request
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public String getAliPayOrderStr(OrderTest orderTest) {

        //最终返回加签之后的，app需要传给支付宝app的订单信息字符串   
        String orderString = "";
        logger.info("==================支付宝下单,商户订单号为："+orderTest.getOutTradeNo());

        //创建商户支付宝订单(因为需要记录每次支付宝支付的记录信息，单独存一个表跟商户订单表关联，以便以后查证)
        AlipaymentOrder alipaymentOrder=new AlipaymentOrder();
        alipaymentOrder.setClubOrderId(orderTest.getId().toString());//商家订单主键
        alipaymentOrder.setOutTradeNo(orderTest.getOutTradeNo());//商户订单号
        alipaymentOrder.setTradeStatus((byte) 0);//交易状态
        alipaymentOrder.setTotalAmount(Double.parseDouble(orderTest.getTotalAmount()));//订单金额
        alipaymentOrder.setReceiptAmount(0.00);//实收金额
        alipaymentOrder.setInvoiceAmount(0.00);//开票金额
        alipaymentOrder.setBuyerPayAmount(0.00);//付款金额
        alipaymentOrder.setRefundFee(0.00); //总退款金额

        try{    
        //实例化客户端（参数：网关地址、商户appid、商户私钥、格式、编码、支付宝公钥、加密类型），为了取得预付订单信息
                AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID, 
                        AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, 
                        AlipayConfig.ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);

                //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称：alipay.trade.app.pay 
                AlipayTradeAppPayRequest ali_request = new AlipayTradeAppPayRequest();

                //SDK已经封装掉了公共参数，这里只需要传入业务参数。以下方法为sdk的model入参方式
                AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();

                //业务参数传入,可以传很多，参考API
                //model.setPassbackParams(URLEncoder.encode(request.getBody().toString())); //公用参数（附加数据）
                model.setBody(orderTest.getBody());                       //对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。
                model.setSubject(orderTest.getSubjecy());                 //商品名称
                model.setOutTradeNo(orderTest.getOutTradeNo());           //商户订单号(自动生成)
               // model.setTimeoutExpress("30m");                 //交易超时时间
                model.setTotalAmount(orderTest.getTotalAmount());         //支付金额
                model.setProductCode("QUICK_MSECURITY_PAY");              //销售产品码（固定值）
                ali_request.setBizModel(model);  
                logger.info("====================异步通知的地址为："+alipayment.getNotifyUrl());
                ali_request.setNotifyUrl(AlipayConfig.notify_url);        //异步回调地址（后台）
                ali_request.setReturnUrl(AlipayConfig.return_url);      //同步回调地址（APP）

                // 这里和普通的接口调用不同，使用的是sdkExecute
        AlipayTradeAppPayResponse alipayTradeAppPayResponse = alipayClient.sdkExecute(ali_request); //返回支付宝订单信息(预处理)
        orderString=alipayTradeAppPayResponse.getBody();//就是orderString 可以直接给APP请求，无需再做处理。
        this.createAlipayMentOrder(alipaymentOrder);//创建新的商户支付宝订单

        } catch (AlipayApiException e) {
            e.printStackTrace();
            logger.info("与支付宝交互出错，未能生成订单，请检查代码！");
        } 

        return orderString;
    }
</code></pre>
<p>2.实现第二个接口：在支付完成之后，支付宝异步通知商户后台订单的付款情况，这个是支付宝每隔一段时间来访问一次的接口，直到你返回success,才会停止访问，这里我分了2个地方进行调用</p>
<pre><code class="java">        /**
     * 支付宝支付成功后.异步请求该接口
     * @param request
     * @return
     * @throws IOException
     */  
    @RequestMapping(value="/notify_url",method=RequestMethod.POST)
    @ResponseBody
    public String notify(HttpServletRequest request,HttpServletResponse response) throws IOException {  
        logger.info("==================支付宝异步返回支付结果开始");
        //1.从支付宝回调的request域中取值  
        //获取支付宝返回的参数集合
        Map&lt;String, String[]&gt; aliParams = request.getParameterMap();  
        //用以存放转化后的参数集合
        Map&lt;String, String&gt; conversionParams = new HashMap&lt;String, String&gt;();  
        for (Iterator&lt;String&gt; iter = aliParams.keySet().iterator(); iter.hasNext();) {  
            String key = iter.next();  
            String[] values = aliParams.get(key);  
            String valueStr = "";  
            for (int i = 0; i &lt; values.length; i++) {  
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";  
            }  
            // 乱码解决，这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化  
            // valueStr = new String(valueStr.getBytes("ISO-8859-1"), "uft-8");  
            conversionParams.put(key, valueStr);  
        }   
        logger.info("==================返回参数集合："+conversionParams);
       String status=alipayMentOrderService.notify(conversionParams);
       return status;
    }
</code></pre>
<pre><code>```java
 /**
     * 支付宝异步请求逻辑处理
     * @param request
     * @return
     * @throws IOException
     */ 
    public String notify(Map&lt;String, String&gt; conversionParams){

        logger.info("==================支付宝异步请求逻辑处理");

         //签名验证(对支付宝返回的数据验证，确定是支付宝返回的)
        boolean signVerified = false;    

        try {  
            //调用SDK验证签名
            signVerified = AlipaySignature.rsaCheckV1(conversionParams, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE); 

        } catch (AlipayApiException e) {  
            logger.info("==================验签失败 ！");    
            e.printStackTrace();         
        }    

        //对验签进行处理
        if (signVerified) {
            //验签通过              
            //获取需要保存的数据
                String appId=conversionParams.get("app_id");//支付宝分配给开发者的应用Id
            String notifyTime=conversionParams.get("notify_time");//通知时间:yyyy-MM-dd HH:mm:ss
            String gmtCreate=conversionParams.get("gmt_create");//交易创建时间:yyyy-MM-dd HH:mm:ss
            String gmtPayment=conversionParams.get("gmt_payment");//交易付款时间
            String gmtRefund=conversionParams.get("gmt_refund");//交易退款时间
            String gmtClose=conversionParams.get("gmt_close");//交易结束时间
            String tradeNo=conversionParams.get("trade_no");//支付宝的交易号
            String outTradeNo = conversionParams.get("out_trade_no");//获取商户之前传给支付宝的订单号（商户系统的唯一订单号）
            String outBizNo=conversionParams.get("out_biz_no");//商户业务号(商户业务ID，主要是退款通知中返回退款申请的流水号)
            String buyerLogonId=conversionParams.get("buyer_logon_id");//买家支付宝账号
            String sellerId=conversionParams.get("seller_id");//卖家支付宝用户号
            String sellerEmail=conversionParams.get("seller_email");//卖家支付宝账号
            String totalAmount=conversionParams.get("total_amount");//订单金额:本次交易支付的订单金额，单位为人民币（元）
            String receiptAmount=conversionParams.get("receipt_amount");//实收金额:商家在交易中实际收到的款项，单位为元
            String invoiceAmount=conversionParams.get("invoice_amount");//开票金额:用户在交易中支付的可开发票的金额
            String buyerPayAmount=conversionParams.get("buyer_pay_amount");//付款金额:用户在交易中支付的金额         
            String tradeStatus = conversionParams.get("trade_status");// 获取交易状态 

            //支付宝官方建议校验的值（out_trade_no、total_amount、sellerId、app_id）
            AlipaymentOrder alipaymentOrder=this.selectByOutTradeNo(outTradeNo);    

            if(alipaymentOrder!=null&amp;&amp;totalAmount.equals(alipaymentOrder.getTotalAmount().toString())&amp;&amp;AlipayConfig.APPID.equals(appId)){
                //修改数据库支付宝订单表(因为要保存每次支付宝返回的信息到数据库里，以便以后查证)
                alipaymentOrder.setNotifyTime(dateFormat(notifyTime));
                alipaymentOrder.setGmtCreate(dateFormat(gmtCreate));
                alipaymentOrder.setGmtPayment(dateFormat(gmtPayment));
                alipaymentOrder.setGmtRefund(dateFormat(gmtRefund));
                alipaymentOrder.setGmtClose(dateFormat(gmtClose));
                alipaymentOrder.setTradeNo(tradeNo);
                alipaymentOrder.setOutBizNo(outBizNo);
                alipaymentOrder.setBuyerLogonId(buyerLogonId);
                alipaymentOrder.setSellerId(sellerId);
                alipaymentOrder.setSellerEmail(sellerEmail);
                alipaymentOrder.setTotalAmount(Double.parseDouble(totalAmount));
                alipaymentOrder.setReceiptAmount(Double.parseDouble(receiptAmount));
                alipaymentOrder.setInvoiceAmount(Double.parseDouble(invoiceAmount));
                alipaymentOrder.setBuyerPayAmount(Double.parseDouble(buyerPayAmount));
                switch (tradeStatus) // 判断交易结果
                        {
                        case "TRADE_FINISHED": // 交易结束并不可退款
                            alipaymentOrder.setTradeStatus((byte) 3);
                            break;
                        case "TRADE_SUCCESS": // 交易支付成功
                            alipaymentOrder.setTradeStatus((byte) 2);                     
                            break;
                        case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
                            alipaymentOrder.setTradeStatus((byte) 1);
                            break;
                        case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
                            alipaymentOrder.setTradeStatus((byte) 0);
                            break;
                        default:
                            break;
                        }
                int returnResult=this.updateByPrimaryKey(alipaymentOrder);    //更新交易表中状态

                if(tradeStatus.equals("TRADE_SUCCESS")) {    //只处理支付成功的订单: 修改交易表状态,支付成功

                    if(returnResult&gt;0){
                         return "success";
                    }else{
                         return "fail";
                    }
                }else{
                    return "fail";
                }

            }else{
                logger.info("==================支付宝官方建议校验的值（out_trade_no、total_amount、sellerId、app_id）,不一致！返回fail");
                return"fail";
            }

        } else {  //验签不通过   
            logger.info("==================验签不通过 ！");
            return "fail";
        }

    }
</code></pre>
<p>3.实现第三个接口：在支付完成之后，跳转回APP时，APP调用商户后台进行最终付款校验。我把主要的处理逻辑写在Service层了，Controller层直接调用就可以，这里就不放Controller层的代码了。</p>
<pre><code class="java">/**
     * 向支付宝发起订单查询请求
     * @param request
     * @return
     * @throws IOException
     */ 
    @Override
    public Byte checkAlipay(String outTradeNo) {
        logger.info("==================向支付宝发起查询，查询商户订单号为："+outTradeNo);

        try {
        //实例化客户端（参数：网关地址、商户appid、商户私钥、格式、编码、支付宝公钥、加密类型）
                AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID, 
                AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, 
                AlipayConfig.ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);
        AlipayTradeQueryRequest alipayTradeQueryRequest = new AlipayTradeQueryRequest();
        alipayTradeQueryRequest.setBizContent("{" +
        "\"out_trade_no\":\""+outTradeNo+"\"" +
        "}");
        AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.execute(alipayTradeQueryRequest);  
        if(alipayTradeQueryResponse.isSuccess()){

            AlipaymentOrder alipaymentOrder=this.selectByOutTradeNo(outTradeNo);
            //修改数据库支付宝订单表
            alipaymentOrder.setTradeNo(alipayTradeQueryResponse.getTradeNo());
            alipaymentOrder.setBuyerLogonId(alipayTradeQueryResponse.getBuyerLogonId());
            alipaymentOrder.setTotalAmount(Double.parseDouble(alipayTradeQueryResponse.getTotalAmount()));
            alipaymentOrder.setReceiptAmount(Double.parseDouble(alipayTradeQueryResponse.getReceiptAmount()));
            alipaymentOrder.setInvoiceAmount(Double.parseDouble(alipayTradeQueryResponse.getInvoiceAmount()));
            alipaymentOrder.setBuyerPayAmount(Double.parseDouble(alipayTradeQueryResponse.getBuyerPayAmount()));
            switch (alipayTradeQueryResponse.getTradeStatus()) // 判断交易结果
                    {
                    case "TRADE_FINISHED": // 交易结束并不可退款
                        alipaymentOrder.setTradeStatus((byte) 3);
                        break;
                    case "TRADE_SUCCESS": // 交易支付成功
                        alipaymentOrder.setTradeStatus((byte) 2);           
                        break;
                    case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
                        alipaymentOrder.setTradeStatus((byte) 1);           
                        break;
                    case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
                        alipaymentOrder.setTradeStatus((byte) 0);
                        break;
                    default:
                        break;
                    }
            this.updateByPrimaryKey(alipaymentOrder); //更新表记录
            return alipaymentOrder.getTradeStatus();
        } else {
            logger.info("==================调用支付宝查询接口失败！");
        }
        } catch (AlipayApiException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return 0;
    }
</code></pre>
<p>至此，代码已经上完了，里面可能涉及部分业务代码，如果各位需要拿代码，需要把业务代码换成自己所需要的。</p>
<p>建议：可以边看API边进行开发，主要是看我们需要给支付传什么参数，支付宝可以给我们传什么参数，不然没看清除，你会多很多坑要踩的，亲试过。</p>
<p>感觉太少图片了，这里给几张API的图片。。你们也可以自己去看 一些支付宝API
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd0a0acc6b23ff9b09408ce2e5b45f484.png&amp;size=m" alt=""></p>
<p><strong>4.关于测试的一些事</strong></p>
<p>支付宝有提供沙箱环境进行测试所使用，见<a href="https://docs.open.alipay.com/204/106450/">支付宝沙箱调试指南</a>。</p>
<p>但是楼主比较有米，直接用真实环境进行测试，其实测一次0.01元也是挺贵的吧，前提是你的电脑必须访问外网和能够被外网所访问，建议可以找个内网穿透的工具。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/92118324</p>]]></description><guid isPermaLink="false">/archives/89dc110c-26b7-4734-a5ca-309d03b8a6d5</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:30:41 GMT</pubDate></item><item><title><![CDATA[lombok的@Accessors注解]]></title><link>https://wiki.dgfygk.com/archives/9e8d8412-6c75-42e4-86b4-278395fc7019</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=lombok%E7%9A%84%40Accessors%E6%B3%A8%E8%A7%A3&amp;url=/archives/9e8d8412-6c75-42e4-86b4-278395fc7019" width="1" height="1" alt="" style="opacity:0;">
<h4>maven 导包：</h4>
<pre><code class="java">&lt;!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
            &lt;version&gt;1.16.18&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;
</code></pre>
<h4>@Accessors翻译是存取器。通过该注解可以控制getter和setter方法的形式。</h4>
<h4>@Accessors 注解用来配置lombok如何产生和显示getters和setters的方法。@Accessors注解既可以注解在类上也可以注解在属性上，@Accessors有三个属性如下：</h4>
<p>*</p>
<h4>fluent</h4>
<p>*</p>
<h4>chain</h4>
<p>*</p>
<h4>prefix</h4>
<h4>1、@Accessors(fluent = “true/false”)</h4>
<p><strong>fluent为一个布尔值，中文含义是流畅的，默认为false, 如果为true 生成的get/set方法则没有set/get前缀,都是基础属性名, 且setter方法返回当前对象</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3df8fd120d974888d64fa4b4f4580152.png&amp;size=m" alt=""></p>
<h4>注：fluent：为true时，默认设置chain为true，setter的方法名修改为字段名</h4>
<h4>2、@Accessors(chain = “true/false”)</h4>
<p><strong>chain为一个布尔值，如果为true生成的set方法返回this，为false生成的set方法是void类型。默认为false，除非当fluent为true时，chain默认则为true, chain = true 是，对对象设置时候可以使用Lambda表达式。</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F84ead335ea53cb68176c2a84e44d3332.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fea1e5738a4cc112c42f3edbe2932f719.png&amp;size=m" alt=""></p>
<h4>3、@Accessors(prefix = “f”)</h4>
<p><strong>使用prefix属性，getter和setter方法会忽视属性名的指定前缀（遵守驼峰命名）</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F05ce5c035316132fbf44438651c52a26.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/94430336</p>]]></description><guid isPermaLink="false">/archives/9e8d8412-6c75-42e4-86b4-278395fc7019</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:30:23 GMT</pubDate></item><item><title><![CDATA[MYSQL 中使用奇数偶数方法]]></title><link>https://wiki.dgfygk.com/archives/7b071a3a-f644-4561-b84d-3267d281d2fa</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MYSQL%20%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%A5%87%E6%95%B0%E5%81%B6%E6%95%B0%E6%96%B9%E6%B3%95&amp;url=/archives/7b071a3a-f644-4561-b84d-3267d281d2fa" width="1" height="1" alt="" style="opacity:0;">
<h4>1、使用按位与</h4>
<pre><code class="sql"> -- 奇数
select * from table WHERE id &amp; 1; 

-- 偶数 【 id先除以2然后乘2 如果与原来的相等就是偶数】
select * from table WHERE id=(id&gt;&gt;1)&lt;&lt;1; 
</code></pre>
<h4>2、正则匹配</h4>
<p>正则匹配最后一位</p>
<pre><code class="sql">-- 奇数
select * from table WHERE id regexp '[13579]$';

-- 偶数
select * from table WHERE id regexp '[02468]$';
</code></pre>
<h4>3、除2取余</h4>
<pre><code class="sql">-- 奇数
select * from table WHERE id % 2 = 1;

select * from table WHERE mod(id, 2) = 1;

-- 偶数
select * from table WHERE id % 2 = 0;

select * from table WHERE mod(id, 2) = 0;
</code></pre>
<h4>4、-1的奇数次方和偶数次方</h4>
<pre><code class="sql">-- 奇数
select * from table WHERE POWER(-1, id) = -1;

--偶数
select * from table WHERE POWER(-1, id) = 1;
</code></pre>
<p>至于查询效率的问题，还请读者自行验证，根据数据的级别自行选择</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/94554039</p>]]></description><guid isPermaLink="false">/archives/7b071a3a-f644-4561-b84d-3267d281d2fa</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 09:29:33 GMT</pubDate></item><item><title><![CDATA[MYSQL按时间段(按小时分组)分组查询当天小时内数据]]></title><link>https://wiki.dgfygk.com/archives/cc4d8d26-7cfb-4741-92b4-21453ff2c8ef</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MYSQL%E6%8C%89%E6%97%B6%E9%97%B4%E6%AE%B5%28%E6%8C%89%E5%B0%8F%E6%97%B6%E5%88%86%E7%BB%84%29%E5%88%86%E7%BB%84%E6%9F%A5%E8%AF%A2%E5%BD%93%E5%A4%A9%E5%B0%8F%E6%97%B6%E5%86%85%E6%95%B0%E6%8D%AE&amp;url=/archives/cc4d8d26-7cfb-4741-92b4-21453ff2c8ef" width="1" height="1" alt="" style="opacity:0;">
<p>运营一个需求，要查询一些数据，每1小时一次的数据 ，Excel表格如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F5d7b6b57e07d0a545a3d9003467eb820.png&amp;size=m" alt=""></p>
<h4>第一肯定要是用 GROUP BY 然后</h4>
<h4>一、单表</h4>
<pre><code class="sql">SELECT
    DATE_FORMAT( create_time, '%Y-%m-%d %H' ) as time,
    COUNT( DISTINCT member_id) as count 
FROM
    tmp_wp_table 
GROUP BY
    time 
ORDER BY
    time
</code></pre>
<p>结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0f205284b9c94824ef9278df97b2970f.png&amp;size=m" alt=""></p>
<h4>二、多表</h4>
<p>多表查询就应该用到union连接， 这里根据需求选择 union 还是 union all 注意union 和 union all 的区别</p>
<pre><code class="sql">SELECT DATE_FORMAT(x.fenDate, '%Y-%m-%d %H' ) as time, count(DISTINCT x.member_id) as count
    FROM(
    SELECT DISTINCT b.user_id, FROM_UNIXTIME(b.intime) as fenDate
    FROM
        wp_table_pay_1 b 
    WHERE
        b.pay1_status = 1 
        AND b.remarks IS NULL 
        AND FROM_UNIXTIME( b.intime )&gt;'2019-06-27' AND FROM_UNIXTIME( b.intime )&lt;'2019-06-28' UNION ALL

    SELECT DISTINCT b.user_id, b.date as fenDate
    FROM
        wp_table_pay_2 b 
    WHERE
        b.pay2_status = 1 
        AND b.remarks IS NULL  
        AND b.date&gt;'2019-06-27' AND b.date&lt;'2019-06-28' UNION ALL

    SELECT DISTINCT b.user_id , b.create_time as fenDate
    FROM
        wp_table_pay_3 b 
    WHERE
        b.pay3_status = 1 
    AND b.remarks IS NULL 
    AND b.create_time&gt;'2019-06-27' AND b.create_time&lt;'2019-06-28' 

    ) x GROUP BY time  ORDER BY time asc
</code></pre>
<p>查询结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F113cd37be4e3ea0d531cb66dd06ce0df.png&amp;size=m" alt=""></p>
<p>如果有更好的处理方式。欢迎评论留言进行交流。</p>
<p><strong>补充：这样查出来的结果如果是null，就不会显示在结果集列表中</strong></p>
<h4>解决思路</h4>
<blockquote>
 <ol>
  <li>思路一： 可以在自己的程序中做额外的补零处理</li>
  <li>思路二：可以自己新建时间列表，把未来10年的日期放进去，然后再跟统计表作连接查询</li>
  <li>思路三：构建一个最近一天（或几天）的结果集，然后和查询的结果集合做left join(本文采用思路三的方式)</li>
 </ol>
</blockquote>
<pre><code class="sql">select a.time,b.count as count
from (
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 1 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 2 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 3 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 4 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 5 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 6 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 7 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 8 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 9 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 10 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 11 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 12 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 13 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 14 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 15 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 16 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 17 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 18 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 19 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 20 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 21 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 22 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 23 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 24 HOUR), '%Y-%m-%d %H' ) as time
) a left join (
    SELECT
      DATE_FORMAT( create_time, '%Y-%m-%d %H' ) as time,
      COUNT( DISTINCT member_id) as count
  FROM
      tmp_wp_table 
  WHERE create_time&gt;'2019-11-20' and create_time&lt;'2019-11-20 23:59:59'
  GROUP BY time
) b on a.time = b.time ORDER BY a.time;
</code></pre>
<p>这样查询的结果为空， 需要把NULL设置为0，这时我们可以利用ifnull函数</p>
<pre><code class="sql">select a.time,ifnull(b.count,0) as count
from (
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 1 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 2 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 3 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 4 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 5 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 6 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 7 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 8 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 9 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 10 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 11 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 12 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 13 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 14 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 15 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 16 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 17 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 18 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 19 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 20 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 21 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 22 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 23 HOUR), '%Y-%m-%d %H' ) as time union all
    SELECT DATE_FORMAT(date_sub(DATE('2019-11-21'), interval 24 HOUR), '%Y-%m-%d %H' ) as time
) a left join (
    SELECT
      DATE_FORMAT( create_time, '%Y-%m-%d %H' ) as time,
      COUNT( DISTINCT member_id) as count
  FROM
      tmp_wp_table
  WHERE create_time&gt;'2019-11-20' and create_time&lt;'2019-11-20 23:59:59'
  GROUP BY time
) b on a.time = b.time ORDER BY a.time;
</code></pre>
<p>查询结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffe13087d323aed172cc455e31f7b7ae6.png&amp;size=m" alt=""></p>
<p>这时构建7天的结果集</p>
<pre><code class="sql">    SELECT curdate() as click_date
    union all
    SELECT date_sub(curdate(), interval 1 day) as click_date
    union all
    SELECT date_sub(curdate(), interval 2 day) as click_date
    union all
    SELECT date_sub(curdate(), interval 3 day) as click_date
    union all
    SELECT date_sub(curdate(), interval 4 day) as click_date
    union all
    SELECT date_sub(curdate(), interval 5 day) as click_date
    union all
    SELECT date_sub(curdate(), interval 6 day) as click_date
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/93980085</p>]]></description><guid isPermaLink="false">/archives/cc4d8d26-7cfb-4741-92b4-21453ff2c8ef</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 09:25:52 GMT</pubDate></item><item><title><![CDATA[Redis key操作]]></title><link>https://wiki.dgfygk.com/archives/f1e20581-0b64-4c2b-91f6-c3c9d09e9e6e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%20key%E6%93%8D%E4%BD%9C&amp;url=/archives/f1e20581-0b64-4c2b-91f6-c3c9d09e9e6e" width="1" height="1" alt="" style="opacity:0;">
<p>redis 的key操作是涉及范围最广的操作 。</p>
<h4>1、Redis 列出key</h4>
<pre><code class="sql">keys  *user*
keys  *
</code></pre>
<p>有3个通配符 *, ? ,[]</p>
<ul>
 <li>*: 通配任意多个字符</li>
 <li>?: 通配单个字符</li>
 <li>[]: 通配括号内的某1个字符</li>
</ul>
<blockquote>
 <p>注：生产已经禁止。更安全的做法是采用scan，原理和操作如下： 针对Keys的改进，支持分页查询Key。在迭代过程中，Keys有增删时不会要锁定写操作，数据集完整度不做任何保证，同一条key可能会被返回多次.对于其他危险的命令，新版本也进行了替代：</p>
</blockquote>
<p>redis-cli下的扫描:</p>
<pre><code>redis-cli --scan --pattern 'chenqun_*'
</code></pre>
<p>这是用scan命令扫描redis中的key，–pattern选项指定扫描的key的pattern。相比keys pattern模式,不会长时间阻塞redis而导致其他客户端的命令请求一直处于阻塞状态。</p>
<h4>2、Redis 测试指定key是否存在</h4>
<pre><code class="sql">exists key       // 返回1表示存在，0不存在
</code></pre>
<h4>3、Redis 删除给定key</h4>
<pre><code class="sql">del key1 key2 ....keyN      // 返回1表示存在，0不存在
</code></pre>
<h4>4、Redis 返回给定key的value类型</h4>
<pre><code>type key    //返回 none 表示不存在key。string字符类型，list 链表类型 set 无序集合类型...
</code></pre>
<h4>5、Redis 返回从当前数据库中随机选择的一个key</h4>
<pre><code>randomkey    // 如果当前数据库是空的，返回空串
</code></pre>
<h4>6、Redis 原子的重命名一个key</h4>
<pre><code>rename oldkey newkey
如果newkey存在，将会被覆盖，返回1表示成功，0失败。可能是oldkey不存在或者和newkey相同

renamenx oldkey newkey 
同上，但是如果newkey存在返回失败
</code></pre>
<h4>7、Redis Key的超时设置处理</h4>
<pre><code>expire key seconds
单位是秒。返回1成功，0表示key已经设置过过期时间或者不存在。 如果想消除超时则使用persist key。如果希望采用绝对超时，则使用expireat命令。

ttl key 
返回设置过过期时间的key的剩余过期秒数 -1表示没有设置过过期时间，对于不存在的key,返回-2。

pexpire key 毫秒数
设置生命周期。

pttl  key
以毫秒返回生命周期。
</code></pre>
<p>注：当client主动访问key会先对key进行超时判断，过时的key会立刻删除。如果clien永远都不再get那条key呢？ 它会在Master的后台，每秒10次的执行如下操作： 随机选取100个key校验是否过期，如果有25个以上的key过期了，立刻额外随机选取下100个key(不计算在10次之内)。可见，如果过期的key不多，它最多每秒回收200条左右，如果有超过25%的key过期了，它就会做得更多，但只要key不被主动get，它占用的内存什么时候最终被清理掉只有天知道。在主从复制环境中，由于上述原因存在已经过期但是没有删除的key，在主snapshot时并不包含这些key，因此在slave环境中我们往往看到dbsize较master是更小的。</p>
<p>原文链接： <a href="https://www.w3cschool.cn/redis_all_about/redis_all_about-sfc726u6.html">https://www.w3cschool.cn/redis_all_about/redis_all_about-sfc726u6.html</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/94390888</p>]]></description><guid isPermaLink="false">/archives/f1e20581-0b64-4c2b-91f6-c3c9d09e9e6e</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:11:08 GMT</pubDate></item><item><title><![CDATA[SQL UNION 和 UNION ALL 操作符]]></title><link>https://wiki.dgfygk.com/archives/d7c12c98-10f5-4c13-97fc-a3d7b4c74d72</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SQL%20UNION%20%E5%92%8C%20UNION%20ALL%20%E6%93%8D%E4%BD%9C%E7%AC%A6&amp;url=/archives/d7c12c98-10f5-4c13-97fc-a3d7b4c74d72" width="1" height="1" alt="" style="opacity:0;">
<h3>SQL UNION 操作符</h3>
<p>UNION 操作符用于合并两个或多个 SELECT 语句的结果集。</p>
<p>请注意，UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时，每条 SELECT 语句中的列的顺序必须相同。</p>
<h4>SQL UNION 语法</h4>
<p>SELECT column_name(s) FROM table_name1
 <br>
 UNION
 <br>
 SELECT column_name(s) FROM table_name2</p>
<h4>注释：默认地，UNION 操作符选取不同的值。如果允许重复的值，请使用 UNION ALL。</h4>
<h4>SQL UNION ALL 语法</h4>
<p>SELECT column_name(s) FROM table_name1
 <br>
 UNION ALL
 <br>
 SELECT column_name(s) FROM table_name2</p>
<p>另外，UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。</p>
<h3>下面的例子中使用的原始表：</h3>
<h4>Employees_China:</h4>
<p>| E_ID | E_Name |
 <br>
 |——|—————-|
 <br>
 | 01 | Zhang, Hua |
 <br>
 | 02 | Wang, Wei |
 <br>
 | 03 | Carter, Thomas |
 <br>
 | 04 | Yang, Ming |</p>
<h4>Employees_USA:</h4>
<p>| E_ID | E_Name |
 <br>
 |——|—————-|
 <br>
 | 01 | Adams, John |
 <br>
 | 02 | Bush, George |
 <br>
 | 03 | Carter, Thomas |
 <br>
 | 04 | Gates, Bill |</p>
<h3>使用 UNION 命令</h3>
<h4>实例</h4>
<p>列出所有在中国和美国的不同的雇员名：</p>
<p>SELECT E_Name FROM Employees_China</p>
<p>UNION</p>
<p>SELECT E_Name FROM Employees_USA</p>
<pre><code>&nbsp;
</code></pre>
<h4>结果</h4>
<p>| E_Name |
 <br>
 |—————-|
 <br>
 | Zhang, Hua |
 <br>
 | Wang, Wei |
 <br>
 | Carter, Thomas |
 <br>
 | Yang, Ming |
 <br>
 | Adams, John |
 <br>
 | Bush, George |
 <br>
 | Gates, Bill |</p>
<p>注释：这个命令无法列出在中国和美国的所有雇员。在上面的例子中，我们有两个名字相同的雇员，他们当中只有一个人被列出来了。UNION 命令只会选取不同的值。</p>
<h3>UNION ALL</h3>
<p>UNION ALL 命令和 UNION 命令几乎是等效的，不过 UNION ALL 命令会列出所有的值。</p>
<p>SQL Statement 1
 <br>
 UNION ALL
 <br>
 SQL Statement 2</p>
<h3>使用 UNION ALL 命令</h3>
<h4>实例：</h4>
<p>列出在中国和美国的所有的雇员：</p>
<p>SELECT E_Name FROM Employees_China</p>
<p>UNION ALL</p>
<p>SELECT E_Name FROM Employees_USA</p>
<pre><code>&nbsp;
</code></pre>
<h4>结果</h4>
<p>| E_Name |
 <br>
 |—————-|
 <br>
 | Zhang, Hua |
 <br>
 | Wang, Wei |
 <br>
 | Carter, Thomas |
 <br>
 | Yang, Ming |
 <br>
 | Adams, John |
 <br>
 | Bush, George |
 <br>
 | Carter, Thomas |
 <br>
 | Gates, Bill |</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/93506613</p>]]></description><guid isPermaLink="false">/archives/d7c12c98-10f5-4c13-97fc-a3d7b4c74d72</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 09:10:51 GMT</pubDate></item><item><title><![CDATA[将Excel文件导入数据库（POI+Excel+MySQL+jsp页面导入）]]></title><link>https://wiki.dgfygk.com/archives/955ae716-75c6-4d07-80a4-5ca441a110b4</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%86Excel%E6%96%87%E4%BB%B6%E5%AF%BC%E5%85%A5%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%88POI%2BExcel%2BMySQL%2Bjsp%E9%A1%B5%E9%9D%A2%E5%AF%BC%E5%85%A5%EF%BC%89&amp;url=/archives/955ae716-75c6-4d07-80a4-5ca441a110b4" width="1" height="1" alt="" style="opacity:0;">
<p>原文章地址：<a href="https://blog.csdn.net/meng564764406/article/details/52426634">https://blog.csdn.net/meng564764406/article/details/52426634</a></p>
<p>现在正在做一个将Excel导入进数据库的一个程序，现在已经实现了一种方法，借鉴了很多。下面详细说一下，功能如何实现的。</p>
<p>优化版本：第一次优化</p>
<p>目前这个文章的实现方法：</p>
<p>浏览器端选择Excel文件，点击上传，服务器目录会多出一个Excel的文件，然后再在服务器端读取这个文件，保存到数据库。</p>
<p>由于篇幅有限，csdn的上传在60M内，jar包加起来就超过了、所以在这里留一个百度云的链接，可以直接下载，导入即可运行。</p>
<p>源码最新地址</p>
<p>由于篇幅问题，这里只保存了关键的源代码，详细的源代码可以去百度云下载，若链接过时，可以给我评论，或者给我发邮箱，qq均可（点击我的头像获取）。</p>
<p>一、介绍一下我们的这个程序的流程</p>
<p>软件环境，win7 + IDEA +MySQL+tomcat</p>
<p>测试浏览器：火狐</p>
<p>其他：jQuery+bootstrap+poi</p>
<p>二、首先看一下包的导入，poi3.17jar包-所有jar包 , 引入时如果只引：</p>
<p><!--&ndash; https://mvnrepository.com/artifact/org.apache.poi/poi &ndash;-->
 <br>
 <dependency>
  <br>
  <groupid>org.apache.poi</groupid>
  <br>
  <artifactid>poi</artifactid>
  <br>
  <version>3.17</version>
  <br>
 </dependency></p>
<p>则无XSSFWorkbook和SXSSHWorkbook,还需引入：
 <br>
 <!--&ndash; https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml &ndash;-->
 <br>
 <dependency>
  <br>
  <groupid>org.apache.poi</groupid>
  <br>
  <artifactid>poi-ooxml</artifactid>
  <br>
  <version>3.17</version>
  <br>
 </dependency></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F98ef1478f039d998a440fa6bfe84a66e.png&amp;size=m" alt=""></p>
<p>三、我们对上面的五个文件开始写吧。</p>
<p>DbUtil.java</p>
<pre><code class="java">package com.app.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.app.common.Common;
import com.app.po.Student_1;


public class DbUtil {

    /**
     * @param sql
     */
    public static void insert(String sql, Student_1 student) throws SQLException {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            Class.forName(Common.DRIVER);
            conn = DriverManager.getConnection(Common.URL, Common.USERNAME, Common.PASSWORD);
            ps = conn.prepareStatement(sql);
            ps.setString(1, student.getNo());
            ps.setString(2, student.getName());
            ps.setString(3, student.getAge());
            ps.setString(4, String.valueOf(student.getScore()));
            boolean flag = ps.execute();
            if(!flag){
                System.out.println("Save data : No. = " + student.getNo() + " , Name = " + student.getName() + ", Age = " + student.getAge() + " succeed!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
            if (conn != null) {
                conn.close();
            }
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static List selectOne(String sql, Student_1 student) throws SQLException {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List list = new ArrayList();
        try {
            Class.forName(Common.DRIVER);
            conn = DriverManager.getConnection(Common.URL, Common.USERNAME, Common.PASSWORD);
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while(rs.next()){
                if(rs.getString("no").equals(student.getNo()) || rs.getString("name").equals(student.getName())|| rs.getString("age").equals(student.getAge())){
                    list.add(1);
                }else{
                    list.add(0);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                rs.close();
            }
            if (ps != null) {
                ps.close();
            }
            if (conn != null) {
                conn.close();
            }
        }
        return list;
    }


    public static ResultSet selectAll(String sql) throws SQLException {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Class.forName(Common.DRIVER);
            conn = DriverManager.getConnection(Common.URL, Common.USERNAME, Common.PASSWORD);
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
             if (rs != null) {
                 rs.close();
             }
             if (ps != null) {
                 ps.close();
             }
             if (conn != null) {
                 conn.close();
             }
         }
         return rs;
     }

 }
</code></pre>
<p>student.java</p>
<pre><code class="java">package com.app.po;


public class Student_1 {
     /**
      * id   
      */
     private Integer id;
     /**
      * 学号
      */
     private String no;
     /**
      * 姓名
      */
     private String name;
     /**
      * 学院
      */
     private String age;
     /**
      * 成绩
      */
     private String score;

     public Integer getId() {
         return id;
     }

     public void setId(Integer id) {
         this.id = id;
     }

     public String getNo() {
         return no;
     }

     public void setNo(String no) {
         this.no = no;
     }

     public String getName() {
         return name;
     }

     public void setName(String name) {
         this.name = name;
     }

     public String getAge() {
         return age;
     }

     public void setAge(String age) {
         this.age = age;
     }

     public String getScore() {
         return score;
     }

     public void setScore(String score) {
         this.score = score;
     }

 }
</code></pre>
<p>common.java</p>
<pre><code class="java">package com.app.common;

public class Common {
    // connect the database
        public static final String DRIVER = "com.mysql.jdbc.Driver";
        public static final String DB_NAME = "test";
        public static final String USERNAME = "root";
        public static final String PASSWORD = "123456";
        public static final String IP = "localhost";
        public static final String PORT = "3306";
        public static final String URL = "jdbc:mysql://" + IP + ":" + PORT + "/" + DB_NAME;

        // common
        public static final String EXCEL_PATH = "lib/student_info.xls";

        // sql
        public static final String INSERT_STUDENT_SQL = "insert into t_student(no, name, age, score) values(?, ?, ?, ?)";
        public static final String UPDATE_STUDENT_SQL = "update t_student set no = ?, name = ?, age= ?, score = ? where id = ? ";
        public static final String SELECT_STUDENT_ALL_SQL = "select id,no,name,age,score from t_student";
        public static final String SELECT_STUDENT_SQL = "select * from t_student where name like ";
}
</code></pre>
<p>readExcel.java</p>
<pre><code class="java">package com.app.excel;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
 import java.util.List;

 import org.apache.poi.hssf.usermodel.HSSFCell;
 import org.apache.poi.hssf.usermodel.HSSFRow;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;

 import com.app.common.Common;
 import com.app.po.Student_1;

 public class ReadExcel {

     public List&lt;Student_1&gt; readXls(String path) throws IOException {
         InputStream is = new FileInputStream(path);
         HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is);
         Student_1 student = null;
         List&lt;Student_1&gt; list = new ArrayList&lt;Student_1&gt;();
         // 循环工作表Sheet
         for (int numSheet = 0; numSheet &lt; hssfWorkbook.getNumberOfSheets(); numSheet++) {
             HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(numSheet);
             if (hssfSheet == null) {
                 continue;
             }
             // 循环行Row
             for (int rowNum = 1; rowNum &lt;= hssfSheet.getLastRowNum(); rowNum++) {
                 HSSFRow hssfRow = hssfSheet.getRow(rowNum);
                 if (hssfRow != null) {
                     student = new Student_1();
                     HSSFCell no = hssfRow.getCell(0);
                     HSSFCell name = hssfRow.getCell(1);
                     HSSFCell age = hssfRow.getCell(2);
                     HSSFCell score = hssfRow.getCell(3);
                     student.setNo(getValue(no));
                     student.setName(getValue(name));
                     student.setAge(getValue(age));
                     student.setScore(getValue(score));
                     list.add(student);
                 }
             }
         }
         return list;
     }

      @SuppressWarnings("static-access")
     private String getValue(HSSFCell hssfCell) {
             if (hssfCell.getCellType() == hssfCell.CELL_TYPE_BOOLEAN) {
                 // 返回布尔类型的值
                 return String.valueOf(hssfCell.getBooleanCellValue());
             } else if (hssfCell.getCellType() == hssfCell.CELL_TYPE_NUMERIC) {
                 // 返回数值类型的值
                 return String.valueOf(hssfCell.getNumericCellValue());
             } else {
                 // 返回字符串类型的值
                 return String.valueOf(hssfCell.getStringCellValue());
             }
         }
 }
</code></pre>
<p>saveDB.java</p>
<pre><code class="java">package com.app.excel;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

 import com.app.common.Common;
 import com.app.util.DbUtil;
 import com.app.po.Student_1;


 public class SaveData2DB {

     @SuppressWarnings({ "rawtypes" })
     public void save(String path) throws IOException, SQLException {
         ReadExcel xlsMain = new ReadExcel();
         Student_1 student = null;
         List&lt;Student_1&gt; list = xlsMain.readXls(path);

         for (int i = 0; i &lt; list.size(); i++) {
             student = list.get(i);
             List l = DbUtil.selectOne(Common.SELECT_STUDENT_SQL + "'%" + student.getName() + "%'", student);
             if (!l.contains(1)) {
                 DbUtil.insert(Common.INSERT_STUDENT_SQL, student);
             } else {
                 System.out.println("The Record was Exist : No. = " + student.getNo() + " , Name = " + student.getName() + ", Age = " + student.getAge() + ", and has been throw away!");
             }
         }
     }
 }
</code></pre>
<p>Upload.java</p>
<pre><code class="java">package com.app.action;



import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adtec.framework.common.util.JsonUtil;
import com.app.excel.SaveData2DB;

import net.sf.json.JSONObject;


public class UpLoad extends HttpServlet {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
            System.out.println("coming.......");

                //得到上传文件的保存目录，将上传的文件存放于WEB-INF目录下，不允许外界直接访问，保证上传文件的安全
                String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
                //上传时生成的临时文件保存目录
                String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
                File tmpFile = new File(tempPath);
                if (!tmpFile.exists()) {
                    //创建临时目录
                    tmpFile.mkdir();
                }

                //消息提示
                String message = "";
                try{
                    //使用Apache文件上传组件处理文件上传步骤：
                    //1、创建一个DiskFileItemFactory工厂
                    DiskFileItemFactory factory = new DiskFileItemFactory();
                    //设置工厂的缓冲区的大小，当上传的文件大小超过缓冲区的大小时，就会生成一个临时文件存放到指定的临时目录当中。
                    factory.setSizeThreshold(1024*100);//设置缓冲区的大小为100KB，如果不指定，那么缓冲区的大小默认是10KB
                    //设置上传时生成的临时文件的保存目录
                    factory.setRepository(tmpFile);
                    //2、创建一个文件上传解析器
                    ServletFileUpload upload = new ServletFileUpload(factory);
                    //监听文件上传进度
                    /*upload.setProgressListener(new ProgressListener(){
                        public void update(long pBytesRead, long pContentLength, int arg2) {
                            System.out.println("文件大小为：" + pContentLength + ",当前已处理：" + pBytesRead);

                        }
                    });*/
                     //解决上传文件名的中文乱码
                    upload.setHeaderEncoding("UTF-8"); 
                    //3、判断提交上来的数据是否是上传表单的数据
                    if(!ServletFileUpload.isMultipartContent(request)){
                        //按照传统方式获取数据
                        return;
                    }

                    //设置上传单个文件的大小的最大值，目前是设置为1024*1024字节，也就是1MB
                    upload.setFileSizeMax(1024*1024);
                    //设置上传文件总量的最大值，最大值=同时上传的多个文件的大小的最大值的和，目前设置为10MB
                    upload.setSizeMax(1024*1024*10);

                    //


                    //4、使用ServletFileUpload解析器解析上传数据，解析结果返回的是一个List&lt;FileItem&gt;集合，每一个FileItem对应一个Form表单的输入项
                    List&lt;FileItem&gt; list = upload.parseRequest(request);
                    for(FileItem item : list){
                        //如果fileitem中封装的是普通输入项的数据
                        if(item.isFormField()){
                            String name = item.getFieldName();
                            //解决普通输入项的数据的中文乱码问题
                            String value = item.getString("UTF-8");
//                            String value = item.getString("gbk");
                            //value = new String(value.getBytes("iso8859-1"),"UTF-8");
                            System.out.println(name + "=" + value);
                        }else{//如果fileitem中封装的是上传文件
                            //得到上传的文件名称，
                            String filename = item.getName();
                            System.out.println(filename+"..");
                            if(filename==null || filename.trim().equals("")){
                                continue;
                            }
                            //注意：不同的浏览器提交的文件名是不一样的，有些浏览器提交上来的文件名是带有路径的，如：  c:\a\b\1.txt，而有些只是单纯的文件名，如：1.txt
                            //处理获取到的上传文件的文件名的路径部分，只保留文件名部分
                            filename = filename.substring(filename.lastIndexOf("\\")+1);
                            //得到上传文件的扩展名
                            String fileExtName = filename.substring(filename.lastIndexOf(".")+1);
                            //如果需要限制上传的文件类型，那么可以通过文件的扩展名来判断上传的文件类型是否合法
                            System.out.println("上传的文件的扩展名是："+fileExtName);
                            //获取item中的上传文件的输入流
                            InputStream in = item.getInputStream();
                            //得到文件保存的名称
                            String saveFilename = makeFileName(filename);
                            //得到文件的保存目录
                            String realSavePath = makePath(saveFilename, savePath);
                            //创建一个文件输出流
                            FileOutputStream out = new FileOutputStream(realSavePath + "\\" + saveFilename);
                            //创建一个缓冲区
                            byte buffer[] = new byte[1024];
                            //判断输入流中的数据是否已经读完的标识
                            int len = 0;
                            StringBuffer sb = new StringBuffer();
                            //循环将输入流读入到缓冲区当中，(len=in.read(buffer))&gt;0就表示in里面还有数据
                            while((len=in.read(buffer))&gt;0){
                                //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
//                              System.out.println(realSavePath);
//                              System.out.println();
                                sb.append(new String(buffer,0,len));
//                                logger.info(sb.toString());
//                                System.out.println(sb.toString());
//                                sb.setLength(0);
                                out.write(buffer, 0, len);
                            }
//                            System.out.println(sb.toString()+"-----");
                            String ss = sb.toString();

//                            System.out.println(sb.);
                            //关闭输入流
                            in.close();
                            //关闭输出流
                            out.close();
                            //删除处理文件上传时生成的临时文件
                            //item.delete();
                            SaveData2DB saveData2DB = new SaveData2DB();
                            saveData2DB.save(realSavePath + "\\" + saveFilename);
                            System.out.println("end");

                            message = "success";
                        }
                    }
                }catch (FileUploadBase.FileSizeLimitExceededException e) {
                    e.printStackTrace();
                    message = "单个文件超出最大值！！！";
                    /*request.setAttribute("message", "单个文件超出最大值！！！");*/
                   /* request.getRequestDispatcher("/message.jsp").forward(request, response);*/
                    return;
                }catch (FileUploadBase.SizeLimitExceededException e) {
                    e.printStackTrace();
                    message = "上传文件的总的大小超出限制的最大值！！！";
                    /*request.setAttribute("message", "上传文件的总的大小超出限制的最大值！！！");*/
                    /*request.getRequestDispatcher("/message.jsp").forward(request, response);*/
                    return;
                }catch (Exception e) {
                    message= "文件上传失败！";
                    e.printStackTrace();
                }
                /*request.setAttribute("message",message);*/
                returnResultJson(response,message);
                /*request.getRequestDispatcher("/message.jsp").forward(request, response);*/
    }


    private String makeFileName(String filename){  //2.jpg
        //为防止文件覆盖的现象发生，要为上传文件产生一个唯一的文件名
        return UUID.randomUUID().toString() + "_" + filename;
    }


    private String makePath(String filename,String savePath){
        //得到文件名的hashCode的值，得到的就是filename这个字符串对象在内存中的地址
        int hashcode = filename.hashCode();
        int dir1 = hashcode&amp;0xf;  //0--15
        int dir2 = (hashcode&amp;0xf0)&gt;&gt;4;  //0-15
        //构造新的保存目录
        String dir = savePath + "\\" + dir1 + "\\" + dir2;  //upload\2\3  upload\3\5
        //File既可以代表文件也可以代表目录
        File file = new File(dir);
        //如果目录不存在
        if(!file.exists()){
            //创建目录
            file.mkdirs();
        }
        return dir;
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

    private void returnResultJson(HttpServletResponse response,Object obj) {
        PrintWriter pw = null;
        try {
            pw = response.getWriter();
            JSONObject resultmessage = JsonUtil.generate(obj);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.setHeader("Cache-Control", "no-cache");
            pw.write(resultmessage.toString());
        } catch (Exception e) {
            pw.write("系统异常，请联系管理员");
        } finally {
            pw.flush();
            pw.close();
        }
    }
}
</code></pre>
<p>Web.xml</p>
<pre><code class="html">&lt;servlet&gt;
        &lt;servlet-name&gt;Upload&lt;/servlet-name&gt;
        &lt;servlet-class&gt;com.app.action.UpLoad&lt;/servlet-class&gt;
    &lt;/servlet&gt;

    &lt;servlet-mapping&gt;
        &lt;servlet-name&gt;Upload&lt;/servlet-name&gt;
        &lt;url-pattern&gt;/fileUpload/UploadServlet&lt;/url-pattern&gt;
    &lt;/servlet-mapping&gt;
</code></pre>
<p>以上就是配置后台文件</p>
<p>现在配置jsp页面吧(jquery),这样的页面插件很多，可以网上搜索你喜欢的。</p>
<pre><code class="javascript">    &lt;div class="htmleaf-container"&gt;
        &lt;div class="container kv-main"&gt;
            &lt;!-- &lt;div class="page-header"&gt;
            &lt;h2&gt;单张上传 &lt;small&gt;&lt;/h2&gt;
            &lt;/div&gt; --&gt;
            &lt;!-- &lt;form enctype="multipart/form-data" id="uploadForm"  method = "post"&gt; --&gt;
                &lt;input id="fileUpload" class="file" type="file" name="fileUpload"  multiple  data-show-preview="true"&gt;
            &lt;!-- &lt;/form&gt; --&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
        $("#fileUpload").fileinput({
            language : "zh",//设置语言
            uploadUrl: "${pageContext.request.contextPath}/fileUpload/UploadServlet",//上传地址
            uploadAsync: true,//同步还是异步
            showCaption:false,//是否显示标题
            showUpload: true,//是否显示上传按钮
            browseClass: "btn btn-primary", //按钮样式 
            allowedFileExtensions : ['jpg', 'png','gif','xls'],//接收的文件后缀
            allowedFileTypes: ['image', 'video', 'flash','excel'],//接收的文件类型['image', 'html', 'text', 'video', 'audio', 'flash','object']
            maxFileCount: 6,//最大上传文件数限制
            overwriteInitial: false,
            maxFileSize: 1000, 
            msgFilesTooMany: "选择上传的文件数量({n}) 超过允许的最大数值{m}！",
            previewFileIcon: '&lt;i class="glyphicon glyphicon-file"&gt;&lt;/i&gt;', 
            enctype: 'multipart/form-data',
            /* allowedPreviewTypes: null, */
            previewFileIconSettings: {
                'docx': '&lt;i class="glyphicon glyphicon-file"&gt;&lt;/i&gt;',
                'xlsx': '&lt;i class="glyphicon glyphicon-file"&gt;&lt;/i&gt;',
                'pptx': '&lt;i class="glyphicon glyphicon-file"&gt;&lt;/i&gt;',
                'jpg': '&lt;i class="glyphicon glyphicon-picture"&gt;&lt;/i&gt;',
                'pdf': '&lt;i class="glyphicon glyphicon-file"&gt;&lt;/i&gt;',
                'zip': '&lt;i class="glyphicon glyphicon-file"&gt;&lt;/i&gt;'
            }
});
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fde92e79d9c0234ad413ae22d3a5cbd5d.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/92123241</p>]]></description><guid isPermaLink="false">/archives/955ae716-75c6-4d07-80a4-5ca441a110b4</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 09:10:33 GMT</pubDate></item><item><title><![CDATA[java 调用支付宝支付接口]]></title><link>https://wiki.dgfygk.com/archives/ab226ea3-e2d8-4c0e-b98e-b74ccde4c78c</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%20%E8%B0%83%E7%94%A8%E6%94%AF%E4%BB%98%E5%AE%9D%E6%94%AF%E4%BB%98%E6%8E%A5%E5%8F%A3&amp;url=/archives/ab226ea3-e2d8-4c0e-b98e-b74ccde4c78c" width="1" height="1" alt="" style="opacity:0;">
<p>最近公司在做支付模块，在接入过程中遇到了很多坑，费了不少事，现在分享一下接入方法，也记录一下，以后可能还用的到。用的是支付宝的<a href="https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.LXh9NK&amp;treeId=62&amp;articleId=103566&amp;docType=1">即时到帐</a>支付功能和<a href="https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1">微信的扫码支付</a>功能，相比起来，个人感觉支付宝的文档和接入方式都比<a href="http://lib.csdn.net/base/wechat">微信</a>的容易理解和操作，也不用自己写页面，接入起来比较方便，毕竟是支付起家的，比微信支付少很多坑，下面就分别介绍着两种支付的接入方法。</p>
<h3>支付宝支付</h3>
<h4>1、申请签约</h4>
<p>目的是得到开发使用的合作伙伴身份（PID）和MD5秘钥，申请地址（即时到账收款）：<a href="https://b.alipay.com/order/productDetail.htm?productId=2015110218012942">https://b.alipay.com/order/productDetail.htm?productId=2015110218012942</a></p>
<p>申请方式在开放平台的文档上有详细说明，这里就不再赘述。</p>
<hr>
<p>在得到PID和秘钥后就可以接入接口了，首先在开放平台中下载官方的demo（<a href="http://lib.csdn.net/base/java">Java</a>+MD5版本），DEMO下载地址：<a href="https://docs.open.alipay.com/54/104509">https://docs.open.alipay.com/54/104509</a></p>
<p>支付宝的demo做的非常好，下载下来直接配置下jdk就可以运行了。如果遇到<a href="http://lib.csdn.net/base/java">Java</a>compiler level does not match错误，说明你用的eclipse或myeclipse的jdk编译版本与demo的JDK编译版本不一致，修改下jdk编译版本就可以了。其实就用到了4个类，如下图</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F80c5f383a33631732a69d1ac84736fa7.png&amp;size=m" alt=""></p>
<p>可以选择把支付功能单独做一个项目，在其他项目调用接口就可以支付，也可以整合到自己的项目里，为了好维护我整合到自己的项目里了。把这四个类放到自己的项目中，引入相应的jar包。</p>
<p>2.1、demo中类的说明</p>
<p>AlipayConfig.java类主要是配置参数信息的类</p>
<pre><code class="java">​
package aliPay.config;

/**
 * @author: wp
 * @Description:
 * @version: 1.0
 * @date: 2019/06/13 10:25
 */
public class AliPayConfig {

    //TODO ↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    // 合作身份者ID，签约账号，以2088开头由16位纯数字组成的字符串，查看地址：https://b.alipay.com/order/pidAndKey.htm
    public static String partner = "你自己的PID";
    // 收款支付宝账号，以2088开头由16位纯数字组成的字符串，一般情况下收款账号就是签约账号
    public static String seller_id = partner;
    // MD5密钥，安全检验码，由数字和字母组成的32位字符串，查看地址：https://b.alipay.com/order/pidAndKey.htm
    public static String key = "你自己的MD5秘钥";
    // 服务器异步通知页面路径  需http://格式的完整路径，不能加?id=123这类自定义参数，必须外网可以正常访问
    //异步通知页面，就是接受支付宝支付结果返回信息的Controller，可以处理自己的支付后的逻辑
    // 服务器异步通知页面路径 需http://或者https://格式的完整路径，不能加?id=123这类自定义参数，必须外网可以正常访问
    //测试环境
    // public static String notify_url = ProInfoUtil.getInstance().getProperty("project_url")+"order/pay/aliPayOrder";
    public static String notify_url = "http://www.xxx.com/alipay/notify_url.do";

    //页面跳转同步通知页面路径 需http://格式的完整路径，不能加?id=123这类自定义参数，必须外网可以正常访问
    //同步跳转的页面，就是支付宝支付成功后页面跳转的url
    //页面跳转同步通知页面路径 需http://或者https://格式的完整路径，不能加?id=123这类自定义参数，必须外网可以正常访问 商户可以自定义同步跳转地址
    // public static String return_url = ProInfoUtil.getInstance().getProperty("project_url")+"order/pay/payResponse";
    public static String return_url = "http://www.xxx.com/alipay/return_url.do";

    // 签名方式
    public static String sign_type = "MD5";

    // 调试用，创建TXT日志文件夹路径，见AlipayCore.java类中的logResult(String sWord)打印方法。
    public static String log_path = "C:\\";

    // 字符编码格式 目前支持 gbk 或 utf-8
    public static String input_charset = "utf-8";

    // 支付类型 ，无需修改
    public static String payment_type = "1";

    // 调用的接口名，无需修改
    public static String service = "create_direct_pay_by_user";


    // TODO ↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

    // TODO 请在这里配置防钓鱼信息，如果没开通防钓鱼功能，为空即可 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    // 防钓鱼时间戳  若要使用请调用类文件submit中的query_timestamp函数
    public static String anti_phishing_key = "";

    // 客户端的IP地址 非局域网的外网IP地址，如：221.0.0.1
    public static String exter_invoke_ip = "";
    // TODO 请在这里配置防钓鱼信息，如果没开通防钓鱼功能，为空即可 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}

​
</code></pre>
<p>AlipayCore.java是整理参数的工具类</p>
<pre><code class="java">package aliPay.config.util;

/**
 * @author: wp
 * @Description:
 * @version: 1.0
 * @date: 2019/06/13 10:42
 */
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import aliPay.config.AlipayConfig;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.httpclient.methods.multipart.FilePartSource;
import org.apache.commons.httpclient.methods.multipart.PartSource;


/* *
 *类名：AlipayFunction
 *功能：支付宝接口公用函数类
 *详细：该类是请求、通知返回两个文件所调用的公用函数核心处理文件，不需要修改
 *版本：3.3
 *日期：2012-08-14
 *说明：
 *以下代码只是为了方便商户测试而提供的样例代码，商户可以根据自己网站的需要，按照技术文档编写,并非一定要使用该代码。
 *该代码仅供学习和研究支付宝接口使用，只是提供一个参考。
 *  &lt;!-- https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient --&gt;
 */

public class AlipayCore {

    /**
     * 除去数组中的空值和签名参数
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map&lt;String, String&gt; paraFilter(Map&lt;String, String&gt; sArray) {

        Map&lt;String, String&gt; result = new HashMap&lt;String, String&gt;();

        if (sArray == null || sArray.size() &lt;= 0) {
            return result;
        }

        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("sign_type")) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    /**
     * 把数组所有元素排序，并按照"参数=参数值"的模式用"&amp;"字符拼接成字符串
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map&lt;String, String&gt; params) {

        List&lt;String&gt; keys = new ArrayList&lt;String&gt;(params.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i &lt; keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);

            if (i == keys.size() - 1) {//拼接时，不包括最后一个&amp;字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&amp;";
            }
        }

        return prestr;
    }

    /**
     * 写日志，方便测试（看网站需求，也可以改成把记录存入数据库）
     * @param sWord 要写入日志里的文本内容
     */
    public static void logResult(String sWord) {
        FileWriter writer = null;
        try {
            writer = new FileWriter(AlipayConfig.log_path + "alipay_log_" + System.currentTimeMillis()+".txt");
            writer.write(sWord);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 生成文件摘要
     * @param strFilePath 文件路径
     * @param file_digest_type 摘要算法
     * @return 文件摘要结果
     */
    public static String getAbstract(String strFilePath, String file_digest_type) throws IOException {
        PartSource file = new FilePartSource(new File(strFilePath));
        if(file_digest_type.equals("MD5")){
            return DigestUtils.md5Hex(file.createInputStream());
        }
        else if(file_digest_type.equals("SHA")) {
            return DigestUtils.sha256Hex(file.createInputStream());
        }
        else {
            return "";
        }
    }
}
</code></pre>
<p>AlipayNotify.java是验证签名的类</p>
<pre><code class="java">package aliPay.util;

/**
 * @author: wp
 * @Description:
 * @version: 1.0
 * @date: 2019/06/13 10:52
 */
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

import aliPay.config.AlipayConfig;
import aliPay.sign.MD5;

/* *
 *类名：AlipayNotify
 *功能：支付宝通知处理类
 *详细：处理支付宝各接口通知返回
 *版本：3.3
 *日期：2012-08-17
 *说明：
 *以下代码只是为了方便商户测试而提供的样例代码，商户可以根据自己网站的需要，按照技术文档编写,并非一定要使用该代码。
 *该代码仅供学习和研究支付宝接口使用，只是提供一个参考
 *************************注意*************************
 *调试通知返回时，可查看或改写log日志的写入TXT里的数据，来检查通知返回是否正常
 */
public class AlipayNotify {

    /**
     * 支付宝消息验证地址
     */
    private static final String HTTPS_VERIFY_URL = "https://mapi.alipay.com/gateway.do?service=notify_verify&amp;";

    /**
     * 验证消息是否是支付宝发出的合法消息
     * @param params 通知返回来的参数数组
     * @return 验证结果
     */
    public static boolean verify(Map&lt;String, String&gt; params) {

        //判断responsetTxt是否为true，isSign是否为true
        //responsetTxt的结果不是true，与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
        //isSign不是true，与安全校验码、请求时的参数格式（如：带自定义参数等）、编码格式有关
        String responseTxt = "false";
        if(params.get("notify_id") != null) {
            String notify_id = params.get("notify_id");
            responseTxt = verifyResponse(notify_id);
        }
        String sign = "";
        if(params.get("sign") != null) {sign = params.get("sign");}
        boolean isSign = getSignVeryfy(params, sign);

        //写日志记录（若要调试，请取消下面两行注释）
        //String sWord = "responseTxt=" + responseTxt + "\n isSign=" + isSign + "\n 返回回来的参数：" + AlipayCore.createLinkString(params);
        //AlipayCore.logResult(sWord);

        if (isSign &amp;&amp; responseTxt.equals("true")) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 根据反馈回来的信息，生成签名结果
     * @param Params 通知返回来的参数数组
     * @param sign 比对的签名结果
     * @return 生成的签名结果
     */
    private static boolean getSignVeryfy(Map&lt;String, String&gt; Params, String sign) {
        //过滤空值、sign与sign_type参数
        Map&lt;String, String&gt; sParaNew = AlipayCore.paraFilter(Params);
        //获取待签名字符串
        String preSignStr = AlipayCore.createLinkString(sParaNew);
        //获得签名验证结果
        boolean isSign = false;
        if(AlipayConfig.sign_type.equals("MD5") ) {
            isSign = MD5.verify(preSignStr, sign, AlipayConfig.key, AlipayConfig.input_charset);
        }
        return isSign;
    }

    /**
     * 获取远程服务器ATN结果,验证返回URL
     * @param notify_id 通知校验ID
     * @return 服务器ATN结果
     * 验证结果集：
     * invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空
     * true 返回正确信息
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
    private static String verifyResponse(String notify_id) {
        //获取远程服务器ATN结果，验证是否是支付宝服务器发来的请求

        String partner = AlipayConfig.partner;
        String veryfy_url = HTTPS_VERIFY_URL + "partner=" + partner + "&amp;notify_id=" + notify_id;

        return checkUrl(veryfy_url);
    }

    /**
     * 获取远程服务器ATN结果
     * @param urlvalue 指定URL路径地址
     * @return 服务器ATN结果
     * 验证结果集：
     * invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空
     * true 返回正确信息
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
    private static String checkUrl(String urlvalue) {
        String inputLine = "";

        try {
            URL url = new URL(urlvalue);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection
                    .getInputStream()));
            inputLine = in.readLine().toString();
        } catch (Exception e) {
            e.printStackTrace();
            inputLine = "";
        }

        return inputLine;
    }
</code></pre>
<p>AlipaySubmit.java模拟form表单请求支付宝支付接口的类</p>
<pre><code class="java">import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import aliPay.config.AlipayConfig;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import aliPay.sign.MD5;

/**
 *类名：AlipaySubmit
 *功能：支付宝各接口请求提交类
 *详细：构造支付宝各接口表单HTML文本，获取远程HTTP数据
 *@author: wp
 *@date: 2019/06/13 11:27
 *说明：
 *以下代码只是为了方便商户测试而提供的样例代码，商户可以根据自己网站的需要，按照技术文档编写,并非一定要使用该代码。
 *该代码仅供学习和研究支付宝接口使用，只是提供一个参考。
 */
public class AlipaySubmit {

    /**
     * 支付宝提供给商户的服务接入网关URL(新)
     */
    private static final String ALIPAY_GATEWAY_NEW = "https://mapi.alipay.com/gateway.do?";

    /**
     * 生成签名结果
     * @param sPara 要签名的数组
     * @return 签名结果字符串
     */
    public static String buildRequestMysign(Map&lt;String, String&gt; sPara) {
        String prestr = AlipayCore.createLinkString(sPara); //把数组所有元素，按照"参数=参数值"的模式用"&amp;"字符拼接成字符串
        String mysign = "";
        if(AlipayConfig.sign_type.equals("MD5") ) {
            mysign = MD5.sign(prestr, AlipayConfig.key, AlipayConfig.input_charset);
        }
        return mysign;
    }

    /**
     * 生成要请求给支付宝的参数数组
     * @param sParaTemp 请求前的参数数组
     * @return 要请求的参数数组
     */
    private static Map&lt;String, String&gt; buildRequestPara(Map&lt;String, String&gt; sParaTemp) {
        //除去数组中的空值和签名参数
        Map&lt;String, String&gt; sPara = AlipayCore.paraFilter(sParaTemp);
        //生成签名结果
        String mysign = buildRequestMysign(sPara);

        //签名结果与签名方式加入请求提交参数组中
        sPara.put("sign", mysign);
        sPara.put("sign_type", AlipayConfig.sign_type);

        return sPara;
    }

    /**
     * 建立请求，以表单HTML形式构造（默认）
     * @param sParaTemp 请求参数数组
     * @param strMethod 提交方式。两个值可选：post、get
     * @param strButtonName 确认按钮显示文字
     * @return 提交表单HTML文本
     */
    public static String buildRequest(Map&lt;String, String&gt; sParaTemp, String strMethod, String strButtonName) {
        //待请求参数数组
        Map&lt;String, String&gt; sPara = buildRequestPara(sParaTemp);
        List&lt;String&gt; keys = new ArrayList&lt;String&gt;(sPara.keySet());

        StringBuffer sbHtml = new StringBuffer();

        sbHtml.append("&lt;form id=\"alipaysubmit\" name=\"alipaysubmit\" action=\"" + ALIPAY_GATEWAY_NEW
                + "_input_charset=" + AlipayConfig.input_charset + "\" method=\"" + strMethod
                + "\"&gt;");

        for (int i = 0; i &lt; keys.size(); i++) {
            String name = (String) keys.get(i);
            String value = (String) sPara.get(name);

            sbHtml.append("&lt;input type=\"hidden\" name=\"" + name + "\" value=\"" + value + "\"/&gt;");
        }

        //submit按钮控件请不要含有name属性
        sbHtml.append("&lt;input type=\"submit\" value=\"" + strButtonName + "\" style=\"display:none;\"&gt;&lt;/form&gt;");
        sbHtml.append("&lt;script&gt;document.forms['alipaysubmit'].submit();&lt;/script&gt;");

        return sbHtml.toString();
    }



    /**
     * 用于防钓鱼，调用接口query_timestamp来获取时间戳的处理函数
     * 注意：远程解析XML出错，与服务器是否支持SSL等配置有关
     * @return 时间戳字符串
     * @throws IOException
     * @throws DocumentException
     * @throws MalformedURLException
     */
    public static String query_timestamp() throws MalformedURLException, DocumentException, IOException {

        //构造访问query_timestamp接口的URL串
        String strUrl = ALIPAY_GATEWAY_NEW + "service=query_timestamp&amp;partner=" + AlipayConfig.partner + "&amp;_input_charset" +AlipayConfig.input_charset;
        StringBuffer result = new StringBuffer();

        SAXReader reader = new SAXReader();
        Document doc = reader.read(new URL(strUrl).openStream());

        List&lt;Node&gt; nodeList = doc.selectNodes("//alipay/*");
        for (Node node : nodeList) {
            // 截取部分不需要解析的信息
            if (node.getName().equals("is_success") &amp;&amp; node.getText().equals("T")) {
                // 判断是否有成功标示
                List&lt;Node&gt; nodeList1 = doc.selectNodes("//response/timestamp/*");
                for (Node node1 : nodeList1) {
                    result.append(node1.getText());
                }
            }
        }

        return result.toString();
    }
}
</code></pre>
<p>这几个类调用支付宝接口的是AlipaySubmit，在网页选好购买的商品时，在系统中生成订单，然后进行支付，浏览器跳转到支付宝支付网站，Controller中代码为：</p>
<pre><code class="java">/**
     * @return void
     * @author wp
     * @description
     * @version 1.0
     * @date 2019/6/13 0013 11:39
     */
    @RequestMapping("pay")
    public void aliPay(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("支付宝支付页面");
        //商户订单号，商户网站订单系统中唯一订单号，必填
        String orderNo = request.getParameter("orderNo");
        //订单名称，必填
        String subjectName = request.getParameter("subjectName");
        //付款金额，必填
        String total_fee = request.getParameter("fee");
        //商品描述，可空
        String body = "法海风控 " + subjectName;
        if ("money".equals(body)) {
            body = "法海风控  余额充值";
        }
        //把请求参数打包成map
        Map&lt;String, String&gt; sParaTemp = new HashMap&lt;&gt;();
        sParaTemp.put("service", AlipayConfig.service);
        sParaTemp.put("partner", AlipayConfig.partner);
        sParaTemp.put("seller_id", AlipayConfig.seller_id);
        sParaTemp.put("_input_charset", AlipayConfig.input_charset);
        sParaTemp.put("payment_type", AlipayConfig.payment_type);
        sParaTemp.put("notify_url", AlipayConfig.notify_url);
        sParaTemp.put("return_url", AlipayConfig.return_url);
        sParaTemp.put("anti_phishing_key", AlipayConfig.anti_phishing_key);
        sParaTemp.put("exter_invoke_ip", AlipayConfig.exter_invoke_ip);
        sParaTemp.put("out_trade_no", orderNo);
        sParaTemp.put("subject", subjectName);
        sParaTemp.put("total_fee", total_fee);
        sParaTemp.put("body", body);
        //其他业务参数根据在线开发文档，添加参数.文档地址:https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.O9yorI&amp;treeId=62&amp;articleId=103740&amp;docType=1
        //如sParaTemp.put("参数名","参数值");
        //建立请求
        String sHtmlText = AlipaySubmit.buildRequest(sParaTemp, "get", "确认");
        response.setHeader("Content-Type", "text/html; charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = response.getWriter();
        System.out.println(sHtmlText);
        out.println(sHtmlText);
    }
</code></pre>
<p>请求aliPay会跳转到支付宝支付页面：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F51d94fb25d2cb282bb28424e512c3ac1.png&amp;size=m" alt=""></p>
<h4>3、接收支付结果通知</h4>
<p>在配置好notify_url之后，支付结果会请求相应的接口，我的是order/pay/aliPayOrder，代码如下</p>
<pre><code class="java">/**
     * 支付宝支付订单
     * @return
     * @throws IOException 
     */
    @ResponseBody
    @RequestMapping(value = "pay/aliPayOrder", method = RequestMethod.POST)
    public void aliPayOrder(HttpServletRequest request,HttpServletResponse response) throws IOException {
        LOGGER.info("支付订单");

        //从request中获得参数Map，并返回可读的Map 
        Map&lt;String, String&gt; params = RequestUtil.getParameterMap(request);
        LOGGER.info(params.toString());
        //验证支付宝签名
        boolean aliSign = AlipayNotify.verify(params);
        if (aliSign) {//验证成功
            //交易状态
            String tradeStatus = params.get("trade_status");
            //订单编号          
            String orderNo = params.get("out_trade_no");
            //支付单号
            String payNo = params.get("trade_no");
            //支付账号
            String payAccount = params.get("buyer_email");
            //支付金额
            String totalFee = params.get("total_fee");
            //收款支付宝账号
            String sellerId = params.get("seller_id");
            if (Constant.ALIPAY_TRADE_SUCCESS.equals(tradeStatus)) {//支付宝支付状态为成功
                //验证支付宝返回信息与请求信息一致
                if (ProInfoUtil.getInstance().getProperty("alipay_partner").equals(sellerId)) {
                    //订单处理状态
                    String orderHandleStatus = "error";
                    //验证订单未做支付处理
                    Order order = orderService.queryOrderByOrderNo(orderNo);
                    //订单已支付
                    if (Constant.DEALSTATUS_PAY.equals(order.getDealStatus())) {
                        response.getWriter().print("success");
                        return;
                    }
                    if (null != order &amp;&amp; Double.parseDouble(totalFee) == order.getDealPrice() &amp;&amp; 
                            Constant.DEALSTATUS_NOT_PAY.equals(order.getDealStatus())) {//验证金额是否和订单一致
                        //更新订单为已支付、更新用户套餐余额、添加用户充值记录、添加用户余额支出记录
                        order.setDealStatus(Constant.DEALSTATUS_PAY);
                        order.setPayNo(payNo);
                        order.setPayType(Constant.ALIPAY);
                        order.setPayAccount(payAccount);
                        try {
                            //支付成功处理支付业务
                            boolean result = orderService.payOrder(order);
                            if (result) {
                                //成功后向支付宝返回成功标志
                                LOGGER.info("支付宝支付成功");
                                orderHandleStatus = "success";
                                response.getWriter().print("success");
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            LOGGER.info("支付宝支付失败");
                            response.getWriter().print("fail");
                        }

                    }
                    //添加支付信息
                    Map&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();
                    map.put("params", params.toString());
                    map.put("payType", Constant.ALIPAY);
                    map.put("orderNo", orderNo);
                    map.put("handleStatus", orderHandleStatus);
                    orderService.addPayInfo(map);
                }
            }

        } else {//验证失败
            LOGGER.info("支付宝返回验证失败");
            response.getWriter().print("fail");
        }
    }


 /** 
  * 从request中获得参数Map，并返回可读的Map 
  *  
  * @param request 
  * @return 
  */  
 @SuppressWarnings("unchecked")  
 public static Map getParameterMap(HttpServletRequest request) {  
     // 参数Map  
     Map properties = request.getParameterMap();  
     // 返回值Map  
     Map&lt;String, String&gt; returnMap = new HashMap&lt;String, String&gt;();  
     Iterator entries = properties.entrySet().iterator();  
     Map.Entry entry;  
     String name = "";  
     String value = "";  
     while (entries.hasNext()) {  
         entry = (Map.Entry) entries.next();  
         name = (String) entry.getKey();  
         Object valueObj = entry.getValue();  
         if(null == valueObj){  
             value = "";  
         }else if(valueObj instanceof String[]){  
             String[] values = (String[])valueObj;  
             for(int i=0;i&lt;values.length;i++){  
                 value = values[i] + ",";  
             }  
             value = value.substring(0, value.length()-1);  
         }else{  
             value = valueObj.toString();  
         }  
         returnMap.put(name, value);  
     }  
     return returnMap;  
 }
</code></pre>
<p>至此，支付宝支付功能已经做完了，其中有几个细节需要添加，比如支付时查询订单状态是否已经支付，是否过期等等，可以根据自己的需求去完善。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/91802048</p>]]></description><guid isPermaLink="false">/archives/ab226ea3-e2d8-4c0e-b98e-b74ccde4c78c</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:10:09 GMT</pubDate></item><item><title><![CDATA[java 通过多线程异步方式来批量插入数据]]></title><link>https://wiki.dgfygk.com/archives/071ce766-e6f5-4fa3-82c2-b86251289590</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%20%E9%80%9A%E8%BF%87%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%BC%82%E6%AD%A5%E6%96%B9%E5%BC%8F%E6%9D%A5%E6%89%B9%E9%87%8F%E6%8F%92%E5%85%A5%E6%95%B0%E6%8D%AE&amp;url=/archives/071ce766-e6f5-4fa3-82c2-b86251289590" width="1" height="1" alt="" style="opacity:0;">
<p>在处理大量数据时，想要快速的处理完毕，就需要使用多线程 + 批量（插入等）处理！！！</p>
<p>采用简单粗暴的形式来告诉你们如何使用多线程+批量插入 来处理千万级的数据》》》</p>
<p>批量插入的方法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8c69b4a3050d43c03e7a20cbed761c25.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdf74e3b8d77ad875d9640a66ecc29084.png&amp;size=m" alt=""></p>
<p>接下来就是获取要处理的数据的方法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F75e0c8cd0e6c758ad36d523cac57189c.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F46c9d52c93148640572805431c885158.png&amp;size=m" alt=""></p>
<p>此处生略了获取最大id getMaxId() 和 关闭 closeQuietly（）方法！！</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/88136542</p>]]></description><guid isPermaLink="false">/archives/071ce766-e6f5-4fa3-82c2-b86251289590</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:09:04 GMT</pubDate></item><item><title><![CDATA[Java反转一个List或ArrayList]]></title><link>https://wiki.dgfygk.com/archives/145b42a0-34a5-4ab3-a751-0d5b5a4126ed</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%8F%8D%E8%BD%AC%E4%B8%80%E4%B8%AAList%E6%88%96ArrayList&amp;url=/archives/145b42a0-34a5-4ab3-a751-0d5b5a4126ed" width="1" height="1" alt="" style="opacity:0;">
<p><strong>（1）使用Collections工具类</strong></p>
<p><strong>// 反转lists</strong></p>
<p><strong>Collections.reverse(lists);</strong></p>
<p><strong>（2）使用栈反转</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb438b3ed90d270798809bd96f708e7fd.png&amp;size=m" alt=""></p>
<p><strong>（3）使用Deque反转</strong></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F433a0ed17b7f8b6fff35db1634f28e2b.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd9fcabc43cb91b0e421de4f91df6f2a5.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/89367173</p>]]></description><guid isPermaLink="false">/archives/145b42a0-34a5-4ab3-a751-0d5b5a4126ed</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:08:36 GMT</pubDate></item><item><title><![CDATA[MobaXterm 如何更好的复制粘贴]]></title><link>https://wiki.dgfygk.com/archives/c8f06174-6631-4664-b6a8-0737eccb5020</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MobaXterm%20%E5%A6%82%E4%BD%95%E6%9B%B4%E5%A5%BD%E7%9A%84%E5%A4%8D%E5%88%B6%E7%B2%98%E8%B4%B4&amp;url=/archives/c8f06174-6631-4664-b6a8-0737eccb5020" width="1" height="1" alt="" style="opacity:0;">
<h2>前言</h2>
<p>MobaXterm是管理shell的神器，但是我一开始用的时候，对它的复制和粘贴功能实在是不熟悉。比如，你在别的地方复制一段命令，想粘贴到MobaXterm中，按Ctrl + V是不行的。可以对它进行设置甚至定制，实现更好的体验。</p>
<h2>复制</h2>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6116dcd245639f536e4f09894980983c.png&amp;size=m" alt=""></p>
<p><strong>方式一</strong>
 <br>
 在MobaXterm里选择文本，使用右键可以文本进行复制。但这种方式有个问题，你复制的内容同时会显示在命令行里。</p>
<p><strong>方式二</strong>
 <br>
 使用Ctrl + Insert(Ins)键，可以对选择的文本进行复制，而且不会显示到命令行里。这种方式和平时我们使用Ctrl + C是一样的。但是比较遗憾的是我没有在MobaXterm里发现如何设置成Ctrl + C。</p>
<p><strong>粘贴</strong>
 <br>
 粘贴功能就完全可以自定义了，我们一般是使用Ctrl + V进行复制。在MobaXterm里，可以直接单击右键，也可以使用Shift + Insert(Ins)进行复制。
 <br>
 其实我们完全可以使用Ctrl + V:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F05c948d2cb5637694fe83d7d06e643fa.png&amp;size=m" alt=""></p>
<p>我们也可以定制其他快捷键，比如<code>Ctrl + F</code>是查找、新建窗口是<code>Ctrl + N</code>等。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/91414710</p>]]></description><guid isPermaLink="false">/archives/c8f06174-6631-4664-b6a8-0737eccb5020</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:08:12 GMT</pubDate></item><item><title><![CDATA[MyBatis + MySQL返回插入成功后的主键id(注解方式)]]></title><link>https://wiki.dgfygk.com/archives/7883d675-226f-4135-8390-01d17bfe4578</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MyBatis%20%2B%20MySQL%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E6%88%90%E5%8A%9F%E5%90%8E%E7%9A%84%E4%B8%BB%E9%94%AEid%28%E6%B3%A8%E8%A7%A3%E6%96%B9%E5%BC%8F%29&amp;url=/archives/7883d675-226f-4135-8390-01d17bfe4578" width="1" height="1" alt="" style="opacity:0;">
<h4>Mybatis 配置文件 useGeneratedKeys 参数只针对 insert 语句生效，默认为 false。当设置为 true 时，表示如果插入的表以自增列为主键，则允许 JDBC 支持自动生成主键，并可将自动生成的主键返回。</h4>
<h4>在insert中将useGeneratedKeys属性设置为true，并制定keyProperty为Table对象的id，关于这两个参数，官方文档解释如下：</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2970efc3aa7d045e3e52045b2b2e8dc7.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/89349241</p>]]></description><guid isPermaLink="false">/archives/7883d675-226f-4135-8390-01d17bfe4578</guid><dc:creator>黄健</dc:creator><category>JAVA</category><category>数据库</category><pubDate>Wed, 25 Sep 2024 09:04:46 GMT</pubDate></item><item><title><![CDATA[Solr查询参数详解]]></title><link>https://wiki.dgfygk.com/archives/252c7ad7-dc11-4c95-9f2c-0f301b4d1f43</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Solr%E6%9F%A5%E8%AF%A2%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3&amp;url=/archives/252c7ad7-dc11-4c95-9f2c-0f301b4d1f43" width="1" height="1" alt="" style="opacity:0;">
<h4>一、 查询参数说明</h4>
<p>在做solr查询的时候，solr提供了很多参数来扩展它自身的强大功能！以下是使用频率最高的一些参数！</p>
<p>1、常用</p>
<p><strong>q</strong> - 查询字符串，这个是必须的。如果查询所有<strong>*:*</strong> <strong>，根据指定字段查询（</strong> <strong>Name</strong> <strong></strong>:张三 AND Address:北京<strong></strong><strong>）</strong></p>
<p><strong>fq</strong> - （filter query）过虑查询，作用：在q查询符合结果中同时是fq查询符合的，例如：<strong>q=Name:张三\&amp;fq=CreateDate:[20081001 TO 20091031],</strong>找关键字mm，并且CreateDate是20081001</p>
<p><strong>fl</strong> - 指定返回那些字段内容，用逗号或空格分隔多个。( <strong>*</strong> <strong>表示所有</strong> <strong>field</strong> ,如果想查询指定字段用逗号或空格隔开（如：<strong>Name,SKU,ShortDescription或Name SKU ShortDescription【注：字段是严格区分大小写的】</strong>）)</p>
<p><strong>start</strong>- 返回第一条记录在完整找到结果中的偏移位置，0开始，一般分页用。</p>
<p><strong>rows</strong>- 指定返回结果最多有多少条记录，配合start来实现分页。</p>
<p><strong>sort</strong> - 排序，格式：sort=
 <field name>
  +
  <desc\|asc>
   [,
   <field name>
    +<desc\|asc>]… 。示例：（<strong>score desc, price asc</strong>）表示先 “score” 降序, 再 “price” 升序，默认是相关性降序。</desc\|asc>
   </field>
  </desc\|asc>
 </field></p>
<p><strong>wt</strong> - (writer type)指定输出格式，可以有 <strong>xml</strong> , <strong>json</strong> , <strong>php</strong> , <strong>phps</strong>。</p>
<p><strong>q.op</strong> 表示q 中 查询语句的 各条件的逻辑操作 AND(与) OR(或)</p>
<p><strong>hl</strong> 是否高亮 ,如<strong>hl=true</strong></p>
<p><strong>hl.fl</strong> 高亮field ,<strong>hl.fl=Name,SKU</strong></p>
<p><strong>hl.snippets</strong> :默认是1,这里设置为3个片段</p>
<p><strong>hl.simple.pre</strong> 高亮前面的格式</p>
<p><strong>hl.simple.post</strong> 高亮后面的格式</p>
<p><strong>facet</strong> 是否启动统计</p>
<p><strong>facet.field</strong> 统计field</p>
<p>二、 Solr运算符</p>
<ol>
 <li>
  <p>“:” 指定字段查指定值，如返回所有值*:*</p>
 </li>
 <li>
  <p>“?” 表示单个任意字符的通配</p>
 </li>
 <li>
  <p>“*” 表示多个任意字符的通配（不能在检索的项开始使用*或者?符号）</p>
 </li>
 <li>
  <p>“\~” 表示模糊检索，如检索拼写类似于"roam"的项这样写：roam\~将找到形如foam和roams的单词；roam\~0.8，检索返回相似度在0.8以上的记录。</p>
 </li>
 <li>
  <p>邻近检索，如检索相隔10个单词的"apache"和"jakarta”，“jakarta apache”\~10</p>
 </li>
 <li>
  <p>“^” 控制相关度检索，如检索jakarta apache，同时希望去让"jakarta"的相关度更加好，那么在其后加上”^“符号和增量值，即jakarta^4 apache</p>
 </li>
 <li>
  <p>布尔操作符AND、\|\|</p>
 </li>
 <li>
  <p>布尔操作符OR、\&amp;\&amp;</p>
 </li>
 <li>
  <p>布尔操作符NOT、!、- （排除操作符不能单独与项使用构成查询）</p>
 </li>
 <li>
  <p>“+” 存在操作符，要求符号”+“后的项必须在文档相应的域中存在</p>
 </li>
 <li>
  <p>( ) 用于构成子查询</p>
 </li>
 <li>
  <p>[] 包含范围检索，如检索某时间段记录，包含头尾，date:[200707 TO 200710]</p>
 </li>
 <li>
  <p>{} 不包含范围检索，如检索某时间段记录，不包含头尾
   <br>
   date:{200707 TO 200710}</p>
 </li>
 <li>
  <p>/ 转义操作符，特殊字符包括+ - \&amp;\&amp; \|\| ! ( ) { } [ ] ^ " \~ * ? : /</p>
 </li>
</ol>
<p>注：①<strong>“+“和”-“表示对单个查询单元的修饰，</strong> <strong>and 、or 、 not</strong> 是对两个查询单元是否做交集或者做差集还是取反的操作的符号</p>
<p>　　 比如:<strong>AB:china +AB:america</strong>,表示的是AB:china忽略不计可有可无，必须满足第二个条件才是对的,而不是你所认为的必须满足这两个搜索条件</p>
<p>　　 如果输入:<strong>AB:china AND AB:america ,解析出来的结果是两个条件同时满足，即+AB:china AND +AB:america或+AB:china +AB:america</strong></p>
<p>　　总而言之，查询语法： 修饰符 字段名:查询关键词 AND/OR/NOT 修饰符 字段名:查询关键词</p>
<p>三、 Solr查询语法</p>
<p><strong>1.</strong> 最普通的查询，比如查询姓张的人（<strong>Name:张</strong>）,如果是精准性搜索相当于SQL SERVER中的LIKE搜索这需要带引号（““）,比如查询含有北京的（Address:“北京”）</p>
<p><strong>2.</strong> 多条件查询，注：<strong>如果是针对单个字段进行搜索的可以用（Name:搜索条件加运算符(OR、AND、NOT) Name：搜索条件</strong>）,比如模糊查询（<strong>Name:张 OR Name:李</strong> ）单个字段多条件搜索不建议这样写，一般建议是在单个字段里进行条件筛选，如（ <strong>Name:张 OR 李</strong>），多个字段查询（<strong>Name:张 + Address:北京</strong>）</p>
<p>3.排序，比如根据姓名升序（<strong>Name asc</strong>）,降序（<strong>Name desc</strong>）</p>
<p>原文链接：<a href="https://www.cnblogs.com/johnwood/p/3449118.html">https://www.cnblogs.com/johnwood/p/3449118.html</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/90060175</p>]]></description><guid isPermaLink="false">/archives/252c7ad7-dc11-4c95-9f2c-0f301b4d1f43</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:04:23 GMT</pubDate></item><item><title><![CDATA[Spring Boot MyBatis 动态数据源切换、多数据源，读写分离]]></title><link>https://wiki.dgfygk.com/archives/4f535ff1-4e8f-4cdb-bb85-d3359a45f2de</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Spring%20Boot%20MyBatis%20%E5%8A%A8%E6%80%81%E6%95%B0%E6%8D%AE%E6%BA%90%E5%88%87%E6%8D%A2%E3%80%81%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90%EF%BC%8C%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB&amp;url=/archives/4f535ff1-4e8f-4cdb-bb85-d3359a45f2de" width="1" height="1" alt="" style="opacity:0;">
<p>原文章地址： <a href="https://blog.csdn.net/u013360850/article/details/78861442">https://blog.csdn.net/u013360850/article/details/78861442</a></p>
<h4>项目地址 <a href="https://github.com/helloworlde/SpringBoot-DynamicDataSource">https://github.com/helloworlde/SpringBoot-DynamicDataSource</a></h4>
<p>本项目使用 Spring Boot 和 MyBatis 实现多数据源，动态数据源的切换；有多种不同的实现方式，在学习的过程中发现没有文章将这些方式和常见的问题集中处理，所以将常用的方式和常见的问题都写在了在本项目的不同分支上：</p>
<ul>
 <li>
  <p><strong>master: 使用了多数据源的 RESTful API 接口，使用 Druid 实现了 DAO 层数据源动态切换和只读数据源负载均衡</strong></p>
 </li>
 <li>
  <p><strong>dev: 最简单的切面和注解方式实现的动态数据源切换</strong></p>
 </li>
 <li>
  <p><strong>druid: 通过切面和注解方式实现的使用 Druid 连接池的动态数据源切换</strong></p>
 </li>
 <li>
  <p><strong>aspect_dao: 通过切面实现的 DAO 层的动态数据源切换</strong></p>
 </li>
 <li>
  <p><strong>roundrobin: 通过切面使用轮询方式实现的只读数据源负载均衡</strong></p>
 </li>
 <li>
  <p><strong>hikari: 升级到SpringBoot 2.0, 数据源使用 Hikari</strong></p>
 </li>
</ul>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc37c078bd7c2167c221bd00d0ccc4f19.png&amp;size=m" alt=""></p>
<h4>在使用的过程中基本踩遍了所有动态数据源切换的坑，将常见的一些坑和解决方法写在了 <a href="https://github.com/helloworlde/SpringBoot-DynamicDataSource/blob/master/Issues.md">Issues</a> 里面</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa56615bea4387929a3bbd90f57e5026f.png&amp;size=m" alt=""></p>
<h4>添加依赖</h4>
<pre><code class="java">dependencies {
    compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-aop')
    compile('com.alibaba:druid-spring-boot-starter:1.1.6')
    runtime('mysql:mysql-connector-java')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}
</code></pre>
<h3>创建数据库及表</h3>
<ul>
 <li>分别创建数据库<code>product_master</code>, <code>product_slave_alpha</code>, <code>product_slave_beta</code>, <code>product_slave_gamma</code></li>
 <li>在以上数据库中分别创建表 <code>product</code>，并插入不同数据</li>
</ul>
<pre><code class="sql">DROP DATABASE IF EXISTS product_master;
CREATE DATABASE product_master;
CREATE TABLE product_master.product(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  price DOUBLE(10,2) NOT NULL DEFAULT 0);
INSERT INTO product_master.product (name, price) VALUES('master', '1');


DROP DATABASE IF EXISTS product_slave_alpha;
CREATE DATABASE product_slave_alpha;
CREATE TABLE product_slave_alpha.product(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  price DOUBLE(10,2) NOT NULL DEFAULT 0);
INSERT INTO product_slave_alpha.product (name, price) VALUES('slaveAlpha', '1');

DROP DATABASE IF EXISTS product_slave_beta;
CREATE DATABASE product_slave_beta;
CREATE TABLE product_slave_beta.product(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  price DOUBLE(10,2) NOT NULL DEFAULT 0);
INSERT INTO product_slave_beta.product (name, price) VALUES('slaveBeta', '1');

DROP DATABASE IF EXISTS product_slave_gamma;
CREATE DATABASE product_slave_gamma;
CREATE TABLE product_slave_gamma.product(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  price DOUBLE(10,2) NOT NULL DEFAULT 0);
INSERT INTO product_slave_gamma.product (name, price) VALUES('slaveGamma', '1');
</code></pre>
<h3>配置数据源</h3>
<ul>
 <li>application.properties</li>
</ul>
<pre><code class="java"># Master datasource config
spring.datasource.druid.master.name=master
spring.datasource.druid.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.master.url=jdbc:mysql://localhost/product_master?useSSL=false
spring.datasource.druid.master.port=3306
spring.datasource.druid.master.username=root
spring.datasource.druid.master.password=123456

# SlaveAlpha datasource config
spring.datasource.druid.slave-alpha.name=SlaveAlpha
spring.datasource.druid.slave-alpha.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.slave-alpha.url=jdbc:mysql://localhost/product_slave_alpha?useSSL=false
spring.datasource.druid.slave-alpha.port=3306
spring.datasource.druid.slave-alpha.username=root
spring.datasource.druid.slave-alpha.password=123456

# SlaveBeta datasource config
spring.datasource.druid.slave-beta.name=SlaveBeta
spring.datasource.druid.slave-beta.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.slave-beta.url=jdbc:mysql://localhost/product_slave_beta?useSSL=false
spring.datasource.druid.slave-beta.port=3306
spring.datasource.druid.slave-beta.username=root
spring.datasource.druid.slave-beta.password=123456

# SlaveGamma datasource config
spring.datasource.druid.slave-gamma.name=SlaveGamma
spring.datasource.druid.slave-gamma.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.slave-gamma.url=jdbc:mysql://localhost/product_slave_gamma?useSSL=false
spring.datasource.druid.slave-gamma.port=3306
spring.datasource.druid.slave-gamma.username=root
spring.datasource.druid.slave-gamma.password=123456

# Druid dataSource config
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size=5
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-wait=60000
spring.datasource.druid.pool-prepared-statements=false
spring.datasource.druid.validation-query=SELECT 1
spring.datasource.druid.validation-query-timeout=30000
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.test-while-idle=true
#spring.datasource.druid.time-between-eviction-runs-millis=
#spring.datasource.druid.min-evictable-idle-time-millis=
#spring.datasource.druid.max-evictable-idle-time-millis=10000

# Druid stat filter config
spring.datasource.druid.filters=stat,wall,log4j
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
spring.datasource.druid.web-stat-filter.session-stat-enable=true
spring.datasource.druid.web-stat-filter.session-stat-max-count=10
spring.datasource.druid.web-stat-filter.principal-session-name=user
#spring.datasource.druid.web-stat-filter.principal-cookie-name=
spring.datasource.druid.web-stat-filter.profile-enable=true
spring.datasource.druid.filter.stat.db-type=mysql
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=1000
spring.datasource.druid.filter.stat.merge-sql=true
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.filter.wall.config.delete-allow=true
spring.datasource.druid.filter.wall.config.drop-table-allow=false
spring.datasource.druid.filter.slf4j.enabled=true
# Druid manage page config
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
#spring.datasource.druid.stat-view-servlet.allow=
#spring.datasource.druid.stat-view-servlet.deny=
spring.datasource.druid.use-global-data-source-stat=true
# Druid AOP config
spring.datasource.druid.aop-patterns=cn.com.hellowood.dynamicdatasource.service.*
spring.aop.proxy-target-class=true

# MyBatis config
mybatis.type-aliases-package=cn.com.hellowood.dynamicdatasource.mapper
mybatis.mapper-locations=mappers/**Mapper.xml
server.port=9999
</code></pre>
<h4><strong>配置数据源</strong></h4>
<ul>
 <li>DataSourceKey.java</li>
</ul>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.common;

public enum DataSourceKey {
    master,
    slaveAlpha,
    slaveBeta,
    slaveGamma
}
</code></pre>
<ul>
 <li>DataSourceRoutingDataSource.java</li>
</ul>
<p>
 <br>
</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe36dda53f4a8eb64f6434b539aa50138.png&amp;size=m" alt=""></p>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected Object determineCurrentLookupKey() {
        logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F521a894b5eb0e6e3c618379304854b38.png&amp;size=m" alt=""></p>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.configuration;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfigurer {

    /**
     * master DataSource
     * @Primary 注解用于标识默认使用的 DataSource Bean，因为有5个 DataSource Bean，该注解可用于 master
     * 或 slave DataSource Bean, 但不能用于 dynamicDataSource Bean, 否则会产生循环调用 
     * 
     * @ConfigurationProperties 注解用于从 application.properties 文件中读取配置，为 Bean 设置属性 
     * @return data source
     */
    @Bean("master")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource master() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * Slave alpha data source.
     *
     * @return the data source
     */
    @Bean("slaveAlpha")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave-alpha")
    public DataSource slaveAlpha() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * Slave beta data source.
     *
     * @return the data source
     */
    @Bean("slaveBeta")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave-beta")
    public DataSource slaveBeta() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * Slave gamma data source.
     *
     * @return the data source
     */
    @Bean("slaveGamma")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave-gamma")
    public DataSource slaveGamma() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * Dynamic data source.
     *
     * @return the data source
     */
    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        Map&lt;Object, Object&gt; dataSourceMap = new HashMap&lt;&gt;(4);
        dataSourceMap.put(DataSourceKey.master.name(), master());
        dataSourceMap.put(DataSourceKey.slaveAlpha.name(), slaveAlpha());
        dataSourceMap.put(DataSourceKey.slaveBeta.name(), slaveBeta());
        dataSourceMap.put(DataSourceKey.slaveGamma.name(), slaveGamma());

        // 将 master 数据源作为默认指定的数据源
        dynamicRoutingDataSource.setDefaultTargetDataSource(master());
        // 将 master 和 slave 数据源作为指定的数据源
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

        // 将数据源的 key 放到数据源上下文的 key 集合中，用于切换时判断数据源是否有效
        DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());

        // 将 Slave 数据源的 key 放在集合中，用于轮循
        DynamicDataSourceContextHolder.slaveDataSourceKeys.addAll(dataSourceMap.keySet());
        DynamicDataSourceContextHolder.slaveDataSourceKeys.remove(DataSourceKey.master.name());
        return dynamicRoutingDataSource;
    }   

    /**
     * 配置 SqlSessionFactoryBean
     * @ConfigurationProperties 在这里是为了将 MyBatis 的 mapper 位置和持久层接口的别名设置到 
     * Bean 的属性中，如果没有使用 *.xml 则可以不用该配置，否则将会产生 invalid bond statement 异常
     * 
     * @return the sql session factory bean
     */
    @Bean
    @ConfigurationProperties(prefix = "mybatis")
    public SqlSessionFactoryBean sqlSessionFactoryBean() {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 配置数据源，此处配置为关键配置，如果没有将 dynamicDataSource 作为数据源则不能实现切换
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        return sqlSessionFactoryBean;
    }

    /**
     * 注入 DataSourceTransactionManager 用于事务管理
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F66533dfb02dae756185760147c1de30c.png&amp;size=m" alt=""></p>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.configuration;


import cn.com.hellowood.dynamicdatasource.common.DataSourceKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DynamicDataSourceContextHolder {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**
     * 用于在切换数据源时保证不会被其他线程修改
     */
    private static Lock lock = new ReentrantLock();

    /**
     * 用于轮循的计数器
     */
    private static int counter = 0;

    /**
     * Maintain variable for every thread, to avoid effect other thread
     */
    private static final ThreadLocal&lt;Object&gt; CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.master);


    /**
     * All DataSource List
     */
    public static List&lt;Object&gt; dataSourceKeys = new ArrayList&lt;&gt;();

    /**
     * The constant slaveDataSourceKeys.
     */
    public static List&lt;Object&gt; slaveDataSourceKeys = new ArrayList&lt;&gt;();

    /**
     * To switch DataSource
     *
     * @param key the key
     */
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    /**
     * Use master data source.
     */
    public static void useMasterDataSource() {
        CONTEXT_HOLDER.set(DataSourceKey.master);
    }

    /**
     * 当使用只读数据源时通过轮循方式选择要使用的数据源
     */
    public static void useSlaveDataSource() {
        lock.lock();

        try {
            int datasourceKeyIndex = counter % slaveDataSourceKeys.size();
            CONTEXT_HOLDER.set(String.valueOf(slaveDataSourceKeys.get(datasourceKeyIndex)));
            counter++;
        } catch (Exception e) {
            logger.error("Switch slave datasource failed, error message is {}", e.getMessage());
            useMasterDataSource();
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Get current DataSource
     *
     * @return data source key
     */
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * To set DataSource as default
     */
    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

    /**
     * Check if give DataSource is in current DataSource list
     *
     * @param key the key
     * @return boolean boolean
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa33812f5e5d1efed47d6aa09d8cb4808.png&amp;size=m" alt=""></p>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.configuration;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DynamicDataSourceAspect {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    private final String[] QUERY_PREFIX = {"select"};

    @Pointcut("execution( * cn.com.hellowood.dynamicdatasource.mapper.*.*(..))")
    public void daoAspect() {
    }

    @Before("daoAspect()")
    public void switchDataSource(JoinPoint point) {
        Boolean isQueryMethod = isQueryMethod(point.getSignature().getName());
        if (isQueryMethod) {
            DynamicDataSourceContextHolder.useSlaveDataSource();
            logger.info("Switch DataSource to [{}] in Method [{}]",
                    DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
        }
    }

    @After("daoAspect()")
    public void restoreDataSource(JoinPoint point) {
        DynamicDataSourceContextHolder.clearDataSourceKey();
        logger.info("Restore DataSource to [{}] in Method [{}]",
                DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
    }

    private Boolean isQueryMethod(String methodName) {
        for (String prefix : QUERY_PREFIX) {
            if (methodName.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }

}
</code></pre>
<h3>配置 Product REST API 接口</h3>
<ul>
 <li>ProductController.java</li>
</ul>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.controller;

import cn.com.hellowood.dynamicdatasource.common.CommonResponse;
import cn.com.hellowood.dynamicdatasource.common.ResponseUtil;
import cn.com.hellowood.dynamicdatasource.modal.Product;
import cn.com.hellowood.dynamicdatasource.service.ProductService;
import cn.com.hellowood.dynamicdatasource.utils.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public CommonResponse getProduct(@PathVariable("id") Long productId) throws ServiceException {
        return ResponseUtil.generateResponse(productService.select(productId));
    }

    @GetMapping
    public CommonResponse getAllProduct() {
        return ResponseUtil.generateResponse(productService.getAllProduct());
    }

    @PutMapping("/{id}")
    public CommonResponse updateProduct(@PathVariable("id") Long productId, @RequestBody Product newProduct) throws ServiceException {
        return ResponseUtil.generateResponse(productService.update(productId, newProduct));
    }

    @DeleteMapping("/{id}")
    public CommonResponse deleteProduct(@PathVariable("id") long productId) throws ServiceException {
        return ResponseUtil.generateResponse(productService.delete(productId));
    }

    @PostMapping
    public CommonResponse addProduct(@RequestBody Product newProduct) throws ServiceException {
        return ResponseUtil.generateResponse(productService.add(newProduct));
    }
}
</code></pre>
<ul>
 <li>ProductService.java</li>
</ul>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.service;

import cn.com.hellowood.dynamicdatasource.mapper.ProductDao;
import cn.com.hellowood.dynamicdatasource.modal.Product;
import cn.com.hellowood.dynamicdatasource.utils.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class ProductService {

    @Autowired
    private ProductDao productDao;

    public Product select(long productId) throws ServiceException {
        Product product = productDao.select(productId);
        if (product == null) {
            throw new ServiceException("Product:" + productId + " not found");
        }
        return product;
    }

    @Transactional(rollbackFor = DataAccessException.class)
    public Product update(long productId, Product newProduct) throws ServiceException {

        if (productDao.update(newProduct) &lt;= 0) {
            throw new ServiceException("Update product:" + productId + "failed");
        }
        return newProduct;
    }

    @Transactional(rollbackFor = DataAccessException.class)
    public boolean add(Product newProduct) throws ServiceException {
        Integer num = productDao.insert(newProduct);
        if (num &lt;= 0) {
            throw new ServiceException("Add product failed");
        }
        return true;
    }

    @Transactional(rollbackFor = DataAccessException.class)
    public boolean delete(long productId) throws ServiceException {
        Integer num = productDao.delete(productId);
        if (num &lt;= 0) {
            throw new ServiceException("Delete product:" + productId + "failed");
        }
        return true;
    }

    public List&lt;Product&gt; getAllProduct() {
        return productDao.getAllProduct();
    }
}
</code></pre>
<ul>
 <li>ProductDao.java</li>
</ul>
<pre><code class="java">package cn.com.hellowood.dynamicdatasource.mapper;

import cn.com.hellowood.dynamicdatasource.modal.Product;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface ProductDao {
    Product select(@Param("id") long id);

    Integer update(Product product);

    Integer insert(Product product);

    Integer delete(long productId);

    List&lt;Product&gt; getAllProduct();
}
</code></pre>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F87223e7e8cfe4f8bdde74b1260addfed.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/92110880</p>]]></description><guid isPermaLink="false">/archives/4f535ff1-4e8f-4cdb-bb85-d3359a45f2de</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:04:08 GMT</pubDate></item><item><title><![CDATA[Jquery 点击添加按钮 可以添加一个一模一样的input输入框]]></title><link>https://wiki.dgfygk.com/archives/6ffc2810-4e71-42a4-8da1-90ce397ca7d5</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Jquery%20%E7%82%B9%E5%87%BB%E6%B7%BB%E5%8A%A0%E6%8C%89%E9%92%AE%20%E5%8F%AF%E4%BB%A5%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E4%B8%80%E6%A8%A1%E4%B8%80%E6%A0%B7%E7%9A%84input%E8%BE%93%E5%85%A5%E6%A1%86&amp;url=/archives/6ffc2810-4e71-42a4-8da1-90ce397ca7d5" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa2b723bd7e26da64b56bedb5865d2ef3.png&amp;size=m" alt=""></p>
<p>点击添加 可以添加一个一模一样的姓名输入框</p>
<p>代码：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbefdd9870dd5d46c209374ef0c80c919.png&amp;size=m" alt=""></p>
<p>实现：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff6b3afffe96348767e8d6ce3bcd72a20.png&amp;size=m" alt=""></p>
<p>效果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0c8ccd14f710696f0b989687500b29c4.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/86228209</p>]]></description><guid isPermaLink="false">/archives/6ffc2810-4e71-42a4-8da1-90ce397ca7d5</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 09:03:39 GMT</pubDate></item><item><title><![CDATA[JQuery 获取input框多个相同name 的 value 值]]></title><link>https://wiki.dgfygk.com/archives/75630956-58a9-46d0-af9e-ce8621510dd9</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JQuery%20%E8%8E%B7%E5%8F%96input%E6%A1%86%E5%A4%9A%E4%B8%AA%E7%9B%B8%E5%90%8Cname%20%E7%9A%84%20value%20%E5%80%BC&amp;url=/archives/75630956-58a9-46d0-af9e-ce8621510dd9" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F01198c0305c821dd569e58e568a524e5.png&amp;size=m" alt=""></p>
<p>点击添加name 可以添加姓名输入框，所添加的input name都是相同的</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0fc5bf31ac43930b56a55a310fc1c6b4.png&amp;size=m" alt=""></p>
<p>获取所有的姓名：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F06e9eb59330fd4a7d8f618cb5c6ddc1f.png&amp;size=m" alt=""></p>
<p>效果如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8bd748635acac7544f15c613d5582360.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/86227984</p>]]></description><guid isPermaLink="false">/archives/75630956-58a9-46d0-af9e-ce8621510dd9</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 09:03:14 GMT</pubDate></item><item><title><![CDATA[一文掌握项目管理全流程14大方法工具模板详解【附模板实例】]]></title><link>https://wiki.dgfygk.com/archives/yi-wen-zhang-wo-xiang-mu-guan-li-quan-liu-cheng-</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%80%E6%96%87%E6%8E%8C%E6%8F%A1%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%85%A8%E6%B5%81%E7%A8%8B14%E5%A4%A7%E6%96%B9%E6%B3%95%E5%B7%A5%E5%85%B7%E6%A8%A1%E6%9D%BF%E8%AF%A6%E8%A7%A3%E3%80%90%E9%99%84%E6%A8%A1%E6%9D%BF%E5%AE%9E%E4%BE%8B%E3%80%91&amp;url=/archives/yi-wen-zhang-wo-xiang-mu-guan-li-quan-liu-cheng-" width="1" height="1" alt="" style="opacity:0;">
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bnnj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 2em"><span style="font-size: 14px">在一个项目的实施过程中，有很多重要的文件和术语起着关键作用。首先是</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目需求建议书（RFP）</span></strong><span style="font-size: 14px">，它就像是项目的蓝图规划书，由需求方发出，详细说明了项目的目标、需求、范围、时间要求以及预算限制等关键信息，为后续的项目开展提供了明确的方向。接着，</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目授权书</span></strong><span style="font-size: 14px">的出现，代表着项目发起方或上级管理层正式授权特定的个人或团队负责项目实施，明确了负责人的权力和责任，让项目得以合法启动。</span></p>
<p style="line-height: 2em"><span style="font-size: 14px">有了授权后，</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目计划文件</span></strong><span style="font-size: 14px">登场，它对整个项目进行全面规划，涵盖目标、范围、时间表、资源需求和风险管理计划等，为项目执行绘制出清晰的路线图。</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目需求文件</span></strong><span style="font-size: 14px">则进一步细化项目的具体需求，包括功能、性能和安全需求等，是项目设计、开发和测试的重要依据。</span></p>
<p style="line-height: 2em"><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目范围说明书</span></strong><span style="font-size: 14px">明确界定了项目的边界，让大家清楚知道哪些工作在项目范围内，哪些不在，以及可交付的成果有哪些。</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">工作分解结构（WBS）词典</span></strong><span style="font-size: 14px">把项目分解成一个个具体的工作包，详细描述每个工作包的内容、负责人、时间安排和成本估算等。</span></p>
<p style="line-height: 2em"><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目资源计划表</span></strong><span style="font-size: 14px">列出项目所需的各种资源，确保资源合理安排。</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目成本估算表</span></strong><span style="font-size: 14px">对各项成本进行估算，让项目在预算范围内进行。</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目质量计划</span></strong><span style="font-size: 14px">保障项目交付成果的质量，明确质量目标和标准。</span></p>
<p style="line-height: 2em"><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目变更管理</span></strong><span style="font-size: 14px">在项目过程中，确保变更有序进行，防止不良影响。</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目阶段性评审报告</span></strong><span style="font-size: 14px">在各阶段结束时评估项目进展，为后续工作提供参考。</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目会议纪要</span></strong><span style="font-size: 14px">记录会议决策和沟通内容。最后，</span><strong><span style="font-size: 14px; color: rgb(255, 169, 0)">项目总结报告</span></strong><span style="font-size: 14px">对整个项目进行全面总结，为未来项目提供经验教训。</span><strong><span style="font-size: 14px">下表详细介绍：</span></strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 984px">
  <colgroup>
   <col style="width: 90px">
   <col style="width: 332px">
   <col style="width: 237px">
   <col style="width: 163px">
   <col style="width: 162px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-width: 1pt;border-left-style: double;border-color: rgb(48, 192, 180);border-top-style: double;background: rgb(125, 223, 215);">
     <p style="text-align: center; "><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">名称</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: 1pt double rgb(48, 192, 180);border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(125, 223, 215);">
     <p style="text-align: center; "><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">介绍</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: 1pt double rgb(48, 192, 180);border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(125, 223, 215);">
     <p style="text-align: center; "><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">具体示例</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: 1pt double rgb(48, 192, 180);border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(125, 223, 215);">
     <p style="text-align: center; "><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">关联关系</span></strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: 1pt double rgb(48, 192, 180);border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(125, 223, 215);">
     <p style="text-align: center; "><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">重要程度</span></strong></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style="">项目需求建议书（RFP）</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">由项目需求方发出，明确阐述项目目标、需求、范围、时间要求、预算限制等关键信息，为潜在项目承接方提供指南。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style="">例如，某软件项目 RFP 中明确提出要开发一款具有特定功能的办公软件，项目周期为 6 个月，预算为 50 万元。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">是项目计划文件、项目需求文件等后续文件的重要依据。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，决定了项目的方向和基本要求。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目授权书</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">由项目发起方或上级管理层签署，正式授权特定个人或团队负责项目实施，明确项目负责人权力和责任。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">授权书明确项目经理张三负责某项目的全面管理，拥有资源调配等权力。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">为项目的启动提供合法性，与项目计划文件等共同确保项目顺利开展。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，是项目开展的前提。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目计划文件</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">对整个项目进行全面规划，包括目标、范围、时间表、资源需求、风险管理计划等，为项目执行提供路线图。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">某项目计划文件中详细列出项目各阶段的时间节点、所需资源及风险应对措施。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style="">依据 RFP、项目需求文件等制定，同时为其他文件的执行提供时间框架。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，指导项目的整体实施。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目需求文件</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">详细描述项目的具体需求，如功能需求、性能需求、安全需求等，是项目设计、开发和测试的重要依据。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">某软件项目需求文件明确软件需具备数据加密、多用户同时登录等功能。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style="">基于 RFP 制定，为项目范围说明书、质量计划等提供需求基础。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，决定项目最终成果是否满足用户需求。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目范围说明书</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">明确界定项目边界，包括工作内容、不包括的内容及可交付成果，帮助项目团队和利益相关者理解项目范围。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">说明书中明确某建筑项目包括主体结构建设但不包括内部精装修。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style="">依据项目需求文件制定，与 WBS 词典等共同明确项目具体工作范围。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，防止范围蔓延。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style="">工作分解结构（WBS）词典</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">对工作分解结构中每个工作包进行详细描述，包括名称、工作内容、负责人、时间安排、成本估算等。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style="">某工作包描述为 “基础工程施工，负责人李四，预计时间 1 个月，成本估算 10 万元。”</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">基于项目范围说明书制定，为资源计划、成本估算等提供具体工作内容。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">较高，确保每个工作包明确执行。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目资源计划表</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">列出项目所需各种资源，如人力资源、物力资源、财力资源等，便于合理安排资源。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style="">明确项目需要工程师 5 名、特定设备若干及预算资金分配。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style="">依据项目计划文件、WBS 词典等制定，为项目执行提供资源保障。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，影响项目进度和质量。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目成本估算表</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">对项目各项成本进行详细估算，包括人力成本、物资成本、设备成本、差旅费等，为预算编制提供依据。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">分别列出不同成本项的具体金额估算。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style="">结合项目资源计划、WBS 词典等进行成本估算，用于控制项目成本。</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，确保项目在预算范围内进行。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目质量计划</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">明确项目质量目标、标准、控制措施和保证措施，确保项目交付成果符合质量要求。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">规定软件项目需经过多轮测试，达到特定性能指标。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">基于项目需求文件和范围说明书制定，保障项目成果质量。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，影响项目的成功与否和用户满意度。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目变更管理</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">一套用于管理项目变更的流程和方法，包括变更请求提出、评估、审批、实施和监控等环节。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">当需求变更时，按照既定流程进行评估和处理。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">贯穿整个项目过程，与其他文件相互作用，确保项目在变更中保持可控。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，防止变更对项目造成不良影响。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目阶段性评审报告</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">在项目各阶段结束时，对项目进展情况进行评估和总结，包括目标达成情况、工作成果、问题风险及下一步计划。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">某阶段评审报告指出已完成工作、存在问题及下一阶段的重点任务。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">依据项目计划文件进行阶段性评估，为后续工作调整提供参考。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">较高，及时发现问题并调整策略。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目会议纪要</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">记录项目会议的主要内容和决策结果，包括时间、地点、参会人员、讨论问题、共识及行动计划。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">详细记录每次项目会议的讨论内容和决策。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">是项目执行过程中的沟通记录，与其他文件相互补充，确保信息传达准确。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">较高，保证项目信息沟通顺畅。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目自我评价表</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目团队成员对项目执行情况进行自我评估，包括目标达成、工作质量、团队协作、沟通效果等方面。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">团队成员对自己在项目中的表现进行打分和评价。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">为项目总结提供内部视角，结合其他文件进行全面总结。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding: 7.2pt 10.8pt;border-left: none;border-right: 1pt double rgb(48, 192, 180);border-top: none;border-bottom-width: 1pt;border-bottom-color: rgb(48, 192, 180);background: rgb(231, 249, 248);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">中等，有助于团队自我提升。</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="90" style="padding: 7.2pt 10.8pt;border-left: 1pt double rgb(48, 192, 180);border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom: 1pt double rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">项目总结报告</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="332" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom: 1pt double rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">在项目结束时，对整个执行过程进行总结和评估，包括目标达成情况、工作成果、经验教训、建议和展望。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="237" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom: 1pt double rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">全面回顾项目过程，总结成功经验和不足之处。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="163" style="padding: 7.2pt 10.8pt;border-left: none;border-right-width: 1pt;border-right-color: rgb(48, 192, 180);border-top: none;border-bottom: 1pt double rgb(48, 192, 180);background: rgb(255, 255, 255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">综合各个文件内容进行总结，为未来项目提供借鉴。</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="162" style="padding:7.2000pt 10.8000pt 7.2000pt 10.8000pt;border-left:none;mso-border-left-alt:none;border-right:1.0000pt double rgb(48,192,180);mso-border-right-alt:0.5000pt double rgb(48,192,180);border-top:none;mso-border-top-alt:0.5000pt solid rgb(48,192,180);border-bottom:1.0000pt double rgb(48,192,180);mso-border-bottom-alt:0.5000pt double rgb(48,192,180);background:rgb(255,255,255);">
     <p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">高，为后续项目提供宝贵经验。</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style=""><strong>01</strong></p>
<p style="">项目需求建议书（RFP）</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uepx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>02</strong></p>
<p style="">项目授权书</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dqkf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>03</strong></p>
<p style="">项目计划文件</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-koff.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>04</strong></p>
<p style="">项目需求文件</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xmlr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>05</strong></p>
<p style="">项目范围说明书</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fdbe.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>06</strong></p>
<p style="">工作分解结构（WBS）词典</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wanq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>07</strong></p>
<p style="">项目资源计划表</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iasq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>08</strong></p>
<p style="">项目成本估算表</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ucdn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>09</strong></p>
<p style="">项目质量计划</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hnjz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>10</strong></p>
<p style="">项目变更管理</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ehoi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>11</strong></p>
<p style="">项目阶段性评审报告</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tpsc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>12</strong></p>
<p style="">项目会议纪要</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-osvw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>13</strong></p>
<p style="">项目自我评价表</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yeku.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>14</strong></p>
<p style="">项目总结报告</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-znaa.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: normal"><span style="font-size: 12px; color: rgb(178, 178, 178)">模板图来源于网络，交流分享，侵删</span></p>
<p style="line-height: normal"></p>
<p style="line-height: normal"><span style="font-size: 12px; color: rgb(178, 178, 178)">原文链接：https://mp.weixin.qq.com/s/K3ZCZwDcQMp4l8Lm3MZD8w</span></p>]]></description><guid isPermaLink="false">/archives/yi-wen-zhang-wo-xiang-mu-guan-li-quan-liu-cheng-</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 08:31:26 GMT</pubDate></item><item><title><![CDATA[JQuery 添加和删除input输入框 每次删除最后一个input框]]></title><link>https://wiki.dgfygk.com/archives/4556971d-eb4c-4256-8791-9a17b606d96d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JQuery%20%E6%B7%BB%E5%8A%A0%E5%92%8C%E5%88%A0%E9%99%A4input%E8%BE%93%E5%85%A5%E6%A1%86%20%E6%AF%8F%E6%AC%A1%E5%88%A0%E9%99%A4%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AAinput%E6%A1%86&amp;url=/archives/4556971d-eb4c-4256-8791-9a17b606d96d" width="1" height="1" alt="" style="opacity:0;">
<p>点击添加姓名按钮 可以添加多个一模一样的输入框 ，点击删除可以删除最后一个输入框 如图所示：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F51e7893d7adc7e70e34810a4307ac366.png&amp;size=m" alt=""></p>
<p>js:</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4ae7f0a731d826d581e650a6d06e7500.png&amp;size=m" alt=""></p>
<p>实现：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc8ef9fa620c2588187a5e595a93dd8cc.png&amp;size=m" alt=""></p>
<p>add效果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3efc215470b2ae4808cdbc1603be42e7.png&amp;size=m" alt=""></p>
<p>remove 效果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd580226660cd5ff7d47af489ab339970.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/86228558</p>]]></description><guid isPermaLink="false">/archives/4556971d-eb4c-4256-8791-9a17b606d96d</guid><dc:creator>黄健</dc:creator><category>软件开发</category><pubDate>Wed, 25 Sep 2024 06:57:13 GMT</pubDate></item><item><title><![CDATA[mybatis的xml中sql语句中in的写法]]></title><link>https://wiki.dgfygk.com/archives/59e20da5-511f-4630-af8e-d5d6e49fdb48</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=mybatis%E7%9A%84xml%E4%B8%ADsql%E8%AF%AD%E5%8F%A5%E4%B8%ADin%E7%9A%84%E5%86%99%E6%B3%95&amp;url=/archives/59e20da5-511f-4630-af8e-d5d6e49fdb48" width="1" height="1" alt="" style="opacity:0;">
<p>传入的参数必须为collection类型的，List 、Map，如果你在地址栏接收到的是字符串，那你需要转为collection类型</p>
<p>在XML中主要使用foreach标签</p>
<p>|——-|—————————————————————————————————————————————————-|
 <br>
 | 1 2 3 | <code>&lt;`</code>foreachitem=<code>"item"` `collection=</code>“listTag”index=<code>"index"` `open=</code>“(“separator=<code>","` `close=</code>“)“<code>&gt;` `#{item}` `&lt;/</code>foreach`<code>&gt;</code> |</p>
<p>foreach的结果为：(23,46,75,43) 这种形式</p>
<p>foreach元素的属性主要有 item，index，collection，open，separator，close。</p>
<p>|————-|———————————————————————————————————————————————————————————|
 <br>
 | 1 2 3 4 5 6 | <code># item表示集合中每一个元素进行迭代时的别名.</code> <code># index指 定一个名字，用于表示在迭代过程中，每次迭代到的位置.</code> <code># collection 为传进来的collection参数的 *类型*</code> <code># open表示该语句以什么开始</code> <code># separator表示在每次进行迭代之间以什么符号作为分隔符</code> <code># close表示以什么结束</code> |</p>
<p>1.如果传入的是单参数且参数类型是一个List的时候，collection属性值为list</p>
<p>传入参数的代码为：</p>
<p>|—|——————————————-|
 <br>
 | 1 | <code>List&lt;Object&gt; addList(List&lt;Object&gt; ids);</code> |</p>
<p>XML为：</p>
<p>|————-|—————————————————————————————————————————————————————————————————————————————————-|
 <br>
 | 1 2 3 4 5 6 | <code>&lt;select id=`</code>“addList”resultType=<code>"map"</code>&gt;select * from tp_trade where id in &lt;``foreach` `item=``"item"` `collection=``"list"` `index=``"index"` `open=``"("` `separator=``","` `close=``")"``&gt;#{item}<!--/``foreach``-->` |</p>
<p>2.如果传入的是单参数且参数类型是一个Array数组的时候，collection属性值为array</p>
<p>传入的参数代码为:</p>
<p>|—|—————————————-|
 <br>
 | 1 | <code>List&lt;Object&gt; addArray(String[] ids);</code> |</p>
<p>XML为：</p>
<p>|————-|———————————————————————————————————————————————————————————————————————————————————–|
 <br>
 | 1 2 3 4 5 6 | <code>&lt;select id=`</code>“addArray”resultType=<code>"map"</code>&gt;select * from tp_trade where tt_type in &lt;``foreach` `item=``"item"` `collection=``"array"` `index=``"index"` `open=``"("` `separator=``","` `close=``")"``&gt;#{item}<!--/``foreach``-->` |</p>
<p>3.如果多个参数，我们会封装成map类型，然后在把需要遍历的list或者array封装到map中。</p>
<p>获得的参数为String:</p>
<p>|——-|——————————————————————————————————|
 <br>
 | 1 2 3 | <code>String str = `</code>“1,2,3,4”<code>;` `Map map =</code>newHashMap();map.put(<code>"ids"</code>,str.spit(<code>","</code>));` |</p>
<p>再把封装好map传入到方法中。</p>
<p>传入的参数代码为:</p>
<p>|—|————————————————|
 <br>
 | 1 | <code>List&lt;Object&gt; addMap(Map&lt;String,Object&gt; map);</code> |</p>
<p>XML为：</p>
<p>|————-|————————————————————————————————————————————————————————————————————————————————–|
 <br>
 | 1 2 3 4 5 6 | <code>&lt;select id=`</code>“addMap”resultType=<code>"map"</code>&gt;select * from tp_trade where id in &lt;``foreach` `item=``"item"` `collection=``"ids"` `index=``"index"` `open=``"("` `separator=``","` `close=``")"``&gt;#{item}<!--/``foreach``-->` |</p>
<p>ids就是数组集合，使用item遍历即可</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/86478222</p>]]></description><guid isPermaLink="false">/archives/59e20da5-511f-4630-af8e-d5d6e49fdb48</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:37:03 GMT</pubDate></item><item><title><![CDATA[XML 中 常用的转义字符]]></title><link>https://wiki.dgfygk.com/archives/cd4853cb-0589-4301-9b5f-ddce09927e7d</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=XML%20%E4%B8%AD%20%E5%B8%B8%E7%94%A8%E7%9A%84%E8%BD%AC%E4%B9%89%E5%AD%97%E7%AC%A6&amp;url=/archives/cd4853cb-0589-4301-9b5f-ddce09927e7d" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F62c5113ea880b0f6fd945131bcf5c456.jpeg&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/86314455</p>]]></description><guid isPermaLink="false">/archives/cd4853cb-0589-4301-9b5f-ddce09927e7d</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:36:25 GMT</pubDate></item><item><title><![CDATA[SpringMVC中controller接收json数据的方法]]></title><link>https://wiki.dgfygk.com/archives/77a1d500-4047-4d2a-8611-59d8da74d3fe</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringMVC%E4%B8%ADcontroller%E6%8E%A5%E6%94%B6json%E6%95%B0%E6%8D%AE%E7%9A%84%E6%96%B9%E6%B3%95&amp;url=/archives/77a1d500-4047-4d2a-8611-59d8da74d3fe" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8f5c7f89b2fb21b1cf54b41106fd7fc7.png&amp;size=m" alt=""></p>
<p>注意：</p>
<p>1.在发送数据时，data键的值一定要写成JSON.stringify(json)，将数据转换成json格式，否则会抛出异常</p>
<p>2.basePath是项目根目录：</p>
<p><code>&lt;%</code></p>
<p><code>String path = request.getContextPath();</code></p>
<p><code>String basePath = request.getScheme()+`</code>“://“<code>+request.getServerName()+</code>“:“<code>+request.getServerPort()+path+</code>“/“`<code>;</code></p>
<p><code>%&gt;</code></p>
<p>controller接收参数时，只要用把 datas传获取就行了</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F16ddec49f674a8215ec011521cd08f80.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/86226952</p>]]></description><guid isPermaLink="false">/archives/77a1d500-4047-4d2a-8611-59d8da74d3fe</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:36:07 GMT</pubDate></item><item><title><![CDATA[IDEA中的maven web 项目中如何设置自己的本地仓库]]></title><link>https://wiki.dgfygk.com/archives/477d4d45-f1fb-4042-bc15-202254804338</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IDEA%E4%B8%AD%E7%9A%84maven%20web%20%E9%A1%B9%E7%9B%AE%E4%B8%AD%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E8%87%AA%E5%B7%B1%E7%9A%84%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93&amp;url=/archives/477d4d45-f1fb-4042-bc15-202254804338" width="1" height="1" alt="" style="opacity:0;">
<p>我们在创建maven项目的时候如何不使用系统指定的本地仓库，而使用自己设置的仓库呢。</p>
<h4>看下面正解：</h4>
<p>正解一：你要想找到settings.xml你就要自己我去官网上去下载apache-maven下面是我自己下载的版本，你下载之后进行压缩就可以</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F124fe42d13479d50441bebefe840ee98.png&amp;size=m" alt=""></p>
<p>正解二：你解压之后在你的解压的文件夹下面的conf文件夹里面有settings.xml文件，在这里你可以将其复制粘贴到你要建的仓库的文件夹中，并且修改里面的路径</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4b8c0ae388d6c969d2f99fb9dcbbed61.png&amp;size=m" alt=""></p>
<h4>注意：你自己随便在那个盘里面建个新的文件夹，再复制粘贴settings.xml进行修改，设置它的存放路径跟阿里巴巴的仓库就行了，然后你在部署项目的时候进行覆盖就就可以了！</h4>
<p>正解三：具体详细配置settings.xml如下</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F171c4fd2e1303fc550cb91aa1e9c45e9.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3f201a80e0e2b34da4fbf022bc388ba7.png&amp;size=m" alt=""></p>
<p><code>&lt;mirror&gt;</code></p>
<pre><code>` ``&lt;url&gt;http:``//maven.aliyun.com/nexus/content/groups/public/&lt;/url&gt; `
</code></pre>
<p><code>&lt;/mirror&gt;</code></p>
<p><code>你可以直接把这部分代码复制到指定的位置即可！</code></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa31d9123ded85c9ac8d18fe8cc6c0991.png&amp;size=m" alt=""></p>
<h4>只要你选择了这个你选择的文件，并且下面会自动给你设置这个你自己在Setting.xml中配置的文件的路径。</h4>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/84069082</p>]]></description><guid isPermaLink="false">/archives/477d4d45-f1fb-4042-bc15-202254804338</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:35:52 GMT</pubDate></item><item><title><![CDATA[IntelliJ IDEA创建maven web项目（IDEA新手适用）]]></title><link>https://wiki.dgfygk.com/archives/dac342ac-984e-46c1-b68f-2f58cb5ffc64</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=IntelliJ%20IDEA%E5%88%9B%E5%BB%BAmaven%20web%E9%A1%B9%E7%9B%AE%EF%BC%88IDEA%E6%96%B0%E6%89%8B%E9%80%82%E7%94%A8%EF%BC%89&amp;url=/archives/dac342ac-984e-46c1-b68f-2f58cb5ffc64" width="1" height="1" alt="" style="opacity:0;">
<p>从eclipse刚转到IDEA，对于这个陌生的工具我表示无言，但听说很好用，也就试试，结果我几乎花了一晚上的时间才搭起起来maven web项目，觉得在此给各位一个搭建maven web项目的教程，指出我踩过的各种坑！</p>
<h4>步骤一：首先先创建一个项目，在这里就是创建一个行家的工作空间</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F6b46b8c46b131e9d1607a9723ef5815b.png&amp;size=m" alt=""></p>
<h4>步骤二：按照下面的步骤操作就可以了，最后next</h4>
<p>首先，选择左边的maven,然后在右边Creater from archetype选项前面打个勾，</p>
<h4>最后选择maven-archetype-webapp（这里不知道是我IDEA的问题还是什么原因，我打webapp搜索它只给出第一个，所以在这个坑上耗了几乎1个小时，都选择了第一个）</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1a956b361035c85c58378d57c10bada9.png&amp;size=m" alt=""></p>
<h4>步骤三：就简单了，填写你的的GroupId跟ArtifictId（瞎写都没问题，当然不建议哈，这里的GroupId一般写公司名，ArtifictId一般我写项目名）–&gt;然后next</h4>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4b36e8ccd40e37b315d6dbf20cf57e53.png&amp;size=m" alt=""></h4>
<p>步骤四：配置自己的行家，然后–&gt;next</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1d48732d40018216cbdb427d22c26b63.png&amp;size=m" alt=""></p>
<h4>步骤五：直接点finish</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F46a46543e86f3d499c2b606252729f37.png&amp;size=m" alt=""></p>
<h4>步骤六：此时目录结构还没出来，稍等片刻</h4>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1fb8baae9fe227f98f49e147ab14210a.png&amp;size=m" alt=""></h4>
<h4>步骤七：等出结果，如果目录结构还不出来直接按图操作一波</h4>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F91992fa2d6d60ad4dc30bdb6cc4f1e90.png&amp;size=m" alt=""></h4>
<p>步骤八：项目的基本目录结构出来后我们最后还需要为项目配置下编译路径还有神器，按图操作把</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fdb45240747f0179a1d8034094ad2fe69.png&amp;size=m" alt="">
 <br>
 出现下图</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe776970356f668a3c5c59a2ecb1fbc88.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd99e51cdeb2696d7a52a51b84ee990a2.png&amp;size=m" alt=""></p>
<h4>接着设置Artifacts</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F052cb63247f8b280f2e048a3af21b19a.png&amp;size=m" alt=""></p>
<h4>选择我们的项目</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8e8cd76e52a2c0bce3f13eca2491ebb4.png&amp;size=m" alt=""></p>
<h4>到了这里再配置的tomcat跑下基本就OK了！</h4>
<h4>步骤九：配置Tomcat的，上面目录结构都出来了基本就没有坑了，现在我们给它配置个tomcat的—按图操作吧</h4>
<h4><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F823edf62e9cd58b8107de61ce934c1b6.png&amp;size=m" alt=""></h4>
<p>出现这个画面，然后按图再操作一波</p>
<p>接着出现这个画面，继续看图操作</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd64aecaf93b359b337810d65582f5c26.png&amp;size=m" alt=""></p>
<h4>然后你选择右边第二个选项，部署，还是看图操作…</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1c8b500ca0c52f687e725bc9673922f0.png&amp;size=m" alt=""></p>
<h4>最后点击确定完成Tomcat的配置，现在就可以启动tomcat的跑跑我们的helloWorld的了！</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb2ddaf0817648156a2ff5f85072c43c6.png&amp;size=m" alt=""></p>
<h4>步骤十：启动tomcat的，看效果</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc4696a6822ca9b48be20dfb6e7c589b1.png&amp;size=m" alt="">
 <br>
 启动完想法一般会自动通过默认浏览器打开的http:localhost:/8080 /，如果没有就直接在直接的浏览器打这个地址，注意我这里的Tomcat自己设置的端口</p>
<p>最后如果还是出不来</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fbd639f6e8a2f3bfc5ac47e6da97e67db.png&amp;size=m" alt=""></p>
<p>这样的结构，那么你就重新下载一个maven….zip包，重新解压，然后设置一下settings.xml文件,具体设置参考我的上一篇博客：<a href="https://blog.csdn.net/zuihongyan518/article/details/84069082">https://blog.csdn.net/zuihongyan518/article/details/84069082</a></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/84190164</p>]]></description><guid isPermaLink="false">/archives/dac342ac-984e-46c1-b68f-2f58cb5ffc64</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:35:32 GMT</pubDate></item><item><title><![CDATA[js插件---日期控件My97DataPicker的使用]]></title><link>https://wiki.dgfygk.com/archives/ebdd7657-83fc-411b-a9b4-f0779098dd52</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=js%E6%8F%92%E4%BB%B6---%E6%97%A5%E6%9C%9F%E6%8E%A7%E4%BB%B6My97DataPicker%E7%9A%84%E4%BD%BF%E7%94%A8&amp;url=/archives/ebdd7657-83fc-411b-a9b4-f0779098dd52" width="1" height="1" alt="" style="opacity:0;">
<h4>1. 注意事项</h4>
<p>（1）My97DatePicker目录是一个整体,不可破坏里面的目录结构,也不可对里面的文件改名,可以改目录名；</p>
<p>（2）各目录及文件的用途:</p>
<p>WdatePicker.js 配置文件,在调用的地方仅需使用该文件,可多个共存,以xx_WdatePicker.js方式命名</p>
<p>config.js 语言和皮肤配置文件,无需引入(4.8以后合并入WdatePicker.js)</p>
<p>calendar.js 日期库主文件,无需引入</p>
<p>目录lang 存放语言文件,你可以根据需要清理或添加语言文件</p>
<p>目录skin 存放皮肤的相关文件,你可以根据需要清理或添加皮肤文件包</p>
<p>（3）当WdatePicker.js里的属性:$wdate=true时,在input里加上class=“Wdate"就会在选择框右边出现日期图标,如果您不喜欢这个样式,可以把class=“Wdate"去掉,另外也可以通过修改skin目录下的WdatePicker.css文件来修改样式</p>
<h4>二、日期控件My97DataPicker</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F637ca0dd600f3bd2c6eb84852803dcef.png&amp;size=m" alt=""></p>
<p>My97DataPicker的下载地址：<a href="https://download.csdn.net/download/zuihongyan518/10841043">https://download.csdn.net/download/zuihongyan518/10841043</a>。下载完成之后，将My97DataPicker文件夹import到WebAPP下。或者直接将My97DataPicker文件夹复制到WebAPP下。</p>
<p>二、在jsp中使用My97DataPicke</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ffb6bcc8122f9f517d93619ba6943ae81.png&amp;size=m" alt=""></p>
<p>My97是基于jQuery使用的，所以在使用中必须要引入jQuery包。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4e1fd18813947775802699eb9668245a.png&amp;size=m" alt=""></p>
<p>在jsp中使用：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0cadf5d533bfa5c7d5bdcf5c7ac2390f.png&amp;size=m" alt=""></p>
<p>三、在js文件中处理时间</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9bcec84dde0e7a6e5736dc6cbe2327ab.png&amp;size=m" alt=""></p>
<p>四、访问结果如下，得到的数值是string类型的。关于My97DataPicker的详细使用，请参考官方文档：<a href="http://www.my97.net/dp/demo/index.htm">http://www.my97.net/dp/demo/index.htm</a></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F18e6350d6d1d5d7c61ca626b8fa8dbe1.png&amp;size=m" alt=""> <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8cfd36abcf920a5329649ce13e04621d.png&amp;size=m" alt=""></p>
<p>五、下面详细讲解它的各种常用功能。
 <br>
 显示年：dateFmt:'yyyy'
 <br>
 显示年月：dateFmt:'yyyyMM'
 <br>
 显示年月日：dateFmt:'yyyyMMdd'
 <br>
 显示年月日时分秒：dateFmt:'yyyyMMdd HH:mm:ss'
 <br>
 格式化:年月日时分秒：dateFmt:'yyyy年MM月dd日 HH时:mm分:ss秒'
 <br>
 设置只读：readOnly:true
 <br>
 隐藏清空按钮：isShowClear:false
 <br>
 设置今天为最小日期：minDate:new Date()
 <br>
 设置今天为最大日期：maxDate:new Date()
 <br>
 设置明天为最小日期：minDate:'%y-%M-{%d+1}'
 <br>
 只能选择本月第一天到最后一天：minDate:'%y-%M-01',maxDate:'%y-%M-%ld'
 <br>
 开始日期不能大于结束日期，且都不能大于今天：
 <br>
 开始日期-maxDate:'#F{$dp.$D(\'endDate\')\|\|\'new Date()\'}'
 <br>
 结束日期-minDate:'#F{$dp.$D(\'startDate\')}',maxDate:new Date()</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/84869461</p>]]></description><guid isPermaLink="false">/archives/ebdd7657-83fc-411b-a9b4-f0779098dd52</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:34:54 GMT</pubDate></item><item><title><![CDATA[Redis 列表(List)]]></title><link>https://wiki.dgfygk.com/archives/85fd4433-b2e8-4606-bb38-f55de1f9a625</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Redis%20%E5%88%97%E8%A1%A8%28List%29&amp;url=/archives/85fd4433-b2e8-4606-bb38-f55de1f9a625" width="1" height="1" alt="" style="opacity:0;">
<h4>Redis的列表是简单的字符串列表，按照插入顺序排序。你可以添加一个元素到列表的头部（左边）或者尾部（右边）。一个列表最多可以包含232 - 1个元素（4294967295，每个列表超过40亿个元素）。</h4>
<h4>实例</h4>
<p>redis 127.0.0.1:6379&gt; LPUSH runoobkey redis
 <br>
 (integer) 1
 <br>
 redis 127.0.0.1:6379&gt; LPUSH runoobkey mongodb
 <br>
 (integer) 2
 <br>
 redis 127.0.0.1:6379&gt; LPUSH runoobkey mysql
 <br>
 (integer) 3
 <br>
 redis 127.0.0.1:6379&gt; LRANGE runoobkey 0 10</p>
<p>1) “mysql”
 <br>
 2) “mongodb”
 <br>
 3) “redis”</p>
<p>在以上实例中我们使用了 <strong>LPUSH</strong> 将三个值插入了名为 <strong>runoobkey</strong> 的列表当中。</p>
<h4>Redis 列表命令</h4>
<p>下表列出了列表相关的基本命令：</p>
<p>| 序号 | 命令及描述 |
 <br>
 |—-|———————————————————————————————————————————————————–|
 <br>
 | 1 | <a href="http://www.runoob.com/redis/lists-blpop.html">BLPOP key1 [key2 ] timeout</a> 移出并获取列表的第一个元素， 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
 <br>
 | 2 | <a href="http://www.runoob.com/redis/lists-brpop.html">BRPOP key1 [key2 ] timeout</a> 移出并获取列表的最后一个元素， 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
 <br>
 | 3 | <a href="http://www.runoob.com/redis/lists-brpoplpush.html">BRPOPLPUSH source destination timeout</a> 从列表中弹出一个值，将弹出的元素插入到另外一个列表中并返回它； 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
 <br>
 | 4 | <a href="http://www.runoob.com/redis/lists-lindex.html">LINDEX key index</a> 通过索引获取列表中的元素 |
 <br>
 | 5 | <a href="http://www.runoob.com/redis/lists-linsert.html">LINSERT key BEFORE\|AFTER pivot value</a> 在列表的元素前或者后插入元素 |
 <br>
 | 6 | <a href="http://www.runoob.com/redis/lists-llen.html">LLEN key</a> 获取列表长度 |
 <br>
 | 7 | <a href="http://www.runoob.com/redis/lists-lpop.html">LPOP key</a> 移出并获取列表的第一个元素 |
 <br>
 | 8 | <a href="http://www.runoob.com/redis/lists-lpush.html">LPUSH key value1 [value2]</a> 将一个或多个值插入到列表头部 |
 <br>
 | 9 | <a href="http://www.runoob.com/redis/lists-lpushx.html">LPUSHX key value</a> 将一个值插入到已存在的列表头部 |
 <br>
 | 10 | <a href="http://www.runoob.com/redis/lists-lrange.html">LRANGE key start stop</a> 获取列表指定范围内的元素 |
 <br>
 | 11 | <a href="http://www.runoob.com/redis/lists-lrem.html">LREM key count value</a> 移除列表元素 |
 <br>
 | 12 | <a href="http://www.runoob.com/redis/lists-lset.html">LSET key index value</a> 通过索引设置列表元素的值 |
 <br>
 | 13 | <a href="http://www.runoob.com/redis/lists-ltrim.html">LTRIM key start stop</a> 对一个列表进行修剪(trim)，就是说，让列表只保留指定区间内的元素，不在指定区间之内的元素都将被删除。 |
 <br>
 | 14 | <a href="http://www.runoob.com/redis/lists-rpop.html">RPOP key</a> 移除列表的最后一个元素，返回值为移除的元素。 |
 <br>
 | 15 | <a href="http://www.runoob.com/redis/lists-rpoplpush.html">RPOPLPUSH source destination</a> 移除列表的最后一个元素，并将该元素添加到另一个列表并返回 |
 <br>
 | 16 | <a href="http://www.runoob.com/redis/lists-rpush.html">RPUSH key value1 [value2]</a> 在列表中添加一个或多个值 |
 <br>
 | 17 | <a href="http://www.runoob.com/redis/lists-rpushx.html">RPUSHX key value</a> 为已存在的列表添加值 |</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/84391800</p>]]></description><guid isPermaLink="false">/archives/85fd4433-b2e8-4606-bb38-f55de1f9a625</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:34:27 GMT</pubDate></item><item><title><![CDATA[缓存在java中的具体使用]]></title><link>https://wiki.dgfygk.com/archives/c635bad5-2ca5-48b4-b1fc-bfc7b37a16ad</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BC%93%E5%AD%98%E5%9C%A8java%E4%B8%AD%E7%9A%84%E5%85%B7%E4%BD%93%E4%BD%BF%E7%94%A8&amp;url=/archives/c635bad5-2ca5-48b4-b1fc-bfc7b37a16ad" width="1" height="1" alt="" style="opacity:0;">
<ul>
 <li>
  <p>缓存，就是将程序或系统经常要调用的对象存在内存中，一遍其使用时可以快速调用，不必再去创建新的重复的实例。</p>
 </li>
 <li>
  <p>这样做可以减少系统开销，提高系统效率。</p>
 </li>
</ul>
<p>缓存主要可分为二大类:</p>
<ul>
 <li>
  <p>通过文件缓存,顾名思义文件缓存是指把数据存储在磁盘上，不管你是以XML格式，序列化文件DAT格式还是其它文件格式；</p>
 </li>
 <li>
  <p>内存缓存，也就是实现一个类中静态Map,对这个Map进行常规的增删查。</p>
 </li>
</ul>
<p>使用缓存需要的包：</p>
<p>import com.google.common.cache.Cache;
 <br>
 import com.google.common.cache.CacheBuilder;
 <br>
 import java.util.concurrent.ExecutionException;
 <br>
 import java.util.concurrent.TimeUnit;</p>
<p>比如：String memberinfo = exampleMapper.selectMemberCountByName(name);在程序中经常要调用，搜索时间1s那么我们就可以把这个搜索的结果存在缓存中，然后每次从缓存中读取结果。</p>
<p>
 <br>
</p>
<p>创建缓存：最大为5 这个可以根据具体情况而定</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F45ce9762e81bcf66790c51c44c3b0bc9.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe5709691158aa3afc71ac448d77c268e.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/85123682</p>]]></description><guid isPermaLink="false">/archives/c635bad5-2ca5-48b4-b1fc-bfc7b37a16ad</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:33:58 GMT</pubDate></item><item><title><![CDATA[如何在maven web项目中配置log4j打印日志到TXT文件及Mybatis sql语句]]></title><link>https://wiki.dgfygk.com/archives/cefdebab-c31e-44c4-9f8d-5e5e6cd42e72</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%A6%82%E4%BD%95%E5%9C%A8maven%20web%E9%A1%B9%E7%9B%AE%E4%B8%AD%E9%85%8D%E7%BD%AElog4j%E6%89%93%E5%8D%B0%E6%97%A5%E5%BF%97%E5%88%B0TXT%E6%96%87%E4%BB%B6%E5%8F%8AMybatis%20sql%E8%AF%AD%E5%8F%A5&amp;url=/archives/cefdebab-c31e-44c4-9f8d-5e5e6cd42e72" width="1" height="1" alt="" style="opacity:0;">
<h4>1、添加依赖</h4>
<p>在pom.xml文件的<code>&lt;dependencies&gt;</code>下添加以下log4j依赖：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F7bf26bb616070611c0fb066a6055e2ee.png&amp;size=m" alt=""></p>
<h4>2 、添加log4j.properties</h4>
<p>在resource目录下添加log4j的配置文件，内容如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F4cb530fe8ae2af53ba0632b73ba2bd34.png&amp;size=m" alt=""></p>
<h4>3 、指定log4j配置文件和监听器</h4>
<p>在web.xml中指定log4j的配置文件，并添加监听器：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2030eb9459023660e5f6723649bb3d5c.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/84584343</p>]]></description><guid isPermaLink="false">/archives/cefdebab-c31e-44c4-9f8d-5e5e6cd42e72</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:33:44 GMT</pubDate></item><item><title><![CDATA[使用mybatis时Mapper.xml文件中如何判断多个参数不为空和null]]></title><link>https://wiki.dgfygk.com/archives/596c8365-f6b1-4b5f-a075-2d17b005aadc</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8mybatis%E6%97%B6Mapper.xml%E6%96%87%E4%BB%B6%E4%B8%AD%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E5%A4%9A%E4%B8%AA%E5%8F%82%E6%95%B0%E4%B8%8D%E4%B8%BA%E7%A9%BA%E5%92%8Cnull&amp;url=/archives/596c8365-f6b1-4b5f-a075-2d17b005aadc" width="1" height="1" alt="" style="opacity:0;">
<h4>在工作中遇到在mybatis中判断两个集合是否为空的条件，原本可以写一个provider去实现，还是尝试在xml中实现。直接上代码：</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F0a738b583d128087473741d9bfb699ed.png&amp;size=m" alt=""></p>
<p>其实，在xml中判断多个参数不为空和null有以下两种方式，具体操作如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fc6a1a3354f8afb6146198b04ee4b1b82.png&amp;size=m" alt=""></p>
<p>Mybatis中where 标签知道只有在一个以上的if条件有值的情况下才去插入"where"子句,若最后的内容是"and"或"or"开头的，where 标签会知道如何将他们去除</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F700bdda5a7f85e5d45c7bdaab36552f6.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/84977522</p>]]></description><guid isPermaLink="false">/archives/596c8365-f6b1-4b5f-a075-2d17b005aadc</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:30:45 GMT</pubDate></item><item><title><![CDATA[java 从内容中或（TXT文本）提取手机号，并用正则判断手机号]]></title><link>https://wiki.dgfygk.com/archives/e1573ef3-5fbb-4375-9176-386dc4fa4e9e</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%20%E4%BB%8E%E5%86%85%E5%AE%B9%E4%B8%AD%E6%88%96%EF%BC%88TXT%E6%96%87%E6%9C%AC%EF%BC%89%E6%8F%90%E5%8F%96%E6%89%8B%E6%9C%BA%E5%8F%B7%EF%BC%8C%E5%B9%B6%E7%94%A8%E6%AD%A3%E5%88%99%E5%88%A4%E6%96%AD%E6%89%8B%E6%9C%BA%E5%8F%B7&amp;url=/archives/e1573ef3-5fbb-4375-9176-386dc4fa4e9e" width="1" height="1" alt="" style="opacity:0;">
<p>// 从内容中提取手机号
 <br>
 public static List<string> getPhone(String content){</string></p>
<p>String regex = “^((13[0-9])\|(14[5,7])\|(15[0-3,5-9])\|(17[0,3,5-8])\|(18[0-9])\|166\|198\|199\|(147))\\d{8}$“;
 <br>
 List<string> result = new ArrayList&lt;&gt;(); 
  <br>if(StringUtils.isBlank(content)){</string></p>
<p>}
 <br>
 Pattern pattern = Pattern.compile(“(?<!--\\d)(?:(?:1[358]\\d{9})\|(?:861[358]\\d{9}))(?!\\d)&ldquo;);
<br  /-->Matcher matcher = pattern.matcher(content);
 <br>
 StringBuffer bf = new StringBuffer(64);
 <br>
 while (matcher.find()) {</p>
<p>bf.append(matcher.group()).append(“,“);
 <br>
 }
 <br>
 int len = bf.length();
 <br>
 if (len &gt; 0) {</p>
<p>bf.deleteCharAt(len - 1);
 <br>
 String phone = bf.toString();
 <br>
 String [] phoneSplit = phone.split(“,“);
 <br>
 for(String telephone : phoneSplit){</p>
<p>boolean isTruePhone = Pattern.matches(regex, telephone);
 <br>
 if(isTruePhone){</p>
<p>result.add(telephone);
 <br>
 System.out.println(telephone);
 <br>
 }
 <br>
 }
 <br>
 }
 <br>
 return result;
 <br>
 }</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/83867593</p>]]></description><guid isPermaLink="false">/archives/e1573ef3-5fbb-4375-9176-386dc4fa4e9e</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:26:10 GMT</pubDate></item><item><title><![CDATA[list集合的分页]]></title><link>https://wiki.dgfygk.com/archives/c16e928f-f307-408a-89b6-18ace1dbead0</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=list%E9%9B%86%E5%90%88%E7%9A%84%E5%88%86%E9%A1%B5&amp;url=/archives/c16e928f-f307-408a-89b6-18ace1dbead0" width="1" height="1" alt="" style="opacity:0;">
<p>list集合分页有两种方法（1）根据subList方法进行分组，（2）自定义方法使用"循环截取某页列表进行分页”</p>
<p>// List<e> subList(int fromIndex, int toIndex); 
  <br>// 它返回原来list的从[fromIndex, toIndex)之间这一部分的视图，之所以说是视图，是因为实际上，返回的list是靠原来的list支持的。 
  <br>// 返回一个以fromIndex为起始索引（包含），以toIndex为终止索引（不包含）的子列表（List）。 
  <br>// 比如集合中的内容为1,2,3,4,5 
  <br>// list.sublist(2,4) 
  <br>// 就返回一个子集合：它的内容包括从下标为2到下标为4,而且这是左闭右开的 
  <br>// 就是说是从大于等于2到小于4 
  <br>// 那子集内容就是3,4(集合的下标都是从0开始)； 
  <br>/** 
  <br>* 利用subList方法进行分页 
  <br>* @param list 分页数据 
  <br>* @param pagesize 页面大小 
  <br>* @param currentPage 当前页面 
  <br>*/ 
  <br>public static List pageBySubList(List list, int pagesize, int currentPage) {</e></p>
<p>int totalcount = list.size();
 <br>
 int pagecount = 0;
 <br>
 List subList;
 <br>
 int m = totalcount % pagesize;
 <br>
 if (m &gt; 0) {</p>
<p>pagecount = totalcount / pagesize + 1;
 <br>
 } else {</p>
<p>pagecount = totalcount / pagesize;
 <br>
 }
 <br>
 if (m == 0) {</p>
<p>subList = list.subList((currentPage - 1) * pagesize, pagesize * (currentPage));
 <br>
 } else {</p>
<p>if (currentPage == pagecount) {</p>
<p>subList = list.subList((currentPage - 1) * pagesize, totalcount);
 <br>
 } else {</p>
<p>subList = list.subList((currentPage - 1) * pagesize, pagesize * (currentPage));
 <br>
 }
 <br>
 }
 <br>
 return subList;
 <br>
 }</p>
<p>/**
 <br>
 * 循环截取某页列表进行分页
 <br>
 * @param dataList 分页数据
 <br>
 * @param pageSize 页面大小
 <br>
 * @param currentPage 当前页面
 <br>
 */
 <br>
 public static List
 <string>
  page(List<string> dataList, int pageSize,int currentPage) {</string>
 </string></p>
<p>List<string> currentPageList = new ArrayList&lt;&gt;(); 
  <br>if (dataList != null \&amp;\&amp; dataList.size() &gt; 0) {</string></p>
<p>int currIdx = (currentPage &gt; 1 ? (currentPage - 1) * pageSize : 0);
 <br>
 for (int i = 0; i &lt; pageSize \&amp;\&amp; i &lt; dataList.size() - currIdx; i++) {</p>
<p>String data = dataList.get(currIdx + i);
 <br>
 currentPageList.add(data);
 <br>
 }
 <br>
 }
 <br>
 return currentPageList;
 <br>
 }</p>
<p>main方法中的代码，自己定义list集合</p>
<p>System.out.println(“对合并的list分页：“);
 <br>
 List subList;
 <br>
 subList = pageBySubList(enterPriseSupplierInfoDtoList,3,2);
 <br>
 System.out.println(subList.toString());</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F17ea3a0267345fbdaed874c6d131971d.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/83867378</p>]]></description><guid isPermaLink="false">/archives/c16e928f-f307-408a-89b6-18ace1dbead0</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:25:48 GMT</pubDate></item><item><title><![CDATA[Java8-用Lambda表达式给List集合排序]]></title><link>https://wiki.dgfygk.com/archives/97397218-1b87-4ae9-bda0-2b1d481e588f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java8-%E7%94%A8Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%BB%99List%E9%9B%86%E5%90%88%E6%8E%92%E5%BA%8F&amp;url=/archives/97397218-1b87-4ae9-bda0-2b1d481e588f" width="1" height="1" alt="" style="opacity:0;">
<p>String createTime1 = “2018-12-13 12:10:10.120”;
 <br>
 String createTime2 = “2018-11-12 14:10:10.120”;
 <br>
 String createTime3 = “2018-10-12 12:10:10.120”;
 <br>
 String createTime4 = “2018-12-12 19:10:10.120”;
 <br>
 String createTime5 = “2018-05-12 18:10:10.120”;
 <br>
 String createTime6 = “2018-11-01 15:10:10.120”;
 <br>
 String createTime7 = “2018-10-01 13:10:10.120”;
 <br>
 String format1 = LocalDateTime.parse(createTime1.substring(0, createTime1.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format2 = LocalDateTime.parse(createTime2.substring(0, createTime2.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format3 = LocalDateTime.parse(createTime3.substring(0, createTime3.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format4 = LocalDateTime.parse(createTime4.substring(0, createTime4.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format5 = LocalDateTime.parse(createTime5.substring(0, createTime5.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format6 = LocalDateTime.parse(createTime6.substring(0, createTime6.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format7 = LocalDateTime.parse(createTime7.substring(0, createTime7.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 double scoreTime1 = Double.parseDouble(“0.” + format1);
 <br>
 double scoreTime2 = Double.parseDouble(“0.” + format2);
 <br>
 double scoreTime3 = Double.parseDouble(“0.” + format3);
 <br>
 double scoreTime4 = Double.parseDouble(“0.” + format4);
 <br>
 double scoreTime5 = Double.parseDouble(“0.” + format5);
 <br>
 double scoreTime6 = Double.parseDouble(“0.” + format6);
 <br>
 double scoreTime7 = Double.parseDouble(“0.” + format7);
 <br>
 // System.out.println(scoreTime);
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto2 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto3 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto4 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto5 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto6 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto7 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto8 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto9 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto10 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto11 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto12 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto13 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto14 = new ExcellentSupplierInfoDto();
 <br>
 List
 <excellentsupplierinfodto>
  enterPriseSupplierInfoDtoList = new ArrayList&lt;&gt;();
  <br>
  List<excellentsupplierinfodto> InfoDtoList = new ArrayList&lt;&gt;(); 
   <br>excellentSupplierInfoDto.setScore(10); 
   <br>excellentSupplierInfoDto.setTime(scoreTime1); 
   <br>excellentSupplierInfoDto.setLevel(1); 
   <br>excellentSupplierInfoDto2.setScore(15); 
   <br>excellentSupplierInfoDto2.setTime(scoreTime2); 
   <br>excellentSupplierInfoDto2.setLevel(1); 
   <br>excellentSupplierInfoDto3.setScore(8); 
   <br>excellentSupplierInfoDto3.setTime(scoreTime3); 
   <br>excellentSupplierInfoDto3.setLevel(1); 
   <br>excellentSupplierInfoDto4.setScore(10); 
   <br>excellentSupplierInfoDto4.setTime(scoreTime4); 
   <br>excellentSupplierInfoDto4.setLevel(1); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto2); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto3); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto4); 
   <br>excellentSupplierInfoDto5.setScore(2); 
   <br>excellentSupplierInfoDto5.setTime(scoreTime5); 
   <br>excellentSupplierInfoDto5.setLevel(0); 
   <br>excellentSupplierInfoDto6.setScore(6); 
   <br>excellentSupplierInfoDto6.setTime(scoreTime6); 
   <br>excellentSupplierInfoDto6.setLevel(0); 
   <br>excellentSupplierInfoDto7.setScore(9); 
   <br>excellentSupplierInfoDto7.setTime(scoreTime7); 
   <br>excellentSupplierInfoDto7.setLevel(0); 
   <br>excellentSupplierInfoDto8.setScore(20); 
   <br>excellentSupplierInfoDto8.setTime(0.1812011013); 
   <br>excellentSupplierInfoDto8.setLevel(0); 
   <br>excellentSupplierInfoDto9.setScore(4); 
   <br>excellentSupplierInfoDto9.setTime(0.1812011015); 
   <br>excellentSupplierInfoDto9.setLevel(0); 
   <br>excellentSupplierInfoDto10.setScore(89); 
   <br>excellentSupplierInfoDto10.setTime(0.1812011016); 
   <br>excellentSupplierInfoDto10.setLevel(0); 
   <br>excellentSupplierInfoDto11.setScore(5); 
   <br>excellentSupplierInfoDto11.setTime(0.1812011017); 
   <br>excellentSupplierInfoDto11.setLevel(0); 
   <br>excellentSupplierInfoDto12.setScore(8); 
   <br>excellentSupplierInfoDto12.setTime(0.1812011017); 
   <br>excellentSupplierInfoDto12.setLevel(0); 
   <br>excellentSupplierInfoDto13.setScore(12); 
   <br>excellentSupplierInfoDto13.setTime(0.1812011011); 
   <br>excellentSupplierInfoDto13.setLevel(0); 
   <br>excellentSupplierInfoDto14.setScore(20); 
   <br>excellentSupplierInfoDto14.setTime(0.1812011018); 
   <br>excellentSupplierInfoDto14.setLevel(0); 
   <br>InfoDtoList.add(excellentSupplierInfoDto5); 
   <br>InfoDtoList.add(excellentSupplierInfoDto6); 
   <br>InfoDtoList.add(excellentSupplierInfoDto7); 
   <br>InfoDtoList.add(excellentSupplierInfoDto8); 
   <br>InfoDtoList.add(excellentSupplierInfoDto9); 
   <br>InfoDtoList.add(excellentSupplierInfoDto10); 
   <br>InfoDtoList.add(excellentSupplierInfoDto11); 
   <br>InfoDtoList.add(excellentSupplierInfoDto12); 
   <br>InfoDtoList.add(excellentSupplierInfoDto13); 
   <br>InfoDtoList.add(excellentSupplierInfoDto14);</excellentsupplierinfodto>
 </excellentsupplierinfodto></p>
<p>//先根据score排序，如果score相等再根据time排序</p>
<p>++enterPriseSupplierInfoDtoList = enterPriseSupplierInfoDtoList.stream().sorted(Comparator.comparing(ExcellentSupplierInfoDto::getScore)
 <br>
 .reversed().thenComparing(ExcellentSupplierInfoDto::getTime)).collect(Collectors.toList());++
 <br>
 System.out.println(“已经排序的list:“);
 <br>
 for (ExcellentSupplierInfoDto supplierInfoDto : enterPriseSupplierInfoDtoList) {</p>
<p>System.out.println(supplierInfoDto.toString());
 <br>
 }
 <br>
 System.out.println(“未排序的list:“);
 <br>
 for (ExcellentSupplierInfoDto supplierInfo : InfoDtoList) {</p>
<p>System.out.println(supplierInfo.toString());
 <br>
 }</p>
<p>enterPriseSupplierInfoDtoList.addAll(InfoDtoList);
 <br>
 System.out.println(“合并后的排序的list:“);
 <br>
 for (ExcellentSupplierInfoDto supplierInfo : enterPriseSupplierInfoDtoList) {</p>
<p>System.out.println(supplierInfo.toString());
 <br>
 }</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F685f202ece5ba79afdb7e5a8d69ffe40.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/83867477</p>]]></description><guid isPermaLink="false">/archives/97397218-1b87-4ae9-bda0-2b1d481e588f</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:23:39 GMT</pubDate></item><item><title><![CDATA[long（Long）与int（Integer）之间的转换]]></title><link>https://wiki.dgfygk.com/archives/0ce8e27b-e5a5-4e7c-8525-737dad9c1cf1</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=long%EF%BC%88Long%EF%BC%89%E4%B8%8Eint%EF%BC%88Integer%EF%BC%89%E4%B9%8B%E9%97%B4%E7%9A%84%E8%BD%AC%E6%8D%A2&amp;url=/archives/0ce8e27b-e5a5-4e7c-8525-737dad9c1cf1" width="1" height="1" alt="" style="opacity:0;">
<p>一、将long型转化为int型，这里的long型是基础类型：</p>
<pre><code>long   a = 10;     
int b = (int)a;   
</code></pre>
<p>二、将Long型转换为int型，这里的Long型是包装类型：</p>
<pre><code>Long a = 10;
int b=a.intValue();
</code></pre>
<p>三、将int型转化为long型，这里的int型是基础类型：</p>
<pre><code>int a = 10;
long b = (int)a;
</code></pre>
<p>四、将Integer型转化为long型，这里的Integer型是包装类型：</p>
<pre><code>int a = 10;
Long b = a.longValue();
</code></pre>
<p>五、还有其它的方法，比如包装类型一般都有parseLong的parseXXX的静态方法，不过一般只能传String类型。</p>
<p>总结：这里的转化就是要注意java的八种基础数据类型以及八种包装数据类型的区别。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/82425187</p>]]></description><guid isPermaLink="false">/archives/0ce8e27b-e5a5-4e7c-8525-737dad9c1cf1</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:23:23 GMT</pubDate></item><item><title><![CDATA[mysql 使用sql语句去掉某个字段中的html标签]]></title><link>https://wiki.dgfygk.com/archives/078a1b8c-9375-4c03-8f03-5c27eaf56459</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=mysql%20%E4%BD%BF%E7%94%A8sql%E8%AF%AD%E5%8F%A5%E5%8E%BB%E6%8E%89%E6%9F%90%E4%B8%AA%E5%AD%97%E6%AE%B5%E4%B8%AD%E7%9A%84html%E6%A0%87%E7%AD%BE&amp;url=/archives/078a1b8c-9375-4c03-8f03-5c27eaf56459" width="1" height="1" alt="" style="opacity:0;">
<p>在给公司的产品查询数据的过程中，要求去掉字段里的一些html标签。</p>
<p>下面就是一个处理html标签的函数。可以用批量修改的脚本加这个函数，将标签去掉。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F26933fdbc6169938bc21d0e0e0a4ae48.png&amp;size=m" alt=""></p>
<h4>查询前后的对比如下</h4>
<p>没有使用上面函数的查询结果：</p>
<p>select a.title,a.areaid,a.progid,b.content,FROM_UNIXTIME(a.updatetime) from table_content a;</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff9a068426da1c3a521c9f231adce1890.png&amp;size=m" alt=""></p>
<p>使用去除HTML标签函数后的结果：</p>
<p>select a.title as '标题',a.areaid as '地区',a.progid as '类型',fnStripTags(b.content) as '内容',FROM_UNIXTIME(a.updatetime) as '时间' from table_content a;</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F72e1bbfc7b218d4d0c1c8f556f7b5bef.png&amp;size=m" alt=""></p>
<p>很显然调用函数之后，凡是有HTML标签的东西都被清楚了；如果在调用函数之后，除了”\&amp;nbsp”，其余的标签基本被去除掉了。如果要去除”\&amp;nbsp”，用replace(enterprise_profile,'\&amp;nbsp','')即可。</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/84065399</p>]]></description><guid isPermaLink="false">/archives/078a1b8c-9375-4c03-8f03-5c27eaf56459</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 06:23:06 GMT</pubDate></item><item><title><![CDATA[MySQL导入‘.frm’、‘.MYD’、‘.MYI’文件]]></title><link>https://wiki.dgfygk.com/archives/03d33d6e-c3f8-4f30-b62a-e51f4842d344</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=MySQL%E5%AF%BC%E5%85%A5%E2%80%98.frm%E2%80%99%E3%80%81%E2%80%98.MYD%E2%80%99%E3%80%81%E2%80%98.MYI%E2%80%99%E6%96%87%E4%BB%B6&amp;url=/archives/03d33d6e-c3f8-4f30-b62a-e51f4842d344" width="1" height="1" alt="" style="opacity:0;">
<p>.frm、.myd、.myi文件，也就是说是MySQL的原始数据文件，这三个文件分别是：</p>
<ul>
 <li>.frm 表结构文件</li>
 <li>.myd 表数据文件</li>
 <li>.myi 表索引文件</li>
</ul>
<p>首先，如果你知道这个数据的表的结构，就用数据库工具创建一样和该数据结构一样的表，表名应该和文件的名称保持一致。然后找到数据库的文件存放的data目录，比如：E:\mysql-5.7.18-winx64\mysql-5.7.18-winx64\data</p>
<p>打开目录你会看到：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3ba5a578aec00562562f097aa5b9cf61.png&amp;size=m" alt=""></p>
<p>然后将你的三个.frm、.myd、.myi文件copy到你创建的文件夹下。最后重启mysql服务即可。</p>
<p>如果你不知都数据库表结构，没有办法创建数据库表，接着看下面的方法：</p>
<p>方法，如下：</p>
<ol>
 <li>新建一个数据库</li>
 <li>在my.ini 中找到 datadir ，指名了数据库文件的存储位置也即是：E:\mysql-5.7.18-winx64\mysql-5.7.18-winx64\data，然后打开这个文件夹</li>
 <li>在里面新建一个文件夹，文件夹的名称和刚建的库名一样</li>
 <li>把三个文件复制进去</li>
 <li>重启一下mysql服务，刷新一下即可。</li>
</ol>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/82804954</p>]]></description><guid isPermaLink="false">/archives/03d33d6e-c3f8-4f30-b62a-e51f4842d344</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:22:45 GMT</pubDate></item><item><title><![CDATA[Timestamp和LocalDateTime 互转]]></title><link>https://wiki.dgfygk.com/archives/b74c7175-26bd-434b-a720-824b6a4c4b32</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Timestamp%E5%92%8CLocalDateTime%20%E4%BA%92%E8%BD%AC&amp;url=/archives/b74c7175-26bd-434b-a720-824b6a4c4b32" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F9ad8cc3d9b2ea55cd5053f974ec26c1e.png&amp;size=m" alt=""></p>
<p>结果是这个样子的：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff8d805a3f1e59457e76264be071a35fc.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/82428610</p>]]></description><guid isPermaLink="false">/archives/b74c7175-26bd-434b-a720-824b6a4c4b32</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:18:30 GMT</pubDate></item><item><title><![CDATA[把一个list集合合并到另一个list集合中]]></title><link>https://wiki.dgfygk.com/archives/3521f87f-06d8-474b-b278-d4ffb0df2b67</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8A%8A%E4%B8%80%E4%B8%AAlist%E9%9B%86%E5%90%88%E5%90%88%E5%B9%B6%E5%88%B0%E5%8F%A6%E4%B8%80%E4%B8%AAlist%E9%9B%86%E5%90%88%E4%B8%AD&amp;url=/archives/3521f87f-06d8-474b-b278-d4ffb0df2b67" width="1" height="1" alt="" style="opacity:0;">
<p>String createTime1 = “2018-12-13 12:10:10.120”;
 <br>
 String createTime2 = “2018-11-12 14:10:10.120”;
 <br>
 String createTime3 = “2018-10-12 12:10:10.120”;
 <br>
 String createTime4 = “2018-12-12 19:10:10.120”;
 <br>
 String createTime5 = “2018-05-12 18:10:10.120”;
 <br>
 String createTime6 = “2018-11-01 15:10:10.120”;
 <br>
 String createTime7 = “2018-10-01 13:10:10.120”;
 <br>
 String format1 = LocalDateTime.parse(createTime1.substring(0, createTime1.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format2 = LocalDateTime.parse(createTime2.substring(0, createTime2.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format3 = LocalDateTime.parse(createTime3.substring(0, createTime3.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format4 = LocalDateTime.parse(createTime4.substring(0, createTime4.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format5 = LocalDateTime.parse(createTime5.substring(0, createTime5.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format6 = LocalDateTime.parse(createTime6.substring(0, createTime6.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 String format7 = LocalDateTime.parse(createTime7.substring(0, createTime7.indexOf(“.“)).replace(” “, “T”)).format(DF);
 <br>
 double scoreTime1 = Double.parseDouble(“0.” + format1);
 <br>
 double scoreTime2 = Double.parseDouble(“0.” + format2);
 <br>
 double scoreTime3 = Double.parseDouble(“0.” + format3);
 <br>
 double scoreTime4 = Double.parseDouble(“0.” + format4);
 <br>
 double scoreTime5 = Double.parseDouble(“0.” + format5);
 <br>
 double scoreTime6 = Double.parseDouble(“0.” + format6);
 <br>
 double scoreTime7 = Double.parseDouble(“0.” + format7);
 <br>
 // System.out.println(scoreTime);
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto2 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto3 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto4 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto5 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto6 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto7 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto8 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto9 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto10 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto11 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto12 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto13 = new ExcellentSupplierInfoDto();
 <br>
 ExcellentSupplierInfoDto excellentSupplierInfoDto14 = new ExcellentSupplierInfoDto();
 <br>
 List
 <excellentsupplierinfodto>
  enterPriseSupplierInfoDtoList = new ArrayList&lt;&gt;();
  <br>
  List<excellentsupplierinfodto> InfoDtoList = new ArrayList&lt;&gt;(); 
   <br>excellentSupplierInfoDto.setScore(10); 
   <br>excellentSupplierInfoDto.setTime(scoreTime1); 
   <br>excellentSupplierInfoDto.setLevel(1); 
   <br>excellentSupplierInfoDto2.setScore(15); 
   <br>excellentSupplierInfoDto2.setTime(scoreTime2); 
   <br>excellentSupplierInfoDto2.setLevel(1); 
   <br>excellentSupplierInfoDto3.setScore(8); 
   <br>excellentSupplierInfoDto3.setTime(scoreTime3); 
   <br>excellentSupplierInfoDto3.setLevel(1); 
   <br>excellentSupplierInfoDto4.setScore(10); 
   <br>excellentSupplierInfoDto4.setTime(scoreTime4); 
   <br>excellentSupplierInfoDto4.setLevel(1); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto2); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto3); 
   <br>enterPriseSupplierInfoDtoList.add(excellentSupplierInfoDto4); 
   <br>excellentSupplierInfoDto5.setScore(2); 
   <br>excellentSupplierInfoDto5.setTime(scoreTime5); 
   <br>excellentSupplierInfoDto5.setLevel(0); 
   <br>excellentSupplierInfoDto6.setScore(6); 
   <br>excellentSupplierInfoDto6.setTime(scoreTime6); 
   <br>excellentSupplierInfoDto6.setLevel(0); 
   <br>excellentSupplierInfoDto7.setScore(9); 
   <br>excellentSupplierInfoDto7.setTime(scoreTime7); 
   <br>excellentSupplierInfoDto7.setLevel(0); 
   <br>excellentSupplierInfoDto8.setScore(20); 
   <br>excellentSupplierInfoDto8.setTime(0.1812011013); 
   <br>excellentSupplierInfoDto8.setLevel(0); 
   <br>excellentSupplierInfoDto9.setScore(4); 
   <br>excellentSupplierInfoDto9.setTime(0.1812011015); 
   <br>excellentSupplierInfoDto9.setLevel(0); 
   <br>excellentSupplierInfoDto10.setScore(89); 
   <br>excellentSupplierInfoDto10.setTime(0.1812011016); 
   <br>excellentSupplierInfoDto10.setLevel(0); 
   <br>excellentSupplierInfoDto11.setScore(5); 
   <br>excellentSupplierInfoDto11.setTime(0.1812011017); 
   <br>excellentSupplierInfoDto11.setLevel(0); 
   <br>excellentSupplierInfoDto12.setScore(8); 
   <br>excellentSupplierInfoDto12.setTime(0.1812011017); 
   <br>excellentSupplierInfoDto12.setLevel(0); 
   <br>excellentSupplierInfoDto13.setScore(12); 
   <br>excellentSupplierInfoDto13.setTime(0.1812011011); 
   <br>excellentSupplierInfoDto13.setLevel(0); 
   <br>excellentSupplierInfoDto14.setScore(20); 
   <br>excellentSupplierInfoDto14.setTime(0.1812011018); 
   <br>excellentSupplierInfoDto14.setLevel(0); 
   <br>InfoDtoList.add(excellentSupplierInfoDto5); 
   <br>InfoDtoList.add(excellentSupplierInfoDto6); 
   <br>InfoDtoList.add(excellentSupplierInfoDto7); 
   <br>InfoDtoList.add(excellentSupplierInfoDto8); 
   <br>InfoDtoList.add(excellentSupplierInfoDto9); 
   <br>InfoDtoList.add(excellentSupplierInfoDto10); 
   <br>InfoDtoList.add(excellentSupplierInfoDto11); 
   <br>InfoDtoList.add(excellentSupplierInfoDto12); 
   <br>InfoDtoList.add(excellentSupplierInfoDto13); 
   <br>InfoDtoList.add(excellentSupplierInfoDto14);</excellentsupplierinfodto>
 </excellentsupplierinfodto></p>
<p>enterPriseSupplierInfoDtoList.addAll(InfoDtoList);
 <br>
 System.out.println(“合并后的排序的list:“);
 <br>
 for (ExcellentSupplierInfoDto supplierInfo : enterPriseSupplierInfoDtoList) {</p>
<p>System.out.println(supplierInfo.toString());
 <br>
 }</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/83867335</p>]]></description><guid isPermaLink="false">/archives/3521f87f-06d8-474b-b278-d4ffb0df2b67</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:18:15 GMT</pubDate></item><item><title><![CDATA[关于mysql错误：Subquery returns more than 1 row]]></title><link>https://wiki.dgfygk.com/archives/47eae7df-fa20-418a-b801-4a40f62a7952</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%B3%E4%BA%8Emysql%E9%94%99%E8%AF%AF%EF%BC%9ASubquery%20returns%20more%20than%201%20row&amp;url=/archives/47eae7df-fa20-418a-b801-4a40f62a7952" width="1" height="1" alt="" style="opacity:0;">
<p>EXPLAIN select * from mytable where shouji = (select shouji from mytable GROUP BY shouji HAVING count(shouji)&gt;2)
 <br>
 &gt; 1242 - Subquery returns more than 1 row
 <br>
 &gt; 时间: 0.002s</p>
<p>这个错误是：子查询返回了多行数据</p>
<p>解决方法：</p>
<p>以</p>
<p>select * from mytable where shouji = (select shouji from mytable GROUP BY shouji HAVING count(shouji)&gt;2);</p>
<p>为例。</p>
<p>1）在子查询条件语句加limit 1,找到一个符合条件的就可以了
 <br>
 select * from mytable where shouji = (select shouji from mytable GROUP BY shouji HAVING count(shouji)&gt;2 limit 1);
 <br>
 3）在子查询前加any关键字
 <br>
 select * from mytable where shouji = any(select shouji from mytable GROUP BY shouji HAVING count(shouji)&gt;2);</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/82967902</p>]]></description><guid isPermaLink="false">/archives/47eae7df-fa20-418a-b801-4a40f62a7952</guid><dc:creator>黄健</dc:creator><category>数据库</category><pubDate>Wed, 25 Sep 2024 06:17:51 GMT</pubDate></item><item><title><![CDATA[使用LocalDateTime计算两个时间的差]]></title><link>https://wiki.dgfygk.com/archives/d8d13861-fe8e-4e11-8bd6-a2f0e8b1c747</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%BF%E7%94%A8LocalDateTime%E8%AE%A1%E7%AE%97%E4%B8%A4%E4%B8%AA%E6%97%B6%E9%97%B4%E7%9A%84%E5%B7%AE&amp;url=/archives/d8d13861-fe8e-4e11-8bd6-a2f0e8b1c747" width="1" height="1" alt="" style="opacity:0;">
<pre><code class="java">LocalDateTime now = LocalDateTime.now();
System.out.println("计算两个时间的差：");
LocalDateTime end = LocalDateTime.now();
Duration duration = Duration.between(now,end);
long days = duration.toDays(); //相差的天数
long hours = duration.toHours();//相差的小时数
long minutes = duration.toMinutes();//相差的分钟数
long millis = duration.toMillis();//相差毫秒数
long nanos = duration.toNanos();//相差的纳秒数
System.out.println(now);
System.out.println(end);

System.out.println("发送短信耗时【 "+days+"天："+hours+" 小时："+minutes+" 分钟："+millis+" 毫秒："+nanos+" 纳秒】");
</code></pre>
<p>
 <br>
</p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/83867265</p>]]></description><guid isPermaLink="false">/archives/d8d13861-fe8e-4e11-8bd6-a2f0e8b1c747</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:17:34 GMT</pubDate></item><item><title><![CDATA[java 如何 判断指定的数字或者串是否是纯数字]]></title><link>https://wiki.dgfygk.com/archives/5f1a63d6-d186-4472-af20-7c56e099eaea</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%20%E5%A6%82%E4%BD%95%20%E5%88%A4%E6%96%AD%E6%8C%87%E5%AE%9A%E7%9A%84%E6%95%B0%E5%AD%97%E6%88%96%E8%80%85%E4%B8%B2%E6%98%AF%E5%90%A6%E6%98%AF%E7%BA%AF%E6%95%B0%E5%AD%97&amp;url=/archives/5f1a63d6-d186-4472-af20-7c56e099eaea" width="1" height="1" alt="" style="opacity:0;">
<p>方法一：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fec2f07f8f5790b29f8d7ec21f5f4d5e8.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fedf42459f3207ab478f13d2d86abf887.png&amp;size=m" alt=""></p>
<p>输出的结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F8fe62abfaa44af5f00459ff57c199eda.png&amp;size=m" alt=""></p>
<p>方法二：可以用java自带的函数去判断</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F1e572deb1a30658b84cb629dda7cba1d.png&amp;size=m" alt=""></p>
<p>或者用下面的方法：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fe2dd14900d5b0bf2d67579f40635a543.png&amp;size=m" alt=""></p>
<p>方法三：可以使用NumberUtils工具类的isNumber()或者isDigits()方法进行判断</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fb4edf65c0c4f1cb9548a6f51205bb6ed.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Ff611bbf22d367a42a79f537e0055aeba.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/82424798</p>]]></description><guid isPermaLink="false">/archives/5f1a63d6-d186-4472-af20-7c56e099eaea</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:14:25 GMT</pubDate></item><item><title><![CDATA[Java 如何判断手机号的运营商，是移动、联通、还是电信？]]></title><link>https://wiki.dgfygk.com/archives/29ddd459-d246-40aa-bbe1-5733b98e78e6</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%20%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E6%89%8B%E6%9C%BA%E5%8F%B7%E7%9A%84%E8%BF%90%E8%90%A5%E5%95%86%EF%BC%8C%E6%98%AF%E7%A7%BB%E5%8A%A8%E3%80%81%E8%81%94%E9%80%9A%E3%80%81%E8%BF%98%E6%98%AF%E7%94%B5%E4%BF%A1%EF%BC%9F&amp;url=/archives/29ddd459-d246-40aa-bbe1-5733b98e78e6" width="1" height="1" alt="" style="opacity:0;">
<p>今天遇到一个问题，就是群发短信，但是短信平台的通道不支持电信手机号的发送。于是 就是要在手机号提交给平台钱先把电信的手机号给过滤掉。然后我就在百度上搜了一下。挺简单，但是还是做一下记录，与卿共享！</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F18747e0dbb153c7211bfb3ec61ed19d3.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F56c7effc47522d9d9086310a767c2f87.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F96030909d9f086e8a19407f62230e03c.png&amp;size=m" alt=""></p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd1030a518f560108c78433d73f2708c2.png&amp;size=m" alt=""></p>
<p>运行结果：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fa24a2fe4c4425a4b69108fba96fa7151.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/81748433</p>]]></description><guid isPermaLink="false">/archives/29ddd459-d246-40aa-bbe1-5733b98e78e6</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:13:55 GMT</pubDate></item><item><title><![CDATA[java 使用maven架包Poi来读取Excel(xlsx与xls)]]></title><link>https://wiki.dgfygk.com/archives/b4380789-c4b9-462d-bc16-e42783798cec</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%20%E4%BD%BF%E7%94%A8maven%E6%9E%B6%E5%8C%85Poi%E6%9D%A5%E8%AF%BB%E5%8F%96Excel%28xlsx%E4%B8%8Exls%29&amp;url=/archives/b4380789-c4b9-462d-bc16-e42783798cec" width="1" height="1" alt="" style="opacity:0;">
<h4>参考了</h4>
<h4><a href="http://kxjhlele.iteye.com/blog/321392">http://kxjhlele.iteye.com/blog/321392</a></h4>
<h4>上面有说明引入那些包的，但没有说明maven怎么配置，一般会以为maven配置时引入poi就行，试了一下，发现引入后不会自动加入另外需要的包，难道非要额外自己引入其他需要的包不可吗？</h4>
<h4>不是的，试了一下，其实不引入poi而是引入poi-ooxml就会自动引入操作xlsx文件所用到的其他包。</h4>
<h4>具体pom配置是：</h4>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F2dc5c43b8e46860a30d6de738fb73bea.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/81589815</p>]]></description><guid isPermaLink="false">/archives/b4380789-c4b9-462d-bc16-e42783798cec</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 06:13:33 GMT</pubDate></item><item><title><![CDATA[Java8 LocalDateTime获取当前时间(戳)]]></title><link>https://wiki.dgfygk.com/archives/02a8d1b4-6789-41d7-9899-7a71d7a16b5f</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java8%20LocalDateTime%E8%8E%B7%E5%8F%96%E5%BD%93%E5%89%8D%E6%97%B6%E9%97%B4%28%E6%88%B3%29&amp;url=/archives/02a8d1b4-6789-41d7-9899-7a71d7a16b5f" width="1" height="1" alt="" style="opacity:0;">
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2Fd05a2d200e3ee822a19829fdcbe55df6.png&amp;size=m" alt=""></p>
<p>以上都是获取当前时间的时间戳。而要想使用localdateTime获取当前时间，只需要LocalDateTime.now()即可。</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F675e1e9962209368941a0b5eea118098.png&amp;size=m" alt=""></p>
<p>结果如下：</p>
<p><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fi-blog.csdnimg.cn%2Fblog_migrate%2F3d2b8e207eefad15c747accafccfe813.png&amp;size=m" alt=""></p>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/82424299</p>]]></description><guid isPermaLink="false">/archives/02a8d1b4-6789-41d7-9899-7a71d7a16b5f</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:09:44 GMT</pubDate></item><item><title><![CDATA[Java8 如何获取当天的开始和结束时间]]></title><link>https://wiki.dgfygk.com/archives/7c00948d-8fb6-4db2-8f4f-a8fc04f779cb</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java8%20%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E5%BD%93%E5%A4%A9%E7%9A%84%E5%BC%80%E5%A7%8B%E5%92%8C%E7%BB%93%E6%9D%9F%E6%97%B6%E9%97%B4&amp;url=/archives/7c00948d-8fb6-4db2-8f4f-a8fc04f779cb" width="1" height="1" alt="" style="opacity:0;">
<pre><code>private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public static void main(String[] args) {
    //获取当前时间
    LocalDateTime nowTime = LocalDateTime.now();
    //获取当前日期
    LocalDate nowDate = LocalDate.now();
    //设置零点
    LocalDateTime beginTime = LocalDateTime.of(nowDate,LocalTime.MIN);
    //将时间进行格式化
    String time1= beginTime.format(dtf);

    //设置当天的结束时间
    LocalDateTime endTime = LocalDateTime.of(nowDate,LocalTime.MAX);
    //将时间进行格式化
    String time2 =dtf.format(endTime);

    System.out.println("今天开始的时间beginTime："+time1);
    System.out.println("今天结束的时间endTime："+time2);
}


输出的结果是：今天开始的时间beginTime：2018-08-16 00:00:00
             今天结束的时间endTime：  2018-08-16 23:59:59
</code></pre>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/81748318</p>]]></description><guid isPermaLink="false">/archives/7c00948d-8fb6-4db2-8f4f-a8fc04f779cb</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 06:06:39 GMT</pubDate></item><item><title><![CDATA[java去掉String两端引号]]></title><link>https://wiki.dgfygk.com/archives/0947b15b-9e46-44b0-b2ec-67c912a44cff</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%E5%8E%BB%E6%8E%89String%E4%B8%A4%E7%AB%AF%E5%BC%95%E5%8F%B7&amp;url=/archives/0947b15b-9e46-44b0-b2ec-67c912a44cff" width="1" height="1" alt="" style="opacity:0;">
<h3>String sql= “select * from table1 where id &gt;100”;</h3>
<h3>str =sql .replace(“\““, ““);</h3>
<h3>system.out.println(str);</h3>
<h3>这句话也就是把String类型数据的前后的双引号用”“给替换掉。</h3>
<h4>输出的结果就是：select * from table1 where id &gt;100</h4>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/81569317</p>]]></description><guid isPermaLink="false">/archives/0947b15b-9e46-44b0-b2ec-67c912a44cff</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 06:06:19 GMT</pubDate></item><item><title><![CDATA[JAVA8 十大新特性详解]]></title><link>https://wiki.dgfygk.com/archives/a5f47c9e-2fb2-495c-a3e6-dee6be68dbcf</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JAVA8%20%E5%8D%81%E5%A4%A7%E6%96%B0%E7%89%B9%E6%80%A7%E8%AF%A6%E8%A7%A3&amp;url=/archives/a5f47c9e-2fb2-495c-a3e6-dee6be68dbcf" width="1" height="1" alt="" style="opacity:0;">
<p><strong>前言：</strong> Java 8 已经发布很久了，很多报道表明Java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章，例如<a href="http://www.javacodegeeks.com/2014/04/playing-with-java-8-lambdas-and-concurrency.html">Playing with Java 8 – Lambdas and Concurrency</a>、<a href="http://www.javacodegeeks.com/2014/04/java-8-date-time-api-tutorial-localdatetime.html">Java 8 Date Time API Tutorial : LocalDateTime</a>和<a href="http://www.javacodegeeks.com/2014/04/abstract-class-versus-interface-in-the-jdk-8-era.html">Abstract Class Versus Interface in the JDK 8 Era</a>。本文还参考了一些其他资料，例如：<a href="http://www.javacodegeeks.com/2014/04/15-must-read-java-8-tutorials.html">15 Must Read Java 8 Tutorials</a>和<a href="http://www.javacodegeeks.com/2014/04/java-8-friday-the-dark-side-of-java-8.html">The Dark Side of Java 8</a>。本文综合了上述资料，整理成一份关于Java 8新特性的参考教材，希望你有所收获。</p>
<ol>
 <li>
  <h2>简介</h2>
 </li>
</ol>
<p>毫无疑问，<a href="http://www.oracle.com/technetwork/java/javase/8train-relnotes-latest-2153846.html">Java 8</a>是Java自Java 5（发布于2004年）之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。在本文中我们将学习这些新特性，并用实际的例子说明在什么场景下适合使用。</p>
<p>这个教程包含Java开发者经常面对的几类问题：</p>
<ul>
 <li>
  <p>语言</p>
 </li>
 <li>
  <p>编译器</p>
 </li>
 <li>
  <p>库</p>
 </li>
 <li>
  <p>工具</p>
 </li>
 <li>
  <p>运行时（JVM）</p>
 </li>
 <li>
  <h2>Java语言的新特性</h2>
 </li>
</ul>
<p>Java 8是Java的一个重大版本，有人认为，虽然这些新特性领Java开发人员十分期待，但同时也需要花不少精力去学习。在这一小节中，我们将介绍Java 8的大部分新特性。</p>
<h3>2.1 Lambda表达式和函数式接口</h3>
<p>Lambda表达式（也称为闭包）是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法，或者把代码本身当作数据处理：<a href="http://www.javacodegeeks.com/2014/03/functional-programming-with-java-8-lambda-expressions-monads.html">函数式开发者</a>非常熟悉这些概念。很多JVM平台上的语言（Groovy、<a href="http://www.javacodegeeks.com/tutorials/scala-tutorials/">Scala</a>等）从诞生之日就支持Lambda表达式，但是Java开发者没有选择，只能使用匿名内部类代替Lambda表达式。</p>
<p>Lambda的设计耗费了很多时间和很大的社区力量，最终找到一种折中的实现方案，可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、<strong>-&gt;</strong>符号和语句块组成，例如：</p>
<pre><code>Arrays.asList( "a", "b", "d" ).forEach( e -&gt; System.out.println( e ) );
</code></pre>
<p>在上面这个代码中的参数<strong>e</strong>的类型是由编译器推理得出的，你也可以显式指定该参数的类型，例如：</p>
<pre><code>Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -&gt; System.out.println( e ) );
</code></pre>
<p>如果Lambda表达式需要更复杂的语句块，则可以使用花括号将该语句块括起来，类似于Java中的函数体，例如：</p>
<ol>
 <li>
  <p>Arrays.asList( “a”, “b”, “d” ).forEach( e -&gt; {</p>
 </li>
 <li>
  <p>System.out.print( e );</p>
 </li>
 <li>
  <p>System.out.print( e );</p>
 </li>
 <li>
  <p>} );</p>
 </li>
</ol>
<p>Lambda表达式可以引用类成员和局部变量（会将这些变量隐式得转换成<strong>final</strong>的），例如下列两个代码块的效果完全相同：</p>
<ol>
 <li>
  <p><code>String separator = ",";</code></p>
 </li>
 <li>
  <p><code>Arrays.asList( "a", "b", "d" ).forEach(</code></p>
 </li>
 <li>
  <p><code>( String e ) -&gt; System.out.print( e + separator ) );</code></p>
 </li>
</ol>
<p>和</p>
<ol>
 <li>
  <p><code>final String separator = ",";</code></p>
 </li>
 <li>
  <p><code>Arrays.asList( "a", "b", "d" ).forEach(</code></p>
 </li>
 <li>
  <p><code>( String e ) -&gt; System.out.print( e + separator ) );</code></p>
 </li>
</ol>
<p>Lambda表达式有返回值，返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行，则可以不用使用<strong>return</strong>语句，下列两个代码片段效果相同：</p>
<pre><code>Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -&gt; e1.compareTo( e2 ) );
</code></pre>
<p>和</p>
<ol>
 <li>
  <p><code>Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -&gt; { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>int result = e1.compareTo( e2 );</code></p>
 </li>
 <li>
  <p><code>return result;</code></p>
 </li>
 <li>
  <p><code>} );</code></p>
 </li>
</ol>
<p>Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容，考虑了很多方法，于是产生了<strong><a href="http://www.javacodegeeks.com/2013/03/introduction-to-functional-interfaces-a-concept-recreated-in-java-8.html">函数接口</a></strong> 这个概念。函数接口指的是只有一个函数的接口，这样的接口可以隐式转换为Lambda表达式。<strong>java.lang.Runnable</strong> 和<strong>java.util.concurrent.Callable</strong> 是函数式接口的最佳例子。在实践中，函数式接口非常脆弱：只要某个开发者在该接口中添加一个函数，则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性，并显式说明某个接口是函数式接口，Java 8 提供了一个特殊的注解<strong>@FunctionalInterface</strong>（Java 库中的所有相关接口都已经带有这个注解了），举个简单的函数式接口的定义：</p>
<ol>
 <li>
  <p><code>@FunctionalInterface</code></p>
 </li>
 <li>
  <p><code>public interface Functional { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>void method();</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>不过有一点需要注意，<a href="https://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html#Interface_Default">默认方法和静态方法</a>不会破坏函数式接口的定义，因此如下的代码是合法的。</p>
<ol>
 <li>
  <p><code>@FunctionalInterface</code></p>
 </li>
 <li>
  <p><code>public interface FunctionalDefaultMethods { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>void method();</code></p>
 </li>
 <li></li>
 <li>
  <p><code>default void defaultMethod() {</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>Lambda表达式作为Java 8的最大卖点，它有潜力吸引更多的开发者加入到JVM平台，并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节，可以参考<a href="http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html">官方文档</a>。</p>
<h3>2.2 接口的默认方法和静态方法</h3>
<p>Java 8使用两个新概念扩展了接口的含义：默认方法和静态方法。<a href="http://blog.csdn.net/yczz/article/details/50896975">默认方法</a>使得接口有点类似traits，不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下，往现存接口中添加新的方法，即不强制那些实现了该接口的类也同时实现这个新加的方法。</p>
<p>默认方法和抽象方法之间的区别在于抽象方法需要实现，而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写，例子代码如下：</p>
<ol>
 <li>
  <p><code>private interface Defaulable { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>// Interfaces now allow default methods, the implementer may or</code></p>
 </li>
 <li>
  <p><code>// may not implement (override) them.</code></p>
 </li>
 <li>
  <p><code>default String notRequired() {</code></p>
 </li>
 <li>
  <p><code>return "Default implementation";</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>private static class DefaultableImpl implements Defaulable { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>private static class OverridableImpl implements Defaulable { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>@Override</code></p>
 </li>
 <li>
  <p><code>public String notRequired() { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>return "Overridden implementation";</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p><strong>Defaulable</strong> 接口使用关键字<strong>default</strong> 定义了一个默认方法<strong>notRequired()</strong> 。<strong>DefaultableImpl</strong> 类实现了这个接口，同时默认继承了这个接口中的默认方法；<strong>OverridableImpl</strong>类也实现了这个接口，但覆写了该接口的默认方法，并提供了一个不同的实现。</p>
<p>Java 8带来的另一个有趣的特性是在接口中可以定义静态方法，例子代码如下：</p>
<ol>
 <li>
  <p><code>private interface DefaulableFactory { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>// Interfaces now allow static methods</code></p>
 </li>
 <li>
  <p><code>static Defaulable create( Supplier&lt; Defaulable &gt; supplier ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>return supplier.get();</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>下面的代码片段整合了默认方法和静态方法的使用场景：</p>
<ol>
 <li>
  <p><code>public static void main( String[] args ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );</code></p>
 </li>
 <li>
  <p><code>System.out.println( defaulable.notRequired() );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>defaulable = DefaulableFactory.create( OverridableImpl::new );</code></p>
 </li>
 <li>
  <p><code>System.out.println( defaulable.notRequired() );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>这段代码的输出结果如下：</p>
<ol>
 <li>
  <p><code>Default implementation</code></p>
 </li>
 <li>
  <p><code>Overridden implementation</code></p>
 </li>
</ol>
<p>由于JVM上的默认方法的实现在字节码层面提供了支持，因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是：给<strong>java.util.Collection</strong> 接口添加新方法，如<strong>stream()</strong> 、<strong>parallelStream()</strong> 、<strong>forEach()</strong> 和<strong>removeIf()</strong>等等。</p>
<p>尽管默认方法有这么多好处，但在实际开发中应该谨慎使用：在复杂的继承体系中，默认方法可能引起歧义和编译错误。如果你想了解更多细节，可以参考<a href="http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html">官方文档</a>。</p>
<h3>2.3 方法引用</h3>
<p>方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用，使得java类的构造方法看起来紧凑而简洁，没有很多复杂的模板代码。</p>
<p>西门的例子中，<strong>Car</strong>类是不同方法引用的例子，可以帮助读者区分四种类型的方法引用。</p>
<ol>
 <li>
  <p><code>public static class Car { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>public static Car create( final Supplier&lt; Car &gt; supplier ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>return supplier.get();</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public static void collide( final Car car ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Collided " + car.toString() );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public void follow( final Car another ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Following the " + another.toString() );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public void repair() {</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Repaired " + this.toString() );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>第一种方法引用的类型是构造器引用，语法是<strong>Class::new</strong> ，或者更一般的形式：<strong>Class<t>::new</t></strong>。注意：这个构造器没有参数。</p>
<ol>
 <li>
  <p><code>final Car car = Car.create( Car::new );</code></p>
 </li>
 <li>
  <p><code>final List&lt; Car &gt; cars = Arrays.asList( car );</code></p>
 </li>
</ol>
<p>第二种方法引用的类型是静态方法引用，语法是<strong>Class::static_method</strong>。注意：这个方法接受一个Car类型的参数。</p>
<pre><code>`cars.forEach( Car::collide );`
</code></pre>
<p>第三种方法引用的类型是某个类的成员方法的引用，语法是<strong>Class::method</strong>，注意，这个方法没有定义入参：</p>
<pre><code>`cars.forEach( Car::repair );`
</code></pre>
<p>第四种方法引用的类型是某个实例对象的成员方法的引用，语法是<strong>instance::method</strong>。注意：这个方法接受一个Car类型的参数：</p>
<ol>
 <li>
  <p><code>final Car police = Car.create( Car::new );</code></p>
 </li>
 <li>
  <p><code>cars.forEach( police::follow );</code></p>
 </li>
</ol>
<p>运行上述例子，可以在控制台看到如下输出（Car实例可能不同）：</p>
<ol>
 <li>
  <p><code>Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d</code></p>
 </li>
 <li>
  <p><code>Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d</code></p>
 </li>
 <li>
  <p><code>Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d</code></p>
 </li>
</ol>
<p>如果想了解和学习更详细的内容，可以参考<a href="http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html">官方文档</a></p>
<h3>2.4 重复注解</h3>
<p>自从Java 5中引入<a href="http://www.javacodegeeks.com/2012/08/java-annotations-explored-explained.html">注解</a>以来，这个特性开始变得非常流行，并在各个框架和项目中被广泛使用。不过，注解有一个很大的限制是：在同一个地方不能多次使用同一个注解。Java 8打破了这个限制，引入了重复注解的概念，允许在同一个地方多次使用同一个注解。</p>
<p>在Java 8中使用<strong>@Repeatable</strong>注解定义重复注解，实际上，这并不是语言层面的改进，而是编译器做的一个trick，底层的技术仍然相同。可以利用下面的代码说明：</p>
<ol>
 <li>
  <p><code>package com.javacodegeeks.java8.repeatable.annotations;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>import java.lang.annotation.ElementType;</code></p>
 </li>
 <li>
  <p><code>import java.lang.annotation.Repeatable;</code></p>
 </li>
 <li>
  <p><code>import java.lang.annotation.Retention;</code></p>
 </li>
 <li>
  <p><code>import java.lang.annotation.RetentionPolicy;</code></p>
 </li>
 <li>
  <p><code>import java.lang.annotation.Target;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public class RepeatingAnnotations { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>@Target( ElementType.TYPE )</code></p>
 </li>
 <li>
  <p><code>@Retention( RetentionPolicy.RUNTIME )</code></p>
 </li>
 <li>
  <p><code>public @interface Filters { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>Filter[] value();</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>@Target( ElementType.TYPE )</code></p>
 </li>
 <li>
  <p><code>@Retention( RetentionPolicy.RUNTIME )</code></p>
 </li>
 <li>
  <p><code>@Repeatable( Filters.class )</code></p>
 </li>
 <li>
  <p><code>public @interface Filter { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>String value();</code></p>
 </li>
 <li>
  <p><code>};</code></p>
 </li>
 <li></li>
 <li>
  <p><code>@Filter( "filter1" )</code></p>
 </li>
 <li>
  <p><code>@Filter( "filter2" )</code></p>
 </li>
 <li>
  <p><code>public interface Filterable {</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public static void main(String[] args) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>System.out.println( filter.value() );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>正如我们所见，这里的<strong>Filter</strong> 类使用@Repeatable(Filters.class)注解修饰，而<strong>Filters</strong> 是存放<strong>Filter</strong> 注解的容器，编译器尽量对开发者屏蔽这些细节。这样，<strong>Filterable</strong> 接口可以用两个<strong>Filter</strong>注解注释（这里并没有提到任何关于Filters的信息）。</p>
<p>另外，反射API提供了一个新的方法：<strong>getAnnotationsByType()</strong> ，可以返回某个类型的重复注解，例如<code>Filterable.class.getAnnoation(Filters.class)</code>将返回两个Filter实例，输出到控制台的内容如下所示：</p>
<ol>
 <li>
  <p><code>filter1</code></p>
 </li>
 <li>
  <p><code>filter2</code></p>
 </li>
</ol>
<p>如果你希望了解更多内容，可以参考<a href="http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html">官方文档</a>。</p>
<h3>2.5 更好的类型推断</h3>
<p>Java 8编译器在类型推断方面有很大的提升，在很多场景下编译器可以推导出某个参数的数据类型，从而使得代码更为简洁。例子代码如下：</p>
<ol>
 <li>
  <p><code>package com.javacodegeeks.java8.type.inference;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public class Value&lt; T &gt; { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>public static&lt; T &gt; T defaultValue() {</code></p>
 </li>
 <li>
  <p><code>return null;</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public T getOrDefault( T value, T defaultValue ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>return ( value != null ) ? value : defaultValue;</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>下列代码是<strong>Value<string></string></strong>类型的应用：</p>
<ol>
 <li>
  <p><code>package com.javacodegeeks.java8.type.inference;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public class TypeInference { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>public static void main(String[] args) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>final Value&lt; String &gt; value = new Value&lt;&gt;();</code></p>
 </li>
 <li>
  <p><code>value.getOrDefault( "22", Value.defaultValue() );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>参数<strong>Value.defaultValue()</strong> 的类型由编译器推导得出，不需要显式指明。在Java 7中这段代码会有编译错误，除非使用<code>Value.&lt;String&gt;defaultValue()</code>。</p>
<h3>2.6 拓宽注解的应用场景</h3>
<p>Java 8拓宽了注解的应用场景。现在，注解几乎可以使用在任何元素上：局部变量、接口类型、超类和接口实现类，甚至可以用在函数的异常定义上。下面是一些例子：</p>
<ol>
 <li>
  <p><code>package com.javacodegeeks.java8.annotations;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>import java.lang.annotation.ElementType;</code></p>
 </li>
 <li>
  <p><code>import java.lang.annotation.Retention;</code></p>
 </li>
 <li>
  <p><code>import java.lang.annotation.RetentionPolicy;</code></p>
 </li>
 <li>
  <p><code>import java.lang.annotation.Target;</code></p>
 </li>
 <li>
  <p><code>import java.util.ArrayList;</code></p>
 </li>
 <li>
  <p><code>import java.util.Collection;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public class Annotations { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>@Retention( RetentionPolicy.RUNTIME )</code></p>
 </li>
 <li>
  <p><code>@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )</code></p>
 </li>
 <li>
  <p><code>public @interface NonEmpty {</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public static class Holder&lt; @NonEmpty T &gt; extends @NonEmpty Object { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>public void method() throws @NonEmpty Exception {</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>@SuppressWarnings( "unused" )</code></p>
 </li>
 <li>
  <p><code>public static void main(String[] args) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>final Holder&lt; String &gt; holder = new @NonEmpty Holder&lt; String &gt;();</code></p>
 </li>
 <li>
  <p><code>@NonEmpty Collection&lt; @NonEmpty String &gt; strings = new ArrayList&lt;&gt;();</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p><strong>ElementType.TYPE_USER</strong> 和<strong>ElementType.TYPE_PARAMETER</strong>是Java 8新增的两个注解，用于描述注解的使用场景。Java 语言也做了对应的改变，以识别这些新增的注解。</p>
<ol>
 <li>
  <h2>Java编译器的新特性</h2>
 </li>
</ol>
<h3>3.1 参数名称</h3>
<p>为了在运行时获得Java程序中方法的参数名称，老一辈的Java程序员必须使用不同方法，例如<a href="https://github.com/paul-hammant/paranamer">Paranamer liberary</a>。Java 8终于将这个特性规范化，在语言层面（使用反射API和<strong>Parameter.getName()方法</strong> ）和字节码层面（使用新的<strong>javac</strong> 编译器以及<strong>-parameters</strong>参数）提供支持。</p>
<ol>
 <li>
  <p><code>package com.javacodegeeks.java8.parameter.names;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>import java.lang.reflect.Method;</code></p>
 </li>
 <li>
  <p><code>import java.lang.reflect.Parameter;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public class ParameterNames { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>public static void main(String[] args) throws Exception { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>Method method = ParameterNames.class.getMethod( "main", String[].class );</code></p>
 </li>
 <li>
  <p><code>for( final Parameter parameter: method.getParameters() ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Parameter: " + parameter.getName() );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>在Java 8中这个特性是默认关闭的，因此如果不带<strong>-parameters</strong>参数编译上述代码并运行，则会输出如下结果：</p>
<pre><code>Parameter: arg0
</code></pre>
<p>如果带<strong>-parameters</strong>参数，则会输出如下结果（正确的结果）：</p>
<pre><code>Parameter: args
</code></pre>
<p>如果你使用Maven进行项目管理，则可以在<strong>maven-compiler-plugin</strong> 编译器的配置项中配置<strong>-parameters</strong>参数：</p>
<ol>
 <li>
  <p><code>&lt;plugin&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;version&gt;3.1&lt;/version&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;configuration&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;compilerArgument&gt;-parameters&lt;/compilerArgument&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;source&gt;1.8&lt;/source&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;target&gt;1.8&lt;/target&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;/configuration&gt;</code></p>
 </li>
 <li>
  <p><code>&lt;/plugin&gt;</code></p>
 </li>
 <li>
  <h2>Java官方库的新特性</h2>
 </li>
</ol>
<p>Java 8增加了很多新的工具类（date/time类），并扩展了现存的工具类，以支持现代的并发编程、函数式编程等。</p>
<h3>4.1 Optional</h3>
<p>Java应用中最常见的bug就是<a href="http://examples.javacodegeeks.com/java-basics/exceptions/java-lang-nullpointerexception-how-to-handle-null-pointer-exception/">空值异常</a>。在Java 8之前，<a href="http://code.google.com/p/guava-libraries/">Google Guava</a>引入了<strong>Optionals</strong> 类来解决<strong>NullPointerException</strong> ，从而避免源码被各种<strong>null</strong> 检查污染，以便开发者写出更加整洁的代码。Java 8也将<strong>Optional</strong>加入了官方库。</p>
<p><strong>Optional</strong> 仅仅是一个容易：存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查，可以参考<a href="http://docs.oracle.com/javase/8/docs/api/">Java 8官方文档</a>了解更多细节。</p>
<p>接下来看一点使用<strong>Optional</strong>的例子：可能为空的值或者某个类型的值：</p>
<ol>
 <li>
  <p><code>Optional&lt; String &gt; fullName = Optional.ofNullable( null );</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Full Name is set? " + fullName.isPresent() );</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Full Name: " + fullName.orElseGet( () -&gt; "[none]" ) );</code></p>
 </li>
 <li>
  <p><code>System.out.println( fullName.map( s -&gt; "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );</code></p>
 </li>
</ol>
<p>如果<strong>Optional</strong> 实例持有一个非空值，则<strong>isPresent()</strong> 方法返回true，否则返回false；<strong>orElseGet()</strong> 方法，<strong>Optional</strong> 实例持有null，则可以接受一个lambda表达式生成的默认值；<strong>map()</strong> 方法可以将现有的<strong>Opetional</strong> 实例的值转换成新的值；<strong>orElse()</strong> 方法与<strong>orElseGet()</strong>方法类似，但是在持有null的时候返回传入的默认值。</p>
<p>上述代码的输出结果如下：</p>
<ol>
 <li>
  <p><code>Full Name is set? false</code></p>
 </li>
 <li>
  <p><code>Full Name: [none]</code></p>
 </li>
 <li>
  <p><code>Hey Stranger!</code></p>
 </li>
</ol>
<p>再看下另一个简单的例子：</p>
<ol>
 <li>
  <p><code>Optional&lt; String &gt; firstName = Optional.of( "Tom" );</code></p>
 </li>
 <li>
  <p><code>System.out.println( "First Name is set? " + firstName.isPresent() );</code></p>
 </li>
 <li>
  <p><code>System.out.println( "First Name: " + firstName.orElseGet( () -&gt; "[none]" ) );</code></p>
 </li>
 <li>
  <p><code>System.out.println( firstName.map( s -&gt; "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );</code></p>
 </li>
 <li>
  <p><code>System.out.println();</code></p>
 </li>
</ol>
<p>这个例子的输出是：</p>
<ol>
 <li>
  <p><code>First Name is set? true</code></p>
 </li>
 <li>
  <p><code>First Name: Tom</code></p>
 </li>
 <li>
  <p><code>Hey Tom!</code></p>
 </li>
</ol>
<p>如果想了解更多的细节，请参考<a href="http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html">官方文档</a>。</p>
<h3>4.2 Streams</h3>
<p>新增的<a href="http://www.javacodegeeks.com/2014/05/the-effects-of-programming-with-java-8-streams-on-algorithm-performance.html">Stream API</a>（java.util.stream）将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善，以便开发者能够写出更加有效、更加简洁和紧凑的代码。</p>
<p>Steam API极大得简化了集合操作（后面我们会看到不止是集合），首先看下这个叫Task的类：</p>
<ol>
 <li>
  <p><code>public class Streams { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>private enum Status { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>OPEN, CLOSED</code></p>
 </li>
 <li>
  <p><code>};</code></p>
 </li>
 <li></li>
 <li>
  <p><code>private static final class Task { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>private final Status status;</code></p>
 </li>
 <li>
  <p><code>private final Integer points;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>Task( final Status status, final Integer points ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>this.status = status;</code></p>
 </li>
 <li>
  <p><code>this.points = points;</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public Integer getPoints() { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>return points;</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public Status getStatus() { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>return status;</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li></li>
 <li>
  <p><code>@Override</code></p>
 </li>
 <li>
  <p><code>public String toString() { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>return String.format( "[%s, %d]", status, points );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>Task类有一个分数（或伪复杂度）的概念，另外还有两种状态：OPEN或者CLOSED。现在假设有一个task集合：</p>
<ol>
 <li>
  <p><code>final Collection&lt; Task &gt; tasks = Arrays.asList(</code></p>
 </li>
 <li>
  <p><code>new Task( Status.OPEN, 5 ),</code></p>
 </li>
 <li>
  <p><code>new Task( Status.OPEN, 13 ),</code></p>
 </li>
 <li>
  <p><code>new Task( Status.CLOSED, 8 )</code></p>
 </li>
 <li>
  <p><code>);</code></p>
 </li>
</ol>
<p>首先看一个问题：在这个task集合中一共有多少个OPEN状态的点？在Java 8之前，要解决这个问题，则需要使用<strong>foreach</strong>循环遍历task集合；但是在Java 8中可以利用steams解决：包括一系列元素的列表，并且支持顺序和并行处理。</p>
<ol>
 <li>
  <p><code>// Calculate total points of all active tasks using sum()</code></p>
 </li>
 <li>
  <p><code>final long totalPointsOfOpenTasks = tasks</code></p>
 </li>
 <li>
  <p><code>.stream()</code></p>
 </li>
 <li>
  <p><code>.filter( task -&gt; task.getStatus() == Status.OPEN )</code></p>
 </li>
 <li>
  <p><code>.mapToInt( Task::getPoints )</code></p>
 </li>
 <li>
  <p><code>.sum();</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( "Total points: " + totalPointsOfOpenTasks );</code></p>
 </li>
</ol>
<p>运行这个方法的控制台输出是：</p>
<pre><code>Total points: 18
</code></pre>
<p>这里有很多知识点值得说。首先，tasks集合被转换成steam表示；其次，在steam上的<strong>filter</strong> 操作会过滤掉所有CLOSED的task；第三，<strong>mapToInt</strong> 操作基于每个task实例的<strong>Task::getPoints</strong> 方法将task流转换成Integer集合；最后，通过<strong>sum</strong>方法计算总和，得出最后的结果。</p>
<p>在学习下一个例子之前，还需要记住一些steams（<a href="http://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps">点此更多细节</a>）的知识点。Steam之上的操作可分为中间操作和晚期操作。</p>
<p>中间操作会返回一个新的steam——执行一个中间操作（例如<strong>filter</strong>）并不会执行实际的过滤操作，而是创建一个新的steam，并将原steam中符合条件的元素放入新创建的steam。</p>
<p>晚期操作（例如<strong>forEach</strong> 或者<strong>sum</strong>），会遍历steam并得出结果或者附带结果；在执行晚期操作之后，steam处理线已经处理完毕，就不能使用了。在几乎所有情况下，晚期操作都是立刻对steam进行遍历。</p>
<p>steam的另一个价值是创造性地支持并行处理（parallel processing）。对于上述的tasks集合，我们可以用下面的代码计算所有任务的点数之和：</p>
<ol>
 <li>
  <p><code>// Calculate total points of all tasks</code></p>
 </li>
 <li>
  <p><code>final double totalPoints = tasks</code></p>
 </li>
 <li>
  <p><code>.stream()</code></p>
 </li>
 <li>
  <p><code>.parallel()</code></p>
 </li>
 <li>
  <p><code>.map( task -&gt; task.getPoints() ) // or map( Task::getPoints )</code></p>
 </li>
 <li>
  <p><code>.reduce( 0, Integer::sum );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( "Total points (all tasks): " + totalPoints );</code></p>
 </li>
</ol>
<p>这里我们使用<strong>parallel</strong> 方法并行处理所有的task，并使用<strong>reduce</strong>方法计算最终的结果。控制台输出如下：</p>
<pre><code>Total points（all tasks）: 26.0
</code></pre>
<p>对于一个集合，经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务，代码如下：</p>
<ol>
 <li>
  <p><code>// Group tasks by their status</code></p>
 </li>
 <li>
  <p><code>final Map&lt; Status, List&lt; Task &gt; &gt; map = tasks</code></p>
 </li>
 <li>
  <p><code>.stream()</code></p>
 </li>
 <li>
  <p><code>.collect( Collectors.groupingBy( Task::getStatus ) );</code></p>
 </li>
 <li>
  <p><code>System.out.println( map );</code></p>
 </li>
</ol>
<p>控制台的输出如下：</p>
<pre><code>`{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}`
</code></pre>
<p>最后一个关于tasks集合的例子问题是：如何计算集合中每个任务的点数在集合中所占的比重，具体处理的代码如下：</p>
<ol>
 <li>
  <p><code>// Calculate the weight of each tasks (as percent of total points)</code></p>
 </li>
 <li>
  <p><code>final Collection&lt; String &gt; result = tasks</code></p>
 </li>
 <li>
  <p><code>.stream() // Stream&lt; String &gt;</code></p>
 </li>
 <li>
  <p><code>.mapToInt( Task::getPoints ) // IntStream</code></p>
 </li>
 <li>
  <p><code>.asLongStream() // LongStream</code></p>
 </li>
 <li>
  <p><code>.mapToDouble( points -&gt; points / totalPoints ) // DoubleStream</code></p>
 </li>
 <li>
  <p><code>.boxed() // Stream&lt; Double &gt;</code></p>
 </li>
 <li>
  <p><code>.mapToLong( weigth -&gt; ( long )( weigth * 100 ) ) // LongStream</code></p>
 </li>
 <li>
  <p><code>.mapToObj( percentage -&gt; percentage + "%" ) // Stream&lt; String&gt;</code></p>
 </li>
 <li>
  <p><code>.collect( Collectors.toList() ); // List&lt; String &gt;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( result );</code></p>
 </li>
</ol>
<p>控制台输出结果如下：</p>
<pre><code>[19%, 50%, 30%]
</code></pre>
<p>最后，正如之前所说，Steam API不仅可以作用于Java集合，传统的IO操作（从文件或者网络一行一行得读取数据）可以受益于steam处理，这里有一个小例子：</p>
<pre><code>&nbsp;
</code></pre>
<ol>
 <li>
  <p><code>final Path path = new File( filename ).toPath();</code></p>
 </li>
 <li>
  <p><code>try( Stream&lt; String &gt; lines = Files.lines( path, StandardCharsets.UTF_8 ) ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>lines.onClose( () -&gt; System.out.println("Done!") ).forEach( System.out::println );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>Stream的方法<strong>onClose</strong> 返回一个等价的有额外句柄的Stream，当Stream的close（）方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用，是Java 8对软件开发的现代范式的响应。</p>
<h3>4.3 Date/Time API(JSR 310)</h3>
<p>Java 8引入了<a href="https://jcp.org/en/jsr/detail?id=310">新的Date-Time API(JSR 310)</a>来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。<strong>java.util.Date</strong> 和后来的<strong>java.util.Calendar</strong>一直没有解决这个问题（甚至令开发者更加迷茫）。</p>
<p>因为上面这些原因，诞生了第三方库<a href="http://www.joda.org/joda-time/">Joda-Time</a>，可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响，并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant（跟日期类似但是精确到纳秒）、duration（持续时间）和时钟操作的类。新设计的API认真考虑了这些类的不变性（从java.util.Calendar吸取的教训），如果某个实例需要修改，则返回一个新的对象。</p>
<p>我们接下来看看java.time包中的关键类和各自的使用例子。首先，<strong>Clock</strong> 类使用时区来返回当前的纳秒时间和日期。<strong>Clock</strong> 可以替代<strong>System.currentTimeMillis()</strong> 和<strong>TimeZone.getDefault()</strong>。</p>
<ol>
 <li>
  <p><code>// Get the system clock as UTC offset</code></p>
 </li>
 <li>
  <p><code>final Clock clock = Clock.systemUTC();</code></p>
 </li>
 <li>
  <p><code>System.out.println( clock.instant() );</code></p>
 </li>
 <li>
  <p><code>System.out.println( clock.millis() );</code></p>
 </li>
</ol>
<p>这个例子的输出结果是：</p>
<ol>
 <li>
  <p><code>2014-04-12T15:19:29.282Z</code></p>
 </li>
 <li>
  <p><code>1397315969360</code></p>
 </li>
</ol>
<p>第二，关注下<strong>LocalDate</strong> 和<strong>LocalTime</strong> 类。<strong>LocalDate</strong> 仅仅包含ISO-8601日历系统中的日期部分；<strong>LocalTime</strong>则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。</p>
<ol>
 <li>
  <p><code>// Get the local date and local time</code></p>
 </li>
 <li>
  <p><code>final LocalDate date = LocalDate.now();</code></p>
 </li>
 <li>
  <p><code>final LocalDate dateFromClock = LocalDate.now( clock );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( date );</code></p>
 </li>
 <li>
  <p><code>System.out.println( dateFromClock );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>// Get the local date and local time</code></p>
 </li>
 <li>
  <p><code>final LocalTime time = LocalTime.now();</code></p>
 </li>
 <li>
  <p><code>final LocalTime timeFromClock = LocalTime.now( clock );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( time );</code></p>
 </li>
 <li>
  <p><code>System.out.println( timeFromClock );</code></p>
 </li>
</ol>
<p>上述例子的输出结果如下：</p>
<ol>
 <li>
  <p><code>2014-04-12</code></p>
 </li>
 <li>
  <p><code>2014-04-12</code></p>
 </li>
 <li>
  <p><code>11:25:54.568</code></p>
 </li>
 <li>
  <p><code>15:25:54.568</code></p>
 </li>
</ol>
<p><strong>LocalDateTime</strong> 类包含了LocalDate和LocalTime的信息，但是不包含ISO-8601日历系统中的时区信息。这里有一些<a href="https://www.javacodegeeks.com/2014/04/java-8-date-time-api-tutorial-localdatetime.html">关于LocalDate和LocalTime的例子</a>：</p>
<ol>
 <li>
  <p><code>// Get the local date/time</code></p>
 </li>
 <li>
  <p><code>final LocalDateTime datetime = LocalDateTime.now();</code></p>
 </li>
 <li>
  <p><code>final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( datetime );</code></p>
 </li>
 <li>
  <p><code>System.out.println( datetimeFromClock );</code></p>
 </li>
</ol>
<p>上述这个例子的输出结果如下：</p>
<ol>
 <li>
  <p><code>2014-04-12T11:37:52.309</code></p>
 </li>
 <li>
  <p><code>2014-04-12T15:37:52.309</code></p>
 </li>
</ol>
<p>如果你需要特定时区的data/time信息，则可以使用<strong>ZoneDateTime</strong>，它保存有ISO-8601日期系统的日期和时间，而且有时区信息。下面是一些使用不同时区的例子：</p>
<ol>
 <li>
  <p><code>// Get the zoned date/time</code></p>
 </li>
 <li>
  <p><code>final ZonedDateTime zonedDatetime = ZonedDateTime.now();</code></p>
 </li>
 <li>
  <p><code>final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );</code></p>
 </li>
 <li>
  <p><code>final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( zonedDatetime );</code></p>
 </li>
 <li>
  <p><code>System.out.println( zonedDatetimeFromClock );</code></p>
 </li>
 <li>
  <p><code>System.out.println( zonedDatetimeFromZone );</code></p>
 </li>
</ol>
<p>这个例子的输出结果是：</p>
<ol>
 <li>
  <p><code>2014-04-12T11:47:01.017-04:00[America/New_York]</code></p>
 </li>
 <li>
  <p><code>2014-04-12T15:47:01.017Z</code></p>
 </li>
 <li>
  <p><code>2014-04-12T08:47:01.017-07:00[America/Los_Angeles]</code></p>
 </li>
</ol>
<p>最后看下<strong>Duration</strong>类，它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同，例子代码如下：</p>
<ol>
 <li>
  <p><code>// Get duration between two dates</code></p>
 </li>
 <li>
  <p><code>final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );</code></p>
 </li>
 <li>
  <p><code>final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>final Duration duration = Duration.between( from, to );</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Duration in days: " + duration.toDays() );</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Duration in hours: " + duration.toHours() );</code></p>
 </li>
</ol>
<p>这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数，输出结果如下：</p>
<ol>
 <li>
  <p><code>Duration in days: 365</code></p>
 </li>
 <li>
  <p><code>Duration in hours: 8783</code></p>
 </li>
</ol>
<p>对于Java 8的新日期时间的总体印象还是比较积极的，一部分是因为Joda-Time的积极影响，另一部分是因为官方终于听取了开发人员的需求。如果希望了解更多细节，可以参考<a href="http://docs.oracle.com/javase/tutorial/datetime/index.html">官方文档</a>。</p>
<h3>4.4 Nashorn JavaScript引擎</h3>
<p>Java 8提供了新的<a href="http://www.javacodegeeks.com/2014/02/java-8-compiling-lambda-expressions-in-the-new-nashorn-js-engine.html">Nashorn JavaScript引擎</a>，使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本，这类Script引擎遵循相同的规则，允许Java和JavaScript交互使用，例子代码如下：</p>
<ol>
 <li>
  <p><code>ScriptEngineManager manager = new ScriptEngineManager();</code></p>
 </li>
 <li>
  <p><code>ScriptEngine engine = manager.getEngineByName( "JavaScript" );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>System.out.println( engine.getClass().getName() );</code></p>
 </li>
 <li>
  <p><code>System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );</code></p>
 </li>
</ol>
<p>这个代码的输出结果如下：</p>
<ol>
 <li>
  <p><code>jdk.nashorn.api.scripting.NashornScriptEngine</code></p>
 </li>
 <li>
  <p><code>Result: 2</code></p>
 </li>
</ol>
<h3>4.5 Base64</h3>
<p><a href="http://www.javacodegeeks.com/2014/04/base64-in-java-8-its-not-too-late-to-join-in-the-fun.html">对Base64编码的支持</a>已经被加入到Java 8官方库中，这样不需要使用第三方库就可以进行Base64编码，例子代码如下：</p>
<ol>
 <li>
  <p><code>package com.javacodegeeks.java8.base64;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>import java.nio.charset.StandardCharsets;</code></p>
 </li>
 <li>
  <p><code>import java.util.Base64;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public class Base64s { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>public static void main(String[] args) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>final String text = "Base64 finally in Java 8!";</code></p>
 </li>
 <li></li>
 <li>
  <p><code>final String encoded = Base64</code></p>
 </li>
 <li>
  <p><code>.getEncoder()</code></p>
 </li>
 <li>
  <p><code>.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );</code></p>
 </li>
 <li>
  <p><code>System.out.println( encoded );</code></p>
 </li>
 <li></li>
 <li>
  <p><code>final String decoded = new String(</code></p>
 </li>
 <li>
  <p><code>Base64.getDecoder().decode( encoded ),</code></p>
 </li>
 <li>
  <p><code>StandardCharsets.UTF_8 );</code></p>
 </li>
 <li>
  <p><code>System.out.println( decoded );</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>这个例子的输出结果如下：</p>
<ol>
 <li>
  <p><code>QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==</code></p>
 </li>
 <li>
  <p><code>Base64 finally in Java 8!</code></p>
 </li>
</ol>
<p>新的Base64API也支持URL和MINE的编码解码。
 <br>
 (<strong>Base64.<em>getUrlEncoder</em>()</strong> / <strong>Base64.<em>getUrlDecoder</em>()</strong> , <strong>Base64.<em>getMimeEncoder</em>()</strong> / <strong>Base64.<em>getMimeDecoder</em>()</strong>)。</p>
<h3>4.6 并行数组</h3>
<p>Java8版本新增了很多新的方法，用于支持并行数组处理。最重要的方法是<strong>parallelSort()</strong> ，可以显著加快多核机器上的数组排序。下面的例子论证了<strong>parallexXxx</strong>系列的方法：</p>
<ol>
 <li>
  <p><code>package com.javacodegeeks.java8.parallel.arrays;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>import java.util.Arrays;</code></p>
 </li>
 <li>
  <p><code>import java.util.concurrent.ThreadLocalRandom;</code></p>
 </li>
 <li></li>
 <li>
  <p><code>public class ParallelArrays { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>public static void main( String[] args ) { &lt;br /&gt;</code></p>
 </li>
 <li>
  <p><code>long[] arrayOfLong = new long [ 20000 ];</code></p>
 </li>
 <li></li>
 <li>
  <p><code>Arrays.parallelSetAll( arrayOfLong,</code></p>
 </li>
 <li>
  <p><code>index -&gt; ThreadLocalRandom.current().nextInt( 1000000 ) );</code></p>
 </li>
 <li>
  <p><code>Arrays.stream( arrayOfLong ).limit( 10 ).forEach(</code></p>
 </li>
 <li>
  <p><code>i -&gt; System.out.print( i + " " ) );</code></p>
 </li>
 <li>
  <p><code>System.out.println();</code></p>
 </li>
 <li></li>
 <li>
  <p><code>Arrays.parallelSort( arrayOfLong );</code></p>
 </li>
 <li>
  <p><code>Arrays.stream( arrayOfLong ).limit( 10 ).forEach(</code></p>
 </li>
 <li>
  <p><code>i -&gt; System.out.print( i + " " ) );</code></p>
 </li>
 <li>
  <p><code>System.out.println();</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
 <li>
  <p><code>}</code></p>
 </li>
</ol>
<p>上述这些代码使用<strong>parallelSetAll()</strong> 方法生成20000个随机数，然后使用<strong>parallelSort()</strong>方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是：</p>
<ol>
 <li>
  <p><code>Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378</code></p>
 </li>
 <li>
  <p><code>Sorted: 39 220 263 268 325 607 655 678 723 793</code></p>
 </li>
</ol>
<h3>4.7 并发性</h3>
<p>基于新增的lambda表达式和steam特性，为Java 8中为<strong>java.util.concurrent.ConcurrentHashMap</strong> 类添加了新的方法来支持聚焦操作；另外，也为<strong>java.util.concurrentForkJoinPool</strong> 类添加了新的方法来支持通用线程池操作（更多内容可以参考<a href="http://academy.javacodegeeks.com/course/java-concurrency-essentials/">我们的并发编程课程</a>）。</p>
<p>Java 8还添加了新的<strong>java.util.concurrent.locks.StampedLock</strong> 类，用于支持基于容量的锁——该锁有三个模型用于支持读写操作（可以把这个锁当做是<strong>java.util.concurrent.locks.ReadWriteLock</strong>的替代者）。</p>
<p>在<strong>java.util.concurrent.atomic</strong>包中也新增了不少工具类，列举如下：</p>
<ul>
 <li>
  <p>DoubleAccumulator</p>
 </li>
 <li>
  <p>DoubleAdder</p>
 </li>
 <li>
  <p>LongAccumulator</p>
 </li>
 <li>
  <p>LongAdder</p>
 </li>
 <li>
  <h2>新的Java工具</h2>
 </li>
</ul>
<p>Java 8提供了一些新的命令行工具，这部分会讲解一些对开发者最有用的工具。</p>
<h3>5.1 Nashorn引擎：jjs</h3>
<p><strong>jjs</strong> 是一个基于标准Nashorn引擎的命令行工具，可以接受js源码并执行。例如，我们写一个<strong>func.js</strong>文件，内容如下：</p>
<ol>
 <li>
  <p><code>function f() {</code></p>
 </li>
 <li>
  <p><code>return 1;</code></p>
 </li>
 <li>
  <p><code>};</code></p>
 </li>
 <li></li>
 <li>
  <p><code>print( f() + 1 );</code></p>
 </li>
</ol>
<p>可以在命令行中执行这个命令：<code>jjs func.js</code>，控制台输出结果是：</p>
<pre><code>`2`
</code></pre>
<p>如果需要了解细节，可以参考<a href="http://docs.oracle.com/javase/8/docs/technotes/tools/unix/jjs.html">官方文档</a>。</p>
<h3>5.2 类依赖分析器：jdeps</h3>
<p><strong>jdeps</strong> 是一个相当棒的命令行工具，它可以展示包层级和类层级的Java类依赖关系，它以<strong>.class</strong>文件、目录或者Jar文件为输入，然后会把依赖关系输出到控制台。</p>
<p>我们可以利用jedps分析下<a href="http://blog.csdn.net/yczz/article/details/50896975">Spring Framework库</a>，为了让结果少一点，仅仅分析一个JAR文件：<strong>org.springframework.core-3.0.5.RELEASE.jar</strong>。</p>
<pre><code>`jdeps org.springframework.core-3.0.5.RELEASE.jar`
</code></pre>
<p>这个命令会输出很多结果，我们仅看下其中的一部分：依赖关系按照包分组，如果在classpath上找不到依赖，则显示"not found”.</p>
<ol>
 <li>
  <p><code>org.springframework.core-3.0.5.RELEASE.jar -&gt; C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar</code></p>
 </li>
 <li>
  <p><code>org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)</code></p>
 </li>
 <li>
  <p><code>-&gt; java.io</code></p>
 </li>
 <li>
  <p><code>-&gt; java.lang</code></p>
 </li>
 <li>
  <p><code>-&gt; java.lang.annotation</code></p>
 </li>
 <li>
  <p><code>-&gt; java.lang.ref</code></p>
 </li>
 <li>
  <p><code>-&gt; java.lang.reflect</code></p>
 </li>
 <li>
  <p><code>-&gt; java.util</code></p>
 </li>
 <li>
  <p><code>-&gt; java.util.concurrent</code></p>
 </li>
 <li>
  <p><code>-&gt; org.apache.commons.logging not found</code></p>
 </li>
 <li>
  <p><code>-&gt; org.springframework.asm not found</code></p>
 </li>
 <li>
  <p><code>-&gt; org.springframework.asm.commons not found</code></p>
 </li>
 <li>
  <p><code>org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)</code></p>
 </li>
 <li>
  <p><code>-&gt; java.lang</code></p>
 </li>
 <li>
  <p><code>-&gt; java.lang.annotation</code></p>
 </li>
 <li>
  <p><code>-&gt; java.lang.reflect</code></p>
 </li>
 <li>
  <p><code>-&gt; java.util</code></p>
 </li>
</ol>
<p>更多的细节可以参考<a href="http://docs.oracle.com/javase/8/docs/technotes/tools/unix/jdeps.html">官方文档</a>。</p>
<ol>
 <li>
  <h2>JVM的新特性</h2>
 </li>
</ol>
<p>使用<strong><a href="http://www.javacodegeeks.com/2013/02/java-8-from-permgen-to-metaspace.html">Metaspace</a></strong> （<a href="http://openjdk.java.net/jeps/122">JEP 122</a>）代替持久代（<strong>PermGen</strong> space）。在JVM参数方面，使用<strong>-XX:MetaSpaceSize</strong> 和<strong>-XX:MaxMetaspaceSize</strong> 代替原来的<strong>-XX:PermSize</strong> 和<strong>-XX:MaxPermSize</strong>。</p>
<ol>
 <li>
  <h2>结论</h2>
 </li>
</ol>
<p>通过为开发者提供很多能够提高生产力的特性，Java 8使得Java平台前进了一大步。现在还不太适合将Java 8应用在生产系统中，但是在之后的几个月中Java 8的应用率一定会逐步提高（PS:原文时间是2014年5月9日，现在在很多公司Java 8已经成为主流，我司由于体量太大，现在也在一点点上Java 8，虽然慢但是好歹在升级了）。作为开发者，现在应该学习一些Java 8的知识，为升级做好准备。</p>
<p>关于Spring：对于企业级开发，我们也应该关注Spring社区对Java 8的支持，可以参考这篇文章——<a href="http://www.infoq.com/cn/articles/spring-4-java-8">Spring 4支持的Java 8新特性一览</a></p>
<ol>
 <li>
  <h2>参考资料</h2>
 </li>
 <li>
  <p><a href="http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html">What's New in JDK 8</a></p>
 </li>
 <li>
  <p><a href="http://docs.oracle.com/javase/tutorial/">The Java Tutorials</a></p>
 </li>
 <li>
  <p><a href="http://blog.arungupta.me/2014/03/wildfly8-jdk8-netbeans8-javaee7-excellent-combo-enterprise-java/">WildFly 8, JDK 8, NetBeans 8, Java EE</a></p>
 </li>
 <li>
  <p><a href="http://winterbe.com/posts/2014/03/16/java-8-tutorial/">Java 8 Tutorial</a></p>
 </li>
 <li>
  <p><a href="http://marxsoftware.blogspot.ca/2014/03/jdeps.html">JDK 8 Command-line Static Dependency Checker</a></p>
 </li>
 <li>
  <p><a href="http://marxsoftware.blogspot.ca/2014/03/illuminating-javadoc-of-jdk-8.html">The Illuminating Javadoc of JDK</a></p>
 </li>
 <li>
  <p><a href="http://blog.jooq.org/2014/04/04/java-8-friday-the-dark-side-of-java-8/">The Dark Side of Java 8</a></p>
 </li>
 <li>
  <p><a href="http://www.eclipse.org/downloads/java8/">Installing Java™ 8 Support in Eclipse Kepler SR2</a></p>
 </li>
 <li>
  <p><a href="http://www.baeldung.com/java8">Java 8</a></p>
 </li>
 <li>
  <p><a href="http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html">Oracle Nashorn. A Next-Generation JavaScript Engine for the JVM</a></p>
 </li>
</ol>
<p>原文链接: https://onlyou.blog.csdn.net//article/details/82078668</p>]]></description><guid isPermaLink="false">/archives/a5f47c9e-2fb2-495c-a3e6-dee6be68dbcf</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 05:38:39 GMT</pubDate></item><item><title><![CDATA[项目改进的四个抓手]]></title><link>https://wiki.dgfygk.com/archives/xiang-mu-gai-jin-de-si-ge-zhua-shou</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%A1%B9%E7%9B%AE%E6%94%B9%E8%BF%9B%E7%9A%84%E5%9B%9B%E4%B8%AA%E6%8A%93%E6%89%8B&amp;url=/archives/xiang-mu-gai-jin-de-si-ge-zhua-shou" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span fontsize="" color="">报警</span></strong></p>
<p style="">10年前我会过别人一个问题：这个逻辑上理论上不可出现空值，要不要进行非空判断呢?</p>
<p style="">现在这个问题终于有答案了：要进行非空判断，判断如果问题发生则立即发出报警，报警中明确标出，如果这个报警被触发，一定是代码里有bug，请立即处理!</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-knru.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">类似这种理论上不应该触发的分支、或者触发了资源的水位，在代码中应该做报警处理。所谓资源的水位是指：</p>
<p style="">比如数据库当初是按照tps3000来申请资源，达到tps2500就很危险了，应该考虑X轴、Y轴、Z轴拆分之类扩容措施。</p>
<p style="">再比如磁盘空间只剩余20%，这就该检查一下是否异常情况导致日志过多、或者业务增长太快，机器配置需要更新，日志设置需要优化等。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-slih.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">监控</span></strong></p>
<p style="">有些问题不需要使用推送(push)这么极端的方式，属于重要不紧急，可以在每天固定时间，或者自己有时间的时候、有需要的时候自己进行check。这种通过拉取(pull)方式来对应的问题一般通过监控来呈现。下面的配图不是有意给脉脉打广告哈。只是觉得这个四象限工作法的小帖子确实不错^_^。</p>
<p style="">注：本人就职于美团，并不就职于脉脉。因为是脉脉KOL，所以会偶尔收到些脉脉的小礼物O(∩_∩)O~</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qzsq.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">美团点评这边用CAT，可以侵入式在程序里灵活埋点。我每天早上9点多到公司第一件事就是查看自己负责项目的CAT有没有异常，异常包括：failed、耗时长、长尾请求，和自己最近有过发版的地方的指标。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tukz.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">有些监控不一定是以监控的形式，比如下面是定时任务的执行状态。如果状态是failed，就需要查查问题原因了。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kqmd.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">数据报表</span></strong></p>
<p style="">调一调焦距，再宏观一点看。我们需要通过数据报表来分析系统、业务状况，看有哪些需要改进的点。看报表的关键是在看之前要自己有一个心理预期。让预期数据和实际数据做一个对比。这也是junit测试assertEquals的原理。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-oiwa.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">举个例子：</p>
<p style="">业务申请资源时，申请服务器成功时会触发incUse，释放服务器成功时会触发decUse。每个小时数据都是下面这样的：申请服务器几百个，释放服务器十几个，哇，一天要消耗近1万个服务器。资源很快就耗尽了吧？这符合预期吗？</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-itgg.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">再举个例子：不比不知道，看看平均耗时就知道谁做的烂。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dhmy.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">在报表中重点数据要用不同的颜色或者特性符号进行标注方便引起注意。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xjtk.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span fontsize="" color="">复盘</span></strong></p>
<p style="">复盘在工作和生活中用的都很好用。</p>
<p style="">昨天男神发微信给我说学校群里通知说考试了。小鲜肉没在家庭群里晒卷子呢，肯定是没考100啊。果然，晚上我俩回到家看到桌子上有份99分的试卷。作为一个中科院心理学毕业的研究生，我第一个反应：别给孩子贴标签，说马虎、不细心之类的。周末咱们做个复盘。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-npmr.jpg&amp;size=m" width="139px" height="139px" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">复盘原本是围棋术语，在下完一盘棋后，双方棋手把对弈过程重新摆一遍，总结过程中的亮点和不足，对局中招法的优劣与得失，并从中寻找提高水平的方法。</span></p>
<p style=""><strong><span fontsize="" color="">何时复盘？</span></strong></p>
<p style=""><span fontsize="" color="">小事及时复盘，大事阶段性复盘，项目结束后全面复盘。</span></p>
<p style=""><strong><span fontsize="" color="">如何复盘？</span></strong></p>
<p style=""><span fontsize="" color="">复盘四步曲：回顾目标、评估结果、分析原因、总结经验。</span></p>
<p style=""><span fontsize="" color="">在回顾目标阶段，建议使用SMART原则。</span></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uhgk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">下面是一个复盘例子：</span></p>
<p style=""><span fontsize="" color="">我在美团基础架构部参与公司的容器化项目HULK。HULK其中有一个子项目:调度系统SHIELD，目标是负责容器的整个生命周期。就是从容器创建到销毁的整个过程。根据目标来制定我们的SLA是扩缩容成功率，即创建和销毁容器的成功率。</span></p>
<p style=""><strong><span fontsize="" color="">回顾目标</span></strong></p>
<p style=""><span fontsize="" color="">在项目启动之初，我们SLA很不稳定，在18年底，也只有97%。当时我们制定的目标是在19年春节结束之前达到周成功率99%；19年4月前达到周成功率99.9%；19年6月前达到周成功率99.95%。</span></p>
<p style=""><strong><span fontsize="" color="">评估结果</span></strong></p>
<p style=""><span fontsize="" color="">从CAT（美团点评分布式开源监控系统）的统计结果来都完成了目标。</span></p>
<p style=""><strong><span fontsize="" color="">分析原因</span></strong></p>
<p style=""><span fontsize="" color="">能够达成目标的主要原因是从目标出发，制定了一系列以目标导向的问题收敛计划，并严格执行。</span></p>
<p style=""><strong><span fontsize="" color="">总结经验</span></strong></p>
<p style=""><span fontsize="" color="">过程中做的好的方面是每一个问题都用了</span><strong><span fontsize="" color="">5Why分析法</span></strong><span fontsize="" color="">找到问题的根因进行根治。不好的方面是对子模块的指标制定的不合理导致走了一些弯路。具体例子如下：</span></p>
<p style=""><span fontsize="" color="">从失败的数据分析，大概1%的失败是由于资源不足引起的。而资源余量子模块carter的调度成功率却基本维持在100%。出现这种差异的原因是如果资源不足，carter服务应该返回资源为0。这样carter服务实时准确的返回了实际的资源情况，这时候不允许扩容发生，就不会出现失败。而carter服务正常运转，但是返回资源还有余量，让扩容进行到执行真实扩容时才发现扩容失败。</span></p>
<p style=""><span fontsize="" color="">问题的原因是carter的指标出现了问题，不符合SMART原则的Relevant原则，与最终目标的相关性不符合预期。carter的SLA指标应该是：调度成功率*结果的正确率。</span></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mryh.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">如上图所示，调度系统SHIELD将对请求的处理划分为请求层和执行层。请求层处理参数合法性校验、资源校验等校验问题。在请求层拦截掉没有资源还进行扩容的请求是符合系统目标的。这个请求处理算是成功的。但是进入到执行层，由于任何原因导致的容器没有正常创建完成都算作失败的。</span></p>
<p style=""><span fontsize="" color="">所以资源校验的成功率应该定义为是否正常拦截了无资源还进行扩容的请求，即：调度成功率*结果的正确率</span></p>]]></description><guid isPermaLink="false">/archives/xiang-mu-gai-jin-de-si-ge-zhua-shou</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 05:38:15 GMT</pubDate></item><item><title><![CDATA[你看不懂的spring原理是因为不知道这几个概念]]></title><link>https://wiki.dgfygk.com/archives/ni-kan-bu-dong-de-springyuan-li-shi-yin-wei-bu-zhi-dao-zhe-ji-ge-gai-nian</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BD%A0%E7%9C%8B%E4%B8%8D%E6%87%82%E7%9A%84spring%E5%8E%9F%E7%90%86%E6%98%AF%E5%9B%A0%E4%B8%BA%E4%B8%8D%E7%9F%A5%E9%81%93%E8%BF%99%E5%87%A0%E4%B8%AA%E6%A6%82%E5%BF%B5&amp;url=/archives/ni-kan-bu-dong-de-springyuan-li-shi-yin-wei-bu-zhi-dao-zhe-ji-ge-gai-nian" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">背景</span></strong></p>
<p style="">问题从一杯咖啡开始。</p>
<p style="">今天我去楼下咖啡机买了一杯「粉黛拿铁」。制作过程中显示：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-abnt.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">我取了做好的粉黛拿铁，喝了一口，果然就是一杯热巧克力。咦咦咦，说好的拿铁呢？虽然我对「零点吧」的咖啡评价很高，觉得味道不亚于星爸爸。但是不可否认它确实给了我一杯假的拿铁。</p>
<p style="">现在很多技术专家好比品鉴咖啡的专家。他们并不需要知道咖啡豆和可可豆的区别，更不知道这两种植物长在树上是什么样子。没关系，这并不影响他们区别一杯咖啡是拿铁、摩卡还是卡布奇诺。就好像工作中遇到团队配合的情况，他们并不需要知道别人团队的产品是怎么实现的，只需要在他们出问题的时候让他们帮忙解决。所以，现在很多工作招高级别的人都要求良好的沟通和推动能力。技术能力反而考察的没有那么细致。</p>
<p style="">但是作为技术人员，了解底层是很重要的。因为我们不是买咖啡的，而是生产咖啡机的。有一天，咖啡机不工作了，经过分析监控报警和日志大数据，终于找到原因：一粒咖啡豆太小卡到了凹槽里。清理了这粒咖啡豆，咖啡机又正常工作了。这就结束了吗？</p>
<p style=""></p>
<p style="">不，我们要复盘，用5Why分析法找到问题的根因。</p>
<p style="">Q：为什么咖啡机会不工作？</p>
<p style="">A：因为一粒咖啡豆卡在里凹槽里。</p>
<p style="">Q：为什么会卡在凹槽里？</p>
<p style="">A：因为这粒咖啡豆太小了，就掉下去了。</p>
<p style="">Q：为什么太小了就会掉下去？</p>
<p style="">A：因为咖啡豆基本都是圆形，咖啡豆之间有很大的空隙，彼此没有压力。只是受到重力影响，就掉下去了。</p>
<p style="">Q：为什么咖啡豆都是圆形，不能生产出方形，彼此紧密契合的咖啡豆吗？</p>
<p style=""></p>
<p style="">这时候就是后悔不知道咖啡豆到底长在哪种树上的时间了。不了解所以没有办法做出合理的改变。</p>
<p style="">现在我们都喜欢用SpringBoot。平时工作的好好的，突然公司要求做一个基础组件的升级，咦咦咦，本地运行的好好的。发布到服务器上跑不起来。当初搭建开发基础框架的小哥早已离职，是看着那堆日志发愣呢？还是了解一下里面的基础概念顺藤摸瓜呢？废话到此为止。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">概念</span></strong></p>
<p style=""><strong><span fontsize="" color="">SpringBoot是什么？</span></strong></p>
<p style="">SpringBoot是一个框架和编程规范。它化简了很多个框架和组件的使用，一站式简单的完成文件配置和应用部署。因为它内置服务器并装备启动类代码，可以快速开启一个Web容器。</p>
<p style=""><strong><span fontsize="" color="">Web容器是什么？</span></strong></p>
<p style="">Web容器是一种可以处理从客户端发出请求的应用程序。在服务器中一个端口就对应一个提供相应服务的程序。在Java方面，web任凭器一般是指Servlet容器。</p>
<p style=""><strong><span fontsize="" color="">Servlet是什么？</span></strong></p>
<p style="">Servlet只是一个接口。它声明了Servlet的生命周期的三个基本方法：init()、service()和destroy()。Web容器会在特定时间调用这些方法。Servlet是提供了一个标准，是J2EE的13个标准接口规范之一。</p>
<p style=""><strong><span fontsize="" color="">J2EE是什么？</span></strong></p>
<p style="">J2EE(Java 2 Platfrom, Enterprise Edition)最初是一个为大企业主机级的计算类似而设计的Java平台。这个标准给提供可重用模块组件组件提供了土壤。就是定义一系列的接口，你们要实现什么功能都要在这些接口基础上。根据场景Java2平台主要包括标准版(J2SE)、企业版(J2EE)和微缩版(J2ME)三个版本。</p>
<p style=""><strong><span fontsize="" color="">Java2平台是什么？</span></strong></p>
<p style="">Sun公司在1998年发表JDK1.2版本的时候，使用了新名字Java 2 Platform。修改后的JDK成为Java 2 Platfrom Software Developing Kit 即J2SDK。</p>
<p style=""><strong><span fontsize="" color="">Web容器和Netty的关系</span></strong></p>
<p style="">Web容器(如tomcat、jetty)主要处理从客户端传过来的消息，将消息分给worker线程来处理。worker线程做的事情就是通过Servlet来做的。而接受消息线程和worker线程之间的协调沟通工作目前主流是Netty来做的。</p>
<p style="">一张图大体表示一下他们的关系：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ngey.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span style="font-size: 20px">总结</span></strong></p>
<p style="">如果做一件事情很麻烦，那一定有一个好方法。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/RpyKG8FMVBbunVQGL0zovA</p>]]></description><guid isPermaLink="false">/archives/ni-kan-bu-dong-de-springyuan-li-shi-yin-wei-bu-zhi-dao-zhe-ji-ge-gai-nian</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 05:37:53 GMT</pubDate></item><item><title><![CDATA[SpringBoot优雅退出]]></title><link>https://wiki.dgfygk.com/archives/springbootyou-ya-tui-chu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E4%BC%98%E9%9B%85%E9%80%80%E5%87%BA&amp;url=/archives/springbootyou-ya-tui-chu" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span fontsize="" color="">背景</span></strong></p>
<p style=""><span fontsize="" color="">我看了一些介绍SpringBoot源码的文章，里面都是从</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">SpringApplication这个外部调用入口开始，重点介绍里面的listener监听器。今天我想想说说SpringBoot的优雅退出机制。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">调用入口调用SpringApplication开始启动SpringBoot</span></p>
<pre><code>@SpringBootApplicationpublic&nbsp;class&nbsp;Application&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;throws&nbsp;Exception&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SpringApplication.run(Application.class,&nbsp;args);&nbsp;&nbsp;&nbsp;&nbsp;}}</code></pre>
<p style=""><span style="font-size: 18px; color: rgb(0, 0, 0)">SpringApplication实例初始化时有以下操作</span></p>
<pre><code>public&nbsp;SpringApplication(Object...&nbsp;sources)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;this.bannerMode&nbsp;=&nbsp;Mode.CONSOLE;&nbsp;&nbsp;&nbsp;&nbsp;this.logStartupInfo&nbsp;=&nbsp;true;&nbsp;&nbsp;&nbsp;&nbsp;this.addCommandLineProperties&nbsp;=&nbsp;true;&nbsp;&nbsp;&nbsp;&nbsp;this.headless&nbsp;=&nbsp;true;&nbsp;&nbsp;&nbsp;&nbsp;this.registerShutdownHook&nbsp;=&nbsp;true;&nbsp;&nbsp;&nbsp;&nbsp;this.additionalProfiles&nbsp;=&nbsp;new&nbsp;HashSet();&nbsp;&nbsp;&nbsp;&nbsp;this.initialize(sources);}</code></pre>
<p style=""><span style="font-size: 14px">其中有一步this.registerShutdownHook注册jvm退出前的事件。</span></p>
<p style=""><strong><span fontsize="" color="">概念</span></strong></p>
<p style=""><span style="font-size: 14px">jvm有shutdwonHook机制，中文习惯叫优雅退出。相当于在linux系统中执行SIGTERM(kill -15 或者 svc -d)时退出前执行的一些操作。</span></p>
<p style=""><strong><span fontsize="" color="">原理</span></strong></p>
<p style=""><span style="font-size: 14px">以linux系统为例说明：</span></p>
<p style=""><span style="font-size: 14px">进程在响应kill -15 pid命令进行关闭操作时默认发送SIGTERM信号至指定进程/进程组。如果进程没有捕获该信号的逻辑，则SIGTERM的作用是终止进程。而registerShutdownHook=true说明有注册的事件需要捕获该信号，先执行相应的逻辑再进行终止。</span></p>
<pre><code>/***&nbsp;Register&nbsp;a&nbsp;shutdown&nbsp;hook&nbsp;with&nbsp;the&nbsp;JVM&nbsp;runtime,&nbsp;closing&nbsp;this&nbsp;context*&nbsp;on&nbsp;JVM&nbsp;shutdown&nbsp;unless&nbsp;it&nbsp;has&nbsp;already&nbsp;been&nbsp;closed&nbsp;at&nbsp;that&nbsp;time.*&nbsp;&lt;p&gt;Delegates&nbsp;to&nbsp;{@code&nbsp;doClose()}&nbsp;for&nbsp;the&nbsp;actual&nbsp;closing&nbsp;procedure.*&nbsp;@see&nbsp;Runtime#addShutdownHook*&nbsp;@see&nbsp;#close()*&nbsp;@see&nbsp;#doClose()*/@Overridepublic&nbsp;void&nbsp;registerShutdownHook()&nbsp;{&nbsp;&nbsp;&nbsp;if&nbsp;(this.shutdownHook&nbsp;==&nbsp;null)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;No&nbsp;shutdown&nbsp;hook&nbsp;registered&nbsp;yet.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.shutdownHook&nbsp;=&nbsp;new&nbsp;Thread()&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;run()&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;synchronized&nbsp;(startupShutdownMonitor)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;doClose();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Runtime.getRuntime().addShutdownHook(this.shutdownHook);&nbsp;&nbsp;&nbsp;}}</code></pre>
<p style=""><span style="font-size: 14px">如上面spring里</span>registerShutdownHook的源码所示，就是注册一个jvm的shutdownHook钩子函数。jvm退出前会执行这个钩子函数。这个关闭操作在强制退出的时候不起作用。强制关闭范围详见下图</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-juqt.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">一个SpringBoot项目在优雅退出时做的事情可以通过log来感受一下</p>
<pre><code>2019/06/20 20:47:20.405 Thread-29 [INFO] NettyHttpServer (NettyHttpServer.java:171) Close NettyHttpServer bind 0.0.0.0/0.0.0.0:5080
2019/06/20 20:47:20.405 Thread-36 [INFO] AnnotationConfigApplicationContext (AbstractApplicationContext.java:984) Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@a37aefe: startup date [Thu Jun 20 20:45:38 CST 2019]; root of context hierarchy
2019/06/20 20:47:20.406 Thread-32 [INFO] NettyServer (NettyServer.java:130) stopping netty server(XXX:9000), sleep 6 seconds!
2019/06/20 20:47:20.407 Thread-35 [INFO] ScheduleManager (ScheduleManager.java:373) Before taskNode quit,check executing task and callback result.
2019/06/20 20:47:20.407 Thread-35 [INFO] ScheduleManager (ScheduleManager.java:228) Destroy task node resources: zk and threadpool.
2019-06-20 20:47:20,430 Thread-35 WARN Unable to register Log4j shutdown hook because JVM is shutting down. Using SimpleLogger</code></pre>
<p style=""><strong><span fontsize="" color="">总结</span></strong></p>
<p style="">分析问题先找抓手</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/V_sb3vvKTiAiaOyv3yYY8A</p>]]></description><guid isPermaLink="false">/archives/springbootyou-ya-tui-chu</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 02:48:11 GMT</pubDate></item><item><title><![CDATA[Java异常处理总结]]></title><link>https://wiki.dgfygk.com/archives/javayi-chang-chu-li-zong-jie</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E6%80%BB%E7%BB%93&amp;url=/archives/javayi-chang-chu-li-zong-jie" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 背景&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-weic.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify; ">最近专门负责团队的项目质量。我在治理异常日志过程中，总结了一下Java的异常处理。上面是我整理的最近自己比较常见的异常知识地图。</p>
<p style="text-align: justify; "><span fontsize="" color="">
  <br>
 </span></p>
<p style="line-height: 25px"><strong>&nbsp; 异常知识地图概述&nbsp;&nbsp;</strong></p>
<p style="text-align: justify; "><span fontsize="" color="">从</span>异<span fontsize="" color="">常知识地图最左边的根开始看，地图从左到右的连线连接的类之间有实实在在的父子关系，在java里通过继承来实现（除了非RuntimeException是个虚拟父节点）。</span></p>
<p style="text-align: justify; "></p>
<p style=""><span style="font-size: 17px; color: rgb(172, 57, 255)">☆</span>Java所有异常的父类是Throwable，它又分为Error和Exception。</p>
<p style=""><span style="font-size: 17px; color: rgb(172, 57, 255)">☆&nbsp;</span>Error是程序判定如果执行了XX逻辑，则应该是至少JVM层面出现了问题。正常情况下不应该发生的。</p>
<p style=""><span style="font-size: 17px; color: rgb(172, 57, 255)">☆&nbsp;</span>Exception意思是环境没有什么问题，出现Exception请开发人员自己搞定。</p>
<p style=""><span style="font-size: 16px; color: rgb(172, 57, 255)">☆&nbsp;</span>Exception分为RuntimeException运行时异常和非运行时异常。</p>
<p style="text-align: justify; "></p>
<p style="text-align: justify; ">说到这里，我们从另外一个维度给异常分类。Java异常又分为检查异常和非检查异常。Error和RuntimeException以及<span fontsize="" color="">RuntimeException</span>的子类是非检查异常。其他是检查异常。这个很好区分。在写Java代码的时候，编译器提示需要try catch或者throws的就是检查异常。其他是非检查异常。后面在具体代码实现里有体现。</p>
<p style="text-align: justify; "></p>
<p style=""><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">☆</span><span style="font-size: 16px; color: rgb(172, 57, 255)">&nbsp;</span><span fontsize="" color="">异常分为检查异常和非检查异常。&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span></p>
<p style="text-align: justify; "></p>
<p style="text-align: justify; "><span fontsize="" color="">
  <br>
 </span></p>
<p style="line-height: 25px"><strong>&nbsp; &nbsp;典型异常发生场景&nbsp; &nbsp;</strong></p>
<p style="text-align: justify; "><span fontsize="" color="">典型异常发生的场景我做了一些demo，上传到了github，地址：</span></p>
<p style="text-align: justify; "><span fontsize="" color="">https://github.com/xiexiaojing/yuna</span></p>
<p style="text-align: justify; "><span fontsize="" color="">为了方面展示使用一个统一的切面来截获异常：</span></p>
<pre><code>@RestControllerAdvicepublic class ControllerThrowableAdvice {
    @ExceptionHandler(Throwable.class)    public String handleThrowable(Throwable e) {        return "ControllerThrowableAdvice消息:" + e.toString();    }}</code></pre>
<p style=""><strong><span style="font-size: 20px">Error</span></strong></p>
<p style="">Error及其子类一般不是用来捕获的，而用来抛出的。因为Error的发生意味着环境有问题，该停下来检修了。所以一般的处理是一旦发生Error，会停止JVM。也就是平时看到的程序起不来。如下java.awt.image.Kernel的源码。</p>
<p style="text-align: justify; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tgad.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify; ">Error除了手工抛出，在常用的类库中不用黑科技是不能稳定复现的。所以我测试类是这么写的</p>
<p style="text-align: justify; ; line-height: 26px"><code>@GetMapping("/errorThrowable")public String showErrorThrowable(){ Error error = new Error("人工抛出一个Error"); throw error;}</code></p>
<p style="">直接访问页面的结果</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qaiy.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span fontsize="" color="">ControllerThrowableAdvice</span><span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">消息:&nbsp;</span></p>
<p style="text-align: justify; "><span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">org.springframework.web.util.NestedServletException:Handler dispatch failed; nested exception is java.lang.Error: 人工抛出一个Error</span></p>
<p style="text-align: justify; "><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">上面错误消息意思是spring mvc通过其核心逻辑</span><span fontsize="" color="">DispatcherServlet</span>没有找到任何一个可以处理这个返回model的，因为直接返回就是一个Error。<span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">最后显示的消息通过ControllerThrowableAdvice进行展示。</span></p>
<p style="text-align: justify; "><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">注意Error是非检查异常，不用显示处理。</span></p>
<p style="text-align: justify; "><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style="text-align: justify; "><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">NPE</span></strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style="text-align: start; "><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">NPE也就是平时说的空指针异常，它非常常见，很多类都没有对null做支持。直到apache提供了common包专门来处理这种情况。防不胜防，时不时项目还是需要为了处理这个异常上线个bugfix。</span></p>
<p style="text-align: justify; ; line-height: 26px"><code>@GetMapping("/npe")public String showNullPointerException() { new HashSet&lt;String&gt;(null); return prefix + "异常未抛出";}</code></p>
<p style="text-align: start; ">直接访问页面的结果</p>
<p style="text-align: start; "><span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">ControllerThrowableAdvice消息:java.lang.NullPointerException</span></p>
<p style="text-align: start; "><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">这是因为new HashSet的时候传入null。程序走不到return就抛出异常了。</span><span fontsize="" color="">最后显示的消息通过</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">ControllerThrowableAdvice</span>进行<span fontsize="" color="">展示。</span></p>
<p style="text-align: start; "><span fontsize="" color="">注意NullPointerException是非检查异常，不用显示处理。</span></p>
<p style="text-align: start; "><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">算数异常</span></strong></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">算数异常非常常见，比如做除0，会</span>抛出异常<span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">java.lang.ArithmeticException: / by zero</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">，提示我们该找数学老师帮我们检查作业了。值得注意的是</span>如果使用BigDecimal.divide来做除法，请直接使用divide(BigDecimal divisor, int scale, RoundingMode roundingMode)这个传3个参数的，避免</p>
<p style="">divide(BigDecimal divisor)这个传1个参数的，因为如果传的值除不尽会抛出<span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.&nbsp;</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">带三个参数的方法会在除不尽的时候按照传入的摄入模式和保留小数点后的位数对数据做处理。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">注意</span><span fontsize="" color="">ArithmeticException</span>是非检查异常，不用显示处理。</p>
<p style="text-align: start; "></p>
<p style="text-align: start; "><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">未声明异常</span></strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style="">未声明异常代码量稍大，想知道测试源码的直接去我github里下载。</p>
<p style="">地址：<span fontsize="" color="">https://github.com/xiexiaojing/yuna</span></p>
<p style="">抛出异常的原理是使用动态代理时，如果被代理的类抛出了一个异常。但是却没有throws声明。代理类找不到匹配的异常类型会抛出InvocationTargetException。从知识地图上可以看到它是非检查异常。最后会被<span style="font-size: 12ptpx; color: rgb(0, 0, 0)">UndeclaredThrowableException来处理。这是java动态代理不优雅的处理方式。建议喜欢看源码、模仿源码的朋友对这一点不要借鉴哦。</span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">重点来看一下运行抛出异常的打印堆栈
  <br></span></p>
<pre><code>java.lang.reflect.UndeclaredThrowableException  at com.sun.proxy.$Proxy17.showException(Unknown Source)  at com.brmayi.yuna.controller.ExceptionController.showUndeclaredThrowableException(ExceptionController.java:62)  at com.brmayi.yuna.controller.IndexControllerTest.showUndeclaredThrowableException(IndexControllerTest.java:34)  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  at java.lang.reflect.Method.invoke(Method.java:498)  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)  at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:44)  at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)  at org.junit.runners.ParentRunner.run(ParentRunner.java:363)  at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:74)  at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)  at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)  at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)  at org.junit.runner.JUnitCore.run(JUnitCore.java:137)  at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)  at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)  at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)  at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  at java.lang.reflect.Method.invoke(Method.java:498)  at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)Caused by: java.lang.reflect.InvocationTargetException  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  at java.lang.reflect.Method.invoke(Method.java:498)  at com.brmayi.yuna.util.ObjProxy.invoke(ObjProxy.java:14)  ... 35 moreCaused by: java.lang.ArithmeticException: / by zero  at com.brmayi.yuna.service.ShowUndeclaredThrowableExceptionService.showException(ShowUndeclaredThrowableExceptionService.java:5)  ... 40 more</code></pre>
<p style="">从打印的堆栈可以看到这个是最终的ArithmeticException抛出时被InvocationTargetException捕获。将原来的参数传给了InvocationTargetException后继续抛出，最后被UndeclaredThrowableException捕获。</p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">注意</span><span fontsize="" color="">UndeclaredThrowableException</span>是非检查异常，不用显示处理。</p>
<p style=""><strong><span style="font-size: 20px">不合法参数异常</span></strong></p>
<p style="">不合法参数异常是很多类或者方法自己定义了java基本规则外的一些规则，不满足会抛出的异常。比如java动态代理的源码里就写了被代理的接口不能超过65535个。否则就抛不合法参数异常。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bupe.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">注意<span fontsize="" color="">IllegalArgumentException</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">是非检查异常，不用显示处理。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">以上都说的是非检查异常。下面开始检查异常。由于IO异常很常见好构造，我们直接来看它的子类。</span></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">套接字异常</span></strong></p>
<p style="">套接字异常在通信编程时非常常见。比如如下代码</p>
<pre><code> @GetMapping("/socket")public String showSocketException() throws Exception {    ServerSocket socket = new ServerSocket(8081);    socket.close();    socket.setReuseAddress(true);    return prefix + "异常未抛出";}</code></pre>
<p style="">启动了一个套接口服务端，马上关闭。关闭后才去调用setReuseAddress。这时候就会抛出<span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">java.net.SocketException: Socket is closed</span>。</p>
<p style=""><span fontsize="" color="">注意</span><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">SocketException是</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">检查异常，需要显示处理。</span></p>
<p style=""><strong><span style="font-size: 20px">绑定异常</span></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;套接字异常有一种情况，可以明确的知道是绑定异常，就不用抛出套接字异常这样模糊的异常了。</p>
<pre><code>@GetMapping("/binding")public String showBindingException() throws Exception {    ServerSocket socket = new ServerSocket(80);    socket.setReuseAddress(true);    return prefix + "异常未抛出";}</code></pre>
<p style="">如上，80端口是http默认端口，不能在自定义通信程序里使用。这时候就会抛出<span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">java.net.BindException: Permission denied</span>。</p>
<p style=""><span fontsize="" color="">注意</span><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">BindException是</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">检查异常，需要显示处理。</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(172, 57, 255)">主机名未知异常</span></strong></p>
<p style="">主机名未知异常在比如内网DNS出现问题、或者远程调用时由于机器下线等原因找不到主机时出现。可以人为连接一个未启用的端口来构造。</p>
<pre><code>@GetMapping("/unknownHost")public String showUnknownHostException() throws Exception {    new Socket("ttt", 5300);    return prefix + "异常未抛出";}</code></pre>
<p style=""><span fontsize="" color="">注意</span>UnknownHostException<span style="font-size: 12ptpx; color: rgb(0, 0, 0)">是</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">检查异常，需要显示处理。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 20px">超时异常</span></strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style="">超时异常因为在分布式系统中涉及程序内部线程间、程序之间的通信多，所以非常常见。具体代码有点长，详见</p>
<p style="">https://github.com/xiexiaojing/yuna</p>
<p style=""><span fontsize="" color="">抛出</span><span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">java.util.concurrent.TimeoutException</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">。它是concurrent包里的一个类。
  <br></span></p>
<p style=""><span fontsize="" color="">注意</span><span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">TimeoutException</span><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">是</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">检查异常，需要显示处理。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 20px">反射操作异常及其子类</span></strong></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">反射操作异常一般只在启动时看到，线上程序运行中一般不会发生。因为常见类里它是这么处理的</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iuro.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">上面可以看到在java.net.InetAddress的源码里，ReflectiveOperationException的处理是直接抛出Error。</p>
<p style="">在程序启动时，经常会由于maven pom里引入的包冲突、版本不合适、或者是缺少包引起「类找不到异常」。例如下面的测试例子：</p>
<pre><code>@GetMapping("/classNotFound")public String showClassNotFoundException() throws Exception {    Class.forName("com.XXX");    return prefix + "异常未抛出";}</code></pre>
<p style="">由于com.XXX不存在。会直接抛出<span fontsize="" color="rgb(123, 12, 0)" style="color: rgb(123, 12, 0)">java.lang.ClassNotFoundException: com.XXX</span>。它是反射操作异常的子类。平时反射操作异常及它的子类异常一旦发生就会<span fontsize="" color="">抛出Error</span>，JVM停止。如下面的源码：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nzeu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="line-height: 25px"><strong>Spring对于异常的处理</strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">默认异常处理</span></strong></p>
<p style="">Spring的MVC在默认情况下对不能处理的异常如404、500会抛出白页。像下面这样：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-eaea.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这是因为Spring MVC的核心处理类DispatcherServlet的doDispatch方法包含代码片段：</p>
<pre><code>mappedHandler = getHandler(processedRequest);// 找到合适的请求处理器if (mappedHandler == null || mappedHandler.getHandler() == null) {    // 原则上如果没有找到则会进入到这里，并且设置response的状态码为404    // 但是经过调试并没有进入到这里    noHandlerFound(processedRequest, response);    return;}</code></pre>
<p style="">它最终处理是返回/error页。也就是白页。不知道大家有没有注意到我前面在介绍Error的时候，定义了error页面的url地址为errorThrowable。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mvxo.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">这是因为error是被Spring自身占用了。如果定义为error，我们将看不到预期的结果，而是下面的白页</span></p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ptqz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这里因为/error是默认页面，返回了999的http错误码，意思是请求被拒绝。</p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">自定义异常处理</span></strong></p>
<p style="">在典型异常的发生场景里一开始就介绍了定义了一个统一错误处理如下：</p>
<pre><code>@RestControllerAdvicepublic class ControllerThrowableAdvice {
    @ExceptionHandler(Throwable.class)    public String handleThrowable(Throwable e) {        return "ControllerThrowableAdvice消息:" + e.toString();    }}</code></pre>
<p style="">这是使用了spring aop做了统一拦截。Advice在AOP的概念中翻译成增强。包括Before、After、Around等增强时机。这里类名用到了Advice意思是在controller发生Throwable时做的增强。看到有的项目喜欢用</p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>@ExceptionHandler(Exception.class)</code></pre>
<p style="">这个也OK。但是我会假设Everything fails! 程序在发生平时不会遇到的问题时也可控。</p>
<p style=""></p>
<p style="line-height: 25px"><strong>&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 总结&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;</strong></p>
<p style="">本文先围绕着异常知识地图介绍了各种异常及出现场景，最后结合Spring论述了在实际工作中如何统一处理异常。这里推荐一个学习方法：梳理知识地图，给地图框架填充内容，让自己的知识体系化。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/Q2HGf0qsS6jrAar-UnPJZQ</p>]]></description><guid isPermaLink="false">/archives/javayi-chang-chu-li-zong-jie</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 02:45:41 GMT</pubDate></item><item><title><![CDATA[从jackson的使用和源码看程序设计]]></title><link>https://wiki.dgfygk.com/archives/cong-jacksonde-shi-yong-he-yuan-ma-kan-cheng-xu-she-ji</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BB%8Ejackson%E7%9A%84%E4%BD%BF%E7%94%A8%E5%92%8C%E6%BA%90%E7%A0%81%E7%9C%8B%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1&amp;url=/archives/cong-jacksonde-shi-yong-he-yuan-ma-kan-cheng-xu-she-ji" width="1" height="1" alt="" style="opacity:0;">
<p style="">背景</p>
<p style=""></p>
<p style="">每隔一段时间，我会梳理一些平时经常用的类库源码。这次和大多数时候一样，我又选了一个简单的来看点源码。这次选的Jackson的ObjectMapper类源码。</p>
<p style=""></p>
<p style="">为什么我要选简单的来看呢？因为提高技术知识只是问题的表面，根本是要提高自己认知问题、分析问题、解决问题的思想高度。研究的问题越复杂，越容易将精力放在解决问题的本身，而阻碍了思想高度的提升。</p>
<p style=""></p>
<p style=""><span fontsize="" color="">同样，这次我们还是套用思考框架来完成这个工作。这次使用学习技术的三步曲what、why、how来思考。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""></p>
<p style="">What</p>
<p style=""></p>
<p style=""></p>
<p style="">Jackson是Java生态圈中处理JSON和XML的类库，是spring-boot默认的JSON解析框架。这是因为spring mvc会自动注册MappingJackson2HttpMessageConverter，从而支持json 输出。</p>
<p style=""></p>
<p style="">Jackson提供了三种数据处理方式。分别是数据绑定、树模型、流式API。其中，<span style="font-size: 16px; color: rgb(77, 77, 77)">数据绑定用于JSON转化，可以将JSON与POJO对象进行转化。</span>是最符合面向对象的一种使用方式。流式API是江湖传闻效率最高的一种使用方式。</p>
<p style=""></p>
<p style="">Why</p>
<p style=""></p>
<p style=""></p>
<p style="">Jackson相对Gson、JSON-lib更为高效，使用灵活方便，所以应用广泛。</p>
<p style=""></p>
<p style="">注意fastjson的效率一般情况下是要超过Jackson的。但是像spring-boot这样的开源平台，最好的方法还是使用更成熟的技术。因为<span fontsize="" color="">像这种JSON解析框架是很容易遭受攻击的对象。自己动手实践一下，可发现，Jackson的性能瓶颈主要在ObjectMapper实例化上。而这个类在实际项目使用时都是被封装好的，可以启动时实例化一次，所以在运行时，效率上谁高谁低还是有待商榷的。</span></p>
<p style=""></p>
<p style=""></p>
<p style="">How</p>
<p style=""></p>
<p style=""></p>
<p style="">和大多数人一样，我也是一看源码就头大。所以我的思路是先从简单的demo入手。代码我上传到了github上。地址：</p>
<p style="">https://github.com/xiexiaojing/yuna</p>
<p style=""></p>
<p style="">值得一提的是：这虽然是一个经典学习场景的学习工具工程，但是完全可以<span fontsize="" color="">作为生产环境的基础代码来用。我们线上跑的代码架子和这个差不了多少。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><strong><span fontsize="" color="">数据绑定方式源码分析</span></strong></p>
<p style=""><strong><span fontsize="" color="">
   <br>
  </span></strong></p>
<p style=""><span fontsize="" color="">我们直</span>接从转换类<span fontsize="" color="">ObjectMapper的源码开始看。</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px">1，采用数组做缓存，一开始就分配好空间，提高效率。</span></strong></p>
<p style=""><strong><span style="font-size: 18px">
   <br>
  </span></strong></p>
<p style="line-height: 26px"><code>public String writeStringAsString(String toWrite) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.writeValueAsString(toWrite);}</code></p>
<p style=""></p>
<p style="">写个测试类，传入null。这时候将看到结果</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wdba.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">跟踪源码可看到真正产生这个结果的是WriterBasedJsonGenerator这个类。它就是打印了个null。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ivtd.png&amp;size=m" width="990px" height="990px" style="display: inline-block"></p>
<p style="">从源码看到打印时采用了Buffer缓存，从一开始就分配好空间。底层采用数据结构读取速度快，效率高。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">2，字符转义处理，做好安全工作。</span></strong></p>
<p style="">再给上面的程序，写个测试类，传入任意字符串。这时候将看到结果</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sucu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">字符串被原样打出来，只是两边多了两个引号，代表是字符串，不是数字。</p>
<p style=""></p>
<p style="">看一下处理的源码，还是WriterBasedJsonGenerator这个类。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wroq.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">对于文本，首先判断文本是否太长，长的话就跳到另一个方法，这个方法用将字符串分段分而治之。看到<span style="font-size: 12ptpx; color: rgb(0, 0, 0)">Escape这个单词，就要想到为了信息安全而将字符转义了。在公司或者github不时会收到jackson、fastjson漏洞，要求升级到XXX版本以上。对，所谓的升级补漏洞最重要的就是给需要</span><span style="font-size: 16px; color: rgb(0, 0, 0)">Escape的列表加了</span>些字符。</p>
<p style=""></p>
<p style="">在文章What部分提到：「流式API是江湖传闻效率最高的一种使用方式」。我们先来验证一下这个传闻是不是真的。</p>
<p style=""></p>
<p style="">首先构造一个Pojo：</p>
<pre><code>@Datapublic class Pojo {    private int id;    private String value;}</code></pre>
<p style=""></p>
<p style="">使用数据绑定方式</p>
<pre><code>public String writePojoAsString(Pojo toWrite) throws Exception {    return objectMapper.writeValueAsString(toWrite);}</code></pre>
<p style=""></p>
<p style="">使用流式API</p>
<pre><code> public String writeJsonGenerator(Pojo toWrite) throws Exception {        JsonFactory factory = new JsonFactory();        ByteArrayOutputStream os = new ByteArrayOutputStream(); //不能加上缓冲  有新增的方法        JsonGenerator jsonGenerator = factory.createGenerator(os, JsonEncoding.UTF8);        //对象开始        jsonGenerator.writeStartObject();        jsonGenerator.writeNumberField("id", toWrite.getId());        jsonGenerator.writeStringField("value", toWrite.getValue());        jsonGenerator.writeEndObject();        jsonGenerator.flush();        jsonGenerator.close();        return os.toString();&nbsp;&nbsp;&nbsp;&nbsp;}</code></pre>
<p style=""></p>
<p style="">因为初始化时要先实例化ObjectMapper，有时间消耗。为了不让流式API转这个便宜。将两个要测试的方法写到一个类里，初始化时先实例化ObjectMapper。并且先运行流式API。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-azxt.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">测试结果来看流式API要比数据绑定快近十倍。如果交换执行顺序会发现差距更为明显。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uvyc.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""></p>
<p style="">跟进ObjectMapper里就会发现，底层其中数据绑定调用的也是同样的API。只是它在进行数据转换的同时还进行了其他的工作。这些工作比如转义、分段等。就看这些是不是我们需要的。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-urra.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">总结</p>
<p style=""></p>
<p style="">说自己掌握了一门语言至少要掌握基本的语法和数据结构、核心库、常用第三方框架、流行的开发框架和部署方法。而这些知识底层有很强的关联性，不断增加自己的知识的深度，更好的触类旁通。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/f6Obmv0OdeCdUwR6Cq-eGA</p>]]></description><guid isPermaLink="false">/archives/cong-jacksonde-shi-yong-he-yuan-ma-kan-cheng-xu-she-ji</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 02:27:14 GMT</pubDate></item><item><title><![CDATA[java的Closeable接口和Cloneable接口]]></title><link>https://wiki.dgfygk.com/archives/javade-closeablejie-kou-he-cloneablejie-kou</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=java%E7%9A%84Closeable%E6%8E%A5%E5%8F%A3%E5%92%8CCloneable%E6%8E%A5%E5%8F%A3&amp;url=/archives/javade-closeablejie-kou-he-cloneablejie-kou" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">概述</span></strong></p>
<p style=""></p>
<p style="">java的Closeable和Cloneable两个接口，一字之差，但是要完成的功能却基本没有什么联系。最大的相同点在于，这两个就是java基本类库有特殊处理的接口，不按规则来，加班两行泪。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">Closeable接口</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">概念</span></strong></p>
<p style=""></p>
<p style="">Closeable是可以关闭的数据源或目标。调用close方法可释放对象保存的资源（如打开文件）。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">原理</span></strong></p>
<p style=""></p>
<p style="">这个实现是靠规则来保证的。实现类必须保证需要有资源释放，并且调用close接口可以实现资源的释放。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">应用</span></strong></p>
<p style=""></p>
<p style="">在网上看到很多OkHttp来进行http请求的例子，里面的Response都没调用close方法进行关闭。将例子拷贝下来压测发现，会偶尔出现下面的告警：</p>
<p style=""><span style="font-size: 14px">[WARN] OkHttp ConnectionPool OkHttpClient&nbsp; A connection to XXX was leaked. Did you forget to close a response body? To see where this was allocated, set the OkHttpClient logger level to FINE: Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);</span></p>
<p style="">并且部分请求没有得到响应。</p>
<p style=""></p>
<p style="">调整程序，实用try-with-resource方法关闭连接后，问题解决。这就是典型的资源没释放引起问题的例子。</p>
<p style=""></p>
<p style="">在JDK1.7之后，只要实现了AutoCloseable或者Closeable接口的类或接口，都可以使用try-with-resource来实现异常处理和资源关闭。这种方法比手动在try外面定义资源，finally里调用close手动关闭资源代码更优雅。</p>
<p style=""></p>
<p style="">Cloneable接口</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">概念</span></strong></p>
<p style=""></p>
<p style="">Cloneable接口是一个标记接口，里面没有定义任何内容。主要是和Object的clone方法配合使用。如果没有实现这个接口，而调用了clone方法，会抛出CloneNotSupportException。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">原理</span></strong></p>
<p style="">
 <br>
 这个方法是为了弥补Object的clone方法缺陷。Object默认是浅拷贝。也就是说它并不会把对象的所有属性拷贝一份，而是有选择性的拷贝。拷贝规则如下：
 <br>
</p>
<p style="">1.基本类型，值拷贝。
 <br>
 2.字符串类型，生成新对象，类似值拷贝。
 <br>
 3.对象，地址引用拷贝。
 <br>
 <br>
 这样做的优点是节约内存。但是为了确保用户使用的时候，是了解自己用的什么拷贝，必须实现Cloneable接口。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">应用</span></strong></p>
<p style="">
 <br>
 很多时候，我们需要实现深拷贝，比如需要每一份拷贝都对值做一定修改。这样深拷贝可避免对原值造成影响。
</p>
<p style=""></p>
<p style="">实现深拷贝，从代码上有两个比较简单的方法。一个是利用序列化和反序列化<span style="font-size: 16px">SerializationUtils.clone方法。还可以用apache的common包中的
  <br>BeanUtils.</span><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">copyProperties来实现。</span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">原文链接：</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">https://mp.weixin.qq.com/s/sdUChb1Nf3qWI8H7gTpzaw</span></p>]]></description><guid isPermaLink="false">/archives/javade-closeablejie-kou-he-cloneablejie-kou</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Wed, 25 Sep 2024 02:13:02 GMT</pubDate></item><item><title><![CDATA[Java&Spring过时的经典语录]]></title><link>https://wiki.dgfygk.com/archives/java-springguo-shi-de-jing-dian-yu-lu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%26Spring%E8%BF%87%E6%97%B6%E7%9A%84%E7%BB%8F%E5%85%B8%E8%AF%AD%E5%BD%95&amp;url=/archives/java-springguo-shi-de-jing-dian-yu-lu" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">字符串拼接：请用StringBuffer代替String直接相加提高性能</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">过去的理论</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style="">有没有人告诉过你开发中不要</p>
<p style="">String newString = "牛郎"+"织女";</p>
<p style="">而是要根据是否线程安全采用</p>
<p style="">String newString = new StringBuffer("牛郎").append("织女").toString();</p>
<p style="">或者</p>
<p style="">String&nbsp;newString =&nbsp;new&nbsp;StringBuilder("牛郎").append("织女").toString();</p>
<p style=""></p>
<p style=""><span fontsize="" color="">因为使用过程中会创建多个字符串对象，而String内部是一个固定长度的字符串数组，所以采用这种方法会浪费内存。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">更新的理论</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style="">在jdk8之前，+拼接底层使用的是concat做字符串拼接就是说：</p>
<p style="">String newString = "牛郎"+"织女";</p>
<p style="">等价于</p>
<p style="">String newString = "牛郎".concat("织女");</p>
<p style=""></p>
<p style="">String#concat底层将要拼接的字符串以数组的形式复制到一个新数组中，空间开销确实大。但是jdk8对+的拼接底层做了优化：</p>
<p style="">编译期会把所有的可以final的字符串合成一个字符串。原理就和一般人会用</p>
<p style=""><span fontsize="" color="">final long dayInMillisecond = 24*3600*1000;</span></p>
<p style=""><span fontsize="" color="">来代替直接将后面的值计算出来，因为这样更直观，执行期效率是一样的。</span></p>
<p style=""></p>
<p style="">而对于需要动态拼接的，会内部转成StringBuilder处理。所以对于动态字符串一般来说效果是一样的，对于静态效率反而高些。</p>
<p style=""></p>
<p style="">但是凡事都有特例，也有jdk处理不够智能的地方。但是根据java的走势来说，符合：简单即正义。长远来说，越简单的写法越是jdk优化的重点，效率还会有提升的空间。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 17px; color: rgb(172, 57, 255)">其他的用法</span></strong></p>
<p style=""><strong><span style="font-size: 17px; color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style="">除了一般的字符串拼接，有时候还需要将列表等一些集合用符号（比如,）连接起来，java8以上提供了StringJoiner来完成这件事。比如list拼接：</p>
<p style="">Lists.newArrayList("迢迢牵牛星",</p>
<p style="">"皎皎河汉女"<span fontsize="" color="">)</span></p>
<p style=""><span fontsize="" color="">.stream().collect(Collectors.joining(","));</span></p>
<p style="">底层用的就是StringJoiner。</p>
<p style=""></p>
<p style="">如果使用google的guava的话，这种情况还有更简单的写法：</p>
<p style="">Joiner.on(",").join(Lists.newArrayList("迢迢牵牛星","皎皎河汉女"));</p>
<p style="">它底层用的是StringBuilder和StringJoiner底层是一样的。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">面试简述SpringMVC的工作原理</span></strong></p>
<p style=""><strong><span style="font-size: 20px">
   <br>
  </span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">过去的理论</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style="">记得15年前面试必备的一道题，面试者需要像小时候背《咏鹅》一样倒背如流：SpringMVC的工作原理。经典回答是这样：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F2tk5ianItRl8Az4up9EHVpxPlb9DDzr5n8f7qZj6YJGVibkhmbE8Eje3WsRZkoWAGRibvshxYTxMtRy66ialGo8x3Q%2F640%3Fwx_fmt%3Dpng%26tp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="677px" height="auto" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">SpringMVC 框架是以请求为驱动，围绕 Servlet 设计，将请求发给控制器，然后通过模型对象，分派器来展示请求结果视图。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">更新的理论</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style=""><span fontsize="" color="">早在Spring3里就封装好了RESTful风格开发方式，开发者只需要将请求的Controller替换成RestController或者方法上加上@ResponseBody即可。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">目前在前后端分离的场景下，经典的SpringMVC中请求流程基本不全用上。
  <br></span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">Spring提供了两种方法将资源的Java表述形式转换为发送给客户端的表述形式：内容协商和消息转换器。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">内容协商就是经典的方法，当控制器的处理方法完成时，返回一个逻辑视图。内容协商是一个特殊的视图解释器。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style="">消息转换提供了一种更为直接的方式，DispatcherServlet不再需要那么麻烦地将模型数据传送到视图中。只是控制器产生数据给消息转换器后就直接返回给客户端了。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">使用HttpClient还是OkHttp来做http请求</span></strong></p>
<p style=""><strong><span style="font-size: 20px">
   <br>
  </span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">过去的理论</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style="">有没有人告诉过你使用OkHttp来代替HttpClient更简洁高效。OkHttp使用build模式创建对象，而且在发送异步请求的时候不需要引入其他的依赖。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">更新的理论</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style="">在基于Spring的JAVA服务端开发中一般会使用各种框架帮我们去完成各种单调重复的工作，比如不管是HttpClient还是OkHttp都需要将返回的reponse自己用编解码工作转成对象再处理。但是如果使用feign则可以省去。</p>
<pre><code></code></pre>
<pre><code>&lt;dependency&gt;
    &lt;groupId&gt;com.netflix.feign&lt;/groupId&gt;
    &lt;artifactId&gt;feign-core&lt;/artifactId&gt;
    &lt;version&gt;8.18.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.netflix.feign&lt;/groupId&gt;
    &lt;artifactId&gt;feign-jackson&lt;/artifactId&gt;
    &lt;version&gt;8.18.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.netflix.feign&lt;/groupId&gt;
    &lt;!--这里也可以换成feign-httpclient--&gt;
    &lt;artifactId&gt;feign-okhttp&lt;/artifactId&gt;
    &lt;version&gt;8.18.0&lt;/version&gt;
&lt;/dependency&gt;
我们需要创建一个bean来指定url和处理超时重试、日志打印等高可用方面的问题。@Bean
public TestHttpService testHttpService() {
    TestHttpService service = Feign.builder()
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .options(new Request.Options(1000, 3500))
            .retryer(new Retryer.Default(5000, 5000, 3))
            .logger(logger)
            .logLevel(Logger.Level.FULL)
            .target(TestHttpService.class, testUrl);
    return service;
}Spring的简化开发主要是使用了声明式代替命令式，在http请求这里当然也可以这么用：public interface TestHttpService {    @RequestLine("GET /xxxx?appkey={appkey}&amp;ips={ip}&amp;username={username}&amp;operator={operator}")
    Response getTest(@Param(value = "appkey") String appkey,
                                       @Param(value = "ip") String ip,
                                       @Param(value = "username") String username,
                                       @Param(value = "operator") String operator);
}运行一下，发现执行过程这么清晰：[TestHttpService] ---&gt; GET http://xxx/x?appkey=x&amp;ips=x&amp;username=x&amp;operator=x HTTP/1.1[TestHttpService] ---&gt; END HTTP (0-byte body)[TestHttpService] &lt;--- HTTP/1.1 200 OK (154ms)[TestHttpService] connection: keep-alive[TestHttpService] content-length: 28[TestHttpService] content-type: application/json;charset=utf-8[TestHttpService] date: Sun, 29 Mar 2020 02:19:43 GMT[TestHttpService] m-traceid: -264385071732215405[TestHttpService] server: openresty[TestHttpService] version: oceanus[TestHttpService]&nbsp;[TestHttpService] {"data":[],"isSuccess":true}[TestHttpService] &lt;--- END HTTP (28-byte body)其他的用法早在Spring3里就已经使用了模板方法来简化http请求的开发，使用RestTemplate不仅可以使用java8的lambda表达式，还可使用消息转换器直接将返回值转成对象的形式，更友好的实现了面向对象编程。当然feign更简洁，并且提供了高可用的支持。</code></pre>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/java-springguo-shi-de-jing-dian-yu-lu</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 02:11:18 GMT</pubDate></item><item><title><![CDATA[测试了一下编解码的执行效果]]></title><link>https://wiki.dgfygk.com/archives/ce-shi-liao-yi-xia-bian-jie-ma-de-zhi-xing-xiao-guo</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B5%8B%E8%AF%95%E4%BA%86%E4%B8%80%E4%B8%8B%E7%BC%96%E8%A7%A3%E7%A0%81%E7%9A%84%E6%89%A7%E8%A1%8C%E6%95%88%E6%9E%9C&amp;url=/archives/ce-shi-liao-yi-xia-bian-jie-ma-de-zhi-xing-xiao-guo" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">背景</span></strong></p>
<p style="">在《<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484300&amp;idx=2&amp;sn=40b7b824814af1999ab1fcb823c3a811&amp;chksm=fafde922cd8a6034a7eeabef85ed886a15cbd433ca3a72175405a9b8eb1ec0f180daa5b10f2f&amp;scene=21#wechat_redirect" target="_blank">程序媛的人生观》</a>这篇文章中，在博客园有热心朋友反馈：</p>
<blockquote>
 <p style="">protosbuff支持的类型少~而且不支持嵌套~性能更没有json高，如不是外网使用节约流量，没有用的必要~</p>
</blockquote>
<p style="">我觉得评论说的很好。但是以淘金式思路来看这个问题，需要提出自己的问题，进行批判性吸收。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">编码效率</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xxfw.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">&nbsp; &nbsp;写了一段代码测试使用protostuff和json两种序列化编码的执行速度。结果每次protostuff的速度都比json快。下面是其中3次的结果：</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dxuj.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; ">（结果1）</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kclf.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果2）</span></p>
<p style=""></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jczs.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果3）</span></p>
<p style=""><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">结论：protostuff编码速度快于json。</span></p>
<p style="">这个数据让我非常的自责，因为可以看到编码的速度在100ms上下，是非常耗时的操作。但是我在编写我们项目代码的时候没有加专门的<span fontsize="" color="">监控</span>统计。</p>
<p style="">为了验证是否和执行顺序有关，调换一下执行顺序。实际上如果在同一个方法里先后运行两次序列化，会影响执行结果。但是因为用了两个独立的junit test方法，所以影响可忽略不计。来看看效果。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sbek.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这是修改了顺序的代码。下面是<span fontsize="" color="">其中3次的结果：</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wked.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果1）</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tarv.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果2）</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-byto.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果3）</span></p>
<p style="">可以看到，<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">protostuff编码速度仍然快于json。</span></p>
<p style=""><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">
  <br>
 </span></p>
<p style=""><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">解码效率</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jfxs.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">改造一下代码，测试一下解码效率。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mwlj.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果1）</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uasq.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果2）</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rjao.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><span fontsize="" color="">（结果3）</span></p>
<p style="">结论：<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">protostuff解码速度远远快于json。</span></p>
<p style=""><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(0, 0, 0)">编码后的大小</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fxhq.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">结果：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wjqh.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">结论：</span><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">protostuff编码后的数据小于json。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">换一个复杂对象验证效果：</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zgqt.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">结果：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bpwk.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">又换了一个更复杂的，结果：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tfep.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">虽然<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">protostuff编码后的数据小于json。</span>但是相差不是很大。这是因为刚才所有的测试（包括效率和大小）都是基于我将protostuff编解码和base64绑定处理了。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-luzb.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这是编码。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cabp.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这是解码。如果不用base64，最后一条的结果是</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ucmv.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">protostuff</span>的优势还是很明显的。</p>
<p style="">关于支持的类型少和不支持嵌套。看上面我用到的对象Pod，是</p>
<p style="">io.fabric8.kubernetes.api.model.Pod，<span fontsize="" color="">有k8s背景的朋友应该知道这个对象有多复杂。测试里面的</span><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">ResourceMessage</span><span fontsize="" color="">对象其实是个泛型对象，里面嵌套了Pod。所以也不存在不支持嵌套这一说。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style="">原文链接：https://mp.weixin.qq.com/s/Ob_caogahB-MxCsYJOWaIw</p>]]></description><guid isPermaLink="false">/archives/ce-shi-liao-yi-xia-bian-jie-ma-de-zhi-xing-xiao-guo</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 01:30:43 GMT</pubDate></item><item><title><![CDATA[一个由于侵入框架引起的故障]]></title><link>https://wiki.dgfygk.com/archives/yi-ge-you-yu-qin-ru-kuang-jia-yin-qi-de-gu-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%80%E4%B8%AA%E7%94%B1%E4%BA%8E%E4%BE%B5%E5%85%A5%E6%A1%86%E6%9E%B6%E5%BC%95%E8%B5%B7%E7%9A%84%E6%95%85%E9%9A%9C&amp;url=/archives/yi-ge-you-yu-qin-ru-kuang-jia-yin-qi-de-gu-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">背景</span></strong></p>
<p style="">其实最近一直想写些帮助大家提高架构底蕴的东西。无奈最近当家的身体抱恙，我白天上班，晚上照顾病人，没有多余的精力 点、线、面的横向思考技术的问题。倒是“无为空自老，含叹负平生”的人生感慨多一些。今天还是谈谈点上的东西。</p>
<p style="">记得早些时候，被别人要求写算法代码没写出来，后来我刷了百道leetcode。算法题再也没难住我了。但是想来，平时工作中是还是真的少些这些底层代码为妙，业务代码有业务代码的写法。写业务代码最忌讳：“炫技”。和做人一样，讲究“技高不炫技”。</p>
<p style="">下面来介绍一个由于侵入了框架内部引起的故障。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">使用Java反射引起的报错</span></strong></p>
<p style="">我们平时用Spring框架写Controller进行http请求处理时，框架帮我们做了JavaBean到传输数据的转换。在内部，实际上数据会先转成map再转成我们要处理的JavaBean。</p>
<p style="">这次，在一个RPC泛化调用的场景(泛化调用主要用于没有接口API的情况下。不需要引入接口 jar 包,而是直接通过 通用接口来发起服务调用,参数及返回值中的所有 pojo均用 map 表示)。有个同学客户端处理时，自己写了一个JavaBean转成Map的方式，他是这么写的：</p>
<p style="">public static Map&lt;String,Object&gt; objectTpMap(Object obj) {</p>
<p style="">&nbsp; &nbsp; &nbsp;Map&lt;String,Object&gt; map&nbsp;= new HashMap();</p>
<p style="">&nbsp; &nbsp; &nbsp;Class&nbsp;objectClass =&nbsp;obj.getClass();</p>
<p style="">&nbsp; &nbsp; &nbsp;while(objectClass != null)&nbsp;{</p>
<p style="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Field[]&nbsp;declaredFields =<span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">&nbsp;objectClass.getDeclaredFields();</span></p>
<p style="">&nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp;for(Field field&nbsp;:&nbsp;declaredFields) {</p>
<p style="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;field.setAccessible(true);</p>
<p style="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;resultMap.put(field.getName(), field.get(object));</p>
<p style="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</p>
<p style="">&nbsp; &nbsp; &nbsp;}</p>
<p style="">&nbsp; &nbsp; &nbsp;return resultMap;</p>
<p style="">}</p>
<p style="">这里，在做JavaBean到Map转化时，用了反射的方法，将对象的所有属性(包含private final类型)都提取出来，传给服务端。</p>
<p style="">服务端组件内部，开始时反序列化时，使用的是getField方法查找这个map中的每一个key。如果对象是private final类型是获取不到的，所以没有影响。后来组件升级，反序列化时，并没有使用getField方法，而是使用了getDeclaredField方法，就可以获取到private final类型的属性，进行赋值操作的时候就产生了问题。</p>
<p style="">这个bug经过了一两年时间才引起了事故。让人意想不到，恢复和排查的响应时间都会收到影响。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">后记</span></strong></p>
<p style="">能写出这样的代码，究其原因，大致有两种：一种遇到这个问题，经过搜索资料和思考，觉得这样可以实现，就没有做更多的思考；另一种是抱着学习的态度，想用一些之前不常用的技术解决问题。这两种原因写出的代码都是建立在对原理了解不透彻的基础上，相当危险。</p>
<p style="">最近在看《山河令》，里面有个诗词底蕴极高的温客行。说话时引经据典，天花乱坠。</p>
<p style="">见到周子舒渡船，温客行说：“但度无所苦，我自迎接汝”。这本是大文豪王献之写给他的爱妾桃叶的诗。因为桃叶怕坐船，王献之就说了：“你只管渡江就好，不用想太多，我自然会在江边迎接你。”这里温客行用的隐隐表露出自己的小心思，倒也用的妙。</p>
<p style="">为了和温客行做对比，里面还出现了个爱附庸风雅的大白兔：曹蔚宁。小曹喜欢阿湘。于是跟阿湘夸她名字好：“九嶷缤兮并迎，灵之来兮如云。”句子里没有出现一个“湘”字。和湘沾边的是前半句“九嶷缤兮并迎“出自屈原的《九歌.湘夫人》。而后半句是曹植的《洛神赋》。洛神叫甄宓啊，和湘字没有半毛钱关系，纯粹是背诗背串了。所以温客行说听小曹背诗，屈原都能被气活过来。这境界反差出来了吧。</p>
<p style="">但是强如温客行，人家在河边喝水，他本是想打招呼示好的话。他来了一首：“沧浪之水清兮，可以濯我缨；沧浪之水浊兮，可以濯我足。”我只是觉得人家在喝水，你又是洗衣服又是洗脚的，这水喝着真倒胃口。你倒是来一首：“<span style="font-size: 16px">问渠那得清如许？为有源头活水来。</span>”&nbsp;也能让人觉得水的甘甜。或者来首：“<span style="font-size: 16px">秋水清无底，萧然静客心。</span>”&nbsp;也可在这炎炎夏日送上一抹凉意。也难怪周子舒不理他。</p>
<p style="">温客行的漏洞可不止这一处，温客行造了30个假的琉璃甲，带着周子舒去看狗咬狗。他在屋顶上说的是：“冲天香阵透岳阳，满城尽是琉璃甲”。这本是黄巢写菊花的诗：“冲天香阵透长安，满城尽带黄金甲。” 你要是改，也要改的彻底一点，人家菊花是香的，香阵一词用的妙，琉璃甲和香有什么关系吗？冲天香阵肯定是没有，冲天的杀气却是名副其实。</p>
<p style="">说这么多，无非是想说明，还是要敬畏生产。不了解原理还是不要直接在要上生产的代码里尝试。侵入框架底层的代码最好不要写，尽量看看能否用写业务代码的方式来解决。连温客行身上都能找到这么多诗词的错处，平时咱们写的业务代码还怕找不出来Bug？一旦出现生产事故，岂不是丢了排面？</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/G7y4Z9HPb2bAAUUmJ7nXnw</p>]]></description><guid isPermaLink="false">/archives/yi-ge-you-yu-qin-ru-kuang-jia-yin-qi-de-gu-zhang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 01:17:42 GMT</pubDate></item><item><title><![CDATA[指标+数据告诉你高并发的瓶颈]]></title><link>https://wiki.dgfygk.com/archives/zhi-biao-shu-ju-gao-su-ni-gao-bing-fa-de-ping-jing</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8C%87%E6%A0%87%2B%E6%95%B0%E6%8D%AE%E5%91%8A%E8%AF%89%E4%BD%A0%E9%AB%98%E5%B9%B6%E5%8F%91%E7%9A%84%E7%93%B6%E9%A2%88&amp;url=/archives/zhi-biao-shu-ju-gao-su-ni-gao-bing-fa-de-ping-jing" width="1" height="1" alt="" style="opacity:0;">
<p style="">并发连接数英文缩写是SBC。</p>
<p style=""></p>
<p style="">nginx在内核优化后可同时容纳10万并发连接数。注意这里面提到了内核优化后，因为nginx处理任务占用资源极少，它的性能瓶颈主要在linux的文件句柄限制。</p>
<p style=""></p>
<p style="">Tomcat 默认配置的最大请求并发数为150。可以改大，但是有一定限制。瓶颈在于操作系统中对于进程中的线程数有限制：Windows 每个进程中的线程数不允许超过 2000，Linux 每个进程中的线程数不允许超过 1000。在 Java 中每开启一个线程需要耗用 1MB 的 JVM 内存空间用于作为线程栈之用。</p>
<p style="">
 <br>
 Jetty的并发量一般来说要优于Tomcat。但是只是在BIO、NIO上处理的区别。资源占用上区别不大，所以Jetty较Tomcat的性能有提升，但提升并不是量级上的。目前流行的组合是SpringBoot里将默认的Tomcat替换成Jetty做容器。
</p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FvnuOn8sxOAI1fiaDKpAqXyw47Znicp8A5QfxicDk4t3nFHytHKmPicMGTlwTU86rmk9LlH7hrkiatfvwPr4HEiah04IQ%2F640%3Ftp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block"></p>
<p style="">每秒处理事务数</p>
<p style=""></p>
<p style="">从用户角度来看，其实用户并不关心后端服务器是否真正同时处理多少并发。用户关心的是我等多久能得到自己关心的结果，即响应时间(RT)。一般小key小value的缓存，请求响应时间平均在几百纳秒。一次数据库访问在两三毫秒。简单数据库访问的用户请求算上网络延时是十几毫秒。一般复杂度逻辑计算一般响应时长在几十到几百毫秒。</p>
<p style="">
 <br>
 而开发人员关心的是怎么在一个时间段内处理更多的请求。如果非要给这段时间加一个期限，那就是1s。从用户角度一个事务可分为用户请求服务器、服务器处理请求、最后返回用户。每秒能处理用户事务的数量就是TPS。
</p>
<p style=""></p>
<p style="">并发连接数是有限的，但是TPS的角度，可以通过队列技术，复用已有连接来处理请求。这时候想要在达到SBC极限时优化，就要化简请求，让单个请求响应时间更短。这个主要取决于事务复杂度。</p>
<p style=""></p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FvnuOn8sxOAI1fiaDKpAqXyw47Znicp8A5QfxicDk4t3nFHytHKmPicMGTlwTU86rmk9LlH7hrkiatfvwPr4HEiah04IQ%2F640%3Ftp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block"></p>
<p style="">集群QPS</p>
<p style=""></p>
<p style="">后来人们发现，查询速度是更重要的一个指标。因为一个网站99%的请求都是查询操作。如果真正需要插入和更新操作，比如支付、注册会员，几秒的时间能忍。如果实在耗时很长，可以分为两阶段提交。一个阶段查询确认是否可以提交成功，返回用户结果。耗时长的真正执行阶段的结果可用户稍后调用查询结果链接查看。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F2tk5ianItRl8ia2CT1TFoXNANFx8IicQibVW1iaUrgG0Aur0ytD2G42ibtt1BMyYmsO0XGIVLnVxp6jibC7jELPwicTZMQ%2F640%3Fwx_fmt%3Dpng%26tp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="256px" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">每秒处理查询请求的次数是QPS。由于用户根本不关心单机可处理的QPS是多少，可以将单机只能处理几百个并发请求的Tomcat或者Jetty这样的Web服务器连成一个集群，前面用nginx之类的做负载均衡。这就可以承受很大的并发量了。前提是Web服务器只有内存计算。</p>
<p style="">像刚刚说的Web服务器，业务通常会把它设计为无状态的。就是用户一个请求打到哪台机器都可以。但是如果用户需要一些获取自己独特的数据，比如自己的名字、生日。这些数据要存储在固定的地方比如数据库。这就是不是访问哪台机器都可以了。必须访问含有自己状态的地方。这些就是有状态服务。</p>
<p style="">在机器资源充足的情况下，有状态的服务通常才是瓶颈的根源。比如mysql。mysql5.7单机TPS可达到5千TPS。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FvnuOn8sxOAI1fiaDKpAqXyw47Znicp8A5QfxicDk4t3nFHytHKmPicMGTlwTU86rmk9LlH7hrkiatfvwPr4HEiah04IQ%2F640%3Ftp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block"></p>
<p style="">服务拆分</p>
<p style=""></p>
<p style="">当mysql这样的有状态服务达到性能瓶颈。聪明的软件工程师想到了从另外的维度来解决问题。读写分离，写请求为了数据一致性完整性不好分开。但是可以拷贝几个副本专门来承接读流量。如上文所说，一个网站99%的请求都是查询操作。专门用来读的数据库因为避免了排他锁，几万QPS是没有问题的。</p>
<p style=""></p>
<p style="">那写请求想解决瓶颈怎么处理好呢？就可以把数据按照一定规则拆分到不同服务器上。这就是数据分片。比较常见的是Elasticsearch的数据分片。mysql的分片一般是软件工程师自己来设计开发。这就是常说的分库分表。</p>
<p style=""></p>
<p style="">水平拆分的主要瓶颈是预算，就是说资源不是无限的。那需要定期做容量评估，把机器数维持在一个合理的范围。</p>
<p style=""></p>
<p style="">除了上面提到的从单机到集群这种流量的水平切分、分库分表水平切分。聪明的软件工程师还想到了从业务上做拆分。让每个子系统的职责更单一，数据库拆出子表，这就是垂直拆分。</p>
<p style=""></p>
<p style="">垂直拆分的瓶颈除了资源之外，还在人员。人就那么多，拆分太多，每个人负责太多的模块，精力会分散。如果一个需求，需要一个开发人员修改多个子模块。他就需要更多的发布上线。</p>
<p style=""></p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FvnuOn8sxOAI1fiaDKpAqXyw47Znicp8A5QfxicDk4t3nFHytHKmPicMGTlwTU86rmk9LlH7hrkiatfvwPr4HEiah04IQ%2F640%3Ftp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block"></p>
<p style="">总结</p>
<p style=""></p>
<p style="">本文从并发连接数的概念引入，由于并发连接数存在硬件上的瓶颈。所以引入QPS、TPS等用户视角概念，从指标中提炼出通过减少响应时间提高并发的方法。我们通常使用缓存等用空间换时间的方法，主要目的就是减少响应时长。</p>
<p style=""></p>
<p style="">当单机不能解决问题，就产生了多机来分担流量的方法，常见的有水平拆分（x轴拆分）和垂直拆分（y轴拆分）。实际上在数据库层还有z轴拆分。瓶颈也从物理资源逐渐转移到人力资源上。随着行业发展，如大家所相信的一样，人才的作用也会更加重要。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/L_OvR6Nxf_ekKOvE4lzsLQ</p>]]></description><guid isPermaLink="false">/archives/zhi-biao-shu-ju-gao-su-ni-gao-bing-fa-de-ping-jing</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 01:08:39 GMT</pubDate></item><item><title><![CDATA[稳定性五件套-熔断的原理和实现]]></title><link>https://wiki.dgfygk.com/archives/wen-ding-xing-wu-jian-tao-rong-duan-de-yuan-li-he-shi-xian</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%A8%B3%E5%AE%9A%E6%80%A7%E4%BA%94%E4%BB%B6%E5%A5%97-%E7%86%94%E6%96%AD%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E5%AE%9E%E7%8E%B0&amp;url=/archives/wen-ding-xing-wu-jian-tao-rong-duan-de-yuan-li-he-shi-xian" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">背景</span></strong></p>
<p style=""></p>
<p style="">打算用五篇的篇幅介绍一下限流、熔断、降级、隔离、超时重试。继<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485297&amp;idx=1&amp;sn=e7882369e07e8a1e6d3b4cc7f89e087e&amp;chksm=fafdeddfcd8a64c90c1915187ba1a6d4ae013b5f0ccedf68459b88585c022cb26420a3d91f7c&amp;scene=21#wechat_redirect" target="_blank">《稳定性五件套-限流的原理和实现》</a>，这是第二篇。主要说一说熔断。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">熔断的概念</span></strong></p>
<p style=""></p>
<p style="">上篇提到过我对限流和熔断区别的理解。限流作用是<strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">防御上游</span></strong>流量超过处理能力的手段，熔断作用是<strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">容错下游</span></strong>的快速失败手段。</p>
<p style=""></p>
<p style="">超时重试也是<strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">容错下游</span></strong>的手段。也经常有人认为熔断和超时重试是一回事，或者需要统一来看，因为都是可以基于熔断器来实现。但我认为它们有本质的区别，熔断针对的是下游的各种错误：超时是一种情况；错误率过高也是一种情况；还有某些特定场景需要对一种特定的返回码做熔断，比如服务拒绝请求。</p>
<p style=""></p>
<p style="">总而言之，超时重试和熔断有两点主要的不同。</p>
<p style=""></p>
<p style="">第一，超时重试和熔断要解决的问题不同。</p>
<p style=""></p>
<p style="">熔断是解决<strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">服务级联故障</span></strong>的问题；超时重试是解决<strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">分布式系统的三态</span></strong>问题。分布式系统的三态是成功、失败和超时。对于超时，如果重试成功，最终是成功的。如果重试之后都失败，那就失败的。这样三态问题可以转换为两态。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-thtf.png&amp;size=m" width="373px" height="370px" style="display: inline-block"></p>
<p style=""></p>
<p style="">第二，超时重试是针对单个请求的处理。而熔断是针对一类或者一组请求的处理。比如一般熔断要结合计数器，当错误超过阈值才熔断。</p>
<p style="text-align: center; "></p>
<p style="">举个生活中的限流例子：</p>
<p style=""></p>
<p style="">上篇提到小A经过一段时间终于找到了心仪的女朋友。但是实际上他心仪的女朋友并不只一个。而是有5个。为了显示对女朋友的喜爱和尊重，他女朋友们发过来的消息他决定基本采取秒回的方式。所以小A很忙。</p>
<p style=""></p>
<p style="">小A同时开着5个消息窗口。实时去关注这些窗口的动态。如果他一些女朋友有段时间很爱聊天。人的精力是有限的，是吧。所以他就采用一定的限流策略。对于其中任意一个女朋友来说：如果1分钟内超过了3条消息需要回复，他就只回复其中的一条。</p>
<p style=""></p>
<p style="">举个生活中的熔断例子：</p>
<p style=""></p>
<p style="">小A除了要接收他女朋友们的消息之外，作为一个暖男。小A还会时不时自己主动嘘寒问暖一下。发消息给他女朋友们提醒她们多喝水。当然他女朋友们自己并不知道有其他人，只知道自己收到了消息。有的女朋友比如小C就回复很快，那小A就会和小C保持一个很频繁的来回通信。</p>
<p style=""></p>
<p style="">有的女朋友比如小D是个程序媛，有时候需要开各种会。小A发给消息很长时间都不回，那他还是老是开着窗口不断看着小D有没有回消息就太浪费了。这时候小A会采用熔断措施，如果超过10分钟不回，他会先关掉窗口。隔半小时再打开看看。</p>
<p style=""></p>
<p style="">小A还有个女朋友小E，她电脑中了病毒，给小A发的都是各种垃圾广告。小A判断这些信息没有必要看了，也做了熔断，关闭了窗口。但他还是会隔半小时再打开看看小E的电脑病毒问题有没有解决，是不是可以正常发消息了。</p>
<p style=""></p>
<p style="">举个生活中的超时重试例子：</p>
<p style=""></p>
<p style="">小A终于觉得自己女朋友实在是太多了，所以他决定和其中一个小F分手。但是小A想好的分手的台词发消息过去，小F都不回。小A都不知道到底分手是成功还是不成功。小A就很小心的每10分钟给小E发个消息问她是否同意这个决定。这就是超时重试。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">熔断的原理</span></strong></p>
<p style=""></p>
<p style="">熔断本质上是做快速失败，防止级联故障引起雪崩。它的主要采用的手段是基于断路器的设计模式。</p>
<p style=""></p>
<p style="">断路器有基本模式和扩展模式。</p>
<p style=""></p>
<p style="">基本模式中，断路器由两个状态和一个动作组成：断路器打开状态、断路器关闭状态和跳闸动作。在断路器关闭状态下，请求过来每次都要先经由跳闸动作，由跳闸动作判断是否需要将状态改成断路器打开状态。断路器打开状态下，请求直接执行快速失败的动作，不会向后请求。</p>
<p style=""></p>
<p style="">扩展模式中，断路器增加了一个半开状态，它允许有限数量的请求通过，如果执行成功，恢复到关闭状态；如果失败，则恢复到开放，然后重启定时器，一段时间后再用半开状态来尝试。扩展模式可以半自动化的进行熔断恢复，避免了基本模式中，一旦断路器被打开完全依赖人工判断是否恢复的弊端。</p>
<p style=""></p>
<p style="">扩展模式对应的状态机流转如下：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tiaq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span style="font-size: 20px">熔断的实现</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">基础实现</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">
   <br>
  </span></strong></p>
<p style="">在Java中业界用的比较多的是hystrix或者<span style="font-size: 16px">resilience4j</span><span fontsize="" color="">来实现熔断。原理差不多。</span>实际上<span style="font-size: 16px">resilience4j</span>是受到hystrix启发而形成的。</p>
<p style=""></p>
<p style="">下面以hystrix为例进行讲解。这是一个根据token认证的服务，正常情况下调用authenticate方法会调用下游来判断token是否有效。但是如果下游一旦出现问题了，因为这是一个内网服务，平时很少有非法流量进来，并且内网服务安全性可控。这个认证的统计学意义更大一些。所以在下游出现故障时可以快速降级直接返回认证成功。避免对整个服务造成影响。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ohog.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">从上面代码可知，使用方面关注点在于@HystrixCommand这个注解。基本用法就是上面的代码示例。</p>
<p style=""></p>
<p style="">下面简单讲解一下源码。重点是<span style="font-size: 12ptpx; color: rgb(0, 0, 0)">HystrixCircuitBreakerImpl这个实现类</span></p>
<pre><code>public static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
    private final HystrixCommandProperties properties;
    private final HystrixCommandMetrics metrics;
    private AtomicBoolean circuitOpen = new AtomicBoolean(false);
    private AtomicLong circuitOpenedOrLastTestedTime = new AtomicLong();

    protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
        this.properties = properties;
        this.metrics = metrics;
    }

    public void markSuccess() {
        if(this.circuitOpen.get() &amp;&amp; this.circuitOpen.compareAndSet(true, false)) {
            this.metrics.resetStream();
        }

    }

    public boolean allowRequest() {
        if(((Boolean)this.properties.circuitBreakerForceOpen().get()).booleanValue()) {
            return false;
        } else if(((Boolean)this.properties.circuitBreakerForceClosed().get()).booleanValue()) {
            this.isOpen();
            return true;
        } else {
            return !this.isOpen() || this.allowSingleTest();
        }
    }

    public boolean allowSingleTest() {
        long timeCircuitOpenedOrWasLastTested = this.circuitOpenedOrLastTestedTime.get();
        return this.circuitOpen.get() &amp;&amp; System.currentTimeMillis() &gt; timeCircuitOpenedOrWasLastTested + (long)((Integer)this.properties.circuitBreakerSleepWindowInMilliseconds().get()).intValue() &amp;&amp; this.circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis());
    }

    public boolean isOpen() {
        if(this.circuitOpen.get()) {
            return true;
        } else {
            HealthCounts health = this.metrics.getHealthCounts();
            if(health.getTotalRequests() &lt; (long)((Integer)this.properties.circuitBreakerRequestVolumeThreshold().get()).intValue()) {
                return false;
            } else if(health.getErrorPercentage() &lt; ((Integer)this.properties.circuitBreakerErrorThresholdPercentage().get()).intValue()) {
                return false;
            } else if(this.circuitOpen.compareAndSet(false, true)) {
                this.circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
                return true;
            } else {
                return true;
            }
        }
    }
}</code></pre>
<p style="">1&gt;这里面成员变量中<span style="font-size: 12ptpx; color: rgb(0, 0, 0)">HystrixCommandProperties是用来处理@HystrixCommand这个注解的commandProperties的。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ryfo.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""></p>
<p style="">和断路器相关的主要是下面这些：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-shgx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span fontsize="" color="">1&gt;如果circuitBreakerForceOpen = true会将</span><span style="font-size: 16px; color: rgb(77, 77, 77)">断路器强制打开</span><span fontsize="" color="">，</span><span style="font-size: 16px; color: rgb(77, 77, 77)">circuitBreakerForceClose = true会将断路器强制关闭。这个一般配合配置中心来用，起到人为</span><span fontsize="" color="">干预的作用。</span></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">
  <br>
 </span></p>
<p style="">2&gt;HystrixCommandMetrics是计数器，用这里面存的健康请求数量来判断是否需要熔断。</p>
<p style=""></p>
<p style="">3&gt;<span style="font-size: 12ptpx; color: rgb(0, 0, 0)">circuitOpen即为断路器状态，有打开和关闭两种。</span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">4&gt;markSuccess() 方法是在</span><span style="font-size: 16px; color: rgb(77, 77, 77)">调用attemptExecution正常逻辑成功时，调用 #markSuccess() 方法，关闭断路器。</span>这时候HystrixCommandMetrics也会重置。</p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">5&gt;allowRequest()是用来判断是否允许指令执行。</span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">6&gt;allowSingleTest()是用来判断是否打开熔断到一定时间了，到了的话就测试一下是否已经恢复了。</span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">7&gt;isOpen()是判断断路器是否打开。</span></p>
<p style=""><span style="font-size: 12ptpx; color: rgb(0, 0, 0)">原文链接：</span><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">https://mp.weixin.qq.com/s/cCQnHtZ8uZ-9JLrXTGEvmA</span></p>]]></description><guid isPermaLink="false">/archives/wen-ding-xing-wu-jian-tao-rong-duan-de-yuan-li-he-shi-xian</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 01:03:20 GMT</pubDate></item><item><title><![CDATA[学习方法：用输出倒逼输入]]></title><link>https://wiki.dgfygk.com/archives/xue-xi-fang-fa-yong-shu-chu-dao-bi-shu-ru</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%AD%A6%E4%B9%A0%E6%96%B9%E6%B3%95%EF%BC%9A%E7%94%A8%E8%BE%93%E5%87%BA%E5%80%92%E9%80%BC%E8%BE%93%E5%85%A5&amp;url=/archives/xue-xi-fang-fa-yong-shu-chu-dao-bi-shu-ru" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">背景</span></strong></p>
<p style="">之前有几个朋友问过我：“想提高自己时，网上说这个也要学。那个也要学。什么新的都说要学。经历又不够，怎么办。”</p>
<p style="">我回答说：“先学习用的到的东西，学以致用。”</p>
<p style="">朋友就叹息到：“面试的时候都会问各种新技术，我太难了。”</p>
<p style="">这些朋友的问题在于认知层面。一两句话可以让大家把认知提升上去，我的能力还不够，所以当时我没有再回复。今天就仔细聊一聊这个话题。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-htwq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">先清楚目标</span></strong></p>
<p style="">先来斗胆揣测一下这些朋友的学习动机。大致有以下四类：一是为为马上要进行的面试做准备；二是为以后要进行的面试做准备；三是想成为牛人；四是工作在技术公司做技术工作，想在公司里立于不败之地。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">目标一：为马上要进行的面试做准备</span></strong></p>
<p style="">这个目标的朋友，我建议先停下来整理一下自己的优劣势和期望的工作。综合评估好之后，查找对应工作岗位的JD（Job Description职位描述）。根据岗位要求针对性学习。</p>
<p style="">那朋友问：“结果面试时人家问的问题不在JD的要求里呢？<span fontsize="" color="">”</span></p>
<p style="">这个问题答案很简单：“不会的就说不会，请面试官直接问下一题。”</p>
<p style="">面试无论哪个大公司都不会要求你每题都会。当然外企的套路可能不是这样。先上来一道算法题。写不出来那就确实不行了。很多业界都公认的大牛也在这件事情上栽过跟头。其实如果你看到一道题，完全不懂，是可以要求面试官换一道题的。过了算法关，外企剩下的题一般都是开放性的问题无所谓会还是不会。只有你回答没有回答到点子上。很有可能自己回答的不好自己也不知道，因为根本不知道面试官在问什么。</p>
<p style="">比如面试官让你造一艘船。你这时候不应该马上告诉他船应该怎么造。而是先跟面试官讲船大概分为木筏、皮艇、游艇、豪华邮轮、航空母舰等几类。你要的是哪类？七问分析法分析下：我们大概有多少预算、多长时间来做？<span fontsize="" color="">你的用户是男是女，他们都喜欢什么颜色？</span>是否需要一键换色功能？</p>
<p style="">设计阶段也一般根据情况分成几期，每一期什么时间，产出是什么……想进外企的朋友可以多读读项目流程规范类，或者如何成为一名项目经理之类的书。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">目标二：为以后要进行的面试做准备</span></strong></p>
<p style=""><span fontsize="" color="">对于这个目标，就是文章开头我说的：“先学习用的到的东西，学以致用。”
  <br></span></p>
<p style=""><span fontsize="" color="">几乎每个公司面试时都会先让面试者进行自我介绍。如果面试者自我介绍只介绍了自己的基本情况，在引导下也没能很好表现出自己的个人优势和过去的业绩，我会评估这个人肯定不合适一个高阶的岗位。因为他根本就没有以终为始的考虑过这个岗位的要求。
  <br></span></p>
<p style=""><span fontsize="" color="">公司想招任何一个人都是希望他们能给公司带来价值，甚至是引领公司上一个新的台阶。所以公司想招的必然是在岗位上可以做出贡献的人。如果一个学的都是自己用不到的东西，是为了应付面试而学习。没有把精力做在自己手头的事情上，只满足于完成了交代给自己的任务，没有做自己的思考、学习和深化。我有理由相信起码现阶段，这个人的格局和认知是不够的，来到这边也不会做出太大的业绩。
  <br></span></p>
<p style=""><span fontsize="" color="">所以如果是考虑一年或者几年之后面试想提高竞争力，最重要的是把自己的工作做好并且总结好。剩下的，虽然大家都在说面试越来越卷。但是坦白讲就那些东东，都有速成的办法，只要不是太笨，不用花长的时间。</span></p>
<p style=""><span fontsize="" color="">面试时有个误区。举个例子：有个同学面试的时候我根据他做的项目，问了一个kafka的问题，回答的一知半解。我问他这个答案是怎么知道的，他说是之前学习了极客空间的课。后我又问了其他几个问题，也都是只记得皮毛。这时我做为面试官，我的想法是我们对学习的认知是不同的。都工作了，学习不再是考察记忆，而在于理解和运用。这位面试者花了大把的时间，每个都学了点皮毛达不到运用的程度，对工作无意义。
  <br></span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">目标三：想成为牛人</span></strong></p>
<p style=""><span fontsize="" color="">这个目标就需要更多的努力了。因为这个目标需要做的好也要说的好。做不好，了解的同事会说名不副实。说的不好，努力得不到放大。基于这个学习的思路应该是先学工作中用到的，把工作做好，之后再向外扩展，形成体系。所谓前沿技术，个人认为并不是需要为了学而刻意的东西。而是自己的工程项目已经达到一定的优秀程度了。为了做到更优秀，就要看业界怎么做的，看看怎么借鉴甚至是超越。这是一个自然而然的过程。</span></p>
<p style=""><span fontsize="" color="">如果面试的时候，面试官着重的考察前沿技术，那说明他目标想招的是一个达到一定高度的架构师。面试者需要考虑自己的实际能力是否可以胜任这个工作。</span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">目标四：工作在技术公司做技术工作，想在公司里立于不败之地。</span></strong></p>
<p style=""><span fontsize="" color="">这个自不必说，更需要先把自己岗位的事情做好，学习工作中最需要的知识啦。
  <br></span></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">再思考方法</span></strong></p>
<p style="">“以提出问题为驱动，以解决问题为整合、用输出倒逼输入”这句话出自古典的《跃迁》，我觉得是自身学习方法的一个诠释。在工作中遇到什么问题或者自己提出<span style="font-size: mediumpx; color: rgb(0, 0, 0)">一个真实的、高价值，并且有可能被解决的问题。</span>以解决问题出发进行学习，从多个维度思考，用解决问题的结果来验证自己学习的有效性。最终通过不断的学习输入<span style="font-size: mediumpx; color: rgb(0, 0, 0)">构建清晰的、自下而上的知识体系</span>，最终输出给团队甚至业界，达到学习动机最初想满足的根本目标。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/CiubE6t1Ung4rd5Zg7FFQg</p>]]></description><guid isPermaLink="false">/archives/xue-xi-fang-fa-yong-shu-chu-dao-bi-shu-ru</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Wed, 25 Sep 2024 00:54:30 GMT</pubDate></item><item><title><![CDATA[volatile关键字的原理和要避免的误区]]></title><link>https://wiki.dgfygk.com/archives/volatileguan-jian-zi-de-yuan-li-he-yao-bi-mian-de-wu-qu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E8%A6%81%E9%81%BF%E5%85%8D%E7%9A%84%E8%AF%AF%E5%8C%BA&amp;url=/archives/volatileguan-jian-zi-de-yuan-li-he-yao-bi-mian-de-wu-qu" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">背景</span></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;最近做code review看到有的同学在承载缓存数据的变量里加了volatile关键字。想起来之前项目中也看到有的同学习惯在从配置中心获取的配置数据的变量上加volatile。今天就来探讨一下这个volatile加的有没有必要。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">volatile关键字的作用</span></strong></p>
<p style=""><span style="font-size: 18px; color: rgb(33, 33, 33)">1&gt;防止指令重排
  <br></span></p>
<p style=""><span style="font-size: 18px; color: rgb(33, 33, 33)">2&gt;禁用工作内存缓冲区，直接使用主内存。
  <br></span></p>
<p style=""><span style="font-size: 18px; color: rgb(33, 33, 33)">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">经典使用场景</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">场景1</span></strong></p>
<pre><code>public static Singleton getInstance() {
    //第一次null检查     
    if (instance == null) {
        synchronized (Singleton.class) {    //1     
            //第二次null检查       
            if (instance == null) {          //2  
                instance = new Singleton();//3  
            }
        }
    }
    return instance;
}</code></pre>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">如果不用volatile，则因为内存模型允许所谓的“无序写入”，可能导致失败</span>。某个线程可能会获得一个未完全初始化的实例。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">场景2</span></strong></p>
<pre><code>private volatile int value;
//读操作，没有synchronized，提高性能
public int getValue() {
    return value;
}

//写操作，必须synchronized。因为x++不是原子操作
public synchronized int increment() {
    return value++;
}</code></pre>
<p style=""><span style="font-size: 18px; color: rgb(33, 33, 33)">这段代码，可实现一个线程间安全的计数器。因为加了valatile关键字。每次线程都能取到最新值做加减。
  <br></span></p>
<p style=""><span style="font-size: 18px; color: rgb(33, 33, 33)">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">要避免的误区</span></strong></p>
<p style="">在代码评审的时候看到volatile被滥用的情况。说说我个人的看法：很少变化，对时间不是特别敏感的情况下不建议用volatile关键字。</p>
<p style="text-align: start; "></p>
<p style="">举个例子：从公司的配置中心取到一个配置数据。不建议用volatile。</p>
<p style="text-align: start; ">一般来说配置中心的架构是下面这个样子</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F2tk5ianItRlicNcqZhVe4vd5J2Jf0h4jFSfeTE1z4e65EWhF5nK7xeQlIUDo8CqTJlGqVibQ78ziah43dzdmWssD6g%2F640%3Fwx_fmt%3Dpng%26tp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="677px" height="677px" style="display: inline-block"></p>
<p style=""><span style="font-size: 18px">一条数据从用户变更到集中存储的配置中心，配置中心下发到真正使用的机器上，之前公司是要经过90s(客户端90s为周期定时去配置中心取最新数据)。</span></p>
<p style=""><span style="font-size: 18px">加了volatile关键字在这种场景只是能更快的看到这个最新值而已。下面我们来测试下这个【更快】有多久。
  <br></span></p>
<pre><code>public class VolatileTest {
    private boolean endRun = false;
    @Test
    public void noVolatile() throws Exception {
        Runnable r1 = new Runnable() {
            public void run() {
                int i = 0;
                while (!endRun) {
                    System.out.println("I am still running" + i++);
                }
            }
        };
        Runnable r2 = new Runnable() {
            public void run() {
                endRun = true;
            }
        };
        new Thread(r1).start();
        new Thread(r2).start();
        Thread.sleep(9000);
        System.out.println("end run");
    }
}</code></pre>
<p style="">这个代码里，在第一个线程使用endRun这个变量。第二个线程去改变endRun这个变量的值。一旦第一个线程看到了第二个线程的值的变化，就会马上停止循环。</p>
<p style="text-align: start; "></p>
<p style="text-align: start; ">运行结果如下：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xzln.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">说明经过了两个循环的时间，线程就读到了另外一个线程变化的值。对照下面的时间延迟表，我们来计算下：</p>
<p style=""><span style="font-size: 18px; color: rgb(33, 33, 33)">
  <br>
 </span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uaeq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">平均执行一行简单代码要执行5个指令。如上执行一个指令需要1ns。每次循环执行2行代码，从运行结果来看共执行了2次。共5*1*2*2=20ns。实际数据应该不是如此，而且是变化的。但是应该都是ns级别的。</p>
<p style="">相比较90s的可见性延迟，ns级别可以忽略不计。</p>
<p style=""></p>
<p style="">再看看为了早ns级看到结果，所花费的开销：volatile关键字本质是让L1缓存、L2缓存这种cpu缓存失效了，直接主存访问。如果要访问的字段在L1缓存里，从配置中心取的数据1天变化一次。以字段放在L2缓存为例。加了volatile关键字，访问时间要从4ns上升到100ns，如果这个变量每个请求都要访问，每秒QPS是1000。则1天为了取这个数据将多花1*24*3600*1000*(100-4)ns约等于8300ms。</p>
<p style=""></p>
<p style="">相比获得的收益来讲，代价要大出好几个数量级。但是本身的时间开销本来就很小，坦白说一般的系统一天多花个8.3s也是可以接受的。但是这样的变量多了，也是个不小的负担。而且这个负担会随着系统压力增加而加重。</p>
<p style=""></p>
<p style="">定时将缓存加载到内存原理相同，不建议使用volatile。</p>
<p style="">
 <br>
 <strong><span style="font-size: 20px; color: rgb(216, 79, 169)">其他引申思考</span></strong>
</p>
<p style="">上面结论是在对时间敏感度不高的情况下不建议用volatile，但是对于一般的系统，用了对系统的影响也还好。最怕的事情是做了一个实际上意义并不大，却引入系统风险的优化。</p>
<p style="">上周我review同学的代码，这个同学是个有技术追求的同学。喜欢写代码的时候进行些小优化。这是个好习惯。但是对review代码的人要求很高。因为普通的业务逻辑影响的就是那么一块范围内的。但是优化却可能会影响其他部分。</p>
<p style="">这次他在好几处，把原来打印更新缓存的日志里，原来只打印：【更新缓存成功】的地方加了个【更新缓存成功，影响数据XX条】。这个XX取的是guava Cache的.size()方法。</p>
<p style="">这段代码我仔细看了guava cache的初始化方法，这个初始化方法非常复杂，里面用了几处断言(不能为空)。这个初始化方法没有统一的try catch捕获异常，一旦有地方抛出异常。有可能会没有完全实例化。我把自己的这个想法提了出来。他通过代码走查梳理向我证明了，确实不会有空指针的情况。我同意他是对的。但是这整个过程说明了我对这件事情的谨慎。如果我们上线了一个功能，功能有问题，新功能上线会有灰度、观察期慢慢上量的过程，影响不会多大。但是改了其他部分，特别是感觉绝对不会有问题的部分。如果出现问题了，自己团队的信任分一下子就会降下来了。以后再进行变更，需要反复证明影响，非常被动。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/hWvbWNXRe4rRKEoEUlrzJQ</p>]]></description><guid isPermaLink="false">/archives/volatileguan-jian-zi-de-yuan-li-he-yao-bi-mian-de-wu-qu</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 10:01:40 GMT</pubDate></item><item><title><![CDATA[hystrix线程池隔离的原理与验证]]></title><link>https://wiki.dgfygk.com/archives/hystrixxian-cheng-chi-ge-chi-de-yuan-li-yu-yan-zheng</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=hystrix%E7%BA%BF%E7%A8%8B%E6%B1%A0%E9%9A%94%E7%A6%BB%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%8E%E9%AA%8C%E8%AF%81&amp;url=/archives/hystrixxian-cheng-chi-ge-chi-de-yuan-li-yu-yan-zheng" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">hystrix隔离原理</span></strong></p>
<p style=""></p>
<p style="">hystrix可以完成隔离、限流、熔断、降级这些常用保护功能。这四个功能可以这么来理解：</p>
<p style=""></p>
<p style="">hystrix的隔离分为线程池隔离和信号量隔离。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px">信号量隔离原理</span></strong></p>
<p style=""></p>
<p style="">信号量隔离就是hystrix的限流功能。虽然名字叫隔离，实际上它是通过信号量来实现的。而信号量说白了就是个计数器。计数器计算达到设定的阈值，直接就做异常处理。</p>
<p style=""></p>
<p style="">ratelimiter的令牌桶算法和漏桶算法，都是直接对请求量来计数。只是令牌桶算法可以将前面一段时间没有用掉的请求量允许余额拿过继续用。而漏桶算法一段时间就允许这么多，前面没用掉的也不能用了。</p>
<p style=""></p>
<p style="">而hystrix信号量隔离限制的是tomcat等Web容器线程数，一段时间仅仅能支持这么多。多余的请求再来请求线程资源，就被拒绝了。所以是一种“曲径通幽”的限流方式。因为实际是通过隔离了部分容器线程资源，也算是一种隔离方式。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">线程池隔离原理</span></strong></p>
<p style=""></p>
<p style="">信号量隔离只是起了个限制作用，它的保护能力有限：如果下游服务有问题，长时间不返回结果。本身信号量隔离对这个单个请求是起不到任何作用的。它只能限制这样的请求太多了就拒绝，不让整个服务挂。</p>
<p style=""></p>
<p style="">为了解决这个问题，hystrix又产生了线程池隔离。这种隔离方式是通过引入额外线程的方式。对原来的web容器线程做管理控制：如果一个线程超时未返回，则熔断。既然引入额外的线程就涉及线程池管理、线程的上下文切换这些额外的开销。所以相比信号量隔离，线程池隔离成本更高。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">熔断原理</span></strong></p>
<p style=""></p>
<p style="">隔离不但可以做保护，还可以做统计：成功了多少失败了多少。既然有统计数据了，它就可以进一步处理：失败太多了，说明现在有问题，执行完了再发现失败太浪费资源，干脆就先不让worker线程执行了。过段时间再试试。这就是断路器模式，也就是熔断的原理。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">降级原理</span></strong></p>
<p style=""></p>
<p style="">任何异常需要熔断的场景，为的都是反正都是错，干脆把这资源省了。直接返回一个预定的错误。这个熔断后返回设定错误的过程就是降级。</p>
<p style=""></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">资源保护的流程</span></strong></p>
<p style=""></p>
<p style="">从上面来看所谓的hystrix的四大功能：限流、隔离、熔断和降级。只是完成了一整个对资源保护的生命周期。来看看对应的BPMN流程图：</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">信号量隔离的流程</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qkns.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">线程池隔离的流程</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-asex.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">现在大家请回答我一个问题：我上面说的是对的吗？</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px">hystrix隔离验证</span></strong></p>
<p style=""></p>
<p style="">采用淘金式的思维，不要别人说什么都信。网上很多技术博客里说的都是错的。我的文章也有可能是错的，事实上我说的很多东西都带有自己脑补的成分。</p>
<p style=""></p>
<p style="">既然不确定是否是对的，就要去验证。先验证</p>
<p style="">1&gt;hystrix隔离确实能限制资源</p>
<p style="">2&gt;信号量隔离采用的Web容器的线程池，而线程池隔离采用的是自己独立的线程池。</p>
<p style=""></p>
<p style="">本次验证，Web容器使用的是spring boot内嵌的jetty。代码已经上传github:</p>
<p style="">https://github.com/xiexiaojing/yuna</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">信号量隔离验证</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">
   <br>
  </span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">隔离hystrix配置</span></strong></p>
<pre><code>import&nbsp;com.netflix.hystrix.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.config.ConfigurableBeanFactory;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class DemoHystrixCommand extends HystrixCommand&lt;String&gt; {    private static final Logger logger = LoggerFactory.getLogger(DemoHystrixCommand.class);    private String poolName;
    public DemoHystrixCommand() {        super(Setter.withGroupKey(                //服务分组                HystrixCommandGroupKey.Factory.asKey("DemoGroup"))                //线程分组                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("DemoPool"))                //线程池配置                .andThreadPoolPropertiesDefaults(                        HystrixThreadPoolProperties.Setter()                                .withCoreSize(2)                                .withKeepAliveTimeMinutes(5)                                .withMaxQueueSize(2)                                .withQueueSizeRejectionThreshold(10))                //线程池隔离                .andCommandPropertiesDefaults(                        HystrixCommandProperties.Setter()                                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)                )        );    }
    @Override    protected String run() throws Exception {        logger.info(poolName + ":我伤心我无奈,可是我默默等待");        TimeUnit.MILLISECONDS.sleep(100);        return poolName + "-run:缘分就是一生的等待";    }
    public void setPoolName(String poolName) {        this.poolName = poolName;    }}</code></pre>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">调用方</span></strong></p>
<pre><code>
import com.brmayi.yuna.util.DemoHystrixCommand;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestControllerpublic class HystrixController {    @Autowired    private ApplicationContext applicationContext;
    @GetMapping("/hystrix")    public String hystrix(String poolName) throws Exception {        DemoHystrixCommand demoHystrixCommand = applicationContext.getBean(DemoHystrixCommand.class);        demoHystrixCommand.setPoolName(poolName);        return demoHystrixCommand.execute();    }}</code></pre>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">验证</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vcxe.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">多点几次</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-seep.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">多点几次</p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-izvp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看日志，日志前缀里Web容器的其他日志线程号和请求hystrix的线程号规则一致，可说明是Web容器的线程。</p>
<p style="">注意看，不管是倩倩还是萍儿，它们的线程数都没有超过最大线程数</p>
<p style=""></p>
<p style=""><strong>线程池隔离验证</strong></p>
<p style=""></p>
<p style="">只要将<strong>隔离hystrix配置</strong>的</p>
<pre><code>HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE</code></pre>
<p style="">改成</p>
<p style="">HystrixCommandProperties.ExecutionIsolationStrategy.<em><span fontsize="" color="rgb(152, 118, 170)" style="color: rgb(152, 118, 170)">THREAD</span></em></p>
<p style="">重启后重复上面验证步骤</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gzhu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看线程池名变成了<strong>隔离hystrix配置</strong>的线程名规则！并且不管是倩倩还是萍儿，它们的线程数都没有超过最大线程数。</p>
<p style=""></p>
<p style="">以上实验说明</p>
<p style="">1&gt;hystrix隔离确实能限制资源</p>
<p style="">2&gt;信号量隔离采用的Web容器的线程池，而线程池隔离采用的是自己独立的线程池。</p>
<p style="">成立。</p>
<p style=""></p>
<p style="">其他部分限于篇幅，我就不验证了。这里用到了打日志的方法验证线程池情况。如果在生产环境，实际上我们是需要对线程池情况做监控的。可以使用java.lang.management包里的工具注册监控。我们平时使用falcon这样的工具来查看，原理也是先通过java.lang.management包里的工具注册上报信息到服务端采集的。如果自己想查看监控结果，可以用jdk自带的jvisualvm安装一个com-sun-tools-visualvm-modules-mbeans.nbm插件来看。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-knkz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><strong><span style="font-size: 20px">总结</span></strong></p>
<p style="text-align: center; "></p>
<p style="">本篇文章的验证部分很粗糙，限于篇幅，没有把所有需要验证的点覆盖全。想验证我花的hystrix资源保护生命周期的图，至少要结合源码和验证两方面。先当留作业了，有时间我把详细过程补上。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/zmckcGqKVbJGlmmTqrQhnw</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/hystrixxian-cheng-chi-ge-chi-de-yuan-li-yu-yan-zheng</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 07:06:59 GMT</pubDate></item><item><title><![CDATA[Java的强引用、软引用、弱引用、虚引用]]></title><link>https://wiki.dgfygk.com/archives/javade-qiang-yin-yong-ruan-yin-yong-ruo-yin-yong-xu-yin-yong</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E7%9A%84%E5%BC%BA%E5%BC%95%E7%94%A8%E3%80%81%E8%BD%AF%E5%BC%95%E7%94%A8%E3%80%81%E5%BC%B1%E5%BC%95%E7%94%A8%E3%80%81%E8%99%9A%E5%BC%95%E7%94%A8&amp;url=/archives/javade-qiang-yin-yong-ruan-yin-yong-ruo-yin-yong-xu-yin-yong" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">背景</span></strong></p>
<p style="">工程中用到guava的本地缓存。它底层实现和API接口上使用了强引用、软引用、弱引用。所以温故知新下，也夯实下基础。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">预备知识</span></strong></p>
<p style="">先来看下GC日志每个字段的含义</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">Young GC示例解释</span></strong></p>
<p style="">[GC (Allocation Failure) [PSYoungGen: 273405K-&gt;20968K(278016K)] 480289K-&gt;473619K(737792K), 0.1090103 secs] [Times: user=0.19 sys=0.27, real=0.11 secs]&nbsp;</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">解释</span></strong></p>
<p style="">[GC(产生GC的原因，例子中是由于分配内存失败)&nbsp; [PSYoungGen: 年轻代回收前空间-&gt;年轻代回收后空间(年轻代总空间)] 堆区的回收前空间-&gt;堆区的回收后空间(堆区的总空间), GC耗时] [Times: 用户空间耗时&nbsp;系统空间耗时,&nbsp;实际耗时]</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">Full GC示例解释&nbsp;</span></strong></p>
<p style="">[Full GC (Ergonomics) [PSYoungGen: 20968K-&gt;20805K(278016K)] [ParOldGen: 452651K-&gt;451654K(864256K)] 473619K-&gt;472460K(1142272K), [Metaspace: 5793K-&gt;5793K(1056768K)], 0.1565987 secs] [Times: user=0.70 sys=0.00, real=0.16 secs]&nbsp;</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">解释</span></strong></p>
<p style="">[Full GC (产生GC原因，例子中是由于要放入老年代的对象超过了老年代的剩余空间) [PSYoungGen: -&gt;年轻代回收前空间-&gt;年轻代回收后空间(年轻代总空间)] [ParOldGen: 老年代回收前空间-&gt;老年代回收后空间(老年代总空间)] 堆区的回收前空间-&gt;堆区的回收后空间(堆区的总空间), [Metaspace: 元空间的回收前空间-&gt;元空间的回收后空间(元空间的总空间)], &nbsp;GC耗时] [Times: 用户空间耗时&nbsp;系统空间耗时,&nbsp;实际耗时]</p>
<p style=""></p>
<p style="">创建一个10M的大对象，重写finalize方法。<span style="font-size: 18px; color: rgb(77, 77, 77)">finalize()方法会在对象被回收前调用</span>，一个对象只有一次被调用的机会。对象可以在这个方法里进行自救，逃过被垃圾回收。Java设计这个方法可以被覆写是为了让有些对象在回收前做一些检查，完成一些前置条件再被垃圾回收。正式代码不建议使用。因为是测试，所以为了验证效果，这里打印GC日志信息。</p>
<pre><code>   byte[] bytes = new byte[10 * 1024 * 1024];
    int index;
    public Ref(int index) {
        this.index = index;
    }

    public byte[] getBytes() {
        return bytes;
    }

    @Override
    public void finalize() {
        System.out.println("index " + index + "'s " + bytes.length + "is going to be GG");
    }
}</code></pre>
<p style=""></p>
<p style="">为了测试，JVM参数统一为-Xms20M -XX:+PrintGCDetails。Xms20M表示堆内存设置最大为20M，-XX:+PrintGCDetails代表打印详细的GC信息。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xecn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">强引用</span></strong></p>
<p style="">先来做个实验(代码已经上传github:https://github.com/xiexiaojing/yuna)</p>
<pre><code>@Test
public void  testRawStrong() {
    List&lt;Ref&gt; list = Lists.newArrayList();
    for(int i=0; i&lt;100; i++) {
        list.add(new Ref(i));
        System.out.println(list.get(i));
    }
}</code></pre>
<p style="">这段代码由上线的设置可知，由于最大设置20M堆空间，所以很快触发了GC。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-enel.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">不过Xmx这个值是建议内存最大使用值。如果内存使用超过这个值，jvm认为还有内存可以使用，也会将对象一直往堆里面放。所以2次GC之后JVM自动扩容了，之后就不再频繁GC。最终用到了满足程序需要的内存。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-insb.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">强引用是直接new出来调用的对象，大家都知道。由上面实验可知，在系统内存很富裕的情况下，因为强引用内存不能被释放，所以会多申请了很多内存。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">软引用</span></strong></p>
<p style="">软引用会<span style="font-size: 16px; color: rgb(64, 64, 64)">在系统将要发生内存溢出异常之前，将会把这些软引用对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存，才会抛出内存溢出异常。</span></p>
<p style=""><span style="font-size: 16px; color: rgb(64, 64, 64)">用实验说明一下，为了防止JVM自动调整堆大小，我们把堆设置-Xmx200M。
  <br></span></p>
<pre><code>@Test
public void  testRawSoft() {
    List&lt;SoftReference&lt;Ref&gt;&gt; list = Lists.newArrayList();
    for(int i=0; i&lt;100; i++) {
        list.add(new SoftReference&lt;&gt;(new Ref(i)));
        System.out.println(list.get(i).get());
    }
}</code></pre>
<p style=""></p>
<p style="">从下面实验结果可以看到数次的 GC之后，内存要撑不住的时候，Ref的软引用对象触发了finalize方法。这意味着它将要被内存回收了。说明GC会引发软引用里对象的内存回收，即使这个软引用本身还被强引用(list调用)着。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mhkh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span style="font-size: 16px; color: rgb(64, 64, 64)">最终回收了这些内存也不能避免OOM的结局：</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ipyo.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">因为软引用通常情况下就是这样，只有内存马上要溢出了才触发它的GC。就好像扁鹊见蔡桓公的时候，蔡桓公的病已经很深了，马上就没救了。所以有了下面弱引用的方法：有病早治。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(64, 64, 64)">弱引用</span></strong></p>
<p style="">弱引用是发生了一次垃圾回收后，既存的弱引用对象就开始回收。通常，一个弱引用对象仅能生存到下一次垃圾回收前。</p>
<p style="">用实验说明一下，为了防止JVM自动调整堆大小，我们把堆设置-Xmx200M。</p>
<pre><code>@Test
public void  testRawWeak() {
    List&lt;WeakReference&lt;Ref&gt;&gt; list = Lists.newArrayList();
    for(int i=0; i&lt;100; i++) {
        list.add(new WeakReference&lt;&gt;(new Ref(i)));
        System.out.println(list.get(i).get());
    }
}</code></pre>
<p style=""></p>
<p style="">从下面的实验结果可知在发生了一次GC之后，已经生成的软引用对象都都回收了。下一次GC，这中间产生的软引用对象也都被回收了。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xswu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px; color: rgb(64, 64, 64)">最终，由于GC及时，整个过程没有爆发OOM，平安的结束了。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-erun.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px; color: rgb(64, 64, 64)">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 20px">虚引用</span></strong></p>
<p style="">虚引用也叫幻影引用。任何时候可能被GC回收，就像没有引用一样。</p>
<p style="">并且他必须和引用队列一起使用，用于跟踪垃圾回收过程，当垃圾回收器回收一个持有虚引用的对象时，在回收对象后，将这个虚引用对象加入到引用队列中，用来通知应用程序垃圾的回收情况。</p>
<p style="text-align: justify; "><span fontsize="" color="">先来实验一下，从下面结果可看到从一开始取出来就是空对象，基本上刚创建出来就被回收了。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wozi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">一个像是从来没有存在过的幻影有什么用呢？Java的Unsafe类和NIO都可以直接访问堆外内存。堆外内存GC管不了，这时候虚引用就排上用场了。我们可以通过引用队列跟踪垃圾回收，做好善后。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">在Guava中使用强软弱引用</span></strong></p>
<pre><code>@Test
public void testStrong() {
    Cache&lt;Integer, Ref&gt; cache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.DAYS).build();
    for(int i=0; i&lt;100; i++) {
        cache.put(i, new Ref(i));
        System.out.println(cache.getIfPresent(i));
    }
    System.out.println(cache.stats().loadSuccessCount());
}

@Test
public void testSoft() {
    Cache&lt;Integer, Ref&gt; cache = CacheBuilder.newBuilder().softValues().build();
    for(int i=0; i&lt;100; i++) {
        cache.put(i, new Ref(i));
        System.out.println(cache.getIfPresent(i));
    }
}

@Test
public void testWeak() {
    Cache&lt;Integer, Ref&gt; cache = CacheBuilder.newBuilder().weakKeys().weakValues().build();
    for(int i=0; i&lt;100; i++) {
        cache.put(i, new Ref(i));
        System.out.println(cache.getIfPresent(i));
    }
}</code></pre>
<p style=""></p>
<p style="">Guava在没有显示设置强、软、弱引用的情况下默认是强引用。这个结论我没有看任何书，而是通过跟踪源码，debug得到的结论。当显示设置为软引用或者弱引用时，运行时GC触发和对象回收之间的关系和自己手动直接测试的结果是一样的，大家可以动手实践下。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">总结</span></strong></p>
<p style="">Java的强软弱虚引用被回收的时机不同：强引用是引用被释放才会回收；软引用是没释放，但是快OOM了就会被回收；弱引用是引用没释放，但是发生了GC后就会被回收；虚引用随时会回收，好像没有存在过，但是会有一个队列来跟踪它的垃圾回收情况。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/1kfSgvz128OfoNwHLWUvhQ</p>]]></description><guid isPermaLink="false">/archives/javade-qiang-yin-yong-ruan-yin-yong-ruo-yin-yong-xu-yin-yong</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 06:36:55 GMT</pubDate></item><item><title><![CDATA[SpringBoot整合web容器]]></title><link>https://wiki.dgfygk.com/archives/springbootzheng-he-webrong-qi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=SpringBoot%E6%95%B4%E5%90%88web%E5%AE%B9%E5%99%A8&amp;url=/archives/springbootzheng-he-webrong-qi" width="1" height="1" alt="" style="opacity:0;">
<p style="">Spring是为简化Java开发而生的。与传统的如Sturts这样的框架相比，它采用最小侵入编程，让开发者更少的感知到它的存在。随着Spring越来越强大，Spring体系也越来越复杂。为了化解戴着镣铐跳舞的窘境，产生了SpringBoot。</p>
<p style="">SpringBoot的理念是约定大于配置。大家都这么用，有了规范，一切就容易多了。这就实现了一个标准化-&gt;流程化-&gt;工具化的跃迁。复杂度大大降低。但是一般一个web工具，引用一个jar包是搞不定的。依赖的冲突问题仍然是一个业界痛点。</p>
<p style="">一般一个web项目使用SpringBoot最好显示的引入5个jar包，其中最后两个是测试用的，请注意统一版本。</p>
<pre><code>&lt;dependency&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;spring-boot-autoconfigure&lt;/artifactId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;${spring.boot.version}&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;${spring.boot.version}&lt;/version&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;exclusions&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;exclusion&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;spring-boot-starter-logging&lt;/artifactId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/exclusion&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;exclusion&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</code></pre>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">spring-boot-starter-tomcat</span>&lt;/artifactId&gt;</p>
<pre><code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/exclusion&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/exclusions&gt;&lt;/dependency&gt;&lt;dependency&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</code></pre>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">spring-boot-starter-jetty</span>&lt;/artifactId&gt;</p>
<pre><code>&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;${spring.boot.version}&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;spring-boot-test&lt;/artifactId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;${spring.boot.version}&lt;/version&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;scope&gt;test&lt;/scope&gt;&lt;/dependency&gt;&lt;dependency&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;${spring.boot.version}&lt;/version&gt;&nbsp;&nbsp;&nbsp;&nbsp;&lt;scope&gt;test&lt;/scope&gt;&lt;/dependency&gt;</code></pre>
<p style="">注意上面红色部分，显示的去掉对tomcat的依赖，使用jetty做容器。</p>
<p style=""><strong><span fontsize="" color="">嵌入式web容器和非嵌入式web容器的差别</span></strong></p>
<p style="">这里想起自己在硅谷时的一件事情。</p>
<p style="">那时候公司里有一批同事都是之前netflix一个团队的，我刚到硅谷的时候，明显能感觉到他们的高傲。开始时，我出于习惯，那些同事在做一些事情的时候，我看到有些问题，我会告诉他们更好的方法和其中的原理。确实有许多我可以指导的地方，不是我技术有多好，而是他们都不是java出身的。对java没有那么精通。每次被我指出来问题，他们处理完总要露出鄙夷的面容，以后我就学聪明了，看到问题我也不说。</p>
<p style="">有次，有个同事问我国内用的什么web容器，我说用的tomcat。结果这个同事突然大笑，笑完还特意拉过来其他同事过来啥也不干就是一起笑。我问他们用的什么，他们也回答不上来。好吧，笑就笑吧，都是中国人，也不涉及什么有辱国威。我的人生已经彪悍的不需要解释了，压根就不在乎你们怎么看我。</p>
<p style="">后来有次，这个同事以为发现了我写的一个bug。所以拉上我，拉上很多开发一起围观再现我的bug。我们围在一起看他调试，结果我发现原来是他参数传错了。在他灰头土脸的时候，我只说了一句：“有问题再找我吧，我先回去了。”</p>
<p style="">因为我总是面对他们的质疑和嘲讽很低调，加上能干活，所以他们非常喜欢我，对我很nice，临走还特意给我饯行。签证到期回国再回来时，第一天他们就来找我，说我应该坐在他们中间，我们都是开发（只有我一个开发，其他都是产品，运营啥的）。总结一个经验：做人要低调，不要专门寻求认同，事情做好了自然有人认同你。</p>
<p style="">OK，先说他们为什么要笑：那是几年前的事情，那时候嵌入式的web容器在美国已经很流行了。但是那时候在国内，因为公司统一的标准还都是使用普通的web容器。一个新型的标准在大公司里普及确实没有这么快的。而嵌入式的web容器只是更便于DevOps，并没有什么性能和稳定性的差别。DevOps需要建立在公司统一基础设施的基础上，公司大了就有历史包袱，也是挺正常的。</p>
<p style="">有人说中国的互联网是国际领先的。这种领先是建立在需求驱动的基础上的。好比小时候我读《水浒》特别羡慕他们大口吃肉，现在我家小鲜肉早上给他煎牛排都已经不怎么吃了，天天换花样，劝饭这个费劲。现在物质发达，我倒觉得从这个方面看我小时候更幸福些，现在的小孩无欲无求。好比是硅谷，没有很强的外部驱动，有精力练就很扎实的基本功，很多人40多岁还是普通程序员。在中国工作两三年就带团队的大有人在，对程序员还是刚需，好像一个有很强消化能力的孩子。说不上哪个好哪个不好。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px">Tomcat和Jetty的差别</span></strong></p>
<p style="">Tomcat和Jetty可以算的上是目前全球范围内最著名的两款Servlet容器。它们的实现都遵循Java Servlet规范。所以对于多数Java Web应用部署任意一个都可以。对应用开发来说，飞行中互换几乎是无感的。</p>
<p style="">从趋势来看，Jetty人气越来越盛。很大部分原因，大家在做选型的时候，都有一个业界调研，调研一线公司都是怎么用的。而目前Google App Engine选择了Jetty。这相当于是在云环境下，这是个默认的选择。</p>
<p style="text-align: center; "><strong><span fontsize="" color="">区别对照表</span></strong></p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 300px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">Jetty</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">Tomcat</p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">轻量</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(0, 249, 0)" style="color: rgb(0, 249, 0)">√</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(255, 38, 0)" style="color: rgb(255, 38, 0)">×</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">节省内存</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(0, 249, 0)" style="color: rgb(0, 249, 0)">√</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(255, 38, 0)" style="color: rgb(255, 38, 0)">×</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">快速高效</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(0, 249, 0)" style="color: rgb(0, 249, 0)">√</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(255, 38, 0)" style="color: rgb(255, 38, 0)">×</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">可插拔性</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(0, 249, 0)" style="color: rgb(0, 249, 0)">√</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(255, 38, 0)" style="color: rgb(255, 38, 0)">×</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">可扩展性</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(0, 249, 0)" style="color: rgb(0, 249, 0)">√</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(255, 38, 0)" style="color: rgb(255, 38, 0)">×</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style="">支持大规模企业级应用高级特性</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(255, 38, 0)" style="color: rgb(255, 38, 0)">×</span></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="border-color: initial;">
     <p style=""><span fontsize="" color="rgb(0, 249, 0)" style="color: rgb(0, 249, 0)">√</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="">Jetty更满足分布式环境的需求，Tomcat有一些企业级环境使用的高级特性，一般用不到，用到时也是收费的。</p>
<p style=""></p>
<p style="">结论：建议用Jetty。&nbsp;</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/3XzD8hyAkJrwRSmdAZEcLw</p>]]></description><guid isPermaLink="false">/archives/springbootzheng-he-webrong-qi</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 24 Sep 2024 06:34:59 GMT</pubDate></item><item><title><![CDATA[JAVA数据处理的常用技术]]></title><link>https://wiki.dgfygk.com/archives/javashu-ju-chu-li-de-chang-yong-ji-shu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JAVA%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%9A%84%E5%B8%B8%E7%94%A8%E6%8A%80%E6%9C%AF&amp;url=/archives/javashu-ju-chu-li-de-chang-yong-ji-shu" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">背景</span></strong></p>
<p style=""><span fontsize="" color="">在实际开发中，数据的处理有五种：获取、传输、存储、分析、转换。每种各对应一些常用的技术。</span></p>
<p style=""><strong><span style="font-size: 20px">序列化和反序列化</span></strong></p>
<p style="">序列化是将对象的信息转换为可传输或可存储形式的过程。反序列化就是反过来让这些可传输的、可存储的信息变回对象。</p>
<p style="">传输的序列化除了安全性的考虑，因为涉及到和第三方通信，所以还有重要的一点是可读性和不变性。而存储的链路短，可控，所以更讲究效率。</p>
<p style="">传输最常用的序列化手段是JSON这样人眼可读的。而存储会使用<span fontsize="" color="">protostuff</span>这种将key值映射成编码来传输的。因为1，2，3比one、two、four更省空间更高效。但是传输中都用编码，解析时就很难判断它的意义。双方还要进行额外的约定。本来3代表four，中间加了一个three，3代表three的话，对方没有及时被通知，那么解析传输过来的消息就是错的。</p>
<p style=""><strong><span style="font-size: 20px">反射</span></strong></p>
<p style="">Java反射是在运行时，对于任何一个类，都可以知道这个类有哪些方法和属性。对于任何一个对象，都能对它的方法和属性进行调用。</p>
<p style="">常用数据转换工具，比如Spring的RequestParam、RequestBody、ResponseBody内部就用了反射机制。还有Jackson等工具类。甚至在业务代码中直接使用反射也是很常见的。比如设计一个AI助手问答机。想实现：</p>
<pre><code>小A：我要搜索美女"冰冰"AI助手：OK，搜索"冰冰"小A：想知道她的"年龄"AI助手：21岁</code></pre>
<p style="">上面这个JAVA实现是这样的：</p>
<pre><code>Field&nbsp;field&nbsp;=&nbsp;美女.class.getDeclaredField(年龄);field.setAccessible(true);return field.get(冰冰).toString();</code></pre>
<p style=""><strong><span style="font-size: 20px">动态代理</span></strong></p>
<p style="">代理模式是为了提供增强的或不同的操作，而插入来替代实际对象的对象。这些操作涉及到与实际对象的通信，所以称为代理。</p>
<p style="">Spring主要的两大思想IoC和AOP。对于IoC，利用的是反射机制。而AOP使用了动态代理，当然底层也是反射。</p>
<p style="">JDK动态代理只能给有接口的类代理。本质是通过反射获取要执行的方法，并在执行前或者后加入一些代理处理操作。</p>
<p style="">cglib本质上用继承的方法实现的，是通过动态生成一个子类去覆盖所要代理的类。用final修饰的不能被覆盖的就不代理了。</p>
<p style="">spring动态代理是优先使用JDK动态代理，如果目标没有实现任何接口，则创建一个cglib代理。如果几个类实现了都实现了一个通用接口，比如Runnable，并且加了Component请spring来负责其生命周期。这时候会抛出一个Proxy代理异常。说期望加载一个Bean，实际上实现却不只一个。这时候可以在这个类上加下面标签强制使用cglib代理来解决。</p>
<pre><code>@EnableAspectJAutoProxy(proxyTargetClass&nbsp;=&nbsp;true)</code></pre>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">总结</span></strong></p>
<p style="">我工作十几年来，查找和排序算法一直被奉为经典。而这两项就是搜索技术的核心。大数据是建立在搜索技术基础上的。AI又是建立在大数据基础上的。可见查找和排序的核心地位。看似高深的技术也是从最底层开始。</p>
<p style="">苦练基本功。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/gBejP_6DfGxotrGzKUqCzA</p>]]></description><guid isPermaLink="false">/archives/javashu-ju-chu-li-de-chang-yong-ji-shu</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 24 Sep 2024 06:12:56 GMT</pubDate></item><item><title><![CDATA[Java中不容拒绝的一种优雅的写法]]></title><link>https://wiki.dgfygk.com/archives/javazhong-bu-rong-ju-jue-de-yi-zhong-you-ya-de-xie-fa</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=Java%E4%B8%AD%E4%B8%8D%E5%AE%B9%E6%8B%92%E7%BB%9D%E7%9A%84%E4%B8%80%E7%A7%8D%E4%BC%98%E9%9B%85%E7%9A%84%E5%86%99%E6%B3%95&amp;url=/archives/javazhong-bu-rong-ju-jue-de-yi-zhong-you-ya-de-xie-fa" width="1" height="1" alt="" style="opacity:0;">
<p style="">做CodeReview的时候，评价一段代码的好坏通常是仁者见仁。还好有一种无法拒绝的理由叫做【规定】。</p>
<p style=""></p>
<p style="">下面讲一种通过规定以理服人的例子：</p>
<p style="text-align: center; "></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">观点</span></strong></p>
<p style="">不要用xx != null &amp;&amp; xx.length()&gt;0&nbsp;来判空，可以使用apache的commons包。例如使用：</p>
<p style="">StringUtils.isNotEmpty</p>
<p style="">CollectionUtils.isEmpty</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">不容拒绝理由</span></strong></p>
<p style="">使用xx != null &amp;&amp; xx.length()&gt;0&nbsp;来判空，公司里的静态代码检查会检查让你覆盖这两个条件，你需要写两个单测来完成任务。而使用apache的commons只需要写一个单测。你的条件覆盖率达标了吗？</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">深层原因</span></strong></p>
<p style="">【编程一生】的文章怎么可能只给一个不容拒绝的理由这么浅。建议这样使用毕竟有背后深层的优势，拿StringUtils.isNotEmpty举例看看apache的代码优雅在哪里。</p>
<p style=""></p>
<p style="">StringUtils类的源码：</p>
<pre><code>public static boolean isNotEmpty(String str) {
    return !isEmpty(str);
}</code></pre>
<pre><code>public static boolean isEmpty(String str) {
    return str == null || str.length() == 0;
}</code></pre>
<p style=""></p>
<p style="">咦，好像和自己实现的差不多嘛！str <mark style="display: inline-block;"> null || str.length() </mark> 0 取个反，还多个一个反运算。效率一点也不高嘛！</p>
<p style=""></p>
<p style="">请注意这两个方法是静态的！静态方法<span style="font-size: mediumpx; color: rgb(18, 18, 18)">在类加载时候初始化，常驻在内存中只存一份。而自己写的判断语句，只能这个方法使用，其他方法同样的语句要复制一份。静态方法更加方便快捷。</span></p>
<p style=""><span style="font-size: mediumpx; color: rgb(18, 18, 18)">
  <br>
 </span></p>
<p style=""><span style="font-size: mediumpx; color: rgb(18, 18, 18)">当然，apache的commons最根本的优势是语义更加清晰，更易读。</span></p>
<p style=""></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/javazhong-bu-rong-ju-jue-de-yi-zhong-you-ya-de-xie-fa</guid><dc:creator>黄健</dc:creator><category>JAVA</category><pubDate>Tue, 24 Sep 2024 06:03:15 GMT</pubDate></item><item><title><![CDATA[关于项目使用组件版本选择的思考]]></title><link>https://wiki.dgfygk.com/archives/guan-yu-xiang-mu-shi-yong-zu-jian-ban-ben-xuan-ze-de-si-kao</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%85%B3%E4%BA%8E%E9%A1%B9%E7%9B%AE%E4%BD%BF%E7%94%A8%E7%BB%84%E4%BB%B6%E7%89%88%E6%9C%AC%E9%80%89%E6%8B%A9%E7%9A%84%E6%80%9D%E8%80%83&amp;url=/archives/guan-yu-xiang-mu-shi-yong-zu-jian-ban-ben-xuan-ze-de-si-kao" width="1" height="1" alt="" style="opacity:0;">
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485934&amp;idx=1&amp;sn=71bc51653cef4a1b3c86e663fb2a3c62&amp;chksm=fafde340cd8a6a5646411696514f350cc1cb00dbac89d6989115f3eeaf1b05d9bb4167b6011e&amp;scene=21#wechat_redirect" target="_blank">《项目中怎样做技术选型》</a>中，我介绍了技术选型的一些思考。一旦选定了用什么技术，还涉及到版本的选择。版本不是越高越好，也不是用的越多越好，涉及很多因素。今天从具体实例入手来聊一聊版本选择的问题。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(255, 41, 65)">实例一&nbsp;servlet版本</span></strong></p>
<p style=""></p>
<p style="">我最近在带一个新项目，因为搭建环境的时候，选择的版本比较新。下游是运行了好多年的老项目，版本相对老一些。其中servlet版本，新项目使用4.0.1版本，它在处理httpHeader时全转成了小写。下游使用3.1.0版本，它是按照原来的大小写原样透传的。这个问题不难处理，下游要兼容一下，做小写转换后再处理。</p>
<p style=""></p>
<p style="">问题是由此引起的思考：新项目很多的版本比较新，是否是最合适的呢？这个问题我没有马上给出结论。而是说这个是个重要不紧急的事情，并不会阻塞开发进度，所以先保持现状不动，我过节有时间好好调研一下再决定。</p>
<p style=""></p>
<p style="">这是新项目中的使用的一些jar包：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ipks.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">这是老项目中使用的一些jar包：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pqmw.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">欢迎大家指出使用版本的问题，帮助我们保持系统稳定性。</p>
<p style=""></p>
<p style="">上面总共涉及三个servlet版本：</p>
<p style="">servlet-api---2.5(实际没有用)</p>
<p style="">javax.servlet-api---3.1.0</p>
<p style="">javax.servlet-api---4.0.1</p>
<p style=""></p>
<p style="">分别进行调研结果如下。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">servlet-api---2.5</span></strong></p>
<p style="">支持配置文件形式注册servlet、listener、filter等</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">javax.servlet-api---3.1.0</span></strong></p>
<p style="">同时支持注解形式注入</p>
<p style="">同时支持动态注入</p>
<p style="">支持扩展插件</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">javax.servlet-api---4.0.1</span></strong></p>
<p style="">支持<span style="font-size: 16px">从HTTP请求推送生成器等新特性</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(255, 41, 65)">结论</span></strong></p>
<p style=""></p>
<p style="">因为实际我们这个项目用<strong><span style="font-size: 18px">javax.servlet-api</span></strong>只是要获取http请求信息，别的都用不到。所以更合适使用线上稳定运行的版本<strong><span style="font-size: 18px">3.1.0</span></strong>。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px">实例二 spring-boot版本</span></strong></p>
<p style=""></p>
<p style="">我们新项目中spring-boot的版本用的是2.3.2.<span style="font-size: 16px">RELEASE。这个也高于目前项目中使用的2.1.*的版本。我也调研了一下官网。</span></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kvki.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">2.1.x版本已经在19年停止维护，商业支持(商业支持是要收费的，但是如果发现重大bug问题会公开)也在21年初停止了。如果继续使用这个版本，一旦有问题，得不到官方支持，不建议使用。</p>
<p style=""></p>
<p style="">2.3.x也已经在去年停止维护，商业支持会支持到今年8月份。这表明2.3.x是一个很成熟的版本了。相比而言，虽然免费维护上已经不再支持，但是看下面：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xsjq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">上面可以看到2.3.x的版本官方还是建议使用的。</p>
<p style=""></p>
<p style="">那在2.3.x中到底使用哪个版本呢？官网中GA(正式版本)建议的是2.3.12.RELEASE。先看一下，各个版本的变更主要变更了什么。</p>
<p style=""></p>
<p style="">通过调研，变更主要是两项：</p>
<p style="">1&gt;bug修复</p>
<p style="">2&gt;Spring Framework等框架的版本升级</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">bug修复</span></strong></p>
<h1 style="" id=""></h1>
<h1 style="" id="spring-boot-2.3.2-%E5%8F%91%E5%B8%83%EF%BC%8C%E8%A7%A3%E5%86%B3-too-many-open-files-%E5%AF%BC%E8%87%B4%E7%9A%84%E5%BA%94%E7%94%A8%E5%AE%95%E6%9C%BA%E9%97%AE%E9%A2%98%E3%80%82">Spring Boot 2.3.2 发布，解决 Too many open files 导致的应用宕机问题。</h1>
<h1 style="" id="-1"></h1>
<h1 style="" id="spring-boot-2.3.6-%E4%B9%9F%E6%98%AF%E4%B8%80%E4%B8%AA%E9%87%8D%E8%A6%81%E7%9A%84fix-bug%E7%89%88%E6%9C%AC">Spring Boot 2.3.6&nbsp;也是一个重要的fix bug版本</h1>
<ul>
 <li>
  <p style="">修复配置元数据注解处理器可能会对布尔属性使用错误访问器的问题&nbsp;#24058</p>
 </li>
 <li>
  <p style="">使用 Tomcat 时，当 Spring Boot 的等价属性 (equivalent property) 没有设置，通过 javax.net.ssl 系统属性设置的密钥和信任存储密码会被 null 覆盖&nbsp;#24052</p>
 </li>
 <li>
  <p style="">@DataJpaTest无法将 H2 和 schema.sql 以及&nbsp;spring.datasource.schema-username 一起使用&nbsp;#24023</p>
 </li>
 <li>
  <p style="">当 Quartz 数据源不是主要数据源时，JdbcStoreTypeConfiguration 会选择主要数据源的事务&nbsp;#24014</p>
 </li>
 <li>
  <p style="">修复 TestTypeExcludeFilter 没有实现其超类所需要的 hashCode 和 equals&nbsp;#24012</p>
 </li>
 <li>
  <p style="">自动配置不应使用@PostConstruct，因为在没有依赖 jakarta-annotation-api 的情况下，它在 Java 11+ 上是被忽略的&nbsp;#24009</p>
 </li>
 <li>
  <p style="">如果配置文件包含一个隐藏的路径元素，则不再加载&nbsp;#23983</p>
 </li>
 <li>
  <p style="">当调用 spring-boot-loader 的 JarFileWrapper.stream() 时，出现 "java.lang.IllegalStateException: zip file closed"&nbsp;#23821</p>
 </li>
</ul>
<p style=""></p>
<h1 style="" id="spring-boot-2.3.9-bug%E4%BF%AE%E5%A4%8D%EF%BC%9A">Spring Boot 2.3.9 BUG修复：</h1>
<ul>
 <li>
  <p style="">在通过jOOQ ＃25279访问数据库之前，由Flyway或Liquibase执行的迁移可能尚未完成。</p>
 </li>
 <li>
  <p style="">Hibernate的新的hibernate-micrometer模块的依赖性管理丢失了＃25277</p>
 </li>
 <li>
  <p style="">DatabaseDriver无法正确检测到Amazon Redshift ＃25265</p>
 </li>
 <li>
  <p style="">如果将bean定义为ConnectionFactory ＃25138，则缺少RabbitMQ指标</p>
 </li>
 <li>
  <p style="">使用JPA延迟存储库时，子上下文的ContextRefreshedEvent可能导致死锁＃24966</p>
 </li>
 <li>
  <p style="">Spring Data Solr支持未标记为已弃用 ＃24942</p>
 </li>
 <li>
  <p style="">在MetricsClientHttpRequestInterceptor中记录指标时失败可能会干扰RestTemplate的主要行为＃24753</p>
 </li>
 <li>
  <p style="">WebMvcTest和WebFluxTest忽略用户提供的Thymeleaf IDialect bean ＃24149</p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">Spring Framework等框架的版本升级</span></strong></p>
<p style=""></p>
<p style="">我们之前线上有用spring boot 2.1.x的版本，对应spring的5.1.x。spring boot 2.3.x的版本对应的主要是spring 5.2.x。看官网怎么说：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hvoc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">5.2.x版本这几年刚刚停止维护，商业维护还有近两年的时间。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-avff.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">官方推荐学习的正式版本除了最新版外，就是5.2.19.RELEASE。</p>
<p style=""></p>
<p style="">spring boot除了spring framework还有一个重要组成netty。官网有介绍2.3.12.RELEASE对应的netty版本是netty4。目前公司已经在推升级组件，其中就涉及netty使用的netty4，与公司要求相符。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-typo.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">另外，我查了spring boot 2.3.x明确表示提供对jdk8及以上的支持，并且在jdk8上有全面的测试。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(255, 41, 65)">结论</span></strong></p>
<p style=""></p>
<p style="">建议采用 spring boot 2.3.12.RELEASE 版本。</p>
<p style=""></p>
<p style="">因为2.3.x版本已经非常稳定，而2.3的小版本更新基本上都是在修复bug。而且修复的是linux交互、死锁等相对底层bug。其依赖版本也符合公司要求。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/erSu5cAxbkaOUYcfWk44sw</p>]]></description><guid isPermaLink="false">/archives/guan-yu-xiang-mu-shi-yong-zu-jian-ban-ben-xuan-ze-de-si-kao</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 05:57:37 GMT</pubDate></item><item><title><![CDATA[spring aop聊点不一样的东西]]></title><link>https://wiki.dgfygk.com/archives/spring-aopliao-dian-bu-yi-yang-de-dong-xi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=spring%20aop%E8%81%8A%E7%82%B9%E4%B8%8D%E4%B8%80%E6%A0%B7%E7%9A%84%E4%B8%9C%E8%A5%BF&amp;url=/archives/spring-aopliao-dian-bu-yi-yang-de-dong-xi" width="1" height="1" alt="" style="opacity:0;">
<h3 style="text-align: center; " id="%E5%89%8D%E8%A8%80"><strong>前言</strong></h3>
<p style="line-height: 26px">前几篇文章本打算写spring aop的，但是强忍着没有写（旁白：也有可能是没想好该怎么写😝），就是为了今天整个专题，因为它是spring中最核心的技术之一，实在太重要了。</p>
<p style="line-height: 26px">关于spring aop的文章网上一搜一大堆，但我想写点不一样的东西，尝试一种全新的写作风格，希望您会喜欢。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-umic.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="text-align: center; " id="%E4%BB%8E%E5%AE%9E%E6%88%98%E5%87%BA%E5%8F%91"><strong>从实战出发</strong></h3>
<p style="line-height: 26px">很多文章讲spring aop的时候，一开始就整一堆概念，等我们看得差不多要晕的时候，才真正进入主题。。。</p>
<p style="line-height: 26px">我却相反，没错，先从实战出发。</p>
<p style="line-height: 26px">在spring aop还没出现之前，想要在目标方法之前先后加上日志打印的功能，我们一般是这样做的：</p>
<pre><code>@Service
public  class TestService {

    public void doSomething1() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }

    public void doSomething2() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }

    public void doSomething3() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }

    public void beforeLog() {
        System.out.println("打印请求日志");
    }

    public void afterLog() {
        System.out.println("打印响应日志");
    }
}
</code></pre>
<p style="line-height: 26px">如果加了新doSomethingXXX方法，就需要在新方法前后手动加beforeLog和afterLog方法。</p>
<p style="line-height: 26px">原本相安无事的，但长此以往，总有会出现几个刺头青。</p>
<p style="line-height: 26px">刺头青A说：每加一个新方法，都需要加两行重复的代码，是不是很麻烦？</p>
<p style="line-height: 26px">刺头青B说：业务代码和公共代码是不是耦合在一起了？</p>
<p style="line-height: 26px">刺头青C说：如果有几千个类中加了公共代码，而有一天我需要删除，是不是要疯了？</p>
<p style="line-height: 26px">spring大师们说：我们提供一套spring的aop机制，你们可以闭嘴了。</p>
<p style="line-height: 26px">下面看看用spring aop（偷偷说一句，还用了<span style="font-size: 14px; color: rgb(89, 89, 89)">aspectj</span>）是如何打印日志的：</p>
<pre><code>@Service
public  class TestService {

    public void doSomething1() {
        System.out.println("==doSomething1==");
    }

    public void doSomething2() {
        System.out.println("==doSomething1==");
    }

    public void doSomething3() {
        System.out.println("==doSomething1==");
    }
}
@Component
@Aspect
public  class LogAspect {

    @Pointcut("execution(public * com.sue.cache.service.*.*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void beforeLog() {
        System.out.println("打印请求日志");
    }

    @After("pointcut()")
    public void afterLog() {
        System.out.println("打印响应日志");
    }
}
</code></pre>
<p style="line-height: 26px">增加了<code>LogAspect</code>类，在类上加了<code>@Aspect</code>注解。先在类中使用<code>@Pointcut</code>注解定义了pointcut方法，然后将beforeLog和afterLog方法移到这个类中，分别加上<code>@Before</code>和<code>@After</code>注解。</p>
<p style="line-height: 26px">改造后，业务方法在TestService类中，而公共方法在LogAspect类中，是分离的。如果要新加一个业务方法，直接加就好，LogAspect类不用改任何代码，新加的业务方法就自动拥有打印日志的功能，是不是很神奇？</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jabf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 26px">spring aop其实是一种横切的思想，通过动态代理技术将公共代码织入到业务方法中。</p>
<p style="line-height: 26px">这里出于5毛钱的友情，有必要温馨提醒一下。aop是一种思想，不是spring独有的，目前市面上比较出名的有：</p>
<ul>
 <li>
  <p style="">aspectj</p>
 </li>
 <li>
  <p style="">spring aop</p>
 </li>
 <li>
  <p style="">jboss aop</p>
  <p style=""></p>
 </li>
</ul>
<p style="">我们现在主流的做法是将spring aop和<span style="font-size: 14px; color: rgb(89, 89, 89)">aspectj结合使用，</span>spring借鉴了AspectJ的切面，以提供注解驱动的AOP。</p>
<p style="line-height: 26px">此时，一个黑影一闪而过。</p>
<p style="line-height: 26px">刺头青D问：你说的“横切”，“动态代理”，“织入” 是什么东东？</p>
<h3 style="text-align: center; " id="%E5%87%A0%E4%B8%AA%E9%87%8D%E8%A6%81%E7%9A%84%E6%A6%82%E5%BF%B5"><strong>几个重要的概念</strong></h3>
<p style="line-height: 26px">根据上面spring aop的代码，用一张图聊聊几个重要的概念：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-enyb.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<ul>
 <li>
  <p style="">连接点（Joinpoint) 程序执行的某个特定位置，如某个方法调用前，调用后，方法抛出异常后，这些代码中的特定点称为连接点。</p>
 </li>
 <li>
  <p style="">切点（Pointcut） 每个程序的连接点有多个，如何定位到某个感兴趣的连接点，就需要通过切点来定位。</p>
 </li>
 <li>
  <p style="">通知（Advice） 增强是织入到目标类连接点上的一段程序代码。</p>
 </li>
 <li>
  <p style="">切面（Aspect） 切面由切点和通知组成，它既包括了横切逻辑的定义，也包括了连接点的定义，SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。</p>
 </li>
 <li>
  <p style="">目标对象（Target） 需要被增强的业务对象</p>
 </li>
 <li>
  <p style="">代理类（Proxy） 一个类被AOP织入增强后，就产生了一个代理类。</p>
 </li>
 <li>
  <p style="">织入（Weaving） 织入就是将增强添加到对目标类具体连接点上的过程。</p>
 </li>
</ul>
<p style="line-height: 26px">还是那个刺头青D说（旁边：这位仁兄比较好学）：spring aop概念弄明白了，挺简单的。<code>@Pointcut</code>注解的<code>execution</code>表达式刚刚看得我一脸懵逼，可以再说说吗，我请你吃饭？</p>
<h3 style="text-align: center; " id="%E5%88%87%E5%85%A5%E7%82%B9%E8%A1%A8%E8%BE%BE%E5%BC%8F"><strong>切入点表达式</strong></h3>
<p style="line-height: 26px"><code>@Pointcut</code>注解的<code>execution</code>切入点表达，看似简单，里面还是有些内容的。为了更直观一些，还是用张图来总结一下：</p>
<p style="line-height: 26px"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tvkg.png&amp;size=m" width="100%" height="100%" style="display: inline-block">该表达式的含义是：匹配访问权限是public，任意返回值，包名为：com.sue.cache.service，下面的所有类所有方法和所有参数类型。图中所有用*表示，比如图中类名用.*表示的是所有类。如果具体匹配某个类，比如：TestService，则表达式可以换成：</p>
<pre><code>@Pointcut("execution(public * com.sue.cache.service.TestService.*(..))")
</code></pre>
<p style="line-height: 26px">其实spring支持9种表达式，<code>execution</code>只是其中一种。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yctx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 26px"></p>
<h3 style="text-align: center; " id="%E6%9C%89%E5%93%AA%E4%BA%9B%E5%85%A5%E5%8F%A3%EF%BC%9F"><strong>有哪些入口？</strong></h3>
<p style="line-height: 26px">先说说我为什么会问这样一个问题？</p>
<p style="line-height: 26px">spring aop有哪些入口？说人话就是在问：spring中有哪些场景需要调用aop生成代理对象，难道你不好奇吗？</p>
<h4 style="" id="%E5%85%A5%E5%8F%A31"><strong>入口1</strong></h4>
<p style="line-height: 26px">AbstractAutowireCapableBeanFactory类的createBean方法中，有这样一段代码：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-crjs.png&amp;size=m" width="100%" height="100%" style="display: inline-block">它通过BeanPostProcessor提供了一个生成代理对象的机会。具体逻辑在AbstractAutoProxyCreator类的postProcessBeforeInstantiation方法中：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vuay.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">说白了，需要实现<code>TargetSource</code>才有可能会生成代理对象。该接口是对<code>Target</code>目标对象的封装，通过该接口可以获取到目标对象的实例。</p>
<p style=""></p>
<p style="line-height: 26px">不出意外，这时，又会冒出一个黑影。</p>
<p style="line-height: 26px">刺头青F说：这里生成代理对象有什么用呢？</p>
<p style="line-height: 26px">有时我们想自己控制bean的创建和初始化，而不需要通过spring容器，这时就可以通过实现<code>TargetSource</code>满足要求。只是创建单纯的实例还好，如果我们想使用代理该怎么办呢？这时候，入口1的作用就体现出来了。</p>
<h4 style="" id="%E5%85%A5%E5%8F%A32"><strong>入口2</strong></h4>
<p style="line-height: 26px">AbstractAutowireCapableBeanFactory类的doCreateBean方法中，有这样一段代码：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dknu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">它主要作用是为了解决对象的循环依赖问题，核心思路是提前暴露singletonFactory到缓存中。</p>
<p style=""></p>
<p style="line-height: 26px">通过getEarlyBeanReference方法生成代理对象：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nwnw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="line-height: 26px">它又会调用wrapIfNecessary方法：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pepu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这里有你想看到的生成代理的逻辑。</p>
<p style=""></p>
<p style="line-height: 26px">这时。。。。，你猜错了，黑影去吃饭了。。。</p>
<h4 style="" id="%E5%85%A5%E5%8F%A33"><strong>入口3</strong></h4>
<p style="line-height: 26px">AbstractAutowireCapableBeanFactory类的initializeBean方法中，有这样一段代码：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fxcg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="line-height: 26px">它会调用到AbstractAutoProxyCreator类postProcessAfterInitialization方法：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rfec.png&amp;size=m" width="100%" height="100%" style="display: inline-block">该方法中能看到我们熟悉的面孔：wrapIfNecessary方法。从上面得知该方法里面包含了真正生成代理对象的逻辑。</p>
<p style="line-height: 26px">这个入口，是为了给普通bean能够生成代理用的，是spring最常见并且使用最多的入口。</p>
<p style="line-height: 26px">下面为了加深印象，用一张图总结一下：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lswe.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="text-align: center; " id="jdk%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86-vs-cglib"><strong>jdk动态代理 vs cglib</strong></h3>
<p style="line-height: 26px">我猜你们对jdk动态代理和cglib是知道的（即使猜错了也不会少块肉😃），但为了照顾一下新朋友，还是有必要把这两种生成代理的方式拿出来说说。</p>
<h4 style="" id="jdk%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86"><strong>jdk动态代理</strong></h4>
<p style="line-height: 26px">jdk动态代理是通过反射技术实现的，生成代理的代码如下：</p>
<pre><code>public  interface IUser {
    void add();
}

public  class User implements IUser{
    @Override
    public void add() {
        System.out.println("===add===");
    }
}

public  class JdkProxy implements InvocationHandler {

    private Object target;

    public Object getProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target,args);
        after();
        return result;
    }

    private void before() {
        System.out.println("===before===");
    }

    private void after() {
        System.out.println("===after===");
    }
}

public  class Test {
    public static void main(String[] args) {
        User user = new User();
        JdkProxy jdkProxy = new JdkProxy();
        IUser proxy = (IUser)jdkProxy.getProxy(user);
        proxy.add();
    }
}
</code></pre>
<p style="line-height: 26px">首先要定义一个接口IUser，然后定义接口实现类User，再定义类JdkProxy实现<code>InvocationHandler</code>接口，重写invoke方法，该方法中实现额外的逻辑。当然，别忘了在getProxy方法中，用<code>Proxy.newProxyInstance</code>方法创建一个代理对象。</p>
<p style="line-height: 26px">jdk动态代理三个要素：</p>
<ul>
 <li>
  <p style="">定义一个接口</p>
 </li>
 <li>
  <p style="">实现InvocationHandler接口</p>
 </li>
 <li>
  <p style="">使用Proxy创建代理对象</p>
 </li>
</ul>
<h4 style="" id="cglib"><strong>cglib</strong></h4>
<p style="line-height: 26px">cglib底层是通过asm字节码技术实现的，生成代理的代码如下：</p>
<pre><code>public  class User {
    public void add() {
        System.out.println("===add===");
    }
}

public  class CglibProxy implements MethodInterceptor {

    private Object target;

    public Object getProxy(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = method.invoke(target,objects);
        after();
        return result;
    }

    private void before() {
        System.out.println("===before===");
    }

    private void after() {
        System.out.println("===after===");
    }
}

public  class Test {
    public static void main(String[] args) {
        User user = new User();
        CglibProxy cglibProxy = new CglibProxy();
        IUser proxy = (IUser)cglibProxy.getProxy(user);
        proxy.add();
    }
}
</code></pre>
<p style="line-height: 26px">这里不需要定义接口，直接定义目标类User，然后实现<code>MethodInterceptor</code>接口，重写<code>intercept</code>方法，该方法中实现额外的逻辑。当然，别忘了在getProxy方法中，通过Enhancer创建代理对象。</p>
<p style="line-height: 26px">cglib两个要素：</p>
<ul>
 <li>
  <p style="">实现MethodInterceptor接口</p>
 </li>
 <li>
  <p style="">使用Enhancer创建代理对象</p>
 </li>
</ul>
<h4 style="" id="spring%E4%B8%AD%E5%A6%82%E4%BD%95%E7%94%A8%E7%9A%84%EF%BC%9F"><strong>spring中如何用的？</strong></h4>
<p style="line-height: 26px">DefaultAopProxyFactory类的createAopProxy方法中，有这样一段代码：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yuvw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">它里面包含：</p>
<p style=""></p>
<ul>
 <li>
  <p style="">JdkDynamicAopProxy jdk动态代理生成类</p>
 </li>
 <li>
  <p style="">ObjenesisCglibAopProxy cglib代理生成类</p>
 </li>
</ul>
<p style="line-height: 26px">JdkDynamicAopProxy类的invoke方法生成的代理对象。而ObjenesisCglibAopProxy类的父类：CglibAopProxy，它的getProxy方法生成的代理对象。</p>
<h4 style="" id="%E5%93%AA%E4%B8%AA%E6%9B%B4%E5%A5%BD%EF%BC%9F"><strong>哪个更好？</strong></h4>
<p style="line-height: 26px">我猜，不是刺头青，是你，可能会来自灵魂深处的一问：jdk动态代理和cglib哪个更好？</p>
<p style="line-height: 26px">其实这个问题没有标准答案，要看具体的业务场景：</p>
<ol>
 <li>
  <p style="">没有定义接口，只能使用cglib，不说它好不行。</p>
 </li>
 <li>
  <p style="">定义了接口，需要创建单例或少量对象，调用多次时，可以使用jdk动态代理，因为它创建时更耗时，但调用时速度更快。</p>
 </li>
 <li>
  <p style="">定义了接口，需要创建多个对象时，可以使用cglib，因为它创建速度更快。</p>
 </li>
</ol>
<blockquote>
 <p style=""><strong><span style="font-size: 34px; color: rgba(64, 184, 250, 0.5)">❝</span></strong></p>
 <p style="line-height: 26px">随着jdk版本不断迭代更新，jdk动态代理创建耗时不断被优化，8以上的版本中，跟cglib已经差不多。所以spring官方默认推荐使用jdk动态代理，因为它调用速度更快。</p>
 <p style=""><span fontsize="" color="rgba(64, 184, 250, 0.5)" style="color: rgba(64, 184, 250, 0.5)">❞</span></p>
</blockquote>
<p style="line-height: 26px">出于人道主义关怀，免费赠送一条有用经验：如果要强制使用cglib，可以通过以下两种方式：</p>
<ul>
 <li>
  <p style="">spring.aop.proxy-target-class=true</p>
 </li>
 <li>
  <p style="">@EnableAspectJAutoProxy(proxyTargetClass = true)</p>
 </li>
</ul>
<h3 style="text-align: center; " id="%E4%BA%94%E7%A7%8D%E9%80%9A%E7%9F%A5"><strong>五种通知</strong></h3>
<p style="line-height: 26px">spring默认提供了五种通知：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ozsk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="line-height: 26px">按照国际惯例，不，按照我个人习惯，先看看他们是怎么用的。</p>
<h4 style="" id="%E5%89%8D%E7%BD%AE%E9%80%9A%E7%9F%A5"><strong>前置通知</strong></h4>
<p style="line-height: 26px">该通知在方法执行之前执行，只需在公共方法上加@Before注解，就能定义前置通知：</p>
<pre><code>@Before("pointcut()")
public void beforeLog(JoinPoint joinPoint) {
    System.out.println("打印请求日志");
}
</code></pre>
<h4 style="" id="%E5%90%8E%E7%BD%AE%E9%80%9A%E7%9F%A5"><strong>后置通知</strong></h4>
<p style="line-height: 26px">该通知在方法执行之后执行，只需在公共方法上加@After注解，就能定义后置通知：</p>
<pre><code>@After("pointcut()")
public void afterLog(JoinPoint joinPoint) {
    System.out.println("打印响应日志");
}
</code></pre>
<h4 style="" id="%E7%8E%AF%E7%BB%95%E9%80%9A%E7%9F%A5"><strong>环绕通知</strong></h4>
<p style="line-height: 26px">该通知在方法执行前后执行，只需在公共方法上加@Round注解，就能定义环绕通知：</p>
<pre><code>@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("打印请求日志");
    Object result = joinPoint.proceed();
    System.out.println("打印响应日志");
    return result;
}
</code></pre>
<h4 style="" id="%E7%BB%93%E6%9E%9C%E9%80%9A%E7%9F%A5"><strong>结果通知</strong></h4>
<p style="line-height: 26px">该通知在方法结束后执行，能够获取方法返回结果，只需在公共方法上加@AfterReturning注解，就能定义结果通知：</p>
<pre><code>@AfterReturning(pointcut = "pointcut()",returning = "retVal")
public void afterReturning(JoinPoint joinPoint, Object retVal) {
    System.out.println("获取结果："+retVal);
}
</code></pre>
<h4 style="" id=""></h4>
<h4 style="" id="-1"></h4>
<h4 style="" id="%E5%BC%82%E5%B8%B8%E9%80%9A%E7%9F%A5"><strong>异常通知</strong></h4>
<p style="line-height: 26px">该通知在方法抛出异常之后执行，只需在公共方法上加@AfterThrowing注解，就能定义异常通知：</p>
<pre><code>@AfterThrowing(pointcut = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
    System.out.println("异常："+e);
}
</code></pre>
<h4 style="" id="-2"></h4>
<p style="line-height: 26px">spring aop给这五种通知，分别分配了一个xxxAdvice类。在ReflectiveAspectJAdvisorFactory类的getAdvice方法中可以看得到：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-oscq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="line-height: 26px">下面用一张图总结一下对应关系：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zxba.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 26px">这五种xxxAdvice类都实现了Advice接口，但是有些差异。</p>
<p style="line-height: 26px">下面三个xxxAdvice类实现了<code>MethodInterceptor</code>接口：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tnkd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 26px">而另外两个类：<code>AspectJMethodBeforeAdvice</code> 和 <code>AspectJAfterReturningAdvice</code> 没有实现上面的接口，这是为什么？</p>
<p style="line-height: 26px">（这里留点悬念，后面的文章会揭晓谜题，敬请期待。）</p>
<p style="line-height: 26px">一个猝不及防，依然是那个刺头青D，放下碗冲过来问了句：这五种通知的执行顺序是怎么样的？</p>
<h4 style="" id="%E5%8D%95%E4%B8%AA%E5%88%87%E9%9D%A2%E6%AD%A3%E5%B8%B8%E6%83%85%E5%86%B5"><strong>单个切面正常情况</strong></h4>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vuof.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h4 style="" id="%E5%8D%95%E4%B8%AA%E5%88%87%E9%9D%A2%E5%BC%82%E5%B8%B8%E6%83%85%E5%86%B5"><strong>单个切面异常情况</strong></h4>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tvkg-epxn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h4 style="" id="%E5%A4%9A%E4%B8%AA%E5%88%87%E9%9D%A2%E6%AD%A3%E5%B8%B8%E6%83%85%E5%86%B5"><strong>多个切面正常情况</strong></h4>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mwkp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h4 style="" id="%E5%A4%9A%E4%B8%AA%E5%88%87%E9%9D%A2%E5%BC%82%E5%B8%B8%E6%83%85%E5%86%B5"><strong>多个切面异常情况</strong></h4>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fpfh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<blockquote>
 <p style=""><strong><span style="font-size: 34px; color: rgba(64, 184, 250, 0.5)">❝</span></strong></p>
 <p style="line-height: 26px">当有多有切面时，按照可以通过@Order(n)指定执行顺序，n值越小越先执行。</p>
 <p style=""><span fontsize="" color="rgba(64, 184, 250, 0.5)" style="color: rgba(64, 184, 250, 0.5)">❞</span></p>
</blockquote>
<h3 style="text-align: center; " id="%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8%E9%93%BE%E5%BC%8F%E8%B0%83%E7%94%A8%EF%BC%9F"><strong>为什么使用链式调用？</strong></h3>
<p style="line-height: 26px">这个问题没人问，是我自己想聊聊（旁白：因为我长得帅，有点自恋了）。</p>
<p style="line-height: 26px">先看看spring是如何使用链式调用的，在ReflectiveMethodInvocation的proceed方法中，有这样一段代码：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vyem.png&amp;size=m" width="100%" height="100%" style="display: inline-block">下面用一张图捋一捋上面的逻辑：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xsmk.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">图中包含了一个递归的链式调用，为什么要这样设计呢？</p>
<p style=""></p>
<p style="line-height: 26px">假如不这样设计，我们代码中是不是需要写很多if...else，根据不同的切面和通知单独处理？</p>
<p style="line-height: 26px">而spring巧妙的使用责任链模式消除了原本需要大量的if...else判断，让代码的扩展性更好，很好的体现了开闭原则：对扩展开放，对修改关闭。</p>
<h3 style="text-align: center; " id="%E7%BC%93%E5%AD%98%E4%B8%AD%E5%AD%98%E7%9A%84%E5%8E%9F%E5%A7%8B%E8%BF%98%E6%98%AF%E4%BB%A3%E7%90%86%E5%AF%B9%E8%B1%A1%EF%BC%9F"><strong>缓存中存的原始还是代理对象？</strong></h3>
<p style="line-height: 26px">我们知道spring中为了性能考虑是有缓存的，通常说包含了三级缓存：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qucf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="line-height: 26px">说时迟那时快，刺头青D的兄弟，刺头青F忍不住赶过来问了句：缓存中存的原始还是代理对象？</p>
<p style="line-height: 26px">我竟然被问得一时语塞，仔细捋了捋，要从三个方面回答：</p>
<h4 style="" id="singletonfactories%EF%BC%88%E4%B8%89%E7%BA%A7%E7%BC%93%E5%AD%98%EF%BC%89"><strong>singletonFactories（三级缓存）</strong></h4>
<p style="line-height: 26px">AbstractAutowireCapableBeanFactory类的doCreateBean方法中，有这样一段代码：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-azzz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">其实之前已经说过，它是为了解决循环依赖问题。这次要说的是addSingletonFactory方法：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yken.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">它里面保存的是singletonFactory对象，所以是原始对象。</p>
<p style=""></p>
<h4 style="" id="earlysingletonobjects%EF%BC%88%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98%EF%BC%89"><strong>earlySingletonObjects（二级缓存）</strong></h4>
<p style="line-height: 26px">AbstractBeanFactory类的doGetBean方法中，有这样一段代码：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gjyp.png&amp;size=m" width="100%" height="100%" style="display: inline-block">在调用getBean方法获取bean实例时，会调用getSingleton尝试先从缓存中看能否获取到，如果能获取到则直接返回。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xsao.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这段代码会先从一级缓存中获取bean，如果没有再从二级缓存中获取，如果还是没有则从三级缓存中获取singletonFactory，通过getObject方法获取实例，将该实例放入到二级缓存中。</p>
<p style="line-height: 26px">答案的谜底就聚焦在getObject方法中，而这个方法又是在哪来定义的呢？</p>
<p style="line-height: 26px">其实就是上面的getEarlyBeanReference方法，我们知道这个方法生成的是代理对象，所以二级缓存中存的是代理对象。</p>
<h4 style="" id="singletonobjects%EF%BC%88%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98%EF%BC%89"><strong>singletonObjects（一级缓存）</strong></h4>
<p style="line-height: 26px">DefaultSingletonBeanRegistry类的getSingleton方法中，有这样一段代码：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sfak.png&amp;size=m" width="100%" height="100%" style="display: inline-block">此时的bean创建、注入和初始化完成了，判断是如果新的单例对象，则会加入到一级缓存中，具体代码如下：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tsim.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="line-height: 26px">出于一块钱的友谊，有必要温馨提醒一下：这里是DefaultSingletonBeanRegistry类的getSingleton方法，跟上面说的AbstractBeanFactory类getSingleton方法不一样。</p>
<h3 style="text-align: center; " id="%E5%87%A0%E4%B8%AA%E5%B8%B8%E8%A7%81%E7%9A%84%E5%9D%91"><strong>几个常见的坑</strong></h3>
<p style="line-height: 26px">我是一个乐于分享的人，虽说有时话比较少（旁边：属于人狠话不多的角色，别惹我）。为了表现我的share精神，给大家总结几个我之前使用spring aop遇过的坑。</p>
<p style="line-height: 26px">我们几乎每天都在用spring aop。</p>
<p style="line-height: 26px">“什么？我怎么不知道？” 你可能会问。</p>
<p style="line-height: 26px">如果你每天在用spring事务的话，就是每天在用spring aop，因为spring事务的底层就用到了spring aop。</p>
<h4 style="" id="%E5%9D%911%EF%BC%9A%E7%9B%B4%E6%8E%A5%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8"><strong>坑1：直接方法调用</strong></h4>
<p style="line-height: 26px">使用spring事务时，直接方法调用：</p>
<pre><code>@Service
public  class UserService {

    @Autowired
    private UserMapper userMapper;

    public void add(UserModel userModel) {
        userMapper.queryUser(userModel);
        save(userModel);
    }

    @Transactional
    public void save(UserModel userModel) {
        System.out.println("保存数据");
    }
}
</code></pre>
<p style="line-height: 26px">这种情况直接方法调用spring aop无法生成代理对象，事务会失效。这个问题的解决办法有很多：</p>
<ol>
 <li>
  <p style="">使用TransactionTemplate手动开启事务</p>
 </li>
 <li>
  <p style="">将事务方法save放到新加的类UserSaveService中，通过userSaveService.save调用事务方法。</p>
 </li>
 <li>
  <p style="">UserService类中@Autowired注入自己的实例userService，通过userService.save调用事务方法。</p>
 </li>
 <li>
  <p style="">通过AopContext类获取代理对象：((UserService)AopContext.currentProxy()).save(user);</p>
 </li>
</ol>
<h4 style="" id="%E5%9D%912%EF%BC%9A%E8%AE%BF%E9%97%AE%E6%9D%83%E9%99%90%E9%94%99%E8%AF%AF"><strong>坑2：访问权限错误</strong></h4>
<pre><code>@Service
public  class UserService {
    @Autowired
    private UserService userService;
    @Autowired
    private UserMapper userMapper;

    public void add(UserModel userModel) {
        userMapper.queryUser(userModel);
        userService.save(userModel);
    }

    @Transactional
    private void save(UserModel userModel) {
        System.out.println("保存数据");
    }
}
</code></pre>
<p style="line-height: 26px">上面用 UserService类中@Autowired注入自己的实例userService的方式解决事务失效问题，如果不出意外的话，是可以的。</p>
<p style="line-height: 26px">但是恰恰出现了意外，save方法被定义成了private的，这时也无法生成代理对象，事务同样会失效。</p>
<p style="line-height: 26px">所以，我们应该拿个小本本记一下，目标方法一定不能定义成private的。</p>
<h4 style="" id="%E5%9D%913%EF%BC%9A%E7%9B%AE%E6%A0%87%E7%B1%BB%E7%94%A8final%E4%BF%AE%E9%A5%B0"><strong>坑3：目标类用final修饰</strong></h4>
<pre><code>@Service
public  class UserService {
    @Autowired
    private UserService userService;
    @Autowired
    private UserMapper userMapper;

    public void add(UserModel userModel) {
        userMapper.queryUser(userModel);
        userService.save(userModel);
    }

    @Transactional
    public final void save(UserModel userModel) {
        System.out.println("保存数据");
    }
}
</code></pre>
<p style="line-height: 26px">这种情况spring aop生成代理对象，重写save方法时，发现的final的，重写不了，也会导致事务失效。</p>
<p style="line-height: 26px">小本本需要再加一条，目标方法一定不能定义成final的。</p>
<h4 style="" id="%E5%9D%914%EF%BC%9A%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E9%97%AE%E9%A2%98"><strong>坑4：循环依赖问题</strong></h4>
<p style="line-height: 26px">在使用@Async注解开启异步功能的场景，它会通过AOP自动生成代理对象。</p>
<pre><code>@Service
public  class TestService1 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}

@Service
public  class TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}
</code></pre>
<p style="line-height: 26px">启动服务会报错：</p>
<pre><code>org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
</code></pre>
<p style="line-height: 26px">至于为什么会报错，我在这里不过多解释了，在我的《<a href="http://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247485600&amp;idx=1&amp;sn=0c49b94e7fbd35c88c4470e936023e3e&amp;chksm=f9800e7acef7876ca05ab45ce9420ea140f188e84153f23d0af9d044f475458ad38d49a6546a&amp;scene=21#wechat_redirect" target="_blank">spring：我是如何解决循环依赖的？</a>》这篇文章中写的很详细。</p>
<p style="line-height: 26px">原文链接：https://mp.weixin.qq.com/s/VW9pJ8NWKouKFzfbQO-LIw</p>]]></description><guid isPermaLink="false">/archives/spring-aopliao-dian-bu-yi-yang-de-dong-xi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 05:45:01 GMT</pubDate></item><item><title><![CDATA[《轻松读懂spring》之 IOC的主干流程（上）]]></title><link>https://wiki.dgfygk.com/archives/qing-song-du-dong-spring-zhi-iocde-zhu-gan-liu-cheng-shang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%8A%E8%BD%BB%E6%9D%BE%E8%AF%BB%E6%87%82spring%E3%80%8B%E4%B9%8B%20IOC%E7%9A%84%E4%B8%BB%E5%B9%B2%E6%B5%81%E7%A8%8B%EF%BC%88%E4%B8%8A%EF%BC%89&amp;url=/archives/qing-song-du-dong-spring-zhi-iocde-zhu-gan-liu-cheng-shang" width="1" height="1" alt="" style="opacity:0;">
<h2 style="text-align: center; " id="%E5%89%8D%E8%A8%80"><strong>前言</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px">最近写的几篇spring系列的文章，收到了很多读者的好评，有些读者希望我再多写几篇这方面的文章。甚至还有读者私信给我，向我请教看spring源码的方法，为此我打算写一个spring源码解读的系列，回馈给一直支持我的粉丝们。</p>
<p style="margin-left: 4px!important;; line-height: 26px">不知道你有没有这些经历：</p>
<ul>
 <li>
  <p style=""><strong>想看spring的源码无从下手</strong></p>
 </li>
 <li>
  <p style=""><strong>spring源码太多，看着看着就跟丢了</strong></p>
 </li>
 <li>
  <p style=""><strong>不知道哪些是主要的，哪些是次要的</strong></p>
 </li>
 <li>
  <p style=""><strong>前几天还记得，今天就忘了</strong></p>
 </li>
</ul>
<p style="margin-left: 4px!important;; line-height: 26px"><code>spring</code>源码很复杂，说实话这类文章不好写，想把它讲清楚很难，写着写着篇幅会很长，读者不一定有耐心看下去，而且看完容易忘记。</p>
<p style="margin-left: 4px!important;; line-height: 26px">我打算用图文相结合的方式，去除糟粕，只解读一些精华部分，给读者们在阅读源码时一个清晰的思路，不至于迷路。另外最关键的是，看完之后可以记住很多关键流程。</p>
<p style="margin-left: 4px!important;; line-height: 26px">在<code>spring</code>的庞大体系中，<code>IOC</code>（控制反转）贯穿始终，其作用不言而喻。我们就先从<code>IOC</code>开始，介绍它的主干流程，给有需要的朋友一些指引。</p>
<h2 style="text-align: center; " id="%E5%85%A5%E5%8F%A3"><strong>入口</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px">spring容器的顶层接口是：<code>BeanFactory</code>，但我们使用更多的是它的子接口：<code>ApplicationContext</code>。</p>
<p style="margin-left: 4px!important;; line-height: 26px">通常情况下，如果我们想要手动初始化通过<code>xml文件</code>配置的spring容器时，代码是这样的：</p>
<pre><code>ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");User user = (User)applicationContext.getBean("name");</code></pre>
<p style="margin-left: 4px!important;; line-height: 26px">如果想要手动初始化通过<code>配置类</code>配置的spring容器时，代码是这样的：</p>
<pre><code>AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);User user = (User)applicationContext.getBean("name");</code></pre>
<p style="margin-left: 4px!important;; line-height: 26px">这两个类应该是最常见的入口了，<span fontsize="" color="">它们却殊途同归，最终都会调用</span><code>refresh</code><span fontsize="" color="">方法，该方法才是spring容器初始化的真正入口。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-glgp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ajml.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">顺便提一下，其实调用<code>refresh</code>方法的类并非只有这两个，我们用一张图整体认识一下：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tycf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">虽说调用<code>refresh</code>方法的类有这么多，但我决定用<code>ClassPathXmlApplicationContext</code>类作为列子给大家讲解，因为它足够经典，而且难度相对来说要小一些。</p>
<p style="margin-left: 4px!important;; line-height: 26px">再次重申一下，由于spring源码代码量巨大，即使我能一次性讲完，恐怕你也没那么多耐心看下去。所以我会采用你好，我也好的方式，忽略一些细枝末节，只抓重点。如果有对某些细节比较感兴趣的同学，欢迎加我微信和我一起交流，或者关注我后续的文章，将会做详细的讲解。</p>
<h2 style="text-align: center; " id="refresh%E6%96%B9%E6%B3%95"><strong>refresh方法</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px"><code>refresh</code>方法是<code>spring ioc</code>的真正入口，它负责初始化spring容器。</p>
<p style="margin-left: 4px!important;; line-height: 26px">既然这个方法的作用是初始化spring容器，那方法名为啥不叫<code>init</code>？</p>
<p style="margin-left: 4px!important;; line-height: 26px">答案很简单，因为它不只被调用一次。</p>
<p style="margin-left: 4px!important;; line-height: 26px">在<code>springboot</code>的<code>SpringAppication</code>类中的<code>run</code>方法会调用<code>refreshContext</code>方法，该方法会调用一次<code>refresh</code>方法。</p>
<p style="margin-left: 4px!important;; line-height: 26px">在<code>springcloud</code>的<code>BootstrapApplicationListener</code>类中的<code>onApplicationEvent</code>方法会调用<code>SpringAppication</code>类中的<code>run</code>方法。也会调用一次<code>refresh</code>方法。</p>
<blockquote>
 <p style="line-height: 26px">这是springboot项目中如果引入了springcloud，则refresh方法会被调用两次的原因。</p>
</blockquote>
<p style="margin-left: 4px!important;; line-height: 26px">在<code>springmvc</code>的<code>FrameworkServlet</code>类中的<code>initWebApplicationContext</code>方法会调用<code>configureAndRefreshWebApplicationContext</code>方法，该方法会调用一次<code>refresh</code>方法，不过会提前判断容器是否激活。</p>
<p style="margin-left: 4px!important;; line-height: 26px">所以这里的<code>refresh</code>表示重新构建的意思。</p>
<p style="margin-left: 4px!important;; line-height: 26px">好了，废话不多说。下面重点看看<code>refresh</code>的关键步骤：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-osdd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">其实上图中一眼看过去好像有很多方法，但是真正的核心的方法不多，我主要讲其中最重要的：</p>
<ul>
 <li>
  <p style=""><strong>obtainFreshBeanFactory</strong></p>
 </li>
 <li>
  <p style=""><strong>invokeBeanFactoryPostProcessors</strong></p>
 </li>
 <li>
  <p style=""><strong>registerBeanPostProcessors</strong></p>
 </li>
 <li>
  <p style=""><strong>finishBeanFactoryInitialization</strong></p>
 </li>
</ul>
<h2 style="text-align: center; " id="%E8%A7%A3%E6%9E%90xml%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6"><strong>解析xml配置文件</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px"><code>obtainFreshBeanFactory</code>方法会解析xml的bean配置，生成<code>BeanDefinition</code>对象，并且注册到spring容器中（说白了就是很多map集合中）。</p>
<p style="margin-left: 4px!important;; line-height: 26px">经过几层调用（细节不说，很简单），会调到<code>AbstractBeanDefinitionReader</code>类的<code>loadBeanDefinitions</code>方法：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-deww.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">该方法会循环<code>locations</code>（applicationContext.xml文件路径）,调用另外一个<code>loadBeanDefinitions</code>方法，一个文件一个文件解析。</p>
<p style="margin-left: 4px!important;; line-height: 26px">经过一些列的骚操作，会将<span style="font-size: 14px; color: rgb(150, 84, 181)">location</span>转换成<span style="font-size: 14px; color: rgb(150, 84, 181)">inputSource</span>和<span style="font-size: 14px; color: rgb(150, 84, 181)">resource</span>，然后再转换成<span style="font-size: 14px; color: rgb(150, 84, 181)">Document</span>对象，方面解析。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ydmx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">在解析xml文件时，需要判断是默认标签，还是自定义标签，处理逻辑不一样：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ceol.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">spring的默认标签只有4种：</p>
<ul>
 <li>
  <p style=""><code>&lt;import/&gt;</code></p>
 </li>
 <li>
  <p style=""><code>&lt;alias/&gt;</code></p>
 </li>
 <li>
  <p style=""><code>&lt;bean/&gt;</code></p>
 </li>
 <li>
  <p style=""><code>&lt;beans/&gt;</code></p>
 </li>
</ul>
<p style="margin-left: 4px!important;; line-height: 26px">对应的处理方法是：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-thuw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">注意常见的：<code>&lt;aop/&gt;</code>、<code>&lt;context/&gt;</code>、<code>&lt;mvc/&gt;</code>等都是自定义标签。</p>
<p style="margin-left: 4px!important;; line-height: 26px">从上图中处理<code>&lt;bean/&gt;</code>标签的<code>processBeanDefinition</code>方法开始，经过一系列调用，最终会调到<code>DefaultBeanDefinitionDocumentReader</code>类的<code>processBeanDefinition</code>方法。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-meai.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">这个方法包含了关键步骤：解析元素生成BeanDefinition 和 注册BeanDefinition。</p>
<p style=""></p>
<p style="margin-left: 4px!important;; line-height: 26px">自定义属性的内容有趣，但是不这里不会讲，现在用得不多，有兴趣的同学加我微信和我私聊。</p>
<h2 style="text-align: center; " id="%E7%94%9F%E6%88%90beandefinition"><strong>生成BeanDefinition</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px">下面重点看看BeanDefinition是如何生成的。</p>
<p style="margin-left: 4px!important;; line-height: 26px">上面的方法会调用<code>BeanDefinitionParserDelegate</code>类的<code>parseBeanDefinitionElement</code>方法：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ebwa.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">一个<code>&lt;bean/&gt;</code>标签会对应一个<code>BeanDefinition</code>对象。</p>
<p style="margin-left: 4px!important;; line-height: 26px">该方法又会调用同名的重载方法：<code>processBeanDefinition</code>，真正创建<code>BeanDefinition</code>对象，并且解析一系列参数填充到对象中：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-okeb.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">其实真正创建<span style="font-size: 14px; color: rgb(150, 84, 181)">BeanDefinition</span>的逻辑是非常简单的，直接<span style="font-size: 14px; color: rgb(150, 84, 181)">new</span>了一个对象：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nkpn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">真正复杂的地方是在前面的各种属性的解析和赋值上。</p>
<h2 style="text-align: center; " id="%E6%B3%A8%E5%86%8Cbeandefinition"><strong>注册BeanDefinition</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px">上面通过解析xml文件生成了很多<code>BeanDefinition</code>对象，下面就需要把<code>BeanDefinition</code>对象注册到spring容器中，这样spring容器才能初始化bean。</p>
<p style="margin-left: 4px!important;; line-height: 26px">在<code>BeanDefinitionReaderUtils</code>类的<code>registerBeanDefinition</code>方法很简单，只有两个流程：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-krvl.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">先看看<code>DefaultListableBeanFactory</code>类的<code>registerBeanDefinition</code>方法是如何注册<code>beanName</code>的：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cnol.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">接下来看看<code>SimpleAliasRegistry</code>类的<code>registerAlias</code>方法是如何注册<code>alias</code>别名的：</p>
<p style="margin-left: 4px!important;; line-height: 26px"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vwmf.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这样就能通过多个不同的<code>alias</code>找到同一个<code>name</code>，再通过<code>name</code>就能找到<code>BeanDefinition</code>。</p>
<h2 style="text-align: center; " id="%E4%BF%AE%E6%94%B9beandefinition"><strong>修改BeanDefinition</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px">上面<code>BeanDefinition</code>对象已经注册到spring容器当中了，接下来，如果想要修改已经注册的<code>BeanDefinition</code><span style="font-size: 16px; color: rgb(0, 0, 0)">对象</span>该怎么办呢？</p>
<p style="margin-left: 4px!important;; line-height: 26px"><code>refresh</code>方法中通过<code>invokeBeanFactoryPostProcessors</code>方法修改<code>BeanDefinition</code>对象。</p>
<p style="margin-left: 4px!important;; line-height: 26px">经过一系列的调用，最终会到<code>PostProcessorRegistrationDelegate</code>类的<code>invokeBeanFactoryPostProcessors</code>方法：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vduv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">流程看起来很长，其实逻辑比较简单，主要是在处理<code>BeanDefinitionRegistryPostProcessor</code>和<code>BeanFactoryPostProcessor</code>。</p>
<p style="margin-left: 4px!important;; line-height: 26px">而<code>BeanDefinitionRegistryPostProcessor</code>本身是一种特殊的<code>BeanFactoryPostProcessor</code>，它也会执行<code>BeanFactoryPostProcessor</code>的逻辑，只是加了一个额外的方法。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aruc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px"><code>ConfigurationClassPostProcessor</code>可能是最重要的<code>BeanDefinitionRegistryPostProcessor</code>，它负责处理<code>@Configuration</code>注解。</p>
<h2 style="text-align: center; " id="%E6%B3%A8%E5%86%8Cbeanpostprocessor"><strong>注册BeanPostProcessor</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px">处理完前面的逻辑，<code>refresh</code>方法接着会调用<code>registerBeanPostProcessors</code>注册<code>BeanPostProcessor</code>，它的功能非常强大，后面的文章会详细讲解。</p>
<p style="margin-left: 4px!important;; line-height: 26px">经过一系列的调用，最终会到<code>PostProcessorRegistrationDelegate</code>类的<code>registerBeanPostProcessors</code>方法：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-heyz.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="margin-left: 4px!important;; line-height: 26px">注意，这一步只是注册<code>BeanPostProcessor</code>，真正的使用在后面。</p>
<h2 style="text-align: center; " id="%E6%80%BB%E7%BB%93"><strong>总结</strong></h2>
<p style="margin-left: 4px!important;; line-height: 26px">前面主要介绍了：</p>
<ol>
 <li>
  <p style=""><strong>spring容器初始化的入口</strong></p>
 </li>
 <li>
  <p style=""><strong>refresh方法的主要流程</strong></p>
 </li>
 <li>
  <p style=""><strong>解析xml配置文件</strong></p>
 </li>
 <li>
  <p style=""><strong>生成BeanDefinition</strong></p>
 </li>
 <li>
  <p style=""><strong>注册BeanDefinition</strong></p>
 </li>
 <li>
  <p style=""><strong>修改BeanDefinition</strong></p>
 </li>
 <li>
  <p style=""><strong>注册BeanPostProcessor</strong></p>
 </li>
</ol>
<p style="">原文链接：https://mp.weixin.qq.com/s/4pYQ_EnimZwEZkt1FPf8pQ</p>]]></description><guid isPermaLink="false">/archives/qing-song-du-dong-spring-zhi-iocde-zhu-gan-liu-cheng-shang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 03:05:28 GMT</pubDate></item><item><title><![CDATA[9条消除if...else的锦囊妙计，助你写出更优雅的代码]]></title><link>https://wiki.dgfygk.com/archives/9tiao-xiao-chu-if...elsede-jin-nang-miao-ji-zhu-ni-xie-chu-geng-you-ya-de-dai-ma</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=9%E6%9D%A1%E6%B6%88%E9%99%A4if...else%E7%9A%84%E9%94%A6%E5%9B%8A%E5%A6%99%E8%AE%A1%EF%BC%8C%E5%8A%A9%E4%BD%A0%E5%86%99%E5%87%BA%E6%9B%B4%E4%BC%98%E9%9B%85%E7%9A%84%E4%BB%A3%E7%A0%81&amp;url=/archives/9tiao-xiao-chu-if...elsede-jin-nang-miao-ji-zhu-ni-xie-chu-geng-you-ya-de-dai-ma" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%89%8D%E8%A8%80"><strong><span style="font-size: 20px; color: rgb(64, 184, 250)">前言</span></strong></h2>
<p style="line-height: 26px">最近在做代码重构，发现了很多代码的烂味道。其他的不多说，今天主要说说那些又臭又长的if...else要如何重构。</p>
<p style="line-height: 26px">在介绍更更优雅的编程之前，让我们一起回顾一下，不好的if...else代码</p>
<h2 style="" id="%E4%B8%80%E3%80%81%E5%8F%88%E8%87%AD%E5%8F%88%E9%95%BF%E7%9A%84if...else"><strong><span style="font-size: 20px; color: rgb(64, 184, 250)">一、又臭又长的if...else</span></strong></h2>
<p style="line-height: 26px">废话不多说，先看看下面的代码。</p>
<pre><code>public interface IPay {  
    void pay();  
}  

@Service  
public class AliaPay implements IPay {  
     @Override  
     public void pay() {  
        System.out.println("===发起支付宝支付===");  
     }  
}  

@Service  
public class WeixinPay implements IPay {  
     @Override  
     public void pay() {  
         System.out.println("===发起微信支付===");  
     }  
}  
  
@Service  
public class JingDongPay implements IPay {  
     @Override  
     public void pay() {  
        System.out.println("===发起京东支付===");  
     }  
}  

@Service  
public class PayService {  
     @Autowired  
     private AliaPay aliaPay;  
     @Autowired  
     private WeixinPay weixinPay;  
     @Autowired  
     private JingDongPay jingDongPay;  
    
   
     public void toPay(String code) {  
         if ("alia".equals(code)) {  
             aliaPay.pay();  
         } else if ("weixin".equals(code)) {  
              weixinPay.pay();  
         } else if ("jingdong".equals(code)) {  
              jingDongPay.pay();  
         } else {  
              System.out.println("找不到支付方式");  
         }  
     }  
}
</code></pre>
<p style="line-height: 26px">PayService类的toPay方法主要是为了发起支付，根据不同的code，决定调用用不同的支付类（比如：aliaPay）的pay方法进行支付。</p>
<p style="line-height: 26px">这段代码有什么问题呢？也许有些人就是这么干的。</p>
<p style="line-height: 26px">试想一下，如果支付方式越来越多，比如：又加了百度支付、美团支付、银联支付等等，就需要改toPay方法的代码，增加新的else...if判断，判断多了就会导致逻辑越来越多？</p>
<p style="line-height: 26px">很明显，这里违法了设计模式六大原则的：<code>开闭原则</code> 和 <code>单一职责原则</code>。</p>
<blockquote>
 <p style=""><strong><span style="font-size: 34px; color: rgba(64, 184, 250, 0.5)">❝</span></strong></p>
 <p style="line-height: 26px">开闭原则：对扩展开放，对修改关闭。就是说增加新功能要尽量少改动已有代码。</p>
 <p style=""><span fontsize="" color="rgba(64, 184, 250, 0.5)" style="color: rgba(64, 184, 250, 0.5)">❞</span></p>
</blockquote>
<blockquote>
 <p style=""><strong><span style="font-size: 34px; color: rgba(64, 184, 250, 0.5)">❝</span></strong></p>
 <p style="line-height: 26px">单一职责原则：顾名思义，要求逻辑尽量单一，不要太复杂，便于复用。</p>
 <p style=""><span fontsize="" color="rgba(64, 184, 250, 0.5)" style="color: rgba(64, 184, 250, 0.5)">❞</span></p>
</blockquote>
<p style="line-height: 26px">那有什么办法可以解决这个问题呢？</p>
<h2 style="" id="%E4%BA%8C%E3%80%81%E6%B6%88%E9%99%A4if...else%E7%9A%84%E9%94%A6%E5%9B%8A%E5%A6%99%E8%AE%A1"><strong><span style="font-size: 20px; color: rgb(64, 184, 250)">二、消除if...else的锦囊妙计</span></strong></h2>
<h3 style="text-align: center; " id="1%E3%80%81%E4%BD%BF%E7%94%A8%E6%B3%A8%E8%A7%A3"><strong>1、使用注解</strong></h3>
<p style="line-height: 26px">代码中之所以要用code判断使用哪个支付类，是因为code和支付类没有一个绑定关系，如果绑定关系存在了，就可以不用判断了。</p>
<p style="line-height: 26px">我们先定义一个注解。</p>
<pre><code>@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface PayCode {  

     String value();    
     String name();  
}
</code></pre>
<p style="line-height: 26px">在所有的支付类上都加上该注解</p>
<pre><code>@PayCode(value = "alia", name = "支付宝支付")  
@Service  
public class AliaPay implements IPay {  

     @Override  
     public void pay() {  
         System.out.println("===发起支付宝支付===");  
     }  
}  

 
@PayCode(value = "weixin", name = "微信支付")  
@Service  
public class WeixinPay implements IPay {  
 
     @Override  
     public void pay() {  
         System.out.println("===发起微信支付===");  
     }  
} 

 
@PayCode(value = "jingdong", name = "京东支付")  
@Service  
public class JingDongPay implements IPay {  
 
     @Override  
     public void pay() {  
        System.out.println("===发起京东支付===");  
     }  
}
</code></pre>
<p style="line-height: 26px">然后增加最关键的类：</p>
<pre><code>@Service  
public class PayService2 implements ApplicationListener&lt;ContextRefreshedEvent&gt; {  
 
     private static Map&lt;String, IPay&gt; payMap = null;  
     
     @Override  
     public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {  
         ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();  
         Map&lt;String, Object&gt; beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);  
        
         if (beansWithAnnotation != null) {  
             payMap = new HashMap&lt;&gt;();  
             beansWithAnnotation.forEach((key, value) -&gt;{  
                 String bizType = value.getClass().getAnnotation(PayCode.class).value();  
                 payMap.put(bizType, (IPay) value);  
             });  
         }  
     }  
    
     public void pay(String code) {  
        payMap.get(code).pay();  
     }  
}
</code></pre>
<p style="line-height: 26px">PayService2类实现了<code>ApplicationListener</code>接口，这样在<code>onApplicationEvent</code>方法中，就可以拿到<code>ApplicationContext</code>的实例。我们再获取打了PayCode注解的类，放到一个map中，map中的key就是PayCode注解中定义的value，跟code参数一致，value是支付类的实例。</p>
<p style="line-height: 26px">这样，每次就可以每次直接通过code获取支付类实例，而不用if...else判断了。如果要加新的支付方法，只需在支付类上面打上PayCode注解定义一个新的code即可。</p>
<p style="line-height: 26px">注意：这种方式的code可以没有业务含义，可以是纯数字，只有不重复就行。</p>
<h3 style="text-align: center; " id="2%E3%80%81%E5%8A%A8%E6%80%81%E6%8B%BC%E6%8E%A5%E5%90%8D%E7%A7%B0"><strong>2、动态拼接名称</strong></h3>
<p style="line-height: 26px">该方法主要针对code是有业务含义的场景。</p>
<pre><code>@Service  
public class PayService3 implements ApplicationContextAware {   
     private ApplicationContext applicationContext;  
     private static final String SUFFIX = "Pay";  

     @Override  
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
        this.applicationContext = applicationContext;  
     }  

     public void toPay(String payCode) {  
         ((IPay) applicationContext.getBean(getBeanName(payCode))).pay();  
     }  

     public String getBeanName(String payCode) {  
         return payCode + SUFFIX;  
     }  
}
</code></pre>
<p style="line-height: 26px">我们可以看到，支付类bean的名称是由code和后缀拼接而成，比如：aliaPay、weixinPay和jingDongPay。这就要求支付类取名的时候要特别注意，前面的一段要和code保持一致。调用的支付类的实例是直接从<code>ApplicationContext</code>实例中获取的，默认情况下<code>bean</code>是单例的，放在内存的一个<code>map</code>中，所以不会有性能问题。</p>
<p style="line-height: 26px">特别说明一下，这种方法实现了<code>ApplicationContextAware</code>接口跟上面的<code>ApplicationListener</code>接口不一样，是想告诉大家获取<code>ApplicationContext</code>实例的方法不只一种。</p>
<h3 style="text-align: center; " id="3%E3%80%81%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E5%88%A4%E6%96%AD"><strong>3、模板方法判断</strong></h3>
<p style="line-height: 26px">当然除了上面介绍的两种方法之外，spring的源码实现中也告诉我们另外一种思路，解决if...else问题。</p>
<p style="line-height: 26px">我们先一起看看<code>spring AOP</code>的部分源码，看一下<code>DefaultAdvisorAdapterRegistry</code>的<code>wrap</code>方法</p>
<pre><code> public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {  
     if (adviceObject instanceof Advisor) {  
        return (Advisor) adviceObject;  
     }  
     if (!(adviceObject instanceof Advice)) {  
        throw new UnknownAdviceTypeException(adviceObject);  
     }  
     Advice advice = (Advice) adviceObject;  
     if (advice instanceof MethodInterceptor) {    
        return new DefaultPointcutAdvisor(advice);  
     }  
     for (AdvisorAdapter adapter : this.adapters) {  
         if (adapter.supportsAdvice(advice)) {  
             return new DefaultPointcutAdvisor(advice);  
         }  
     }  
     throw new UnknownAdviceTypeException(advice);  
 }
</code></pre>
<p style="line-height: 26px">重点看看<code>supportAdvice</code>方法，有三个类实现了这个方法。我们随便抽一个类看看</p>
<pre><code>class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {  
 
     @Override  
     public boolean supportsAdvice(Advice advice) {  
        return (advice instanceof AfterReturningAdvice);  
     }  
 
     @Override  
     public MethodInterceptor getInterceptor(Advisor advisor) {  
        AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();  
        return new AfterReturningAdviceInterceptor(advice);  
     }   
}
</code></pre>
<p style="line-height: 26px">该类的<code>supportsAdvice</code>方法非常简单，只是判断了一下<code>advice</code>的类型是不是<code>AfterReturningAdvice</code>。</p>
<p style="line-height: 26px">我们看到这里应该有所启发。</p>
<p style="line-height: 26px">其实，我们可以这样做，定义一个接口或者抽象类，里面有个<code>support</code>方法判断参数传的code是否自己可以处理，如果可以处理则走支付逻辑。</p>
<pre><code>public interface IPay {  
     boolean support(String code);   
     void pay();  
}  

@Service  
public class AliaPay implements IPay {   
     @Override  
     public boolean support(String code) {  
        return "alia".equals(code);  
     }  
 
     @Override  
     public void pay() {  
        System.out.println("===发起支付宝支付===");  
     }  
}  
 
@Service  
public class WeixinPay implements IPay {  
 
     @Override  
     public boolean support(String code) {  
        return "weixin".equals(code);  
     }  
 
     @Override  
     public void pay() {  
        System.out.println("===发起微信支付===");  
     }  
}  

@Service  
public class JingDongPay implements IPay {  
     @Override  
     public boolean support(String code) {  
        return "jingdong".equals(code);  
     }  
 
     @Override  
     public void pay() {  
        System.out.println("===发起京东支付===");  
     }  
}
</code></pre>
<p style="line-height: 26px">每个支付类都有一个support方法，判断传过来的code是否和自己定义的相等。</p>
<pre><code>@Service  
public class PayService4 implements ApplicationContextAware, InitializingBean {  

     private ApplicationContext applicationContext;  
     private List&lt;IPay&gt; payList = null;  

     @Override  
     public void afterPropertiesSet() throws Exception {  
         if (payList == null) {  
             payList = new ArrayList&lt;&gt;();  
             Map&lt;String, IPay&gt; beansOfType = applicationContext.getBeansOfType(IPay.class);  
 
             beansOfType.forEach((key, value) -&gt; payList.add(value));  
         }  
     }  
 
     @Override  
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
        this.applicationContext = applicationContext;  
     }  
 
     public void toPay(String code) {  
         for (IPay iPay : payList) {  
             if (iPay.support(code)) {  
                iPay.pay();  
             }  
         }  
     }  
}
</code></pre>
<p style="line-height: 26px">这段代码中先把实现了IPay接口的支付类实例初始化到一个list集合中，返回在调用支付接口时循环遍历这个list集合，如果code跟自己定义的一样，则调用当前的支付类实例的pay方法。</p>
<h3 style="text-align: center; " id="4.%E7%AD%96%E7%95%A5%2B%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F"><strong>4.策略+工厂模式</strong></h3>
<p style="line-height: 26px">这种方式也是用于code是有业务含义的场景。</p>
<ul>
 <li>
  <p style=""><code>策略模式</code>定义了一组算法，把它们一个个封装起来, 并且使它们可相互替换。</p>
 </li>
 <li>
  <p style=""><code>工厂模式</code>用于封装和管理对象的创建，是一种创建型模式。</p>
 </li>
</ul>
<pre><code>public interface IPay {
    void pay();
}

@Service
public class AliaPay implements IPay {

    @PostConstruct
    public void init() {
        PayStrategyFactory.register("aliaPay", this);
    }


    @Override
    public void pay() {
        System.out.println("===发起支付宝支付===");
    }

}

@Service
public class WeixinPay implements IPay {

    @PostConstruct
    public void init() {
        PayStrategyFactory.register("weixinPay", this);
    }

    @Override
    public void pay() {
        System.out.println("===发起微信支付===");
    }
}

@Service
public class JingDongPay implements IPay {

    @PostConstruct
    public void init() {
        PayStrategyFactory.register("jingDongPay", this);
    }

    @Override
    public void pay() {
        System.out.println("===发起京东支付===");
    }
}

public class PayStrategyFactory {

    private static Map&lt;String, IPay&gt; PAY_REGISTERS = new HashMap&lt;&gt;();


    public static void register(String code, IPay iPay) {
        if (null != code &amp;&amp; !"".equals(code)) {
            PAY_REGISTERS.put(code, iPay);
        }
    }


    public static IPay get(String code) {
        return PAY_REGISTERS.get(code);
    }
}

@Service
public class PayService3 {

    public void toPay(String code) {
        PayStrategyFactory.get(code).pay();
    }
}
</code></pre>
<p style="line-height: 26px">这段代码的关键是<code>PayStrategyFactory</code>类，它是一个策略工厂，里面定义了一个全局的map，在所有IPay的实现类中注册当前实例到map中，然后在调用的地方通过<code>PayStrategyFactory</code>类根据code从map获取支付类实例即可。</p>
<h3 style="text-align: center; " id="5.%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F"><strong>5.责任链模式</strong></h3>
<p style="line-height: 26px">这种方式在代码重构时用来消除if...else非常有效。</p>
<ul>
 <li>
  <p style="">责任链模式：将请求的处理对象像一条长链一般组合起来，形成一条对象链。请求并不知道具体执行请求的对象是哪一个，这样就实现了请求与处理对象之间的解耦。</p>
 </li>
</ul>
<p style="line-height: 26px">常用的<code>filter</code>、<code>spring aop</code>就是使用了责任链模式，这里我稍微改良了一下，具体代码如下：</p>
<pre><code>public abstract class PayHandler {

    @Getter
    @Setter
    protected PayHandler next;

    public abstract void pay(String pay);

}

@Service
public class AliaPayHandler extends PayHandler {


    @Override
    public void pay(String code) {
        if ("alia".equals(code)) {
            System.out.println("===发起支付宝支付===");
        } else {
            getNext().pay(code);
        }
    }

}

@Service
public class WeixinPayHandler extends PayHandler {

    @Override
    public void pay(String code) {
        if ("weixin".equals(code)) {
            System.out.println("===发起微信支付===");
        } else {
            getNext().pay(code);
        }
    }
}

@Service
public class JingDongPayHandler extends PayHandler {


    @Override
    public void pay(String code) {
        if ("jingdong".equals(code)) {
            System.out.println("===发起京东支付===");
        } else {
            getNext().pay(code);
        }
    }
}

@Service
public class PayHandlerChain implements ApplicationContextAware, InitializingBean {

    private ApplicationContext applicationContext;
    private PayHandler header;


    public void handlePay(String code) {
        header.pay(code);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Map&lt;String, PayHandler&gt; beansOfTypeMap = applicationContext.getBeansOfType(PayHandler.class);
        if (beansOfTypeMap == null || beansOfTypeMap.size() == 0) {
            return;
        }
        List&lt;PayHandler&gt; handlers = beansOfTypeMap.values().stream().collect(Collectors.toList());
        for (int i = 0; i &lt; handlers.size(); i++) {
            PayHandler payHandler = handlers.get(i);
            if (i != handlers.size() - 1) {
                payHandler.setNext(handlers.get(i + 1));
            }
        }
        header = handlers.get(0);
    }
}
</code></pre>
<p style="line-height: 26px">这段代码的关键是每个PayHandler的子类，都定义了下一个需要执行的PayHandler子类，构成一个链式调用，通过PayHandlerChain把这种链式结构组装起来。</p>
<h3 style="text-align: center; " id="6%E3%80%81%E5%85%B6%E4%BB%96%E7%9A%84%E6%B6%88%E9%99%A4if...else%E7%9A%84%E6%96%B9%E6%B3%95"><strong>6、其他的消除if...else的方法</strong></h3>
<p style="line-height: 26px">当然实际项目开发中使用if...else判断的场景非常多，上面只是其中几种场景。下面再列举一下，其他常见的场景。</p>
<h4 style="" id="1.%E6%A0%B9%E6%8D%AE%E4%B8%8D%E5%90%8C%E7%9A%84%E6%95%B0%E5%AD%97%E8%BF%94%E5%9B%9E%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2"><strong>1.根据不同的数字返回不同的字符串</strong></h4>
<pre><code>public String getMessage(int code) {  
     if (code == 1) {  
        return "成功";  
     } else if (code == -1) {  
        return "失败";  
     } else if (code == -2) {  
        return "网络超时";  
     } else if (code == -3) {  
        return "参数错误";  
     }  
     throw new RuntimeException("code错误");  
}
</code></pre>
<p style="line-height: 26px">其实，这种判断没有必要，用一个枚举就可以搞定。</p>
<pre><code>public enum MessageEnum {  
     SUCCESS(1, "成功"),  
     FAIL(-1, "失败"),  
     TIME_OUT(-2, "网络超时"),  
     PARAM_ERROR(-3, "参数错误");  

     private int code;  
     private String message;  

     MessageEnum(int code, String message) {  
         this.code = code;  
         this.message = message;  
     }  
   
     public int getCode() {  
        return this.code;  
     }  

     public String getMessage() {  
        return this.message;  
     }  
  
     public static MessageEnum getMessageEnum(int code) {  
        return Arrays.stream(MessageEnum.values()).filter(x -&gt; x.code == code).findFirst().orElse(null);  
     }  
}
</code></pre>
<p style="line-height: 26px">再把调用方法稍微调整一下</p>
<pre><code>public String getMessage(int code) {  
     MessageEnum messageEnum = MessageEnum.getMessageEnum(code);  
     return messageEnum.getMessage();  
}
</code></pre>
<p style="line-height: 26px">完美。</p>
<h4 style="" id="2.%E9%9B%86%E5%90%88%E4%B8%AD%E7%9A%84%E5%88%A4%E6%96%AD"><strong>2.集合中的判断</strong></h4>
<p style="line-height: 26px">上面的枚举MessageEnum中的getMessageEnum方法，如果不用java8的语法的话，可能要这样写</p>
<pre><code>public static MessageEnum getMessageEnum(int code) {  
     for (MessageEnum messageEnum : MessageEnum.values()) {  
         if (code == messageEnum.code) {  
            return messageEnum;  
         }  
     }  
     return null;  
}
</code></pre>
<p style="line-height: 26px">对于集合中过滤数据，或者查找方法，java8有更简单的方法消除if...else判断。</p>
<pre><code>public static MessageEnum getMessageEnum(int code) {  
     return Arrays.stream(MessageEnum.values()).filter(x -&gt; x.code == code).findFirst().orElse(null);  
}
</code></pre>
<h4 style="" id="3.%E7%AE%80%E5%8D%95%E7%9A%84%E5%88%A4%E6%96%AD"><strong>3.简单的判断</strong></h4>
<p style="line-height: 26px">其实有些简单的if...else完全没有必要写，可以用三目运算符代替，比如这种情况：</p>
<pre><code>public String getMessage2(int code) {  
     if(code == 1) {  
        return  "成功";  
     }  
     return "失败";  
}
</code></pre>
<p style="line-height: 26px">改成三目运算符：</p>
<pre><code>public String getMessage2(int code) {  
    return code == 1 ? "成功" : "失败";  
}
</code></pre>
<p style="line-height: 26px">修改之后代码更简洁一些。</p>
<h4 style="" id="4.spring%E4%B8%AD%E7%9A%84%E5%88%A4%E6%96%AD"><strong>4.spring中的判断</strong></h4>
<p style="line-height: 26px">对于参数的异常，越早被发现越好，在spring中提供了<code>Assert</code>用来帮助我们检测参数是否有效。</p>
<pre><code> public void save(Integer code，String name) {  
     if(code == null) {
       throw Exception("code不能为空");     
     } else {
         if(name == null) {
             throw Exception("name不能为空");     
         } else {
             System.out.println("doSave");
         }
     }
 }

</code></pre>
<p style="line-height: 26px">如果参数非常多的话，if...else语句会很长，这时如果改成使用<code>Assert</code>类判断，代码会简化很多：</p>
<pre><code> public String save2(Integer code，String name) {      
     Assert.notNull(code,"code不能为空"); 
     Assert.notNull(name,"name不能为空"); 
     System.out.println("doSave");
 }

</code></pre>
<p style="line-height: 26px">当然，还有很多其他的场景可以优化if...else，我再这里就不一一介绍了，感兴趣的朋友可以给我留言，一起探讨和研究一下。</p>
<p style="line-height: 26px"></p>
<p style="line-height: 26px">原文链接：https://mp.weixin.qq.com/s/WpdfBydLFQQDKjRbweqOYQ</p>]]></description><guid isPermaLink="false">/archives/9tiao-xiao-chu-if...elsede-jin-nang-miao-ji-zhu-ni-xie-chu-geng-you-ya-de-dai-ma</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 02:56:48 GMT</pubDate></item><item><title><![CDATA[mq的那些破事儿，你不好奇吗？]]></title><link>https://wiki.dgfygk.com/archives/mqde-na-xie-po-shi-er-ni-bu-hao-qi-ma</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=mq%E7%9A%84%E9%82%A3%E4%BA%9B%E7%A0%B4%E4%BA%8B%E5%84%BF%EF%BC%8C%E4%BD%A0%E4%B8%8D%E5%A5%BD%E5%A5%87%E5%90%97%EF%BC%9F&amp;url=/archives/mqde-na-xie-po-shi-er-ni-bu-hao-qi-ma" width="1" height="1" alt="" style="opacity:0;">
<h2 style="text-align: center; " id="%E5%89%8D%E8%A8%80"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">前言</span></strong></h2>
<p style="line-height: 1.75em">最近mq越来越火，很多公司在用，很多人在用，其重要性不言而喻。但是如果我让你回答下面的这些问题：</p>
<ol>
 <li>
  <p style="">我们为什么要用mq？</p>
 </li>
 <li>
  <p style="">引入mq会多哪些问题？</p>
 </li>
 <li>
  <p style="">如何解决这些问题？</p>
 </li>
</ol>
<p style="line-height: 1.75em">你心中是否有答案了呢？本文将会一一为你解答，这些看似平常却很有意义的问题。</p>
<h2 style="text-align: center; " id="1-%E4%BC%A0%E7%BB%9F%E6%A8%A1%E5%BC%8F%E6%9C%89%E5%93%AA%E4%BA%9B%E7%97%9B%E7%82%B9%EF%BC%9F"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">1 传统模式有哪些痛点？</span></strong></h2>
<h3 style="" id="1.1-%E7%97%9B%E7%82%B91"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">1.1 痛点1</span></strong></h3>
<p style="line-height: 1.75em">有些复杂的业务系统，一次用户请求可能会同步调用N个系统的接口，需要等待所有的接口都返回了，才能真正的获取执行结果。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mjzf.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这种同步接口调用的方式<code>总耗时比较长</code>，非常影响用户的体验，特别是在网络不稳定的情况下，极容易出现接口超时问题。</p>
<h3 style="" id="1.2-%E7%97%9B%E7%82%B92"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">1.2 痛点2</span></strong></h3>
<p style="line-height: 1.75em">很多复杂的业务系统，一般都会拆分成多个子系统。我们在这里以用户下单为例，请求会先通过订单系统，然后分别调用：支付系统、库存系统、积分系统 和 物流系统。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pfvk.png&amp;size=m" width="100%" height="100%" style="display: inline-block">系统之间<code>耦合性太高</code>，如果调用的任何一个子系统出现异常，整个请求都会异常，对系统的稳定性非常不利。</p>
<h3 style="" id="1.3-%E7%97%9B%E7%82%B93"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">1.3 痛点3</span></strong></h3>
<p style="line-height: 1.75em">有时候为了吸引用户，我们会搞一些活动，比如秒杀等。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-irtc.png&amp;size=m" width="100%" height="100%" style="display: inline-block">如果用户少还好，不会影响系统的稳定性。但如果用户突增，一时间所有的请求都到数据库，可能会导致数据库无法承受这么大的压力，响应变慢或者直接挂掉。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rlgm.png&amp;size=m" width="100%" height="100%" style="display: inline-block">对于这种突然出现的<code>请求峰值</code>，无法保证系统的稳定性。</p>
<h2 style="text-align: center; " id="2-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8mq%EF%BC%9F"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">2 为什么要用mq？</span></strong></h2>
<p style="line-height: 1.75em">对于上面传统模式的三类问题，我们使用mq就能轻松解决。</p>
<h3 style="" id="2.1-%E5%BC%82%E6%AD%A5"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">2.1 异步</span></strong></h3>
<p style="line-height: 1.75em">对于痛点1：同步接口调用导致响应时间长的问题，使用mq之后，将同步调用改成异步，能够显著减少系统响应时间。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qytf.png&amp;size=m" width="100%" height="100%" style="display: inline-block">系统A作为消息的生产者，在完成本职工作后，就能直接返回结果了。而无需等待消息消费者的返回，它们最终会独立完成所有的业务功能。</p>
<p style="line-height: 1.75em">这样能避免<code>总耗时比较长</code>，从而影响用户的体验的问题。</p>
<h3 style="" id="2.2-%E8%A7%A3%E8%80%A6"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">2.2 解耦</span></strong></h3>
<p style="line-height: 1.75em">对于痛点2：子系统间耦合性太大的问题，使用mq之后，我们只需要依赖于mq，避免了各个子系统间的强依赖问题。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fxvl.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">订单系统作为消息生产者，保证它自己没有异常即可，不会受到支付系统等业务子系统的异常影响，并且各个消费者业务子系统之间，也互不影响。</p>
<p style="line-height: 1.75em">这样就把之前复杂的业务子系统的依赖关系，转换为只依赖于mq的简单依赖，从而显著的降低了系统间的耦合度。</p>
<h3 style="" id="2.3-%E6%B6%88%E5%B3%B0"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">2.3 消峰</span></strong></h3>
<p style="line-height: 1.75em">对于痛点3：由于突然出现的<code>请求峰值</code>，导致系统不稳定的问题。使用mq后，能够起到消峰的作用。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qekc.png&amp;size=m" width="100%" height="100%" style="display: inline-block">订单系统接收到用户请求之后，将请求直接发送到mq，然后订单消费者从mq中消费消息，做写库操作。如果出现<code>请求峰值</code>的情况，由于消费者的消费能力有限，会按照自己的节奏来消费消息，多的请求不处理，保留在mq的队列中，不会对系统的稳定性造成影响。</p>
<h2 style="text-align: center; " id="3-%E5%BC%95%E5%85%A5mq%E4%BC%9A%E5%A4%9A%E5%93%AA%E4%BA%9B%E9%97%AE%E9%A2%98%EF%BC%9F"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">3 引入mq会多哪些问题？</span></strong></h2>
<p style="line-height: 1.75em">引入mq后让我们子系统间耦合性降低了，异步处理机制减少了系统的响应时间，同时能够有效的应对<code>请求峰值</code>问题，提升系统的稳定性。</p>
<p style="line-height: 1.75em">但是，引入mq同时也会带来一些问题。</p>
<h3 style="" id="3.1-%E9%87%8D%E5%A4%8D%E6%B6%88%E6%81%AF%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">3.1 重复消息问题</span></strong></h3>
<p style="line-height: 1.75em">重复消费问题可以说是mq中普遍存在的问题，不管你用哪种mq都无法避免。</p>
<p style="line-height: 1.75em">有哪些场景会出现重复的消息呢？</p>
<ol>
 <li>
  <p style="">消息生产者产生了重复的消息</p>
 </li>
 <li>
  <p style="">kafka和rocketmq的offset被回调了</p>
 </li>
 <li>
  <p style="">消息消费者确认失败</p>
 </li>
 <li>
  <p style="">消息消费者确认时超时了</p>
 </li>
 <li>
  <p style="">业务系统主动发起重试</p>
 </li>
</ol>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yimc.png&amp;size=m" width="100%" height="100%" style="display: inline-block">如果重复消息不做正确的处理，会对业务造成很大的影响，产生重复的数据，或者导致数据异常，比如会员系统多开通了一个月的会员。</p>
<h3 style="" id="3.2-%E6%95%B0%E6%8D%AE%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">3.2 数据一致性问题</span></strong></h3>
<p style="line-height: 1.75em">很多时候，如果mq的消费者业务处理异常的话，就会出现数据一致性问题。比如：一个完整的业务流程是，下单成功之后，送100个积分。下单写库了，但是消息消费者在送积分的时候失败了，就会造成<code>数据不一致</code>的情况，即该业务流程的部分数据写库了，另外一部分没有写库。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cxxb.png&amp;size=m" width="100%" height="100%" style="display: inline-block">如果下单和送积分在同一个事务中，要么同时成功，要么同时失败，是不会出现数据一致性问题的。</p>
<p style="line-height: 1.75em">但由于跨系统调用，为了性能考虑，一般不会使用强一致性的方案，而改成达成最终一致性即可。</p>
<h3 style="" id="3.3-%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">3.3 消息丢失问题</span></strong></h3>
<p style="line-height: 1.75em">同样消息丢失问题，也是mq中普遍存在的问题，不管你用哪种mq都无法避免。</p>
<p style="line-height: 1.75em">有哪些场景会出现消息丢失问题呢？</p>
<ol>
 <li>
  <p style="">消息生产者发生消息时，由于网络原因，发生到mq失败了。</p>
 </li>
 <li>
  <p style="">mq服务器持久化时，磁盘出现异常</p>
 </li>
 <li>
  <p style="">kafka和rocketmq的offset被回调时，略过了很多消息。</p>
 </li>
 <li>
  <p style="">消息消费者刚读取消息，已经ack确认了，但业务还没处理完，服务就被重启了。</p>
 </li>
</ol>
<p style="line-height: 1.75em">导致消息丢失问题的原因挺多的，<code>生产者</code>、<code>mq服务器</code>、<code>消费者</code> 都有可能产生问题，我在这里就不一一列举了。最终的结果会导致消费者无法正确的处理消息，而导致数据不一致的情况。</p>
<h3 style="" id="3.4-%E6%B6%88%E6%81%AF%E9%A1%BA%E5%BA%8F%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">3.4 消息顺序问题</span></strong></h3>
<p style="line-height: 1.75em">有些业务数据是有状态的，比如订单有：下单、支付、完成、退货等状态，如果订单数据作为消息体，就会涉及顺序问题了。如果消费者收到同一个订单的两条消息，第一条消息的状态是下单，第二条消息的状态是支付，这是没问题的。但如果第一条消息的状态是支付，第二条消息的状态是下单就会有问题了，没有下单就先支付了？<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hyfv.png&amp;size=m" width="100%" height="100%" style="display: inline-block">消息顺序问题是一个非常棘手的问题，比如：</p>
<ul>
 <li>
  <p style=""><code>kafka</code>同一个<code>partition</code>中能保证顺序，但是不同的<code>partition</code>无法保证顺序。</p>
 </li>
 <li>
  <p style=""><code>rabbitmq</code>的同一个<code>queue</code>能够保证顺序，但是如果多个消费者同一个<code>queue</code>也会有顺序问题。</p>
 </li>
</ul>
<p style="line-height: 1.75em">如果消费者使用多线程消费消息，也无法保证顺序。</p>
<p style="line-height: 1.75em">如果消费消息时同一个订单的多条消息中，中间的一条消息出现异常情况，顺序将会被打乱。</p>
<p style="line-height: 1.75em">还有如果生产者发送到mq中的路由规则，跟消费者不一样，也无法保证顺序。</p>
<h3 style="" id="3.5-%E6%B6%88%E6%81%AF%E5%A0%86%E7%A7%AF"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">3.5 消息堆积</span></strong></h3>
<p style="line-height: 1.75em">如果消息消费者读取消息的速度，能够跟上消息生产者的节奏，那么整套mq机制就能发挥最大作用。但是很多时候，由于某些批处理，或者其他原因，导致消息消费的速度小于生产的速度。这样会直接导致消息堆积问题，从而影响业务功能。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bddg.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这里以下单开通会员为例，如果消息出现堆积，会导致用户下单之后，很久之后才能变成会员，这种情况肯定会引起大量用户投诉。</p>
<h3 style="" id="3.6-%E7%B3%BB%E7%BB%9F%E5%A4%8D%E6%9D%82%E5%BA%A6%E6%8F%90%E5%8D%87"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">3.6 系统复杂度提升</span></strong></h3>
<p style="line-height: 1.75em">这里说的系统复杂度和系统耦合性是不一样的，比如以前只有：系统A、系统B和系统C 这三个系统，现在引入mq之后，你除了需要关注前面三个系统之外，还需要关注mq服务，需要关注的点越多，系统的复杂度越高。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-idnp.png&amp;size=m" width="100%" height="100%" style="display: inline-block">mq的机制需要：生产者、mq服务器、消费者。</p>
<p style="line-height: 1.75em">有一定的学习成本，需要额外部署mq服务器，而且有些mq比如：rocketmq，功能非常强大，用法有点复杂，如果使用不好，会出现很多问题。有些问题，不像接口调用那么容易排查，从而导致系统的复杂度提升了。</p>
<h2 style="text-align: center; " id="4-%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E8%BF%99%E4%BA%9B%E9%97%AE%E9%A2%98%EF%BC%9F"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">4 如何解决这些问题？</span></strong></h2>
<p style="line-height: 1.75em">mq是一种趋势，总体来说对我们的系统是利大于弊的，难道因为它会出现一些问题，我们就不用它了？</p>
<p style="line-height: 1.75em">那么我们要如何解决这些问题呢？</p>
<h3 style="" id="4.1-%E9%87%8D%E5%A4%8D%E6%B6%88%E6%81%AF%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">4.1 重复消息问题</span></strong></h3>
<p style="line-height: 1.75em">不管是由于生产者产生的重复消息，还是由于消费者导致的重复消息，我们都可以在消费者中这个问题。</p>
<p style="line-height: 1.75em">这就要求消费者在做业务处理时，要做幂等设计，如果有不知道如何设计的朋友，可以参考《<a href="http://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247486496&amp;idx=1&amp;sn=063f248ea9ae0b594194bcfc9c7449d8&amp;chksm=f9800afacef783ec3034bbf1b065be01d92225c5442039388cf7a1dad16d0120f93034075b69&amp;scene=21#wechat_redirect" target="_blank">高并发下如何保证接口的幂等性？</a>》，里面介绍得非常详情。</p>
<p style="line-height: 1.75em">在这里我推荐增加一张消费消息表，来解决mq的这类问题。消费消息表中，使用<code>messageId</code>做<code>唯一索引</code>，在处理业务逻辑之前，先根据messageId查询一下该消息有没有处理过，如果已经处理过了则直接返回成功，如果没有处理过，则继续做业务处理。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yehg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="4.2-%E6%95%B0%E6%8D%AE%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">4.2 数据一致性问题</span></strong></h3>
<p style="line-height: 1.75em">我们都知道数据一致性分为：</p>
<ul>
 <li>
  <p style="">强一致性</p>
 </li>
 <li>
  <p style="">弱一致性</p>
 </li>
 <li>
  <p style="">最终一致性</p>
 </li>
</ul>
<p style="line-height: 1.75em">而mq为了性能考虑使用的是<code>最终一致性</code>，那么必定会出现数据不一致的问题。这类问题大概率是因为消费者读取消息后，业务逻辑处理失败导致的，这时候可以增加<code>重试机制</code>。</p>
<p style="line-height: 1.75em">重试分为：<code>同步重试</code> 和 <code>异步重试</code>。</p>
<p style="line-height: 1.75em">有些消息量比较小的业务场景，可以采用同步重试，在消费消息时如果处理失败，立刻重试3-5次，如何还是失败，则写入到<code>记录表</code>中。但如果消息量比较大，则不建议使用这种方式，因为如果出现网络异常，可能会导致大量的消息不断重试，影响消息读取速度，造成<code>消息堆积</code>。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rxes.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">而消息量比较大的业务场景，建议采用异步重试，在消费者处理失败之后，立刻写入<code>重试表</code>，有个<code>job</code>专门定时重试。</p>
<p style="line-height: 1.75em">还有一种做法是，如果消费失败，自己给同一个topic发一条消息，在后面的某个时间点，自己又会消费到那条消息，起到了重试的效果。如果对消息顺序要求不高的场景，可以使用这种方式。</p>
<h3 style="" id="4.3-%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">4.3 消息丢失问题</span></strong></h3>
<p style="line-height: 1.75em">不管你是否承认有时候消息真的会丢，即使这种概率非常小，也会对业务有影响。生产者、mq服务器、消费者都有可能会导致消息丢失的问题。</p>
<p style="line-height: 1.75em">为了解决这个问题，我们可以增加一张<code>消息发送表</code>，当生产者发完消息之后，会往该表中写入一条数据，状态status标记为待确认。如果消费者读取消息之后，调用生产者的api更新该消息的status为已确认。有个job，每隔一段时间检查一次消息发送表，如果5分钟（这个时间可以根据实际情况来定）后还有状态是待确认的消息，则认为该消息已经丢失了，重新发条消息。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dydx.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这样不管是由于生产者、mq服务器、还是消费者导致的消息丢失问题，job都会重新发消息。</p>
<h3 style="" id="4.4-%E6%B6%88%E6%81%AF%E9%A1%BA%E5%BA%8F%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">4.4 消息顺序问题</span></strong></h3>
<p style="line-height: 1.75em">消息顺序问题是我们非常常见的问题，我们以<code>kafka</code>消费订单消息为例。订单有：下单、支付、完成、退货等状态，这些状态是有先后顺序的，如果顺序错了会导致业务异常。</p>
<p style="line-height: 1.75em">解决这类问题之前，我们先确认一下，消费者是否真的需要知道中间状态，只知道最终状态行不行？</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yyll.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">其实很多时候，我真的需要知道的是最终状态，这时可以把流程优化一下：</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vkxo.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这种方式可以解决大部分的消息顺序问题。</p>
<p style="line-height: 1.75em">但如果真的有需要保证消息顺序的需求。订单号路由到不同的<code>partition</code>，同一个订单号的消息，每次到发到同一个<code>partition</code>。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iusc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="4.5-%E6%B6%88%E6%81%AF%E5%A0%86%E7%A7%AF"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">4.5 消息堆积</span></strong></h3>
<p style="line-height: 1.75em">如果消费者消费消息的速度小于生产者生产消息的速度，将会出现消息堆积问题。其实这类问题产生的原因很多，如果你想进一步了解，可以看看我的另一篇文章《<a href="http://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&amp;mid=2247486202&amp;idx=1&amp;sn=23f249d3796eb53aff9cf41de6a41761&amp;chksm=f9800c20cef785361afc55298d26e8dc799751a472be48eae6c02b508b7cb8c62ba3ac4eb99b&amp;scene=21#wechat_redirect" target="_blank">我用kafka两年踩过的一些非比寻常的坑</a>》。</p>
<p style="line-height: 1.75em">那么消息堆积问题该如何解决呢？</p>
<p style="line-height: 1.75em">这个要看消息是否需要保证顺序。</p>
<p style="line-height: 1.75em">如果不需要保证顺序，可以读取消息之后用多线程处理业务逻辑。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-unbi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">这样就能增加业务逻辑处理速度，解决消息堆积问题。但是线程池的核心线程数和最大线程数需要合理配置，不然可能会浪费系统资源。</p>
<p style="line-height: 1.75em">如果需要保证顺序，可以读取消息之后，将消息按照一定的规则分发到多个队列中，然后在队列中用单线程处理。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wgvp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">好了，今天先分享到这来，下期再见。我在这里只是抛砖引玉，其实mq相关的内容还有很多，比如：定时发送、延迟发送、私信队列、事务问题等等，有兴趣的朋友可以找我私聊。</p>
<p style="">原文链接：https://mp.weixin.qq.com/s/j5Jedf7OSPkyNs11610v8Q</p>]]></description><guid isPermaLink="false">/archives/mqde-na-xie-po-shi-er-ni-bu-hao-qi-ma</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 02:49:28 GMT</pubDate></item><item><title><![CDATA[高并发下秒杀商品，你必须知道的9个细节]]></title><link>https://wiki.dgfygk.com/archives/gao-bing-fa-xia-miao-sha-shang-pin-ni-bi-xu-zhi-dao-de-9ge-xi-jie</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%AB%98%E5%B9%B6%E5%8F%91%E4%B8%8B%E7%A7%92%E6%9D%80%E5%95%86%E5%93%81%EF%BC%8C%E4%BD%A0%E5%BF%85%E9%A1%BB%E7%9F%A5%E9%81%93%E7%9A%849%E4%B8%AA%E7%BB%86%E8%8A%82&amp;url=/archives/gao-bing-fa-xia-miao-sha-shang-pin-ni-bi-xu-zhi-dao-de-9ge-xi-jie" width="1" height="1" alt="" style="opacity:0;">
<h2 style="text-align: center; " id="%E5%89%8D%E8%A8%80"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">前言</span></strong></h2>
<p style="line-height: 1.75em">高并发下如何设计秒杀系统？这是一个高频面试题。这个问题看似简单，但是里面的水很深，它考查的是高并发场景下，从前端到后端多方面的知识。</p>
<p style="line-height: 1.75em">秒杀一般出现在商城的<code>促销活动</code>中，指定了一定数量（比如：10个）的商品（比如：手机），以极低的价格（比如：0.1元），让大量用户参与活动，但只有极少数用户能够购买成功。这类活动商家绝大部分是不赚钱的，说白了是找个噱头宣传自己。</p>
<p style="line-height: 1.75em">虽说秒杀只是一个促销活动，但对技术要求不低。下面给大家总结一下设计秒杀系统需要注意的9个细节。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zyyx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="text-align: center; " id="1-%E7%9E%AC%E6%97%B6%E9%AB%98%E5%B9%B6%E5%8F%91"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">1 瞬时高并发</span></strong></h2>
<p style="line-height: 1.75em">一般在<code>秒杀时间点</code>（比如：12点）前几分钟，用户并发量才真正突增，达到秒杀时间点时，并发量会达到顶峰。</p>
<p style="line-height: 1.75em">但由于这类活动是大量用户抢少量商品的场景，必定会出现<code>狼多肉少</code>的情况，所以其实绝大部分用户秒杀会失败，只有极少部分用户能够成功。</p>
<p style="line-height: 1.75em">正常情况下，大部分用户会收到商品已经抢完的提醒，收到该提醒后，他们大概率不会在那个活动页面停留了，如此一来，用户并发量又会急剧下降。所以这个峰值持续的时间其实是非常短的，这样就会出现瞬时高并发的情况，下面用一张图直观的感受一下流量的变化：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wjcj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">像这种瞬时高并发的场景，传统的系统很难应对，我们需要设计一套全新的系统。可以从以下几个方面入手：</p>
<ol>
 <li>
  <p style="">页面静态化</p>
 </li>
 <li>
  <p style="">CDN加速</p>
 </li>
 <li>
  <p style="">缓存</p>
 </li>
 <li>
  <p style="">mq异步处理</p>
 </li>
 <li>
  <p style="">限流</p>
 </li>
 <li>
  <p style="">分布式锁</p>
 </li>
</ol>
<h2 style="text-align: center; " id="2.-%E9%A1%B5%E9%9D%A2%E9%9D%99%E6%80%81%E5%8C%96"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">2. 页面静态化</span></strong></h2>
<p style="line-height: 1.75em">活动页面是用户流量的第一入口，所以是并发量最大的地方。</p>
<p style="line-height: 1.75em">如果这些流量都能直接访问服务端，恐怕服务端会因为承受不住这么大的压力，而直接挂掉。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cxvy.png&amp;size=m" width="100%" height="100%" style="display: inline-block">活动页面绝大多数内容是固定的，比如：商品名称、商品描述、图片等。为了减少不必要的服务端请求，通常情况下，会对活动页面做<code>静态化</code>处理。用户浏览商品等常规操作，并不会请求到服务端。只有到了秒杀时间点，并且用户主动点了秒杀按钮才允许访问服务端。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uits.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这样能过滤大部分无效请求。</p>
<p style="line-height: 1.75em">但只做页面静态化还不够，因为用户分布在全国各地，有些人在北京，有些人在成都，有些人在深圳，地域相差很远，网速各不相同。</p>
<p style="line-height: 1.75em">如何才能让用户最快访问到活动页面呢？</p>
<p style="line-height: 1.75em">这就需要使用CDN，它的全称是Content Delivery Network，即内容分发网络。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dlfx.png&amp;size=m" width="100%" height="100%" style="display: inline-block">使用户就近获取所需内容，降低网络拥塞，提高用户访问响应速度和命中率。</p>
<h2 style="text-align: center; " id="3-%E7%A7%92%E6%9D%80%E6%8C%89%E9%92%AE"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">3 秒杀按钮</span></strong></h2>
<p style="line-height: 1.75em">大部分用户怕错过<code>秒杀时间点</code>，一般会提前进入活动页面。此时看到的<code>秒杀按钮</code>是置灰，不可点击的。只有到了秒杀时间点那一时刻，秒杀按钮才会自动点亮，变成可点击的。</p>
<p style="line-height: 1.75em">但此时很多用户已经迫不及待了，通过不停刷新页面，争取在第一时间看到秒杀按钮的点亮。</p>
<p style="line-height: 1.75em">从前面得知，该活动页面是静态的。那么我们在静态页面中如何控制秒杀按钮，只在秒杀时间点时才点亮呢？</p>
<p style="line-height: 1.75em">没错，使用js文件控制。</p>
<p style="line-height: 1.75em">为了性能考虑，一般会将css、js和图片等静态资源文件提前缓存到CDN上，让用户能够就近访问秒杀页面。</p>
<p style="line-height: 1.75em">看到这里，有些聪明的小伙伴，可能会问：CDN上的js文件是如何更新的？</p>
<p style="line-height: 1.75em">秒杀开始之前，js标志为false，还有另外一个随机参数。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qqbp.png&amp;size=m" width="100%" height="100%" style="display: inline-block">当秒杀开始的时候系统会生成一个新的js文件，此时标志为true，并且随机参数生成一个新值，然后同步给CDN。由于有了这个随机参数，CDN不会缓存数据，每次都能从CDN中获取最新的js代码。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tgoo.png&amp;size=m" width="100%" height="100%" style="display: inline-block">此外，前端还可以加一个定时器，控制比如：10秒之内，只允许发起一次请求。如果用户点击了一次秒杀按钮，则在10秒之内置灰，不允许再次点击，等到过了时间限制，又允许重新点击该按钮。</p>
<h2 style="text-align: center; " id="4-%E8%AF%BB%E5%A4%9A%E5%86%99%E5%B0%91"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">4 读多写少</span></strong></h2>
<p style="line-height: 1.75em">在秒杀的过程中，系统一般会先查一下库存是否足够，如果足够才允许下单，写数据库。如果不够，则直接返回该商品已经抢完。</p>
<p style="line-height: 1.75em">由于大量用户抢少量商品，只有极少部分用户能够抢成功，所以绝大部分用户在秒杀时，库存其实是不足的，系统会直接返回该商品已经抢完。</p>
<p style="line-height: 1.75em">这是非常典型的：<code>读多写少</code>&nbsp;的场景。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uerm.png&amp;size=m" width="100%" height="100%" style="display: inline-block">如果有数十万的请求过来，同时通过数据库查缓存是否足够，此时数据库可能会挂掉。因为数据库的连接资源非常有限，比如：mysql，无法同时支持这么多的连接。</p>
<p style="line-height: 1.75em">而应该改用缓存，比如：redis。</p>
<p style="line-height: 1.75em">即便用了redis，也需要部署多个节点。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xpcx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="text-align: center; " id="5-%E7%BC%93%E5%AD%98%E9%97%AE%E9%A2%98"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">5 缓存问题</span></strong></h2>
<p style="line-height: 1.75em">通常情况下，我们需要在redis中保存商品信息，里面包含：商品id、商品名称、规格属性、库存等信息，同时数据库中也要有相关信息，毕竟缓存并不完全可靠。</p>
<p style="line-height: 1.75em">用户在点击秒杀按钮，请求秒杀接口的过程中，需要传入的商品id参数，然后服务端需要校验该商品是否合法。</p>
<p style="line-height: 1.75em">大致流程如下图所示：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kryt.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">根据商品id，先从缓存中查询商品，如果商品存在，则参与秒杀。如果不存在，则需要从数据库中查询商品，如果存在，则将商品信息放入缓存，然后参与秒杀。如果商品不存在，则直接提示失败。</p>
<p style="line-height: 1.75em">这个过程表面上看起来是OK的，但是如果深入分析一下会发现一些问题。</p>
<h3 style="" id="5.1-%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">5.1 缓存击穿</span></strong></h3>
<p style="line-height: 1.75em">比如商品A第一次秒杀时，缓存中是没有数据的，但数据库中有。虽说上面有如果从数据库中查到数据，则放入缓存的逻辑。</p>
<p style="line-height: 1.75em">然而，在高并发下，同一时刻会有大量的请求，都在秒杀同一件商品，这些请求同时去查缓存中没有数据，然后又同时访问数据库。结果悲剧了，数据库可能扛不住压力，直接挂掉。</p>
<p style="line-height: 1.75em">如何解决这个问题呢？</p>
<p style="line-height: 1.75em">这就需要加锁，最好使用分布式锁。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-howv.png&amp;size=m" width="100%" height="100%" style="display: inline-block">当然，针对这种情况，最好在项目启动之前，先把缓存进行<code>预热</code>。即事先把所有的商品，同步到缓存中，这样商品基本都能直接从缓存中获取到，就不会出现缓存击穿的问题了。</p>
<p style="line-height: 1.75em">是不是上面加锁这一步可以不需要了？</p>
<p style="line-height: 1.75em">表面上看起来，确实可以不需要。但如果缓存中设置的过期时间不对，缓存提前过期了，或者缓存被不小心删除了，如果不加速同样可能出现缓存击穿。</p>
<p style="line-height: 1.75em">其实这里加锁，相当于买了一份保险。</p>
<h3 style="" id="5.2-%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">5.2 缓存穿透</span></strong></h3>
<p style="line-height: 1.75em">如果有大量的请求传入的商品id，在缓存中和数据库中都不存在，这些请求不就每次都会穿透过缓存，而直接访问数据库了。</p>
<p style="line-height: 1.75em">由于前面已经加了锁，所以即使这里的并发量很大，也不会导致数据库直接挂掉。</p>
<p style="line-height: 1.75em">但很显然这些请求的处理性能并不好，有没有更好的解决方案？</p>
<p style="line-height: 1.75em">这时可以想到<code>布隆过滤器</code>。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qugw.png&amp;size=m" width="100%" height="100%" style="display: inline-block">系统根据商品id，先从布隆过滤器中查询该id是否存在，如果存在则允许从缓存中查询数据，如果不存在，则直接返回失败。</p>
<p style="line-height: 1.75em">虽说该方案可以解决缓存穿透问题，但是又会引出另外一个问题：布隆过滤器中的数据如何跟缓存中的数据保持一致？</p>
<p style="line-height: 1.75em">这就要求，如果缓存中数据有更新，则要及时同步到布隆过滤器中。如果数据同步失败了，还需要增加重试机制，而且跨数据源，能保证数据的实时一致性吗？</p>
<p style="line-height: 1.75em">显然是不行的。</p>
<p style="line-height: 1.75em">所以布隆过滤器绝大部分使用在缓存数据更新很少的场景中。</p>
<p style="line-height: 1.75em">如果缓存数据更新非常频繁，又该如何处理呢？</p>
<p style="line-height: 1.75em">这时，就需要把不存在的商品id也缓存起来。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gsju.png&amp;size=m" width="100%" height="100%" style="display: inline-block">下次，再有该商品id的请求过来，则也能从缓存中查到数据，只不过该数据比较特殊，表示商品不存在。需要特别注意的是，这种特殊缓存设置的超时时间应该尽量短一点。</p>
<h2 style="text-align: center; " id="6-%E5%BA%93%E5%AD%98%E9%97%AE%E9%A2%98"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">6 库存问题</span></strong></h2>
<p style="line-height: 1.75em">对于库存问题看似简单，实则里面还是有些东西。</p>
<p style="line-height: 1.75em">真正的秒杀商品的场景，不是说扣完库存，就完事了，如果用户在一段时间内，还没完成支付，扣减的库存是要加回去的。</p>
<p style="line-height: 1.75em">所以，在这里引出了一个<code>预扣库存</code>的概念，预扣库存的主要流程如下：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ioyq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">扣减库存中除了上面说到的<code>预扣库存</code>和<code>回退库存</code>之外，还需要特别注意的是库存不足和库存超卖问题。</p>
<h3 style="" id="6.1-%E6%95%B0%E6%8D%AE%E5%BA%93%E6%89%A3%E5%87%8F%E5%BA%93%E5%AD%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">6.1 数据库扣减库存</span></strong></h3>
<p style="line-height: 1.75em">使用数据库扣减库存，是最简单的实现方案了，假设扣减库存的sql如下：</p>
<pre><code>update&nbsp;product&nbsp;set&nbsp;stock=stock-1&nbsp;where&nbsp;id=123;
</code></pre>
<p style="line-height: 1.75em">这种写法对于扣减库存是没有问题的，但如何控制库存不足的情况下，不让用户操作呢？</p>
<p style="line-height: 1.75em">这就需要在update之前，先查一下库存是否足够了。</p>
<p style="line-height: 1.75em">伪代码如下：</p>
<pre><code>int&nbsp;stock&nbsp;=&nbsp;mapper.getStockById(123);
if(stock&nbsp;&gt;&nbsp;0)&nbsp;{
&nbsp;&nbsp;int&nbsp;count&nbsp;=&nbsp;mapper.updateStock(123);
&nbsp;&nbsp;if(count&nbsp;&gt;&nbsp;0)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;addOrder(123);
&nbsp;&nbsp;}
}
</code></pre>
<p style="line-height: 1.75em">大家有没有发现这段代码的问题？</p>
<p style="line-height: 1.75em">没错，查询操作和更新操作不是原子性的，会导致在并发的场景下，出现库存超卖的情况。</p>
<p style="line-height: 1.75em">有人可能会说，这样好办，加把锁，不就搞定了，比如使用synchronized关键字。</p>
<p style="line-height: 1.75em">确实，可以，但是性能不够好。</p>
<p style="line-height: 1.75em">还有更优雅的处理方案，即基于数据库的乐观锁，这样会少一次数据库查询，而且能够天然的保证数据操作的原子性。</p>
<p style="line-height: 1.75em">只需将上面的sql稍微调整一下：</p>
<pre><code>update&nbsp;product&nbsp;set&nbsp;stock=stock-1&nbsp;where&nbsp;id=product&nbsp;and&nbsp;stock&nbsp;&gt;&nbsp;0;
</code></pre>
<p style="line-height: 1.75em">在sql最后加上：<code>stock &gt; 0</code>，就能保证不会出现超卖的情况。</p>
<p style="line-height: 1.75em">但需要频繁访问数据库，我们都知道数据库连接是非常昂贵的资源。在高并发的场景下，可能会造成系统雪崩。而且，容易出现多个请求，同时竞争行锁的情况，造成相互等待，从而出现死锁的问题。</p>
<h3 style="" id="6.2-redis%E6%89%A3%E5%87%8F%E5%BA%93%E5%AD%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">6.2 redis扣减库存</span></strong></h3>
<p style="line-height: 1.75em">redis的<code>incr</code>方法是原子性的，可以用该方法扣减库存。伪代码如下：</p>
<pre><code> boolean&nbsp;exist&nbsp;=&nbsp;redisClient.query(productId,userId);
&nbsp;&nbsp;if(exist)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;
&nbsp;&nbsp;}
&nbsp;&nbsp;int&nbsp;stock&nbsp;=&nbsp;redisClient.queryStock(productId);
&nbsp;&nbsp;if(stock&nbsp;&lt;=0)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;
&nbsp;&nbsp;}
&nbsp;&nbsp;redisClient.incrby(productId,&nbsp;-1);
&nbsp;&nbsp;redisClient.add(productId,userId);
return&nbsp;1;
</code></pre>
<p style="line-height: 1.75em">代码流程如下：</p>
<ol>
 <li>
  <p style="">先判断该用户有没有秒杀过该商品，如果已经秒杀过，则直接返回-1。</p>
 </li>
 <li>
  <p style="">查询库存，如果库存小于等于0，则直接返回0，表示库存不足。</p>
 </li>
 <li>
  <p style="">如果库存充足，则扣减库存，然后将本次秒杀记录保存起来。然后返回1，表示成功。</p>
 </li>
</ol>
<p style="line-height: 1.75em">估计很多小伙伴，一开始都会按这样的思路写代码。但如果仔细想想会发现，这段代码有问题。</p>
<p style="line-height: 1.75em">有什么问题呢？</p>
<p style="line-height: 1.75em">如果在高并发下，有多个请求同时查询库存，当时都大于0。由于查询库存和更新库存非原则操作，则会出现库存为负数的情况，即<code>库存超卖</code>。</p>
<p style="line-height: 1.75em">当然有人可能会说，加个<code>synchronized</code>不就解决问题？</p>
<p style="line-height: 1.75em">调整后代码如下：</p>
<pre><code>&nbsp;&nbsp;&nbsp;boolean&nbsp;exist&nbsp;=&nbsp;redisClient.query(productId,userId);
&nbsp;&nbsp;&nbsp;if(exist)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;
&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;synchronized(this)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;stock&nbsp;=&nbsp;redisClient.queryStock(productId);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(stock&nbsp;&lt;=0)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redisClient.incrby(productId,&nbsp;-1);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redisClient.add(productId,userId);
&nbsp;&nbsp;&nbsp;}

return&nbsp;1;
</code></pre>
<p style="line-height: 1.75em">加<code>synchronized</code>确实能解决库存为负数问题，但是这样会导致接口性能急剧下降，每次查询都需要竞争同一把锁，显然不太合理。</p>
<p style="line-height: 1.75em">为了解决上面的问题，代码优化如下：</p>
<pre><code>boolean&nbsp;exist&nbsp;=&nbsp;redisClient.query(productId,userId);
if(exist)&nbsp;{
&nbsp;&nbsp;return&nbsp;-1;
}
if(redisClient.incrby(productId,&nbsp;-1)&lt;0)&nbsp;{
&nbsp;&nbsp;return&nbsp;0;
}
redisClient.add(productId,userId);
return&nbsp;1;
</code></pre>
<p style="line-height: 1.75em">该代码主要流程如下：</p>
<ol>
 <li>
  <p style="">先判断该用户有没有秒杀过该商品，如果已经秒杀过，则直接返回-1。</p>
 </li>
 <li>
  <p style="">扣减库存，判断返回值是否小于0，如果小于0，则直接返回0，表示库存不足。</p>
 </li>
 <li>
  <p style="">如果扣减库存后，返回值大于或等于0，则将本次秒杀记录保存起来。然后返回1，表示成功。</p>
 </li>
</ol>
<p style="line-height: 1.75em">该方案咋一看，好像没问题。</p>
<p style="line-height: 1.75em">但如果在高并发场景中，有多个请求同时扣减库存，大多数请求的incrby操作之后，结果都会小于0。</p>
<p style="line-height: 1.75em">虽说，库存出现负数，不会出现<code>超卖的问题</code>。但由于这里是预减库存，如果负数值负的太多的话，后面万一要回退库存时，就会导致库存不准。</p>
<p style="line-height: 1.75em">那么，有没有更好的方案呢？</p>
<h3 style="" id="6.3-lua%E8%84%9A%E6%9C%AC%E6%89%A3%E5%87%8F%E5%BA%93%E5%AD%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">6.3 lua脚本扣减库存</span></strong></h3>
<p style="line-height: 1.75em">我们都知道lua脚本，是能够保证原子性的，它跟redis一起配合使用，能够完美解决上面的问题。</p>
<p style="line-height: 1.75em">lua脚本有段非常经典的代码：</p>
<pre><code>&nbsp;&nbsp;StringBuilder&nbsp;lua&nbsp;=&nbsp;new&nbsp;StringBuilder();
&nbsp;&nbsp;lua.append("if&nbsp;(redis.call('exists',&nbsp;KEYS[1])&nbsp;==&nbsp;1)&nbsp;then");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;local&nbsp;stock&nbsp;=&nbsp;tonumber(redis.call('get',&nbsp;KEYS[1]));");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(stock&nbsp;==&nbsp;-1)&nbsp;then");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;1;");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;end;");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(stock&nbsp;&gt;&nbsp;0)&nbsp;then");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redis.call('incrby',&nbsp;KEYS[1],&nbsp;-1);");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;stock;");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;end;");
&nbsp;&nbsp;lua.append("&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;");
&nbsp;&nbsp;lua.append("end;");
&nbsp;&nbsp;lua.append("return&nbsp;-1;");
</code></pre>
<p style="line-height: 1.75em">该代码的主要流程如下：</p>
<ol>
 <li>
  <p style="">先判断商品id是否存在，如果不存在则直接返回。</p>
 </li>
 <li>
  <p style="">获取该商品id的库存，判断库存如果是-1，则直接返回，表示不限制库存。</p>
 </li>
 <li>
  <p style="">如果库存大于0，则扣减库存。</p>
 </li>
 <li>
  <p style="">如果库存等于0，是直接返回，表示库存不足。</p>
 </li>
</ol>
<h2 style="text-align: center; " id="7-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">7 分布式锁</span></strong></h2>
<p style="">之前我提到过，在秒杀的时候，需要先从缓存中查商品是否存在，如果不存在，则会从数据库中查商品。如果数据库中，则将该商品放入缓存中，然后返回。如果数据库中没有，则直接返回失败。</p>
<p style=""></p>
<p style="">大家试想一下，如果在高并发下，有大量的请求都去查一个缓存中不存在的商品，这些请求都会直接打到数据库。数据库由于承受不住压力，而直接挂掉。</p>
<p style=""></p>
<p style="">那么如何解决这个问题呢？</p>
<p style=""></p>
<p style="">这就需要用redis分布式锁了。</p>
<h3 style="" id="7.1-setnx%E5%8A%A0%E9%94%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">7.1 setNx加锁</span></strong></h3>
<p style="line-height: 1.75em">使用redis的分布式锁，首先想到的是<code>setNx</code>命令。</p>
<pre><code>if&nbsp;(jedis.setnx(lockKey,&nbsp;val)&nbsp;==&nbsp;1)&nbsp;{
&nbsp;&nbsp;&nbsp;jedis.expire(lockKey,&nbsp;timeout);
}
</code></pre>
<p style="line-height: 1.75em">用该命令其实可以加锁，但和后面的设置超时时间是分开的，并非原子操作。</p>
<p style="line-height: 1.75em">假如加锁成功了，但是设置超时时间失败了，该lockKey就变成永不失效的了。在高并发场景中，该问题会导致非常严重的后果。</p>
<p style="line-height: 1.75em">那么，有没有保证原子性的加锁命令呢？</p>
<h3 style="" id="7.2-set%E5%8A%A0%E9%94%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">7.2 set加锁</span></strong></h3>
<p style="line-height: 1.75em">使用redis的set命令，它可以指定多个参数。</p>
<pre><code>String&nbsp;result&nbsp;=&nbsp;jedis.set(lockKey,&nbsp;requestId,&nbsp;"NX",&nbsp;"PX",&nbsp;expireTime);
if&nbsp;("OK".equals(result))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;true;
}
return&nbsp;false;
</code></pre>
<p style="line-height: 1.75em">其中：</p>
<ul>
 <li>
  <p style="">lockKey：锁的标识</p>
 </li>
 <li>
  <p style="">requestId：请求id</p>
 </li>
 <li>
  <p style="">NX：只在键不存在时，才对键进行设置操作。</p>
 </li>
 <li>
  <p style="">PX：设置键的过期时间为 millisecond 毫秒。</p>
 </li>
 <li>
  <p style="">expireTime：过期时间</p>
 </li>
</ul>
<p style=""></p>
<p style=""><span fontsize="" color="rgb(1, 1, 1)" style="color: rgb(1, 1, 1)">由于该命令只有一步，所以它是原子操作。</span></p>
<h3 style="" id="7.3-%E9%87%8A%E6%94%BE%E9%94%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">7.3 释放锁</span></strong></h3>
<p style="line-height: 1.75em">接下来，有些朋友可能会问：在加锁时，既然已经有了lockKey锁标识，为什么要需要记录requestId呢？</p>
<p style="line-height: 1.75em">答：requestId是在释放锁的时候用的。</p>
<pre><code>if&nbsp;(jedis.get(lockKey).equals(requestId))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;jedis.del(lockKey);
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;true;
}
return&nbsp;false;
</code></pre>
<p style="line-height: 1.75em">在释放锁的时候，只能释放自己加的锁，不允许释放别人加的锁。</p>
<p style="line-height: 1.75em">这里为什么要用requestId，用userId不行吗？</p>
<p style="line-height: 1.75em">答：如果用userId的话，假设本次请求流程走完了，准备删除锁。此时，巧合锁到了过期时间失效了。而另外一个请求，巧合使用的相同userId加锁，会成功。而本次请求删除锁的时候，删除的其实是别人的锁了。</p>
<p style="line-height: 1.75em">当然使用lua脚本也能避免该问题：</p>
<pre><code>if&nbsp;redis.call('get',&nbsp;KEYS[1])&nbsp;==&nbsp;ARGV[1]&nbsp;then&nbsp;
&nbsp;return&nbsp;redis.call('del',&nbsp;KEYS[1])&nbsp;
else&nbsp;
&nbsp;&nbsp;return&nbsp;0&nbsp;
end
</code></pre>
<p style="line-height: 1.75em">它能保证查询锁是否存在和删除锁是原子操作。</p>
<h3 style="" id="7.4-%E8%87%AA%E6%97%8B%E9%94%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">7.4 自旋锁</span></strong></h3>
<p style="line-height: 1.75em">上面的加锁方法看起来好像没有问题，但如果你仔细想想，如果有1万的请求同时去竞争那把锁，可能只有一个请求是成功的，其余的9999个请求都会失败。</p>
<p style="line-height: 1.75em">在秒杀场景下，会有什么问题？</p>
<p style="line-height: 1.75em">答：每1万个请求，有1个成功。再1万个请求，有1个成功。如此下去，直到库存不足。这就变成均匀分布的秒杀了，跟我们想象中的不一样。</p>
<p style="line-height: 1.75em">如何解决这个问题呢？</p>
<p style="line-height: 1.75em">答：使用自旋锁。</p>
<pre><code>try&nbsp;{
&nbsp;&nbsp;Long&nbsp;start&nbsp;=&nbsp;System.currentTimeMillis();
&nbsp;&nbsp;while(true)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;result&nbsp;=&nbsp;jedis.set(lockKey,&nbsp;requestId,&nbsp;"NX",&nbsp;"PX",&nbsp;expireTime);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;("OK".equals(result))&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;true;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;time&nbsp;=&nbsp;System.currentTimeMillis()&nbsp;-&nbsp;start;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(time&gt;=timeout)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(50);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(InterruptedException&nbsp;e)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
&nbsp;
}&nbsp;finally{
&nbsp;&nbsp;&nbsp;&nbsp;unlock(lockKey,requestId);
}&nbsp;&nbsp;
return&nbsp;false;
</code></pre>
<p style="line-height: 1.75em">在规定的时间，比如500毫秒内，自旋不断尝试加锁，如果成功则直接返回。如果失败，则休眠50毫秒，再发起新一轮的尝试。如果到了超时时间，还未加锁成功，则直接返回失败。</p>
<h3 style="" id="7.5-redisson"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">7.5 redisson</span></strong></h3>
<p style="line-height: 1.75em">除了上面的问题之外，使用redis分布式锁，还有锁竞争问题、续期问题、锁重入问题、多个redis实例加锁问题等。</p>
<p style="line-height: 1.75em">这些问题使用redisson可以解决，由于篇幅的原因，在这里先保留一点悬念，有疑问的私聊给我。后面会出一个专题介绍分布式锁，敬请期待。</p>
<h2 style="text-align: center; " id="8-mq%E5%BC%82%E6%AD%A5%E5%A4%84%E7%90%86"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">8 mq异步处理</span></strong></h2>
<p style="line-height: 1.75em">我们都知道在真实的秒杀场景中，有三个核心流程：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-eoea.png&amp;size=m" width="100%" height="100%" style="display: inline-block">而这三个核心流程中，真正并发量大的是秒杀功能，下单和支付功能实际并发量很小。所以，我们在设计秒杀系统时，有必要把下单和支付功能从秒杀的主流程中拆分出来，特别是下单功能要做成mq异步处理的。而支付功能，比如支付宝支付，是业务场景本身保证的异步。</p>
<p style="line-height: 1.75em">于是，秒杀后下单的流程变成如下：<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tigo.png&amp;size=m" width="100%" height="100%" style="display: inline-block">如果使用mq，需要关注以下几个问题：</p>
<h3 style="" id="8.1-%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">8.1 消息丢失问题</span></strong></h3>
<p style="line-height: 1.75em">秒杀成功了，往mq发送下单消息的时候，有可能会失败。原因有很多，比如：网络问题、broker挂了、mq服务端磁盘问题等。这些情况，都可能会造成消息丢失。</p>
<p style="line-height: 1.75em">那么，如何防止消息丢失呢？</p>
<p style="line-height: 1.75em">答：加一张消息发送表。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zlxy.png&amp;size=m" width="100%" height="100%" style="display: inline-block">在生产者发送mq消息之前，先把该条消息写入消息发送表，初始状态是待处理，然后再发送mq消息。消费者消费消息时，处理完业务逻辑之后，再回调生产者的一个接口，修改消息状态为已处理。</p>
<p style="line-height: 1.75em">如果生产者把消息写入消息发送表之后，再发送mq消息到mq服务端的过程中失败了，造成了消息丢失。</p>
<p style="line-height: 1.75em">这时候，要如何处理呢？</p>
<p style="line-height: 1.75em">答：使用job，增加重试机制。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zrsu.png&amp;size=m" width="100%" height="100%" style="display: inline-block">用job每隔一段时间去查询消息发送表中状态为待处理的数据，然后重新发送mq消息。</p>
<h3 style="" id="8.2-%E9%87%8D%E5%A4%8D%E6%B6%88%E8%B4%B9%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">8.2 重复消费问题</span></strong></h3>
<p style="line-height: 1.75em">本来消费者消费消息时，在ack应答的时候，如果网络超时，本身就可能会消费重复的消息。但由于消息发送者增加了重试机制，会导致消费者重复消息的概率增大。</p>
<p style="line-height: 1.75em">那么，如何解决重复消息问题呢？</p>
<p style="line-height: 1.75em">答：加一张消息处理表。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ppfx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.75em">消费者读到消息之后，先判断一下消息处理表，是否存在该消息，如果存在，表示是重复消费，则直接返回。如果不存在，则进行下单操作，接着将该消息写入消息处理表中，再返回。</p>
<p style="line-height: 1.75em">有个比较关键的点是：下单和写消息处理表，要放在同一个事务中，保证原子操作。</p>
<h3 style="" id="8.3-%E5%9E%83%E5%9C%BE%E6%B6%88%E6%81%AF%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">8.3 垃圾消息问题</span></strong></h3>
<p style="line-height: 1.75em">这套方案表面上看起来没有问题，但如果出现了消息消费失败的情况。比如：由于某些原因，消息消费者下单一直失败，一直不能回调状态变更接口，这样job会不停的重试发消息。最后，会产生大量的垃圾消息。</p>
<p style="line-height: 1.75em">那么，如何解决这个问题呢？<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zapp.png&amp;size=m" width="100%" height="100%" style="display: inline-block">每次在job重试时，需要先判断一下消息发送表中该消息的发送次数是否达到最大限制，如果达到了，则直接返回。如果没有达到，则将次数加1，然后发送消息。</p>
<p style="line-height: 1.75em">这样如果出现异常，只会产生少量的垃圾消息，不会影响到正常的业务。</p>
<h3 style="" id="8.4-%E5%BB%B6%E8%BF%9F%E6%B6%88%E8%B4%B9%E9%97%AE%E9%A2%98"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">8.4 延迟消费问题</span></strong></h3>
<p style="line-height: 1.75em">通常情况下，如果用户秒杀成功了，下单之后，在15分钟之内还未完成支付的话，该订单会被自动取消，回退库存。</p>
<p style="line-height: 1.75em">那么，在15分钟内未完成支付，订单被自动取消的功能，要如何实现呢？</p>
<p style="line-height: 1.75em">我们首先想到的可能是job，因为它比较简单。</p>
<p style="line-height: 1.75em">但job有个问题，需要每隔一段时间处理一次，实时性不太好。</p>
<p style="line-height: 1.75em">还有更好的方案？</p>
<p style="line-height: 1.75em">答：使用延迟队列。</p>
<p style="line-height: 1.75em">我们都知道rocketmq，自带了延迟队列的功能。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pvaa.png&amp;size=m" width="100%" height="100%" style="display: inline-block">下单时消息生产者会先生成订单，此时状态为待支付，然后会向延迟队列中发一条消息。达到了延迟时间，消息消费者读取消息之后，会查询该订单的状态是否为待支付。如果是待支付状态，则会更新订单状态为取消状态。如果不是待支付状态，说明该订单已经支付过了，则直接返回。</p>
<p style="line-height: 1.75em">还有个关键点，用户完成支付之后，会修改订单状态为已支付。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lhhd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="text-align: center; " id="9-%E5%A6%82%E4%BD%95%E9%99%90%E6%B5%81%EF%BC%9F"><strong><span style="font-size: 18px; color: rgb(72, 179, 120)">9 如何限流？</span></strong></h2>
<p style="line-height: 1.75em">通过秒杀活动，如果我们运气爆棚，可能会用非常低的价格买到不错的商品（这种概率堪比买福利彩票中大奖）。</p>
<p style="line-height: 1.75em">但有些高手，并不会像我们一样老老实实，通过秒杀页面点击秒杀按钮，抢购商品。他们可能在自己的服务器上，模拟正常用户登录系统，跳过秒杀页面，直接调用秒杀接口。</p>
<p style="line-height: 1.75em">如果是我们手动操作，一般情况下，一秒钟只能点击一次秒杀按钮。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dafv.png&amp;size=m" width="100%" height="100%" style="display: inline-block">但是如果是服务器，一秒钟可以请求成上千接口。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xpgc.png&amp;size=m" width="100%" height="100%" style="display: inline-block">这种差距实在太明显了，如果不做任何限制，绝大部分商品可能是被机器抢到，而非正常的用户，有点不太公平。</p>
<p style="line-height: 1.75em">所以，我们有必要识别这些非法请求，做一些限制。那么，我们该如何现在这些非法请求呢？</p>
<p style="line-height: 1.75em">目前有两种常用的限流方式：</p>
<ol>
 <li>
  <p style="">基于nginx限流</p>
 </li>
 <li>
  <p style="">基于redis限流</p>
 </li>
</ol>
<h3 style="" id="9.1-%E5%AF%B9%E5%90%8C%E4%B8%80%E7%94%A8%E6%88%B7%E9%99%90%E6%B5%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">9.1 对同一用户限流</span></strong></h3>
<p style="line-height: 1.75em">为了防止某个用户，请求接口次数过于频繁，可以只针对该用户做限制。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-msdc.png&amp;size=m" width="100%" height="100%" style="display: inline-block">限制同一个用户id，比如每分钟只能请求5次接口。</p>
<h3 style="" id="9.2-%E5%AF%B9%E5%90%8C%E4%B8%80ip%E9%99%90%E6%B5%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">9.2 对同一ip限流</span></strong></h3>
<p style="line-height: 1.75em">有时候只对某个用户限流是不够的，有些高手可以模拟多个用户请求，这种nginx就没法识别了。</p>
<p style="line-height: 1.75em">这时需要加同一ip限流功能。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kpbv.png&amp;size=m" width="100%" height="100%" style="display: inline-block">限制同一个ip，比如每分钟只能请求5次接口。</p>
<p style="line-height: 1.75em">但这种限流方式可能会有误杀的情况，比如同一个公司或网吧的出口ip是相同的，如果里面有多个正常用户同时发起请求，有些用户可能会被限制住。</p>
<h3 style="" id="9.3-%E5%AF%B9%E6%8E%A5%E5%8F%A3%E9%99%90%E6%B5%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">9.3 对接口限流</span></strong></h3>
<p style="line-height: 1.75em">别以为限制了用户和ip就万事大吉，有些高手甚至可以使用代理，每次都请求都换一个ip。</p>
<p style="line-height: 1.75em">这时可以限制请求的接口总次数。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-eqaz.png&amp;size=m" width="100%" height="100%" style="display: inline-block">在高并发场景下，这种限制对于系统的稳定性是非常有必要的。但可能由于有些非法请求次数太多，达到了该接口的请求上限，而影响其他的正常用户访问该接口。看起来有点得不偿失。</p>
<h3 style="" id="9.4-%E5%8A%A0%E9%AA%8C%E8%AF%81%E7%A0%81"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">9.4 加验证码</span></strong></h3>
<p style="line-height: 1.75em">相对于上面三种方式，加验证码的方式可能更精准一些，同样能限制用户的访问频次，但好处是不会存在误杀的情况。</p>
<p style="line-height: 1.75em"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rhgd.png&amp;size=m" width="100%" height="100%" style="display: inline-block">通常情况下，用户在请求之前，需要先输入验证码。用户发起请求之后，服务端会去校验该验证码是否正确。只有正确才允许进行下一步操作，否则直接返回，并且提示验证码错误。</p>
<p style="line-height: 1.75em">此外，验证码一般是一次性的，同一个验证码只允许使用一次，不允许重复使用。</p>
<p style="line-height: 1.75em">普通验证码，由于生成的数字或者图案比较简单，可能会被破解。优点是生成速度比较快，缺点是有安全隐患。</p>
<p style="line-height: 1.75em">还有一个验证码叫做：<code>移动滑块</code>，它生成速度比较慢，但比较安全，是目前各大互联网公司的首选。</p>
<h3 style="" id="9.5-%E6%8F%90%E9%AB%98%E4%B8%9A%E5%8A%A1%E9%97%A8%E6%A7%9B"><strong><span style="font-size: 17px; color: rgb(72, 179, 120)">9.5 提高业务门槛</span></strong></h3>
<p style="line-height: 1.75em">上面说的加验证码虽然可以限制非法用户请求，但是有些影响用户体验。用户点击秒杀按钮前，还要先输入验证码，流程显得有点繁琐，秒杀功能的流程不是应该越简单越好吗？</p>
<p style="line-height: 1.75em">其实，有时候达到某个目的，不一定非要通过技术手段，通过业务手段也一样。</p>
<p style="line-height: 1.75em">12306刚开始的时候，全国人民都在同一时刻抢火车票，由于并发量太大，系统经常挂。后来，重构优化之后，将购买周期放长了，可以提前20天购买火车票，并且可以在9点、10、11点、12点等整点购买火车票。调整业务之后（当然技术也有很多调整），将之前集中的请求，分散开了，一下子降低了用户并发量。</p>
<p style="line-height: 1.75em">回到这里，我们通过提高业务门槛，比如只有会员才能参与秒杀活动，普通注册用户没有权限。或者，只有等级到达3级以上的普通用户，才有资格参加该活动。</p>
<p style="line-height: 1.75em">这样简单的提高一点门槛，即使是黄牛党也束手无策，他们总不可能为了参加一次秒杀活动，还另外花钱充值会员吧？</p>
<p style="line-height: 1.75em">原文链接：https://mp.weixin.qq.com/s/SuoiIKKCojBghgPms67W_g</p>]]></description><guid isPermaLink="false">/archives/gao-bing-fa-xia-miao-sha-shang-pin-ni-bi-xu-zhi-dao-de-9ge-xi-jie</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 02:42:49 GMT</pubDate></item><item><title><![CDATA[五个经典的破坏双亲委派场景，Java被啪啪打脸]]></title><link>https://wiki.dgfygk.com/archives/wu-ge-jing-dian-de-po-pi-shuang-qin-wei-pai-chang-jing-javabei-pa-pa-da-lian</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%BA%94%E4%B8%AA%E7%BB%8F%E5%85%B8%E7%9A%84%E7%A0%B4%E5%9D%8F%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E5%9C%BA%E6%99%AF%EF%BC%8CJava%E8%A2%AB%E5%95%AA%E5%95%AA%E6%89%93%E8%84%B8&amp;url=/archives/wu-ge-jing-dian-de-po-pi-shuang-qin-wei-pai-chang-jing-javabei-pa-pa-da-lian" width="1" height="1" alt="" style="opacity:0;">
<p style=""><span style="font-size: 16px">在</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486853&amp;idx=1&amp;sn=5af0c8abed9b589d2b221bce92a76dd8&amp;chksm=fafde72bcd8a6e3db14d1d76333954d3efbd8b6b5a204e192bf342e8327e9e3c9dc4cf93bbd4&amp;scene=21#wechat_redirect" target="_blank"><span style="font-size: 16px">《深入理解Java类加载机制，再也不用死记硬背了》</span></a><span style="font-size: 16px">这篇文章中提到，从JVM的角度看，加载的读取二进制流和初始化阶段，是开放了主导权给用户的。而剩下的所有部分都是JVM内部完成的。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-amda.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">那为什么要这样做呢？这是符合面向对象中的开闭原则和封装思想的设计。JVM将类加载内部复杂的实现封装了起来，拒绝开发者修改。只提供了一个拓展接口，用于二进制流的读取。</span></p>
<p style="text-align: center; "></p>
<p style=""><span style="font-size: 16px">流程上搞懂了，那JVM是怎样使用代码来实现这些步骤的呢？这就要聊到Java的类加载器了。
  <br></span></p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">类加载器</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">类加载器的分类是Java规范，属于抽象的概念。规范将类加载器分成启动类和非启动类两大类。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-btau.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><span style="font-size: 16px">HotSpot对类加载器实现有以下分类(以下描述中省略了HotSpot定语)：
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iynf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">启动类加载器是C/C++语言实现的，无法作为对象被程序引用。主要用来加载Java的核心类库。类库主要由Java启动参数指定，默认是${JAVA_HOME}/lib。HotSpot还会对类名进行白名单校验来提高安全性。
  <br></span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">非启动类加载器都是使用Java来实现，继承自java.lang.ClassLoader，可以作为类对象被程序引用。它也分成3种，简单说一下区别。
  <br></span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">扩展类加载器ExtensionClassLoader加载的是${JAVA_HOME}/lib/ext下或者启动时指定的类库。它目标是加载Java类库的扩展，是标准类库的补充。</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">应用类加载器ApplicationClassLoader加载的是环境变量classpath指定的类库。咱们平时使用maven的话，就是使用maven加载的类库和自己编写的代码。</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">用户类加载器就是用户自己定义的类加载器，也叫自定义类加载器。只要继承于应用类加载器即可。定义不同的类加载器会使用不同的命名空间。因为不同的命名空间是个隐含的限定名区分，是不同的对象。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="text-align: center; "><strong><span style="font-size: 18px">双亲委派模型</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">双亲委派的目标是在默认情况下，一个限定名的类只会被一个类加载器加载并解析使用，这样在程序中，它就是唯一的，不会产生歧义。</span></p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ddyi.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px">在被动的情况下，当一个类加载器收到类加载请求，它不会首先自己去加载。而是传递给父加载器。这样，所有的类首先都会先由最上层的启动类加载器进行加载，只有父加载器无法完成类加载才会由子加载器完成。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">白话来说：双亲委派模型中，如果类A调用了类B，那类B可能是类A在使用过程中被动加载进来的。那如果类A是用应用程序类加载器加载的，那么类B只能由应用程序类加载器或其父类或者上一层父类来加载。不能由自定义类加载器来来加载。</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">双亲委派模型并不是一个拥有强约束力的模型。它存在设计缺陷，在一些情况下可以被主动破坏。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">五种经典的破坏双亲委派场景</span></strong></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 17px; color: rgb(216, 79, 169)">第一次破坏</span></strong></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">在《深入理解Java虚拟机》这本书中，记录了怎样破坏双亲委派：因为双亲委派机制原则在java.lang.ClassLoader的loadClass方法中。只要重写loadClass方法就可以破坏。书中还写了一个重写loadClass方法来进行破坏的小样例，这个小样例被称为双亲委派的第一次破坏。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 17px; color: rgb(216, 79, 169)">Tomcat场景</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">这个破坏的样例有没有什么实际价值意义呢？还真有，后来Tomcat就使用这种方式对双亲委派进行破坏，来达到使用一个web容器部署两个或者多个应用程序，不同的应用程序，可能会依赖同一个第三方类库的不同版本，还要能保证每一个应用程序的类库都是独立、相互隔离的效果。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">tomcat自定义了类加载器，重写loadClass方法使其优先加载自己目录下的class文件，来达到class私有的效果。不过咱们现在流行使用的都是嵌入式的web容器了，将来更多的场景还是一个应用程序使用一个单独的web容器。所以这种破坏双亲委派的价值在降低。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 17px; color: rgb(216, 79, 169)">基于SPI的三种经典破坏场景</span></strong></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">还有一种java特性叫做SPI，在《JAVA SPI(Service Provider Interface)原理、设计及源码解析》里，我不仅说了什么是SPI，还提到了三种经典场景都在使用，它们分别是</span><span fontsize="" color="">jdbc、Dubbo、Eleasticsearch。没错，这三种经典场景通过SPI都使</span><span style="font-size: 16px">得双亲委派遭到破坏。下面以jdbc为例做说明。</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">jdbc驱动Driver在十几年前，我还手写过用DriverManager来加载的代码。</span></p>
<p style=""><span style="font-size: 12px">Class.forName("com.mysql.cj.jdbc.Driver");</span></p>
<p style=""><span style="font-size: 12px">Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "1234");</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">DriverManager初始化时是这样的，注意红框内容。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-phro.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">红框内容翻译一下就是说：由启动类加载器加载的DriverManager初始化时要去加载Driver驱动。</span></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kmwm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px">jdbc驱动由Serverloader.load加载。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dzxm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px">而Serverloader.load里优先使用当前线程的类加载器而不是自身使用的类加载器来加载Driver。当前线程是使用方要么是应用类加载器要么是自定义类加载器，总归类A调用类B，B没有使用父类而使用了子类加载器，所以破坏了双亲委派。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 17px; color: rgb(216, 79, 169)">双亲委派被破坏的补救措施</span></strong></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">那朋友就问了，java.lang.ClassLoader把loadClass方法定义为final是不是就解决了双亲委派被破坏的问题呢？java.lang.ClassLoader的loadClass方法在Java很早的版本就有了，而双亲委派模型是在JDK1.2中引入的。Java是向下兼容的，所以不是不想改而是改不了了。一个补救措施是推荐使用findClass方法而不是直接重写loadClass。当然了，如果有人登录了服务器，把JDK文件给替换了，也就失效了。这个不在讨论范围。</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">原文链接：https://mp.weixin.qq.com/s/cRBWC9aiEHnwEwYOISTfLQ</span></p>]]></description><guid isPermaLink="false">/archives/wu-ge-jing-dian-de-po-pi-shuang-qin-wei-pai-chang-jing-javabei-pa-pa-da-lian</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 02:30:22 GMT</pubDate></item><item><title><![CDATA[深入理解Java类加载机制，再也不用死记硬背了]]></title><link>https://wiki.dgfygk.com/archives/shen-ru-li-jie-javalei-jia-zai-ji-zhi-zai-ye-bu-yong-si-ji-ying-bei-liao</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6%EF%BC%8C%E5%86%8D%E4%B9%9F%E4%B8%8D%E7%94%A8%E6%AD%BB%E8%AE%B0%E7%A1%AC%E8%83%8C%E4%BA%86&amp;url=/archives/shen-ru-li-jie-javalei-jia-zai-ji-zhi-zai-ye-bu-yong-si-ji-ying-bei-liao" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">谈谈“会”的三个层次</span></strong></p>
<p style=""></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486732&amp;idx=1&amp;sn=523d754d7944be46985d098c82f79af2&amp;chksm=fafde7a2cd8a6eb47d6145a8538b907b8b118b09ddb530cd57ba80feaa7b539559279debe893&amp;scene=21#wechat_redirect" target="_blank">《说透分布式事务》</a>中，我举例里说明了会与会的差别。对一门语言的学习，这里谈谈我理解的“会”的三个层次：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bhcy.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">第一层：了解这门语言的语法、写法，我把它叫做 hello world 级别；</p>
<p style="">第二层：了解这门语言的优劣势以及它的生态，了解这门语言的能力范围，我把它叫做&nbsp;应用 级别；</p>
<p style="">第三层：了解这门语言的底层运行机制，这有利于对程序进行调优，以及当程序遇到了比较罕见的问题时能够从根上分析解决它。我把它叫做&nbsp;掌握&nbsp;级别。</p>
<p style=""></p>
<p style="">在简历上写掌握某种语言的，一般面试官也会问一些很深入原理的问题，个人认为比较合理。自己作为一个15年一线Java开发，自认为有资格把掌握Java写在简历上。今天，我就来聊聊我对双亲委派机制一些理解。</p>
<p style=""></p>
<p style="">插个题外话，在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486810&amp;idx=1&amp;sn=7fc09ea3625a2f2302a5fd67b92111ee&amp;chksm=fafde7f4cd8a6ee2cbd81fc73e29014be77f30f87e7eff6109f419415cff631672203392c2b2&amp;scene=21#wechat_redirect" target="_blank">《高并发下秒杀商品，你必须知道的9个细节》</a>中，有朋友问配图是用什么画的。这里介绍一下自己的经验：</p>
<p style=""></p>
<p style="">1）思维导图还是processon更加方便：</p>
<p style="">https://www.processon.com/i/594d313ae4b08b003f2ec84a</p>
<p style=""></p>
<p style="">2）流程图还是draw.io，这个不推荐在线编辑，慢到怀疑人生。安装版本也是免费的，官网可轻松下载。开头图的框图效果是draw.io的框图有个 Sketch 样式。这个样式很好看，但是不建议用于文献等正式场合。正式场合的图最好方方正正，不要太圆润，粗细均匀。</p>
<p style=""></p>
<p style="">3）生成曲线图、柱状图这些，还是习惯用excel。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">Java类加载机制</span></strong></p>
<p style=""></p>
<p style="">首先我们需要思考一件事，我们编写的Java代码，是如何在各种各样的操作系统上运行起来的。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-crby.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">Java文件通过Javac编译成class文件，这种中间码被称为字节码。然后由JVM加载字节码。这个过程就称为类加载。</p>
<p style="">
 <br>
 运行时，由解释器将字节码解释为一行行的机器码来执行。在程序运行期间，即时编译器会针对热点代码，将该部分字节码编译成机器码以获取更高的执行效率。在整个运行时，解释器和即时编译器相互配合，使程序几乎能达到和编译型语言几乎一样的执行速度。这个部分交给专业的编译器开发人员来做，咱们本篇不做深入讲解。
</p>
<p style=""></p>
<p style="">到此上面那张图就讲完了，不要问我右上角那两个表情是怎么回事。就是发现编辑的时候竟然可以添加表情，觉得好玩就试试看。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">类的生命周期</span></strong></p>
<p style=""></p>
<p style="">在详细讲解之前，我们明确一下类加载流程的目的。站在高处去看，就是把一份被javac编译过的文件通过加载，生成某种形式的class文件的数据结构送进内存。程序可以调用这个数据结构来构造出Java对象。这个过程是在运行时进行的，也是Java动态拓展性的根基。</p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-znno.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">上面这张图表现了类的整个生命周期。而类加载呢，只包含了加载、链接和初始化三个阶段。加载只是类加载的第一个环节，两者要注意区分。解析部分是灵活的，它可以在初始化环节之前或者之后进行，实现后期绑定。类加载的其他环节的顺序是不可改变的。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">加载</span></strong></p>
<p style=""></p>
<p style="">加载是一个读取class文件，将其转化为某种静态数据结构而存储在方法区内，并在堆中生成一个便于用户调用的Java对象的过程。</p>
<p style="">
 <br>
 这里值得注意的是，这个Java文件不一定是本地文件，泛指各种来源的二进制流，比如网络、数据库或者比如动态代理技术这样的即时生成的class文件。
</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">验证</span></strong></p>
<p style=""></p>
<p style="">验证的步骤很多，上面的图画得不完全准确。对文件格式的校验其实是发生在加载阶段的。通过才能顺利加载。顺利加载并不代表JVM完全认可了这个类，还要进行语法和语义上的分析，保证这个类不会危害JVM，这是对元数据和字节码上的验证。在解析阶段，还会进行符号引用的验证。随着JVM版本的升高，验证过程也在被不断丰富。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">准备</span></strong></p>
<p style=""></p>
<p style="">准备就是为静态变量赋初始值，注意这里的初始值是JVM默认初始值，是固定的，不是咱们写代码时的那个初始值。这里有个比较容易混淆的概念。</p>
<p style=""></p>
<p style="">Java内存规范定义了方法区这种抽象概念。主流的JVM实现如HotSpot在JDK8之前使用永久代这种在堆中开辟专门空间的实现方式，JDK8之后使用元空间这种直接内存取代。</p>
<p style=""></p>
<p style="">HotSpot的实现：类的元信息、常量池、静态变量等都存在在JDK8之前都存在在永久代这种方法区的具体实现中。JDK8之后，常量池、静态变量被从方法区移除，转移到了堆中。元信息这些依然保留在方法区，具体的存储方式改成了元空间。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">解析</span></strong></p>
<p style=""></p>
<p style="">解析是将符号引用替换为直接引用。</p>
<p style=""></p>
<p style=""><strong>静态解析</strong></p>
<p style=""></p>
<p style="">符号引用就是假如类A引用了类B，加载阶段是静态解析，这时候B还没有被放到JVM内存中，这时候A引用的只是代表B的符号，这是符号引用。</p>
<p style=""></p>
<p style="">直接引用就是类A在解析阶段发现自己引用了B，如果这个时候B还没被加载。就是直接触发B的类加载，之后B的符号引用会被替换成实际地址。这被称为直接引用。</p>
<p style=""></p>
<p style=""><strong>动态解析</strong></p>
<p style=""></p>
<p style="">本文类的生命周期部分引出了后期绑定这个概念。后期绑定其实就是动态解析。如果代码使用了多态。B是一个抽象类或者接口，A就不能知道究竟要用哪个来替换，只能等到实际发生调动时在进行实际地址的替换。这就是为什么有的解析发生在初始化之后。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">类加载的过程今天就讲这些。咱们来回顾一下类加载的五个阶段。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zsge.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">从JVM的角度看，加载的读取二进制流和初始化阶段，是开放了主导权给用户的。用户可以使用动态代理等手段选择是否这个阶段进行加载。还可以使用多态的手段选择是否在这个阶段进行初始化。而剩下的所有部分都是JVM内部完成的。</p>
<p style=""></p>
<p style="">此时你可以闭上眼睛回顾一下类加载的五个阶段，是不是不用死记硬背也能了然于胸了。</p>
<p style=""></p>
<p style=""><span style="font-size: 12px">内容参考：</span></p>
<p style=""><span style="font-size: 12px">https://www.bilibili.com/video/BV14U4y1L75q</span></p>
<p style=""><span style="font-size: 12px">原文链接：https://mp.weixin.qq.com/s/b8W5X6KnIaN1hfYxGhrGMA</span></p>]]></description><guid isPermaLink="false">/archives/shen-ru-li-jie-javalei-jia-zai-ji-zhi-zai-ye-bu-yong-si-ji-ying-bei-liao</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Tue, 24 Sep 2024 01:26:12 GMT</pubDate></item><item><title><![CDATA[进行一个sql优化需要做哪些准备？-Metadata Lock和online DDL]]></title><link>https://wiki.dgfygk.com/archives/jin-xing-yi-ge-sqlyou-hua-xu-yao-zuo-na-xie-zhun-bei--metadata-lockhe-online-ddl</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%BF%9B%E8%A1%8C%E4%B8%80%E4%B8%AAsql%E4%BC%98%E5%8C%96%E9%9C%80%E8%A6%81%E5%81%9A%E5%93%AA%E4%BA%9B%E5%87%86%E5%A4%87%EF%BC%9F-Metadata%20Lock%E5%92%8Conline%20DDL&amp;url=/archives/jin-xing-yi-ge-sqlyou-hua-xu-yao-zuo-na-xie-zhun-bei--metadata-lockhe-online-ddl" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">优化的必要性和急迫性分析</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">真的有必要优化吗？</span></strong></p>
<h1 style="" id="%E5%9C%A8%E3%80%8A%E5%AE%B9%E6%98%93%E5%BC%95%E8%B5%B7%E9%9B%AA%E5%B4%A9%E7%9A%84%E4%B8%A4%E4%B8%AA%E5%A4%84%E7%90%86%E3%80%8B%E9%87%8C%EF%BC%8C%E6%88%91%E6%8F%90%E5%88%B0%E4%B8%80%E4%B8%AA%E6%85%A2%E6%9F%A5%E8%AF%A2%E9%97%AE%E9%A2%98%E3%80%82%E5%9B%A0%E4%B8%BA%E4%B8%80%E4%B8%AA%E6%85%A2%E6%9F%A5%E8%AF%A2%E9%80%A0%E6%88%90%E4%BA%86%E8%81%94%E8%B0%83%E7%8E%AF%E5%A2%83%E7%9A%84%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8C%82%E6%8E%89%EF%BC%8C%E6%88%91%E4%BB%AC%E8%BF%9B%E8%A1%8C%E4%BA%86%E7%82%B9%E7%BA%BF%E9%9D%A2%E7%9A%84%E6%A2%B3%E7%90%86%EF%BC%8C%E6%8E%92%E6%9F%A5%E4%BA%86%E6%89%80%E6%9C%89%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%B8%E5%85%B3%E7%9A%84%E9%9A%90%E6%82%A3%EF%BC%8C%E7%A0%94%E7%A9%B6%E4%BA%86%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%E3%80%82">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485779&amp;idx=1&amp;sn=95a8b630d96dc0e76436f7b1c3d5bd51&amp;chksm=fafde3fdcd8a6aebaf8128b65255d8d38bdd695a02c2c218e5435e29073726b1c2e5525a9355&amp;scene=21#wechat_redirect" target="_blank">《容易引起雪崩的两个处理》</a>里，我提到一个慢查询问题。因为一个慢查询造成了联调环境的数据库挂掉，我们进行了点线面的梳理，排查了所有数据库相关的隐患，研究了优化方案。</h1>
<h1 style="" id="%E5%9C%A8%E7%A0%94%E7%A9%B6%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%E8%BF%87%E7%A8%8B%E4%B8%AD%EF%BC%8C%E9%87%8D%E7%82%B9%E8%AE%A8%E8%AE%BA%E7%9A%84%E6%98%AF%E5%93%AA%E4%BA%9B%E8%AF%A5%E5%81%9A%E3%80%81%E5%93%AA%E4%BA%9B%E4%B8%8D%E5%81%9A%E3%80%82%E5%9B%A0%E4%B8%BA%E5%9C%A8%E8%BF%9B%E8%A1%8C%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E7%AB%8B%E9%A1%B9%E7%9A%84%E6%96%B0%E9%A1%B9%E7%9B%AE%E6%97%B6%EF%BC%8C%E8%87%AA%E7%84%B6%E6%98%AF%E5%9C%A8%E5%B7%A5%E6%9C%9F%E5%85%81%E8%AE%B8%E7%9A%84%E8%8C%83%E5%9B%B4%E5%86%85%EF%BC%8C%E5%B0%BD%E9%87%8F%E9%87%87%E7%94%A8%E9%AB%98%E6%95%88%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9B%E4%BD%86%E6%98%AF%E4%B8%80%E6%97%A6%E5%B7%B2%E7%BB%8F%E4%B8%8A%E7%BA%BF%E5%88%B0%E7%94%9F%E4%BA%A7%EF%BC%8C%E5%86%8D%E8%BF%9B%E8%A1%8C%E4%BC%98%E5%8C%96%E9%9C%80%E8%A6%81%E8%80%83%E8%99%91%E9%A3%8E%E9%99%A9%E7%9A%84%E4%BB%A3%E4%BB%B7%E5%92%8C%E6%94%B6%E7%9B%8A%E6%98%AF%E5%90%A6%E6%88%90%E6%AD%A3%E6%AF%94%E3%80%82">在研究优化方案过程中，重点讨论的是哪些该做、哪些不做。因为在进行一个已经立项的新项目时，自然是在工期允许的范围内，尽量采用高效的方法；但是一旦已经上线到生产，再进行优化需要考虑风险的代价和收益是否成正比。</h1>
<p style="">举个具体的例子，做优化方案时，我们整理了所有的数据库表、DML语句。密密麻麻的Excel啊，辛苦了认真细致的开发小哥哥。</p>
<p style="">但是最终在所有未加索引和索引不合理的相关表中，真正要优化的范围我们限定到了存在慢查询问题的表。而且，有的表可以通过数据定时清理解决的，也排除在了优化索引范围外。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-arxn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">所以，最后经过好几天的梳理和讨论，近百张(分表算1张)表中，我们定下来需要优化的表有两张。</p>
<p style="">那这两张表的加索引的最坏影响是什么？不加索引的最坏影响是什么？</p>
<p style="">首先一个经验：后加的索引没有十足的把握不要加唯一索引，否则数据会被合并，很多数据会消失！普通索引最坏的影响是锁表？锁表不可怕，可怕的是把表写坏了，不能用了。这时候就需要DBA操作前数据表备份，出现问题立即恢复。恢复时间段内的业务影响要评估清楚。</p>
<p style="">不加索引的最坏影响呢。慢查询会把数据库搞挂，一个数据库放多个团队的数据库还可能把多个团队的数据库搞挂！所以加索引要比不加索引更能确保生产的稳定。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">真的有必要现在优化吗？</span></strong></p>
<p style="">这两张当然最好合并进行一次加索引的变更。因为首先这两张表的读写不是很频繁，其实的风险不是非常高。这里要注意，敬畏生产。对每次生产都要谨慎谨慎再谨慎。</p>
<p style="">只是没有明确的等级，我才说不是非常高。意思是相比较多次变更可能引起的人工出错率增加，权衡之后合并投产风险更低。</p>
<p style="">方案定好了之后，领导直接给开发小哥哥说让他在一个关键期之前尽快投产。我知道之后跟开发小哥哥说你别着急，我找领导说一下，这件事关键期之后才做。</p>
<p style="">领导说要尽快做的依据我完全能理解：“联调环境都被打挂了，风险很高啊。”我找到领导，告诉他出现问题的数据表在生产环境，数据量只有联调环境的1/20。而且联调环境的数据库机器配置比生产环境低不止一个等级。注意，这些原因是要说的，但是最重要的是要有数据支撑，所以我还告诉领导这个慢查询在生产环境的时间耗时。领导听了我的汇报评估确实可以放到关键期之后做。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hozv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这里还想补充一句，作为一个小团队的直接负责人，在风险评估上起的作用非常关键。开发小哥哥本身由于经验等方面的原因，需要别人把关。上级领导没有时间把事情了解的那么透彻，不然自己的价值在哪里。领导依赖自己的汇报，所以自己的决策非常重要。这里方法论没有，就是要多想，翻来覆去的想。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">验证</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">涉及的数据库知识</span></strong></p>
<p style=""><strong>AC-NL-RO事务</strong></p>
<p style="">先给大家普及一个认知。请大家想一个问题：咱们普通的select查询语句会用到事务吗？</p>
<p style="">事务包括两类，显式事务和AC-NL-RO（auto-commit non-locking read-only）事务。</p>
<ul>
 <li>
  <p style="">显式事务包括两类</p>
  <ul>
   <li>
    <p style="">关闭AutoCommit下的操作</p>
   </li>
   <li>
    <p style="">以begin或start transaction开始的操作</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">AC-NL-RO可理解为AutoCommit开启下的select操作</p>
 </li>
</ul>
<p style="">这问题的答案就很清楚了：普通的select查询语句也使用了AC-NL-RO事务。</p>
<h3 style="" id="metadata-lock"><strong><span style="font-size: 17px">Metadata Lock</span></strong></h3>
<p style="">Metadata Lock源起于一个bug，MySQL官方文档链接：http://bugs.mysql.com/bug.php?id=989</p>
<p style="">这个bug大致意思是说：当一个会话在主库执行DML操作还没提交时，另一个会话对同一个对象执行了DDL操作如drop table，而由于MySQL的binlog是基于事务提交的先后顺序进行记录的，因此在从库上应用时，就出现了先drop table，然后再向table中insert的情况，导致从库应用出错。</p>
<p style="">因此，MySQL在5.5.3版本后引入了Metadata lock，只有在事务结束后才会释放Metadata lock，因此在事务提交或回滚前，是无法进行DDL操作的。</p>
<h1 style="text-align: start; " id="online-ddl"><strong><span style="font-size: 17px">online DDL</span></strong></h1>
<p style="">online DDL是在mysql5.6版本后加入的特性，用于支持DDL执行期间DML语句的并行操作，提高数据库的吞吐量。</p>
<p style="">常用的有三种类型：</p>
<ul>
 <li>
  <p style="">copy（ALGORITHM=COPY）这部分是offline的，在DDL执行期间其他DML不能并行，也是5.6版本前的DDL执行方法。其间生成临时表（server层的操作支持所有引擎），用于写入原表修改过的数据，同时在原表路径下会生成临时表的.frm和.ibd文件。在innodb中不支持使用inplace的操作都会自动使用copy方式执行，而MyISAM表只能使用copy方式。</p>
 </li>
 <li>
  <p style="">inplace（ALGORITHM=INPLACE）所有操作在innodb引擎层完成，不需要经过临时表的中转。除上图两种特殊索引创建外，其他以inplace方式执行的操作都是online的，执行期间其他DML操作可以并行，其中又以是否重建表又分为两个部分rebuild和no-rebuild。</p>
 </li>
 <li>
  <p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">INSTANT，只需修改数据字典中的元数据，无需拷贝数据也无需重建整表，同样，也无需加排他MDL锁，原表数据也不受影响。整个DDL过程几乎是瞬间完成的，也不会阻塞DML。这个新特性是8.0.12引入</span>的。</p>
 </li>
</ul>
<p style="">现在主流的mysql版本是5.6或者5.7。这两个版本如何区分DDL语句是使用了copy方式还是inplace方式呢？</p>
<p style="">只需要查看语句执行完成输出结果中的 X rows affected，如果X为0则是inplace（online）方式，如果不为0则是copy（offline）方式。</p>
<p style=""><strong>rebuild&amp;no-rebuild</strong></p>
<p style="">online ddl主要包括3个阶段，prepare阶段，ddl执行阶段，commit阶段，rebuild方式比no-rebuild方式实质多了一个ddl执行阶段，prepare阶段和commit阶段类似。</p>
<p style="">Prepare阶段：</p>
<ol>
 <li>
  <p style="">创建新的临时frm文件</p>
 </li>
 <li>
  <p style="">持有EXCLUSIVE-MDL锁，禁止读写</p>
 </li>
 <li>
  <p style="">根据alter类型，确定执行方式(copy,online-rebuild,online-norebuild)</p>
 </li>
 <li>
  <p style="">更新数据字典的内存对象</p>
 </li>
 <li>
  <p style="">分配row_log对象记录增量</p>
 </li>
 <li>
  <p style="">生成新的临时ibd文件</p>
 </li>
</ol>
<p style="">ddl执行阶段：</p>
<ol>
 <li>
  <p style="">降级EXCLUSIVE-MDL锁，允许读写</p>
 </li>
 <li>
  <p style="">扫描old_table的聚集索引每一条记录rec</p>
 </li>
 <li>
  <p style="">遍历新表的聚集索引和二级索引，逐一处理</p>
 </li>
 <li>
  <p style="">根据rec构造对应的索引项</p>
 </li>
 <li>
  <p style="">将构造索引项插入sort_buffer块</p>
 </li>
 <li>
  <p style="">将sort_buffer块插入新的索引</p>
 </li>
 <li>
  <p style="">处理ddl执行过程中产生的增量(仅rebuild类型需要)</p>
 </li>
</ol>
<p style="">commit阶段</p>
<ol>
 <li>
  <p style="">升级到EXCLUSIVE-MDL锁，禁止读写</p>
 </li>
 <li>
  <p style="">重做最后row_log中最后一部分增量</p>
 </li>
 <li>
  <p style="">更新innodb的数据字典表</p>
 </li>
 <li>
  <p style="">提交事务(刷事务的redo日志)</p>
 </li>
 <li>
  <p style="">修改统计信息</p>
 </li>
 <li>
  <p style="">rename临时idb文件，frm文件</p>
 </li>
 <li>
  <p style="">变更完成</p>
 </li>
</ol>
<p style=""><strong>建索引属于哪种模式</strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vzdv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">验证内容</span></strong></p>
<ul>
 <li>
  <p style="">验证加索引前后数据表中的数据没有任何变化。</p>
 </li>
 <li>
  <p style="">验证各种索引的组合效果验证：单独索引、联合索引、联合索引谁在前谁在后。</p>
 </li>
 <li>
  <p style="">验证加索引过程的耗时和可能的影响。上面讲的数据库知识其实就是为了说明这个问题：创建索引操作需要rebuild，耗时长。并且select操作都可能会影响这个操作，增加耗时，验证时这个因素一定要考虑。</p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">评估影响</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">操作本身的影响</span></strong></p>
<ul>
 <li>
  <p style="">DML（数据操作语言）影响，在表上添加s索引会直接影响写操作性能（因为添加记录的同时还有创建相应记录的索引，这也是要耗资源的。）。</p>
 </li>
 <li>
  <p style="">DDL（数据定义语言）影响，随着表大小的不断增加，对性能的影响也会不断增加。比如：ALTER语句会耗费更多的时间。</p>
 </li>
 <li>
  <p style="">磁盘空间的影响，往往在添完一个索引后表占用的空间大小会成倍的增加。</p>
 </li>
</ul>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">对相关方的影响</span></strong></p>
<p style="">评估影响的业务，通知影响方，并确定需要避开的时间段。避开的原因请详细阅读上面提到的数据库知识。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">优化有风险，立项需谨慎。</p>]]></description><guid isPermaLink="false">/archives/jin-xing-yi-ge-sqlyou-hua-xu-yao-zuo-na-xie-zhun-bei--metadata-lockhe-online-ddl</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 07:27:01 GMT</pubDate></item><item><title><![CDATA[ThreadLocal&MDC内存泄漏问题]]></title><link>https://wiki.dgfygk.com/archives/threadlocal-mdcnei-cun-xie-lou-wen-ti</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=ThreadLocal%26MDC%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E9%97%AE%E9%A2%98&amp;url=/archives/threadlocal-mdcnei-cun-xie-lou-wen-ti" width="1" height="1" alt="" style="opacity:0;">
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483678&amp;idx=1&amp;sn=2091e0f6b52cd859284fb14baec7565c&amp;chksm=fafdebb0cd8a62a632c71b43bfd92c387520ca9a4c2a7038fab5c667efa935352e7793a6fe5d&amp;scene=21#wechat_redirect" target="_blank">《大话高可用》</a>里，之前的老大有过总结，高可用就是：<span fontsize="" color="">别人死我们不死，自己不作死，不被别人搞死。</span></p>
<p style=""></p>
<p style="">这段时间，网上都在传Log4j2的lookup远程执行代码漏洞。这个漏洞要想造成危害，基本都是被别人搞死的。因为只有url链接或者页面输入了可执行脚本才会触发。</p>
<p style=""></p>
<p style="">最近在重构<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484045&amp;idx=2&amp;sn=25cf3c0399cf92edbd57e207f18afea3&amp;chksm=fafde823cd8a61354d12e2355829b78b0a84b422ec4d3569bbbff711cb9b1cb3315eafaef9ac&amp;scene=21#wechat_redirect" target="_blank">《简明日志规范》</a>，就是重构我自己之前开源的一个统一日志的组件。对org.slf4j.MDC不放心，怕引发内存泄露等线上长时间运行才产生的问题。要是有这个问题，比Log4j2的漏洞更不可原谅。因为这纯属自己作死。所以做了一个小研究。</p>
<p style=""></p>
<p style="">MDC（Mapped Diagnostic Context，映射调试上下文）是日志系统提供的一种方便在多线程条件下记录日志的功能。可以理解为一个存储容器，原理是将信息存储在了ThreadLocal里。ThreadLocal和线程绑定。程序里用的都是线程池，理论上应该不会出现无限膨胀的情况。总归还是不放心。</p>
<p style=""></p>
<p style="">利用周末时间进行了源码研究和测试，终于放心了。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">验证</span></strong></p>
<p style=""></p>
<p style="">首先读了源码，从MDC跟到ThreadLocal，反复的找，没有找到存储ThreadLocal相关信息的容器。因为要是有，且释放做的不好的话是会无限膨胀的。先进行了粗浅的理论研究之后。测试验证一下。</p>
<p style=""></p>
<p style="">验证代码如下，实际上MDC.put的内容我放了1万字，便于确认效果。</p>
<pre><code>public static void main(String[] args) {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 200, 1, TimeUnit.DAYS, new LinkedTransferQueue&lt;&gt;());
    for (long i = 0; i &lt; Long.MAX_VALUE; i++) {
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                   MDC.put("k", Thread.currentThread().getName());
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        });
    }
}</code></pre>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">场景1</span></strong></p>
<p style="">将最大线程数调到1W，内存增长根本停不下来，我的电脑明显心有余力不足，没有达到效果。<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485666&amp;idx=1&amp;sn=281374a2c8b286fe9c993e2f4698a261&amp;chksm=fafde24ccd8a6b5ad998f2e893045bb387b3e26b9c7e20802b12b8a2fb2f32dca3db1c2fcf51&amp;scene=21#wechat_redirect" target="_blank">《Java线程池总结》</a>有对线程池参数的讲解，这里不赘述。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">场景2</span></strong></p>
<p style="">核心线程池数10，最大线程数20，在线程执行代码的结尾增加MDC.clear()。因为大多数的最佳实践是结束时都要加，避免线程复用造成的取出数据不准确。日志不准确是个问题，但是没有内存泄露这么可怕。这里加和不加做对比，验证如果忘记加会不会对内存造成压力。</p>
<p style="">结果运行的进程占2095.1M时达到稳定。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pdyn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">场景3</span></strong></p>
<p style="">上面场景中去掉MDC.clear()。</p>
<p style="">结果运行的进程占2045.8M时达到稳定。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-haxz.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">场景4</span></strong></p>
<p style="">核心线程池数1，最大线程数2，在线程执行代码的结尾增加MDC.clear()。</p>
<p style="">结果运行的进程占2116.7M时达到稳定。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zjci.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">场景5</span></strong></p>
<p style="">上面场景中去掉MDC.clear()。</p>
<p style="">结果运行的进程占2109.3M时达到稳定。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xxrq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">场景6</span></strong></p>
<p style="">上面场景中修改核心线程池数2，最大线程数4。</p>
<p style="">结果运行的进程占2120.7M时达到稳定。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lgwn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">试验结论</span></strong></p>
<p style="">无论是否执行MDC.clear()。最终内存占用都会趋于稳定，无内存泄露。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">理论分析</span></strong></p>
<p style=""></p>
<p style="">虽然验证了，还是不放心。为什么呢？做了这么久的面试官，我深知ThreadLocal内存漏洞问题是一道经典面试题啊。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-paao.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">
 <br>
 MDC是基于ThreadLocal做的，ThreadLocal泄露了，MDC可以幸免吗？测试没有泄露是不是测试姿势不对？
</p>
<p style=""></p>
<p style="">为了知其然而知其所以然，从ThreadLocal内存泄露的具体案例出发，更加仔细的研究了源码。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">ThreadLocal内存泄露问题</span></strong></p>
<pre><code>下面这段测试代码在循环中会出现膨胀，循环过多会导致内存泄露。public void test() throws Exception {for (int i = 0; i &lt; 100; i++) {
        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set("t1"+i);
        Thread t = Thread.currentThread();
        log.info(t);
    }
}</code></pre>
<p style=""></p>
<p style="">原因从debug现象来看，随着循环数i的增长，t也就是当前线程的threadLocals数随之增长。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zvxj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">跟踪threadLocal.set("t1"+i)代码到set方法的源码：</p>
<pre><code>public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}解释一下：1&gt;threadLocal获取到当前线程，获取当前线程的ThreadLocalMap对象。2&gt;如果map存在，则将当前threadLocal对象作为key设置value="t1"+i3&gt;如果map不存在，则新建一个map，并将当前threadLocal对象作为key设置value="t1"+i。就是说一个线程唯一对应一个ThreadLocalMap对象。threadLocal对象是ThreadLocalMap这个map中的一个key。将上面的test测试类用伪代码调整成正常的顺序大家应该就能理解了：public void test() throws Exception {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    for (int i = 0; i &lt; 100; i++) {
        ThreadLocal threadLocal = new ThreadLocal();
        map.set(threadLocal,"t1"+i);
        log.info(t);
    }
}</code></pre>
<p style="">所以threadLocal 的引用一直在当前线程的ThreadLocalMap手里，并不是循环下一个这个引用就不可达了。所以会一直创建新的保存老的，直到内存溢出，如下面运行截图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kyzm.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">简而言之: threadLocal使用不当，可能会内存泄露。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">MDC为什么无内存泄露问题</span></strong></p>
<p style=""></p>
<p style="">跟MDC.put("k", Thread.currentThread().getName())到源码发现其实put的真正承载容器是static MDCAdapter mdcAdapter。这是一个静态类，在MDC类源码的底部有mdcAdapter的初始化过程static静态块，表明了整个JVM只有一份。</p>
<p style=""></p>
<p style="">MDCAdapter 是一个接口，具体的实现因为这是slf4j门面中的。实现会有所不同，但logback等主流日志实现都是new一个ThreadLocal，将Map容器绑定到ThreadLocal中。代码片段感受一下：</p>
<pre><code>public class LogbackMDCAdapter implements MDCAdapter {
    final ThreadLocal&lt;Map&lt;String, String&gt;&gt; copyOnThreadLocal = new ThreadLocal();</code></pre>
<pre><code>public void put(String key, String val) throws IllegalArgumentException {
    if (key == null) {
        throw new IllegalArgumentException("key cannot be null");
    } else {
        Map&lt;String, String&gt; oldMap = (Map)this.copyOnThreadLocal.get();
        Integer lastOp = this.getAndSetLastOperation(1);
        if (!this.wasLastOpReadOrNull(lastOp) &amp;&amp; oldMap != null) {
            oldMap.put(key, val);
        } else {
            Map&lt;String, String&gt; newMap = this.duplicateAndInsertNewMap(oldMap);
            newMap.put(key, val);
        }

    }
}</code></pre>
<p style="">简而言之：一个线程顶多会创建一个ThreadLocal，所以不会内存泄露。</p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">
   <br>
  </span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">1、不要假定，要证明。证明之后要理解，逻辑自洽。</p>
<p style="">2、<span fontsize="" color="">别人死我们不死，自己不作死，不被别人搞死。</span></p>]]></description><guid isPermaLink="false">/archives/threadlocal-mdcnei-cun-xie-lou-wen-ti</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 07:25:49 GMT</pubDate></item><item><title><![CDATA[达到什么标准就可以上线了？]]></title><link>https://wiki.dgfygk.com/archives/da-dao-shi-me-biao-zhun-jiu-ke-yi-shang-xian-liao</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%BE%BE%E5%88%B0%E4%BB%80%E4%B9%88%E6%A0%87%E5%87%86%E5%B0%B1%E5%8F%AF%E4%BB%A5%E4%B8%8A%E7%BA%BF%E4%BA%86%EF%BC%9F&amp;url=/archives/da-dao-shi-me-biao-zhun-jiu-ke-yi-shang-xian-liao" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 24px">背景</span></strong></p>
<p style="">在<a href="https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484300&amp;idx=2&amp;sn=40b7b824814af1999ab1fcb823c3a811&amp;chksm=fafde922cd8a6034a7eeabef85ed886a15cbd433ca3a72175405a9b8eb1ec0f180daa5b10f2f&amp;token=1623804271&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank">《程序媛的人生观》</a>这篇文章中，一些朋友提出了自己的疑问：“看起来静儿的发版上线很不规范，为什么一个大公司会允许这样的事情呢？”这是个很好的问题，值得我好好总结分享一下。</p>
<p style=""></p>
<p style="">在考虑上线标准之前，先考虑这么一个问题，你处于哪个通道？</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 24px">通道</span></strong></p>
<p style=""><span style="font-size: 17px">前端通道、系统通道和后台通道的发版上线流程差别会很大。</span></p>
<p style=""><strong><span style="font-size: 17px">前端通道：</span></strong><span style="font-size: 17px">用户体验、视觉效果会占很大比重，一般来说需要依赖专业人员进行人工测试。</span></p>
<p style=""><strong><span style="font-size: 17px">系统通道：</span></strong><span style="font-size: 17px">产品流程的理解和把控是其中的一个关键点。自动化测试基础上还需要人工测试进一步验证。</span></p>
<p style=""><strong><span style="font-size: 17px">后台通道：</span></strong><span style="font-size: 17px">也是静儿所在的通道。业界有一个比较流行的理念：“开发人员应该测试自己的代码。”</span></p>
<p style=""><span style="font-size: 17px">我们团队中经常在说的一句话叫：“你不可能既当运动员又当裁判。”那怎么来解决这个冲突呢？</span></p>
<p style=""><span style="font-size: 17px">自动化测试。基础架构这边有专门的QA。他们大部分时间都在收集测试用例，开发自动化测试工具。</span></p>
<p style="">开发人员需要自己写单体测试，单体测试是在设计阶段，对功能进行建模阶段就写好的。所以对一个后台开发来说，完全可以在几分钟之内完成一套完整的上线前验收。</p>
<p style=""></p>
<p style="">能否在几分钟之内就可以进行一套完整的上线前验收也和公司、部门或者团队的策略有关系。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 24px">策略</span></strong></p>
<p style="">不知道有的朋友有没有注意到这个问题：有的公司虽然是大公司，但是门槛并不高。面试的时候着重考察面试者的理解力、沟通能力。如果背景不错，不会太卡技术这一块。当然，一般来讲这种公司给的待遇也比较一般。</p>
<p style="">这并不是说这个公司不够好，相反，正是因为公司有非常完善的流程把控，所以才摆脱了对成员个人能力的重度依赖。然而，一般来讲，流程是用来保证底线的。一个注重成员成长的团队希望的是能够激发成员的上限。</p>
<p style="">所以静儿所在的团队有这么一种策略：如果成员对自己的流程把握足够好，不出问题，上级会逐渐放宽对成员的监督。如果把控的不好，就会天天被盯流程。</p>
<p style="">这样的好处：做的好的成员的创造性可以充分得到发挥。对于上级自身，也可以抽身出来cover更大的事情。</p>
<p style=""></p>
<p style="">在<a href="https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484300&amp;idx=2&amp;sn=40b7b824814af1999ab1fcb823c3a811&amp;chksm=fafde922cd8a6034a7eeabef85ed886a15cbd433ca3a72175405a9b8eb1ec0f180daa5b10f2f&amp;token=1623804271&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank">《程序媛的人生观》</a><span fontsize="" color="">这篇文章中，静儿用了一个很不专业的词：「临时决定」。从另一个角度来看这个问题，什么事情是可以临时决定的？</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">定性</span></strong></p>
<p style="">静儿之所以可以<span fontsize="" color="">临时决定给序列化和反序列化接口新添加一个实现。这里面包含了两点信息：</span></p>
<p style=""><span fontsize="" color="">1.这是添加实现，最最坏的情况下，实现出问题了，是可以开关切换切换回原来的实现的。</span></p>
<p style=""><span fontsize="" color="">2.这不改变完成的功能，就是说这是一个重构。</span></p>
<p style=""><span fontsize="" color="">在开发的时候「忍不住觉得之前的代码写的太烂了需要重构」是一个工程师的基本素养。如果回过头看觉得原来的代码写的不错，这是一个信号：同学，你该看书学习了。</span></p>
<p style=""><span fontsize="" color="">所以，对于新功能，静儿团队中是需要开会一起做DEMO验收演示的。但是对于重构，开发人员完全有权利自己决定何时重构。因为功能不变，之前已有的自动化测试流程无需任何更改，完全可以覆盖重构后的场景。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 24px">背后的技术支撑</span></strong></p>
<p style=""><span fontsize="" color="">「技术强就是任性。」这句话不错的，但是技术强不是指静儿个人的技术能力。而是我司的整套基础设施。这里就不谈我们强大的DevOps这些东西。我们就说</span><a href="https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484300&amp;idx=2&amp;sn=40b7b824814af1999ab1fcb823c3a811&amp;chksm=fafde922cd8a6034a7eeabef85ed886a15cbd433ca3a72175405a9b8eb1ec0f180daa5b10f2f&amp;token=1623804271&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="">《程序媛的人生观》</span></a><span fontsize="" color="">这篇文章中，静儿提到终于用了整个晚上，到凌晨5点完成上线，静儿最后把线上到哪里了呢？</span></p>
<p style=""><span fontsize="" color="">上到了线下环境。这里线下环境是什么概念呢。静儿所在的团队负责的是容器的生命周期管理。容器有给广大用户直接访问的线上环境。还有一个环境，业务部门是我们的用户，他们用来测试、开发的容器，对我们来说是正式环境。如果线下环境出了问题，会影响我们的用户满意度。</span></p>
<p style=""><span fontsize="" color="">一般来说，我们的代码想上到线上环境，至少要经常两天的线下环境观察。这个观察是什么意思呢？</span></p>
<p style=""><span fontsize="" color="">发版前写好发版checklist，在一定范围内周知说我要发版了，如果有问题请及时联系我。</span></p>
<p style=""><span fontsize="" color="">然后采用灰度发布，每发版一组机器就要进行整个流程的检验，如果有问题最快的解决方法是将有问题的机器禁用。每组发版之间有强制的时间间隔，在间隔内不能发版下一组。</span></p>
<p style=""><span fontsize="" color="">发布完成后QA那边的自动化工具又一次起了作用，它会自动模拟用户完成所有分支的操作。然后给出报告。</span></p>
<p style=""><span fontsize="" color="">各方面都OK之后，周知说发版完成符合预期。第二天一大早起来业务巡查，看看是否一切正常。</span></p>
<p style=""><span fontsize="" color="">一天的午高峰和晚高峰监控数据是一定要看的。CAT、OCTO、业务大盘、监控大盘……</span></p>
<p style=""><span fontsize="" color="">最重要的是及时看看是否有业务反馈问题。这都是在线下环境做的。</span></p>
<p style=""><a href="https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484300&amp;idx=2&amp;sn=40b7b824814af1999ab1fcb823c3a811&amp;chksm=fafde922cd8a6034a7eeabef85ed886a15cbd433ca3a72175405a9b8eb1ec0f180daa5b10f2f&amp;token=1623804271&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="">《程序媛的人生观》</span></a><span fontsize="" color="">这篇文章中提到的那次发版，由于质量好。上周四发版线下，上到线上环境是这周一晚上9点后开始操作。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">从「技术好就是任性」这个再延伸一下，为什么</span><a href="https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484300&amp;idx=2&amp;sn=40b7b824814af1999ab1fcb823c3a811&amp;chksm=fafde922cd8a6034a7eeabef85ed886a15cbd433ca3a72175405a9b8eb1ec0f180daa5b10f2f&amp;token=1623804271&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="">《程序媛的人生观》</span></a><span fontsize="" color="">这篇文章中看起来编码很随意？这是因为设计架构做的好。临时决定给序列化和反序列化接口新添加一个实现。实现加在哪里了呢？每个产品至少有两个服务组成：核心服务和非核心服务。我们的MQ都是用来数据预处理用的，都是在非核心链路上。出问题了，半夜临时挂一下，就算有和静儿一样的程序员在半夜扩容测试机器。服务产生的最坏影响是数据反应有延迟😀</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><strong><span fontsize="" color="">总结</span></strong></p>
<p style=""><span fontsize="" color="">昂贵的工具不一定能制作出更好的设计。--《程序员修炼之道》</span></p>]]></description><guid isPermaLink="false">/archives/da-dao-shi-me-biao-zhun-jiu-ke-yi-shang-xian-liao</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 06:26:39 GMT</pubDate></item><item><title><![CDATA[系统跨年的那些事儿]]></title><link>https://wiki.dgfygk.com/archives/xi-tong-kua-nian-de-na-xie-shi-er</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%B3%BB%E7%BB%9F%E8%B7%A8%E5%B9%B4%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF&amp;url=/archives/xi-tong-kua-nian-de-na-xie-shi-er" width="1" height="1" alt="" style="opacity:0;">
<p style="">大家过年好！</p>
<p style=""></p>
<p style="">跨年夜除了好好享受假期，系统上从技术上到业务上，都有值得关注的事儿。一年才触发一次，频率低，风险高。更需谨慎。</p>
<p style=""></p>
<p style="">由于今年是我的本命年。本命年犯太岁。“太岁当头坐 ，无喜必有祸”。本命年顺的话，一顺百顺，鸿运当头，势不可挡。所以今天的标题全用大红色<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F2tk5ianItRl8k65gSmaC5C3CHGODq8IYDRTt0gFDPpXTe7HM08ZMpVDsEnG66RFmbfcRv9Eh2OthhLEoCRgN8yg%2F640%3Fwx_fmt%3Dpng%26tp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(255, 41, 65)">经典跨年Bug</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vvpw.png&amp;size=m" width="459px" height="187px" style="display: inline-block"></p>
<p style="">在2021年的最后两分钟，我怀着忐忑和期待的心情迎接着本命年。并匆忙写下下面的代码：</p>
<p style=""></p>
<pre><code>import cn.hutool.core.date.DateTime;</code></pre>
<pre><code>public void test() throws Exception {
    System.out.println(new DateTime().toString("YYYY-MM-dd HH:MM:SS"));
}</code></pre>
<p style="text-align: center; "></p>
<p style="">运行结果：</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">2022</span></strong>-12-31 23:<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">12:651</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">
   <br>
  </span></strong></p>
<p style="">很多朋友知道我侧重于稳定性方面的研究，但是最近提这个词提的少了。在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485367&amp;idx=1&amp;sn=7dcbc8600f3c528b9726974a9cea8ae4&amp;chksm=fafded19cd8a640f225891a88bd9946f7f52a3633329706bd09025c24b7a3941cd1326cc0a75&amp;scene=21#wechat_redirect" target="_blank">《架构-稳定性建设逻辑问题实战总结》</a>和<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485151&amp;idx=1&amp;sn=679de99669a0c7577706663ec59e2cdc&amp;chksm=fafdec71cd8a6567cc09ec14afbf8bbf9964abcdca3cf42d9934ca70fc4c81272b90bc0692d5&amp;scene=21#wechat_redirect" target="_blank">《稳定性的海因里希法则》</a>里我也总结过：稳定性更多的是意识，不是方法。</p>
<p style=""><span fontsize="" color="">稳定性方法本质就是两点：</span></p>
<h1 style="text-align: start; " id=""></h1>
<h1 style="text-align: start; " id="-1"></h1>
<ul>
 <li>
  <p style="">多问几个为什么</p>
 </li>
 <li>
  <p style="">多问几个如果发生了某某问题怎么办</p>
 </li>
</ul>
<p style="">这里本质上就是一个“为什么”的问题：所用的技术一定要深入理解。这个运行结果可能和一些人想象的不一致，本质上不是技术上的坑，而是对于日期格式化符号没有理解。</p>
<p style="">在Java的SimpleDateFormat类注释中有一张表，如下表所示：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cimt.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 30px">在规定中，y表示year，而Y表示Week Year！</p>
<p style="line-height: 30px"></p>
<p style="line-height: 30px"><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">什么是Week Year</span></strong></p>
<p style="line-height: 30px">我们知道，不同的国家对于一周的开始和结束的定义是不同的。如在中国，我们把星期一作为一周的第一天，而在美国，他们把星期日作为一周的第一天。因为不同人对于日期和时间的表示方法有不同的理解，于是，大家就共同制定了了一个国际规范：ISO 8601 。</p>
<p style="line-height: 30px"></p>
<p style="line-height: 30px">国际标准化组织的国际标准ISO 8601是日期和时间的表示方法，全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。</p>
<p style="line-height: 30px"></p>
<p style="line-height: 30px">在 ISO 8601中。week year是说某一周只能属于一个年份。<span style="font-size: 16px; color: rgb(34, 34, 34)">2021年12月31</span>日不能既属于今年的最后一个星期，又是明年的第一个星期。</p>
<p style="line-height: 30px"></p>
<p style="line-height: 30px">对于一年的第一个week year有以下四种等效说法：</p>
<p style="line-height: 30px">1，本年度第一个星期四所在的星期</p>
<p style="line-height: 30px">2，1月4日所在的星期</p>
<p style="line-height: 30px">3，本年度第一个至少有4天在同一星期内的星期</p>
<p style="line-height: 30px">4，星期一在去年12月29日至今年1月4日以内的星期</p>
<p style="line-height: 30px"></p>
<p style="line-height: 30px">按照这个规范，2021年12月30日是星期四，满足是新一年的第一个星期。那就是说打从2021年12月26日(星期日，<span style="font-size: 16px; color: rgb(34, 34, 34)">ISO 8601中</span>这是<span style="font-size: 16px; color: rgb(34, 34, 34)">一周的第一天</span>)开始，<span style="font-size: 16px; color: rgb(34, 34, 34)">week year</span>就已经是2022年了，新年已经到来了6天了！</p>
<p style="line-height: 30px"></p>
<p style="line-height: 30px">日期格式化符号里M大写的代表月份，小写m代表分钟，大写S代表毫秒，小写s代表秒。而一个<span style="font-size: 16px; color: rgb(34, 34, 34)">日期格式化符号</span>出现多少次就代表最少占多少位，不够的前面补0。</p>
<pre><code></code></pre>
<pre><code></code></pre>
<pre><code>public void test() throws Exception {
    System.out.println(new DateTime(DateTime.of("2021-12-26","yyyy-MM-dd"))
            .toString("yyyy-MM-dd HH:mm:sssss"));
}</code></pre>
<p style="">运行结果：</p>
<p style="">2021-12-26 00:00:00000</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(255, 41, 65)">业务经典问题</span></strong></p>
<p style="">2022年1月1日 0点10分。已经收到一些消息：网上反映QQ红包服务器炸了、QQ红包发不出去，拼多多红包提现慢、不能提现，微信红包提示使用人数过多等。说明大家对新年活动的参与度很高嘛。</p>
<p style="">其实在各个领域都有一些跨年的业务要特殊处理。最常见的一个例子就是“年终决算”。在各个银行，年终决算就是要把账结清。如果一边在做业务，一边在算账，很难算清楚。所以很多系统干脆暂停业务来进行年终决算。</p>
<p style="">年终决算并不是会计等领域的特定名词。早年的农村生产大队也要进行年终决算。办年终结算首先要列出6个指标来：</p>
<p style="">1，生产队总收入</p>
<p style="">2，生产队净收入</p>
<p style="">3，可分配收入</p>
<p style="">4，全年劳动日值，也就是说社员劳动一天折算的工分换成现金有多少钱</p>
<p style="">5，每户社员应当分配的数额</p>
<p style="">6，每户社员应当分配的实物和现金数量</p>
<p style="">是不是听起来相当专业，据说那时候每个生产队都会有一两个这方面的高手，精通珠算。让人不禁感叹：高手在民间。</p>]]></description><guid isPermaLink="false">/archives/xi-tong-kua-nian-de-na-xie-shi-er</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 06:05:23 GMT</pubDate></item><item><title><![CDATA[日常开发部署时要避免的两个打爆磁盘的问题]]></title><link>https://wiki.dgfygk.com/archives/ri-chang-kai-fa-bu-shu-shi-yao-bi-mian-de-liang-ge-da-bao-ci-pan-de-wen-ti</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%97%A5%E5%B8%B8%E5%BC%80%E5%8F%91%E9%83%A8%E7%BD%B2%E6%97%B6%E8%A6%81%E9%81%BF%E5%85%8D%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%89%93%E7%88%86%E7%A3%81%E7%9B%98%E7%9A%84%E9%97%AE%E9%A2%98&amp;url=/archives/ri-chang-kai-fa-bu-shu-shi-yao-bi-mian-de-liang-ge-da-bao-ci-pan-de-wen-ti" width="1" height="1" alt="" style="opacity:0;">
<p style="">日常开发部署时要避免的两个问题，处理不当，打爆磁盘，写COE，写 casestudy，怀疑职业选择。</p>
<p style="">今天来带大家排排雷。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">生产日志输出到控制台上</span></strong></p>
<p style=""></p>
<p style="">有的同学在开发的时候，自己本机调试，喜欢把日志打到控制台上。方式是在logback.xml或者是log4j2.xml文件中进行了以下的配置(其他语言类似)：</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;!--configuration：根节点，以下3个属性都是根节点的配置，一般用默认参数即可--&gt;&lt;!--scan（扫描）: 当此属性设置为true时，配置文件如果发生改变，将会被重新加载，默认值为true--&gt;&lt;!--scanPeriod:多长时间扫描一次，默认60毫秒扫描一次--&gt;&lt;!--debug：为true时打印出logback内部日志信息，实时查看logback运行状态，默认是false--&gt;&lt;!--日志分几个级别：debug（输出调试信息，一般情况应用程序不会使用debug日志）、info（输出主要信息）、error（输出错误信息）--&gt;&lt;configuration scan="true" scanPeriod="60 seconds" debug="false"&gt;&lt;!--property：子节点，定义一个变量--&gt;&lt;!--name:LOG_PATH日志定义在哪个文件下--&gt;&lt;property name="LOG_PATH" value="./logs"&gt;&lt;/property&gt;    &lt;!--appender:指定日志输出的目的地，目的地可以是控制台、文件等--&gt;    &lt;!--ConsoleAppender:控制台输出--&gt;    &lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;        &lt;!--layout:布局，输出的日志布局是什么，负责把事件转换成字符串，格式化的日志信息输出--&gt;        &lt;layout&gt;            &lt;!--%d：日期，代表几月几日输出的--&gt;            &lt;!--%thread：线程名，web工程都是多线程--&gt;            &lt;!--%5-level：代表输出级别；级别从左显示5个字符宽度--&gt;            &lt;!--%logger{35}：打出的类名最长35个字符，否则按照句点分割--&gt;            &lt;!--%msg：日志消息--&gt;            &lt;!--%n：换行符--&gt;            &lt;pattern&gt;                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n            &lt;/pattern&gt;        &lt;/layout&gt;    &lt;/appender&gt;    &lt;!--root：根节点设置日志级别，设置使用哪些根节点，使用的是appender节点name的值--&gt;    &lt;root level="info"&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;appender-ref&nbsp;ref="STDOUT"&gt;&lt;/appender-ref&gt;    &lt;/root&gt;&lt;/configuration&gt;</code></pre>
<p style="">
 <br>
 上面代码中ConsoleAppender会处理将日志打印到控制台上。
</p>
<p style=""></p>
<p style="">在本机这样调试没有问题，但是到了生产环境，一般需要应用日志与启动日志分离。像上面的配置，root下配置了所有日志都会打印到控制台，而启动日志没有做好日志压缩和滚动清理，就会打满磁盘。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">历史日志未清理</span></strong></p>
<p style=""></p>
<p style="">机器故障是日常运维中经常出现的问题。一般情况下，机器是可以被修复的，修复时间一般要几天到几个月不等。修复好了，手工一点的公司，会通知开发人员机器已恢复，可以将应用启用了。</p>
<p style=""></p>
<p style="">然而，应用启动后一段时间磁盘，竟然收到告警说磁盘马上要被打满了。这是怎么回事咧。以logback为例说说日志清理的原理。</p>
<pre><code>&lt;!-- 日志文件输出 --&gt;&lt;appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"&gt;    &lt;File&gt;${log.base}/${log.moduleName}.log    &lt;/File&gt;&lt;!-- 设置日志不超过${log.max.size}时的保存路径，注意如果 是web项目会保存到Tomcat的bin目录 下 --&gt;    &lt;!-- 滚动记录文件，先将日志记录到指定文件，当符合某个条件时，将日志记录到其他文件。--&gt;    &lt;rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"&gt;        &lt;FileNamePattern&gt;${log.base}/archive/${log.moduleName}_all_%d{yyyy-MM-dd}.%i.log.zip        &lt;/FileNamePattern&gt;        &lt;!-- 当天的日志大小 超过${log.max.size}时,压缩日志并保存 --&gt;        &lt;timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"&gt;            &lt;maxFileSize&gt;${log.max.size}&lt;/maxFileSize&gt;        &lt;/timeBasedFileNamingAndTriggeringPolicy&gt;        &lt;maxHistory&gt;${log.max.history}&lt;/maxHistory&gt;    &lt;/rollingPolicy&gt;    &lt;!-- 日志输出的文件的格式  --&gt;    &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;        &lt;pattern&gt;%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread]%logger{56}.%method\(\):%L -%msg%n&lt;/pattern&gt;    &lt;/layout&gt;&lt;/appender&gt;</code></pre>
<p style=""></p>
<p style="">其中如下定义了压缩和历史日志的保存策略，有两个比较重要的参数：maxFileSize，maxHistory</p>
<pre><code>&lt;timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"&gt;    &lt;maxFileSize&gt;${log.max.size}&lt;/maxFileSize&gt;&lt;/timeBasedFileNamingAndTriggeringPolicy&gt;&lt;maxHistory&gt;${log.max.history}&lt;/maxHistory&gt;</code></pre>
<pre><code>
</code></pre>
<h6 style="" id="%E5%85%88%E7%9C%8B%E4%B8%80%E4%B8%8B%E7%BB%A7%E6%89%BF%E5%85%B3%E7%B3%BB%E5%9B%BE">先看一下继承关系图</h6>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kkeu.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">继承关系</p>
<h6 style="" id="maxhistory%E9%BB%98%E8%AE%A4%E4%B8%BA0%EF%BC%8C%E7%94%B1%E6%BA%90%E7%A0%81%E5%8F%AF%E4%BB%A5%E7%9C%8B%E5%87%BA(timebasedrollingpolicy-start())%EF%BC%8C%E5%8F%AA%E6%9C%89%E4%B8%8D%E4%B8%BA0%E5%B9%B6%E4%B8%94%E6%B8%85%E7%90%86%E4%BB%BB%E5%8A%A1%E5%BC%80%E5%90%AF%E6%A0%87%E5%BF%97%E4%BD%8Dtrue%E6%97%B6%E4%BC%9A%E8%A7%A6%E5%8F%91%E6%B8%85%E7%90%86%E6%93%8D%E4%BD%9C">maxHistory默认为0，由源码可以看出(TimeBasedRollingPolicy-start())，只有不为0并且清理任务开启标志位true时会触发清理操作</h6>
<pre><code>if (maxHistory != INFINITE_HISTORY) {  archiveRemover = timeBasedFileNamingAndTriggeringPolicy.getArchiveRemover();  archiveRemover.setMaxHistory(maxHistory);  if(cleanHistoryOnStart) {    addInfo("Cleaning on start up");    archiveRemover.clean(new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime()));  }}</code></pre>
<p style=""></p>
<p style="">清理过程会先计算需要清理的时间范围</p>
<pre><code>int computeElapsedPeriodsSinceLastClean(long nowInMillis) {    long periodsElapsed = 0;    //如果尚未执行过清理操作，则默认清理除保留天数外64天以内的日志(由INACTIVITY_TOLERANCE_IN_MILLIS决定)    if (lastHeartBeat == UNINITIALIZED) {      addInfo("first clean up after appender initialization");      periodsElapsed = rc.periodsElapsed(nowInMillis, nowInMillis + INACTIVITY_TOLERANCE_IN_MILLIS);      if (periodsElapsed &gt; MAX_VALUE_FOR_INACTIVITY_PERIODS)        periodsElapsed = MAX_VALUE_FOR_INACTIVITY_PERIODS;    } else {      //如果已经执行过清理操作，则清理从上次到当前时间的需要清理的时间周期。      periodsElapsed = rc.periodsElapsed(lastHeartBeat, nowInMillis);      if (periodsElapsed &lt; 1) {        addWarn("Unexpected periodsElapsed value " + periodsElapsed);        periodsElapsed = 1;      }    }    return (int) periodsElapsed;}</code></pre>
<p style=""></p>
<h6 style="" id="%E5%88%A0%E9%99%A4%E6%97%B6%E4%BC%9A%E6%A0%B9%E6%8D%AE%E5%BD%93%E5%A4%A9%E6%97%B6%E9%97%B4%EF%BC%8C%E7%94%9F%E6%88%90%E4%B8%80%E4%B8%AA%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%2F*****%2Frsms_all_2018-04-09.(%5Cd%7B1%2C3%7D).log.zip%EF%BC%8C%E6%BB%A1%E8%B6%B3%E6%9D%A1%E4%BB%B6%E7%9A%84%E6%96%87%E4%BB%B6%E5%B0%B1%E4%BC%9A%E8%A2%AB%E6%B8%85%E7%90%86%E6%8E%89%E3%80%82">删除时会根据当天时间，生成一个正则表达式：/*****/rsms_all_2018-04-09.(\d{1,3}).log.zip，满足条件的文件就会被清理掉。</h6>
<h6 style="" id=""></h6>
<h6 style="" id="%E7%94%B1%E4%BB%A5%E4%B8%8A%E5%8F%AF%E4%BB%A5%E5%BE%97%E5%87%BA%E4%B8%A4%E4%B8%AA%E7%BB%93%E8%AE%BA"><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">由以上可以得出两个结论</span></strong></h6>
<p style=""></p>
<ol>
 <li>
  <p style="">如果首次项目启动时，超出maxHistory定义的时间的64天之前的日志是不会被清理的</p>
 </li>
 <li>
  <p style="">如果当天日志的编号超出3位数后缀，也将不会被清理</p>
 </li>
</ol>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">上面两个问题其实可能对于很多人来说，理解是很简单的事情。但是有两点需要提醒：</p>
<p style="">1、要从原理上彻底理解清楚</p>
<p style="">2、要有良好的规范和方法来避免这样的问题。这一点，目前大多数公司都做不到</p>]]></description><guid isPermaLink="false">/archives/ri-chang-kai-fa-bu-shu-shi-yao-bi-mian-de-liang-ge-da-bao-ci-pan-de-wen-ti</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 05:59:29 GMT</pubDate></item><item><title><![CDATA[懂得三境界-使用dubbo时请求超过问题]]></title><link>https://wiki.dgfygk.com/archives/dong-de-san-jing-jie-shi-yong-dubboshi-qing-qiu-chao-guo-wen-ti</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%87%82%E5%BE%97%E4%B8%89%E5%A2%83%E7%95%8C-%E4%BD%BF%E7%94%A8dubbo%E6%97%B6%E8%AF%B7%E6%B1%82%E8%B6%85%E8%BF%87%E9%97%AE%E9%A2%98&amp;url=/archives/dong-de-san-jing-jie-shi-yong-dubboshi-qing-qiu-chao-guo-wen-ti" width="1" height="1" alt="" style="opacity:0;">
<p style="">刚才下班回家路上，无意中听到大街上放的歌，歌词有这么一句：“毡房外又有驼铃声声响起，我知道那一定不是你”。这一句我似乎听懂了歌者的魂牵梦绕和绝望，如果在十年前我大概只能感受出悠扬的声调里溢出的悲凉吧。</p>
<p style="">在工作上，我没有十年的时间来把思考能力上升一个等级。对于一个问题，需要在很短的时间反复思考，深层次的弄懂。懂得，有三个初级境界，对应三个方法：</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">1&gt;字面理解-what、why、how
   <br></span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">2&gt;前因后果-5why
   <br></span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">3&gt;选择最优-SMART</span></strong></p>
<h1 style="" id="%E6%83%B3%E4%BA%86%E8%A7%A3what%E3%80%81why%E3%80%81how%E9%BB%84%E9%87%91%E5%9C%88%E6%B3%95%E5%88%99%E6%88%96%E8%80%855why%E5%88%86%E6%9E%90%E6%B3%95%E5%8F%AF%E5%8F%82%E8%80%83%E6%88%91%E4%B9%8B%E5%89%8D%E7%9A%84%E6%96%87%E7%AB%A0%EF%BC%9A%E3%80%8A%E4%BB%A3%E7%A0%81%E8%8D%A3%E8%BE%B1%E8%A7%82-%E4%BB%A5%E8%BF%90%E7%94%A8%E9%A3%8E%E6%A0%BC%E4%B8%BA%E8%8D%A3%EF%BC%8C%E4%BB%A5%E9%9A%8F%E6%84%8F%E7%BC%96%E7%A0%81%E4%B8%BA%E8%80%BB%E3%80%8B%EF%BC%9B%E6%83%B3%E4%BA%86%E8%A7%A3smart%E5%8E%9F%E5%88%99%E5%8F%AF%E5%8F%82%E8%80%83%E6%88%91%E4%B9%8B%E5%89%8D%E7%9A%84%E6%96%87%E7%AB%A0%EF%BC%9A%E3%80%8A%E7%9F%A5%E5%90%8D%E4%BA%92%E8%81%94%E7%BD%91%E5%85%AC%E5%8F%B8%E9%9C%80%E8%A6%81%E4%BB%80%E4%B9%88%E6%A0%B7%E7%9A%84%E4%BA%BA%E6%89%8D%E3%80%8B%E3%80%82">想了解what、why、how黄金圈法则或者5why分析法可参考我之前的文章：<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484645&amp;idx=1&amp;sn=f86183a5a248a03adf8d8da3452b79fe&amp;chksm=fafdee4bcd8a675d2335942673334f188e7826c39fe24314f91a5151d78c1554527ca612beb8&amp;scene=21#wechat_redirect" target="_blank">《代码荣辱观-以运用风格为荣，以随意编码为耻》</a>；想了解SMART原则可参考我之前的文章：<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484071&amp;idx=1&amp;sn=52157bf558c74a94de69e6fa8f138d51&amp;chksm=fafde809cd8a611f0ec4dd66e40efa74f1f3ce4d69d89c23a2bc243e31dd3442cec8fc0a300d&amp;scene=21#wechat_redirect" target="_blank">《知名互联网公司需要什么样的人才》</a>。</h1>
<p style="">回到主线：为什么是初级境界？我自己也不知道更高级别的境界是有什么，因为自己境界没有达到。但是至少有：刻入骨髓 这一境界。</p>
<p style="">举个例子：十二年前，有次在大街上走，我走过一个【北京银行】的大门，银行二楼的玻璃哗啦啦掉下来。我知道身后有危险，有很多玻璃落到了距离我身后不到十公分的地方。我当时很镇定的继续向前走。等过了危险区，我很想神经质的大叫。因为走路时再多犹豫2秒，可能脑袋上被扎的全是玻璃。所以走路时时时刻刻都会想着离楼房远一些，并且好像感受到了自己脑袋被玻璃扎。我理解这可以算作把楼房玻璃很危险的理解刻入骨髓。</p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(122, 79, 214)">字面理解</span></strong></p>
<p style="">今天举的这个例子纯粹是技术问题，终于不需要用蹩脚的比喻把事情描述的更难理解来达到脱敏的效果。</p>
<p style=""><strong><span style="font-size: 20px; color: rgb(0, 209, 0)">what</span></strong></p>
<p style="">我们采用的是dubbo服务，这是个稳定成熟的RPC框架。但是我们在某些应用中会发现，只要这个应用一发布(或者重启),就会出现请求超时的问题，如下图所示:
 <br>
 <img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qknh.png&amp;size=m" width="100%" height="100%" style="display: inline-block">
 <br>
 而且都是第一笔请求会报错，之后就再也没有问题了。<span style="font-size: 12px">(上图来源于网络，下面有参考文章。如果可以，我想把这些字调的再小一些)</span></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(0, 209, 0)">why</span></strong></p>
<p style="">我当时很快就定位了问题，因为在内网wiki上、技术博客上，很多人都写了这个坑。所以不讲排查思路了，直接讲结论：</p>
<p style="">在server端连接数过多, linux系统有个连接队列溢出了。溢出的连接被丢弃，但是client端不知道，仍然给此server发送消息。连接没有建立自然发送不成功。client发第一笔消息超时，相当于探活失败，client端于是重新建立连接。连接成功建立后开始正常的通信，所以后面都成功了。</p>
<p style=""><strong><span style="font-size: 20px; color: rgb(0, 209, 0)">how</span></strong></p>
<p style="">怎么来解决这个问题呢？四个思路。</p>
<p style="">第一个是队列溢出了，那就说明队列太小。可以把队列值改大。dubbo使用的是一个写死的默认值：50。可以修改dubbo源码把值改大或者干脆动态获取队列值。</p>
<p style="">第二个是队列数不变，实际连接数减少。减少server端的连接方，比如有些client端其实没有实际业务调用这个server端了，就双方聊聊把无用的依赖去掉。</p>
<p style="">第三个是可以让服务端在丢弃连接的同时给client端通知一下，linux有个系统参数/proc/sys/net/ipv4/tcp_abort_on_overflow，默认为0。不会给client端发通知，但是设置为1时会给server端发一个reset请求，客户端收到会重连。</p>
<p style="">第四个是让client端定时心跳探测。探测发现超时了马上重连，超时的那笔只是探测请求，不影响业务。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lhxo.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><strong>前因后果</strong></p>
<p style="">作为软件工程师，重要的一个软素质是批判性思维。多问几个问题，找到答案，理解就能更进一步。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q1: </span></strong>提到溢出的队列到底是什么队列？</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">A:&nbsp;</span></strong>下图是TCP连接三次握手的示意图。</p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-skiu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong>一次握手：</strong></p>
<p style="">一开始client端和server端都处于closed状态(未建立连接状态)。client端主动向server端发起syn请求建立连接请求，server端收到后将与client端的连接设置为listen状态(半连接状态)。问题来了，server端怎么保存与client端的状态呢？总需要有地方存呀，存的地方就是队列。连接队列又叫backlog队列。到这里，server端与client端的半连接建立了。这里的backlog队列也叫半连接队列。</p>
<p style=""><strong>二次握手：</strong></p>
<p style="">server端返回ack应答+syn请求给client，意思是：ack我收到了你的请求，syn你收到我的了没？client端收到server端响应，将自己的状态设置为established状态(连接状态)。</p>
<p style=""><strong>三次握手：</strong></p>
<p style="">client向server端发送一个ack响应，告诉server端收到。然后server端收到后将与client端的连接设置为established状态(全连接状态)。同样，全连接状态在server端也需要一个backlog队列存储。这里的backlog队列也叫全连接队列。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q2:&nbsp;</span></strong>backlog队列到底是全连接队列还是半连接队列？</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">A:&nbsp;</span></strong>这个问题让我想起别的事情。我大学是东北大学，有次看到校内论坛上有个帖子：“东北大学和东南大学谁更有资格叫东大？”最终没啥结论，东北大学内网再论证自己该叫东大，东南大学内网肯定不认。</p>
<p style="">但是backlog的问题还是有达成共识的可能的。backlog其实是一个连接队列，在Linux内核2.2之前，backlog包括半连接状态和全连接状态两种队列。在Linux内核2.2之后，分离为两个backlog来分别限制半连接（SYN_RCVD状态）队列大小和全连接（ESTABLISHED状态）队列大小。</p>
<p style=""><strong>半连接队列：</strong></p>
<p style="">队列长度由/proc/sys/net/ipv4/tcp_max_syn_backlog指定，默认为2048。</p>
<p style=""><strong>全连接队列：</strong></p>
<p style="">队列长度由/proc/sys/net/core/somaxconn和使用listen函数时传入的参数，二者取最小值。默认为128。</p>
<p style="">在Linux内核2.4.25之前，是写死在代码常量 SOMAXCONN ，在Linux内核2.4.25之后，在配置文件/proc/sys/net/core/somaxconn中直接修改，或者在 /etc/sysctl.conf 中配置 net.core.somaxconn = 128 。</p>
<p style="">想到这里我恍然大悟，东北大学、东方大学、东南大学在自己的地盘都有资格简称东大（这里讲这个插曲是为了澄清一件事情：我昨天下午4点发的文章里标题是一个北大妹子，那篇文章是帮朋友的忙，北大妹子不是我，我是东大妹子）。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q3:&nbsp;</span></strong>到底是全连接队列还是半连接队列溢出导致了超时？</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">A:&nbsp;</span></strong>server端与client端进行二次握手的前提是server端认为自己与client建立连接是没有任何问题的。如果server端半连接队列溢出了，自己这边都没有处于半连接状态，自然不会发送ack+syn给client端。client端做的应该是重新尝试建立连接，不是发送数据。请求会发送到已经建立好连接的server端(server端是多机器多活部署的)不会造成请求超时。</p>
<p style="">而二次握手一旦完成，进行三次握手时，如果全连接队列已满，服务器收到客户端发来的ACK, 不会将该连接的状态从SYN_RCVD变为ESTABLISHED。但是客户端已经认为连接建立好了开始发送数据了，这时候是有可能造成超时的。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q4:&nbsp;</span></strong>全连接队列满了之后server端是怎么处理的呢？</p>
<p style="">当全连接队列已满时，则根据 tcp_abort_on_overflow 的值来执行相应动作。</p>
<p style=""><strong>tcp_abort_on_overflow = 0 处理：</strong></p>
<p style="">则服务器建立该连接的定时器，这个定时器是一个服务器的规则是从新发送syn+ack的时间间隔成倍的增加，比如从新了第二次握手，进行了5次，这五次的时间分别是 1s, 2s,4s,8s,16s,这种倍数规则叫“二进制指数退让”(binary exponential backoff)。</p>
<p style="">给客户端定时从新发回SYN+ACK即重新进行第二次握手，(如果客户端设定的超时时间比较短就很容易出现异常)服务器重新进行第二次握手的次数由/proc/sys/net/ipv4/tcp_synack_retries 这个linux系统参数决定。</p>
<h4 style="" id="tcp_abort_on_overflow-%3D-1-%E5%A4%84%E7%90%86%EF%BC%9A"><strong>tcp_abort_on_overflow = 1&nbsp;处理：</strong></h4>
<p style="">当 tcp_abort_on_overflow 等于1 时,发送一个reset请求重置连接。客户端收到可以尝试再次从第一次握手开始建立连接或者其他处理。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q5:&nbsp;</span></strong>怎么验证确实是backlog队列溢出呢？</p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">ss 是 Socket Statistics 的缩写。ss 命令可以用来获取 socket 统计信息</span>。ss -l 是显示listen状态的数据，如下所示：</p>
<pre><code>[root@localhost ~]# ss -lState       Recv-Q Send-Q        Local Address:Port           Peer Address:Port     LISTEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;128&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*:http&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   *:*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LISTEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;128&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:::ssh&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:::*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LISTEN      0      128                        *:ssh                         *:*       LISTEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;::1:smtp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:::*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LISTEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;127.0.0.1:smtp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*:*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></pre>
<p style="">在LISTEN状态，其中 Send-Q 即为全连接队列的最大值，Recv-Q 则表示全连接队列中等待被server段处理的数量。数量为0，说明处理能力很够；Send-Q =Recv-Q ，满了，再来就丢弃掉了。</p>
<p style="">但是这是一个实时的数据，一段时间有拥塞，过一会儿就好了怎么查呢？</p>
<p style=""><span style="font-size: 16px; color: rgb(85, 85, 85)">可以使用netstat -s 可以查看被全连接队列丢弃的数据。</span></p>
<pre><code>[root@localhost ~]# netstat -s | grep "times the listen queue of a socket overflowed"35552 times the listen queue of a socket overflowed</code></pre>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">补充说明:&nbsp;</span></strong>半连接队列很多文章叫做<span style="font-size: 16px">SYN QUEUE队列。全连接队列很多文章叫做ACCEPT QUEUE队列。这是一些研究linux源码的同学根据源码的命名来叫的。</span></p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(122, 79, 214)">选择最优</span></strong></p>
<p style="">马云说：“选择比努力重要”&nbsp;。懂得三境界，第三境界的重点不是懂，而是得。最终要根据懂了的内容决策出最优方案。</p>
<p style="">除了字面理解里提到的四种思路，前因后果里还提到了重新进行第二次握手的次数由/proc/sys/net/ipv4/tcp_synack_retries 这个linux系统参数决定。</p>
<p style="">分别来分析一下各个方案的可行性和优缺点：</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">方案1：把队列值调大</span></strong></p>
<p style="">这个队列值是指<span style="font-size: 16px">全连接队列</span>，调大之后，client端的二次握手就在这个队列里排队等待server端真正建立连接。假设队列值调到上限65535。第65535号请求在排队的过程，client端是established状态，数据可能会发送过来，服务端还没有established状态，还不能处理。</p>
<p style="">到什么时候能处理呢？65535个请求全部处理完需要13s的样子。对一般的服务来说妥妥的超时。所以nginx和redis都是使用的511，让响应时间在100ms内完成。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">方案2：减少连接数</span></strong></p>
<p style="">只要能减少的下来，这是理想的法子。现在server端都过载了，可想而知，接入的client端不再少数，推动他们一个个去梳理和改造，就算大家执行力很强，把改下的下了。可想而知，废弃的也一般不会有多少。不展开了啊，现在已经三千多字了，争取五千字内结束。</p>
<p style="">还有没有别的方法减少连接数呢？最简单的就是使用分治法。</p>
<p style=""><strong>划分子集</strong></p>
<p style="">跟同事讨论请教的时候，他给我提供了一个划分子集的思路。让client端只和server端一部分服务器建立连接。有两种分配谁跟谁连接的算法，一个是随机算法。但是server端服务器我最多见过几千台组成一个集群的。对随机(虽然连接数是服务器台数的n倍)来说，样本是很少的，会很不均匀；另外一个是确定性算法，思路也很简单。连接的client端及数量是确定的，那就排个序，按照server端数量分配一下。这样连接数是均匀的，但是就没办法做到请求级别的流量均匀。</p>
<p style=""><strong>粘滞连接</strong></p>
<p style="">尽可能让客户端总是向同一提供者发起调用，除非该提供者挂了，再连另一台。&lt;dubbo:protocol name="dubbo" sticky="true" /&gt;。如果每个client端都只和一个server端建立连接。那server端压力就是原来的(1/机器台数)。不够加机器就行了，横向可扩展。</p>
<p style="">这种做法最大的问题是高可用和并发请求的问题，对于可用性要求不高、请求量不高的服务(比如后台定时任务定时拉取可重试)其实是可以用的。但是这需要client端的自觉性，而对维护这个client端的人员来讲，他们自身是没有好处的，因为原本也就是只是重启时发生一次超时嘛。所以客户端在可以的情况下愿不愿意这样做就看格局了。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">方案3：服务端通知</span></strong></p>
<p style="">服务端通知上面<strong><span fontsize="" color="">前因后果</span></strong>中有提到可以设置</p>
<p style="">/proc/sys/net/ipv4/tcp_synack_retries</p>
<p style="">重新进行几次进行第二次握手。但是这个阶段，client端可能会发数据包过来造成超时；另外，可以设置</p>
<p style="">/proc/sys/net/ipv4/tcp_abort_on_overflow=1</p>
<p style="">整个握手直接断掉，client端是closed状态，它会找其他established状态的连接进行数据包发送，不会造成超时。事实上，调研了一些大厂，</p>
<p style="">tcp_abort_on_overflow=1是作为默认配置的。</p>
<p style=""><strong>方案4：客户端探测</strong></p>
<p style="">客户端探测想自己做的话比较麻烦，比如说把，客户端调了n个服务，每个服务建立了n个连接。资源开销大，还必须要复用这些已经建立的连接，复杂度高。</p>
<p style="">其实provider 和consumer 有双向心跳(探测)的，那为什么没检测出并进行重连？</p>
<p style="">这个首先面临的问题：client端认为连接成功了，但server端认为没有成功。那么server端 是不会发送心跳给 client端的。</p>
<p style="">client端是不是应该发心跳给server端呢？是的，原来使用dubbo2.5.3版本时3分钟client端会发送一个探测，之后把问题连接closed掉。只是dubbo 2.6.9使用了netty4。他们强强联手搞出来一个bug，探测机制楞没生效！</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lgbl.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">心跳有个条件，就是lastRead 和 lastWrite 不为空。那就需要看哪里设置了这两个参数。通过代码查到client端连接成功和server端连接成功的时候都会设置。这里只考虑client端情况，对比netty3发现netty4里少了</p>
<p style="">NettyServerHandler的handler链处理。这个handler链处理就是用来初始化那两个值的。</p>
<p style="">除了改client端源码，有没有别的方法让client端探测生效呢？其实什么都不用TCP就有keepalive(探活)机制。默认是7200秒，也就是2小时。可以修改：</p>
<h2 style="" id="%2Fproc%2Fsys%2Fnet%2Fipv4%2Ftcp_keepalive_time">/proc/sys/net/ipv4/tcp_keepalive_time&nbsp;</h2>
<h2 style="" id="%E5%8D%95%E4%BD%8D%E6%98%AF%E7%A7%92">单位是秒</h2>
<h2 style="" id=""></h2>
<p style="">好了，解决问题的方法就讲到这里，完结撒花~~</p>
<p style=""></p>
<p style="">咦，说好的SMART原则呢？</p>
<blockquote>
 <p style="">S代表具体的(Specific)</p>
 <p style="">M代表可衡量的(Measurable)</p>
 <p style="">A代表可达到的(Attainable)</p>
 <p style="">R代表与最终目标是相关的(Relevant)&nbsp;</p>
 <p style="">T代表有明确的截止期限(Time-bound)</p>
 <p style="">编程一生，公众号：编程一生<a href="https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484071&amp;idx=1&amp;sn=52157bf558c74a94de69e6fa8f138d51&amp;chksm=fafde809cd8a611f0ec4dd66e40efa74f1f3ce4d69d89c23a2bc243e31dd3442cec8fc0a300d&amp;token=199412102&amp;lang=zh_CN#rd" class="blockquote_article">知名互联网公司需要什么样的人才</a></p>
</blockquote>
<p style="">方案这么多，哪种是最好的呢？看场景。方案4提到了问题其实是开源组件有bug导致。但改开源组件，看公司规划、开源社区支持，A可行性上有制约；</p>
<p style="">方案2涉及很多整改和推动，T时效上有制约。方案虽多，排除法排除一下能剩下一个就不错了。</p>]]></description><guid isPermaLink="false">/archives/dong-de-san-jing-jie-shi-yong-dubboshi-qing-qiu-chao-guo-wen-ti</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 05:54:03 GMT</pubDate></item><item><title><![CDATA[懂了！国际算法体系对称算法DES原理]]></title><link>https://wiki.dgfygk.com/archives/dong-liao-guo-ji-suan-fa-ti-xi-dui-cheng-suan-fa-desyuan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%87%82%E4%BA%86%EF%BC%81%E5%9B%BD%E9%99%85%E7%AE%97%E6%B3%95%E4%BD%93%E7%B3%BB%E5%AF%B9%E7%A7%B0%E7%AE%97%E6%B3%95DES%E5%8E%9F%E7%90%86&amp;url=/archives/dong-liao-guo-ji-suan-fa-ti-xi-dui-cheng-suan-fa-desyuan-li" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">概念</span></strong></p>
<p style="">加密领域主要有国际算法和国密算法两种体系。<span fontsize="" color="">国密算法是国家密码局认定的国产密码算法。国际算法是由美国安全局发布的算法。由于国密算法安全性高等一系列原因。国内的银行和支付机构都推荐使用国密算法。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rcep.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">从上图可知，对称加密算法在算法体系里占了半壁江山。因为国际和国密算法的过程差异并不大。只是应用的数学公式和秘钥位数不同。DES在里面算是基础，所以今天主要介绍一下DES的原理。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">原理</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">密码</span></strong></p>
<p style="">咱们从加密的原理说起。举个最简单的加密：</p>
<p style="">我有一段明文：520</p>
<p style="">我的秘钥是：221</p>
<p style="">我的加密算法是：加法</p>
<p style="">加密后的密文就是：741</p>
<p style="">如果这个密文被截获了，接受者看到的信息是：741(气死你)，和原来明文520是完全不同的，不能知道信息原有的意思。</p>
<p style=""></p>
<p style="">从这个简单的例子可知：加密是包含：明文、秘钥、加密算法和密文四个要素的。加密算法可公开可不公开，常见的算法有：位移、循环位移、异或、置换、数学函数。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">凯撒密码</span></strong></p>
<p style="">加密技术从古罗马凯撒时候就在用：凯撒密码</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dxol.png&amp;size=m" width="540px" height="540px" style="display: inline-block"></p>
<p style="">凯撒密码是古罗马时期凯撒大帝和他的将军们通信时使用的加密方式：</p>
<p style="">明文：由26个字母组成</p>
<p style="">秘钥：1到25之间的任意数字</p>
<p style="">加密算法：循环位移</p>
<p style="">密文：举例明文为eat 秘钥为2，对照上面图片的凯撒密码盘可以得到密文是gcv。</p>
<p style=""></p>
<p style="">咱们来动手写个凯撒密码(代码已经上传github:
 <br>
 （https://github.com/xiexiaojing/yuna)：</p>
<pre><code>@Test
public void caesarCipher() {
    String text = "love was growing in eyes";  //明文
        int key = 3; //秘钥
        String cipher = encryptCaesarCipher(text, key); //密文
        System.out.println(cipher);
}

//凯撒密码加密算法
private String encryptCaesarCipher(String text, int key) {
    char[] chars = text.toCharArray();
    for(int i=0; i&lt;chars.length; i++) {
        if(chars[i] != ' ') {
            chars[i] = (char)(chars[i]+key);
        }

        //如果超过了26个字母，则减去26
        if(chars[i]&gt;122) {
            chars[i] = (char)(chars[i]-26);
        }
    }
    return new String(chars);
}</code></pre>
<p style="">运行得到密文：oryh zdv jurzlqj lq hbhv</p>
<p style=""></p>
<p style="">凯撒密码连小朋友都能破解。一旦被人知道用的凯撒密码，算法是已知的。要破解秘钥拿个明文和密文试试就知道了。就是平时说的暴力破解法可以很容易破解。对于这种全是英文字符的也可以使用频率分析法。频率分析法可以理解为基于大数据的方法，因为26个字母中，e的使用频率高。如果比如一篇文章，单词足够多的话，出现频率最高的字母xxx就是e。xxx的char值e的char值就是秘钥了。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kgvd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">DES</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">概述</span></strong></p>
<p style="">DES全称为Data Encryption Standard，即数据加密标准，是一种分组加密算法。<span style="font-size: 18px; color: rgb(77, 77, 77)">其分组长度为64bit，密钥长度为64bit，其中8bit为校验位，所以实际长度为56bit。</span></p>
<p style=""></p>
<p style="text-align: start; "><span style="font-size: 18px">先介绍一下校验位。举个例子，咱们的身份证号码都是18位。这18位包含：</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rfic.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: start; ">其中最后一位就是校验位，原理是利用将前面部分利用某种算法计算得到一个数。如果校验位与算法得到的不一致，则数据是有问题的。所以身份证本身是有不通过查库就可以简单验证有效性功能的。</p>
<p style="text-align: start; "></p>
<p style="text-align: start; ">回到DES算法。DES算法的秘钥必须是64位，参与加密计算的是56位。这是原始秘钥。这个原始秘钥会用一个函数转换成16个64位秘钥。</p>
<p style="text-align: start; "></p>
<p style="text-align: start; "><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">加密过程</span></strong></p>
<p style="">DES的加密过程：</p>
<p style="">明文64位-&gt;初始IP置换-&gt;16轮加密变换-&gt;逆初始IP置换-&gt;密文</p>
<p style=""></p>
<p style="">DES算法的这个过程又被称为Feistel网络。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gqxh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">简单解释下：</p>
<p style="">明文我们自己想写多长写多长。但是加密的时候每次以64bit作为一个分组。最后将密文拼接起来。</p>
<p style="">然后执行一个IP置换(初始置换)操作。IP置换就是按位置换。举例来说64bit就是64个0和1。把第40个位置上的数换成第50个位置上的数就是置换了。</p>
<p style="">置换好的64个bit会分成两个32bit。然后用相同的加密算法每次传不同的转换后秘钥做16轮。</p>
<p style="">然后将两组32bit拼接起来再进行一次IP置换(终结置换)变成密文</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">分组组合</span></strong></p>
<p style="">刚才介绍的加密过程是把明文的一块怎么加密成密文。DES密码块与密码块连接方式遵循对称加密的方式。</p>
<p style="">对称加密有两种方式，一种是分组加密，一种是序列加密。</p>
<p style="">分组加密，也叫块加密(block cyphers)，一次加密明文中的一个块。是将明文按一定的位长分组，明文组经过加密运算得到密文组，密文组经过解密运算（加密运算的逆运算），还原成明文组。</p>
<p style="">序列加密，也叫流加密(stream cyphers)，一次加密明文中的一个位。是指利用少量的密钥通过某种复杂的运算（密码算法）产生大量的伪随机位流，用于对明文位流的加密。</p>
<p style=""></p>
<p style="">分组加密算法中，<span style="font-size: 18px; color: rgb(77, 77, 77)">分组密码的设计基本遵循混淆原则和扩散原则。</span>有ECB,CBC,CFB,OFB这4种算法模式。DES有ECB和CBC两种实现。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">ECB模式</span></strong></p>
<p style="">ECB模式就是每组明文分别加密后拼接起来。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ruld.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""></p>
<p style=""><strong>CBC模式</strong></p>
<p style="">CBC即密码分组链接（Cipher-block chaining）的简称。在CBC模式中，每个明文块先与前一个密文块进行异或后，再进行加密。在这种方法中，每个密文块都依赖于它前面的所有明文块。同时，为了保证每条消息的唯一性，在第一个块中需要使用初始化向量。在DES中，初始化向量就是秘钥。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cxmj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 76, 0)" style="color: rgb(255, 76, 0)">程序实现</span></strong></p>
<p style="">我们用程序来实现一下DES加密算法</p>
<pre><code>@Test
public void desCiper() throws Exception{
    String key = "12345678";
    String text = "我知道我是任性很任性，伤透了你的心";  //明文
        String cipher = encryptDesCipher(text, key); //密文
        System.out.println(cipher);
    text = decryptDesCipher(cipher, key);  //明文
         System.out.println(text);
}

//DES加密算法
private String encryptDesCipher(String text, String origKey) throws Exception{
    Key key = new SecretKeySpec(origKey.getBytes(), "DES");
    Cipher cipher = Cipher.getInstance("DES");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    return Base64.encode(cipher.doFinal(text.getBytes()));
}

//DES加密算法
private String decryptDesCipher(String text, String origKey) throws Exception{
    Key key = new SecretKeySpec(origKey.getBytes(), "DES");
    Cipher cipher = Cipher.getInstance("DES");
    cipher.init(Cipher.DECRYPT_MODE, key);
    return new String(cipher.doFinal(Base64.decode(text)));
}</code></pre>
<p style=""></p>
<p style="">运行结果：</p>
<p style="">xo6mmVe8j1/d60cAAiFz1HAXxihi2FH5d0zMWILvEYISWR52lguy2TbMZQ4vuulCdO8WvxMRuXE=</p>
<p style="">我知道我是任性很任性，伤透了你的心</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">3DES</span></strong></p>
<p style="">3DES即3重加密算法，是对每个数据块应用三次DES算法。这是为了应对计算机计算能力增强，DES变的容易破解而产生的。</p>
<p style="">主要方法就是DES秘钥64位，3DES秘钥64*3=192位，分成个三个秘钥，进行2轮DES加密，1轮DES解密。最终得到结果。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wxeq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">AES</span></strong></p>
<p style="">AES全称是高级加密标准(Advanced Encryption Standard)。是用来替代DES/3DES的。主要过程如下，这里不过多介绍。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ianr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span style="font-size: 20px">应用</span></strong></p>
<p style="">我们来回顾下https的SSL握手过程：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hmwc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">SSL握手的最后，双方会用非对称秘钥协商出一个对称秘钥。用对称秘钥来加密传输的数据。之所以这样做是因为非对称加密安全性高但是效率低，对称秘钥正好相反。对称秘钥可以被暴力破解，破解需要时间。如果破解出来时就过期了，再通信就用另外的秘钥就能保证信息安全。</p>
<p style=""></p>
<p style="">我们来模拟一下已经协商好的秘钥之后http客户端与服务器端的通信。(代码已经上传github:（https://github.com/xiexiaojing/yuna)：</p>
<pre><code>@Test</code></pre>
<pre><code>public void client() throws Exception {
    int i = 1;
    while (i &lt;= 2) {
        Socket socket = new Socket("127.0.0.1", 520);
        //向服务器端第一次发送字符串
        OutputStream netOut = socket.getOutputStream();
        InputStream io = socket.getInputStream();
        String msg = i == 1 ? "客户端：我知道我是任性太任性，伤透了你的心。我是追梦的人，追一生的缘分。":
        "客户端：我愿意嫁给你，你却不能答应我。";
        System.out.println(msg);
        netOut.write(encryptDesCipher(msg.getBytes(), "12345678"));
        netOut.flush();
        byte[] bytes = new byte[i==1?104:64];
        io.read(bytes);
        String response = new String(decryptDesCipher(bytes,"12345678"));
        System.out.println(response);
        netOut.close();
        io.close();
        socket.close();
        i++;
    }
}

@Test
public void server() throws Exception {
    ServerSocket serverSocket = new ServerSocket(520);
    int i = 1;
    while (i &lt;= 2) {
        String msg = i == 1 ? "服务端：我知道你是任性太任性，伤透了我的心。同是追梦的人，难舍难分。" :
                "服务端：你愿意嫁给我，我却不能向你承诺。";
        Socket socket = serverSocket.accept();
        InputStream io = socket.getInputStream();
        byte[] bytes = new byte[i==1?112:64];
        io.read(bytes);
        System.out.println(new String(decryptDesCipher(bytes,"12345678")));
        OutputStream os = socket.getOutputStream();
        System.out.println(msg);
        byte[] outBytes = encryptDesCipher(msg.getBytes(), "12345678");
        os.write(outBytes);
        os.flush();
        os.close();
        io.close();
        i++;
    }
}</code></pre>
<pre><code>//DES加密算法
private byte[] encryptDesCipher(byte[] text, String origKey) throws Exception {
    Key key = new SecretKeySpec(origKey.getBytes(), "DES");
    Cipher cipher = Cipher.getInstance("DES");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    return cipher.doFinal(text);
}

//DES加密算法
private byte[] decryptDesCipher(byte[] text, String origKey) throws Exception {
    Key key = new SecretKeySpec(origKey.getBytes(), "DES");
    Cipher cipher = Cipher.getInstance("DES");
    cipher.init(Cipher.DECRYPT_MODE, key);
    return cipher.doFinal(text);
}</code></pre>
<p style="">运行结果</p>
<p style="">客户端：我知道我是任性太任性，伤透了你的心。我是追梦的人，追一生的缘分。</p>
<p style="">服务端：我知道你是任性太任性，伤透了我的心。同是追梦的人，难舍难分。</p>
<p style="">客户端：我愿意嫁给你，你却不能答应我。</p>
<p style="">服务端：你愿意嫁给我，我却不能向你承诺。</p>
<p style=""></p>
<p style="">这个程序实现了简单的客户端和服务器端的DES加密方式通信。稍加改造可以实现一个信息加密的聊天小程序。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">总结</span></strong></p>
<p style="">本文使用概念、原理、应用的传统型逻辑架构来对DES做系统梳理。里面涉及到的一些基本知识限于篇幅省略了一些。</p>
<p style="">比如Base64编码。它是加密时常用的编码方式，我们平时所看到的密钥都是base64后的结果。可以简单理解为对2的6次方进行64进制运算，可防止乱码丢失字节。</p>
<p style="">再比如填充字节这部分也没有介绍，有兴趣可以自己查阅下。</p>
<p style=""></p>
<p style="">加解密很多人工作中或多或少都有涉及。我个人认为只要涉及的地方至少要了解到原理和架构层面，才能避免遇到问题时【拿着锤子找钉子】找不到真正问题的窘境。而只有动手实践才能避免当时了解了，过后又忘了需要再看一遍而产生重复工作的问题</p>]]></description><guid isPermaLink="false">/archives/dong-liao-guo-ji-suan-fa-ti-xi-dui-cheng-suan-fa-desyuan-li</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 05:50:29 GMT</pubDate></item><item><title><![CDATA[深入理解MQ生产端的底层通信过程-理解channel]]></title><link>https://wiki.dgfygk.com/archives/shen-ru-li-jie-mqsheng-chan-duan-de-di-ceng-tong-xin-guo-cheng-li-jie-channel</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3MQ%E7%94%9F%E4%BA%A7%E7%AB%AF%E7%9A%84%E5%BA%95%E5%B1%82%E9%80%9A%E4%BF%A1%E8%BF%87%E7%A8%8B-%E7%90%86%E8%A7%A3channel&amp;url=/archives/shen-ru-li-jie-mqsheng-chan-duan-de-di-ceng-tong-xin-guo-cheng-li-jie-channel" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">引子</span></strong></p>
<p style="">在上一篇<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a>里，标题里用到了“魔鬼训练”。一向注重标题贴合内容本意，为什么我会用这个词，这里跟大家解释一下。</p>
<p style=""></p>
<p style="">这是我自己的一个学习方法。有些朋友是知道的，我日语学的不错的。1年的时间内从0开始学习通过了最高级(一级)的考试。当时我就是用的这种方法。简单介绍一下当时学习的整个过程：</p>
<p style=""></p>
<p style="">我加入了当时一个特别大(鼎盛时期有8W人)的对日公司。听小伙伴们说有的人3个月就可以过日语三级(最低级别是四级)了。我就想啊，别人要是可以我也可以。于是我一个0基础，直接跳过了四级班，直接报了三级班。</p>
<p style=""></p>
<p style="">可以想象，每次上课，我如听天书，就是奔着老师的颜值去的。但是私下里，我自己在赶进度。自己找其他人学习了假名等基础知识，然后抱着四级课本从头到尾的捋内容。捋完之后开始捋三级课本。怎么可能看的懂！但是我在三级课本里用到的四级内容的地方，我知道去哪里找了。所以当我终于赶上了老师的进度，三级的东西基本不会，但是四级的内容就差不多了。</p>
<p style=""></p>
<p style="">我一边跟着老师学，听不明白的，我就找之前的内容自己补习。三个月后的日语考试，在这个班30多个人中，我考了十几名。</p>
<p style=""></p>
<p style="">我如法炮制，考二级的时候，我在整个公司一起举行的考试中考了第7名。考一级的时候，我是第1名。第2名的分数被我远远的甩在了后面。</p>
<p style=""></p>
<p style="">于是我的名字那时候经常出现在公司的周刊上面。还有人专门从其他办公楼跑过来看一眼，说是想知道这个传说中的人物长成什么样子。</p>
<p style=""></p>
<p style="">这整件事情魔鬼在哪里呢，外面只看到了结果。那时候刚大学毕业，大家一起住公司宿舍。那个宿舍有7层楼，只有两层有人住。一层住男生，一层住女生。我下班之后就找没人的楼层自己在那里读日语。除了别人来叫我玩，我很少主动找别人玩的，有时间就学啊。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dbnb.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">1年的时间努力一把其实够好几年收益的。我虽然当时努力的方向不对，但是也收益了。头两年我是在沈阳的，来到北京之后找工作去了当时不错的名企，薪资也比同届的其他人高出不少。其实那时候我的技术很菜，但是日语能学的这么好，说明学习能力不错的。年轻可培养嘛，所以人家也很乐意收。</p>
<p style=""></p>
<p style="">不过这里说明了，努力的方向不对。如果我用来学习技术，那我这几年的整个人生状态都将被改变。但是我花了2年搞日语；花了1年多搞文学；如果不是要生小鲜肉，我大概已经北大文学系研究生毕业了；后来读的是中科院心理学的研究生。爱好，我是认真的。人各有所求，虽然技术上发力的晚，我也不后悔。各阶段自己想做的事情都做了。</p>
<p style=""></p>
<p style="">简而言之，以上的学习方法推荐给大家，近期的文章安排也是遵循这一思路。另外一点很重要：这不是一个成功的例子，而是一个失败的教训。虽然一直以来平台都不错，还是有希望的。不可否认的是因为我那时候没有好好学习技术，现在步履艰难。换个词大家应该就能理解了：不务正业。</p>
<p style="text-align: center; ">。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zoug.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">深入理解Channel</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">引入Channel</span></strong></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485843&amp;idx=1&amp;sn=ae07db807669699bc4bec807aa468a5c&amp;chksm=fafde33dcd8a6a2b8a34483bc61771c3cf99a49a7baf46e47e770c58372808e2053f27a203e1&amp;scene=21#wechat_redirect" target="_blank">《RabbitMQ设计原理解析》</a>里提到RabbitMQ的工作原理如下：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fory.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">简单理解一下：在生产端也就是产生消息的客户端。如果咱们使用MQ发送消息，那就是咱们的应用服务器。它会通过Channel和MQ服务器也就是Broker进行通信。在Broker内部有一个路由层也就是Exchange。Exchange是交换机的意思，顾名思义，它的作用是进行一些规则的匹配，根据不同的规则将不同的消息转发到不同的消息队列Queue上。Queue里的消息到达消费端也要进行Channel。在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485991&amp;idx=1&amp;sn=98d17273c84eefb4a4ad23f749bedb4a&amp;chksm=fafde089cd8a699f12ff60689f73e9ef8a189f3dc2dd36031ea27936863038685125ca0467f1&amp;scene=21#wechat_redirect" target="_blank">《架构师三大难-领域划分问题》</a>的<strong>示例三(异步处理模式)</strong>里，我也讲了：消费端和生产端有可能是同一个应用，也可以设计为不同的应用。</p>
<p style=""></p>
<p style="">好，那在这整个流程中，Channel到底是个什么东西呢？</p>
<p style=""></p>
<p style="">其实Channel通常翻译为信道或者渠道。不是Linux网络编程里的标准概念，而是一种抽象。什么的抽象呢？文件的读取等操作的抽象。看下面Java NIO中最重要的通道的实现：</p>
<ul>
 <li>
  <p style="">FileChannel：从文件中读写数据(不可异步读写)</p>
 </li>
 <li>
  <p style="">DatagramChannel：&nbsp;能通过UDP读写网络中的数据</p>
 </li>
 <li>
  <p style="">SocketChannel：能通过TCP读写网络中的数据</p>
 </li>
 <li>
  <p style="">ServerSocketChannel：可以监听新进来的TCP连接，像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel</p>
 </li>
</ul>
<p style=""></p>
<p style="">再联想一下上篇<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a>里提到的Socket的本质：</p>
<p style="">socket的本质就是一种类型的文件，所以一个socket在进行读写操作时会对应一个文件描述符fd(file descriptor)。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mvsy.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这一段是让大家回忆一下：socket是一种文件。对应于“文件的读取等操作的抽象”中的文件。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">Java中怎么使用Channel</span></strong></p>
<p style="">Channel的概念语言无关，这里只是因为本人更熟悉Java。使用别的语言不影响对概念的理解。</p>
<p style=""></p>
<p style="">上篇文章中用到的ServerSocket和Socket类中都有getChannel的实现：</p>
<pre><code>
/**
 * Returns the unique {@link java.nio.channels.SocketChannel SocketChannel}
 * object associated with this socket, if any.
 *
 * &lt;p&gt; A socket will have a channel if, and only if, the channel itself was
 * created via the {@link java.nio.channels.SocketChannel#open
 * SocketChannel.open} or {@link
 * java.nio.channels.ServerSocketChannel#accept ServerSocketChannel.accept}
 * methods.
 *
 * @return  the socket channel associated with this socket,
 *          or {@code null} if this socket was not created
 *          for a channel
 *
 * @since 1.4
 * @spec JSR-51
 */
public SocketChannel getChannel() {
    return null;
}</code></pre>
<p style=""></p>
<p style="">注释直接翻译成中文：</p>
<p style="">返回一个唯一的SocketChannel对象或者返回null。一个Socket只有在Channel本身是由SocketChannel.open或者ServerSocketChannel.accept方法创建之时才会存在(不为null)。</p>
<p style=""></p>
<p style="">上面代码已经很清楚了，直接使用Socket时，getChannel就是返回null。没有用的。那正确的获取SocketChannel的方法是什么呢？来看下面的代码：</p>
<pre><code>@Test
public void getChannel() throws Exception {
    InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 520);
    for (int i=1; i &lt;= 2; i++) {
        SocketChannel socketChannel = SocketChannel.open(inetSocketAddress);
        String msg = i == 1 ? "客户端：我知道我是任性太任性，伤透了你的心。我是追梦的人，追一生的缘分。" :
                "客户端：我愿意嫁给你，你却不能答应我。";
               ByteBuffer writeBuffer = ByteBuffer.allocate(20000);
        writeBuffer.put(msg.getBytes());
        writeBuffer.flip();
        socketChannel.write(writeBuffer);
        writeBuffer.clear();

        ByteBuffer readBuffer = ByteBuffer.allocate(20000);
        readBuffer.flip();
        socketChannel.read(readBuffer);

        socketChannel.close();
    }
}</code></pre>
<p style=""></p>
<p style="">服务端代码还是用<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a>中服务端的代码。因为客户端没有打印任何东西，直接看服务端运行结果：</p>
<p style="">客户端：我知道我是任性太任性，伤透了你的心。我是追梦的人，追一生的缘分。&nbsp; &nbsp;&nbsp;</p>
<p style="">服务端：我知道你是任性太任性，伤透了我的心。同是追梦的人，难舍难分。</p>
<p style="">客户端：我愿意嫁给你，你却不能答应我。&nbsp; &nbsp; &nbsp; &nbsp;</p>
<p style="">服务端：你愿意嫁给你，我却不能向你承诺。</p>
<p style="">和上篇文章运行结果一致。</p>
<p style=""></p>
<p style="">看到这里大家是不是更疑惑了，SocketChannel和Socket做的同样的事情？</p>
<pre><code>public static SocketChannel open(SocketAddress remote) throws IOException {
    SocketChannel sc = open();
    try {
       sc.connect(remote);&nbsp;&nbsp;&nbsp;&nbsp;} catch (Throwable x) {         try {
          sc.close();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch (Throwable suppressed) {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x.addSuppressed(suppressed);
         }
        throw x;
    }
    assert sc.isConnected();
    return sc;
}</code></pre>
<p style="">注意里面标红加粗的三个方法，open方法点进去有实现。这里有只截取open方法中我想说明的地方：</p>
<pre><code>SocketChannelImpl(SelectorProvider var1) throws IOException {
    super(var1);
    this.fd = Net.socket(true);    this.fdVal = IOUtil.fdVal(this.fd);
    this    this.state = 0;
}</code></pre>
<p style=""></p>
<p style="">结合客户端例子的代码，发现了什么？</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zduv.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这张图眼熟不？<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a>的图又来了一遍。看！TCP客户端这里做的事情，Channel全都做了！</p>
<p style=""></p>
<p style="">SocketChannel就是Socket 的替代类, 支持阻塞通信与非阻塞通信。Socket原生只支持最简的阻塞通信。Socket是在java.net下，SocketChannel在java.nio包下。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">MQ中怎么使用Channel</span></strong></p>
<p style="">下面是RabbitMQ的客户端代码，为什么用RabbitMQ呢？<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485843&amp;idx=1&amp;sn=ae07db807669699bc4bec807aa468a5c&amp;chksm=fafde33dcd8a6a2b8a34483bc61771c3cf99a49a7baf46e47e770c58372808e2053f27a203e1&amp;scene=21#wechat_redirect" target="_blank">《RabbitMQ设计原理解析》</a>里有说明：因为它实现了AMQP的标准协议。先上代码：</p>
<pre><code>    public static final String EXCHANGE_NAME = "RABBITMQ_Fanout"; &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;throws&nbsp;Exception{        ConnectionFactory connectionFactory = new ConnectionFactory();        connectionFactory.setHost("localhost");        Connection connection = connectionFactory.newConnection();        Channel channel = connection.createChannel();         channel.exchangeDeclare(EXCHANGE_NAME, "fanout");        String message = "This message is from Fanout mode.特点是Consumer均可获取到消息";        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("---【Producer发送消息】"&nbsp;+&nbsp;message&nbsp;+&nbsp;"---"&nbsp;);         channel.close();;        connection.close();&nbsp;&nbsp;&nbsp;&nbsp;}</code></pre>
<p style=""></p>
<p style="">解析一下这段代码：这是一个生产端的代码。首先新建一个连接工厂的实例。一个连接工厂可以创建多个连接。一个连接可以创建多个Channel信道。Channel可以声明为fanout广播，也可以设置其他路由形式。然后就可以通过Channel进行数据发送了。</p>
<p style=""></p>
<p style="">在AMQP协议中，有channel的概念，在RabbitMq中,channel表示逻辑连接或者叫虚拟连接，是棣属于TCP连接的。一个TCP连接里可以创建多个channel,在Rabbit MQ里，消息的发送和接收都是基于channel的。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-quxd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">有了TCP连接后,还需要channel的原因如下:</p>
<ul>
 <li>
  <p style="">创建和销毁TCP连接很耗时;</p>
 </li>
 <li>
  <p style="">打开太多TCP连接,耗操作系统资源，并发量大到一定程度,系统的吞吐量会降低；</p>
 </li>
 <li>
  <p style="">使用一个connection多channel的方式,可以提升连接的利用率。</p>
 </li>
</ul>
<p style="">因此采用多个channel多路复用一个TCP连接的方式才比较合理。</p>
<p style=""></p>
<p style="">https://www.rabbitmq.com/api-guide.html#connection-and-channel-lifspan</p>
<p style="">说话要有证据，官网上这样说：</p>
<p style="">Client&nbsp;connections&nbsp;are meant to be long-lived. The underlying protocol is designed and optimized for long running connections. That means that opening a new connection per operation, e.g. a message published, is unnecessary and strongly discouraged as it will introduce a lot of network roundtrips and overhead.</p>
<p style="">Channels&nbsp;are also meant to be long-lived but since many recoverable protocol errors will result in channel closure, channel lifespan could be shorter than that of its connection. Closing and opening new channels per operation is usually unnecessary but can be appropriate. When in doubt, consider reusing channels first.</p>
<p style="">Channel-level exceptions&nbsp;such as attempts to consume from a queue that does not exist will result in channel closure. A closed channel can no longer be used and will not receive any more events from the server (such as message deliveries).&nbsp;</p>
<p style=""></p>
<p style="">翻译一下：</p>
<p style="">客户端连接是要长久存活的，基础协议(AMQP协议)里做了连接要长久存活的优化。就是说为了发一条消息而打开一个新的连接没有必要，而且强烈不推荐。因为会带来额外的通信开销。</p>
<p style="">Channel也是长久存活的，但是由于许多可以恢复的错误会导致Channel关闭，所以channel的生命周期会比连接短。每次操作都关闭和打开新Channel，没有必要但是却可以是比较合适的选择。如果不知道怎么做更好的时候，优先复用Channel。</p>
<p style="">Channel级别的异常比如尝试消费一个不存在的队列会导致Channel关闭。一个关闭的Channel不会再被使用，也不会再收到任何如消息传递之类的服务端事件。</p>
<p style=""></p>
<p style="">简单来说，Channel是为了复用连接产生的，如果Channel异常了，就可以直接关闭Channel不至于整个连接都关闭，减少开销。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">Java的Channel和MQ的Channel比较</span></strong></p>
<p style="">本质上，不管是Java的Channel还是MQ的Channel都为了连接的复用而生。所以Java的Channel出现在java.nio包里，做的是io多路复用的事情。只是因为Channel是个逻辑概念，究竟是先产生Channel，还是必须依附于连接由设计者自己说了算。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">很多公司都有渠道(Channel)一说，但是不同的场景意义各不相同。比如：业务渠道、支付渠道。本质上，这些渠道的对端各有不同，又希望复用，才产生了网关。从这个意义上来说，网关非常合适用多路复用来解释。</p>
<p style=""></p>
<p style="">Channel这个概念是理解NIO、netty的基础，也是理解MQ底层通信过程的基础。不管是什么类型的MQ，在创建连接时都需要同时配置channel，只不过有时候使用了约定大于配置，不是显式的。其实这一篇我写了万把字。太长了所以拆成了几篇，看完这几篇，我预期上很多之前看不懂的源会清晰很多。</p>]]></description><guid isPermaLink="false">/archives/shen-ru-li-jie-mqsheng-chan-duan-de-di-ceng-tong-xin-guo-cheng-li-jie-channel</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:32:48 GMT</pubDate></item><item><title><![CDATA[状态保持解决方案-分布式session的历史血脉]]></title><link>https://wiki.dgfygk.com/archives/zhuang-tai-bao-chi-jie-jue-fang-an-fen-bu-shi-sessionde-li-shi-xie-mai</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%8A%B6%E6%80%81%E4%BF%9D%E6%8C%81%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88-%E5%88%86%E5%B8%83%E5%BC%8Fsession%E7%9A%84%E5%8E%86%E5%8F%B2%E8%A1%80%E8%84%89&amp;url=/archives/zhuang-tai-bao-chi-jie-jue-fang-an-fen-bu-shi-sessionde-li-shi-xie-mai" width="1" height="1" alt="" style="opacity:0;">
<p style="">上篇文章<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486245&amp;idx=1&amp;sn=494e71a356ad4bd26633b5ae5278215f&amp;chksm=fafde18bcd8a689de3cfa45c0bbe629bc098d26a85106caff62b0bfa069055374515865a3b26&amp;scene=21#wechat_redirect" target="_blank">《网络通信之Session的历史血脉》</a>是围绕着理解底层通信来的。因为不管是MQ还是持久层框架中，建立连接后都使用了session来保持或者保存状态数据。</p>
<p style=""></p>
<p style="">上篇文章和<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486186&amp;idx=1&amp;sn=f21e17b21beb4c99cef0e076071ac859&amp;chksm=fafde044cd8a69526aa3ec9edbe71ea5aa41de2ef236494201dc2f20701ed8abc6be1a72344f&amp;scene=21#wechat_redirect" target="_blank">《深入理解MQ生产端的底层通信过程-理解channel》</a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486210&amp;idx=1&amp;sn=fd7a60ca9b4e41f942dd0b2653228774&amp;chksm=fafde1accd8a68ba4f41f19d3543984f47756e884eb395b295cc7f4f1cd0575bc0305fba5431&amp;scene=21#wechat_redirect" target="_blank">《网络字节序列-大端序和小端序》</a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486242&amp;idx=1&amp;sn=483566d3fd97f333d3ea114dbbe8f7d1&amp;chksm=fafde18ccd8a689abfcb9959079a3cbfc4ecf98e52d34318bf9490422622bddb0c4b81ef2288&amp;scene=21#wechat_redirect" target="_blank">《https引起的跨域问题-COE&amp;casestudy》</a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486103&amp;idx=1&amp;sn=943c5b7f3558df78164321806f273c8f&amp;chksm=fafde039cd8a692f86f0ac97b0e1e05ece075de3e4f33e6a5ec90b269c51aed1963ff3107e0e&amp;scene=21#wechat_redirect" target="_blank">《懂得三境界-使用dubbo时请求超过问题》</a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485988&amp;idx=1&amp;sn=efad5a030bc0a933ac5af2ee09f3c6c4&amp;chksm=fafde08acd8a699c198d0d65c413273f05f2796cfd873160c2ba81ed3f33aebfaccb62234cb7&amp;scene=21#wechat_redirect" target="_blank">《一个http请求进来都经过了什么(2021版)》</a>是一个系列。只是用不同的方法在讲底层通信。中间尝试了很多方法和形式，旨在让大家能看下去，深层理解。生怕因为太过枯燥令大家前功尽弃、半途而废。</p>
<p style=""></p>
<p style="">因为在上一篇里有朋友评论里提到了分布式session。其实分布式session本身是有点脱离我的文章大纲的，但是既然有朋友提出来了，总得把事情讲明白，所以加更了这篇。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">分布式session解决了什么问题？</span></strong></p>
<p style=""></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484368&amp;idx=1&amp;sn=b3c8d41b98ecf2b976d0b025c0322055&amp;chksm=fafde97ecd8a6068cc4d25f2766261870a67baa4cbf9dc0d4920302fcf09e74ee682025efabc&amp;scene=21#wechat_redirect" target="_blank">《稳定性「三十六计」- 无状态化》</a>里我提到：<span fontsize="" color="">随着容器化、云原生等的流行，DevOps团队也在不断鼓吹「以无状态为荣，以有状态为耻」。因为有状态的服务难以部署、难以扩展。</span></p>
<p style=""></p>
<p style=""><span fontsize="" color="">「</span>状态<span fontsize="" color="">」</span>其实对用户体验而言是个好东西。保存用户的登录状态；浏览器异常关闭再次打开恢复用户之前的窗口状态；记录用户的偏好状态……于是勤于动脑的软件工程师们就想了：怎么既能拥有无状态化的扩展性，又拥有有状态时的良好用户体验呢？</p>
<p style=""></p>
<p style="">答案就是分布式session。它更像是一种业务问题的解决方案，而上篇文章所讲的session更合适的描述是一种技术设计抽象。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-obet.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">常用的分布式session解决方案</span></strong></p>
<p style=""></p>
<h3 style="text-align: start; " id="2007%E5%B9%B4%E8%87%B3%E4%BB%8A-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%AD%98%E5%82%A8"><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">2007年至今-客户端存储</span></strong></h3>
<p style=""><strong>使用cookie</strong></p>
<p style=""></p>
<p style="">客户端存储是指直接将信息存储在cookie中。客户端通过http协议和服务器进行cookie交互，通常用来存储一些不敏感信息。它有很明显的缺点：</p>
<ul>
 <li>
  <p style="">数据存储在客户端，存在安全隐患</p>
 </li>
 <li>
  <p style="">cookie存储大小、类型存在限制</p>
 </li>
 <li>
  <p style="">数据存储在cookie中，如果一次请求cookie过大，会给网络增加更大的开销</p>
 </li>
</ul>
<p style=""></p>
<p style="">虽然这种方式有缺点，但是可以通过合理的控制减少副作用。所以我从07年参加工作时就在用这种方法，现在的系统也还在用。JWT就是其中一个典型，它长度小，信息做了加密，一般仍然是存储在cookie中解决单点登录问题。</p>
<p style=""></p>
<p style=""><strong>JWT</strong></p>
<p style=""></p>
<p style="">JWT：Json Web Token，是基于Json的一个公开规范，这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息，他的两大使用场景是：认证和数据交换。</p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-srgs.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">由服务端根据规范生成一个令牌（token），并且发放给客户端。此时客户端请求服务端的时候就可以携带者令牌，以令牌来证明自己的身份信息。</p>
<p style=""></p>
<p style="">2009年开始我进行互联网领域，有几年和JWT打交道比较多。因为那时候，你想不想做全栈都是全栈，因为前后端不分离，做什么都是一坨。也就是近7年更专注做后端了。那时候记忆比较深的就是cookie里存着一段用很简易的加密做的字符串，用户名信息等关键信息之间用一个下划线隔开，还有一个带有效期的随机数字段，数据库里也存一份。匹配上了就认证成功用户登录状态session有效。</p>
<p style=""></p>
<h2 style="" id="%E6%B2%A1%E5%90%AC%E8%AF%B4%E8%B0%81%E5%AE%B6%E7%94%A8%E8%BF%87-session%E4%BC%9A%E8%AF%9D%E4%BF%9D%E6%8C%81%EF%BC%88%E9%BB%8F%E6%BB%9E%E4%BC%9A%E8%AF%9D%EF%BC%89"><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">没听说谁家用过-session会话保持（黏滞会话）</span></strong></h2>
<p style=""></p>
<p style="">会话保持是利用负载均衡的原地址Hash算法实现，负载均衡服务器总是将来源于同一IP的请求分发到同一台服务器上，，也可以根据cookie信息将同一个用户的请求每次都分发到同一台服务器上，不过这时的负载均衡服务器必须工作在HTTP协议层上。这种会话保持也叫黏滞会话(Sticky Sessions)。</p>
<p style=""></p>
<p style="">这种方案虽然保证了每个用户都能准确的拿到自己的session，而且大量用户访问也不怕，但是这种会话保持不符合系统高可用的需求。这种方案有着致命的缺陷：一旦某台服务器发生宕机，则该服务器上的所有session信息就会不存在，用户请求就会切换到其他服务器，而其他服务器因为没有其对应的session信息导致无法完成相关业务。所以这种方法基本上不会被采纳。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">2010年-服务器session复制</span></strong></p>
<p style=""></p>
<p style="">原理：任何一个服务器上的session发生改变（增删改），该节点会把这个 session的所有内容序列化，然后广播给所有其它节点，以此来保证Session同步。</p>
<p style="">优点：可容错，各个服务器间session能够实时响应。</p>
<p style="">缺点：会对网络负荷造成一定压力，如果session量大的话可能会造成网络堵塞，拖慢服务器性能。</p>
<p style=""></p>
<p style="">我在10年的时候，在公司举行了一个讲座上听人家说过，自己没用过。对这个印象也不深：因为当初那个讲座是leader非要我去的，我坐在第一排，然后……睡着了。因为确实也比较无聊，互联网场景下很少有场景需要这样用。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">2011年至今-状态集中存储</span></strong></p>
<p style=""></p>
<p style="">2011年，我经过两年的努力，终于在公司里，从一个写业务代码的开发，转成了一个中间件开发者。当时让我一个负责约等于是开发一个zookeeper吧。那时候zookeeper还没有普及，当时很多公司都在开发自己的中间件。</p>
<p style="">
 <br>
 一个下午，我在测试环境发布了一个不成熟的版本，自己的服务挂了。一屋子的人都站了起来。因为我这边挂了，其他人的模块都跑不起来。好像有点跑题了，总之，从那之后，市面上开始出现了各种集中式的状态存储：zookeeper、memcache缓存，其实数据库也是一种。
</p>
<p style=""></p>
<p style="">本质上，对于Java开发者来说，session存储的东西只是从JVM内移到了JVM外。而这种场景下会话保持就相当于是按照hash值进行路由，session复制就相当于集中存储的副本了。也就是说<span style="font-size: 16px">session会话保持和服务器session复制实际</span>上是现代状态集中存储的基础原理，只不过是管理方由业务开发人员转移到中间件管理端，降低了对业务开发人员的开发管理成本。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">session使用注意事项</span></strong></p>
<p style=""></p>
<p style="">15年时，我们线上发生过一个问题：我正在验证一个线上功能。为了更好的验证，用chrome浏览器登录一个用户，用IE登录另外一个用户。这时候，我刷新了IE页面，结果IE用户突然自己变成了chrome的用户。以多年做技术的敏锐嗅觉，我意识到在组件层面出了问题。于是将这个问题立即上报了技术经理。技术经理听了我的报告，联系了负责CDN的团队。经过他们的排查，CDN的静态缓存策略设置的有问题，他们立即更新了策略解决了问题。（现在想来一身冷汗啊，随随便便就操作生产了？）</p>
<p style=""></p>
<p style="">上面的教训告诉咱们：使用session要十分注意对请求解析的筛选。</p>]]></description><guid isPermaLink="false">/archives/zhuang-tai-bao-chi-jie-jue-fang-an-fen-bu-shi-sessionde-li-shi-xie-mai</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:26:26 GMT</pubDate></item><item><title><![CDATA[网络字节序列-大端序和小端序]]></title><link>https://wiki.dgfygk.com/archives/wang-luo-zi-jie-xu-lie-da-duan-xu-he-xiao-duan-xu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%BB%9C%E5%AD%97%E8%8A%82%E5%BA%8F%E5%88%97-%E5%A4%A7%E7%AB%AF%E5%BA%8F%E5%92%8C%E5%B0%8F%E7%AB%AF%E5%BA%8F&amp;url=/archives/wang-luo-zi-jie-xu-lie-da-duan-xu-he-xiao-duan-xu" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></p>
<p style=""></p>
<p style="">从<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485988&amp;idx=1&amp;sn=efad5a030bc0a933ac5af2ee09f3c6c4&amp;chksm=fafde08acd8a699c198d0d65c413273f05f2796cfd873160c2ba81ed3f33aebfaccb62234cb7&amp;scene=21#wechat_redirect" target="_blank">《一个http请求进来都经过了什么(2021版)》</a>到<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486103&amp;idx=1&amp;sn=943c5b7f3558df78164321806f273c8f&amp;chksm=fafde039cd8a692f86f0ac97b0e1e05ece075de3e4f33e6a5ec90b269c51aed1963ff3107e0e&amp;scene=21#wechat_redirect" target="_blank">《懂得三境界-使用dubbo时请求超过问题》</a>，再到<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a>和<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486186&amp;idx=1&amp;sn=f21e17b21beb4c99cef0e076071ac859&amp;chksm=fafde044cd8a69526aa3ec9edbe71ea5aa41de2ef236494201dc2f20701ed8abc6be1a72344f&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">深入理解MQ生产端的底层通信过程-理解channel》</span></a><span style="font-size: 16px">，我的思路是一篇篇的深入网络通信底层。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">到了通信底层，必须要了解的一个知识是网络字节序列。这个东东，99%以上的开发中都用不到。but:</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px">首先这个问题比较好理解；第二，要手动处理二进制流的话就不得不考虑，否则可能测试的时候完全没有发现问题，到线上出现诡异事件；第三，这个知识如此之重要，我记得大学的时候课本上也讲过。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">字节序列的历史</span></strong></p>
<p style=""></p>
<p style="">字节序列分成大端序（big endian或称大尾序）和小端序（little endian或称小尾序）。那为什么大小尾序会称为大小端序呢？</p>
<p style=""></p>
<p style="">术语“little endian(小端)”和“big endian(大端)”出自Swift的《格列佛游记》（Gulliver's Trabels）一书，其中交战的两个派别无法就应该从哪一端（小端还是大端）打开一个半熟的鸡蛋达成一致。就像鸡蛋的问题一样，选择何种字节顺序没有技术上的理由。</p>
<p style=""></p>
<p style="">“小”国和“大”国，这两大强国在过去三年一直在苦战。战争开始是由于：我们大家都认为，吃鸡蛋前，原始的方法是打破鸡蛋较大的一端，可是“小”国的祖父小时候吃鸡蛋，一次按古法打鸡蛋是碰巧将一个手指弄破了，因此他的父亲，当时的皇帝，就下了一道敕令，命令全体臣民吃鸡蛋时打破鸡蛋较小的一端，违令者重罚。老百姓们对这项命令极为反感。历史告诉我们，由此曾发生过六次叛乱，其中一个皇帝送了命，另一个丢了王位。这些叛乱大多都是由“大”国的人们煽动起来的。因为叛乱平息后，流亡的人总是逃到“大”国去寻救避难，“大”国的人口就增加了。据估计，先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端，曾出版过几百本大部著作，不过大端派的书一直是受禁的，法律也规定该派的任何人不得做官。</p>
<p style=""></p>
<p style="">Swift是在讽刺英国和法国之间的持续的冲突。Danny Cohen，一位网络协议的早期开创者，第一次使用这两个术语来指代字节顺序，后来这个术语被广泛接纳了。因为产生两种序列的标准和Swift讲的故事如出一辙：几十年前，两强互相看不惯，摩托罗拉(Motorala)使用大端序，英特尔(Intel)使用小端序。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">大端序和小端序的理解</span></strong></p>
<p style=""></p>
<p style="">数值例如0x2266使用两个字节储存：高位字节是0x22，低位字节是0x66。</p>
<blockquote>
 <ul>
  <li>
   <p style="">大端字节序：高位字节在前，低位字节在后，这是人类读写数值的方法。</p>
  </li>
  <li>
   <p style="">小端字节序：低位字节在前，高位字节在后，即以0x6622形式储存。</p>
  </li>
 </ul>
</blockquote>
<p style="">0x1234567大端字节序和小端字节序的写法如下图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ejrx.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">打个比方：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-togg.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">计算机电路先处理低位字节，效率比较高，因为计算都是从低位开始的。所以，计算机的内部处理都是小端字节序。但是，人类还是习惯读写大端字节序。所以，除了计算机的内部处理，其他的场合几乎都是大端字节序，比如网络传输和文件储存。</p>
<p style=""></p>
<p style="">结论：网络字节序列传输采用大端序。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">标准不同引发的问题</span></strong></p>
<p style=""></p>
<p style="">2016年我做了一款用于处理图片位置被旋转的方法和工具，并成功申请了专利。起因是这样的：</p>
<p style=""></p>
<p style="">做过一个项目，有天运营打电话过来说：“有个用户上传了头像到咱们应用上，结果头像是倒置的。”我去后台一看果然是和练倒立一样倒置着脑袋。我们自己测试的时候，从来没有发生这种问题。但是用户所有的声音都是有道理的。当时我们动员了公司所有人用我们的应用看看有没有类似的情况。终于在一个三星的手机上重现了这个问题。</p>
<p style=""></p>
<p style="">原来照片属性中是存储了旋转信息的，我们之所以看到照片显示的位置都很正确，其实是iphone系统，还有大部分的安卓系统发现图片被旋转了之后都有将图片旋转回来的处理。但是在部分android系统中，却没有做这种处理。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pluw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">上面两张图片，每张图片被旋转了旋转了8个角度。这里面只有它们的EXIF信息里的orientation值不同。EXIF信息是数码相机的拍摄信息，在压缩文件的格式中只有JPEG格式的储存了这种信息。它可以有1-8的8个值。分别对应于旋转0度，水平翻转，旋转180度，垂直翻转，顺时针90度—+水平翻转，顺时针90度，顺时针90度+垂直翻转，逆时针90度。这时就会发生这种情况。</p>
<p style=""></p>
<p style="">经过调查发现目前并没有现成的java类库来做这件事情，基本都是从系统层面就解决了这个问题。确实有图片处理的类库，可是添加一个大jar包进来就只为获取图片的旋转信息，jar包可有将近一个MB呢。所以咱们尽量还是自己写工具类来解决问题吧。因为这种解决这种问题不涉及其他的处理，这时只用这样一个针对特定问题的工具类增加了代码的可读性，减少了服务器负担。</p>
<p style=""></p>
<p style="">实现原理简单可以理解为：存储图片的旋转信息都是放在EXIF信息里。所以只要找到EXIF信息，根据EXIF信息里的旋转方向将图片旋转回来即可。当然实际上各种考虑要复杂的多。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">后记</span></strong>
 <br></p>
<p style="">还记得<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485790&amp;idx=1&amp;sn=28d1275aec18350f7627e81bbdab4dfc&amp;chksm=fafde3f0cd8a6ae60b893b6998175db1c20e96328a5721a1b00a2d7b231d141ba7e85f02c6f0&amp;scene=21#wechat_redirect" target="_blank">《面试中一个暴露能力等级的问题》</a>那篇文章吗？相信只要在工作中遇到问题仔细思考，不放过，一定能说出让面试官眼前一亮的故事。</p>]]></description><guid isPermaLink="false">/archives/wang-luo-zi-jie-xu-lie-da-duan-xu-he-xiao-duan-xu</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:23:15 GMT</pubDate></item><item><title><![CDATA[https引起的跨域问题-COE&casestudy]]></title><link>https://wiki.dgfygk.com/archives/httpsyin-qi-de-kua-yu-wen-ti-coe-casestudy</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=https%E5%BC%95%E8%B5%B7%E7%9A%84%E8%B7%A8%E5%9F%9F%E9%97%AE%E9%A2%98-COE%26casestudy&amp;url=/archives/httpsyin-qi-de-kua-yu-wen-ti-coe-casestudy" width="1" height="1" alt="" style="opacity:0;">
<p style="">问题很简单，开发一个后台服务。这个服务呢，包含了几个团队的后台服务之间的相互调用。这些后台服务之间的顶级域名(相关概念后面会解释)相同，请求却报错说有跨域问题。经调查，原因是调用方是http服务，被调用方是https服务，引起的跨域问题。如下图所示：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-adnp.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">这里假设问题到生产环境才发现，本篇使用COE的写法来论述问题。COE(correction of error)更正错误，是很多公司用来做问题或者事故复盘的手段。COE因为名称中带着“有人犯错啦”的意思，很多公司为了表明“追究责任不是目的，重要的是吸取教训，避免同样和同类的问题”，把COE叫做casestudy个案研究。实践证明，不管叫什么，恐惧一点也不会减少。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-upcs.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="text-align: center; "></p>
<p style=""><strong><span style="font-size: 20px">事故描述
   <br></span></strong></p>
<p style="">red服务从前端调用gray服务时报错：跨域错误。经排查确认由于http的域名下从前端调用https导致。</p>
<p style=""><strong><span style="font-size: 20px">
   <br>
  </span></strong></p>
<p style=""><strong><span style="font-size: 20px">事故责任人</span></strong></p>
<p style="">都是我的错</p>
<p style=""><strong><span style="font-size: 20px">
   <br>
  </span></strong></p>
<p style=""><strong><span style="font-size: 20px">事故影响
   <br></span></strong></p>
<p style="">新服务上线，无正式业务接入，无业务影响。</p>
<p style=""><strong><span style="font-size: 20px">
   <br>
  </span></strong></p>
<p style=""><strong><span style="font-size: 20px">时间线
   <br></span></strong></p>
<p style="">14:00 灰度发布新版本并进行验证</p>
<p style="">14:20&nbsp;验证发现使用Chrome浏览器通过【检查】-【网络】功能观察到发起了一个OPTIONS类型的COR跨域请求，请求报错：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mbiy.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">15:00&nbsp;确认是http访问https导致违反了同源策略，导致跨域问题。新服务上线，无正式业务接入，无业务影响。所以未进行版本回滚。</p>
<p style=""><strong><span style="font-size: 20px">
   <br>
  </span></strong></p>
<p style=""><strong><span style="font-size: 20px">根本原因分析
   <br></span></strong></p>
<p style="">1、为什么会发生问题？</p>
<p style="">出于安全原因，浏览器限制从脚本内发起的跨源HTTP请求。当一个资源从与该资源本身所在的服务器不同源，请求一个资源时，资源会发起一个跨域 HTTP 请求。</p>
<p style="">同源的判断标准是：</p>
<ul>
 <li>
  <p style="">协议</p>
 </li>
 <li>
  <p style="">域名</p>
 </li>
 <li>
  <p style="">端口号</p>
 </li>
</ul>
<p style="">以上三者都要相同。发生问题的请求，由于其中域名默认只要求顶级域名相同，此条件满足；协议一个是https，一个是http，不满足；https端口为443，http端口为80，不满足。总体不满足同源策略，而且被访问的后端没有做跨域处理。</p>
<p style="">2、为什么没有做跨域处理？</p>
<p style="">因为顶级域名相同，并未考虑到有跨域问题。且整个在测试环境下测试通过，并未发生问题。</p>
<p style="">3、为什么测试环境没有问题？</p>
<p style="">因为测试环境，同时支持http和https两种方式，当时配置时没有考虑两者直接的差异，直接使用http的路径。</p>
<p style="">在生产环境，根据安全的要求，关闭了http方式，只能使用https方式，造成问题。</p>
<p style="">4、为什么调用端使用http协议？</p>
<p style="">因为调用端的框架是几年前的老系统，线上环境追求稳定性，变更成本高，目前还维持之前的现状。</p>
<p style="">5、为什么调用方是post调用，实际上的请求却是options?</p>
<p style="">出于安全考虑，并不是所有域名访问后端服务都可以。其实在正式跨域之前，浏览器会根据需要发起一次预检（也就是option请求）。预检请求不成功，不会发起正式请求。</p>
<p style="">浏览器将CORS请求分为两类：简单请求（simple request）和非简单请求（not-simple-request）,简单请求浏览器不会预检，而非简单请求会预检。</p>
<p style="">同时满足下列三大条件，就属于简单请求，否则属于非简单请求</p>
<ul>
 <li>
  <p style="">请求方式只能是：GET、POST、HEAD</p>
 </li>
 <li>
  <p style="">HTTP请求头限制这几种字段：Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID</p>
 </li>
 <li>
  <p style="">Content-type只能取：application/x-www-form-urlencoded、multipart/form-data、text/plain</p>
 </li>
</ul>
<p style="">对于简单请求，浏览器直接请求，会在请求头信息中，增加一个origin字段，来说明本次请求来自哪个源（协议+域名+端口）。服务器根据这个值，来决定是否同意该请求，服务器返回的响应会多几个头信息字段，如图所示：上面的头信息中，三个与CORS请求相关，都是以Access-Control-开头。</p>
<ul>
 <li>
  <p style="">Access-Control-Allow-Origin：该字段是必须的，* 表示接受任意域名的请求，还可以指定域名</p>
 </li>
 <li>
  <p style="">Access-Control-Allow-Credentials：该字段可选，是个布尔值，表示是否可以携带cookie，（注意：如果Access-Control-Allow-Origin字段设置*，此字段设为true无效）</p>
 </li>
 <li>
  <p style="">Access-Control-Allow-Headers：该字段可选，里面可以获取Cache-Control、Content-Type、Expires等，如果想要拿到其他字段，就可以在这个字段中指定。</p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">经验教训
   <br></span></strong></p>
<p style="">首先是知识方面：之前对同域有误解，认为只需要顶级域名相同。实际上需要符合域名、协议和端口三者同源的同源策略。</p>
<p style="">其次是没有对线上线下的差异做仔细的调研分析。</p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">
   <br>
  </span></strong></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">后续优化</span></strong></p>
<p style="">首先要修复问题，跨域有风险。可以使用调用方自带的代理功能。被调用方提供一个RPC调用，调用方使用代理来转换。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">小知识</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">根域名</span></strong></p>
<p style="">简单的来说就是类似.com这种，或者中国的.cn英国的.uk日本的.jp，这些是由“互联网名称与数字地址分配机构”（The Internet Corporation for Assigned Names and Numbers ，简称ICANN）来分配的。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">顶级域名</span></strong></p>
<p style="">简单来说顶级域名就是就是在根域名的前面加上你自己定义的字母或数字等字符串包括连字符-（包括连字符但是不能连续是连字符，且连字符不能在第一个）例如这样web.com或者web.cn这样的既是顶级域名。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">二级域名</span></strong></p>
<p style="">当你注册了一个顶级域名后，例如web.cn，你就可以在你的互联网服务提供商的dns解析系统上自由分配你的二级域名或者三级域名等等，web1.web.cn,web2.web1.web.cn等等，以此类推根域名前面有几个字符串就是几级域名。</p>]]></description><guid isPermaLink="false">/archives/httpsyin-qi-de-kua-yu-wen-ti-coe-casestudy</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:18:37 GMT</pubDate></item><item><title><![CDATA[池式结构-连接池]]></title><link>https://wiki.dgfygk.com/archives/chi-shi-jie-gou-lian-jie-chi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B1%A0%E5%BC%8F%E7%BB%93%E6%9E%84-%E8%BF%9E%E6%8E%A5%E6%B1%A0&amp;url=/archives/chi-shi-jie-gou-lian-jie-chi" width="1" height="1" alt="" style="opacity:0;">
<p style="">分布式协调系统如zookeeper、etcd，客户端都是使用一个长连接。记得之前使用k8s客户端(核心是etcd)时候，需要自己实现close()接口，我在close接口里判断如果连接断了，就重新连接。zookeeper的客户端实现也是大同小异。</p>
<p style=""></p>
<p style="">但是像数据库连接、MQ连接这种，更常见的实现方式是连接池。本文采用<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484931&amp;idx=1&amp;sn=756db99b2fa4ce9c5e9f168d8799d692&amp;chksm=fafdecadcd8a65bb81a0e153e453c2d090c48c73fc7457ccf2b7255f497c441170b651e060e5&amp;scene=21#wechat_redirect" target="_blank">【<strong><span style="font-size: 18px">情境型</span></strong>】</a>逻辑结构来介绍连接池的能力和使用注意事项。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nblf.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">能力-为什么要使用连接池</span></strong></p>
<p style=""></p>
<p style="">本文开头提到zookeeper和etcd客户端使用的是单一长连接，而不是连接池。咱们举两个典型场景就能明白这些大牛设计者的苦心。</p>
<p style=""></p>
<p style="">zookeeper的典型使用场景是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485181&amp;idx=1&amp;sn=df4568317a038fcff3b77cd66d0b7bb6&amp;chksm=fafdec53cd8a654573d4210e755fac7c95b5145aa8a5c76cb3df64478a44da0446945ef04c19&amp;scene=21#wechat_redirect" target="_blank">服务治理</a>。比如使用dubbo这种服务治理框架，全公司的服务都在向zookeeper读写数据。如果zookeeper的客户端是开放出来的连接池，那zookeeper服务端要维持的连接数就是：全公司的服务数*连接池大小。ulimit(linux系统命令用来查询文件句柄数)数很快就不够用了。</p>
<p style=""></p>
<p style="">etcd的典型使用场景是k8s。k8s一般用来管理整个公司的服务器集群。同理，全公司的服务器都在向etcd读写数据。如果etcd的客户端是开放出来的连接池，那etcd服务端要维持的连接数就是：全公司的服务器数*连接池大小。ulimit数会更快不够用的。</p>
<p style=""></p>
<p style="">zookeeper和etcd都是主备类服务，只有一个主服务在提供写能力，不可以横向扩容。所以这种情况下，客户端连接使用连接池，那简直就是连接上的灾难。而由于处理的发布订阅数据传输本身就很小，使用连接池简直是：琉璃瓦盖鸡窝——大材小用。</p>
<p style=""></p>
<p style="">但是像数据库实例这样，使用方是固定的，很多公司的生产环境一个数据库实例是一个团队单独使用的。使用方数量固定，连接的机器几千台顶天了。而且很多业务逻辑就是CURD，面向数据库编程，传输信息量很大，就不得不使用连接池了。</p>
<p style=""></p>
<p style="">数据库连接池是个相对复杂的处理，一些开源大牛也会很容易写出来bug。下面咱们通过具体情境来了解一下数据库连接池使用的要点。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">情境1-druid 1.0.29版本连接失效bug</span></strong></p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nwmo.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">上图展示了客户端通过连接池连接服务器的过程。咱们平时使用druid连接池要配置很多的参数，这些参数一部分是控制伸缩性的参数：最小连接数、最大连接数等。另外一部分是探活参数，如：空闲连接触发检查阈值</p>
<h2 style="" id="timebetweenevictionrunsmillis">timeBetweenEvictionRunsMillis</h2>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zrxw.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">连接池管理的复杂，从上图可以管中窥豹-可见一斑。一个线程专门用来创建和管理连接，一个专门负责取连接。druid 1.0.29版本连接失效bug就发生在图中红框的连接是否可用部分。</p>
<p style=""></p>
<pre><code>DruidDataSource中有个检查连接是否有效的方法：</code></pre>
<pre><code>public void validateConnection(Connection conn) throws SQLException {    String query = this.getValidationQuery();    if (conn.isClosed()) {        throw new SQLException("validateConnection: connection closed");    } else if (this.validConnectionChecker != null) {        boolean result = true;        Exception error = null;
        try {            result = this.validConnectionChecker.isValidConnection(conn, this.validationQuery, this.validationQueryTimeout);        } catch (Exception var9) {            error = var9;        }
        if (!result) {            SQLException sqlError = error != null ? new SQLException("validateConnection false", error) : new SQLException("validateConnection false");            throw sqlError;        }    } else {        if (null != query) {            Statement stmt = null;            ResultSet rs = null;
            try {                stmt = conn.createStatement();                if (this.getValidationQueryTimeout() &gt; 0) {                    stmt.setQueryTimeout(this.getValidationQueryTimeout());                }
                rs = stmt.executeQuery(query);                if (!rs.next()) {                    throw new SQLException("validationQuery didn't return a row");                }            } finally {                JdbcUtils.close(rs);                JdbcUtils.close(stmt);            }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;}}</code></pre>
<p style="">注意上面代码第6行开始有个bug，result初始为true，检查连接是否异常，抛出异常直接捕获，result还是true，意思是校验没有问题。在服务端重启等场景下，这个检查会抛出socket相关异常。实际上服务端已经断开连接了。这时不会进行连接的销毁重建。造成使用了已经断开的连接来操作数据库，导致执行失败。</p>
<p style=""></p>
<p style="">这是生产环境真实发生过的案例。这个bug是个低级错误，后续版本已经修复了。建议不要用这个有坑版本。</p>
<p style=""></p>
<p style="">另外，建议常常做业务开发、使用数据库连接池的朋友结合下面常用参数参考看几遍DruidDataSource的源码。以便更好的了解参数的使用。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-acia.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">情境2-ActiveMQ未使用连接池连挂服务端</span></strong></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">
   <br>
  </span></strong></p>
<p style="">activeMQ有个是否使用连接池的配置：</p>
<p style="">spring.activemq.pool.enabled=false</p>
<p style="">这里false代表不使用连接池，不使用连接池在activeMQ里表达的意思是：<span style="font-size: 18px; color: rgb(73, 80, 87)">每发送一条数据创建一个连接</span>。于是有个同学进行了这样的配置以后，服务端出现大量的<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486403&amp;idx=1&amp;sn=9dbbda3df1deeb5f43c8b7f2cdd87b97&amp;chksm=fafde16dcd8a687be735bf315fa50773cd61ac48f47cd1a1304c792bc1fafa31b4a3705dd9a3&amp;scene=21#wechat_redirect" target="_blank">time_wait</a>。time_wait表示服务端和客户端已经在进行四次挥手断开连接中了。</p>
<p style=""></p>
<p style="">keepalive探活会探测到连接不可用就会进行这个断开连接操作。断连操作有一步很耗时，就是time_wait，因为它有2MSL的等待时间。很多连接的最终释放都卡在这里了。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jhxz.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">老连接不释放，服务器资源一直占用。新连接建立就会失败，表现出来就是服务器挂了。连接参数配置需谨慎：</p>
<pre><code>#true表示使用连接池spring.activemq.pool.enabled=true#连接池最大连接数spring.activemq.pool.max-connections=5#空闲的连接过期时间，默认为30秒spring.activemq.pool.idle-timeout=30000#强制的连接过期时间，与idleTimeout的区别在于：idleTimeout是在连接空闲一段时间失效，而expiryTimeout不管当前连接的情况，只要达到指定时间就失效。默认为0，never spring.activemq.pool.expiry-timeout=0</code></pre>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">MSL解释</span></strong></p>
<p style=""></p>
<p style="">最后解释一下time_wait的2MSL。MSL是什么：</p>
<p style="">MSL是Maximum Segment Lifetime英文的缩写，中文可以译为“报文最大生存时间”，它是任何报文在网络上存在的最长时间，超过这个时间报文将被丢弃。因为tcp报文（segment）是ip数据报（datagram）的数据部分，而ip头中有一个TTL域，TTL是time to live的缩写，中文可以译为“生存时间”，这个生存时间是由源主机设置初始值但不是存的具体时间，而是存储了一个ip数据报可以经过的最大路由数，每经过一个处理他的路由器此值就减1，当此值为0则数据报将被丢弃，同时发送ICMP报文通知源主机。RFC 793中规定MSL为2分钟，实际应用中常用的是30秒，1分钟和2分钟等。</p>
<p style=""></p>
<p style="">&nbsp; &nbsp; TCP的TIME_WAIT状态也称为2MSL等待状态，当TCP的一端发起主动关闭，在发出最后一个ACK包后，即发送了第四次挥手的ACK包后就进入了TIME_WAIT状态，必须在此状态上停留两倍的MSL时间，等待2MSL时间主要目的是怕最后一个ACK包对方没收到，那么对方在超时后将重发第三次挥手的FIN包，主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用，要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。</p>]]></description><guid isPermaLink="false">/archives/chi-shi-jie-gou-lian-jie-chi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:14:41 GMT</pubDate></item><item><title><![CDATA[HTTP状态码1XX深入理解]]></title><link>https://wiki.dgfygk.com/archives/httpzhuang-tai-ma-1xxshen-ru-li-jie</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=HTTP%E7%8A%B6%E6%80%81%E7%A0%811XX%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3&amp;url=/archives/httpzhuang-tai-ma-1xxshen-ru-li-jie" width="1" height="1" alt="" style="opacity:0;">
<p style="">前段时间看了《御赐小仵作》，里面有很多细节很有心。看了一些评论都是：终于在剧里能够看到真正在搞事业、发了工资第一时间还钱的正常人了。我印象比较深的是王府才能吃上的葡萄。觉得非常合理。剧里说的明明白白，是唐朝中晚期唐宣宗的时候，那时候丝绸之路刚刚开通，西域（现在的新疆以及更西的地方）的葡萄终于能吃上了，这就和那一整段历史给对应上了。</p>
<p style=""></p>
<p style="">谈到对应的问题，咱们回到正题，http状态码1XX。对于http状态200、404、500，大家可能熟悉一些。1XX可能实际中从来没有见过，今天咱们用剥洋葱的叙述方式，拨开1XX状态码的层层面纱。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(0, 209, 0)">定义</span></strong></p>
<p style=""></p>
<p style="">HTTP状态码（英语：HTTP Status Code）是用以表示网页服务器超文本传输协议响应状态的3位数字代码。这句话要注意解读了。我先来考考大家读懂了没。请回答：</p>
<p style=""></p>
<p style=""></p>
<p style="">这里我先不回答，无法决定的朋友再多读几遍定义。</p>
<p style=""></p>
<p style="">状态码1XX表示服务端已经收到了请求，但是还需要进一步处理。</p>
<p style=""></p>
<p style="">白居易有一首《问刘十九》：</p>
<p style="text-align: center; ">绿蚁新醅酒，红泥小火炉。
 <br>
 晚来天欲雪，能饮一杯无？</p>
<p style=""></p>
<p style="">这就和服务端可能会返回1XX的场景很像。客户端发起一个请求：我这里有美酒和暖炉，天要下雪了，来喝一杯？服务端收到之后返回1XX。代表接受邀请。这时候客户端就可以真正摆上一桌酒菜迎接客人了。如果服务端返回4XX，客户端也不用消耗资源杀鸡做菜了。</p>
<p style=""></p>
<p style="">1xx状态码是 HTTP/1.1 版本新定义的，用来表示请求被正常接收，会进行进一步处理。这些状态码相对较新，并且 HTTP/1.0 版本无法识别，所以原则上不应该向HTTP/1.0版本的客户端发送任何1xx状态码。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(0, 209, 0)">100 Continue</span></strong></p>
<p style="text-align: center; "></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cnje.jpg&amp;size=m" width="991px" height="558px" style="display: inline-block"></p>
<p style="">该状态码说明服务器收到了请求的初始部分，并请客户端继续发送。在服务器发送了 100 Continue 状态码之后，如果收到客户端的请求，则必须进行响应。</p>
<p style=""></p>
<p style="">这个状态码实际上是对如下场景的一种优化：客户端有一个较大的文件需要上传并保存，但是客户端不知道服务器是否愿意接受这个文件，所以希望在消耗网络资源进行传输之前，先询问一下服务器的意愿。实际操作为客户端发送一条特殊的请求报文，报文的头部应包含</p>
<pre><code>Expect: 100-continue</code></pre>
<p style="">此时，如果服务器愿意接受，就会返回 100 Continue 状态码，反之则返回 417 Expectation Failed 状态码。对于客户端而言，如果客户端没有发送实际请求的打算，则不应该发送包含 100 Continue Expect 的报文，因为这样会让服务器误以为客户端将要发送一个请求。大家可以基于对100的理解再回想一下《问刘十九》。</p>
<p style=""></p>
<p style="">之前提到过，并不是所有的HTTP应用都支持 100 Continue 这个状态码（例如HTTP/1.0及之前的版本的代理或服务器）所以客户端不应该在发送 100 Continue Expect 后一直等待服务器的响应，在一定时间后，客户端应当直接发送计划发送的内容。</p>
<p style=""></p>
<p style="">而对于服务器而言，也不应当把 100 Continue 当作一个严格的判断方法。服务器有可能在发送回应之前就收到了客户端发来的主体报文。此时服务器就不需要再发送 100 Continue 作为回应了。但仍然需要在接受完成后返回适当的状态码。理论上，当服务器收到一个 100 Continue Expect 请求时，应当进行响应。但服务器永远也不应向没有发送 100 Continue Expect 请求的客户端发送100 Continue 状态码作为回应。这里提到的应当进行响应是指：假设服务器不打算接收客户端将要发送的主体报文，也应当做适当的响应（例如发送 417 Expectation Failed）而不是单纯的关闭连接，这样会对客户端在网络层面上产生影响。</p>
<p style=""></p>
<p style="">作为代理的HTTP应用在收到带有 100 Continue Expect 的请求时，需要进行额外的判断。假设代理服务器明确知道报文下游的HTTP版本是兼容 HTTP/1.1 的，或者代理服务器不知道报文下游的版本，它都应当转发这条 100 Continue Expect 请求。但是如果代理服务器明确知道报文下游的应用无法处理 100 Continue Expect 的话，则应当直接向客户端返回 417 Expectation Failed 作为响应。而这也并非唯一的解决办法，另一种可行的办法是直接向客户端返回 100 Continue ，然后向下游传递删除了 100 Continue Expect 的报文。</p>
<p style=""></p>
<p style="">另外，如果代理服务器决定为 HTTP/1.0 及之前的版本服务的话，那么当它收到来自服务器的 100 Continue 响应报文时，则不应当向客户端转发这条响应，因为客户端很可能不知道如何处理该报文。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="">101 Switching&nbsp;Protocols</span></strong></p>
<p style=""></p>
<p style="">HTTP 101 Switching Protocol（协议切换）状态码表示服务器应客户端升级协议的请求对协议进行切换。</p>
<p style=""></p>
<p style="">此机制始终由客户端发起，并且服务器可能接受或拒绝切换到新协议。客户端可使用常用的协议（如HTTP / 1.1）发起请求，请求说明需要切换到HTTP / 2或甚至到WebSocket。</p>
<p style="text-align: start; ; line-height: inherit">我们来看一个实际的例子:</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aqpn.png&amp;size=m" width="677px" height="677px" style="display: inline-block"></p>
<p style=""></p>
<p style="">为了实现WebSocket通信，首先需要客户端发起一次普通HTTP请求。也就是说，WebSocket的建立是依赖HTTP的。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vehv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">其中HTTP头部字段Upgrade: websocket和Connection: Upgrade非常重要，告诉服务器通信协议将发生改变，转为WebSocket协议。支持WebSocket的服务器端在确认以上请求后，应返回状态码为101 Switching Protocols的响应:</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-osrh.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">其中字段Sec-WebSocket-Accept是由服务器对前面客户端发送的Sec-WebSocket-Key进行确认和加密后的结果，相当于一次验证，以帮助客户端确信对方是真实可用的WebSocket服务器。</p>
<p style=""></p>
<p style="">验证通过后，这个握手响应就确立了WebSocket连接，此后，服务器端就可以主动发信息给客户端了。此时的状态比较像服务器端和客户端接通了电话，无论是谁有什么信息想告诉对方，开口就好了。</p>
<p style=""></p>
<p style="">一旦建立了WebSocket连接，此后的通信就不再使用HTTP了，改为使用WebSocket独立的数据帧。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xzix.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(0, 209, 0)">102&nbsp;Processing</span></strong></p>
<p style="text-align: center; "></p>
<h3 style="" id="102-processing%E6%98%AF%E7%94%B1webdav%EF%BC%88rfc-2518%EF%BC%89%E6%89%A9%E5%B1%95%E7%9A%84%E7%8A%B6%E6%80%81%E7%A0%81%EF%BC%8C%E4%BB%A3%E8%A1%A8%E5%A4%84%E7%90%86%E5%B0%86%E8%A2%AB%E7%BB%A7%E7%BB%AD%E6%89%A7%E8%A1%8C%E3%80%82">102 Processing是由WebDAV（RFC 2518）扩展的状态码，代表处理将被继续执行。</h3>
<p style=""></p>
<p style="">Web服务器可能需要相当长的时间来处理复杂的请求。当客户端的浏览器发送包含多个涉及复杂需求的子请求的WebDAV请求时，服务器需要一些时间来处理并发送此代码“102–Processing”。此代码旨在通过通知客户端服务器收到请求并对其进行处理来避免客户端出现超时错误。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(0, 209, 0)">总结</span></strong></p>
<p style=""></p>
<p style="">HTTP状态码的设计规则是：前面一位是分类。2XX表示服务端已经收到了请求，并且已经分析处理完；3XX表示服务端已经收到了请求，但是还需要其他资源或者服务处理；4XX表示服务端已经收到了请求，但是无法理解，说明客户端请求姿势不正确；5XX表示服务端已经收到了请求，但是由于服务端自身问题无法正确响应。</p>
<p style=""></p>
<p style="">这种编码方法在很多地方都在用。</p>
<p style=""></p>
<p style="">比如身份证号规则</p>
<p style=""><span style="font-size: 16px">前1、2位数字表示：所在省（直辖市、自治区）的代码；第3、4位数字表示：所在地级市（自治州）的代码；第5、6位数字表示：所在区（县、自治县、县级市）的代码；第7—14位数字表示：出生年、月、日；第15、16位数字表示：所在地的派出所的代码；</span>第17位数字表示性别：奇数表示男性，偶数表示女性；<span style="font-size: 16px">第18位数字是校检码。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">比如行别代码</span></p>
<p style=""><span style="font-size: 16px">支付系统为每类参与者分配一个标识号，由</span><span fontsize="" color="">三位数字组成，第一位是银行类型，剩下两位是标识号。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">支付系统的参与者行号规则和身份证规则很像，除了每几位都有特殊含义，结尾一位也是验证码。反正参与者行号规则我是记下来了。默念了8遍记下来的，真不容易。</span></p>]]></description><guid isPermaLink="false">/archives/httpzhuang-tai-ma-1xxshen-ru-li-jie</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:08:28 GMT</pubDate></item><item><title><![CDATA[趣谈IO多路复用的本质]]></title><link>https://wiki.dgfygk.com/archives/qu-tan-ioduo-lu-fu-yong-de-ben-zhi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%B6%A3%E8%B0%88IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E7%9A%84%E6%9C%AC%E8%B4%A8&amp;url=/archives/qu-tan-ioduo-lu-fu-yong-de-ben-zhi" width="1" height="1" alt="" style="opacity:0;">
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486564&amp;idx=1&amp;sn=07ae2b4f11ceedb029f6351e3d67c2dc&amp;chksm=fafde6cacd8a6fdc9f5b0ce668bcc7b57655f3cb3c427152ae70cf9f96bb6a6b778689e74dc4&amp;scene=21#wechat_redirect" target="_blank">《轻松搞懂5种IO模型》</a>中，我发起了一个投票。</p>
<p style=""></p>
<p style=""></p>
<p style=""></p>
<p style="">答案是【同步IO多路复用】。目前，60%的朋友答对了。原因这里解释一下。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">同步和异步的概念区别</span></strong></p>
<p style=""></p>
<p style="">同步：线程自己去获取结果。(一个线程)</p>
<p style="">异步：线程自己不去获取结果，而由其他线程送结果。(至少两个线程)</p>
<p style=""></p>
<p style="">异步执行如下图所示，除非不需要知道结果，否则一般会有一个回调方法。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dyzz.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">IO多路复用的本质</span></strong></p>
<p style=""></p>
<p style="">为了彻底理解IO多路复用是同步还是异步，咱们探究一下IO多路复用的本质。</p>
<p style=""></p>
<p style="">I/O多路复用，复用的IO监听等待这条路。实际上就是用select/poll/epoll监听多个io对象，当io对象有变化（有数据）的时候就通知用户进程。好处就是单个进程可以处理多个socket。</p>
<p style=""></p>
<p style="">select/poll/epoll的优势并不是对于单个连接能处理得更快，而是在于能处理更多的连接。</p>
<p style=""></p>
<p style="">对于每一个socket，一般都设置成为non-blocking，但是，整个用户的process其实是一直被阻塞的。只不过process是被select这个函数阻塞，而不是被socket IO给阻塞。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pzjf.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">I/O多路复用的流程如上图所示：</p>
<p style=""></p>
<p style="">（1）当用户进程调用了select，那么整个进程会被阻塞；</p>
<p style="">（2）而同时，内核会“监视”所有select负责的socket；</p>
<p style="">（3）当任何一个socket中的数据准备好了，select就会返回；</p>
<p style="">（4）这个时候用户进程再调用read/accept/write操作，做一些数据从内核拷贝到用户进程这样的事情。</p>
<p style=""></p>
<p style="">所以，I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符，而这些<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486330&amp;idx=1&amp;sn=e85e201059c1c5575481e03531b06212&amp;chksm=fafde1d4cd8a68c235ef3cbed83bf0b6ac3a80052c0a492d8c2a217006cf4fa530f2f74f01c3&amp;scene=21#wechat_redirect" target="_blank">文件描述符</a>（套接字描述符）其中的任意一个进入读就绪状态，select()函数就可以返回。</p>
<p style=""></p>
<p style="">事实上，I/O 多路复用有时候性能比同步阻塞IO还更差一些。因为这里需要使用两个系统调用(select 和 recvfrom)，而同步阻塞IO只调用了一个系统调用(recvfrom)。但是，用select的优势在于它可以同时处理多个连接。所以，如果处理的连接数不是很高的话，可能延迟还更大。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">打个比方：行军打仗讲究粮草先行。诸葛亮比较牛，他打仗只带少量粮草，其他靠敌军送。这天他又派了暗探去查看敌军粮草的守卫情况。如果敌人守备松懈，则趁机偷粮。如果这个暗探只偷一袋粮食，那效率最高的是不是他看到敌军守备松懈就直接进去偷粮（同步阻塞IO）？但是他要偷的是十万大军的粮食，那他就要先回去汇报一声：“守备松懈啦”。然后百人小分队一起去把粮草偷出来(I/O 多路复用)。当然啦，以诸葛亮的一贯作风而言，最后他还得放一把火。</p>
<p style=""></p>
<p style="">暗探在同步阻塞模式下，打探敌情也是他，偷粮也是他。在诸葛亮团队中，暗探在打探敌情时最终暗探是第一个获取到结果的。暗探在偷粮时也是第一个自己知道结果的。（同步）</p>
<p style=""></p>
<p style="">暗探在I/O 多路复用模式下，打探敌情也是他，偷粮是百人小分队。在诸葛亮团队中，暗探在打探敌情时最终执行者暗探是第一个获取到结果的。百人小分队在偷粮时也是百人小分队自己先知道结果的。（同步）</p>
<p style=""></p>
<p style="">综上，IO多路复用是同步的。</p>]]></description><guid isPermaLink="false">/archives/qu-tan-ioduo-lu-fu-yong-de-ben-zhi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:02:25 GMT</pubDate></item><item><title><![CDATA[轻松搞懂5种IO模型]]></title><link>https://wiki.dgfygk.com/archives/qing-song-gao-dong-5zhong-iomo-xing</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%BD%BB%E6%9D%BE%E6%90%9E%E6%87%825%E7%A7%8DIO%E6%A8%A1%E5%9E%8B&amp;url=/archives/qing-song-gao-dong-5zhong-iomo-xing" width="1" height="1" alt="" style="opacity:0;">
<p style="">同步阻塞IO、同步非阻塞IO、IO多路复用、异步阻塞IO、异步非阻塞IO，这五种IO模型有没有朋友记过多次了，但是总是记不住？那是因为没有理解本质。5年前我记住了，到现在发现记忆和区分仍然很清晰，今天把理解方法介绍给大家。</p>
<p style=""></p>
<p style="">首先，大家先思考一个问题：</p>
<p style=""></p>
<p style=""></p>
<p style=""></p>
<p style="">IO操作其实主要为了读和写。本文以读数据做说明。</p>
<p style=""></p>
<p style="">当程序调用read方法时，会切换到系统内核来完成真正的读取。而读取又分为等待数据和复制数据两个阶段。如下图所示：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-adzq.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">同步阻塞IO</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bsgy.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">如上图所示，用户线程发起一个read请求，会切换到内核空间。这时候如果没有数据过来，则用户线程和对应的内核线程什么都做不了，一直等到有数据进来，并且完成了内核态的数据复制才继续返回用户空间继续执行。这整个过程是阻塞的。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">同步非阻塞IO</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ntyd.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">如上图所示，用户线程发起一个read请求，会切换到内核空间。这时候如果没有数据过来，则直接返回。它可以再次轮循发起read，如果某一次发现有数据过来，则等待完成了内核态的数据复制才继续返回用户空间继续执行。</p>
<p style=""></p>
<p style="">这个过程中，没有数据时是非阻塞的。有数据时是阻塞的，被称为非阻塞IO。这种方式涉及多次内核切换，某些情况下反而会影响性能。之前业界发生过一个由阻塞切换成非阻塞，流量高峰时性能不足引起的重大故障。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">IO多路复用</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-flqu.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">如上图所示，用户线程发起一个select请求，会切换到内核空间。这时候如果没有数据过来，则阻塞直到有数据时返回给用户线程。用户线程收到有数据的消息，发起read操作同步等待直到完成内核态的数据复制才继续返回用户空间继续执行。</p>
<p style=""></p>
<p style="">这时候，是不是有朋友冒出来一个问题：似乎看不到多路复用的优势啊。似乎阻塞才是最佳选择啊。</p>
<p style=""></p>
<p style="">上面都是以最简单的例子来介绍的，下面来看一个复杂一些的阻塞IO。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-biwh.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">如上图所示，用户线程发起一个read请求，会切换到内核空间。这时候又有另外一个连接请求过来。这个线程不会立即影响这个连接请求，而是一直等到有数据进来，并且完成了内核态的数据复制才继续返回用户空间继续执行。处理完第一个连接的所有read操作之后，才会响应新的连接。新连接从accept（netty中建立连接的函数）到read都是同步阻塞的，每次只能处理一个连接的事件。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-icsk.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">如上图所示，多路复用时，用户程序发起一个select操作，会返回一批事件，有read、write、accept（netty中建立连接的事件）。这时候，该等的时间select操作都已经做了。这时候，用户线程可以用新的线程(worker线程)直接去建立连接、复制数据。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">异步非阻塞IO</span></strong></p>
<p style=""></p>
<p style="">这里就要明确IO模型中，同步和异步的概念了。</p>
<p style=""></p>
<p style="">同步：线程自己去获取结果。(一个线程)</p>
<p style="">异步：线程自己不去获取结果，而由其他线程送结果。(至少两个线程)</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ajjg.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">如上图所示，异步是通过回调来完成的。用户程序发起read操作只是去通知操作系统我在等待数据。另外一个线程等待数据复制完成回调read方法返回结果。异步IO从实现上是基于操作系统信号驱动的，也叫信号驱动IO。</p>
<p style=""></p>
<p style="">异步阻塞IO和异步非阻塞IO又有什么区别呢？看上面的过程，异步read操作去通知完操作系统肯定是直接返回的，也就是肯定是非阻塞的。其实根本没有异步阻塞这种说法，纯属误传。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">最近发现，为了把一件事情讲清楚，要写的字越来越多。因为写的过程中会引出一些额外层面的问题需要解释。两者没有分离好反而不好理解。</p>
<p style=""></p>
<p style="">我就想出来现在的办法，先在一篇文章中阐述一件事，同时抛出来一个问题让大家思考。然后另起一篇把问题讲透。就像本周的<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486533&amp;idx=1&amp;sn=a7a6bb50bb1ce4fb88510ab5b1fb9288&amp;chksm=fafde6ebcd8a6ffd53ade8cd730c7436b93199b8bfcfa160d29056312432e4db5bfba3568eb0&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">HTTP状态码1XX深入理解</span>》</a>和<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486543&amp;idx=1&amp;sn=e61c5e6e427fca705414108e3cc4fd64&amp;chksm=fafde6e1cd8a6ff773e553fd317b64a8f78dc15e8c340fa9762cacef25a7b811c7322839dbfc&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">【答案公布】客户端与服务端通信时，所有的http状态码是否都是服务端返回的？</span>》</a>。自己觉得这种方式更加清晰，大家觉得如何呢？</p>]]></description><guid isPermaLink="false">/archives/qing-song-gao-dong-5zhong-iomo-xing</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 03:01:20 GMT</pubDate></item><item><title><![CDATA[架构师之路-https底层原理]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-shi-zhi-lu-httpsdi-ceng-yuan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B9%8B%E8%B7%AF-https%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86&amp;url=/archives/jia-gou-shi-zhi-lu-httpsdi-ceng-yuan-li" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></p>
<p style="">先说说我对架构师的理解。从业务能力上，需要的是发现问题和解决问题的能力；从团队建设上，需要的是能培养团队的业务能力；从项目管理上，把控好整个项目和软件产品的全生命周期。</p>
<p style="">我搜索了一下架构师的培训课程，讲什么的也有。实际上是Java从入门到精通的课也冠名架构师课程；讲持久层框架的也冠名架构师课程；讲组件、中间件的也冠名架构师课程；讲分布式系统设计的也冠名架构师课程。个人意见哈，内容其实没有什么问题，但是课程开始之前至少让大家有个整体的视图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zkxp.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">熟悉https等常用技术的底层原理，可以增强解决实际问题的能力，作出正确的决策。举个例子：</p>
<p style="">接到安全团队的指令，要求各个团队梳理域名是否存在证书将要到期的安全风险。怎么来给团队下发具体任务呢？这个问题在文章最后总结并回答。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">简单入门</span></strong></p>
<p style="">先来回答下面两个问题：</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">1、大家都知道使用https协议传输默认使用443端口，那什么情况下必须使用443，什么情况下不使用443？</span></strong></p>
<p style="">大家想一下端口的本质是什么。其实端口有两种，一种是物理端口，比如集线器、路由器的端口。大学的时候记得有门课叫集成电路，里面有讲到过。另一种是逻辑端口，是TCP/IP协议的端口，也常叫网络端口。本质上一个端口是用来绑定服务的，那服务器用监听什么端口，客户端就要向哪个端口发起请求。我们自己也可以写个简单的例子，写过这个例子应该很容易就能明白。<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485527&amp;idx=1&amp;sn=21413445605b9b2b9f883ba7b446c64c&amp;chksm=fafde2f9cd8a6bef39582ee0075fcb846bf5c320dbddb0b4f0363a88f42d760109acf8ae198f&amp;scene=21#wechat_redirect" target="_blank">《懂了！国际算法体系对称算法DES原理》</a>也有完整的例子。</p>
<p style="">简单点说服务端可以自己决定什么端口提供服务。但是呢，大家各自为政，记忆起来麻烦也不便于端口管理。所以逻辑端口也进行了划分。其中0~1023是公认端口，比如80就默认是http的端口、443就是https的端口。这样约定好了，就不需要特殊来记了。所以一些对全人类开发的公网网站，就是用的默认端口。但是你自己或者公司内部用的或者和合作方商量好的，也可以用其他端口，只要服务提供方认就行。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">2、使用443的端口的同时，80端口还会用吗？</span></strong></p>
<p style="">这就是涉及到https的本质了。https本质是http协议上加了一个安全层。安全层做了一些处理，http要做的事情一点也没有减少。就相当于在药粒外加了一层胶囊，本质上起作用的还是药粒呀；快递送来的海鲜放在保鲜箱里还加了保鲜袋本质还是买的海鲜呀。所以当然要用啦。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uoxd.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">安全层原理</span></strong></p>
<p style="">既然https协议本质只是http上加了安全层，本文就主要将安全层是怎么实现的。当然要靠加密。加密又分成对称加密和非对称加密。要使用对称加密，客户端和服务端要有一个相同的秘钥，这个秘钥怎么能安全的传输就成了问题。非对称加密是公钥只能用来加密，私钥只能用来解密。那服务端自己存私钥，客户端来请求的时候，服务端把公钥发给它就可以了。</p>
<p style="">那有的朋友就问了，那别的客户端来请求是不是也拿到的是同一个公钥呀，信息就不安全了呀。没错，同一个服务端给所有的客户端返回的是相同的公钥。但是威胁网络安全的黑客需要是截获信息，不是公钥。信息发送方用公钥把信息加密了，黑客没有私钥解不开。他能拿到公钥和密文，想解开那就只能暴力破解，把所有语言文字的组合用公钥试一下看看和截获到的密文是不是一样的。如果用常用的RSA2048的话，要80年。</p>
<p style="">总之，非对称加密是比较安全的，但是它慢。通常加解密速度是对称加密的几百倍，信息大了速度还会快速下降。所以https协议是用非对称加密沟通一个对称秘钥。真正的信息就用对称秘钥来加解密了。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">CA证书</span></strong></p>
<p style="">很多人都听过网络抓包吧。抓包的原理和黑客截获信息差不多，都是自己作为一个代理。客户端B想访问C网站，黑客D发现了。D就伪装成了C把自己的公钥发给了B。B发请求“我是B，我的提货卡号码是XBXB”到网站C时，黑客D截获了这个信息，用自己的私钥解开，之后把数据篡改成“我是D，我的提货卡号码是XBXB”发给网站C。于是本来B的东西就被D领走了。抓包也是这种充当代理的原理。</p>
<p style="">为了防止这一问题。客户端就需要在收到公钥的时候做一下判断，信任的公钥才使用。于是出现了一些大家都很认可的组织，它们可以颁发公钥。</p>
<p style=""><span fontsize="" color="">打个比方，在唐朝，贺知章贺老可谓是少有的文坛官场双得意的代表，“二月春风”没少光顾他家。从7品官做到3品大员，是皇帝的老铁，为国服务50年后退休皇帝了还送豪宅，后来还被追封礼部尚书。《长安十二时辰》里那个80多的老头何监就是影射的贺知章，因为他退休前的官职就是监事，人称贺监。不少诗坛大家就是受了他的推荐才与皇帝建立了通信成为了公务员。所以，贺监还有一个身份是“大唐最佳星探”。</span></p>
<p style=""><span fontsize="" color="">这里贺监就是大家都认可的组织，在贺监80多岁的时候，40多岁的李白找到贺监，请求他给自己签发一份公钥。贺监就用自己的私钥给他做了签发写了推荐信，不仅如此，还金龟换酒传为佳话。皇帝想选公务员，李白拿出了贺监的推荐信。皇帝一看，确实是贺监的笔迹，马上就给李白封了公务员。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-eozt.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">这里提一下，很多时候https抓包可以成功，原理是客户端很多时候并不验证证书的合法性。有个选项check=false。那至于哪些是认证机构，这个是系统预装的。可以在IE浏览器的internet选项里查到预装的证书。在中国，很多金融机构，它们的认证机构是央行。这些证书都是有有效期的，有的三年一签发、有的五年一签发。</p>
<p style="">上面图中有显示，证书除了公钥信息，还有数字签名信息。数字签名当然需要有信息来进行签名，具体信息就是我们在生成密钥时需要输入的那些，如下图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ixgn.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">回答开头的问题：</p>
<p style="">接到安全团队的指令，要求各个团队梳理域名是否存在证书将要到期的安全风险。怎么来给团队下发具体任务呢？</p>
<p style="">1、要梳理的是提供https服务的服务端，只有服务端才会需要找认证机构签发证书</p>
<p style="">2、只有需要外部认证的证书有过期的概念，自己签发的可以更新也可以不更新。可以通过向服务端发请求的获取到证书，查看证书是谁签发的、有效期是哪天</p>
<p style="">架构师经常面临着需要做出正确的决策，比如连续几天团队都在执行生产程序发布，但是后面几天大家有纪律涣散的趋势，这时候应该敏锐的察觉到并及时指出并纠正。这些都需要对原理或者事情的走向有深刻的认知才能做出正确和及时的判断。</p>]]></description><guid isPermaLink="false">/archives/jia-gou-shi-zhi-lu-httpsdi-ceng-yuan-li</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:58:25 GMT</pubDate></item><item><title><![CDATA[深入浅出操作系统的零拷贝]]></title><link>https://wiki.dgfygk.com/archives/shen-ru-qian-chu-cao-zuo-xi-tong-de-ling-kao-bei</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%9A%84%E9%9B%B6%E6%8B%B7%E8%B4%9D&amp;url=/archives/shen-ru-qian-chu-cao-zuo-xi-tong-de-ling-kao-bei" width="1" height="1" alt="" style="opacity:0;">
<p style="">在 kafka、netty 这些技术中，零拷贝都是一个重要的考点。但是零拷贝与这些具体的技术无关，关键点是数据传输。就像冰糖葫芦里的山楂：冰糖葫芦里重要的组成可以有山楂。但是山楂并不是冰糖葫芦特有的，羊羹里也可以有。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wnpf.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">下面是一个 MQ 的基本流程。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ykjo.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">如果采用传统方法进行数据传输，消息从存储系统到达消费者需要经过4次拷贝。如果使用零拷贝技术，情况会怎样呢？</p>
<p style=""></p>
<p style=""></p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">传统模式下的数据拷贝过程</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">过程解释</span></strong></p>
<p style=""></p>
<p style="">传统模式下，上图红框中经过了从文件读数据和从 socket 进行数据发送两个过程。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nwkn.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">内部流程如下图所示：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-irqi.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">用户进程如 Java 程序想进行 File.read ，需要将数据进行 DMA 拷贝读取到到文件读取缓冲区。还记得<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486487&amp;idx=1&amp;sn=0cff83eead864825e415811ddc215420&amp;chksm=fafde6b9cd8a6fafb71bef95e6a8c6dc870c89f2e9b487064078d89e23215c8e2a948acfc634&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">时刻掌握系统运行状态-深度理解top命令》</span></a><span style="font-size: 16px">里的&nbsp;buffers/cached 空间</span>吗？文件读取缓冲区占用的就是这个空间。</p>
<p style=""></p>
<p style="">文件读取缓冲区仍然是内核空间，用户进程要使用还需要进行一次 CPU 拷贝将数据拷贝到应用进程缓冲区。这时候用户进程比如 Java 程序就可以对数据进行排序、过滤等操作了。这个过程就完成了 File.read。</p>
<p style=""></p>
<p style="">如果数据想发送到网卡，也就是 Socket.send。还需要再进行一次 CPU 拷贝发送到套接字发送缓冲区进行中转，这个地方也是要占用&nbsp;<span style="font-size: 16px">buffers/cached 空间</span>的。中转这个过程很快，所以&nbsp;<span style="font-size: 16px">buffers/cached 空间</span>可以很快被释放。</p>
<p style=""></p>
<p style="">数据从中转站还要进行一次 DMA 拷贝，将数据运送到网络设备缓冲区，最终发送到网络上。这个过程就完成了 Socket.send。</p>
<p style=""></p>
<p style="">这个过程要进行几次上下文切换呢？File.read 这个函数需要先调用发起内核请求，进入到内核空间操作，这是一次内核切换。内核操作完成返回内核的结果，这是第二次内核切换。同理， Socket.send 也需要两次内核切换。这里的用户态到内核态的切换就是上下文切换。总共是4次。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">性能测试</span></strong></p>
<p style=""></p>
<p style="">这种方式性能如何呢？咱们来测试一下。</p>
<p style=""></p>
<p style="">写个程序从本地电脑中读取自己的一张照片，这张照片5M多大，发送到服务端。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-glvm.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">服务端只要能接收客户端数据就可以，我随便写了一个：</p>
<pre><code>    public static void server() throws Exception {        ServerSocket serverSocket = new ServerSocket(520);        int i = 1;        while (true) {            Socket socket = serverSocket.accept();            int left = 0;            while (left &gt;= 0) {                InputStream io = socket.getInputStream();                byte[] bytes = new byte[1024];                left = io.read(bytes);            }        }    }</code></pre>
<p style=""></p>
<p style="">客户端读取数据并发送到网络：</p>
<pre><code>@GetMapping(path&nbsp;=&nbsp;"hi")public String hi() throws Exception {    client();    return "end";}
public&nbsp;void&nbsp;client()&nbsp;throws&nbsp;Exception&nbsp;{    Socket socket = new Socket("127.0.0.1", 520);    //向服务器端第一次发送字符串    OutputStream netOut = socket.getOutputStream();    InputStream io = new FileInputStream("D:\\photo\\编程一生.JPG");    long begin = System.currentTimeMillis();    byte[] bytes = new byte[1024];    while (io.read(bytes) &gt;= 0) {        netOut.write(bytes);    }    System.out.println("耗时为" + (System.currentTimeMillis() - begin) + "ms");    netOut.close();    io.close();    socket.close();}</code></pre>
<p style=""></p>
<p style="">服务启动后：http://localhost:8080/hi 访问5次，结果如下：</p>
<p style="">耗时为450ms</p>
<p style="">耗时为437ms</p>
<p style="">耗时为424ms</p>
<p style="">耗时为423ms</p>
<p style="">耗时为420ms</p>
<p style=""></p>
<p style="">结论：使用传统方式，5M多的数据读取到发送需要400多毫秒。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">零拷贝</span><span style="font-size: 20px; color: rgb(216, 79, 169)">过程</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">过程解释</span></strong></p>
<p style=""></p>
<p style="">linux操作系统中有个 sendFile 方法可以不通过用户进行，直接将数据从磁盘发送到网络设备缓冲区。在 linux2.1 版本的 sendFile 过程如下图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nfkf.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">到了 linux2.4 ，linux 的 sendFile 进行了优化，实现了完全没有 CPU 拷贝实现数据传输。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tyju.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">不管是 linux2.1 还是 linux2.4 ，都是 linux 自身实现的，函数都对应的是 sendFile 。上层比如 Java 可以使用 transferTo 和 transferFrom 使用 sendFile 方法，这两个方法是 netty 实现的重要工具，一个是发送数据用，一个是接收数据用。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">性能测试</span></strong></p>
<p style=""></p>
<p style="">这种方式性能如何呢？咱们来测试一下。</p>
<p style=""></p>
<p style="">服务端不变，客户端代码如下：</p>
<pre><code>@GetMapping(path&nbsp;=&nbsp;"hi")public String hi() throws Exception {    client();    return "end";}
 public void client() throws Exception {      SocketChannel socket = SocketChannel.open();      socket.connect(new InetSocketAddress("127.0.0.1", 520));      FileChannel io = new FileInputStream("D:\\photo\\编程一生.JPG").getChannel();      long begin = System.currentTimeMillis();      io.transferTo(0, io.size(), socket);      System.out.println("耗时为" + (System.currentTimeMillis() - begin) + "ms");      io.close();      socket.close();  }</code></pre>
<p style=""></p>
<p style="">服务启动后：http://localhost:8080/hi 访问5次，结果如下：</p>
<p style="">耗时为44ms</p>
<p style="">耗时为33ms</p>
<p style="">耗时为43ms</p>
<p style="">耗时为46ms</p>
<p style="">耗时为35ms</p>
<p style=""></p>
<p style="">结论：使用零拷贝方式，5M多的数据读取到发送需要40多毫秒。与传统方式相比，性能提高10倍。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">内存映射模式</span><span style="font-size: 20px; color: rgb(216, 79, 169)">与零拷贝</span></strong></p>
<p style=""></p>
<p style="">linux 系统有零拷贝，windows 也希望减少拷贝和下上下切换，它依靠内存映射(MMAP)。当然，linux 也支持内存映射，并且在 RocketMQ 等的实现上发挥着巨大作用。</p>
<p style=""></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rjni.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">通过与上面传统方式比较，可看到由于内存映射发挥作用，在文件读取时减少了一次 CPU 拷贝。</p>
<p style=""></p>
<p style="">在 Java 中可以通过下面方法进行内存映射：</p>
<pre><code> RandomAccessFile raf = new RandomAccessFile(file, "rw");&nbsp;MappedByteBuffer&nbsp;mmap&nbsp;=&nbsp;raf.getChannel().map(FileChannel.MapMode.READ_WRITE,&nbsp;0,&nbsp;500);&nbsp;&nbsp;&nbsp;</code></pre>
<p style=""></p>
<p style="">在 MQ 的实现上，内存映射(MMAP)和 sendFile 零拷贝是提升性能的利器。下面做一个比较：</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fegj.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">上面可以看到 RocketMQ 由于使用了内存映射吞吐量远高于 ActiveMQ 和 RabbitMQ ，Kafka 由于使用了零拷贝又比 RocketMQ 提高了一个数量级。</p>
<p style=""></p>
<p style="">实际上 RabbitMQ 的实现大量借鉴了 Kafka ，那 RabbitMQ 为什么不直接使用 Kafka 的零拷贝提高性能呢？因为 RabbitMQ 不仅仅是将数据从磁盘发送出去，还需要在内存中做一些排序、过滤等高级操作。</p>
<p style=""></p>
<p style="">最后大家再来思考一个问题：零拷贝和内存映射两种模式下，各需要几次上下文切换？</p>]]></description><guid isPermaLink="false">/archives/shen-ru-qian-chu-cao-zuo-xi-tong-de-ling-kao-bei</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:57:47 GMT</pubDate></item><item><title><![CDATA[技术方案设计的方法]]></title><link>https://wiki.dgfygk.com/archives/ji-shu-fang-an-she-ji-de-fang-fa</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88%E8%AE%BE%E8%AE%A1%E7%9A%84%E6%96%B9%E6%B3%95&amp;url=/archives/ji-shu-fang-an-she-ji-de-fang-fa" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">背景</span></strong></p>
<p style=""></p>
<p style="">前段时间接手了一个还处于方案设计阶段的工作，我重新做了设计。觉得新方案比旧方案业务清晰明朗、解决了旧方案的缺陷。我就很高兴，跟同事聊这个事情。同事就问我是怎么想到这些的呢。</p>
<p style=""></p>
<p style="">我说了一些细节的，但是没有把核心本质讲出来。我觉得这是个很难回答的问题。因为一个方案怎么更合适，主要因素包含业务理解、个人经验、思维逻辑。这3个要素一般都是靠经年累月的积累才获得的。从这些中提取出别人可以学习和使用的方法确实不是一会儿就能想出来的事情。</p>
<p style=""></p>
<p style="">市面上可以看到的关于技术方案设计的经典的书都没有仔细的去讲这个问题。比如《人月神话》讲的是项目大了怎么办、《架构即未来》讲的是架构扩展性的问题。那对于最普通的CURD(创建更新读取删除)系统究竟怎么去设计。都是做CURD的，架构师和普通开发区别在哪里。本文就来探讨一下这个问题。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">CURD系统怎么做出技术含量</span></strong></p>
<p style=""><strong>
  <br>
 </strong></p>
<p style="">我前几年在面试别人的时候，遇到过一些面试者，已经带领别人做过项目了。可能人家的管理能力是不错的。项目管理是个博大精深的学问，可能一个项目在不同的阶段，更上层会找不同的管理者来做。</p>
<p style=""></p>
<p style="">比如初创团队，会找些善于创业、擅长打造敏捷团队的管理者。这样的管理者可能会比较善于鼓舞团队士气。而到了一定阶段，项目业务量起来之后，不再求快，而是求稳。这时候管理者可能会换人，新的管理者做事并不那么快，但是做决策的路很正。路很正怎么理解，打个比方说：别人提了需求，前一个领导人很好，人家来需求他就接。这个领导不是，他会分析利弊和领域、业务是否合适放在这里。再比如更一层领导提了一个需求，要求什么什么时间做完。这个领导会去分析上层领导更本质的需求，宁可延期，要做稳做好。而项目稳定之后，继续稳定下去已经没有什么业绩了。这时候可能会再换领导，这个领导会带来一些原来积累的规划、方法。</p>
<p style=""></p>
<p style="">总之，这个面试者可能有这些能力，但是我们要招聘一个技术人员，先考察硬实力，所以很自然的先请他介绍之前的项目做了什么和怎么做的。他自己也很没底气的说就是CURD嘛。给人感觉就是没有想法，在工作过程中思考的少。
 <br></p>
<p style="">拿一个后台管理系统来说，要对重要数据进行添加修改删除，一般需要审核、审计，敏感数据脱敏，甚至需要数据灰度。将这整套流程串联起来，需要一个工作流系统，可能会用到工作流引擎。目前有些数据脱敏已经用上了NLP(自然语言处理)技术。这里面每一步都涉及知识点。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">数据模型</span></strong></p>
<p style=""><strong><span style="font-size: 20px">
   <br>
  </span><span style="font-size: 18px; color: rgb(216, 79, 169)">有限状态机设计</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">
   <br>
  </span></strong></p>
<p style="">刚才提到后台管理系统里用到审核、审计、灰度。这三个可以与业务流程分离，做成三个工具组件。审核有审核状态：审核中、审核成功，审核失败。灰度也有待灰度、灰度中、灰度成功、灰度失败四种状态。</p>
<p style=""></p>
<p style="">假如有一条数据，目前它的状态可以分成数据状态：有效数据和无效数据。另外还有审核状态和灰度状态。这时候数据模型(可以简单理解成数据库的数据表设计)应该设计成三个状态用三个数据字段表示呢，还是用一个数据字段来表示这三种状态呢。</p>
<p style=""></p>
<p style="">这要看情况，一般来说，如果三种状态之前是相互独立的，比如：审核中是否可以待灰度，也可能是灰度中？对于一个工作流系统来说，一般要先审核通过才会进行灰度流程。就是说各个状态是互斥的。所以设计成三个字段，程序的判断逻辑会很复杂，因为有些组合是不成立的。这时候更合理的一个设计是用一个状态字段表示，通过状态机流转来控制。</p>
<p style=""></p>
<p style="">设计方案中可以通过类似下面的流转图来体现：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ezrj.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">实际方案设计过程中我改造了一下，状态正常从左到右流转，将终态用不同的形状(这里用椭圆)来表示。这样表示秩序感强些，更好理解。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vjpu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">数据表设计</span></strong></p>
<p style=""></p>
<p style="">在数据表设计时，要至少考虑数据类型、是否默认、是否为空和索引设计。比如对于钱什么的，需要用decimal(10,2)，具体精确到多少位，要根据业务理解来定。对于加密字符什么的，加密如果是md5的，可以用char，而不用varchar来提高效率。</p>
<p style=""></p>
<p style="">这里面就好像《红楼梦》里的台词一样，每条都很有学问。所以面试时的场景题也经常爱让进行一个数据表设计。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-icig.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">流程设计</span></strong></p>
<p style=""></p>
<p style="">我比较建议在一个设计方案开始时先把设计流程图先给出来，便于对方案有个整体认知。</p>
<p style="text-align: center; "></p>
<p style="">流程图也很有学问，【亿图】软件上把流程图分成了下面的子类。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sltx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">我个人最常用的是泳道图和BPMN。BPMN可以简单理解为业务流程图和泳道图差别不大。就是在普通流程图基础上增加一个分类，这个分类可以是阶段、也可以是执行者这些。就是给流程图多增加一个维度的信息，理解起来更清晰。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pweq.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">总结</span></strong></p>
<p style=""></p>
<p style="">在设计方案里还必须要有里程碑和未来规划。有句废话叫只怕有心人。在工作中，往往工作出色的并不是最聪明的，也不是花时间最长的。而是真正用心做的人。特别是设计方案，建议多用前人和大家总结的设计模板。模板最能代表一种设计思路。自己想到的、设计模板想到的都要考虑清楚，把方案做细。</p>
<p style=""></p>
<p style="">遇到具体的问题，要做业界调研。之前听过一个实习生做的方案，他说业界调研的时候并没有查到特别相关的方案资料。当时我的领导就说了一句特别值得思考的话：如果没有查到，肯定是你的检索关键词不对。</p>
<p style=""></p>
<p style="">我后来反复斟酌这句话，发现能想到的检索关键词才是真正体现自己对业务理解的。同时，业界调研的时候不仅要对业务相关做调研，需求可以分解，具体到每个步骤也需要调研。调研过程是个极好的学习机会。</p>
<p style=""></p>
<p style="">下功夫和不下功夫的区别，想想《红楼梦》，之所以这么多红学者。人家红楼梦的作者设计每句话都是剧透，人物说出的话，都会被啪啪打脸。里面的每道菜谱都是值得专业厨师来研究的。这都是心用到了。优秀架构师也是这样炼成的。之所以说这个废话，因为我就被diss过方案做的不够细，我也看过别人的方案不够细的。时常回想被diss不够细的那句话，觉得真的是金玉良言。心怀感激。</p>]]></description><guid isPermaLink="false">/archives/ji-shu-fang-an-she-ji-de-fang-fa</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:52:48 GMT</pubDate></item><item><title><![CDATA[架构师三大难-领域划分问题]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-shi-san-da-nan-ling-yu-hua-fen-wen-ti</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B8%89%E5%A4%A7%E9%9A%BE-%E9%A2%86%E5%9F%9F%E5%88%92%E5%88%86%E9%97%AE%E9%A2%98&amp;url=/archives/jia-gou-shi-san-da-nan-ling-yu-hua-fen-wen-ti" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">引子</span></strong></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485868&amp;idx=1&amp;sn=843459f44e37003814c7a9b44bf721ec&amp;chksm=fafde302cd8a6a142123716eef9228df519285d9d1b94b6a135fb701adf8cf826a570298f497&amp;scene=21#wechat_redirect" target="_blank">《架构师之路-redis集群解析》</a>提到：提出有水平的问题、做出有水平的总结和建议、做出有水平的回答 是架构师面临的三大难。</p>
<p style="">天天开会，最怕开会。开会十分钟，准备半天功。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lhps.gif&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">下面是围绕这三大难展开的故事。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">情景-领域划分问题</span></strong></p>
<p style="">几年前的一天，在一个会上，完全不相关的团队人员在进行我们系统的架构评审。由于他们对我的系统不了解，提的问题多是针对架构师个人能力上的。</p>
<p style="">我在介绍的时候提到：“根据系统的特点，按照角色划分领域的同时结合现有人员情况划分了下面几个应用……”，然后我被打断了，有人提问说：“应用是按领域划分还是按照人员划分？”</p>
<p style="text-align: center; ">针对这种埋坑的问题，选项有A和B，那更合理的答案一般是C。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kqpd.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">翻译提问者的问题其实是在问：“不是都是用领域划分领域吗？按照人员划分的方法不对吧？”</p>
<p style="">先来分析一下，我顺着提问者的话说会怎样：“模块需要按照领域划分，模块是分层级的。人员划分决定的是独立部署单位的粒度，在实际项目中应该综合考虑。”</p>
<p style="">这样虽然回答了提问者的问题，但是提问者很显然有知识上的盲区，需要我给他解惑的地方：“究竟要怎样划分应用？”而我的回答没有给出他完整的答案，他会继续找一些回答的漏洞来细化问题，比如：“资源预算不算做考虑粒度的因素吗？” 假定我每次顺着他的思路来，整个回答过程就没有清晰的结构了。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fagx.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">所以我需要直接针对他本质的问题展开回答，以下是回答内容：</p>
<p style="">在这次介绍的系统中，最主要的依据是按照领域来划分模块，同时根据资源和人员等情况来决定独立部署的应用模块的粒度。</p>
<p style="">但是在其他的系统中，根据不同的系统特点，模块或者应用的划分上，考虑侧重点会有所不同。我举几个例子。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">示例一(管道过滤器模式)</span></strong></p>
<p style="">比如工作流类的系统，从总体架构上采用的是管道过滤器模式</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vxtv.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">如上图，在这种系统中主要有两种角色，一种是管理者角色，负责把其他模块组织串联起来，整体对外提供服务。记得之前做个这样的项目，管理者角色的模块系统名叫captain（当时大家都以漫威英雄人物命名，captain对应美国队长）。其他模块都是一个个过滤器。是否要将每个过滤器独立应用部署，还是主要根据人力和资源来定。只要设计清晰，将来人力和资源有调整，或者随着业务的发展，对稳定性有个更好的要求，可能会需要根据可用性做一个隔离。高SLA和低SLA的单独部署，高SLA的多地区多机房部署。</p>
<p style=""><strong>示例二(三平面分离模式)</strong></p>
<p style="">比如三平面分离架构系统，详情可参考我之前的文章<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485742&amp;idx=1&amp;sn=b3fa976a6bc5ec30c3f4b7b93d60711f&amp;chksm=fafde380cd8a6a968c2b8204ec1e5ebd48625b2ba51714f581d358007d69ba0e2e80223434ba&amp;scene=21#wechat_redirect" target="_blank">《三平面分离架构》</a>。简单来说分成最核心的流程控制平面、次核心的组件支撑平面和SLA只要求两个9的管理运维平面。如下图：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dfme.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">所以领域划分时这三个平面要边界分明，三个平面可用性级别不同，资源分配也不同。比如最核心的流程控制平台日志存储要90天，其他可能需要30天；流程控制平面可能需每笔请求开始和结束打日志，而其他服务只需要异常时打日志；流程控制平面和组件支撑平面需要四地八中心高可用部署，而管理运维平台只需要两机房容灾。所以核心是要将三个平面分开以分配不同的资源。</p>
<p style=""><strong>示例三(异步处理模式)</strong></p>
<p style="">有些应用整体是实现一个大职责，但是被中间件分成了两个部分。比如有个服务是异步处理模式。所谓异步处理模式是将一个执行耗时长的流程分成两个阶段。比如退款操作。用户提交一个退款请求，先会收到一个实时通知：“您的退款请求已经收到，退款会于1~2个工作日内到账。”之后系统会将这个退款请求扔到MQ中，慢慢来消费处理。</p>
<p style="">这种模式的服务，根据实际资源等情况可以分成两个独立部署的系统，或者合在一个应用里既作为MQ的生产者又作为MQ的消费者。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nxrh.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">如果观察到别人总是就细节进行追问，这时候可以先把思路跳出来弄清楚他的本质问题是什么。</p>]]></description><guid isPermaLink="false">/archives/jia-gou-shi-san-da-nan-ling-yu-hua-fen-wen-ti</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:50:03 GMT</pubDate></item><item><title><![CDATA[架构视角-文件的通用存储原理]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-shi-jiao-wen-jian-de-tong-yong-cun-chu-yuan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E8%A7%86%E8%A7%92-%E6%96%87%E4%BB%B6%E7%9A%84%E9%80%9A%E7%94%A8%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86&amp;url=/archives/jia-gou-shi-jiao-wen-jian-de-tong-yong-cun-chu-yuan-li" width="1" height="1" alt="" style="opacity:0;">
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;架构师是互联网行业高薪又紧俏的资源。成为架构师最基本的是设计能力。设计与设计的区别主要体现在两方面：</p>
<p style="">1，深度：要解决哪些问题？这个问题背后的根本问题是什么？还有什么问题没有发现？对应的能力是发现和解决问题的能力。</p>
<p style="">2，体系：要解决的问题的属于哪一类的问题？这类问题能否进一步抽象，让系统解决更大的问题？对应的抽象归纳和体系化思维的能力。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;而做架构的基本功就是研究成熟成功的系统，并总结归纳为一种设计方法添加到自己的设计库中。今天我们来看看<span fontsize="" color="">文件存储机制的通用实现及原理-谈Kafka、Redis、基于Lucene的搜索引擎等中间件和数据库的文件存储机制。如果你有九年义务教育以上学历，并且觉得看不懂这篇文章，请给我留言~~</span></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;顺便插一句，不知道大家有没有奇怪静儿最近都没有写高可用方面的文章。事情是这样的，对于高可用的很多设计、架构，静儿都在进行专利申请中，为了避免对公司造成影响和损失，暂时处于多想不说的阶段。
  <br></span></p>
<p style=""></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一个商业化中间件的性能好坏，其文件存储机制设计是衡量一个消息队列服务技术水平和最关键指标之一。下面先介绍一下各个中间件的存储机制。看不懂可直接跳到最后。</p>
<p style=""></p>
<p style=""><strong>各个中间件的存储机制</strong></p>
<p style="">&nbsp; &nbsp;&nbsp;</p>
<p style="line-height: 28px"><strong><span fontsize="" color="rgb(255, 255, 255)" style="color: rgb(255, 255, 255)">1</span>Kafka<span style="font-size: 17px; color: rgb(51, 51, 51)">&nbsp;&nbsp;</span></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;Kafka是最初由Linkedin公司开发，是一个分布式、分区的、多副本的、多订阅者，基于zookeeper协议的分布式日志系统(也可以当做MQ系统)，常见可以用于web/nginx日志、访问日志，消息服务等等，Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。</p>
<p style="">设计特点</p>
<ul>
 <li>
  <p style="">Kafka把topic中一个partition(大块)大文件分成多个小文件段，通过多个小文件段，就容易定期清除或删除已经消费完的文件，减少磁盘占用。</p>
 </li>
 <li>
  <p style="">通过索引信息可以快速定位message和确定response的最大大小。</p>
 </li>
 <li>
  <p style="">通过index(索引)元数据全部映射到memory（内存），可以避免segment file（文件片段）的IO磁盘操作。</p>
 </li>
 <li>
  <p style="">通过索引文件稀疏存储，可以大幅降低index文件元数据占用空间大小。</p>
 </li>
</ul>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一张图解释一下刚才说的特点：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-novt.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;&nbsp;&nbsp;从上面的图中可以看到，生产者和消费者并没有直接的交互。这就达到了生产者消费者模式的第一个好处：解耦。作为消费者，可以自定义消费线程数和消费者数量。这就达到了生产者消费者模式的第二个好处：限流。这也是大家使用MQ一般想要达到的目的。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style="line-height: 28px"><strong><span fontsize="" color="rgb(255, 255, 255)" style="color: rgb(255, 255, 255)">2</span>Redis<span style="font-size: 17px; color: rgb(51, 51, 51)">&nbsp;&nbsp;</span></strong></p>
<p style="">&nbsp; &nbsp;</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;谈redis首先要纠正一个误区：缓存比mysql快。为了说明这个问题，下面是静儿自己动手得到的数据。</p>
<p style="">结论：使用了缓存后响应时间不稳定</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uwip.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">结论：mysql的响应时间非常稳定</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-oyxm.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 14px">从cat监控中可以看到缓存的get时间确实较长。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kwvb.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;顺便跑题一下，静儿测试中还发现在jvm正常执行业务逻辑，不太复杂无外部调用，一般是1到2毫秒。但是如果抛出了异常，这段代码执行时间要变成100ms+。所以一般代码中禁止用抛出非必要异常来代替正常逻辑。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qklx.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;之所以给大家打这个预防针，是因为下面要给出Redis的描述了。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;Redis本质上是一个key-value类型的内存数据库，整个数据库加载在内存当中进行操作，定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作，Redis的性能非常出色，每秒可以处理超过10万次读写操作，是已经性能最快的key-value DB。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;Redis存储机制分成两种Snapshot和AOF。无论是哪种机制，Redis都是将数据存储在内存中。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;AOF工作原理：是将数据先存在内存，但是在存储的时候会使用fsync(无阻塞进程的)来完成对本次写操作的日志记录。AOF最关键的配置就是关于调用fsync追加日志文件的频率，有两种预设频率。always：每次记录进来都添加。everysecond每秒添加一次。</p>
<p style="">存储模式性能和安全比较：</p>
<p style="">1.性能</p>
<p style="">snapshot性能是要明显高于AOP方式的，原因有两点：</p>
<p style="">1&gt;采用二进制方式存储，数据文件小，加载快速。</p>
<p style="">2&gt;存储的时候是按照配置中的save策略来存储，每次都是聚合很多数据批量存储，写入的效率高。AOF一般都是工作在实时或准实时模式下。相对存储频率高，效率低。</p>
<p style="">2.数据安全</p>
<p style="">AOF数据安全性高于Snapshot，原因：</p>
<p style="">snapshot存储是基于累积批量的思想，累积的数据越多写入效率越高，但是如果长时间数据不写入RDB（redis的数据库），redis遇到了崩溃，没写入的数据就无法恢复。</p>
<p style=""></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;Redis中有rewrite功能，AOF的存储是按照记录日志的方式去工作的，成千上万的数据插入必然导致日志文件的扩大，Redis这个时候会根据配置合理触发rewrite操作。这个操作是将最终保留值记录到日志文件中，从而缩小日志文件的大小。这个类似于lucene的optimize操作。</p>
<p style=""><span fontsize="" color="">&nbsp;&nbsp;</span></p>
<p style="line-height: 28px"><strong><span style="font-size: 16px; color: rgb(255, 255, 255)">3</span><span style="font-size: 16px">Mysql/MariaDB</span><span style="font-size: 17px; color: rgb(51, 51, 51)">&nbsp;&nbsp;</span></strong></p>
<p style="">&nbsp; &nbsp;</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;Mysql与目前流行的nosql数据库最大的不同是规定了严格的数据类型。数据类型因为在创建表时在内存中严格划定了地址空间，所以能限定字段的数据存储长度。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;数据类型限定范围的方式有两种：1是严格限定空间，划分了多少空间就只能存储多少数据，超出的数据将被切断；2是使用额外的字节的bit位来标记某个数据，存储了就进行标记，不存储就不标记。下面介绍3种类型的存储方式。</p>
<p style="">1.整形的存储</p>
<p style="">它严格限定空间，每个已划分的字节上的bit位上的0和1直接可以计算出数值，所以它的范围是根据bit位的数量值来计算的。一个字节有8个bit位，一个bit位可以构成2的8次方=256个数值。同理2字节共2的16次方=65526个数值。也就是说，在0-255之间的数字都只占用1个字节，256-65535之间的数字占用2个字节。</p>
<p style="">2.char的存储</p>
<p style="">char类型是常被成为定长字符串类型，它严格限定空间长度，但它限定的是字符数不是字节数。它有“短了就使用空格补足”的能力。</p>
<p style="">3.varchar常被称为“变长字符串类型”。它存储数据时使用额外的字节的bit位来标记某个字节是否存储了数据。每存储1个字节占用一个bit位进行标记。一个额外的字节可以标记256个字节，2个额外的字节可以标记65536个字节。MySQL/mariadb限制了最大能存储65536个字节。这表示，如果是单字节的字符，它最多能存储65536个字符，如果是多字节字符，如UTF8的每个字节占用3个字节，它最多能存储65536/3=21845个字符。</p>
<p style="">&nbsp; &nbsp;</p>
<p style="line-height: 28px"><strong><span style="font-size: 16px; color: rgb(255, 255, 255)">4</span><span style="font-size: 16px">基于Lucene的搜索引擎</span><span style="font-size: 17px; color: rgb(51, 51, 51)">&nbsp;&nbsp;</span></strong></p>
<p style="">&nbsp; &nbsp;</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;提到<span fontsize="" color="">基于Lucene的搜索引擎，大家可能更熟悉ElasticSearch（ES）。静儿有段时间专门负责公司的搜索，没有现在这么忙，本应该是非常难得和宝贵的机会。有大把的时间研究源码，可惜当时整个心都不在工作上。当时用的还是solr4.X.X(solr是基于Lucene的搜索引擎系统，lucene和solr版本同步更新，目前最新是7.6.0)，当时solr每月发布一个新版本。版本有很多bug，我当时是在源码上做了很多修复补丁的。但是当时各种意识都很薄弱。如果当初给apache提patch，有些应该是能通过的。这就能形成了一个良好的反馈循环，可能现在的状况截然不同。</span></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;想起dubbo有段时间已经不更新版本，后来捐给了apache，给这个项目又重新注入了生机。其中的一个教训就是大家一定要有很强的开源意识。这种意识还包括在自己做功能方案和开发实现的时候要有业界调研和对标的意识。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;静儿的梦想是实现自己的搜索引擎中间件，所以目前从事的是和这个原理有很深渊源的工作：容器调度。什么？看不出来联系？那这个以后再慢慢聊。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Lucene的数据都存在一个目录下，一个目录构成了一个索引。Lucene经多年演进优化，现在的一个索引文件可以分为4个部分：词典、倒排表、正向文件、列式存储。一张表解释lucene的存储原理：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-llvl.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong>通用原理</strong></p>
<p style="">&nbsp; &nbsp; 1.高频读取操作放于内存。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;2.文件分段减少磁盘IO操作。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;3.磁盘存储防止数据丢失。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;4.更好的结构化可以提升存储和读取效率。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;以上です。不解释。</p>
<p style=""></p>
<p style=""><strong>总结</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;之前和同事聊天，同事说他们架构师基于原来的版本设计了一个非常完美的2.0方案。但是这个方案并不解决现有的任何问题。这也是静儿想做架构并且可以很轻松的找到一个架构师职位，但一直都是在项目组内自己动手写代码的原因：一个旁观者想了解内部的痛点很困难。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;前段时间大家纷纷剖析拼多多优惠券事件背后的技术问题。不可否认，技术是有点菜。但是拼多多目前整体看是成功的。成功在哪里呢？它成功的解决了商家（其他平台门槛高，入住不了）和消费者（以更低的价格买到商品）的痛点。所以这点状况并未伤及元气。</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;在做设计的时候，我会首先问自己一个问题：现在哪里最痛？架构讲究深度和体系。深度解决的是痛点根本性的问题。体系解决的是未来的痛点问题。不以解决问题为目的设计的系统都是“成功之母”。</p>]]></description><guid isPermaLink="false">/archives/jia-gou-shi-jiao-wen-jian-de-tong-yong-cun-chu-yuan-li</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:47:48 GMT</pubDate></item><item><title><![CDATA[业务开发转基础开发，这三种「高可用」架构你会么？]]></title><link>https://wiki.dgfygk.com/archives/ye-wu-kai-fa-zhuan-ji-chu-kai-fa-zhe-san-zhong-gao-ke-yong-jia-gou-ni-hui-me</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%9A%E5%8A%A1%E5%BC%80%E5%8F%91%E8%BD%AC%E5%9F%BA%E7%A1%80%E5%BC%80%E5%8F%91%EF%BC%8C%E8%BF%99%E4%B8%89%E7%A7%8D%E3%80%8C%E9%AB%98%E5%8F%AF%E7%94%A8%E3%80%8D%E6%9E%B6%E6%9E%84%E4%BD%A0%E4%BC%9A%E4%B9%88%EF%BC%9F&amp;url=/archives/ye-wu-kai-fa-zhuan-ji-chu-kai-fa-zhe-san-zhong-gao-ke-yong-jia-gou-ni-hui-me" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">一、业务开发与基础开发的区别</span></strong></p>
<p style=""><strong><span style="font-size: 18px">- 划分方式</span></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;一种将后台开发细分的方式：前台开发(业务)、中台开发(中间件、应用基础服务、PAAS服务、IAAS服务)、后台开发(运维开发)。一般前台开发对应于业务开发，中台开发对应基础开发，后台开发对应运维。</p>
<p style=""><strong><span style="font-size: 18px">- 规模</span></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;基础开发的目标是解决业务的公共痛点，所以一般数据量、并发量大于业务。就美团来说，一般的基础服务日调用量在十亿到千亿。因此，高并发、高可用是常规话题。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">二、三种高可用架构</span></strong></p>
<p style=""><strong><span style="font-size: 18px">- 多租户集群架构</span></strong></p>
<p style=""><strong>定义：</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;多租户技术(Multi-tenancy Technology)，或称多重租赁技术，是一种软件架构技术。它是在探讨与实现如何在多用户公用相同的系统或组件时，仍可确保各用户间的数据隔离性。</p>
<p style=""><strong>目标：</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;按照业务情况分为不同租户，单个租户出现问题，不会影响其他租户。</p>
<p style=""><strong>关键功能模块：</strong></p>
<ul>
 <li>
  <p style="">租户管理</p>
 </li>
 <li>
  <p style="">权限管理</p>
 </li>
 <li>
  <p style="">资源管理</p>
 </li>
</ul>
<p style=""><strong>应用举例：</strong></p>
<h2 style="" id="%E3%80%8A%E7%BE%8E%E5%9B%A2%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1%E6%A1%86%E6%9E%B6%E5%8F%8A%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86%E7%B3%BB%E7%BB%9Focto%E3%80%8B%E9%87%8C%E4%BD%BF%E7%94%A8appkey%E4%BD%9C%E4%B8%BA%E7%A7%9F%E6%88%B7%E3%80%81%E6%9D%83%E9%99%90%E5%92%8C%E8%B5%84%E6%BA%90%E5%88%92%E5%88%86%E7%9A%84%E4%BE%9D%E6%8D%AE%E6%9D%A5%E5%AE%9E%E7%8E%B0%E5%A4%9A%E7%A7%9F%E6%88%B7%E9%9B%86%E7%BE%A4%E7%AE%A1%E7%90%86%E3%80%82%E5%90%84%E4%B8%AA%E5%85%AC%E5%8F%B8%E8%87%AA%E5%B7%B1%E7%9A%84kafka%E9%9B%86%E7%BE%A4%E4%B9%9F%E6%98%AF%E6%A0%B9%E6%8D%AE%E6%8E%A5%E5%85%A5%E4%B8%9A%E5%8A%A1%E5%AE%9E%E7%8E%B0%E4%BA%86%E5%A4%9A%E7%A7%9F%E6%88%B7%E9%9B%86%E7%BE%A4%E7%AE%A1%E7%90%86%E3%80%82"><a href="https://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484193&amp;idx=1&amp;sn=3b799207a163c7bfe2905669d6956191&amp;scene=21#wechat_redirect" target="_blank">&nbsp;&nbsp;&nbsp;&nbsp;《美团分布式服务通信框架及服务治理系统OCTO》</a>里使用appkey作为租户、权限和资源划分的依据来实现多租户集群管理。各个公司自己的Kafka集群也是根据接入业务实现了多租户集群管理。</h2>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px">- 应用熔断架构</span></strong></p>
<p style=""><strong>目标：</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;针对混合使用集群，单个应用的问题可能拖垮整个集群，单个服务异常情况下，提供熔断机制，不会由于单个应用影响整个集群的问题。</p>
<p style=""><strong>常用熔断条件：</strong></p>
<ul>
 <li>
  <p style="">线程数过高</p>
 </li>
</ul>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;熔断原因：由于容器化技术的广泛流行，CPU超售普遍存在。一个业务的异常线程会影响其他业务。</p>
<ul>
 <li>
  <p style="">大量慢查询</p>
 </li>
</ul>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;熔断原因：在集中式ES集群、Redis集群等场景，由于存在连接数资源共享，一个业务的慢查询会阻塞其他业务。</p>
<ul>
 <li>
  <p style="">调用量异常</p>
 </li>
</ul>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;熔断原因：在同步调用时，每个调用都不释放线程，等待结果返回。一个业务的调用量异常可能会拖垮整个集群。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px">- 立体监控架构</span></strong></p>
<p style=""><strong>黑天鹅现象：</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;非常难以预测的罕见现象，通常会引起连锁负面反应甚至颠覆。</p>
<p style=""><strong>目标：</strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;为了从海量监控、报警风暴数据中找到产生影响的「黑天鹅」，立体监控应任而生。</p>
<p style=""><strong>特点：</strong></p>
<ul>
 <li>
  <p style="">多维数据模型</p>
 </li>
 <li>
  <p style="">在多维度上灵活查询</p>
 </li>
 <li>
  <p style="">多种可视化图表及仪表盘支持</p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">三、总结</span></strong></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;亲身经历是教育的最好形式。</p>]]></description><guid isPermaLink="false">/archives/ye-wu-kai-fa-zhuan-ji-chu-kai-fa-zhe-san-zhong-gao-ke-yong-jia-gou-ni-hui-me</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:44:50 GMT</pubDate></item><item><title><![CDATA[架构思考-业务快速增长时的容量问题]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-si-kao-ye-wu-kuai-su-zeng-chang-shi-de-rong-liang-wen-ti</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E6%80%9D%E8%80%83-%E4%B8%9A%E5%8A%A1%E5%BF%AB%E9%80%9F%E5%A2%9E%E9%95%BF%E6%97%B6%E7%9A%84%E5%AE%B9%E9%87%8F%E9%97%AE%E9%A2%98&amp;url=/archives/jia-gou-si-kao-ye-wu-kuai-su-zeng-chang-shi-de-rong-liang-wen-ti" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">背景</span></strong></p>
<p style="">之前做过一个项目，数据库存储采用的是mysql。当时面临着业务指数级的增长，存储容量不足。当时采用的措施是</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">1&gt;短期解决容量的问题</span></strong></p>
<p style=""><span style="font-size: 16px">mysql从5.6升级5.7，因为数据核心且重要，数据库主从同步采用的是全同步， 利用5.7并行复制新特性，减少了主从同步的延迟，提高了吞吐量。</span></p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">当时业务量高峰是2000TPS，5.6时可承受的最大TPS是3000，升级到5.7压测可承受的最大TPD是5000.</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">2&gt;流量拆分，从根本上解决容量问题</span></strong></p>
<p style="text-align: start; ; line-height: 22px">首先进行容量评估，通过对于业务开展规划、活动预估，年底的容量会翻5倍。由于目前指数级增长的特性，数据库要预留至少4倍的冗余。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">要对数据库进行扩容，因为我们已经使用的是最顶配的SSD物理机了，就算可以在linux内核层面对numa进行绑核和非绑核等测试调参优化性能，提升容量也很有限。注意：一般的业务系统numa绑核会提高性能，但是mysql等数据库系统是相反的。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">所以垂直扩容不成功，就看看是否可以拆分流量。mysql流量拆分方式有x轴拆分(水平拆分)、y轴拆分(垂直拆分)、z轴拆分。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">其中y轴拆分<span style="font-size: 16px">(垂直拆分)</span>就是目前都在说做垂直领域，就是在一个细分领域里做深入的意思。由此可以很容易的记住垂直拆分的意思就是按照业务领域进行拆分，<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">专库专用</span>。实际上能按领域拆分是最理想的，因为这种拆分业务清晰；拆分规则明确；系统之间整合或扩展容易。但是因为当时的业务已经很简单，y轴拆分已经没有什么空间，这种拆分不能达到扩容20倍的目的。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">z轴拆分近几年没有听说过了，实际上大家也一直在用。这种方式是将一张大表拆分为<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">子母表</span>，就是分为概要信息和详细信息。这种拆分方式对解决容量问题意义不大。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">比较可行的一个方案是水平拆分。就是常说的<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">分库分表</span>。按照容量评估，数据库水平拆分一拆十，根据业务特点找一个标准字段来进行取模。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">水平拆分一个技术点在于新老切换。</p>
<p style="text-align: start; ; line-height: 22px">采用的是<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">数据库双写</span>的方式，采用异步确保性的补偿型事务，发送实时和延迟两个MQ，通过开关来控制以老数据为准还是新数据库为准。开始时以老数据库为准，观察新老数据没有一致性问题之后，在一个低峰期，关闭了系统入口，等数据库没有任何变更之后切换开关，再打开系统入口。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 20px">问题</span></strong></p>
<p style="text-align: start; ; line-height: 22px">对于容量问题，上面采用的是一次性拆分到位的方法。对于一个规模稍大的公司来讲，10组物理机(1组包含1主N从)的成本还好。</p>
<p style="text-align: start; ; line-height: 22px">1&gt;如果量级再次升级，需要每周增加10台数据库才能支撑容量呢？</p>
<p style="text-align: start; ; line-height: 22px">2&gt;并且对系统可用性还有强要求，1s的停机都不可以接受呢？</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 20px">解决方案分析</span></strong></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">垂直流量拆分</span></strong></p>
<p style="text-align: start; ; line-height: 22px">首先我要分析的是每周增加10台数据库这个容量是不是合理的。是否存在放大效应或者说可以减少对mysql这种昂贵资源的使用，转为增加对HBase、Elasticsearch这种低成本高扩展性资源的使用呢？</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="">基于这个思路，我们需要梳理下是否有可垂直拆分的流量。比如</span><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">正向流量和负向流量</span>。所谓正向流量是指比如交易下单，负向流量就是取消订单，包括已<span fontsize="" color="">付款取消、未付款取消、已到货取消、未到货取消等等。实际上负向流量在总订单里占比很少，但是业务要比正向交易业务复杂。将正向和逆向拆分的一个主要优势是分</span>治<span fontsize="" color="">思想，可以降低两部分各自的复杂度。将流量拆分重心转移到正向流量上。
  <br></span></p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">对于正向流量，一个业务比较常用的流量拆分思路是<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">CQRS命令查询分离</span>，也就是常说的读写分离。如果读流量大于写流量。可以考虑能否将读流量进一步拆分。拆分成实时和离线，将实时性要求不高的查询走ES。ES的数据可以通过同步binlog变更获得。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">另外一个思路是将数据库<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">按照历史数据来拆分</span>。就是数据库里只保存一定时间内的实时数据。超过指定时间则进行数据归档。将数据归档到HBase等，一般对于历史的查询实时性要求也不是很高。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">垂直流量拆分可能遇到的问题</span></strong></p>
<p style="text-align: start; ; line-height: 22px">以上方法都是只考虑问题1<span style="font-size: 16px">如果量级再次升级，需要每周增加10台数据库才能支撑容量的方案。如果再考虑问题2并且对系统可用性还有强要求，1s的停机都不可以接受。就需要看上述方案可能会遇到的问题。</span></p>
<p style="text-align: start; ; line-height: 22px"><span style="font-size: 16px">
  <br>
 </span></p>
<p style="text-align: start; ; line-height: 22px"><span style="font-size: 16px">拆分正向流量和负向流量、CQRS都需要改造，改造过程就需要过渡。过渡可以采用上面说的双写方式，观察运行情况进行切换。切换过程中也可以不关闭流量。</span></p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><span style="font-size: 16px">麻烦的是数据归档。因为数据归档后删除数据库的数据，变更生效时，针对innodb来说，意味着数据结构重建，频繁IO。这会影响OLTP在线事务的处理。可以考虑按表来归档，控制操作频率，控制单位时间内对IO的影响。
  <br></span></p>
<p style="text-align: start; ; line-height: 22px"><span style="font-size: 16px">
  <br>
 </span></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">分布式关系型数据库</span></strong></p>
<p style="text-align: start; ; line-height: 22px">分布式关系型数据库本质上是通过增加代理等方式将分库分表做的更加隐蔽。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">阿里巴巴分布式关系数据库(DRDS)，前身是淘宝分布式数据层(TDDL)，核心就是用于分库分表管理的代理层，宣称可实现<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">平滑扩容</span>。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kmzo.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><span style="font-size: 16px; color: rgb(0, 0, 0)">扩容过程实际是物理数据迁移的过程，引擎层按照分库迁移后的逻辑先在物理节点上建立新的分库，然后保留一个时间点进行全量的数据迁移。完成全量迁移后，开始基于先前保留的时间点进行增量的数据追赶。当增量数据追赶到两边的数据几乎一致时，对数据库进行瞬时停写，将最后的数据追平，引擎层进行分库逻辑的路由切换，路由规则切换完成后就完成了核心的扩容逻辑，整个切换过程在毫秒级别完成。</span></p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px">因为整个过程是毫秒级，所以可以做到业务层没有感知，等多就是看到扩容过程中请求延时增加了不到1s。从原理上来说是可行的。</p>
<p style="text-align: start; ; line-height: 22px"></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">NOSQL解决方案</span></strong></p>
<p style="">像这么大的数据量一个很好的参考是12306。12306采用的是Geode。它是有数据库功能的内存数据网格（In-Memory Data Grid,IMDG）。其重要特性是</p>
<p style=""></p>
<p style="">1)集群内存总容量，现在Geode可以实现单个节点200-300GB内存，总集群包含300个节点的大型集群，因此总容量可以达到90TB左右的级别。</p>
<p style=""></p>
<p style="">2)Geode集群功能非常强大，实现了内存中数据Shard分布，自动管理，集群故障自动恢复，自动平均分布等一系列企业级的功能，而且有自带的集群间数据同步功能。</p>
<p style=""></p>
<p style="">3)在CAP原理下（不了解的话可以百度一下CAP不可能三角），Geode可以保证集群内数据的<span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">强一致性</span>，注意是真正的强一致性而不是最终一致性，再加上分区可用性，因此是一个CP型的产品，可以提供统一的数据视图，支持高并发下的acid事务。</p>
<p style=""></p>
<p style="">采用新的解决方案最大问题是平滑过渡，平滑过渡方面我还是觉得上面提到的数据库双写方式安全可靠。</p>
<p style=""></p>
<p style="text-align: start; ; line-height: 22px"><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">系统共建的解决方案</span></strong></p>
<p style="text-align: start; ; line-height: 22px">如果达到我所说的量级，基本上在一个行业中是处于垄断地位的。并不是一家纯的互联网公司。这种公司可以采用和互联网大厂合作的方式、用已经有这方面经验的大厂，来根据自己内部系统的特性共建一套合适自己的定制化数据库。</p>]]></description><guid isPermaLink="false">/archives/jia-gou-si-kao-ye-wu-kuai-su-zeng-chang-shi-de-rong-liang-wen-ti</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:37:58 GMT</pubDate></item><item><title><![CDATA[架构-稳定性建设逻辑问题实战总结]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-wen-ding-xing-jian-she-luo-ji-wen-ti-shi-zhan-zong-jie</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84-%E7%A8%B3%E5%AE%9A%E6%80%A7%E5%BB%BA%E8%AE%BE%E9%80%BB%E8%BE%91%E9%97%AE%E9%A2%98%E5%AE%9E%E6%88%98%E6%80%BB%E7%BB%93&amp;url=/archives/jia-gou-wen-ding-xing-jian-she-luo-ji-wen-ti-shi-zhan-zong-jie" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">总述</span></strong></p>
<p style=""></p>
<p style="">稳定性问题分为逻辑问题和架构问题。</p>
<p style=""></p>
<p style="">逻辑问题三板斧：理念正确、流程规范、刨根问底。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(255, 0, 0)">逻辑问题</span></strong></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(255, 0, 0)">
   <br>
  </span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">理念正确</span></strong></p>
<p style=""></p>
<p style="">曹操煮酒论英雄，对刘备发表了自己对英雄的看法：</p>
<blockquote>
 <p style="">胸怀大志，腹有良策，包藏宇宙之机，吞吐天地之气。</p>
</blockquote>
<p style=""></p>
<p style="">意思是说所谓英雄，要志气远大，计谋精良。胸怀能包含宇宙，志气能吞吐天地。对稳定性建设来说就是既要有道，又要有术，道为先。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">稳定性理念举例</span></strong></p>
<p style=""></p>
<p style="">Everything fails!</p>
<p style=""></p>
<p style="">如果一件事情有可能发生则在生产环境中一定会发生。</p>
<p style=""></p>
<p style="">不要容忍破窗户。</p>
<p style=""></p>
<p style="">过程对了结果一定不会差。</p>
<p style=""></p>
<p style="">一个问题可能是许多事故的原因。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">WHY</span></strong></p>
<p style=""></p>
<p style="">理念是目标和原则。错误的理念产生不了正确的行动，在稳定性方面是巨大的隐患。</p>
<p style=""></p>
<p style="">试想如果一个人觉得一个系统是不可能出问题的，那他一定就不会制定故障处理的紧急预案，出现问题了也不能很好的控制影响范围。</p>
<p style=""></p>
<p style="">如果觉得一个问题是小概率事件是不会发生的，就不会对问题进行修复和补救。而所谓小概率事件如果发生概率在万分之一。一般线上系统一天调用量就不只几万次，所以也就没有什么小概率事件了。</p>
<p style=""></p>
<p style="">小的问题不修复，问题积少成多，不但修复变的困难，还会让人产生反正已经这样了的放弃心理，最终造成大问题。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">流程规范</span></strong></p>
<p style=""></p>
<p style="">很多大公司的稳定性60%以上都是通过流程来保障的，有些流程经过自动化，开发人员习以为常，反而没有去深究其背后的技术原理。比如变更管理流程，一般大公司会有相应的系统，而这个系统实际上是将变更管理的所有要点做了自动化。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">变更管理</span></strong></p>
<p style=""></p>
<p style="">变更管理大体上分为两部分：变更识别和变更流程。</p>
<p style=""></p>
<p style="">变更识别：</p>
<p style="">要限制变更的影响，首先应该确保每一个生产变更都要有以下的数据记录</p>
<p style="">1&gt;变更的准确日期和时间</p>
<p style="">2&gt;将要发生变更的系统</p>
<p style="">3&gt;实际的变更</p>
<p style="">4&gt;变更期待的结果</p>
<p style="">5&gt;变更人员的联系方式</p>
<p style=""></p>
<p style="">变更流程：</p>
<p style="">1&gt;提出2&gt;批准3&gt;计划4&gt;实施5&gt;验证6&gt;报告</p>
<p style=""></p>
<p style="">变更识别是适用于初创型小公司的一种轻量级流程，一般有一定规模的公司都会使用变更流程。而变更流程的大部分过程都可以通过持续集成实现。这个过程的目的是为了安全的变更而不是减缓变更。</p>
<p style=""></p>
<p style="">在实际开发的时候通常会遇到一个矛盾：一方面要控制发版频率和时间，因为变更要耗费时间和精力，最重要的是每次上线都有风险。所以会有静默期、发版许可时间；另一方面要让变更尽量小，因为变更越大风险越高。</p>
<p style=""></p>
<p style="">对于这个矛盾，我的看法是这两个是两个分开的问题。</p>
<p style=""></p>
<p style="">发版时间只要控制在低峰期以及人员齐全的时间段，比如不要在周五，因为周六休息，问题不能及时发现。发版频率需要靠每次问题解决彻底、每次发布阶段清晰等设计开发层面解决，就是说每次发版尽量：与其扬汤止沸，不如釜底抽薪。这样一个问题或功能确保一次上线成功，不用打补丁，这样来减少频率。</p>
<p style=""></p>
<p style="">变更尽量小，我的看法是最好一次发布只是一个变更，不要多个成员的不同内容一起上线。否则一旦出现问题不好定位。有的人看法是3个内容一起上线只有1次风险，而分三次上线会有3次风险。我认为哈，如果3次变更真的出现了3个问题是不是代码质量太差了，需要从其他方面先把质量提升上来。而分为3次，每次变更内容清晰，也便于更高的流程把控和上线验证，风险总体是要低的。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">风险管理</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-unna.png&amp;size=m" width="677px" height="677px" style="display: inline-block"></p>
<p style="">评估特定行动风险有种方法叫故障模式及影响分析法(Failure mode and effects analysis, FMEA)。</p>
<p style=""></p>
<p style="">每个故障的现象可以根据下述三个因素来打分：故障的可能性、严重性和可检测性。可以选择使用1、3和9作为打分的范围，这是一种保守的打分方法，同时可以把高风险因素和中低风险因素区分开来。故障的可能性基本上是这个特定故障真实发生的概率。故障的严重性是指如果故障发生，对客户和业务产生的总体影响。这种影响可以用金钱损失、声誉损失或任何与业务有关的其他因素来测量。故障的可检测能力指如果故障发生你是否能够注意到。</p>
<p style=""></p>
<p style="">对单项失效模式和效果打分后，将这些分数相乘得到总的风险评分，即可能性得分*严重性得分*检测能力得分。这个分数显示了一个特定组件在整体行为中的整体风险。</p>
<p style=""></p>
<p style="">FMEA过程的下一步是确定可以执行或落实到位的缓解步骤，已降级特定因素的风险。例如，如果一个功能组件的可检测能力有非常高的风险分数，这意味着如果事件发生，那将很难发出通知。因此该团队可能会决定提前准备一些查询，在产品或服务发布后，每小时检查一次数据库，检测是否有故障发生的迹象，如丢失数据或数据错误。此环节措施对该组件的风险因素有降级的作用，同时应该说明风险可以降级到什么程度。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tott.png&amp;size=m" width="701px" height="884px" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">流程规范术实例</span></strong></p>
<p style=""></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">1&gt;设计阶段</span></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">统一设计模板、其中我编写了稳定性三十六计的checklist，可以作为稳定性设计的参考规范，详见：</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484379&amp;idx=1&amp;sn=012608c46885969e2b6b6dd5fcf1d70a&amp;chksm=fafde975cd8a606321a8cc42caa24501df999aa53453943173e6a972c5425ba99252a895dd6e&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">《</span><span fontsize="" color="">稳定性「三十六计」实战和背后的逻辑》</span></a></p>
<p style=""></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">2&gt;开发阶段</span></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">2.1&gt;可行性验证阶段写好测试用例，测试驱动开发</span></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">2.2&gt;与第三方交互，交互前后都要打日志。交互后的日志要把第三方返回的结果打印出来。一旦第三方出现问题。我们拿着第三方返回的结果来跟第三方沟通。避免责怪他人讹的出现。</span></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">
  <br>
 </span></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">3&gt;上线规范</span></p>
<p style="text-align: start; ; line-height: 22px"><span fontsize="" color="rgb(38, 38, 38)" style="color: rgb(38, 38, 38)">提测分支至少2名同学进行code review。Reviewer一般为之前负责过此模块开发的同学和架构师。</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(172, 57, 255)">刨根问底</span></strong></p>
<p style=""></p>
<p style="">还是那句话：与其扬汤止沸，不如釜底抽薪。实施刨根问底最常用的手段是复盘。说复盘先从问题的发生说起。</p>
<p style=""></p>
<p style="">一旦出现了问题或者故障，第一反应是什么？找原因？错！第一件要做的事情是「恢复现场」，先解决问题，控制和降低影响。比如生产环境突然load飙升、线程池被打满了。这时候应该马上启动紧急预案，重启服务或者机器，然后紧急扩容。如果问题发生前有过变更，则立即回滚。在发生问题的时候最好有个指挥者负责分派任务和协调人员。</p>
<p style=""></p>
<p style="">现场恢复后再着手调查原因，可以多个人从不同层面来分析问题。比如这次问题主要是一个变更引起的。那么变更的开发人员是问题分析的主力，一般也是问题的责任人和复盘的发起者。但是其他人可以同时通过代码review<span fontsize="" color="">、监控等数据分析角度帮助一起定位。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">原因基本定位之后，如果大家都还没离开。一个比较好的方式是以非常轻松的聊天的方式，让了解问题的人都自然的聚在一起，开一个头脑风暴的茶话会，将问题事前、事中、事后可以优化的都提出来，作为事件正式复盘前的素材。
  <br></span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">复盘的流程
  <br></span></p>
<p style=""><span fontsize="" color="">1&gt;事件概述 2&gt;事件影响 3&gt;时间线&nbsp;4&gt;5why根本原因分析&nbsp;5&gt;经验教训&nbsp;6&gt;TODO</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">总结</span></strong></p>
<p style=""></p>
<p style="">稳定性问题中逻辑问题和架构问题的产生原因和侧重点都不同。架构问题如果不快速治理，很容易造成级联故障、雪崩等问题，从而引发稳定性危机。而架构大体满足需求时，逻辑问题是日常工作中更经常面对的问题。</p>
<p style=""></p>
<p style="">减少逻辑问题一要靠人二要靠流程。所谓靠人，就是人的能力和素质越强，问题越少。<span fontsize="" color="">其中素质就包含理念价值观和刨根问题的精神和能力。对于流程，很多大公司都有很好的工具或系统来进行流程规范。但是作为开发人员，一定要避免「离开了平台，自己什么都不是」。流程规范的系统实现都很简单，关键点是实现了什么，平时的时候建议多加思索，将平台能力转化为自身能力。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">最后，这句话很重要：「与其扬汤止沸，不如釜底抽薪。」</span></p>]]></description><guid isPermaLink="false">/archives/jia-gou-wen-ding-xing-jian-she-luo-ji-wen-ti-shi-zhan-zong-jie</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:34:17 GMT</pubDate></item><item><title><![CDATA[设计开发中要避免的两个坑和一种可借鉴的设计思想]]></title><link>https://wiki.dgfygk.com/archives/she-ji-kai-fa-zhong-yao-bi-mian-de-liang-ge-keng-he-yi-zhong-ke-jie-jian-de-she-ji-si-xiang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AE%BE%E8%AE%A1%E5%BC%80%E5%8F%91%E4%B8%AD%E8%A6%81%E9%81%BF%E5%85%8D%E7%9A%84%E4%B8%A4%E4%B8%AA%E5%9D%91%E5%92%8C%E4%B8%80%E7%A7%8D%E5%8F%AF%E5%80%9F%E9%89%B4%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3&amp;url=/archives/she-ji-kai-fa-zhong-yao-bi-mian-de-liang-ge-keng-he-yi-zhong-ke-jie-jian-de-she-ji-si-xiang" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">项目名不要叫base、basic</span></strong></p>
<p style="">打个比方，一个公司在建设初期，公司技术人员十几个。架构师就按照具体情况给了个适合当前情况的SOA(面向服务架构)设计：分成4个业务模块，将共用部分提取出一个公共模块basic。后来公司越做越好，越做越大。公司技术人员增长了5倍。于是4个业务模块和basic模块成了5个团队。</p>
<p style="">现在basic团队面临着一个巨大挑战：业务上不属于4个业务模块领域内、或者和其他业务模块2个以上模块领域上都沾边不太好区分到底是哪个领域的。通通被交给basic团队来开发维护。basic团队很忙很累，苦不堪言。但是拒绝接这些需求都没有借口：谁叫你是basic，就该你做！</p>
<p style="">但是basic团队忙忙碌碌，实际只是承担了其他团队一些数据模型维护、其他一些不能独立成为一个产品的工作。最终的产品成果归其他团队。没有业绩。于是basic团队同事纷纷跳槽。</p>
<p style="">现在怎么办呢？重新招人呗。结果新招来的人很负责的通读了自己要维护的代码，发现不知道自己的代码在干什么。代码没人敢改，没人敢动。</p>
<p style="">上头看着不行，想将这个模块的功能按照领域驱动的思想划分到各个领域去维护，功能代码没团队敢接……</p>
<p style="">这是在很多大公司很多部门都发生过的真实事件，血泪的教训。所以我建议你的项目千万不要叫base、basic之类包罗万象的词。</p>
<p style="">那有人问：我们正处于这么一个初期阶段，上头非要我们做一个通用基础模块，我们怎么办？我的建议是该做的照样做，名字宁愿叫“盖娅”也别叫basic。盖娅是希腊神话里的超原始神，是众神之母，本身含有基础的意思。但是只要不叫basic，等到领域划分的时候只要可以自圆其说还是有希望把边界理清楚的。叫basic就哭吧。</p>
<p style="">不能只提问题不说解决方法。对于这种应用，改造迫在眉睫的时候。不建议用重构来解决，逻辑改动的风险不可预估。建议另起炉灶重做功能，把里面的功能清晰价值大的做一个抽象升级，给新接入和正在改造的接入方使用。老系统不动，让其渐渐失去生命力直到最后可完全下线。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">避免随手优化</span></strong></p>
<p style="">世界上有很多事情是反直觉的：一个努力上进的开发小姐姐，每天晚上10点多急匆匆的合上电脑去赶末班地铁；而他们公司的前台小姐姐逛了一天的某宝，5点下班从容的走进一辆法拉利812。但是，开发小姐姐感觉每天充实快乐，前台小姐姐感觉自卑空虚。</p>
<p style="">忘记上面的例子，不是重点。重点是我们通常以为看到代码写的不好，愿意花时间做优化是件好事，实际上反而是危险的。</p>
<p style="">假如我们看到某个代码，明显有逻辑错误，想随手改改。你就要考虑一件事情：这段明显有问题的代码为什么在线上运行着没有人来报bug？有一种正常运行叫做【负负得正】。如果把错误的逻辑改对了反而可能引起问题。</p>
<p style="">遇到开发小哥哥改并非之前需求和技术方案中涉及的代码，出于技术追求不堪忍受太烂代码的，我代码评审的时候，手都是瑟瑟发抖，反复的在拿捏影响。对于未开始动手优化，在看这篇文章的你，我的建议是：避免随手优化。</p>
<p style="">有的朋友说，那看着这些代码【破窗户】(破窗户形容已经开始变坏的代码任由放着继续变坏)就放着不管了？如果觉得很有必要改，可以提出来讨论，作为一个专门的优化事项，评估好风险与收益，做一个完善的方案再行动，避免好心办坏事。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">【冷却期】设计</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qoku.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">上面是银行卡转账的一个截图。上面有个大家经常用的功能：输入短信验证码。要知道每发送一条短信都是要给短信代理公司交钱的。不仅如此，如果短信发送过于频繁，会导致排队。很久才能收到。或者等的花儿都谢了也收不到。所以现在的短信发送一般会设置60s的冷却期。</p>
<p style="">但是延迟还是不可避免。所以发信短信内部一定要做异步哦。同步等结果的话，怕是系统一天挂好几次。</p>
<p style="">60s还收不到现在这个时代也还是会经常出现的。那就再点次发送。结果这次收到了两条。问题来了，谁是最新的？两个都让用户输入试试这个用户体验，想必用户留存率会大打折扣。</p>
<p style="">所以现在会在发送短信验证码同时发送核对序号，只需要输入序号相同的验证码。</p>
<p style="">除了发短信，对于其他宝贵资源的使用和申请，比如IP申请，都可以使用【冷却期】设计来解决可能造成的资源浪费问题。</p>]]></description><guid isPermaLink="false">/archives/she-ji-kai-fa-zhong-yao-bi-mian-de-liang-ge-keng-he-yi-zhong-ke-jie-jian-de-she-ji-si-xiang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:29:56 GMT</pubDate></item><item><title><![CDATA[三平面分离架构]]></title><link>https://wiki.dgfygk.com/archives/san-ping-mian-fen-chi-jia-gou</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%89%E5%B9%B3%E9%9D%A2%E5%88%86%E7%A6%BB%E6%9E%B6%E6%9E%84&amp;url=/archives/san-ping-mian-fen-chi-jia-gou" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></p>
<p style="">开始正文之前先来看两个前沿技术的架构。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">1、SDN&nbsp;软件定义网络</span></strong></p>
<p style="">软件定义网络（Software Defined Network，SDN）是由美国斯坦福大学clean-slate课题研究组提出的一种新型网络创新架构，是网络虚拟化的一种实现方式。SDN技术的成功运用将使得网络更加智能、更开放、更简单，使得用户成为网络真正的主人。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fybb.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">其核心技术OpenFlow通过将网络设备的控制面与数据面分离开来，从而实现了网络流量的灵活控制，使网络作为管道变得更加智能，为核心网络及应用的创新提供了良好的平台。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">2、服务网格</span></strong></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">“服务网格” 是近年来非常火热的技术，其全托管的思维非常适合云原生场景。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-blok.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">“服务网格” 核心分为控制面与数据面：数据面主要是一个名为 Sidecar 的代理组件，它通过接收控制面发送的路由与控制信息来定向转发或处理数据。这样一些坐落在服务网格里的应用就将整个分布式逻辑交给了底层，自己不用关心了。一旦与底层解耦，灵活性大大增加，更符合云原生的标准。</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">WHAT</span></strong></p>
<p style="">今天所聊的三平面分离架构是指控制平面、数据平面和管理平面三平面分离。古老而经典的“三大分离架构”因为有5/7的字重复了，为了避免大家混淆，这里也提一下：三大分离架构设计原则是指动静分离、前后端分离、读写分离。两者不在一个分析维度上。</p>
<p style=""><span fontsize="" color="">三平面分离架构</span>非常适合一些通信类服务、通道类服务。这是在网上搜到的一个交换机的三平面分离架构：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sosh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">在互联网软件领域，网关类服务、渠道类服务、工作流类服务都非常合适这种架构。下面是一个API网关服务的架构：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kzqc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">WHY</span></strong></p>
<p style="">简单科普下API网关的概念。随着微服务架构的流行，API网关也越来越受欢迎。在微服务体系架构中，将应用程序按垂直领域划分为几个、十几个、几百个微服务。客户端要和几百个服务对接，安全性和快速开发方面都有很大障碍。API网关可以充当使用这些微服务的客户端的中央接口，主要功能称为路由。下面是一个典型的API网关示意图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nugt.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">API网关的基本作用如下：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vhjr.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">常见的API网关有：nginx、spring cloud Zuul（最初是Netflix Zuul）。</p>
<p style="">一个基本的API网关需要进行安全控制，比如需要权限认证，权限认证可以分为控制面、数据面和管理面。控制面可以简单理解为判断是否有权限的逻辑，这段逻辑是固定的，修改频率很低。数据面可以简单理解为存储层，存储了认证信息，比如用户密码。管理面是用来维护认证信息的增删改查。</p>
<p style=""><span fontsize="" color="">一个基本的API网关需要有限流功能。限流功能也可以分为控制面、数据面和管理面。控制面可以简单理解为判断是否要限流的逻辑，这段逻辑是固定的，修改频率很低。数据面可以简单理解为存储层，存储了需要对谁限流、阈值是多少等信息。管理面是用来维护限流信息的增删改查。</span></p>
<p style=""><span fontsize="" color="">一个基本的API网关需要打印完善的日志用于监控。日志功能也可以分为控制面、数据面和管理面。控制面可以简单理解为打印日志的逻辑，这段逻辑一般只有其他功能发生改变时才会改变。数据面可以简单理解为存储层，存储到磁盘，后又被集中日志采集分析器采集走。管理面是根据日志做成的监控运维平台，可以展示请求趋势、请求量、成功率等。并可以配置相应的告警或者进行BI分析。</span></p>
<p style=""><span fontsize="" color="">API网关还可以包含很多部分，这里不一一列举。每个部分都可以采用控制面、数据面和管理面三个平面分离的设计方法。因为控制面是修改频率很低的稳定态，侧重业务逻辑，高可用要求很高。数据面重心在于数据模型的合理抽象，抽象好了，数据模型也是稳定态。变化的是数据。数据会通过管理面来运维。管理面只是后台操作，高可用要求不高，可用很少的资源部署，允许宕机。晚上没有人操作时停掉服务都可以。</span></p>
<p style=""><span fontsize="" color=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-otda.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<p style=""><span fontsize="" color="">我们再来看一下这个API网关服务的架构，会发现这个架构是一个以维持系统稳定、快速支撑业务为核心的架构。合理的数据模型可以把新业务直接以数据的形式录入进来，无需任何代码修改。合理的数据模型如此之重要，所以有了MDD(模型驱动设计)的概念，这是题外话。</span></p>
<p style=""><span fontsize="" color="">当系统非常稳定了，开发人员想做一些高大上的事情，本着no zuo no die(不作不死)的原则，核心逻辑是绝对不能动的。但是我们可以从外围入手，把数据获取出来做分析决策。比如路由分发逻辑，路由有可能暂时不可用，这时可以在管理平台搞一个路由探测功能，探测路由的可用性，不可用时自动进行路由切换。还可以在管理做做大数据分析，没准发现的那个规律又新引领了一个行业。
  <br></span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">后记</span></strong></p>
<p style="">三平面分离架构的普适性和我所了解的一个大女主宫斗剧套路的普适性可以做类比。大家仔细回忆一下，自己看过的80%以上的大女主宫斗剧是不是这样的剧情：</p>
<p style="">1、女主家庭可能遭到了变故，但必定她出身不俗</p>
<p style="">2、她会遇到自己最好的姐妹女二号，将来会更为她最大的对手</p>
<p style="">3、遇到了男主后，女二号变成反派</p>
<p style="">4、经过一系列斗争，女主好不好不一定，女二号一定下场很惨</p>
<p style="">5、完结撒花~~</p>
<p style="">这个大女主宫斗剧的套路由于从不同维度完美的符合了激烈冲突、对手相当等方法学理论，可认为一种最佳实践。</p>
<p style="">三平面分离架构也是事物正常运行的一种最佳实践，想想世界是怎么运转的：</p>
<p style="">1、控制平面：世界上不变的是法则，如：质量守恒、万有引力</p>
<p style="">2、数据平面：所有的事物是数据，如：人、地球、风</p>
<p style="">3、管理平面：天降陨石、天才的降生，世界大战……谁知道不是人作为小小的数据看不清全貌的大手在掌控呢？</p>]]></description><guid isPermaLink="false">/archives/san-ping-mian-fen-chi-jia-gou</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:27:00 GMT</pubDate></item><item><title><![CDATA[架构师之路-redis集群解析]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-shi-zhi-lu-redisji-qun-jie-xi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B9%8B%E8%B7%AF-redis%E9%9B%86%E7%BE%A4%E8%A7%A3%E6%9E%90&amp;url=/archives/jia-gou-shi-zhi-lu-redisji-qun-jie-xi" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="%E5%BC%95%E5%AD%90"><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></h1>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hpqo.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">上篇<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485862&amp;idx=1&amp;sn=414f61b537094b14dab2682a343700e9&amp;chksm=fafde308cd8a6a1e993ed8774cef1c8dc1233761a17b27395b89e3d2c74b0908e0971aeedf9a&amp;scene=21#wechat_redirect" target="_blank">《架构师之路-https底层原理》</a>里我提到了上面的整体视图，文章也介绍了想要真正能在工作中及时正确解决问题的基本功：原理理解透彻。今天以redis集群解析为例介绍一个及时敏锐的发现问题的基本功：深入分析。</p>
<p style="">我认为达到深入分析有三个步骤：</p>
<p style="">第一步，深入理解</p>
<p style="">第二步，学以致用</p>
<p style="">第三步，千人千问</p>
<p style="">第一步redis集群各种原理介绍的人也很多；第二步很多人实际项目中大概也对redis集群不陌生；所以本文主要讲第三步：千人千问。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">提出问题</span></strong></p>
<p style="">"redis集群使用时有什么注意事项？"这是之前有段时间我面试喜欢问的一个问题。我的一个观点是作为redis集群的使用方而不是开发者首先要做的是用好。通过这个问题确定候选人用好了，再去挖掘他是通过了解了怎么用好的。所以我通常不会一开始就问一些中间件的原理，而是先从使用者的角度提出问题。</p>
<p style="">下面列举了6条代表性的回答：</p>
<p style="">1&gt;防止集中失效</p>
<p style="">2&gt;单线程执行，注意不要卡住</p>
<p style="">3&gt;注意客户端和服务端的版本匹配</p>
<p style="">4&gt;分片要保持流量均匀</p>
<p style="">5&gt;注意超时时间配置</p>
<p style="">6&gt;当内存缓存用，推荐删除代替更新</p>
<p style=""></p>
<p style="">每条问题前面加上个为什么，就引出了6条新的问题。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">解决问题</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q1：为什么要防止缓存集中失效？</span></strong></p>
<p style="">A：缓存穿透、缓存集中失效和缓存雪崩并称为缓存世界的三大问题。先来总结理解一下这三个概念。这三个概念都是建立在缓存的一大作用就是对后端存储，比如mysql的保护。缓存没有保护住mysql，一个或一些到mysql了，那就是穿透；一个时间点缓存数据没有了，打到mysql了就是集中失效；缓存完全丧失了保护mysql的能力，请求全打到mysql了，就是雪崩。</p>
<p style="">所以防止缓存集中失效是对后端存储的保护。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q2：为什么单线程执行，注意不要卡住？</span></strong></p>
<p style="">A：卡住换个专业点的词就是阻塞嘛。什么叫阻塞呢，一辆小轿车A在单行道跑，遇到前面一辆车B停了，那A就被阻塞了。如果A和B都在高速单行道上跑，A开了160迈、B开了140迈。就算高速的允许最高速度是120迈(咱就当路过的是没有摄像头的路段，这俩车肆无忌惮)，那A是不是还是被B给阻塞了呢？所以卡住造成的最直接影响就是快的快不起来，因为单线程不能绕行嘛。</p>
<p style="">有人说不是可以多开几个redis嘛。是滴，但是客户端分请求使用的是crc16，根本不会先探测哪个服务端比较空闲呀。所以后面来的总会被阻塞。</p>
<p style="">注意不要卡住还有个大家更常听到的名字：“避免大key问题”。其实我刚听到这个名字的时候是觉得很奇怪的。因为避免大key实际上是要避免key所对应的value不要太大。我之前一直觉得这个名字取的不对。那应该叫“避免大value问题”。后来想想这确实是正宗的中国话。比如张三的媳妇，人家都怎样叫呢？一般都是张三媳妇、张三家的。因为他家主要是张三出来抛头露面。那redis取值也一样，一般是先知道key，从key取value。用这个key取出来的数据大，就是大key问题啦。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q3：如果不注意客户端和服务端版本匹配会引发什么问题？</span></strong></p>
<p style="">A：先来思考客户端做了什么事情。我理解它就做了两件事：第一是使用RESP（Redis自定义的序列化协议）传输客户端命令并返回结果。第二是为了做第一件事，因为Redis集群是直连服务端模式，所以计算命令要落在哪个节点、哪个哈希槽上也是客户端来做的，我就称为选节点吧。</p>
<p style="">其实要回答客户端和服务端不匹配会引发什么问题，正规的方法应该是查看客户端版本升级都做过什么更新。</p>
<p style="">一般升级会做的是客户端依赖的jar包变了。这个可能会引起程序启动错误，但是这个往往启动成功了就不会再有问题，和服务端版本没有直接关系。</p>
<p style="">十年前还在用memcache的时候，发生过一次升级客户端版本，因为算法发生了变化，所以导致缓存全部不命中的问题。Redis最近的算法一直是crc16。如果不存在分布式算法不兼容问题的话，下一个要考虑的是大迭代是Redis3.0版本，支持了集群，集群模式是必须要匹配的。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q4：为什么分片要保持流量均匀？</span></strong></p>
<p style="">A：要提分片先来回忆一下redis集群的发展史，从单机版到主从版，后来有了大家可能很耳熟的哨兵模式。哨兵模式就是给主从增加一个监控，发现主节点挂了自动把从节点升级为主节点，有了故障自动迁移的功能。但是直到哨兵时代都只有一个主节点，也就是处理写请求的节点，不能称之为真正的集群。这也是很无奈的事情，一旦多个节点写一份数据，就涉及到数据一致性的问题。</p>
<p style="">一个蜂巢只能有一个蜂后，多出来一个，蜜蜂们就不会正常提供采蜜服务了，都打架去了。但是分成两个蜂巢呢？秩序就会恢复。所以现在的集群基本都是分片的原理。之前主从和哨兵的经验不能废弃，加上分片。redis集群就是将一个完整服务数据分成几份，每份都带着从节点，故障时可自动转移的一个整体。之前在《<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485567&amp;idx=1&amp;sn=0644277d2c046890a14442830a4ed0f3&amp;chksm=fafde2d1cd8a6bc70307efef696654f89074c2219f824af72effe5eb027cee983b364e4279c8&amp;scene=21#wechat_redirect" target="_blank">Redis集群搭建采坑总结</a><span style="font-size: 16px">》里讲过，1个节点的集群会有问题，最少需要3主3从也就是6个redis进程。3个主方便在1个挂掉之后重新选主。</span></p>
<p style="">梳理了这个之后，分片保持流量均匀这件事也很容易了。就是Q2的问题，均匀更不容易阻塞嘛。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q5：为什么要注意超时时间配置？</span></strong></p>
<p style="">A：提到redis的key的过期时间，首先想到的是redis的术语中，带过期时间的key又叫volatile key，就是不稳定key。怎么不稳定呢？就相当key这个对象有value和过期时间2个属性。过期时间这个属性1s改变一次(redis领域内时间都是以秒为单位)，一直在变，当然不稳定。</p>
<p style="">如果把过期时间理解为key的一个属性，那也很好理解：对其进行del、set命令时过期时间也会删除；rename会把过期时间传给新的key；incr、lpush、hset等命令改变的是key的存储容器，没有改变key这个对象本身所以不会影响过期时间。</p>
<p style="">值得注意的是persist命令就是持久化保存的意思，将不稳定变成稳定，过期时间也自动删除了。</p>
<p style="">Redis在服务端有过期策略，但是对客户端是不感知的。客户端访问过期的就是一个表现，访问不到了。实际上服务端是有两种策略配合使用，一个是惰性删除，就是访问的时候发现过期了，就直接删除了；另一个策略会定期去删除，这个是为了防止一个过期的key总是不被访问到，还占着资源不释放。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q6：为什么当内存缓存用，推荐删除代替更新？</span></strong></p>
<p style="">A：一般大家出于数据一致性的考虑，会选择删除代替更新。这都是基于更新一定要更新数据库的固有思路。并发场景下，A的值1先被更新到数据库再更新缓存时，又来了一个更新请求把A的值更新为2。如果这时候执行更新为1的服务器性能不好或者网络传输速度比更新为2的慢，导致2在数据库是最新值，而设置为1的后更新了缓存。缓存就和数据库不一致了。</p>
<p style="">但只是记住删除代替更新不太够。如果先删除缓存再更新数据库，其他请求可能会把数据库老的值再加载到缓存中。记得之前有人介绍缓存还有三大种模式：Cache-Aside、Read-Through/Write-Through、Write-Behind。</p>
<p style="">Cache-Aside就是先更新数据库再删除缓存数据，可以避免上面提到的持续脏读的问题，顶多就是更新数据库的那一小段时间有更新延迟可见。我们给Cache-Aside起个中文名，叫经典模式。</p>
<p style="">Read-Through/Write-Through就是数据以缓存为准，数据库的操作是缓存发起的。Read-Through是在读数据时发现缓存过期了，那缓存自己去数据库加载新的数据，读数据还是读取缓存值。Write-Through写数据时调用方只负责写缓存，缓存自己去同步更新数据库。Read-Through/Write-Through一般配合使用。</p>
<p style="">Write-Behind和Write-Through的区别是虽然都是是写数据时调用方只负责写缓存，但是Write-Behind缓存自己去异步更新数据库。</p>
<p style="">因为Read-Through/Write-Through、Write-Behind都是以缓存为准，缓存不可靠，所以还是推荐经典模式。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">后记</span></strong></p>
<p style="">一些朋友问我一边上班一边写文章哪有那么多时间呀。细心的朋友可能会发现我的文章一般是周末或者周一，再不就是节假日或者哪天失眠了发出来。因为内容都是非上班时间写的，但是每次下笔腹稿都是提前打好的。个人意见哈，作为架构师，很多人都会形成随时随地为工作思考和总结的习惯。所以很多人看着下班很早，人家回家路上，晒太阳的时候……未必没在想工作的事情。</p>
<p style="">架构师三件占时间的事：会议、评审和演讲。对应有三大难：提出有水平的问题、做出有水平的总结和建议、做出有水平的回答。所以每天有很多的腹稿要打。腹稿按照一定的框架结构整理就是文章。</p>
<p style="">如果大家都架构师的三大难有兴趣，我可以举一些具体的示例和解决方法。大家投票吧，如果在看超过10个，我就写这个</p>]]></description><guid isPermaLink="false">/archives/jia-gou-shi-zhi-lu-redisji-qun-jie-xi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:24:46 GMT</pubDate></item><item><title><![CDATA[架构之思-分析那些深入骨髓的设计原则]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-zhi-si-fen-xi-na-xie-shen-ru-gu-sui-de-she-ji-yuan-ze</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E4%B9%8B%E6%80%9D-%E5%88%86%E6%9E%90%E9%82%A3%E4%BA%9B%E6%B7%B1%E5%85%A5%E9%AA%A8%E9%AB%93%E7%9A%84%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99&amp;url=/archives/jia-gou-zhi-si-fen-xi-na-xie-shen-ru-gu-sui-de-she-ji-yuan-ze" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></p>
<p style="">遵从SOLID五大设计原则、遵从三大编程范式……很多的设计原则对于像我这样工作十几年的人来说，已经刻到了骨髓里。</p>
<p style="">在平时工作中，不自觉的进行了熟练的运用：看到公司里有个基础数据这样的服务，明知道很难很难也要决心治理掉：“这种服务不应该存在！任何一个软件模块都应该只对一个用户或系统利益相关者负责(单一职责原则)。我们的代码是要长长久久运行N个世纪的，不应该将领域不清的部分堆到一处！”</p>
<p style="">有一次跟刚工作几年的小伙子讨论的时候，就是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485848&amp;idx=1&amp;sn=714255a0606d4065ec0ba3177180e4fc&amp;chksm=fafde336cd8a6a20a9d1ed238a7306a7a6ecae3f36392cd9b14038cadce08edeb1059539d60f&amp;scene=21#wechat_redirect" target="_blank">《面对编码分歧怎样展开讨论》</a>里逻辑分析那一段，我突然意识到自己正面临着危险：很多原则是在很多年前思考并开始运用了，那时候的批判性思维还很弱，时代也在飞速的发展，是不是很多金科玉律当时并没有想明白、或者理解有偏差、或者应该被更新了。我是否正在逐渐走向经验主义？</p>
<p style="">想到这里，我决心从头来梳理分析自己深入骨髓的设计原则。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bkrs.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">SOLID原则</span></strong></p>
<p style="">先简单回忆一下SOLID原则的内容：</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">SRP：</span></strong>单一职责原则，任何一个软件模块应该只对某一类行为者负责。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">OCP：</span></strong>开闭原则，设计良好的软件应该易于扩展(对扩展开放)，同时抗拒修改(对修改关闭)。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">LSP：</span></strong>里氏替换原则，尽量使用抽象(如父类)，避免使用具体(如子类)，以便于方便的进行替换。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">ISP：</span></strong>接口隔离原则，客户端不应该依赖于它不需要的接口。这里啰嗦两句，Bob大叔在自己的巅峰之作《架构整洁之道》中详细介绍了SOLID原则，后来设计原则逐渐演变为六大，多出来的一个是LOD迪米特法则，又称最少知识原则，我一直找不到六大设计原则的出处，知道的朋友还烦请告知。我个人观点，接口隔离原则与迪米特法则异曲同工，所以没有必要放进来。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">DIP：</span></strong>依赖反转原则，多使用抽象接口，尽量避免使用多变的实现类。</p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485848&amp;idx=1&amp;sn=714255a0606d4065ec0ba3177180e4fc&amp;chksm=fafde336cd8a6a20a9d1ed238a7306a7a6ecae3f36392cd9b14038cadce08edeb1059539d60f&amp;scene=21#wechat_redirect" target="_blank">《面对编码分歧怎样展开讨论》</a>里逻辑分析那一段，我本身之所以认为自己是对的，原因是同事的设计违反了LSP里氏替换原则和DIP依赖反转原则，同时还间接的违反了OCP开闭原则。</p>
<p style="">落笔在这个地方踌躇了很久。我该怎么证明自己这样是对的还是错的呢？这个问题最后还是想起了Bob大叔的观点，才和自己达成和解。</p>
<p style="">Bob大叔说：</p>
<blockquote>
 <p style="">科学和数学在证明方法上有着根本性的不同，科学理论和科学定律通常是无法被证明的，比如我们没法证明万有引力的正确性，但我们可以用科学实验来演示这些定律的正确性。而且不管做多少次正确的实验，也无法排除在今后的某次实验可能会推翻万有引力定律的可能性。&nbsp;</p>
 <p style="">这就是科学理论和定律的特点：它们可以被伪证，但是没有办法被证明。如果某个结论经过一定努力没有办法证明是伪证，我们则认为它在当下是足够正确的。</p>
</blockquote>
<p style="">从这里吸取的营养是：我应该从本身这么做是否正确出发。<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485848&amp;idx=1&amp;sn=714255a0606d4065ec0ba3177180e4fc&amp;chksm=fafde336cd8a6a20a9d1ed238a7306a7a6ecae3f36392cd9b14038cadce08edeb1059539d60f&amp;scene=21#wechat_redirect" target="_blank">《面对编码分歧怎样展开讨论》</a>里逻辑分析那一段，实际上同事已经认同了他要解决的问题有别的方法去解决，而我的建议有更好的扩展性和可维护性。</p>
<p style="">扩展性和可维护性又在软件领域有多重要的作用呢？软件之所以叫软件，软本身就有灵活的意思，如果以后都不太会变化，这段逻辑刻在硬件上不是更高效嘛。为了达到软件的本来目的，软件系统必须足够软，应该很容易被修改。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ralu.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">三大编程范式</span></strong></p>
<p style="">先来简单回忆一下三大编程范式：</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">结构化编程</span></strong></p>
<p style="">结构化编程对程序控制权的直接转移进行了限制和规范。</p>
<p style="">对结构化编程的结构举个例子，大家就明白了：顺序结构、分支结构和循环结构。现在大多数编程语言都禁止使用goto这样的无限制跳转语句，因为它将会损害程序的整体结构。</p>
<p style="">工作十几年，自己从未写过goto语句。但是见过一些源码有goto语句的，那时候才见识了goto的厉害：用它可以跳转到任何代码位置，不受限制。它破坏了程序的封装，修改一个类的内部结构变的很危险，增加了耦合性。</p>
<p style="">不过我们不必担心自己没有遵循结构化编程的范式，只要是按照编程语言推荐的语法都是遵循这一范式的。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">面向对象编程</span></strong></p>
<p style="">面向对象编程对程序控制权的间接转移进行了限制和规范。</p>
<p style="">面向过程和面向对象最大的不同在于，面向对象有更好的可读性和重用性。</p>
<p style="">记得头几年评价别人代码写的不怎么样会这样说：这个同学用面向对象的语言写出了面向过程的程序。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">函数式编程</span></strong></p>
<p style="">函数式编程对程序中的赋值进行了限制和规范。</p>
<p style="">面向对象编程是对数据进行抽象，函数式编程是对行为的抽象。我们来理解一下什么是对行为的抽象。</p>
<p style="">下面代码可以被编译通过：</p>
<pre><code>new ArrayList&lt;Integer&gt;().stream().forEach(x-&gt; System.out.println(x=x+1));</code></pre>
<p style="">下面代码不可以被编译通过：</p>
<pre><code>int i =0;
new ArrayList&lt;Integer&gt;().stream().forEach(x-&gt; System.out.println(i+=x));</code></pre>
<p style="">提示说i应该是final或者effectively(实际上) final。</p>
<p style="">为什么函数式编程要求用到的变量i为不可变的？但是没有要求x是不可变呢？</p>
<p style="">区别是x是函数的参数也就是输入，i是函数外变量。而函数式编程是对行为抽象，就是说对输入进行了一系列的处理行为，得到一个输出；不能对其他数据进行操作，对其他数据操作是面向编程做的事情。</p>
<p style="">举个生活中的例子：</p>
<p style="">记得高中的时候特别喜欢陆游那首&lt;卜算子.咏梅&gt;</p>
<blockquote>
 <p style="">驿外断桥边，寂寞开无主。</p>
 <p style="">已是黄昏独自愁，更着风和雨。&nbsp;</p>
 <p style="">无意苦争春，一任群芳妒。</p>
 <p style="">零落成泥碾作尘，只有香如故。</p>
</blockquote>
<p style="">这首古文描述了对梅花的加工行为。这个行为抽象为函数是这个样子的：</p>
<pre><code>function&nbsp;梅花变香泥(一枝梅)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;第一步：孤立它&nbsp;&nbsp;&nbsp;&nbsp;第二步：让它经历黑暗&nbsp;&nbsp;&nbsp;&nbsp;第三步：让它经历风雨&nbsp;&nbsp;&nbsp;&nbsp;第四步：让其他花儿妒忌它&nbsp;&nbsp;&nbsp;&nbsp;第五步：让它凋落到泥里化为尘土只保留香气}</code></pre>
<p style="">这里“梅花变香泥”行为被抽象，对调用者来说只要调用了这个函数，就是调用了那5步骤的行为。这里仅能对一枝梅处理，一枝红杏出墙来到这里，她只能对这枝梅产生改变，她可以嫉妒这枝梅冬天开放。“<span style="font-size: 16px">梅花已谢杏花新</span>”，让梅花零落成泥后让杏花开放，这就不是这个函数该做的事了。</p>
<p style="">面向对象编程可以做这件事情，它是对数据的抽象：</p>
<blockquote>
 <p style="">暖气潜催次第春，梅花已谢杏花新。</p>
</blockquote>
<pre><code>暖气对象 暖气;春对象&nbsp;春;梅花对象 梅花;杏花对象&nbsp;杏花;public&nbsp;春对象&nbsp;描述春天()&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;梅花.状态=谢了;&nbsp;&nbsp;&nbsp;&nbsp;杏花.状态=开了;&nbsp;&nbsp;&nbsp;&nbsp;春.空气状态=暖气;&nbsp;&nbsp;&nbsp;&nbsp;春.梅花状态=谢了;&nbsp;&nbsp;&nbsp;&nbsp;春.杏花状态=开了;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;春;}</code></pre>
<p style="">我有对结构化编程没有什么疑问，毕竟50年前有人就用数学方法证明了顺序结构、分支结构和循环结构的正确性。</p>
<p style="">但是作为一直以java语言作为主要开发语言的我，java是面向对象的这句话一直在脑子里和引入函数式做斗争。</p>
<p style="">函数式编程确实有很多优势：因为函数式编程的引入变量都是不可变的，虚拟机实现时可以去掉很多多余的锁，并发处理更快；代码简洁；内聚性更好……
 <br>
 我仔细想了一下，对诸如java这种面向对象的编程语言来说，函数式编程和面向接口编程一样，是局部实现的技巧，整体结构还是面向对象的。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qvwv.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">后记</span></strong></p>
<h1 style="" id="%E5%9C%A8%E4%B8%8A%E7%AF%87%E3%80%8A%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B9%8B%E8%B7%AF-redis%E9%9B%86%E7%BE%A4%E8%A7%A3%E6%9E%90%E3%80%8B%E6%9C%80%E5%90%8E%E6%88%91%E8%AF%B4%E5%88%B0%E5%A6%82%E6%9E%9C%E5%9C%A8%E7%9C%8B%E8%B6%85%E8%BF%8710%EF%BC%8C%E6%88%91%E5%B0%B1%E5%86%99%E7%AF%87%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B8%89%E5%A4%A7%E9%9A%BE%E7%9A%84%E6%96%87%E7%AB%A0%EF%BC%8C%E5%8F%AA%E5%8F%AF%E6%83%9C%E5%91%A8%E5%85%AD%E5%8F%91%E6%96%87%E4%B8%80%E5%90%91%E9%98%85%E8%AF%BB%E9%87%8F%E4%B8%8D%E9%AB%98%EF%BC%8C%E8%99%BD%E7%84%B6%E2%80%9C%E5%9C%A8%E7%9C%8B%E7%8E%87%E2%80%9D%E8%BE%83%E5%B9%B3%E6%97%B6%E5%B7%B2%E7%BB%8F%E6%8F%90%E9%AB%98%E5%BE%88%E5%A4%9A%E4%BA%86%EF%BC%8C%E7%9B%AE%E5%89%8D%E8%BF%98%E6%B2%A1%E8%BE%BE%E5%88%B0%E3%80%82%E4%BD%86%E6%98%AF%E2%80%9C%E5%9C%A8%E7%9C%8B%E7%8E%87%E2%80%9D%E4%B8%8A%E6%9D%A5%E4%BA%86%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%84%9F%E5%8F%97%E5%88%B0%E5%A4%A7%E5%AE%B6%E7%9A%84%E6%94%AF%E6%8C%81%EF%BC%8C%E8%AE%A9%E6%88%91%E5%85%85%E6%BB%A1%E5%8A%9B%E9%87%8F%E3%80%82%E5%A5%B3%E5%AD%A9%E5%AD%90%E5%98%9B%EF%BC%8C%E6%AF%94%E8%BE%83%E6%84%9F%E6%80%A7%EF%BC%8C%E5%86%B3%E5%AE%9A%E6%9C%AC%E5%91%A8%E5%8A%A0%E6%9B%B4%E8%BF%99%E7%AF%87%EF%BC%8C%E8%A1%A8%E8%BE%BE%E4%B8%80%E4%B8%8B%E8%87%AA%E5%B7%B1%E7%9A%84%E6%84%9F%E6%81%A9~~">在上篇<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485868&amp;idx=1&amp;sn=843459f44e37003814c7a9b44bf721ec&amp;chksm=fafde302cd8a6a142123716eef9228df519285d9d1b94b6a135fb701adf8cf826a570298f497&amp;scene=21#wechat_redirect" target="_blank">《架构师之路-redis集群解析》</a>最后我说到如果在看超过10，我就写篇架构师三大难的文章，只可惜周六发文一向阅读量不高，虽然“在看率”较平时已经提高很多了，目前还没达到。但是“在看率”上来了，可以<span fontsize="" color="">感受到大家的支持，让我充满力量。女孩子嘛，比较感性，决定本周加更这篇，表达一下自己的感恩~~</span></h1>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/jia-gou-zhi-si-fen-xi-na-xie-shen-ru-gu-sui-de-she-ji-yuan-ze</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:18:02 GMT</pubDate></item><item><title><![CDATA[领域驱动设计(DDD)中简单易用的10种技巧]]></title><link>https://wiki.dgfygk.com/archives/ling-yu-qu-dong-she-ji-ddd-zhong-jian-dan-yi-yong-de-10zhong-ji-qiao</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%28DDD%29%E4%B8%AD%E7%AE%80%E5%8D%95%E6%98%93%E7%94%A8%E7%9A%8410%E7%A7%8D%E6%8A%80%E5%B7%A7&amp;url=/archives/ling-yu-qu-dong-she-ji-ddd-zhong-jian-dan-yi-yong-de-10zhong-ji-qiao" width="1" height="1" alt="" style="opacity:0;">
<p style="">领域驱动设计(DDD)，因非常适合与微服务进行配合而闻名，因《领域驱动设计》那本书的难懂而让人望而却步。</p>
<p style=""></p>
<p style="">其实《领域驱动设计》这本书讲的是：以领域为核心，在代码中体现领域的思想，开发人员和领域专家要紧密沟通。这也侧面给出了公司划分组织结构的建议。</p>
<p style=""></p>
<p style="">开始之前，我要声明一点：领域驱动设计不是万能药，它适合于实时系统，而对于分析汇总的场景，则不必使用领域驱动设计。</p>
<p style=""></p>
<p style="">技术是在进步的，现在大家对领域驱动设计抽象的越来越好。今天就来总结一下领域驱动设计简单易用的技巧。首先声明一下：提到的技巧不一定是领域驱动设计提出来的，但是这些技巧可以帮助我们更好的进行领域驱动设计。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">限界上下文</span></strong></p>
<p style=""></p>
<p style="">画架构图尽量简单易懂。但是项目本身很复杂怎么办呢？那就要按问题域划分子域。每一个子领域架构是很简单的，想看到全貌可以将它们之间的关联形成上下文地图。这就是限界上下文的作用。</p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pigy.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">DDD建议区分场景来划分子领域，通过依赖接口而不是具体实现解耦。使用微服务实现限界上下文中的低耦合。微服务不是简单的拆分，而是对设计提出了更高的要求。</p>
<p style=""></p>
<p style="">举个常用的例子：SOA架构中建议进行数据库操作单独提供服务进行，使用方通过接口依赖实现低耦合。这个思想和DDD的思想异曲同工：DDD建议将领域对象与数据库映射，把数据表的增删改查采用接口调用实现。底层数据表结构的修改对上层屏蔽。这就是将软件对象进行持久化操作，并且形成独立的限界上下文。DDD还要求数据库表设计以领域模型为核心，并且推荐不要在sql中进行join操作，而是通过逻辑层来进行，让表与表之间的关系直接转化为软件对象之间的关系。</p>
<p style=""></p>
<p style="">如果看到这里你还是觉得限界上下文很抽象，不用担心，其他的技巧就是来帮忙咱们划分独立的子领域。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">聚合与聚合根</span></strong></p>
<p style=""></p>
<p style="">聚合体现的是整体与部分的关系。部分与整体有相同的生命周期。一旦将对象间的关系设计成聚合，外部对象只能访问“聚合根”。充血模型就是一种聚合的实现。</p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-amzy.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">我在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485928&amp;idx=1&amp;sn=d60b63b554e5d1b054f273e898ed12c5&amp;chksm=fafde346cd8a6a50fbf7d2d4b3eafc36ba356e161251504203465effcf187dc5368cff9f04b9&amp;scene=21#wechat_redirect" target="_blank">《CURD系统怎么做出技术含量--怎样引导面试》</a>里用实例介绍过充血模型。白话来说，就是一个普通的POJO，如：“人”对象，里面有对应属性，如姓、名、年龄、学历、出生年月。贫血模型中“人”对象只包含可以通过lombok自动生成的方法。而充血模型则可以包含由各个属性组成的一个完整简历的方法，或者是直接返回星座的方法。外部只能通过这些方法，也就是“聚合根”来操作“人”这个对象，不能直接访问其内部属性。因为部分和整体有相同的生命周期，意味着人这个对象被销毁，它的属性和方法也同时消失。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">DDD工厂与装载</span></strong></p>
<p style=""></p>
<p style="">DDD工厂与设计模式中的工厂有较大的差别。</p>
<p style=""></p>
<p style="">设计模式中的工厂，是将被调用方设计成一个接口下的多个实现，并装配到工厂中。工厂负责通过key值找到对应的实现类，创建出来，返回给调用方。这样就降低了调用方与被调用方的耦合度。</p>
<p style=""></p>
<p style="">DDD工厂是通过工厂创建领域对象，作为对象生命周期的起点。它的核心在于装配这个环节，比如订单工厂需要将订单对象是要将订单概述、订单明细统一封装到订单对象中。</p>
<p style=""></p>
<p style="">举个例子：漫画中的超级英雄在设计模式工厂中，工厂创建出来的时候可能是个小菜鸟，在业务逻辑运行过程中，他可能会需要升级打怪才变成超级英雄。而在DDD工厂中，工厂会负责给超级英雄披上钢铁侠的盔甲，装配好美国队长的盾牌。他从工厂里出来就可以拯救世界了。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">DDD仓库</span></strong></p>
<p style=""></p>
<p style="">DDD工厂和DDD仓库都是聚合的实现。通过DDD工厂装配好的领域对象会返回到DDD仓库中。事实上Spring上下文就是一个DDD仓库底座。它包含了程序需要的各种领域对象。有些领域对象如DAO是从数据库中取的，有些是从缓存中取的，还有些如POJO是直接创建的。但是业务逻辑不关心领域对象的来源，只需要在需要的时候直接使用，就与领域对象的具体实现之间做了解耦。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ctfd.jpg&amp;size=m" width="710px" height="481px" style="display: inline-block"></p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">事件风暴法</span></strong></p>
<p style=""></p>
<p style="">在领域设计之初的需求分析阶段，需求分析的基本思路就是统一语言建模。它是我们的指导思想。但落实到实践层面可以采用的方法是事件风暴法。它是一种基于工作坊的DDD实践方法，可以帮助我们发现业务领域中正在发生的事件，指导领域建模以及程序开发。</p>
<p style=""></p>
<p style="">事件风暴中的风暴是头脑风暴的意思，我在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484524&amp;idx=1&amp;sn=471a7ea978dece958c3b0285435551fe&amp;chksm=fafdeec2cd8a67d43af8ef1a346b2f2b012eb738b44d0755e3b6d5a49f2ccede10fae94efe72&amp;scene=21#wechat_redirect" target="_blank">《复联4里用到的方法论》</a>中详细解释过头脑风暴法，这里不再赘述。事件即事实，已经发生，不会更改。信息管理系统可以将这些事实以信息的形式存储，即信息就是事实。信息管理系统的作用就是将信息存储，管理与跟踪。对事件可以采用上游发布、下游订阅的方式进行解耦，按照业务流程来梳理领域事件。</p>
<p style=""></p>
<p style="">举个例子：把大象装冰箱分成三步，每个步骤都是一个事件，每个事件之间要解耦。打开冰箱门涉及冰箱领域对象、人领域对象等。这个事件完成将会触发一个信息存储：冰箱状态变成已打开。这些都是围绕着打开冰箱门这个事件展开。这个事件完成后，只要对第二步把大象装冰箱进行通知即可。之后的事情再不会和第一步发生关系。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">防腐层</span></strong></p>
<p style=""></p>
<p style="">防腐层（Anti-corruption layer，简称 ACL）介于新逻辑和旧逻辑之间，用于确保新逻辑的设计不受老逻辑的限制。是一种在不同逻辑间转换的机制。</p>
<p style=""></p>
<p style="">创建一个防腐层，以根据客户端自己的域模型为客户提供功能。该层通过其现有接口与新逻辑进行通信，几乎不需要对其进行任何修改。因此，防腐层隔离不仅是为了保护你的系统免受异常代码的侵害，还在于分离不同的域并确保它们在将来保持分离。</p>
<p style=""></p>
<p style="">举个例子：我之前带过的项目，由于架构不能满足业务增长的需求，需要进行大范围重构。这里我们使用了“留壳扣瓤”的方法，对外接口保持不变，内部实现换成新的，这个接口层就是我们的防腐层。当然啦，不要以为瓤随随便便就可以换了，过渡期我们保留了旧逻辑，用开关的方式在新老逻辑之间做灰度和切换，并准备好随时降级。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">谦卑对象模式</span></strong></p>
<p style=""></p>
<p style="">谦卑对象模式是整洁架构中提出的一个概念。最初的设计目的是帮助单元测试的编写者区分容易测试的行为与难以测试的行为。难以测试的行为即谦卑对象，要尽量的简单，因为这些难以通过测试保证其正确性。</p>
<p style=""></p>
<p style="">例如：GUI是很难进行单元测试的。可以利用谦卑对象模式将GUI的这两种行为拆分成视图和数据两部分。视图部分属于难以测试的谦卑对象，这种对象的代码应该越简单越好。</p>
<p style=""></p>
<p style="">再比如：数据库网关接口的实现，属于谦卑对象。所以我们使用成熟的框架，自身的代码尽量简单。测试时我们只需要对网关接口进行Mock，测试其他逻辑。网关究竟是怎么与数据库进行交互的，往往不在单元测试中考虑。</p>
<p style=""></p>
<p style="">谦卑对象模式与DDD有什么关系呢？近年来，<span style="font-size: 16px">高可用、稳定性、弹力设计、容错设计和业务连续性越来越得到公司的重视。基于稳定性的</span>隔离术也逐渐成为划分问题域的考虑方向。我在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485188&amp;idx=1&amp;sn=3618568ba3bf9c396195d8e2c4e6723d&amp;chksm=fafdedaacd8a64bca74024452fb88a56ff99aa6bcd9ae841fca1b91cbff40d4dd78698b09dcc&amp;scene=21#wechat_redirect" target="_blank">《服务的容灾与容错》</a>这篇文章中总结了隔离术，这里不再赘述。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">DDD本质就是利用分治的思想将复杂的事情简单化，本文所讲的技巧就是按什么来分治的问题。本文涉及的概念整体结构如下图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-owui.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/ling-yu-qu-dong-she-ji-ddd-zhong-jian-dan-yi-yong-de-10zhong-ji-qiao</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:15:22 GMT</pubDate></item><item><title><![CDATA[M*N个策略造成类爆炸怎样重构？]]></title><link>https://wiki.dgfygk.com/archives/m-nge-ce-lue-zao-cheng-lei-bao-zha-zen-yang-chong-gou</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=M%2AN%E4%B8%AA%E7%AD%96%E7%95%A5%E9%80%A0%E6%88%90%E7%B1%BB%E7%88%86%E7%82%B8%E6%80%8E%E6%A0%B7%E9%87%8D%E6%9E%84%EF%BC%9F&amp;url=/archives/m-nge-ce-lue-zao-cheng-lei-bao-zha-zen-yang-chong-gou" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">背景</span></strong></p>
<p style=""></p>
<p style="">收到朋友针对他们遇到的具体问题写一篇文章的邀请，问题是这样的</p>
<p style=""></p>
<p style="">朋友:</p>
<p style="">我们公司对接了M个三方服务 &nbsp;然后还有N种业务 &nbsp;实现用策略的话感觉有点类爆炸 &nbsp;也想不到什么好方法了 &nbsp;这也是早上刚接到一个重构支付的需求想到的 &nbsp; 根据业务和供应商搞了太多策略实现了[晕]</p>
<p style=""></p>
<p style="">我:</p>
<p style="">初步感觉应该用打造接入平台的方式解决</p>
<p style=""></p>
<p style="">朋友:</p>
<p style="">开始的时候没有考虑到 &nbsp;代码一言难尽了 [捂脸] &nbsp;谢谢谢老师</p>
<p style=""></p>
<p style="">我从对话中分解出下面几个问题</p>
<p style="">1、理解重构、重写和重新架构</p>
<p style="">2、怎样做重构？</p>
<p style="">3、重构过程与业务支撑之间怎样平衡？</p>
<p style=""></p>
<p style="">下面我针对这三个问题展开论述。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">理解重构、重写和重新架构</span></strong></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">重构是对代码和功能进行</span><span style="font-size: 16px; color: rgb(25, 25, 25)">渐进式改进</span>；重写是<span style="font-size: 16px; color: rgb(25, 25, 25)">要摧毁它，重新构建；重新架构在大框架上做修改和再设计，具体每个实现模块可能是用原来的。</span></p>
<p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">举个例子，搭积木。重构就是我用积木搭建了一个小马，发现小马某些部分不好看，我就把几块积木调整调整，让小马更好看；重写就是我用积木搭建了一个小马，发现自己搭建好了完全不喜欢，把积木推到了重新搭，既然是重新搭，搭好的东西和原来差距有多大就不好说了；重新架构就是我用积木搭建了一个小马，发现自己其实想要个小桥，没有必要全部推到重做，而是可以先画好设计图，然后直接把小马的四条腿拆下来做桥墩，脊背部分做一部分桥面，实在不能用的再拆下来重安装。</span></p>
<p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">重构是小步跑的过程，如果对外提供的功能没有变化，这是稳妥的方法；重写往往伴随着功能上的升级，不然对于“我不知道这段代码是干什么用的但我不敢删”这种事情很难保证对外提供的功能没有变化。
  <br></span></p>
<p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">重构和重写与修改量没有直接关系。有个哲学问题叫做“特修斯之船”，是说一艘船持续的在海上航行，船不断有部分老化或者坏掉进行替换，最终整艘船的所有部分都换过一遍，这时候船还是原来那艘船吗？</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qhni.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px; color: rgb(25, 25, 25)">
  <br>
 </span></p>
<p style="">所以咱们不再纠结概念，统一用重构这个词，聚焦于怎么解决问题。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">怎样做重构</span></strong></p>
<p style=""></p>
<p style="">首先需要对系统进行一个生命周期预估，包括：</p>
<ul>
 <li>
  <p style=""><span fontsize="" color="">未来需求预判</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">模块划分拆解</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">总结历史问题</span></p>
 </li>
</ul>
<p style=""></p>
<p style=""><span style="font-size: 16px">朋友说：“开始的时候没有考虑到 &nbsp;代码一言难尽了”，其实大可不必这样想。架构和代码</span>不是设计出来的，是演进出来的。如果一开始要我做，一段时间之后，也很可能会遇到相同觉得非重构不可的情况。虽然我一定会做未来需求预判，但是现实往往会出现很多例外情况，让程序充满了妥协。</p>
<p style=""></p>
<p style="">那针对朋友遇到的问题，怎么设计更合理呢？</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">支付重构</span></strong></p>
<p style=""></p>
<p style="">如果只是重构支付部分，我个人觉得相对容易。因为支付需要的要素比较好抽象，主要是下游支付渠道需要什么。自从有了网联，现在对接方也比较固定了。直接接网联或者微信支付、支付宝支付、拉卡拉…… 他们所需要的要素都差不多。因为它们都要接入网联完成国家监管的需求，所以要素主要看网联需要哪些要素。</p>
<p style=""></p>
<p style="">当然，它们的签名方式可能会不同；调用的支付渠道地址不同；给支付渠道传递的时候入参组装方式也不同。甚至有的支付渠道还需要有特殊的步骤。这时候怎么抽象呢？</p>
<p style=""></p>
<p style="">推荐“工作流”+策略模式。这是一种实现积木化的方式。定义一个统一接口，比如</p>
<p style="">class Executor {</p>
<p style="">&nbsp; &nbsp; &nbsp; void execute(Task task);</p>
<p style="">}</p>
<p style="">task里放置所有的入参，以及每次执行时产生的，下游需要的中间数据。签名等流程各自实现此接口。这样就实现了自由组装的重用。</p>
<p style=""></p>
<p style="">举个例子，下面<span style="font-size: 16px">“</span>积木块”各自实现了自己的execute：</p>
<p style=""></p>
<p style="">微信支付签名、微信支付入参组装、微信支付结果解析、支付宝<span style="font-size: 16px">支付签名、支付宝支付入参组装、支付宝支付结果解析、</span>支付宝特殊处理、下游http调用(这里假设微信和支付宝都是http的post请求来调用的，这样发起调用来说两者只是请求地址和入参不同）</p>
<p style=""></p>
<p style="">这时候可以组装微信支付流程：</p>
<p style="">List list&nbsp;= new List();</p>
<p style="">list.put(微信支付签名);</p>
<p style=""><span style="font-size: 16px">list.put(微信支付入参组装);</span></p>
<p style=""><span style="font-size: 16px">list.put(下游http调用);</span></p>
<p style=""><span style="font-size: 16px">list.put(微信支付结果解析);</span></p>
<p style=""></p>
<p style="">for(Executor e ：list)&nbsp;{</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;e.execute(task);</p>
<p style="">}</p>
<p style=""></p>
<p style="">同样可以组装支付宝支付流程：</p>
<p style="">List list&nbsp;= new List();</p>
<p style="">list.put(<span style="font-size: 16px">支付宝</span>支付签名);</p>
<p style=""><span style="font-size: 16px">list.put(支付宝支付入参组装);</span></p>
<p style=""><span style="font-size: 16px">list.put(下游http调用);</span></p>
<p style=""><span style="font-size: 16px">list.put(支付宝支付结果解析);</span></p>
<p style=""><span style="font-size: 16px">list.put(支付宝支付特殊处理);</span></p>
<p style=""></p>
<p style="">for(Executor e ：list)&nbsp;{</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;e.execute(task);</p>
<p style="">}</p>
<p style=""></p>
<p style="">不同的流程只是list不同，这个list是可以时间配置化的，可以配置在配置文件中、甚至可以从数据库中读取。微信支付流程和支付宝支付流程是两种不同的策略，可以使用策略模式。入参选择哪种策略就用哪种策略执行。这样，各个流程之间没有干扰，积木块变更影响小还可以随意组合灵活性高。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">整体重构</span></strong></p>
<p style=""></p>
<p style="">朋友的业务复杂性还不在于支付，而在于收单。解决方法就是文章开头提到的接入平台的方式。打造平台的关键不是技术，而是规范。<span style="font-size: 16px">自己制定规范。别人接入都要按照规范来。</span></p>
<p style=""></p>
<p style="">这里有前提：公司自身的影响力。平台是底层自己需要有决策权，比如微信开放平台，想接入就要理解并使用他们的API。而因为微信基础在，必须按照人家说的来，这就是平台影响力。</p>
<p style=""></p>
<p style="">在实际工作中，一般有两种情况。接入方自身也没有什么规范，本身就要求功能可用，这时候咱们自身有规范，对方比较容易接受。另外一种，对方是实力背景的大公司，也有自己的规范。这时候咱们处于弱势，要按照别人的规范来，可以使用上面支付重构的积木化方法进行进行组合。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">重构过程与业务支撑之间怎样平衡？</span></strong></p>
<p style=""></p>
<p style="">在重构过程中一般会遇到两顶思考帽问题：一顶是需求的帽子，一顶是重构的帽子。有限的时间怎么分配呢？建议一开始就定好目标：什么阶段要重构到什么程度，然后分解工作。最后分摊到每天比如需要2小时时间。那么在需求开发时间评估的时候就要把这个时间扣掉。</p>
<p style=""></p>
<p style="">重构做完，老逻辑迁移到新逻辑有风险。这时候我的建议是：</p>
<p style=""></p>
<p style="">首先，把新功能当成重构结果的验金石。有新功能，先在新功能上验证新逻辑的正确性。因为新功能上线到上量有一个过程，初期出了问题影响也不会很大。</p>
<p style=""></p>
<p style="">然后，老逻辑迁移需要灰度。可以一个业务一个业务的灰度迁移，也可以按照流量百分比进行迁移。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">最后总结一下对重构的几点建议：</p>
<p style="">1、小步快跑，稳步迭代</p>
<p style="">2、设计与代码的重构结伴而行</p>
<p style="">3、如果模块或者函数不能想到一个好名字，很可能是设计出了问题</p>
<p style="">4、每步要保证足够简单，想象随时可能会停止</p>]]></description><guid isPermaLink="false">/archives/m-nge-ce-lue-zao-cheng-lei-bao-zha-zen-yang-chong-gou</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:12:15 GMT</pubDate></item><item><title><![CDATA[说透分布式事务]]></title><link>https://wiki.dgfygk.com/archives/shuo-tou-fen-bu-shi-shi-wu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E8%AF%B4%E9%80%8F%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1&amp;url=/archives/shuo-tou-fen-bu-shi-shi-wu" width="1" height="1" alt="" style="opacity:0;">
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483696&amp;idx=1&amp;sn=071cb785dc5f01e53397f3995f88a5d3&amp;chksm=fafdeb9ecd8a628873e20dfd813280e1e3d6bda86907de6772bf8a0a9f7e06250a5073034373&amp;scene=21#wechat_redirect" target="_blank">《分布式事务：从刚性事务到柔性事务》</a>中，讲过分布式事务。晚上打开微信发现咱们【编程一生】用户群里又讨论起来了。觉得有必要再讲一遍。想加入一起讨论的，可以加我微信<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)"> brmayi</span></strong> 注明【入群】。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">分布式事务的本质</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">事务起源</span></strong></p>
<p style=""></p>
<p style="">事务本意就是要处理的事情。计算机中引入了这个词用来表示要处理事情的单元。单元是不可再分的，在数据库中就把这种不可再分给出了明确的特性定义，就是ACID原则。为了和分布式系统中的事务做区分，又叫刚性事务。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">ACID原则</span></strong></p>
<p style=""></p>
<ul>
 <li>
  <p style=""><strong>原子性</strong>（Atomicity）</p>
  <p style="">事务必须要么全都执行，要么全都不执行。</p>
  <p style=""></p>
 </li>
 <li>
  <p style=""><strong>一致性</strong>（Consistency):</p>
  <p style="">保证数据的完整性。事务必须使数据库从一个一致性状态变换到另外一个一致性状态。</p>
  <p style=""></p>
 </li>
 <li>
  <p style=""><strong>隔离性</strong>（Isolation）</p>
  <p style="">一个事务的执行不能被其他事务干扰，即一个事务内部的操作及使用的数据对并发的其他事务是隔离的，并发执行的各个事务之间不能互相干扰。</p>
  <p style=""></p>
 </li>
 <li>
  <p style=""><strong>持久性</strong>（Durability）:
   <br>
   一个事务一旦被提交，它对数据库中数据的改变就是永久性的，接下来的其他操作和数据库故障不应该对其有任何影响,该修改即使出现系统故障也将一直保持。</p>
  <p style=""></p>
  <p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mglk.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
 </li>
</ul>
<p style=""></p>
<p style="">什么事儿都有个程度。人和人之间的认知是不一样的。举个真实的例子：有次我带儿子去参加公司的轰趴。大家在玩三国杀，我不会。我只是象征性的问我儿子会不会。他斩钉截铁的说会。我明知道他的水平还是让他试了试。结果十几个同事和我儿子展开了异常艰难的对决。这个对决让我充分意识到吃饭时我发的200元红包发少了，太对不起大家了。</p>
<p style=""></p>
<p style="">儿子的会，连我这个不会的人都开出来他分不清敌我。我们同事们高风亮节，让这么一个人在三国沙场上活到了最后。</p>
<p style=""></p>
<p style="">回到事务的ACID特性。事务隔离性有四个级别。在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485279&amp;idx=1&amp;sn=49ce5a8c586979388a1828686a6cbd21&amp;chksm=fafdedf1cd8a64e7d95b948cd60135db1618e71c3307d405e9b3c27c5fddedeaf1b1b5f00c4b&amp;scene=21#wechat_redirect" target="_blank">《MySQL常见6个考题在实际工作中的运用》</a>中有介绍，这里不赘述。重点讲一致性的三个级别。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">数据一致性三个级别</span></strong></p>
<p style=""></p>
<h3 style="" id="%E5%BC%BA%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%88strict-consistency%EF%BC%89"><strong>强一致性（Strict Consistency）</strong></h3>
<p style="">也称为：<strong>原子一致性（Atomic Consistency）线性一致性（Linearizable Consistency）</strong></p>
<p style="">强一致性有两个要求：</p>
<ul>
 <li>
  <p style="">任何一次读都能读到某个数据的最近一次写的数据。</p>
 </li>
 <li>
  <p style="">系统中的所有进程，看到的操作顺序，都和全局时钟下的顺序一致。</p>
 </li>
</ul>
<p style=""></p>
<h3 style="" id="%E9%A1%BA%E5%BA%8F%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%88sequential-consistency%EF%BC%89"><strong>顺序一致性（Sequential Consistency）</strong></h3>
<p style="">有两个要求：</p>
<ul>
 <li>
  <p style="">任何一次读都能读到某个数据的最近一次写的数据。</p>
 </li>
 <li>
  <p style="">系统的所有进程的顺序一致，而且是合理的。即不需要和全局时钟下的顺序一致，错的话一起错，对的话一起对。</p>
 </li>
</ul>
<p style=""></p>
<h3 style="" id="%E5%BC%B1%E4%B8%80%E8%87%B4%E6%80%A7"><strong>弱一致性</strong></h3>
<p style="">数据更新后，如果能容忍后续的访问只能访问到部分或者全部访问不到，则是弱一致性。</p>
<p style=""></p>
<p style="">其实每个级别都还可以细分。这里只讲一种和今天主题相关的：最终一致性。它属于弱一致性。</p>
<p style=""></p>
<h4 style="" id="%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4%E6%80%A7"><strong>最终一致性</strong></h4>
<p style="">不保证在任意时刻任意节点上的同一份数据都是相同的，但是随着时间的迁移，不同节点上的同一份数据总是在向趋同的方向变化。</p>
<p style="">简单说，就是在一段时间后，节点间的数据会最终达到一致状态。</p>
<p style=""></p>
<p style="">在数据库事务定义ACID的时候，没有想那么多。其实数据库事务中的一致性是强一致。但是强一致到了分布式系统中就遇到了困难。</p>
<p style=""></p>
<h4 style="" id="cap%E5%AE%9A%E7%90%86"><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">CAP定理</span></strong></h4>
<p style=""></p>
<p style="">CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的，他指出WEB服务无法同时满足下面3个属性：</p>
<ul>
 <li>
  <p style="">一致性(Consistency) ：客户端知道一系列的操作都会同时发生(生效)</p>
 </li>
 <li>
  <p style="">可用性(Availability) ：每个操作都必须以可预期的响应结束</p>
 </li>
 <li>
  <p style="">分区容错性(Partition tolerance) ：即使出现单个组件无法可用,操作依然可以完成</p>
 </li>
</ul>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mcsa.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">在分布式系统中，一个Web应用至多只能同时支持上面的两个属性。显然，任何横向扩展策略都要依赖于数据分区。因此，设计人员必须在一致性与可用性之间做出选择。</p>
<p style=""></p>
<p style="">孔子曾经曰过：没有100分的爱人，只有50分的两个人的共同努力。那聪明的工程师就想了：一个WEB应用至多能支持两个属性达到100分，也就是200分。那我能不能让三个属性都支持到60分。3*60分=180分。200分是上限的话，还可以根据应用自身的需求决定把剩余20分应用到哪方面的提交。这三个60分是什么标准呢？这就是BASE理论。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">BASE理论</span></strong></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">
   <br>
  </span></strong></p>
<ul>
 <li>
  <p style="">Basically Available（基本可用）</p>
 </li>
 <li>
  <p style="">Soft state（软状态）</p>
 </li>
 <li>
  <p style="">Eventually consistent（最终一致性）</p>
 </li>
</ul>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iism.jpg&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">BASE理论和分布式事务到底有什么关系呢？我搜索了一下，发现没有一篇文章真正把它们的关系说明白。网上的东西给我感觉：它们觉得有关系，又不明白关系到底是啥。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">分布式事务与BASE理论的关系</span></strong></p>
<p style=""></p>
<p style="">既然是事务要遵循的是ACID特性。分布式系统与单机在隔离性和持久性上没有明显的差异。所以重点关注的是原子性和一致性。原子性是说要么全做，要么全都不做，这样数据就是一致的。所以原子性是因，根本要保证的数据一致性。</p>
<p style=""></p>
<p style="">所以实际工作中，我很少听到谈论具体问题的时候用分布式事务这个概念，更多的是说数据一致性。我之前写的<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484990&amp;idx=1&amp;sn=8dc1444160af4be808f8cdbd21e6c28b&amp;chksm=fafdec90cd8a65862023d129a095a286209efefcf699b28351b65128df3aa7e2c2d009ff0aa0&amp;scene=21#wechat_redirect" target="_blank">《数据一致性-对账》</a>这篇文章里提到了好几种分布式事务常用的手段，但是我没有说分布式事务这个词。工作中用这个词，具体解决的问题不明确，不建议用，建议把具体要解决的问题想清楚。</p>
<p style=""></p>
<p style="">既然解决的问题是数据一致性问题，web服务不能不考虑可用性，单纯只保证一致性，所以就要考虑BASE理论了。觉得我没讲清楚的扣圆周率小数点后四位。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">你真的需要分布式事务吗？</span></strong></p>
<p style=""></p>
<p style="">既然分布式事务根本上解决的是分布式数据一致性问题，在大多数问题上使用seata等框架太重了。咱们平时有很多简单有效的方法一直在用。</p>
<p style=""></p>
<p style="">比如支付交易中，有典型的补偿型数据一致性保障：发起一笔支付没有收到响应，可以使用补偿查询作为第一层数据一致性保障；查询半小时还是查不回来，就关单操作，该冲正的冲正(取消交易，已付款退回账户中)，这是第二层数据一致性保障；清结算之前还要对账(做核对校验，对不上人工处理)，这是第三层数据一致性保障。在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484200&amp;idx=1&amp;sn=4ae0df004f968bb785c15d53270a5800&amp;chksm=fafde986cd8a609057f4d5737ebcfce6b768f263636b9f9faf90bf30164027dd1e1014c7abce&amp;scene=21#wechat_redirect" target="_blank">《程序常用的设计技巧》</a>里我也提过其他的补偿性方案。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">看问题还是要从本质问题出发，最好不要引入额外的概念引起歧义。</p>]]></description><guid isPermaLink="false">/archives/shuo-tou-fen-bu-shi-shi-wu</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:08:14 GMT</pubDate></item><item><title><![CDATA[四种常用的微服务架构拆分方式]]></title><link>https://wiki.dgfygk.com/archives/si-zhong-chang-yong-de-wei-fu-wu-jia-gou-chai-fen-fang-shi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9B%9B%E7%A7%8D%E5%B8%B8%E7%94%A8%E7%9A%84%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E6%8B%86%E5%88%86%E6%96%B9%E5%BC%8F&amp;url=/archives/si-zhong-chang-yong-de-wei-fu-wu-jia-gou-chai-fen-fang-shi" width="1" height="1" alt="" style="opacity:0;">
<p style="">微服务架构并无标准架构，不然什么架构师大会也不会各个系统架构百花齐放了。虽然没有固定的套路，却有一些经验，今天就来做一个总结。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于角色拆分</span></strong></p>
<p style=""></p>
<p style="">这种拆分方式常见于基础设施以及其PaaS层的架构，比如<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485181&amp;idx=1&amp;sn=df4568317a038fcff3b77cd66d0b7bb6&amp;chksm=fafdec53cd8a654573d4210e755fac7c95b5145aa8a5c76cb3df64478a44da0446945ef04c19&amp;scene=21#wechat_redirect" target="_blank"><strong><u>服务治理</u></strong></a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484514&amp;idx=1&amp;sn=5e841cfbfdd405168c08368d7a78dbe2&amp;chksm=fafdeecccd8a67da2f520388da9d13cb72b476e0b372d6e105b66d333ec70e1a17b013763d1c&amp;scene=21#wechat_redirect" target="_blank"><strong><u>k8s</u></strong></a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484948&amp;idx=1&amp;sn=5e5e32ebde19bfb3803e697b6ddf4ef4&amp;chksm=fafdecbacd8a65ac45ac2fabfbba02c70a9ea556e4449991b55a3cd3d9223c636af5f1edd947&amp;scene=21#wechat_redirect" target="_blank"><strong><u>kafka</u></strong></a>。所谓基础组件的PaaS层是说，基础设施本身主要作为基础设施使用，是IaaS层。但是基础设施本身需要维护功能，需要增删改查等运维操作。业界基于开源做的二次开发也着重在做这方面的工作。</p>
<p style=""></p>
<p style="">下面以kafka做说明。因为要上升到PaaS层，下图基于美团对kafka的二次开发封装，产品名叫<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483874&amp;idx=2&amp;sn=786c19686a3f33ef6e6e13c5f6c961ef&amp;chksm=fafdeb4ccd8a625a1d8b34eeb9d55ad4d9530ee548c1cffdeb49dff4eeeb287c193bacf1eae3&amp;scene=21#wechat_redirect" target="_blank"><strong><u>mafka</u></strong></a>。</p>
<p style=""></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-aovo.png&amp;size=m" width="635px" height="464px" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">咱们直接看★标注的部分mafka-manager，这个就是运维操作统一管理端。充当的就是管理者的角色。再看看kafka其他组件的名称：生产者( producer )、消费者( consumer )、经纪人( broker )。核查员( monitor )。架构的组织都是根据角色来来划分的。</p>
<p style=""></p>
<p style="">为什么我要将mafka-manager用<span fontsize="" color="">★标注</span>呢。因为不管是基础设施还是别的，以产品化的观点，需要对外提供一个完整的产品体验。完整的产品包含什么呢？统一的产品外观、统一的接口定义、统一的服务运营。</p>
<p style=""></p>
<p style="">我们要接入mafka，虽然最终程序里要用的是生产者、消费者这些，但是第一步都要在mafka-manager对应的界面上申请。<span fontsize="" color="">mafka-manager</span>类似于网关入口的角色。业界有专门把这些接口服务抽象出来叫做api网关。</p>
<p style=""></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483841&amp;idx=1&amp;sn=c236d6829a2cce8ad56236bf75085bbf&amp;chksm=fafdeb6fcd8a62794646fd34a37c0532c92bba41d7664badc4db18162b141d0c8285e5d29a52&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">k8s</span></u></strong></a>架构中，api网关这个概念更加明显一些。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zmtb.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">kubectl是k8s的控制台命令交互界面、web UI是浏览器交互界面，不同的交互界面会走统一的api server。这里api server就是api网关服务。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于可扩展性拆分</span></strong></p>
<p style=""></p>
<h1 style="" id="%E9%A6%96%E5%85%88%E6%9D%A5%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%8Bakf%E6%89%A9%E5%B1%95%E7%AB%8B%E6%96%B9%E4%BD%93%E3%80%82"><span style="font-size: 17px">首先来了解一下AKF扩展立方体。</span></h1>
<h1 style="" id=""></h1>
<h1 style="" id="akf%E6%89%A9%E5%B1%95%E7%AB%8B%E6%96%B9%E4%BD%93%EF%BC%88scalability-cube%EF%BC%89%EF%BC%8C%E6%98%AF%E3%80%8A%E6%9E%B6%E6%9E%84%E5%8D%B3%E6%9C%AA%E6%9D%A5%E3%80%8B%E4%B8%80%E4%B9%A6%E4%B8%AD%E6%8F%90%E5%87%BA%E7%9A%84%E5%8F%AF%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%EF%BC%8C%E8%BF%99%E4%B8%AA%E7%AB%8B%E6%96%B9%E4%BD%93%E6%9C%89%E4%B8%89%E4%B8%AA%E8%BD%B4%E7%BA%BF%EF%BC%8C%E6%AF%8F%E4%B8%AA%E8%BD%B4%E7%BA%BF%E6%8F%8F%E8%BF%B0%E6%89%A9%E5%B1%95%E6%80%A7%E7%9A%84%E4%B8%80%E4%B8%AA%E7%BB%B4%E5%BA%A6%EF%BC%9A"><strong>AKF扩展立方体（Scalability Cube）</strong><span style="font-size: 17px">，是《</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485553&amp;idx=1&amp;sn=945e46d81bef62f35ac447895d99cdd2&amp;chksm=fafde2dfcd8a6bc905cd0e9e50a9e51247a6d53f614a948b89209341a55877893a3fcba295dd&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">架构即未来</span></u></strong></a><span style="font-size: 17px">》一书中提出的可扩展模型，这个立方体有三个轴线，每个轴线描述扩展性的一个维度：</span></h1>
<p style="">X轴 —— 代表无差别的克隆服务和数据，工作可以很均匀的分散在不同的服务实例上；</p>
<p style="">Y轴 —— 关注应用中职责的划分，比如数据类型，交易执行类型的划分；</p>
<p style="">Z轴 —— 关注服务和数据的优先级划分，如分地域划分。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ynko.webp&amp;size=m" width="567px" height="346px" style="display: inline-block"></p>
<p style=""></p>
<p style="">白话来说：X轴拆分就是通过加机器水平拆分；Y轴拆分就是按业务逻辑垂直拆分；Z轴拆分就是按照算法进行分片，这个算法比如按地域，不同地域访问不同的分片或者服务。</p>
<p style=""></p>
<p style="">举个例子，比如一般公司的<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485868&amp;idx=1&amp;sn=843459f44e37003814c7a9b44bf721ec&amp;chksm=fafde302cd8a6a142123716eef9228df519285d9d1b94b6a135fb701adf8cf826a570298f497&amp;scene=21#wechat_redirect" target="_blank"><strong><u>redis集群</u></strong></a>会有一个团队来进行统一维护。redis集群有主有从，数据都是一样的，多副本容灾，这是X轴水平的拆分。一个公司很多业务，redis团队会对不同的业务提供不同的集群，这是Y轴垂直拆分；集群内部数据会通过sharding做分片，这是Z轴算法拆分。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于稳定性拆分</span></strong></p>
<p style=""></p>
<p style="">在业务架构中，通常会通过核心模块的划分或者主次链路的划分来进行微服务拆分。</p>
<p style=""></p>
<p style="">在《<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485742&amp;idx=1&amp;sn=b3fa976a6bc5ec30c3f4b7b93d60711f&amp;chksm=fafde380cd8a6a968c2b8204ec1e5ebd48625b2ba51714f581d358007d69ba0e2e80223434ba&amp;scene=21#wechat_redirect" target="_blank"><strong><u>三平面分离架构</u></strong></a>》中，我提到过分离出<span fontsize="" color="">控制平面</span>、<span fontsize="" color="">数据平面</span>和管理平面。这本质上也是通过核心模块划分来进行拆分的一种方式。控制平台一般是核心链路，核心数据作为控制逻辑的一部分可以通过本地缓存等措施弱依赖于数据平面。管理平台是后台管理等，用于增删改查，人工操作时才用，其他时间挂掉都没关系。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于资源需求拆分</span></strong></p>
<p style=""></p>
<p style="">根据性能需求来进行拆分。简单来说就是访问量特别大，访问频率特别高的业务，又要保证高效的响应能力，这些业务对性能的要求特别高。比如积分竞拍、低价秒杀、限量抢购。</p>
<p style=""></p>
<p style="">我们要识别出某些超高并发量的业务，尽可能把这部分业务独立拆分出来。这么做的原因非常简单，一个保证满足高性能业务需求，另一个保证业务的独立性，不互相影响。&nbsp;</p>
<p style=""></p>
<p style="">类似积分竞拍、超低价秒杀、限量抢购，对瞬间峰值和计算性能要求是非常高的。这部分的业务如果跟其他通用业务放在一块，一个是可能互相影响，比如某个链路阻塞，会导致雪崩沿调用链向上传递。</p>
<p style=""></p>
<p style="">另外一个是如果多个业务耦合在一块，发布频率变高、服务扩缩容变难、维护复杂度变高。如下图例子所示，订单服务是一个性能要求高的服务，一般会单独拆分。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hgxx.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<h1 style="" id="-1"></h1>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 17px">咱们实际工作中，通常会发现一种拆分方式和另一种拆分方式并不冲突。一个完整的架构也不只使用了一种拆分方式。</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486632&amp;idx=1&amp;sn=7b443147ff6b4d10ab0c09ee6e1e1d73&amp;chksm=fafde606cd8a6f109870d9ad2c47cc42497de5a0f18d84bcdb174d3bf13e49efe8b6bf1a27d2&amp;scene=21#wechat_redirect" target="_blank"><u><span style="font-size: 17px">《</span></u><strong><u><span style="font-size: 17px">领域驱动设计(DDD)中简单易用的10种技巧</span></u></strong><u><span style="font-size: 17px">》</span></u></a><span style="font-size: 17px">也可以配合来使用。</span></p>
<p style=""><span style="font-size: 17px">
  <br>
 </span></p>
<p style=""><span style="font-size: 17px">本文提到的api网关严格上不是业务划分时的一个模块。业界通常把api网关作为一个基础设施。如果在架构图中包含了api网关通常是下图所示：
  <br></span></p>
<p style=""><span style="font-size: 17px"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yxlk.jpg&amp;size=m" width="797px" height="521px" style="display: inline-block"></span></p>
<p style=""><span style="font-size: 17px">
  <br>
 </span></p>
<p style="">上图中的架构既包含了业务逻辑的业务划分，也有配置中心、注册中心这样的技术划分。总体上来说是一个技术架构图。而api网关本身也更多被归为是技术概念，而不是业务概念。</p>]]></description><guid isPermaLink="false">/archives/si-zhong-chang-yong-de-wei-fu-wu-jia-gou-chai-fen-fang-shi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:04:07 GMT</pubDate></item><item><title><![CDATA[服务治理篇-应用架构的演变]]></title><link>https://wiki.dgfygk.com/archives/fu-wu-zhi-li-pian-ying-yong-jia-gou-de-yan-bian</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86%E7%AF%87-%E5%BA%94%E7%94%A8%E6%9E%B6%E6%9E%84%E7%9A%84%E6%BC%94%E5%8F%98&amp;url=/archives/fu-wu-zhi-li-pian-ying-yong-jia-gou-de-yan-bian" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="%E5%BA%94%E7%94%A8%E6%9E%B6%E6%9E%84%E7%9A%84%E6%BC%94%E5%8F%98%E8%AE%B2%E7%9A%84%E6%96%87%E7%AB%A0%E5%BE%88%E5%A4%9A%E4%BA%86%EF%BC%8C%E4%BD%86%E6%98%AF%E6%88%91%E7%9C%8B%E8%BF%99%E4%BA%9B%E6%96%87%E7%AB%A0%EF%BC%8C%E5%8C%85%E6%8B%AC%E6%88%91%E8%87%AA%E5%B7%B1%E4%B9%8B%E5%89%8D%E5%86%99%E7%9A%84%E4%B8%A4%E7%AF%87%E6%96%87%E7%AB%A0%E3%80%8A%E7%BE%8E%E5%9B%A2%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1%E6%A1%86%E6%9E%B6%E5%8F%8A%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86%E7%B3%BB%E7%BB%9Focto%E3%80%8B%E5%92%8C%E3%80%8A%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86%E7%9A%84%E6%8A%80%E6%9C%AF%E8%A1%80%E8%84%89%E3%80%8B%EF%BC%8C%E5%85%B6%E5%AE%9E%E6%B2%A1%E6%9C%89%E6%8A%8A%E6%A6%82%E5%BF%B5%E8%AE%B2%E5%BE%97%E7%89%B9%E5%88%AB%E6%B8%85%E6%A5%9A%E3%80%82%E6%84%9F%E8%A7%89%E4%B9%8D%E4%B8%80%E7%9C%8B%E6%98%AF%E8%BF%99%E4%B9%88%E5%9B%9E%E4%BA%8B%EF%BC%8C%E4%BB%94%E7%BB%86%E4%B8%80%E6%83%B3%E6%BB%A1%E8%84%B8%E9%97%AE%E5%8F%B7%E3%80%82"><span style="font-size: 17px">应用架构的演变讲的文章很多了，但是我看这些文章，包括我自己之前写的两篇文章《</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484193&amp;idx=1&amp;sn=3b799207a163c7bfe2905669d6956191&amp;chksm=fafde98fcd8a609969265c00796ba2374059679a67d0c14443f9fad5c3e751a9ccf6657a71d7&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">美团分布式服务通信框架及服务治理系统OCTO</span></u></strong></a><span style="font-size: 17px">》和《</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485181&amp;idx=1&amp;sn=df4568317a038fcff3b77cd66d0b7bb6&amp;chksm=fafdec53cd8a654573d4210e755fac7c95b5145aa8a5c76cb3df64478a44da0446945ef04c19&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">服务治理的技术血脉</span></u></strong></a><span style="font-size: 17px">》，其实没有把概念讲得特别清楚。感觉乍一看是这么回事，仔细一想满脸问号。</span></h1>
<h1 style="" id=""><span style="font-size: 17px">
  <br>
 </span></h1>
<p style="text-align: center; "><span style="font-size: 17px"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zfkm.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></span></p>
<h1 style="" id="-1"></h1>
<h1 style="" id="-2"><span style="font-size: 17px">
  <br>
 </span></h1>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486103&amp;idx=1&amp;sn=943c5b7f3558df78164321806f273c8f&amp;chksm=fafde039cd8a692f86f0ac97b0e1e05ece075de3e4f33e6a5ec90b269c51aed1963ff3107e0e&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">Dubbo</span></u></strong></a><span style="font-size: 17px">官网上有一个架构演进的介绍。并附有下面这张图。
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kxca.png&amp;size=m" width="732px" height="253px" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span style="font-size: 12px">内容参考地址：
  <br></span></p>
<p style=""><span style="font-size: 12px">https://dubbo.apache.org/zh/docs/v2.7/user/preface/background/</span></p>
<p style=""></p>
<p style=""><span style="font-size: 17px">这段介绍代表了整个业界应用演进的大致方向，但不够全面，侧重于服务治理要解决的问题。本文整体采用</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484931&amp;idx=1&amp;sn=756db99b2fa4ce9c5e9f168d8799d692&amp;chksm=fafdecadcd8a65bb81a0e153e453c2d090c48c73fc7457ccf2b7255f497c441170b651e060e5&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">要素型逻辑结构</span></u></strong></a><span style="font-size: 17px">，从不同方面深入对应用架构演变的认知。</span></p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">单一应用架构</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 17px">当网站流量很小时，只需一个应用，将所有功能都部署在一起，以减少部署节点和成本。此时，用于简化增删改查工作量的数据库访问框架(ORM)是关键。
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uplx.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">我在07年毕业的时候，基本上项目还是采用这种架构。08年我在日本给著名化妆品公司“资生堂”做项目。这个项目成本估算为300人月。光开发人员就几十个。代码全都写到一个应用中。</p>
<p style=""></p>
<p style="">其中我记忆最深的功能是开店和闭店。每天上班要执行开店，就是要启动应用，启动耗时1小时。每天下班要执行闭店，就是关闭应用，关闭耗时1小时还多。这个应用在资生堂所有门店应用之后，门店的工作人员因这个应用要早上班1小时，晚下班1小时。</p>
<p style=""></p>
<p style="">当时我们是和东芝合作的，也算是日本鼎鼎有名的企业了。这堪比蜗牛的运行速度<span fontsize="" color="">放在现在简直不可想象。主要就是因为应用做的事情太多了。</span></p>
<p style="">
 <br>
 <span fontsize="" color="">那时候还没有代码静态检查、安全扫描、执行全量单元测试检查覆盖率这些常规保障措施。要是有，开发小哥哥更哭了。这些检查执行速度和代码量有很大关系。想想看，资生堂那个项目的话，提交代码到检查完成，按照现在一般的检查执行速度，七八个小时吧。可以想象，DevOps毫无用武之地。</span>
</p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">前后端分离架构</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-puxs.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<h1 style="" id="%E4%B8%BA%E4%BA%86%E8%A7%A3%E5%86%B3%E4%B8%8A%E9%9D%A2%E5%9B%BE%E4%B8%AD%E5%9B%9B%E7%82%B9%E9%97%AE%E9%A2%98%EF%BC%8C%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E6%9E%B6%E6%9E%84%E5%BA%94%E8%BF%90%E8%80%8C%E7%94%9F%E3%80%82%E8%BF%99%E7%A7%8D%E6%9E%B6%E6%9E%84%E5%89%8D%E7%AB%AF%E9%A1%B9%E7%9B%AE%E4%B8%8E%E5%90%8E%E7%AB%AF%E9%A1%B9%E7%9B%AE%E6%98%AF%E4%B8%A4%E4%B8%AA%E9%A1%B9%E7%9B%AE%EF%BC%8C%E6%94%BE%E5%9C%A8%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%8C%E9%9C%80%E8%A6%81%E7%8B%AC%E7%AB%8B%E9%83%A8%E7%BD%B2%EF%BC%8C%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E7%9A%84%E5%B7%A5%E7%A8%8B%EF%BC%8C%E4%B8%A4%E4%B8%AA%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BB%A3%E7%A0%81%E5%BA%93%EF%BC%8C%E4%B8%8D%E5%90%8C%E7%9A%84%E5%BC%80%E5%8F%91%E4%BA%BA%E5%91%98%E3%80%82%E5%89%8D%E5%90%8E%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E9%9C%80%E8%A6%81%E7%BA%A6%E5%AE%9A%E4%BA%A4%E4%BA%92%E6%8E%A5%E5%8F%A3%EF%BC%8C%E5%AE%9E%E7%8E%B0%E5%B9%B6%E8%A1%8C%E5%BC%80%E5%8F%91%EF%BC%8C%E5%BC%80%E5%8F%91%E7%BB%93%E6%9D%9F%E5%90%8E%E9%9C%80%E8%A6%81%E8%BF%9B%E8%A1%8C%E7%8B%AC%E7%AB%8B%E9%83%A8%E7%BD%B2%E3%80%82%E5%89%8D%E7%AB%AF%E5%8F%AA%E9%9C%80%E8%A6%81%E5%85%B3%E6%B3%A8%E9%A1%B5%E9%9D%A2%E7%9A%84%E6%A0%B7%E5%BC%8F%E4%B8%8E%E5%8A%A8%E6%80%81%E6%95%B0%E6%8D%AE%E7%9A%84%E8%A7%A3%E6%9E%90%26%E6%B8%B2%E6%9F%93%EF%BC%8C%E8%80%8C%E5%90%8E%E7%AB%AF%E4%B8%93%E6%B3%A8%E4%BA%8E%E5%85%B7%E4%BD%93%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E3%80%82"><span style="font-size: 17px">为了解决上面图中四点问题，前后端分离架构应运而生。这种架构前端项目与后端项目是两个项目，放在两个不同的服务器，需要独立部署，两个不同的工程，两个不同的代码库，不同的开发人员。前后端工程师需要约定交互接口，实现并行开发，开发结束后需要进行独立部署。前端只需要关注页面的样式与动态数据的解析&amp;渲染，而后端专注于具体业务逻辑。</span></h1>
<p style=""></p>
<p style="">这种架构对应对项目复杂性没有质的改善，最大的作用是开发人员不再需要是全栈工程师，人员分工有了初步的分工。在<span fontsize="" color="">扩展和协同上的问题依然严峻。</span></p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">垂直应用架构</span></strong></p>
<p style=""></p>
<p style="">当访问量主键增大，单一应用增加机器带来的加速度越来越小，将应用拆成互不相干的几个应用，以提升效率。此时，用于加速前端页面开发的Web框架(MVC)是关键。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-tous.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="">这里咱们来思考一个问题：什么是垂直领域？</p>
<p style=""></p>
<p style="">09年时我入职人人网。那时人人网刚刚从校内网改名，被炒的很热，招的开发人员背景也都相当的好。那段时间，人人网的技术是经常和百度做比较的。结果却是昙花一现，那么多牛人没能把人人网做得更好。</p>
<p style=""></p>
<p style="">后来很多人出来自己创业，创业时对人人网的失败做自省。很多人就提到要做垂直领域社交，这种普适性的社交缺少个性化优势。</p>
<p style=""></p>
<p style="">所谓垂直领域就是专业领域。它是社会分工精细化的产物。前后端分离也是一种垂直拆分。</p>
<p style=""></p>
<p style="">但是Dubbo官网上介绍应用架构的演变时提到的垂直应用架构并不完全是这个意思。它的垂直拆分更纯粹些，应用之间是完全独立的，很少有交互，就像上面图中的样子。比如微信和QQ。不然也不会说用于加速前端页面开发的Web框架(MVC)是关键。但是在业界说垂直领域、垂直应用都是按专业领域划分没有任何问题。以<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486924&amp;idx=1&amp;sn=99516d226d297fe1a19d9ee252d7672d&amp;chksm=fafde762cd8a6e74f295ce70bed5d4cc4712688d9826bb1e70c251fff1a5757a0633e8244846&amp;scene=21#wechat_redirect" target="_blank"><strong><u>AKF扩展立方体</u></strong></a>里对Y轴的描述为准。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">分布式服务架构</span></strong></p>
<p style=""></p>
<p style="">当垂直应用越来越多，应用之间交互不可避免，将核心业务抽取出来，作为独立的服务，逐渐形成稳定的服务中心，使前端应用能更快速地响应多变的市场需求。此时，用于提高业务复用及整合的分布式服务框架(RPC)是关键。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sryd.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span fontsize="" color="">咱们来思考一个问题：什么是分布式？</span></p>
<p style=""></p>
<p style="">其实分布式是分布式计算的简称。分布式系统就是采用分布式计算的系统。分布式计算是计算机科学中一个研究方向，它研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分，然后把这些部分分配给多个计算机进行处理，最后把这些计算结果综合起来得到最终的结果。</p>
<p style=""></p>
<p style="">白话来说：一个程序或系统，只要运行在不同的机器上，都可以叫做分布式。它是以缩短单个任务的执行时间来提升效率的。因为最终需要将结果综合起来，所以关键点在于通信。在这种架构趋势下，以RPC为核心的服务治理应运而生，Dubbo是其中的典型代表。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">流动计算架构</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 17px; color: rgb(77, 77, 77)">当服务越来越多，容量的评估，小服务资源的浪费等问题逐渐显现，此时需增加一个调度中心基于访问压力实时管理集群容量，提高集群利用率。此时，用于提高机器利用率的资源调度和治理中心(SOA)是关键。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mpah.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">这段来自Dubbo官网的描述，我最初看到的表情是这样的：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fasl.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span fontsize="" color="rgb(77, 77, 77)" style="color: rgb(77, 77, 77)">资源调度和治理中心</span>≈SOA吗？当然不是，SOA是面向服务的意思，Dubbo官网所说的服务专指服务治理平台。服务治理平台负责资源调度和治理中心。它和分布式服务架构的区别是分布式服务架构是应用服务之间彼此通信。而SOA是以服务治理平台为总线，进行统一管理。说白了是SOA的一个应用。</p>
<p style=""></p>
<p style="">这里的流动计算架构按照刚才的分析就是面向服务治理的架构。两者是否能划等号呢？</p>
<p style=""></p>
<p style="">流动计算架构按照英文Elastic Computing直译可叫做弹性计算架构，有动态的意思。简单的分布式计算，两两之间通过RPC调用，从一个节点的角度仅能看到其上下游，有点管中窥豹的意味。如果有一个中心节点，它可以站在更大的视角，就可以对整体的请求做动态分发路由了。这就包含将请求发往处理能力更强的节点，如果监控到整体处理能力不足，可以有动态添加节点或者限流熔断等一系列的能力。就是说面向服务治理的架构实现了流动计算。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">云计算架构
   <br></span></strong></p>
<p style=""></p>
<p style="">按照Dubbo官网的描述方式，我斗胆给出流动计算架构下一代架构的描述：</p>
<p style=""></p>
<p style="">当服务数量攀升、复杂性越来越高，由于通信引发的网络调用、限流、熔断和监控等问题需要让开发者不感知，无侵入性的实现。此时，用于服务间通信的基础设施层<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484324&amp;idx=1&amp;sn=78986320566babe59cc2c0da81c7f5b6&amp;chksm=fafde90acd8a601ceff0bb501a0c1f6cd10b5408d3e4fed43d9e6c8605f800297abead8abdf5&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">服务网格</span></u></strong></a>是关键。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nqrw.png&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">像上面一样，咱们来探讨一下云计算架构和服务网格的关系。</p>
<p style=""></p>
<p style=""><span style="font-size: 17px">云计算（cloud computing）是分布式计算的一种，指的是通过网络“云”将巨大的数据计算处理程序分解成无数个小程序，然后，通过多部服务器组成的系统进行处理和分析这些小程序得到结果并返回给用户。云重点突然一个多字，是服务数量激增量变引起的质变。</span></p>
<p style=""></p>
<p style="">服务网格是云计算架构的解决方案。它是将服务治理平台这种PaaS层下沉到IaaS层成为对用户更加透明的基础设施。经常听说某某大厂在做自下而上的服务网格，本质上是将现在公司使用的服务治理不断下沉，让用户体验更加友好的过程。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">本篇文章用阅读理解的方式叙述了应用架构的演变。本质上除了文章中的论点，如之前的很多文章一样，暗线在讲<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485748&amp;idx=1&amp;sn=0ad1457703464924754a49c139c005e0&amp;chksm=fafde39acd8a6a8cbf2073f40f48484fc02d20de48bf917631235c9f3da64a8d82667f47c5d9&amp;scene=21#wechat_redirect" target="_blank"><strong><u>技术学习方法</u></strong></a>：<span fontsize="" color="">以提出问题为驱动，以解决问题为整合、用输出倒逼输入</span></p>]]></description><guid isPermaLink="false">/archives/fu-wu-zhi-li-pian-ying-yong-jia-gou-de-yan-bian</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 02:02:41 GMT</pubDate></item><item><title><![CDATA[系统架构的11条原则]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E7%9A%8411%E6%9D%A1%E5%8E%9F%E5%88%99&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基本原则</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则一：价值为王</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style="">价值为王的另一种说法叫做YAGNI。YAGNI 是 You aren’t gonna need it 的缩写。该原则的基本含义就是，不应该开发任何当前不使用的功能。因为这些占用开发成本的功能，可能根本没有人用。而且不仅仅是开发成本打了水漂，你还要不断投入维护成本，来保证这些无人使用的功能可以正常运行。</p>
<p style="">要了解阿姆达尔定律，它告诉我们，我们不可能无限制的提升系统某一部分的效率。要提升的总体效果有没有产生相应的价值。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则二：以终为始</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484524&amp;idx=1&amp;sn=471a7ea978dece958c3b0285435551fe&amp;chksm=fafdeec2cd8a67d43af8ef1a346b2f2b012eb738b44d0755e3b6d5a49f2ccede10fae94efe72&amp;scene=21#wechat_redirect" target="_blank"><strong><u>以终为始</u></strong></a>是一种思维模式，最早出自《黄帝内经》，先人是在告诫后人要在人生的春天就认真思考人生终点的意义和价值。其引申义有三：一是凡事要有目标；二是凡事要有计划；三是凡事要有原则。正所谓“凡事预则立，不预则废”。</p>
<p style="">白话来说，以终为始，就是在做事之前，先想想结果是什么样子的，这个结果是否能达到最初的目标。小心X-Y问题：为了解决 X问题，觉得用 Y 可以解，于是研究 Y 问题，结果搞到最后，发现原来要解决的 X 问题。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则三：分治原则</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style="">做架构时不要想着一次性把所有的功能都做好，要拥抱 <a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485060&amp;idx=1&amp;sn=02720887a28154ede413f85fa0af29f6&amp;chksm=fafdec2acd8a653cbbbc8995eb9dd17053288aaa1509a2fc5f74d1ccc7f33d9d307ca76dbe7f&amp;scene=21#wechat_redirect" target="_blank"><strong><u>MVP</u></strong></a>（Minimal Viable Product），最小可运行版本。先让程序完成最基本功能上线，根据反馈调整和决定下一步的迭代。</p>
<p style="">迭代着去做事情，敏捷开发的思路。对于每个功能点，创建里程碑，然后去迭代。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则四：服务自治</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style="">在系统设计时，要考虑服务上线后，对于问题要自感知、自修复、自优化、自运维及自安全。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则五：拥抱变化</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<h1 style="" id="%E9%87%8D%E8%A7%86%E6%9E%B6%E6%9E%84%E6%89%A9%E5%B1%95%E6%80%A7%E5%92%8C%E5%8F%AF%E8%BF%90%E7%BB%B4%E6%80%A7%E3%80%82%E6%97%A0%E7%8A%B6%E6%80%81%E7%9A%84%E7%B3%BB%E7%BB%9F%E7%9A%84%E6%98%AF%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%E5%92%8C%E7%9B%B4%E6%8E%A5%E7%9A%84%E3%80%82%E4%BB%BB%E4%BD%95%E6%97%B6%E5%80%99%E9%83%BD%E8%A6%81%E8%80%83%E8%99%91%E8%BF%99%E4%B8%80%E7%82%B9%EF%BC%8C%E4%B8%8D%E8%A6%81%E6%90%9E%E4%B8%AA%E4%B8%8D%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%EF%BC%8C%E6%9C%89%E7%8A%B6%E6%80%81%E7%9A%84%E4%B8%9C%E4%B8%9C%E5%87%BA%E6%9D%A5%E3%80%82%E5%90%A6%E5%88%99%EF%BC%8C%E4%B8%80%E6%97%A6%E9%9C%80%E8%A6%81%E6%94%B9%E5%8F%98%EF%BC%8C%E6%88%90%E6%9C%AC%E5%BE%88%E9%AB%98%E3%80%82"><span style="font-size: 17px">重视架构扩展性和可运维性。</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484368&amp;idx=1&amp;sn=b3c8d41b98ecf2b976d0b025c0322055&amp;chksm=fafde97ecd8a6068cc4d25f2766261870a67baa4cbf9dc0d4920302fcf09e74ee682025efabc&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">无状态的系统</span></u></strong></a><span style="font-size: 17px">的是可扩展的和直接的。任何时候都要考虑这一点，不要搞个不可扩展的，有状态的东东出来。否则，一旦需要改变，成本很高。</span></h1>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则六：简单即正义</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 17px">解析：</span></p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485195&amp;idx=1&amp;sn=9aa72718cdcc24d872f0a89cf63ea34f&amp;chksm=fafdeda5cd8a64b3a55f4de3fdc48d2ee396074d851f686b70a7e679dbcf26862348a79182eb&amp;scene=21#wechat_redirect" target="_blank"><strong><u>简单即正义</u></strong></a>的另一种说法叫做KISS。KISS(Keep it simple,sutpid)保持每件事情都尽可能的简单。用最简单的解决方案来解决问题。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则七：尽量自动化</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style="">人力成本既慢又贵，还有经常不断的人工失误。如果不能降低人力成本，反而需要更多的人，那么这个架构设计一定是失败的。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">稳定性原则</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则八：依赖最简</span></strong></p>
<p style=""></p>
<p style="">解释：</p>
<p style="">依赖原则是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484076&amp;idx=1&amp;sn=0c2b291a31ed53e9c8af06d131aabdd4&amp;chksm=fafde802cd8a6114430a5c1eb8609481059341089ba2c4113012764578152570b6149780e3d5&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">去除依赖、弱化依赖、控制依赖</span></u></strong></a>。多一个依赖多一分风险。能不依赖则不依赖，能异步弱依赖不要同步强依赖。实在不能弱依赖的，比如必须要调用加密存储来获取数据库的密码，不然无法连接数据库，可以控制获取密码在服务启动时进行，如果获取不到则服务启动失败，因为现在都是集群部署，一台无法启动不影响整体提供服务。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则九：不作不死</span></strong></p>
<p style=""></p>
<p style="">解释：</p>
<p style="">尽可能的做较少的功能。当有疑问的时候，就不要去做，甚至干掉。很多功能从来不会被使用。最多留个扩展点就够了。</p>
<p style="">等到有人提出再说（除非是影响核心流程，否则就等到需要的时候再去做）。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则十：容灾容错</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485188&amp;idx=1&amp;sn=3618568ba3bf9c396195d8e2c4e6723d&amp;chksm=fafdedaacd8a64bca74024452fb88a56ff99aa6bcd9ae841fca1b91cbff40d4dd78698b09dcc&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">Everything fails!</span>&nbsp;</u></strong></a>人都是要死的，机器都是要坏的。<span fontsize="" color="">如果一件事情有可能发生则在生产环境中一定会发生，架构中要做好容错设计。</span></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则十一：用成熟的技术</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484324&amp;idx=1&amp;sn=78986320566babe59cc2c0da81c7f5b6&amp;chksm=fafde90acd8a601ceff0bb501a0c1f6cd10b5408d3e4fed43d9e6c8605f800297abead8abdf5&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">不要给别人的技术当小白鼠</span></u></strong></a>，不要因技术本身的问题影响系统的稳定。尽可能的使用红利大的主流技术，而不要自己发明轮子，更不要魔改。在技术选型上，千万不要被——“你看某个公司也在用这个技术”，或是一些在论坛上看到的一些程序员吐槽技术的观点（没有任何的数据，只有自己的喜好）来决定自己的技术，还是看看主流大多数公司实际在用的技术栈，会更靠谱一些。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">一张图总结今天的内容：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vwao.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:55:45 GMT</pubDate></item><item><title><![CDATA[温故知新-什么是软件工程]]></title><link>https://wiki.dgfygk.com/archives/wen-gu-zhi-xin-shi-me-shi-ruan-jian-gong-cheng</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B8%A9%E6%95%85%E7%9F%A5%E6%96%B0-%E4%BB%80%E4%B9%88%E6%98%AF%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B&amp;url=/archives/wen-gu-zhi-xin-shi-me-shi-ruan-jian-gong-cheng" width="1" height="1" alt="" style="opacity:0;">
<p style="">回忆起大学，记忆最深的就是那些学不明白的专业课了。其实我认为专业自己学不明白的根本原因是：老师也不明白。现在信息比我上大学的时候丰富了许多，该抽点时间弥补一下大学时的缺憾。今天咱们就来聊一聊我大学时没有上过的这个专业的必修课《软件工程导论》。</p>
<p style=""></p>
<p style="">为什么我大学时为什么没有上过这个课呢。我竟然在学习这个课的过程中找到了答案。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">什么是软件</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">软件定义</span></strong></p>
<p style=""></p>
<p style="">1983年IEEE提出了软件的定义：计算机程序、文档、运行程序必需的数据、方法、规则。</p>
<p style=""></p>
<p style="">因为方法和规则在文档中说明，在程序中实现。所以软件定义可以被简化为：</p>
<p style="text-align: center; ">软件=程序+文档+数据</p>
<p style="text-align: center; "></p>
<p style="">这个定义告诉咱们：程序只是软件的一部分。平时工作工作中也需要把文档和数据重视起来，做好检查。设计图、架构图、接口文档甚至是产品文档都是软件工程师需要参与的部分。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">软件分类</span></strong></p>
<p style=""></p>
<p style="">软件分类有很多不同的划分方法。今天来看两种：基于软件功能的划分和基于软件规模的划分。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">基于软件功能的划分</span></strong></p>
<p style=""></p>
<ul>
 <li>
  <p style="">系统软件</p>
  <p style="">与计算机硬件紧密配合使计算机更好工作的软件。比如：操作系统、编译程序、杀毒软件。</p>
 </li>
</ul>
<p style=""></p>
<ul>
 <li>
  <p style="">支撑软件</p>
  <p style="">协助软件开发的工具性软件。比如：intelij、画图工具。</p>
  <p style=""></p>
 </li>
 <li>
  <p style="">应用软件</p>
  <p style="">这个细分种类最多。咱们读者中大多数也都是从事这个开发的。又包含：商业处理软件、工程与科学计算软件、智能产品嵌入软件、人工智能软件、个人计算机软件五类。</p>
  <p style="">对于应用软件我不举例子，因为大家接触比较多，留给大家自己思考。我还留了一个思考题：</p>
  <p style=""></p>
  <p style=""></p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">基于软件规模的划分</span></strong></p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jwry.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">这是根据参加人数、开发周期和代码行数来划分的。实际工作中，微型规模的我常见的只有脚本类了。按照项目制的话，中型、大型较为常见。</p>
<p style=""></p>
<p style="">微软有两个曾经家喻户晓的产品，一个是电子邮件系统exchange2000，一个是操作系统windows2000。今天大家来感受一下其成本上的差异：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hbbx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">windows2000光项目经理就有250人，不愧是你。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">什么是软件工程</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">软件工程产生的背景</span></strong></p>
<p style=""></p>
<p style="">软件工程的产生也是问题驱动的产物，所以就不得不说软件发展过程和软件危机。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yoyu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">软件发展经历了4个阶段。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">阶段一</span></strong></p>
<p style=""></p>
<p style="">在第一个阶段程序设计中，最重要的特性是软件生产的个体化。就是说软件的生产者和使用者是同一个人。因为是软件设计者自己来使用，没有沟通成本，所以没有文档，只有程序设计。计算机之父冯诺依曼发明计算机当时的用途也是为自己减少人工计算。</p>
<p style=""></p>
<p style="">提到冯诺依曼，咱们顺便理一理和计算机发明有关的4个人物与计算机的关系。他们分别是：巴贝奇、艾达、图灵和冯诺依曼。</p>
<p style=""></p>
<p style="">巴贝奇，在中国很少听到，但在国外却是公认的计算机之父。准确的说是通用计算机之父。1823年，英国人巴贝奇，得到政府的支持，设计一台容量为20位数的计算机。这台机器与冯诺依曼计算机的最大区别是他是机械物理原理，不用电。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dmzh.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">
 <br>
 之后，一个小他20岁的伯爵夫人找到了他，提出要做他的合伙人。这个人就是被称为历史上第一个程序员的女神艾达。艾达是诗人拜伦的女儿。没错，就是那个写《唐璜》的英国浪漫主义诗人拜伦。艾达没有继承父亲的文学事业却继承了数学家母亲的衣钵。艾达为计算机设计了30多种功能和算法，在一个世纪之后，这被称之为“程序”。可惜的是这个史上第一位程序员由于受到机械时代的限制，所有的设计没有实现，本人其实一行代码都没有写过。
</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dxdj.webp&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">图灵被称为计算机科学之父，人工智能之父。他是计算机理论的设计者，并没有存留下来的计算机实体。其理论思想到现在仍不过时。可惜的是其本人由于同性取向的问题受尽迫害，最终自杀。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rclt.jpg&amp;size=m" width="200px" height="200px" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486330&amp;idx=1&amp;sn=e85e201059c1c5575481e03531b06212&amp;chksm=fafde1d4cd8a68c235ef3cbed83bf0b6ac3a80052c0a492d8c2a217006cf4fa530f2f74f01c3&amp;scene=21#wechat_redirect" target="_blank">《白话linux操作系统原理》</a>中，我讲过冯诺依曼体系结构。事实上，冯诺依曼体系结构是站在巨人肩膀上的产物：体系结构第一个要点<span fontsize="" color="">计算机的数制采用二进制，这个最初是艾达提出，并由巴贝奇采纳作为分析机的设计基础。冯诺依曼设计了第一台电子计算机。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kcmc.jpg&amp;size=m" width="25%" height="auto" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span fontsize="" color="">阶段二</span></strong></p>
<p style=""></p>
<p style="">在第二个阶段程序系统阶段中，出现了开发定制化软件产品的团队或者公司，由于那个阶段团体规模较小，主要以程序开发为主，没有技术上实际的提升，这些团队被叫做软件作坊。</p>
<p style=""></p>
<p style="">因为我毕业的时候，这种软件公司还是主流。所以也理解了大学时专业课的安排：都是以程序设计为主。还不到软件工程的层次。</p>
<p style=""></p>
<p style="">回过头来说软件作坊，软件系统的规模越来越大，复杂程度越来越高，软件可靠性问题也越来越突出。原来的个人设计、个人使用的方式不再能满足要求，迫切需要改变软件生产方式，提高软件生产率，软件危机开始爆发 。</p>
<p style=""></p>
<p style="">软件危机就是泛指由于计算机软件的开发和维护过程中所遇到的一系列严重问题。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">阶段三</span></strong></p>
<p style=""></p>
<p style="">1968年，北大西洋公约组织在德国召开会议，提出了软件工程的概念，希望用工程化的原则和方法来克服软件危机。这门学科产生直接让软件进入软件工程阶段。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">阶段四</span></strong></p>
<p style=""></p>
<p style="">阶段四是个蓬勃发展的时期，就像人们学会了炼铁之后，铸造业的蓬勃发展一样。我刚毕业的时候，找工作经常问到的几个概念就出自这个阶段。下面介绍这个阶段出现的几个常用模式。</p>
<p style=""></p>
<p style="">C/S模式</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zyar.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">客户端/服务器二层架构客户端负担较重，被称为胖客户端。</p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lloj.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""><span fontsize="" color="">客户端人机交互程序/应用服务器</span>/数据库服务器三层架构，也是现在都比较常见的架构，被称为瘦客户端。比如咱们用的手机APP。</p>
<p style=""></p>
<p style="">B/S模式</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rbmk.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style="">浏览器/服务器模式也分三层，实现了客户端不需要安装软件。这个大家就已经熟悉到了不用举例了。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">软件工程的定义</span></strong></p>
<p style=""></p>
<p style="">软件工程是在阶段二为了应对软件危机而生的。</p>
<p style=""></p>
<p style="">1993年，IEEE给出了软件工程的定义：把系统化、规范化、可度量的途径用于软件开发、运行和维护过程中，研究其实现途径。</p>
<p style=""></p>
<p style="">咱们知道IEEE的定义一向不好记忆和理解。所以下面从研究内容上给出软件工程的内容范围：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ybkm.png&amp;size=m" width="50%" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">从软件到软件工程的发展过程，我们了解到软件工程是为了解决软件发展过程中遇到的问题而生，主要分为工程技术和工程管理。而研究生的很多专业就是对软件工程领域的细分。</p>]]></description><guid isPermaLink="false">/archives/wen-gu-zhi-xin-shi-me-shi-ruan-jian-gong-cheng</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:51:43 GMT</pubDate></item><item><title><![CDATA[白话linux操作系统原理]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%99%BD%E8%AF%9Dlinux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%8E%9F%E7%90%86&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style="">虽然计算机相关专业，操作系统和计算机组成原理是必修课。但是大学时和真正从事相关专业工作之后，对于知识的认知自然会发生变化。还很有可能，一辈子呆在学校的老师们只是照本宣科，自己的理解也不深。所以今天我站在真正排查解决问题时的需要层面，用白话说一说linux操作系统的那些知识。</p>
<p style=""></p>
<p style="">本文整体采用的是类似递归调用的递进式结构。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">linux内核的本质</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-evfh.png&amp;size=m" width="608px" height="762px" style="display: inline-block"></p>
<p style="">简单来说如上图所示，咱们平时的应用程序或者linux命令要和操作系统打交道，都要经过一个叫做linux内核的软件。所有的硬件操作都需要通过他。就像古代女子出嫁，都要有媒人。</p>
<p style=""></p>
<p style="">虽然现在都是婚姻自由，没有父母之命媒妁之言那一套。其实在古代，媒人对于整个古代历史上的婚姻而言，起到的正向的作用。现在社会欺骗的婚姻也不少。而古代媒人都要是对两家熟悉，或者做过背调，掌握了情况的人。多数都是门当户对，除了个别婚前就心有所属的，大多数还是过得很幸福的。毕竟多数人的标准都差不多：好看就行。我觉得自己老公长得就挺好看，只是出去打听了一下，95%的人不这么认为<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F2tk5ianItRlicaj3NoVtl5MyZZIr39JD2AvMD6TgATfCrR9qDnxaeU7RtoKpbwUnaEia52TJziaqgKlCSVBIzD7Plg%2F640%3Fwx_fmt%3Dpng%26tp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="20px" height="auto" style="display: inline-block"></p>
<p style=""></p>
<p style="">本文不是为封装制度正名。想表达的是：媒人最初是一种保护机制。而linux之父<span style="font-size: 16px">林纳斯最初设计linux</span>内核也是设计成一种保护机制。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mmhw.jpg&amp;size=m" width="506px" height="471px" style="display: inline-block"></p>
<p style=""></p>
<p style="">为什么说linux内核是一种保护机制呢，这要从冯·诺依曼体系结构说起。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">冯·诺依曼体系结构</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xpdb.png&amp;size=m" width="604px" height="364px" style="display: inline-block"></p>
<p style="">冯·诺依曼体系结构的要点是：</p>
<p style="">
 <br>
 计算机的数制采用二进制。计算机应该按照程序顺序执行。它采用存储程序方式，指令和数据不加区别，混合存储在同一个存储器中。数据和程序在内存中是没有区别的，它们都是内存中的数据。当EIP指针指向哪，CPU就加载哪段内存中的数据。如果是不正确的指令格式，CPU就会发生错误中断。
</p>
<p style=""></p>
<p style="">这里提到计算机是在顺序的执行指令，但是咱们明明可以一边听歌一边敲代码。这个除了在目前多CPU架构下可以实现，之前单CPU下也可以实现。因为有时钟分片。如果没有插入优先级高的任务，cpu会在均匀的执行多项任务分片。因为CPU执行速度快，人可能完全感觉不到实际上是断续执行的。</p>
<p style=""></p>
<p style="">不过我记得上大学的时候，03年我买了一台笔记本用来打魔兽。当时可是用的市面上的顶配呢。有时候还是会一卡一卡的。可以切身感受到时钟分片。</p>
<p style=""></p>
<p style="">如果出现错误，可以通过中断来处理。中断也需要等待时钟分片。好在linux内核的分片十分合理，让中断可以及时响应。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ponr.png&amp;size=m" width="546px" height="761px" style="display: inline-block"></p>
<p style=""></p>
<p style="">在现代CPU的保护模式中，每个内存段都有其描述符。这个描述符记录着这个内存段的访问权限。在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a>里我就提到过文件描述符。这里稍详细的解释下。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">文件描述符</span></strong></p>
<p style=""></p>
<p style="">linux系统中，一切皆文件。文件描述符是一个索引值，指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时，内核向进程返回一个文件描述符。在程序设计中，一些涉及底层的程序编写往往会围绕着文件描述符展开。</p>
<p style=""></p>
<h3 style="" id="%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E3%80%81%E6%96%87%E4%BB%B6%E3%80%81%E8%BF%9B%E7%A8%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB"><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">文件描述符、文件、进程间的关系</span></strong></h3>
<p style=""></p>
<h4 style="" id="1.%E6%8F%8F%E8%BF%B0%EF%BC%9A">1.描述：</h4>
<ul>
 <li>
  <p style="">每个文件描述符会与一个打开的文件相对应</p>
 </li>
 <li>
  <p style="">不同的文件描述符也可能指向同一个文件</p>
 </li>
 <li>
  <p style="">相同的文件可以被不同的进程打开，也可以在同一个进程被多次打开</p>
  <p style=""></p>
 </li>
</ul>
<h4 style="" id="2.%E7%B3%BB%E7%BB%9F%E4%B8%BA%E7%BB%B4%E6%8A%A4%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%EF%BC%8C%E5%BB%BA%E7%AB%8B%E4%BA%86%E4%B8%89%E4%B8%AA%E8%A1%A8">2.系统为维护文件描述符，建立了三个表</h4>
<ul>
 <li>
  <p style="">进程级的文件描述符表</p>
 </li>
 <li>
  <p style="">系统级的文件描述符表</p>
 </li>
 <li>
  <p style="">文件系统的i-node表</p>
 </li>
</ul>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sluf.png&amp;size=m" width="789px" height="398px" style="display: inline-block"></p>
<p style=""></p>
<h4 style="" id="3.%E9%80%9A%E8%BF%87%E8%BF%99%E4%B8%89%E4%B8%AA%E8%A1%A8%EF%BC%8C%E8%AE%A4%E8%AF%86%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6"><span style="font-size: 17px">3.通过这三个表，认识文件描述符</span></h4>
<p style=""><span style="font-size: 17px"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-oinz.png&amp;size=m" width="812px" height="516px" style="display: inline-block"></span></p>
<ul>
 <li>
  <p style="">在进程A中，文件描述符1和30都指向了同一个打开的文件句柄（#23），这可能是该进程多次对执行打开操作</p>
 </li>
 <li>
  <p style="">进程A中的文件描述符2和进程B的文件描述符2都指向了同一个打开的文件句柄（#73），这种情况有几种可能，1.进程A和进程B可能是父子进程关系;2.进程A和进程B打开了同一个文件，且文件描述符相同（低概率事件=_=）；3.A、B中某个进程通过UNIX域套接字将一个打开的文件描述符传递给另一个进程。</p>
 </li>
 <li>
  <p style="">进程A的描述符0和进程B的描述符3分别指向不同的打开文件句柄，但这些句柄均指向i-node表的相同条目（#1936），换言之，指向同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了打开请求。同一个进程两次打开同一个文件，也会发生类似情况。</p>
 </li>
</ul>
<h3 style="" id="%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E9%99%90%E5%88%B6"><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">文件描述符限制</span></strong></h3>
<p style="">　　</p>
<p style="">有资源的地方就有战争，文件描述符也是一种资源，系统中的每个进程都需要有文件描述符才能进行改变世界的宏图霸业。世界需要秩序，于是就有了“文件描述符限制”的规定。</p>
<h4 style="" id="%E5%A6%82%E4%B8%8B%E8%A1%A8%EF%BC%9A">如下表：</h4>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kbjv.png&amp;size=m" width="892px" height="396px" style="display: inline-block"></p>
<p style=""></p>
<p style="">查看文件描述符限制也可以使用linux命令。</p>
<ul>
 <li>
  <p style="">找到需要检查的进程id</p>
 </li>
 <li>
  <p style="">查看该进程的资源，比如fd 是描述符，limits是限制。</p>
 </li>
 <li>
  <p style="">查看该进程的限制，如图，在 Max open files 那一行，可以看到当前设置中soft最大文件描述符的数量为1024。</p>
 </li>
</ul>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xclf.png&amp;size=m" width="632px" height="585px" style="display: inline-block"></p>
<p style=""></p>
<p style="">在《提供一个排查性能问题的思路》里，我就提到过解决过too many open files问题。咱们今天来实际理解一下。执行下面命令：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vjlm.png&amp;size=m" width="815px" height="280px" style="display: inline-block"></p>
<p style="">这就是一个进程实际占用的文件描述符和文件描述符数。超过系统设定值就会发生too many open files异常。这里大家应该可以切实理解一个文件描述符就是一个文件，文件描述符占用超限就是too many open files啦。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">特殊的文件描述符</span></strong></p>
<p style=""></p>
<p style="">有三个特殊的文件描述符，分别是0、1、2，对应每个进程的标准输入、标准输出和错误输出。每个进程启动时，操作系统就会给它分配这三个标准的文件描述符。咱们平时用的console控制台就是通过读写这三个文件来实现滴。</p>
<p style=""></p>
<p style="">linux基础里有介绍怎么重定向，下面一条命令可以将标准输出1和错误输出2重定向到一个文件：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pjun.png&amp;size=m" width="759px" height="211px" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">linux的内存分段、中断机制和文件描述符限制都是内核的保护机制。当然这并不全面，还有其他机制。</p>
<p style=""></p>
<p style="">本篇内容<span fontsize="" color="">和</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486245&amp;idx=1&amp;sn=494e71a356ad4bd26633b5ae5278215f&amp;chksm=fafde18bcd8a689de3cfa45c0bbe629bc098d26a85106caff62b0bfa069055374515865a3b26&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="">《网络通信之Session的历史血脉》</span></a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486186&amp;idx=1&amp;sn=f21e17b21beb4c99cef0e076071ac859&amp;chksm=fafde044cd8a69526aa3ec9edbe71ea5aa41de2ef236494201dc2f20701ed8abc6be1a72344f&amp;scene=21#wechat_redirect" target="_blank">《深入理解MQ生产端的底层通信过程-理解channel》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486210&amp;idx=1&amp;sn=fd7a60ca9b4e41f942dd0b2653228774&amp;chksm=fafde1accd8a68ba4f41f19d3543984f47756e884eb395b295cc7f4f1cd0575bc0305fba5431&amp;scene=21#wechat_redirect" target="_blank">《网络字节序列-大端序和小端序》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486242&amp;idx=1&amp;sn=483566d3fd97f333d3ea114dbbe8f7d1&amp;chksm=fafde18ccd8a689abfcb9959079a3cbfc4ecf98e52d34318bf9490422622bddb0c4b81ef2288&amp;scene=21#wechat_redirect" target="_blank">《https引起的跨域问题-COE&amp;casestudy》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486103&amp;idx=1&amp;sn=943c5b7f3558df78164321806f273c8f&amp;chksm=fafde039cd8a692f86f0ac97b0e1e05ece075de3e4f33e6a5ec90b269c51aed1963ff3107e0e&amp;scene=21#wechat_redirect" target="_blank">《懂得三境界-使用dubbo时请求超过问题》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485988&amp;idx=1&amp;sn=efad5a030bc0a933ac5af2ee09f3c6c4&amp;chksm=fafde08acd8a699c198d0d65c413273f05f2796cfd873160c2ba81ed3f33aebfaccb62234cb7&amp;scene=21#wechat_redirect" target="_blank">《一个http请求进来都经过了什么(2021版)》</a><span fontsize="" color="">是一个系列。等全部串联起来，之前很多让人望而却步的生产问题，会有了大致的排查方向吧~</span></p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>Linux</category><pubDate>Mon, 23 Sep 2024 01:44:54 GMT</pubDate></item><item><title><![CDATA[白话TCP/IP原理]]></title><link>https://wiki.dgfygk.com/archives/bai-hua-tcp-ipyuan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%99%BD%E8%AF%9DTCP%2FIP%E5%8E%9F%E7%90%86&amp;url=/archives/bai-hua-tcp-ipyuan-li" width="1" height="1" alt="" style="opacity:0;">
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">TCP/IP(Transmission-Control-Protocol/Internet-Protocol)，中文译名为传输控制协议/因特网互联协议，又名网络通讯协议，是Internet最基本的协议、也是Internet国际互联网络的基础。</span></p>
<p style=""></p>
<p style="text-align: start; "><span fontsize="" color="rgb(77, 77, 77)" style="color: rgb(77, 77, 77)">我上大学的时候就是一门必修课。工作后我还专门重新看了一遍，觉得比上学时理解的多了些。但是书本上东西毕竟不贴合工作。本文结合工作中常用的方面以及现实中出现过的线上问题来讲解说明。</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">TCP/IP协议栈为什么是“栈”？</span></strong></p>
<p style=""></p>
<p style="">大学的时候课本上讲过：开放系统互联参考模型OSI/RM<span fontsize="" color="">分7层，从低到高分别是物理层，数据链路层，网络层，传输层，会话层，表示层和应用层。</span></p>
<p style=""></p>
<p style="">先从7层模型入手，是因为下面讲的TCP/IP四层模型里缺少咱们平时工作中常用的RPC远程过程调用协议，它工作在会话层，基于下面图中的socket来实现。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zulu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">实际上互联网通信使用的是TCP/IP协议栈。这里举例来说明为什么叫“栈”。</p>
<p style=""></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">栈是一种先进后出的数据结构</span>。<span style="font-size: 16px; color: rgb(77, 77, 77)">拿一个HTTP报文来说</span>，<span style="font-size: 16px; color: rgb(77, 77, 77)">HTTP报文属于应用层协议的报文，我们输入网址，首先会调用到DNS协议（域名协议</span>）。<span style="font-size: 16px; color: rgb(77, 77, 77)">HTTP报文在传输层用的是TCP协议，我们把TCP压入栈中，再将IP层也压入栈中。链路层的话，就用最常见的以太网。</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">好了，现在我们的栈里面从头至尾依次是：</span></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">HTTP协议-TCP协议-IP协议-以太帧头</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">然后我们先忽略最底层的物理层，假设这个封装好的栈一样的报文漂洋过海，来到了它的目的地。对端收到这个报文以后，也就是我们封装好的这个栈一样的东西以后该怎么办呢？会不会也是先拿HTTP呢？因为这个报文是我们构造的一个栈，所以说它的顺序肯定也是栈。</span></p>
<p style=""></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">因此拿取的顺序就是：</span></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">以太帧头-IP协议-TCP协议-HTTP协议</span></p>
<p style=""></p>
<p style=""><span fontsize="" color="">在</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</span></a><span fontsize="" color="">中我讲过：传输层操作是在内核空间完成的，就是说不是靠咱们平时的应用编码可以直接介入的。咱们平时直接用的就是应用层协议。想通过应用层操作传输层怎么办呢？这就用到了socket编程。</span>因为HTTP协议的内容被封装进入了socket。这里称为套接字层。总体示意图如下：
 <br></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qfzh.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">发现没，最先被封装入的HTTP报文(套接字层)是最后才被拿走的。</span></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">TCP三次握手和四次挥手</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">三次握手</span></strong></p>
<p style=""></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486103&amp;idx=1&amp;sn=943c5b7f3558df78164321806f273c8f&amp;chksm=fafde039cd8a692f86f0ac97b0e1e05ece075de3e4f33e6a5ec90b269c51aed1963ff3107e0e&amp;scene=21#wechat_redirect" target="_blank">《懂得三境界-使用dubbo时请求超过问题》</a>这篇文章中，我讲过三次握手。这里用打比方的方式再讲一遍：</p>
<p style=""></p>
<p style="">我在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483841&amp;idx=1&amp;sn=c236d6829a2cce8ad56236bf75085bbf&amp;chksm=fafdeb6fcd8a62794646fd34a37c0532c92bba41d7664badc4db18162b141d0c8285e5d29a52&amp;scene=21#wechat_redirect" target="_blank">《两地书--K8s基础知识》</a>里讲过司马相如和卓文君的故事。其实在巜史记》和《汉书》里有记载说：他们的爱情是一场精心策划的阴谋。</p>
<p style=""></p>
<p style="">司马相如从一开始就打算找个富婆。所以和当地县令窜通，县令经常登门拜访司马相如，司马相如却避而不见。但县令还是天天来。很有三顾茅庐的意思。当地都在传这个人得县令如此垂青一定不简单。</p>
<p style=""></p>
<p style="">卓文君的爹是当地首富，听说之后就请司马相如去坐客。司马相如在宴会上的目标是和卓文君建立通信联系。他精心计算过：卓文君夫婿刚刚去世，被接回娘家。不像未出阁的姑娘那么挑剔。自己又仪表堂堂，文采不凡。应该搞得定。</p>
<p style=""></p>
<p style="">但是想傍富婆的何只他一人？他精心准备了一曲《凤求凰》，向卓文君发出第一次建立通信的请求。</p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ecoz.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">第一次握手，客户端司马相如发出请求。服务端卓文君只有一个进程专门处理所有的连接请求，处理能力有限。所以这个才子、那个才子的建立联系请求会先放入队列，服务端会按照顺序来处理申请。</p>
<p style=""></p>
<p style="">服务端卓文君收到客户端司马相如的申请，会验证收到的信息是否有误(实际TCP中<span fontsize="" color="rgb(77, 77, 77)" style="color: rgb(77, 77, 77)">检验的是通信数据是否有错误</span>)。无误则对这个客户端的状态变成listen状态，并向司马相如发出一个回执：“你的琴弹的不错，可以建立联系。”这就是第二次握手。</p>
<p style=""></p>
<p style="">客户端司马相如收到服务端卓文君的回执，认为自己的目的已达到，把自己标记为established状态，并进行第三次握手：“好的，仰慕已久。”这时候客户端已经一厢情愿的认为自己可以自由与服务端通信了。</p>
<p style=""></p>
<p style="">但是服务端卓文君那边呢，目前还处在与客户端司马相如建立连接的过程中，请求在全连接队列中排队呢。毕竟要应付的才子多啊。客户端司马相如的回执还在排队，卓文君还没处理呢。所以这时候，客户端司马相如如果发了一封书信(传输的数据)。卓文君没还没跟自己丫鬟说跟客户端司马相如已经建立联系了呢。所以书信根本到不了卓文君手上。这封信发送就失败了。</p>
<p style=""></p>
<p style="">等服务端卓文君处理到客户端司马相如的请求，真正建立连接。司马相如的书信才真正到达卓文君手上。达到了司马相如的第一步。</p>
<p style=""></p>
<p style="">后来司马相如让卓文君和他私奔。他们回到司马相如的家乡，卓文君才发现司马相如家一贫如洗。卓文君问娘家要钱，娘家不给。司马相如就带着卓文君回到了卓文君家乡，并让卓文君抛头露面当庐卖酒。为的就是套路文君爸，让他爸觉得脸上无光，主动给钱。</p>
<p style=""></p>
<p style="">司马相如计划得逞，文君爸给了他们万贯家财，让他们回司马相如老家过日子。但是钱大手大脚花，也很快花完了。司马相如就进京找钱去了。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">四次挥手</span></strong></p>
<p style=""></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ladv.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">这次司马相如果然得到了汉武帝赏识。司马相如发达了就想休妻再娶。</span><span fontsize="" color="">终于某日，客户端司马相如给服务端卓文君送出了一封十三字的分手信：“一二三四五六七八九十百千万。”&nbsp;无亿，就是说我</span>就你已经无意啦。这就是第一次挥手。实际上第一次挥手会告诉服务端如果你还有数据没有发送完成，则不必急着关闭Socket，可以继续发送数据。</p>
<p style=""></p>
<p style="">服务端卓文君收到这个消息，非常震惊。她还没有准备好。于是，先回复了一个消息让客户端司马相如等一等。这就是第二次挥手。(这个时候客户端就进入FIN_WAIT状态，继续等待服务端的FIN报文。)</p>
<p style=""></p>
<p style="">在这时候，他们还是夫妻关系。服务端卓文君可以继续给客户端司马相如发送消息。服务端卓文君先回复了一首《怨郎诗》：</p>
<blockquote>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">一</span></strong>别之后，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">二</span></strong>地相思，</p>
 <p style="">只道是<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">三四</span></strong>月，</p>
 <p style="">又谁知<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">五六</span></strong>年。</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">七</span></strong>弦琴无心弹，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">八</span></strong>行书无可传，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">九</span></strong>曲连环从中折断，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">十</span></strong>里长亭望眼欲穿。</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">百</span></strong>思想，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">千</span></strong>系念，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">万</span></strong>般无奈把君怨。</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">万</span></strong>语千言说不完，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">百</span></strong>无聊赖<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">十</span></strong>倚栏。</p>
 <p style="">重<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">九</span></strong>登高看孤雁，</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">八</span></strong>月中秋月圆人不圆。</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">七</span></strong>月半，秉烛烧香问苍天。</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">六</span></strong>月伏天人人摇扇我心寒。</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">五</span></strong>月石榴似火红，偏遭阵阵冷雨浇花端。</p>
 <p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">四</span></strong>月枇杷未黄，我欲对镜心意乱。</p>
 <p style="">急匆匆，<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">三</span></strong>月桃花随水转；</p>
 <p style="">飘零零，<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">二</span></strong>月风筝线儿断。</p>
 <p style="">噫，郎呀郎，恨不得下<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">一</span></strong>世，你为女来我做男。</p>
</blockquote>
<p style="">等服务端卓文君把自己想说的话说完了，就给客户端司马相如回复说：“闻君有两意，故来相决绝。”(出自<span style="font-size: 16px; color: rgb(77, 77, 77)">《白头吟》</span>)意思是我准备好了，既然你变心啦，咱们就算了吧。这就是第三次挥手。</p>
<p style=""></p>
<p style="">这时候，如果客户端司马相如是一般人，得偿所愿了。就会回复一个：“祝你幸福。”这是韩剧里标准的分手时最后话术，是通知服务端彻底断绝关系的。然后再等等确认服务端彻底没有反馈就是分手成功了。这就是第四次挥手。(实际上客户端收到FIN报文后，就知道可以关闭连接了，但是它还是不相信网络，怕服务端不知道要关闭，所以发送ACK后进入TIME_WAIT状态，如果服务端没有收到ACK则可以重传。)</p>
<p style=""></p>
<p style="">但是司马相如是个艺人啊。艺人最怕脱粉啊。想想司马相如挣钱靠的主要是《长门赋》这样用来挽回感情的怨妇诗。自己这般铁石心肠，那诗还有谁会买账呢！还会得罪多少达官贵人。何况卓文君的这两篇《怨郎诗》和《白头吟》文采如此出众！聪明如司马相如马上就知道如果不继续维持这段才子佳人的佳话，那事业也没了。所以他选择了重新三次握手建立连接。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">TCP长连接和短连接</span></strong></p>
<p style=""></p>
<p style="">TCP连接有三次握手和四次挥手的加持，被称为可靠的连接。UDP这样的数据报被称为不可靠的连接。可靠就有代价。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-macj.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">长连接和短连接的本质区别如上图。短连接每次通信都伴随着三次握手和四次挥手，而长连接复用了这两个过程。为了让收益最大化成本最小化，不同的应用场景会选择采用长连接或者短连接。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px">长连接和短连接各自的优缺点</span></strong></p>
<p style=""></p>
<ul>
 <li>
  <p style="">长连接可以省去较多的TCP建立和关闭的操作，减少浪费，节约时间，但是一直连接对于客户端来说比较耗电。</p>
 </li>
 <li>
  <p style="">对于频繁请求资源的客户端来说，较适用长连接。</p>
 </li>
 <li>
  <p style="">客户端与服务端之间的连接如果一直不关闭的话，会存在一个问题：随着客户端连接越来越多，服务端早晚有扛不住的时候，这时候服务端需要采取一些策略，如关闭一些长时间没有读写事件发生的连接，这样可以避免一些恶意连接导致服务端服务受损。</p>
 </li>
 <li>
  <p style="">如果条件再允许就可以以客户端机器为颗粒度，限制每个客户端的最大长连接数，这样可以完全避免某些客户端出问题后连累服务端。</p>
 </li>
 <li>
  <p style="">短连接对于服务器来说管理较为简单，存在的连接都是有用的连接，不需要额外的控制手段。</p>
 </li>
 <li>
  <p style="">一次TCP连接和断开需要7个来回，如果客户端请求频繁，将在TCP的建立和关闭操作上浪费大量时间和带宽。</p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">短连接应用场景</span></strong></p>
<p style=""></p>
<p style="">一般网站类的web服务都是短连接。试想一个普通网站，比如查看我这篇公众号文章，总共有6000多个字。一旦打开，内容加载完之后，很长时间不用再次传输数据。那占着连接是不是很浪费？</p>
<p style=""></p>
<p style="">而且服务端可承载的最大连接数是有限的，不然文件句柄不够用啊。一个网站希望用几十、几百台4核8G就可以支撑日活几百万，那最好使用短连接。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">长连接应用场景</span></strong></p>
<p style=""></p>
<p style="">公司内的各个系统之间使用RPC。大家使用的工具不太相同，有的公司自己基于thrift协议进行开发，有的使用开源的Dubbo。但是大家都头脑清醒的使用了长连接。</p>
<p style=""></p>
<p style="">因为内部场景下，上下游是固定的，接入的客户端数量也相对固定。长连接节省连接建立开销，请求量上来了也可以直接进行数据传输相对高效。</p>
<p style=""></p>
<p style="">公司内部使用的中间件也大多使用长连接。例如：MQ、k8s、Redis、mysql。提到这些不得不提相关的两个技术。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">keepalive保活机制</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: mediumpx; color: rgb(100, 100, 100)">KeepAlive并不是TCP协议规范的一部分，但在几乎所有的TCP/IP协议栈（不管是Linux还是Windows）中，都实现了KeepAlive功能。</span></p>
<p style=""></p>
<p style="">先看看使用场景：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-opwk.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">我没看过这个电影，也不知道是哪个电影里的场景。只是知道，张艺谋在一个姑娘楼下不断的叫喊：“安红我想你”&nbsp;这个镜头有段时间特别火。</p>
<p style=""></p>
<p style="">客户端张艺谋发送了消息。服务端收到消息后一看，瞧给你牛的，然后没理客户端，傻狗客户端一直在等待，但是不知道是不是服务器挂掉了？</p>
<p style=""></p>
<p style=""><span style="font-size: mediumpx; color: rgb(18, 18, 18)">这时候TCP协议提出一个办法，当客户端端等待超过一定时间后自动给服务端发送一个空的报文，如果对方回复了这个报文证明连接还存活着，如果对方没有报文返回且进行了多次尝试都是一样，那么就认为连接已经丢失，客户端就没必要继续保持连接了。如果没有这种机制就会有很多空闲的连接占用着系统资源。原理如下图：</span></p>
<p style=""><span style="font-size: mediumpx; color: rgb(18, 18, 18)"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ihfc.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<h2 style="" id=""></h2>
<h2 style="" id="%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E5%AE%83%3F"><strong><span style="font-size: 18px">如何设置它?</span></strong></h2>
<h2 style="" id="-1"></h2>
<h2 style="" id="%E5%9C%A8%E8%AE%BE%E7%BD%AE%E4%B9%8B%E5%89%8D%E6%88%91%E4%BB%AC%E5%85%88%E6%9D%A5%E7%9C%8B%E7%9C%8Bkeepalive%E9%83%BD%E6%94%AF%E6%8C%81%E5%93%AA%E4%BA%9B%E8%AE%BE%E7%BD%AE%E9%A1%B9">在设置之前我们先来看看KeepAlive都支持哪些设置项</h2>
<ul>
 <li>
  <p style="">KeepAlive默认情况下是关闭的，上层应用可以通过开关来开启和关闭</p>
 </li>
 <li>
  <p style="">tcp_keepalive_time<span style="font-size: 17px">: KeepAlive的空闲时长，或者说每次正常发送心跳的周期，默认值为7200s（2小时）</span></p>
 </li>
 <li>
  <p style="">t<span style="font-size: 17px">cp_keepalive_intvl: KeepAlive探测包的发送间隔，默认值为75s</span></p>
 </li>
 <li>
  <p style="">t<span style="font-size: 17px">cp_keepalive_probes: 在tcp_keepalive_time之后，没有接收到对方确认，继续发送保</span>活<span style="font-size: 17px">探测包次数，默认值为9（次）</span></p>
 </li>
</ul>
<p style=""></p>
<p style="">我们讲讲在Linux操作系统和使用Java如何设置。</p>
<p style=""></p>
<h2 style="" id="%E5%9C%A8linux%E5%86%85%E6%A0%B8%E8%AE%BE%E7%BD%AE"><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">在Linux内核设置</span></strong></h2>
<p style=""></p>
<p style="">KeepAlive默认不是开启的，如果想使用KeepAlive，需要在你的应用中设置SO_KEEPALIVE才可以生效。</p>
<p style=""></p>
<p style="">查看当前的配置：</p>
<pre><code>cat /proc/sys/net/ipv4/tcp_keepalive_time
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
cat /proc/sys/net/ipv4/tcp_keepalive_probes</code></pre>
<p style="text-align: start; ">在Linux中我们可以通过修改&nbsp;<strong><span fontsize="" color="">/etc/sysctl.conf</span></strong>&nbsp;的全局配置：</p>
<pre><code>net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9</code></pre>
<p style="text-align: start; ">添加上面的配置后输入&nbsp;<strong><span fontsize="" color="">sysctl -p</span></strong>&nbsp;使其生效，你可以使用&nbsp;<strong><span fontsize="" color="">sysctl -a | grep keepalive</span></strong>&nbsp;命令来查看当前的默认配置</p>
<blockquote>
 <p style="">如果应用中已经设置<strong><span fontsize="" color="">SO_KEEPALIVE</span></strong>，程序不用重启，内核直接生效</p>
</blockquote>
<h2 style="" id="%E4%BD%BF%E7%94%A8netty4%E8%AE%BE%E7%BD%AE"><strong><span style="font-size: 17px; color: rgb(0, 209, 0)">使用Netty4设置</span></strong></h2>
<p style="text-align: start; ">这里我们使用常用的Java网络框架Netty来设置，只需要在服务端设置即可：</p>
<pre><code>EventLoopGroup bossGroup   = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
   ServerBootstrap b = new ServerBootstrap();
   b.group(bossGroup, workerGroup)
           .channel(NioServerSocketChannel.class)
           .option(ChannelOption.SO_BACKLOG, 100)
           .childOption(ChannelOption.SO_KEEPALIVE, true)
           .handler(new LoggingHandler(LogLevel.INFO));

   // Start the server.
   ChannelFuture f = b.bind(8088).sync();
   // Wait until the server socket is closed.
   f.channel().closeFuture().sync();
} finally {
   // Shut down all event loops to terminate all threads.
   bossGroup.shutdownGracefully();
   workerGroup.shutdownGracefully();
}</code></pre>
<p style="text-align: start; ">这段代码来自经典的<strong><span fontsize="" color="">echo</span></strong>服务器，我们在<strong><span fontsize="" color="">childOption</span></strong>中开启了<strong><span fontsize="" color="">SO_KEEPALIVE</span></strong>。
 <br>
 Java程序只能做到设置<strong><span fontsize="" color="">SO_KEEPALIVE</span></strong>选项，其他配置项只能依赖于<strong><span fontsize="" color="">sysctl</span></strong>配置，系统进行读取。</p>
<h2 style="" id="%E4%BD%BF%E7%94%A8%E7%9A%84%E5%9C%BA%E6%99%AF"><strong><span style="font-size: 17px; color: rgb(0, 209, 0)">使用的场景</span></strong></h2>
<p style=""></p>
<p style=""><span style="font-size: mediumpx; color: rgb(18, 18, 18)">一般我们使用KeepAlive时会修改空闲时长，避免资源浪费，系统内核会为每一个TCP连接建立一个保护记录，相对于应用层面效率更高。</span></p>
<p style="">常见的几种使用场景：</p>
<ul>
 <li>
  <p style="">检测挂掉的连接（导致连接挂掉的原因很多，如服务停止、网络波动、宕机、应用重启等）</p>
 </li>
 <li>
  <p style="">防止因为网络不活动而断连，如使用NAT代理或者防火墙的时候，经常会出现这种问题</p>
 </li>
 <li>
  <p style="">HTTP协议的Keep-Alive意图在于连接复用，同一个连接上串行方式传递请求-响应数据</p>
 </li>
 <li>
  <p style="">TCP的KeepAlive机制意图在于保活、心跳，检测连接错误</p>
 </li>
</ul>
<p style="text-align: start; ">KeepAlive通过定时发送探测包来探测连接的对端是否存活，但通常也会许多在业务层面处理，他们之间的特点是：</p>
<ul>
 <li>
  <p style="">TCP自带的KeepAlive使用简单，发送的数据包相比应用层心跳检测包更小，仅提供检测连接功能</p>
 </li>
 <li>
  <p style="">应用层心跳包不依赖于传输层协议，无论传输层协议是TCP还是UDP都可以用</p>
 </li>
 <li>
  <p style="">应用层心跳包可以定制，可以应对更复杂的情况或传输一些额外信息</p>
 </li>
 <li>
  <p style="">KeepAlive仅代表连接保持着，而心跳包往往还代表客户端可正常工作</p>
 </li>
</ul>
<p style="">像Dubbo这种通信中间件都使用到了TCP的保活机制。k8s客户端和服务端是基于http协议的长连接，用到了http的保活复用连接。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">连接池技术</span></strong></p>
<p style=""></p>
<p style="">上面四次挥手讲了，如果服务器总是在断开连接，tcp会总是处于time wait状态。很多连接没有得到真正的释放。像数据库操作，是非常频繁的。咱们一般都不会像下图这么每次都申请和关闭连接吧。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sqbg.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">所以就希望尽可能的复用三次握手和四次挥手的过程，让客户端和服务端投入更多的资源在数据传输上。这就要用到连接池。</p>
<p style=""></p>
<p style="">连接池是一种池式结构。其他的池式结构有：线程池、协程池、内存池和对象池。它们的实现都很接近。连接池中也有很多大家听说过的场景，如：数据库连接池、MQ的连接池、Redis的连接池。</p>
<p style=""></p>
<p style="">提到MQ的连接池，之前的时候，有个同事排查我们使用的MQ并发吞吐太低。因为用的是标准的<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485843&amp;idx=1&amp;sn=ae07db807669699bc4bec807aa468a5c&amp;chksm=fafde33dcd8a6a2b8a34483bc61771c3cf99a49a7baf46e47e770c58372808e2053f27a203e1&amp;scene=21#wechat_redirect" target="_blank">Java消息服务JMS</a>客户端，跟了代码发现里面用到了connection.close。就怀疑用的短连接。其实close方法的实现一般不是销毁连接，还是归还到连接池。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kvln.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">连接池咱们平时使用最多、面试最多的是其中控制伸缩性的参数：最小连接数、最大连接数。最小连接数主要是要限定池的大小，最大连接数主要是限定能打开的最大连接数。使用连接池进行通信的流程如下图所示：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-lvrg.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">关于连接池更详细的内容因为预计还要2千字以上，我会专门写一篇文章来讲。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="">细思极恐</span>的Socket</strong></p>
<p style=""></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a><span fontsize="" color="">中我专门讲过socket，它功能强大但是让人细思极恐。之前发生过一个线上问题，某个时刻发生了几笔请求超时，排查发现打印Dubbo调用的来源IP时，触发了DNS反解析。网络闪断，连接不到DNS服务器，结果夯住10s直到超时。</span></p>
<p style=""></p>
<p style=""><span fontsize="" color="">熟悉Java语言的朋友可以了解一下当时线上问题的原因：
  <br></span></p>
<p style="">dubbo调用的来源IP使用的是dubbo的</p>
<p style="">RpcContext.getContext().getRemoteAddress().getHostName();</p>
<p style="">因为RpcContext.getContext()是基于上篇文章<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486339&amp;idx=1&amp;sn=ca9b6a4ba1a98a9182b7ff9e81a7e7ea&amp;chksm=fafde12dcd8a683b385853e053fb7444ef3772f09d39c977c1a69581e4ff294a45e8ab8fd53b&amp;scene=21#wechat_redirect" target="_blank">《ThreadLocal&amp;MDC内存泄漏问题》</a>中提到的ThreadLocal，就是保存在了线程里的一个固定值。所以也就相当于</p>
<p style="">new InetSocketAddress(固定的IP, 固定的端口).getHostName();</p>
<p style="">这个方法会进行DNS lookup！所以具体这个函数的执行时间受到网络状况的影响。建议可以直接使用IP的地方使用getAddress()代替getHostName()。</p>
<p style=""></p>
<p style="">这个问题可以这么来理解：</p>
<p style="">相当于在程序里执行了一个linux命令：nslookup 某IP</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-azkk.png&amp;size=m" width="511px" height="340px" style="display: inline-block"></p>
<p style=""></p>
<p style="">为什么说让人细思极恐呢？想想看，使用Socket编程，只需一行代码，有时候就是隐式的，实际上却可能发出了一个绕地球半圈的信号，关键多数开发者还不知道。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">如大家所见，本篇算是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486330&amp;idx=1&amp;sn=e85e201059c1c5575481e03531b06212&amp;chksm=fafde1d4cd8a68c235ef3cbed83bf0b6ac3a80052c0a492d8c2a217006cf4fa530f2f74f01c3&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">白话linux操作系统原理</span>》</a>的姊妹篇。<span fontsize="" color="">和</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486245&amp;idx=1&amp;sn=494e71a356ad4bd26633b5ae5278215f&amp;chksm=fafde18bcd8a689de3cfa45c0bbe629bc098d26a85106caff62b0bfa069055374515865a3b26&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="">《网络通信之Session的历史血脉》</span></a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486186&amp;idx=1&amp;sn=f21e17b21beb4c99cef0e076071ac859&amp;chksm=fafde044cd8a69526aa3ec9edbe71ea5aa41de2ef236494201dc2f20701ed8abc6be1a72344f&amp;scene=21#wechat_redirect" target="_blank">《深入理解MQ生产端的底层通信过程-理解channel》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486166&amp;idx=1&amp;sn=08a24c175c02a4b146dc52088d410599&amp;chksm=fafde078cd8a696e616aebcb72aeef444e1e0008bf950a368933195f13c0a268face0d3ee371&amp;scene=21#wechat_redirect" target="_blank">《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486210&amp;idx=1&amp;sn=fd7a60ca9b4e41f942dd0b2653228774&amp;chksm=fafde1accd8a68ba4f41f19d3543984f47756e884eb395b295cc7f4f1cd0575bc0305fba5431&amp;scene=21#wechat_redirect" target="_blank">《网络字节序列-大端序和小端序》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486242&amp;idx=1&amp;sn=483566d3fd97f333d3ea114dbbe8f7d1&amp;chksm=fafde18ccd8a689abfcb9959079a3cbfc4ecf98e52d34318bf9490422622bddb0c4b81ef2288&amp;scene=21#wechat_redirect" target="_blank">《https引起的跨域问题-COE&amp;casestudy》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486103&amp;idx=1&amp;sn=943c5b7f3558df78164321806f273c8f&amp;chksm=fafde039cd8a692f86f0ac97b0e1e05ece075de3e4f33e6a5ec90b269c51aed1963ff3107e0e&amp;scene=21#wechat_redirect" target="_blank">《懂得三境界-使用dubbo时请求超过问题》</a><span fontsize="" color="">、</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485988&amp;idx=1&amp;sn=efad5a030bc0a933ac5af2ee09f3c6c4&amp;chksm=fafde08acd8a699c198d0d65c413273f05f2796cfd873160c2ba81ed3f33aebfaccb62234cb7&amp;scene=21#wechat_redirect" target="_blank">《一个http请求进来都经过了什么(2021版)》</a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485862&amp;idx=1&amp;sn=414f61b537094b14dab2682a343700e9&amp;chksm=fafde308cd8a6a1e993ed8774cef1c8dc1233761a17b27395b89e3d2c74b0908e0971aeedf9a&amp;scene=21#wechat_redirect" target="_blank">《架构师之路-https底层原理》</a>是一个系列。</p>
<h1 style="text-align: start; " id="-2"></h1>
<p style=""></p>
<p style="">在之前的文章中说过，我尽量想办法让大家能把这些系列文章有兴趣看下去。如果大家坚持下去，会对以后的工作有很大帮助。<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486330&amp;idx=1&amp;sn=e85e201059c1c5575481e03531b06212&amp;chksm=fafde1d4cd8a68c235ef3cbed83bf0b6ac3a80052c0a492d8c2a217006cf4fa530f2f74f01c3&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">白话linux操作系统原理</span>》</a>这篇文章发出之后，反馈特别好。我就在想是不是这里存在着可以让大家坚持下去的点。可是，我自己都没搞清楚这个点在哪里。</p>
<p style=""></p>
<p style="">只要是用心写了，对自己的知识能力也有自信，知道所写的对大家知识能力是很有帮助的。那就坚持下去。相信可以帮助到大家，也为祖国的科技发展尽一份力。</p>]]></description><guid isPermaLink="false">/archives/bai-hua-tcp-ipyuan-li</guid><dc:creator>黄健</dc:creator><category>计算机基础</category><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:32:24 GMT</pubDate></item><item><title><![CDATA[监控系统怎样做？]]></title><link>https://wiki.dgfygk.com/archives/jian-kong-xi-tong-zen-yang-zuo</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%9B%91%E6%8E%A7%E7%B3%BB%E7%BB%9F%E6%80%8E%E6%A0%B7%E5%81%9A%EF%BC%9F&amp;url=/archives/jian-kong-xi-tong-zen-yang-zuo" width="1" height="1" alt="" style="opacity:0;">
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uvww.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">监控类型自底向上分为资源监控、服务监控和业务监控。希望打造公司级的监控系统最好的时机是系统规划时，如果把监控设计往后放，将会面临一个巨大的难题：推行和现有不兼容的规范。</p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">三种监控类型</span></strong></p>
<p style=""><strong><span style="font-size: 18px">资源监控</span></strong></p>
<p style="">这个相对简单，随着k8s的兴起，目前比较流行的是Prometheus。zabbix和falcon也比较普遍。因为对业务侵入性较小，属于比较容易实现和维护的部分。</p>
<p style=""><strong><span style="font-size: 18px">服务监控</span></strong></p>
<p style="">中间件、数据库和安全漏洞扫描，这些软件依赖于软件本身自带的配套监控，因为是量身定做，监控效果取决于技术选型等因素，可以各个击破，不需要特殊的进行统筹考虑。</p>
<p style="">应用接口监控的健康检查是需要统一标准的。一般的做法是就算服务本身是基于websocket协议、dubbo协议等进行通信，但还是会暴露一个http接口用于统一进行健康检查的监控。这个接口对每个应用来说，请求路径、请求方式(一般是get请求)，返回结构都相同。如果响应返回码属于200~399范围，‌则证明服务探活成功，‌否则探活失败。响应内容里通常还包含信息标识应用组件是否准备好开始处理用户请求。这对于启动时间较长的程序特别有用，‌如需要加载磁盘数据或依赖外部模块启动完成才能提供服务的情况。‌如果应用组件的就绪检查失败，‌集群会屏蔽请求访问该组件；‌若检查成功，‌则会开放对该应用组件的访问。‌<span style="font-size: 14px; color: rgb(51, 51, 51)">‌</span></p>
<p style=""><span fontsize="" color="">应用接口监控的耗时、QPS和成功率因为一旦协议确定，标准也确定。协议实现端通过切面等方式可以进行统一处理，只要引入相应的处理组件即可。</span></p>
<p style=""><strong><span style="font-size: 18px">业务监控</span></strong></p>
<p style="">业务监控不管是日志监控还是调用链监控，本质上都是基于日志埋点。</p>
<p style="">调用链监控需要在基于协议（比如http协议的请求头）和线程内部处理时（比如使用threadlocal）来传递和保持traceID，以便于在日志中将请求链路进行串联。</p>
<p style=""><span fontsize="" color="">日志埋点总体来说分为两类：结构化日志和非结构化日志。</span></p>
<p style=""><span fontsize="" color="">结构化日志里有一类重要日志是访问日志。这就要涉及开头说的巨大难题：推行和现有不兼容的规范。因为访问日志的最大作用是统计。统计系统状态、业务状态。我建议的规范是两码一态返回值结构。</span></p>
<pre><code>{  "sysCd":"00000000",  "sysMsg":"OK"  "biz":{      "bizCd":"00000000",       "bizMsg":"OK",      "data":{          "serverTime": 1499827319559       }    }}</code></pre>
<p style="">明确标识出系统码和业务码。如果系统码显示有系统错误，业务码就不需要关注。系统码显示有问题，一定是需要系统维护人员处理的。系统码正常，业务码显示有问题，那理论上来说更可能是用户自身的问题，比如付款时余额不足等。系统码和业务码共同决定了请求的最终状态。这种设计在运维上提供了极大的方便：系统码显示的问题需要系统维护人员来处理，业务码显示有问题可以大胆的让用户自己检查，不用半夜将开发小哥哥叫起来。</p>
<p style="">结构化日志里还可以用来记录与第三方交互的结果。非结构化日志一般会打印两种：一种是控制台日志，一种是错误日志。大多数业务放在云上，看到的POD日志是控制台日志。特别是在程序发布时，如果日志滚动太快，有可能忽略重要信息。这时候怎样处理呢？建议把结构化日志内容单独文件打印，不输出到控制台。因为结构化日志查起来比较方便，控制台尽量只打印非结构化日志。</p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">数据可视化</span></strong></p>
<p style="">Grafana和Kibana都是开源的数据可视化和监控工具，但它们在设计目标、数据源和特性上有所不同。</p>
<p style=""><strong><span style="font-size: 18px">Grafana</span></strong></p>
<p style="">设计目标：Grafana是一个通用的开源数据可视化工具，主要用于监控和展示各种数据源的时间序列数据。它可以帮助用户快速创建、编辑和共享数据仪表板。</p>
<p style="">数据源：Grafana支持多种数据源，包括但不限于Graphite、InfluxDB、OpenTSDB、Elasticsearch、Prometheus、MySQL和PostgreSQL等。这使得Grafana可以灵活地集成到各种环境中。</p>
<p style="">特性：</p>
<p style="">灵活的查询和聚合：Grafana提供了一个强大的查询编辑器，允许用户根据需求定制查询，以及使用不同的聚合函数来处理数据。</p>
<p style="">丰富的图表类型：Grafana支持多种图表类型，如折线图、柱状图、饼图、散点图等，用户可以根据需要选择合适的图表来展示数据。</p>
<p style="">警报和通知：Grafana内置了警报功能，允许用户设置阈值，当数据达到或超过这些阈值时，Grafana会发送通知给相关人员。</p>
<p style="">强大的插件系统：Grafana有一个活跃的插件社区，用户可以通过安装插件来扩展其功能，比如添加新的数据源支持、图表类型等。</p>
<p style=""><strong><span style="font-size: 18px">Kibana</span></strong></p>
<p style="">设计目标：Kibana是一个为Elasticsearch设计的开源数据可视化和探索工具。它是ELK中的K，主要用于分析存储在Elasticsearch中的数据，特别是日志和时序数据。</p>
<p style="">数据源：Kibana主要依赖于Elasticsearch作为数据源，但也可以与Logstash和Beats等其他Elasticsearch生态系统组件集成，以提供更强大的日志处理和搜索功能。</p>
<p style="">特性：</p>
<p style="">日志分析：Kibana提供了一个强大的日志分析界面，允许用户通过简单的查询和过滤来探索和分析存储在Elasticsearch中的日志数据。</p>
<p style="">丰富的可视化选项：Kibana支持多种图表类型，如直方图、折线图、饼图等，用户可以根据需要选择合适的图表来展示数据。</p>
<p style="">按时间统计数据：Kibana允许用户在不同的时间范围内查看数据，这对于分析时间序列数据非常有用。</p>
<p style="">集成开发：Kibana还提供了许多集成开发功能，如代码高亮、自动完成等，使得在Kibana中编写和测试查询变得更加容易。</p>
<p style=""><strong><span style="font-size: 18px">总结</span></strong></p>
<p style="">Grafana和Kibana都是强大的数据可视化和监控工具，但Grafana更加通用，支持多种数据源，而Kibana则更专注于Elasticsearch和日志分析。</p>]]></description><guid isPermaLink="false">/archives/jian-kong-xi-tong-zen-yang-zuo</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:16:09 GMT</pubDate></item><item><title><![CDATA[注册中心与配置中心，是分？是合？]]></title><link>https://wiki.dgfygk.com/archives/zhu-ce-zhong-xin-yu-pei-zhi-zhong-xin-shi-fen-shi-he</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E4%B8%8E%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%EF%BC%8C%E6%98%AF%E5%88%86%EF%BC%9F%E6%98%AF%E5%90%88%EF%BC%9F&amp;url=/archives/zhu-ce-zhong-xin-yu-pei-zhi-zhong-xin-shi-fen-shi-he" width="1" height="1" alt="" style="opacity:0;">
<p style="">一零年代初，我还自己动手写过socket连接来做服务注册发现，后来有了zookeeper就方便多了，那时候zookeeper也直接做配置中心使用。后来出现了一些专门的服务注册发现组件如Eureka\etcd\consul，专门的配置中心比如spring cloud config\apollo。但是spring cloud全家桶后来又用nacos把注册发现和配置中心合在一起了。</p>
<p style=""><strong><span style="font-size: 18px">为什么它们能合在一起用同一个服务提供？</span></strong></p>
<p style="">因为本质上，注册中心和配置中心都是数据变化了，各个服务可以实时感知。对注册中心来说，服务状态作为了一种特殊的数据。它们核心都在做一件事：数据变化动态感知。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dqvp.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">zookeeper采用的是基于会话的长连接，wacher监听机制，对变更事件进行发布监听。zookeeper能承载的连接数是可扩展的。咱们使用基于zookeeper的框架，比如dubbo\hadoop这些，都需要在客户端配置zookeeper集群节点，比如：</p>
<pre><code>server.1=zoo1:2888:3888server.2=zoo2:2888:3888server.3=zoo3:2888:3888</code></pre>
<p style="">如果连服务端采用的是随机数算法，即随机选一台服务器进行长连接的话，服务端数量增加，可支持的客户端就可以增加。</p>
<p style="">但是zookeeper能承载的数据量因为数据模型类似于文件系统，是树状结构，不易于分割，所以受限于硬件。</p>
<p style="">配置中心在业务上是有约束的，它必须是一个CP的系统。就是说对数据一致性的要求是要高于可用性的。而zookeeper刚好是一个CP的系统。</p>
<p style=""><strong><span style="font-size: 18px">为什么要将注册中心与配置中心分开？</span></strong></p>
<p style=""><span fontsize="" color="">zookeeper</span>所谓的配置中心和etcd配置中心是一个道理。</p>
<p style="">将etcd拆开来看，ETC +D(distributed)，也就是为分布式系统存储配置信息(这可能和小部分开发者认为的etcd的作用有些出入，很多人潜意识认为etcd就是用于服务发现、发布订阅等，其实这和配置信息没有冲突)，当然这个配置信息功能又和业务侧使用的apolo等配置中心有些差异，apolo等配置中心主要用于最上层的业务开发的信息配置，而ETCD则是用于服务注册中心等基本架构的配置。</p>
<p style=""><span fontsize="" color="">业务开发的信息配置相比较而言，虽然同样需要数据变更感知，但是数据结构上灵活性大，存储上也更灵活。所以出现了专门的配置中心，比如spring cloud config将数据存储在git仓库中，apollo存储在数据库中。但是它们依赖于注册发现，比如apollo架构上就依赖于eureka。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-augc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">而只作为注册中心的Eureka不需要像zookeeper一样既可以注册永久节点又可以注册临时节点。Eureka只支持注册临时节点。它是一个AP的系统。</p>
<p style=""><strong><span style="font-size: 18px">为什么</span><span fontsize="" color="">spring cloud全家桶后来又用nacos把注册发现和配置中心合在一起了？</span></strong></p>
<p style="text-align: start; ">Nacos<span style="font-size: mediumpx; color: rgb(25, 27, 31)">在注册时可以选择注册临时节点或者永久节点</span>，如果在Nacos上注册临时节点，那么Nacos就是AP服务，保证高可用。如果Nacos上注册永久节点，那么Nacos就是CP服务，保证数据一致性。有了这个条件，距离配置中心，只差一个支持数据库这样的外部存储。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-pjmd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">而现代微服务架构更倾向于一体化解决方案，同时兼顾高可用性、‌数据一致性、‌集群扩展性以及多地域部署和容灾支持，‌构建一个功能丰富、‌易于使用的平台。两个功能同时提供，总体成本更低，维护性更好。</p>
<p style=""><strong><span style="font-size: 18px">总结</span></strong></p>
<p style="">注册中心与配置中心核心都是<span fontsize="" color="">数据变化动态感知，但使用场景不同。其演变过程经历了根据不同的开发场景进行针对性设计的过程，最终经过对前面设计的去粗取精，结合现代微服务架构的特点进行了一体化设计。</span></p>
<p style=""><strong><span style="font-size: 18px">其他</span></strong></p>
<p style="">在实际使用eureka的过程中出现过两次线上问题。两次线上问题的本质原因是eureka服务端资源不足，造成排队上下线。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mruc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">第一次问题是在服务上线时，我猜测线程池队列应该已经溢出，任务直接丢弃了。当时有提过需要加大Eureka的资源，但是负责同学说资源够用，因为监控覆盖的原因，没有直接证据，所以当时没有处理。</p>
<p style="">第二次问题是在服务下线时，下线有延迟，之后自动恢复。当时的处理有减小缓存时间从默认30s改成10s。但客户端实际1分多才会真正下线，这个时间不是本质原因。本质还是资源不足，任务排队。后来负责同学找到了资源瓶颈点：JVM限制。进行扩容之后解决。在客户端也做了相应的容灾：如果下线失败，重新执行下线。</p>]]></description><guid isPermaLink="false">/archives/zhu-ce-zhong-xin-yu-pei-zhi-zhong-xin-shi-fen-shi-he</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:14:03 GMT</pubDate></item><item><title><![CDATA[架构师应该在团队中发挥怎样的作用？]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-shi-ying-gai-zai-tuan-dui-zhong-fa-hui-zen-yang-de-zuo-yong</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E5%B8%88%E5%BA%94%E8%AF%A5%E5%9C%A8%E5%9B%A2%E9%98%9F%E4%B8%AD%E5%8F%91%E6%8C%A5%E6%80%8E%E6%A0%B7%E7%9A%84%E4%BD%9C%E7%94%A8%EF%BC%9F&amp;url=/archives/jia-gou-shi-ying-gai-zai-tuan-dui-zhong-fa-hui-zen-yang-de-zuo-yong" width="1" height="1" alt="" style="opacity:0;">
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gfqs.png&amp;size=m" width="531px" height="659px" style="display: inline-block"></p>
<p style="text-align: justify; "><span style="font-size: var(--articleFontsize)px">架构师分为5种：</span></p>
<p style="text-align: justify; ">1.企业架构师EA(Enterprise Architect)&nbsp; &nbsp;EA的职责是决定整个公司的技术路线和技术发展方向。</p>
<p style="text-align: justify; ">2.基础结构架构师IA(Infrastructure Architect)&nbsp; IA的工作就是提炼和优化技术方面积累和沉淀形成的基础性的、公共的、可复用的框架和组件，这些都是一个技术型公司传承下来的最宝贵的财富之一；</p>
<p style="text-align: justify; ">3.特定技术架构 TSA (Technology-Specific Architect)&nbsp; &nbsp;主要从事类似安全架构、存储架构等专项技术的规划和设计工作；</p>
<p style="text-align: justify; ">4.解决方案架构师SA (Solution Architect)。专于解决方案的规划和设计</p>
<p style="text-align: justify; ">5.业务架构师BA (Business Architect)。在业务团队内部，和leader打配合。</p>
<p style="text-align: justify; ">和业务团队联系最为紧密的业务架构师，在团队中，leader和架构师之间一般是刘备和诸葛亮之间的关系。如果leader本身技术能力特别强，那可能会演变成曹操和荀彧。荀彧在战略方面为曹操规划制定了统一北方的蓝图和军事路线，多次修正曹操的战略方针。</p>
<p style="text-align: justify; ">不管是诸葛亮、荀彧，或者其他的架构师例如：张良、羊皮大夫百里奚，在团队中有两个重要作用：第一个是制定规范、规划和方案，第二个是在团队中有足够的影响力，在关键时刻发挥作用。</p>
<p style="text-align: justify; ">那要问影响力是怎么来的，那就是个人技术能力的一个认可度了。前段时间我在做拆库分表的技术方案评审时，听到人家说就是现在业务不太忙，想把事情做掉。具体为什么要一拆十还是拍脑袋的，未来谁也说不好。就这件事，我谈谈自己的观点。仁者见仁，没有谁对谁错，只是谈谈自己的思考。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">为什么要分表？</span></strong></p>
<p style="text-align: justify; ">之所以想着去做这件事，我理解是为了未来解决扩展性的问题。是需要依托于架构整体规划的。架构整体规划重要的环节是容量评估。容量评估除了在大的技术方案上要考虑之外，一般还需要架构师定期去做。比如1个季度、半年、1年做一次容量评估。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">未来的不确定因素太多，容量怎样去评估呢？</span></strong></p>
<p style="text-align: justify; ">我们的目标是什么。如果目标是同行业前5。可以从他们公开的财务报表等数据调研他们的数据量级。比如行业第五，日单量是500万。拆10张表。一个数据库在量级达到千万级别，性能开始下降。也就是说1张表大概可以存2天的数据，10张表可以存20天的数据。数据历史记录如果可以允许查半年的。那就不够了。分成100张表，过了半年可以进行归档，就可以持续支撑日单量500万的交易。</p>
<p style="text-align: justify; ">实际上一般很少有人要做分库分表了，一次这种大动作还只提高10倍的容量。因为就单库来说，分成1百张表、1千张表，磁盘容量都不是瓶颈。瓶颈在支撑的TPS上。就一个高性能物理机的数据库来说，大概可以支撑5000TPS量级。在这种情况下，表分的细一些，可以提高单表的性能。况且，这种逻辑分表并不需要额外的资源支持，不会增加成本。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">容量评估时还需要考虑哪些因素？</span></strong></p>
<p style="text-align: justify; ">容量评估时还需要考虑其他的设计：读写分离、数据归档。</p>
<p style="text-align: justify; ">读写分离，如果读写比例是1比1，分离后容量会增加1倍。但分离和不分离的区别不是简单改代码的问题，而是分离后要考虑读的延迟。比如mysql主从延迟一般在1s。实时业务的场景其实不能够允许这么大的延迟。但有些场景，比如历史查询、列表查询是可以的。如果读写分离是需要想清楚哪些场景要拆分到主库，哪些是拆分到从库的。会不会涉及事务问题。</p>
<p style="text-align: justify; ">数据归档，因为已经需要使用像sharding-jdbc这种中间件了。这种中间件不仅可以支持哈希散列算法，还可以按照时间散列的。所以可以直接在分表设计时就可以设计为_上半年下半年标识_哈希，这样只要有定时脚本数据表直接提前建好，自动实现数据归档，无需额外服务处理。比如现在已经是下半年。现在上半年只有读的流量，没有写的流量了。下半年的读写都很多。所以上半年数据属于冷数据，下半年数据属于热数据，实现了冷热分离。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">其他还有什么考虑点？</span></strong></p>
<p style="text-align: justify; ">数据库分库分表是个大动作，一般要提前很久规划，过程因为涉及数据一致性校验等环节，相对持续时间也很长。而这个过程是对业务进行梳理的绝佳机会。比如可以趁此机会将原本的数据库事务改成分布式事务，提升架构的性能和扩展性。</p>
<p style="text-align: justify; ">而架构师要自己或者带领团队成员做好各个方面的考虑。考虑问题要比leader全面，并具有前瞻性视野。</p>
<p style="text-align: justify; "><span style="font-size: var(--articleFontsize)px">架构师分为5种：</span></p>
<p style="text-align: justify; ">1.企业架构师EA(Enterprise Architect)&nbsp; &nbsp;EA的职责是决定整个公司的技术路线和技术发展方向。</p>
<p style="text-align: justify; ">2.基础结构架构师IA(Infrastructure Architect)&nbsp; IA的工作就是提炼和优化技术方面积累和沉淀形成的基础性的、公共的、可复用的框架和组件，这些都是一个技术型公司传承下来的最宝贵的财富之一；</p>
<p style="text-align: justify; ">3.特定技术架构 TSA (Technology-Specific Architect)&nbsp; &nbsp;主要从事类似安全架构、存储架构等专项技术的规划和设计工作；</p>
<p style="text-align: justify; ">4.解决方案架构师SA (Solution Architect)。专于解决方案的规划和设计</p>
<p style="text-align: justify; ">5.业务架构师BA (Business Architect)。在业务团队内部，和leader打配合。</p>
<p style="text-align: justify; ">和业务团队联系最为紧密的业务架构师，在团队中，leader和架构师之间一般是刘备和诸葛亮之间的关系。如果leader本身技术能力特别强，那可能会演变成曹操和荀彧。荀彧在战略方面为曹操规划制定了统一北方的蓝图和军事路线，多次修正曹操的战略方针。</p>
<p style="text-align: justify; ">不管是诸葛亮、荀彧，或者其他的架构师例如：张良、羊皮大夫百里奚，在团队中有两个重要作用：第一个是制定规范、规划和方案，第二个是在团队中有足够的影响力，在关键时刻发挥作用。</p>
<p style="text-align: justify; ">那要问影响力是怎么来的，那就是个人技术能力的一个认可度了。前段时间我在做拆库分表的技术方案评审时，听到人家说就是现在业务不太忙，想把事情做掉。具体为什么要一拆十还是拍脑袋的，未来谁也说不好。就这件事，我谈谈自己的观点。仁者见仁，没有谁对谁错，只是谈谈自己的思考。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">为什么要分表？</span></strong></p>
<p style="text-align: justify; ">之所以想着去做这件事，我理解是为了未来解决扩展性的问题。是需要依托于架构整体规划的。架构整体规划重要的环节是容量评估。容量评估除了在大的技术方案上要考虑之外，一般还需要架构师定期去做。比如1个季度、半年、1年做一次容量评估。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">未来的不确定因素太多，容量怎样去评估呢？</span></strong></p>
<p style="text-align: justify; ">我们的目标是什么。如果目标是同行业前5。可以从他们公开的财务报表等数据调研他们的数据量级。比如行业第五，日单量是500万。拆10张表。一个数据库在量级达到千万级别，性能开始下降。也就是说1张表大概可以存2天的数据，10张表可以存20天的数据。数据历史记录如果可以允许查半年的。那就不够了。分成100张表，过了半年可以进行归档，就可以持续支撑日单量500万的交易。</p>
<p style="text-align: justify; ">实际上一般很少有人要做分库分表了，一次这种大动作还只提高10倍的容量。因为就单库来说，分成1百张表、1千张表，磁盘容量都不是瓶颈。瓶颈在支撑的TPS上。就一个高性能物理机的数据库来说，大概可以支撑5000TPS量级。在这种情况下，表分的细一些，可以提高单表的性能。况且，这种逻辑分表并不需要额外的资源支持，不会增加成本。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">容量评估时还需要考虑哪些因素？</span></strong></p>
<p style="text-align: justify; ">容量评估时还需要考虑其他的设计：读写分离、数据归档。</p>
<p style="text-align: justify; ">读写分离，如果读写比例是1比1，分离后容量会增加1倍。但分离和不分离的区别不是简单改代码的问题，而是分离后要考虑读的延迟。比如mysql主从延迟一般在1s。实时业务的场景其实不能够允许这么大的延迟。但有些场景，比如历史查询、列表查询是可以的。如果读写分离是需要想清楚哪些场景要拆分到主库，哪些是拆分到从库的。会不会涉及事务问题。</p>
<p style="text-align: justify; ">数据归档，因为已经需要使用像sharding-jdbc这种中间件了。这种中间件不仅可以支持哈希散列算法，还可以按照时间散列的。所以可以直接在分表设计时就可以设计为_上半年下半年标识_哈希，这样只要有定时脚本数据表直接提前建好，自动实现数据归档，无需额外服务处理。比如现在已经是下半年。现在上半年只有读的流量，没有写的流量了。下半年的读写都很多。所以上半年数据属于冷数据，下半年数据属于热数据，实现了冷热分离。</p>
<p style="text-align: justify; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">其他还有什么考虑点？</span></strong></p>
<p style="text-align: justify; ">数据库分库分表是个大动作，一般要提前很久规划，过程因为涉及数据一致性校验等环节，相对持续时间也很长。而这个过程是对业务进行梳理的绝佳机会。比如可以趁此机会将原本的数据库事务改成分布式事务，提升架构的性能和扩展性。</p>
<p style="text-align: justify; ">而架构师要自己或者带领团队成员做好各个方面的考虑。考虑问题要比leader全面，并具有前瞻性视野。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F2tk5ianItRl9GbN4PB4gn7OyXloG5xcBJvmHlvSicfh9gqIZjRicMUyickPsjyCR7BWealH4vtxgP3unsickv7NQorg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg%26tp%3Dwxpic%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1&amp;size=m" alt="图片" width="500px" height="auto" style="display: inline-block"></p>
<p style=""><span style="font-size: var(--articleFontsize)px">架构师分为5种：</span></p>
<p style="">1.企业架构师EA(Enterprise Architect)&nbsp; &nbsp;EA的职责是决定整个公司的技术路线和技术发展方向。</p>
<p style="">2.基础结构架构师IA(Infrastructure Architect)&nbsp; IA的工作就是提炼和优化技术方面积累和沉淀形成的基础性的、公共的、可复用的框架和组件，这些都是一个技术型公司传承下来的最宝贵的财富之一；</p>
<p style="">3.特定技术架构 TSA (Technology-Specific Architect)&nbsp; &nbsp;主要从事类似安全架构、存储架构等专项技术的规划和设计工作；</p>
<p style="">4.解决方案架构师SA (Solution Architect)。专于解决方案的规划和设计</p>
<p style="">5.业务架构师BA (Business Architect)。在业务团队内部，和leader打配合。</p>
<p style="">和业务团队联系最为紧密的业务架构师，在团队中，leader和架构师之间一般是刘备和诸葛亮之间的关系。如果leader本身技术能力特别强，那可能会演变成曹操和荀彧。荀彧在战略方面为曹操规划制定了统一北方的蓝图和军事路线，多次修正曹操的战略方针。</p>
<p style="">不管是诸葛亮、荀彧，或者其他的架构师例如：张良、羊皮大夫百里奚，在团队中有两个重要作用：第一个是制定规范、规划和方案，第二个是在团队中有足够的影响力，在关键时刻发挥作用。</p>
<p style="">那要问影响力是怎么来的，那就是个人技术能力的一个认可度了。前段时间我在做拆库分表的技术方案评审时，听到人家说就是现在业务不太忙，想把事情做掉。具体为什么要一拆十还是拍脑袋的，未来谁也说不好。就这件事，我谈谈自己的观点。仁者见仁，没有谁对谁错，只是谈谈自己的思考。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">为什么要分表？</span></strong></p>
<p style="">之所以想着去做这件事，我理解是为了未来解决扩展性的问题。是需要依托于架构整体规划的。架构整体规划重要的环节是容量评估。容量评估除了在大的技术方案上要考虑之外，一般还需要架构师定期去做。比如1个季度、半年、1年做一次容量评估。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">未来的不确定因素太多，容量怎样去评估呢？</span></strong></p>
<p style="">我们的目标是什么。如果目标是同行业前5。可以从他们公开的财务报表等数据调研他们的数据量级。比如行业第五，日单量是500万。拆10张表。一个数据库在量级达到千万级别，性能开始下降。也就是说1张表大概可以存2天的数据，10张表可以存20天的数据。数据历史记录如果可以允许查半年的。那就不够了。分成100张表，过了半年可以进行归档，就可以持续支撑日单量500万的交易。</p>
<p style="">实际上一般很少有人要做分库分表了，一次这种大动作还只提高10倍的容量。因为就单库来说，分成1百张表、1千张表，磁盘容量都不是瓶颈。瓶颈在支撑的TPS上。就一个高性能物理机的数据库来说，大概可以支撑5000TPS量级。在这种情况下，表分的细一些，可以提高单表的性能。况且，这种逻辑分表并不需要额外的资源支持，不会增加成本。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">容量评估时还需要考虑哪些因素？</span></strong></p>
<p style="">容量评估时还需要考虑其他的设计：读写分离、数据归档。</p>
<p style="">读写分离，如果读写比例是1比1，分离后容量会增加1倍。但分离和不分离的区别不是简单改代码的问题，而是分离后要考虑读的延迟。比如mysql主从延迟一般在1s。实时业务的场景其实不能够允许这么大的延迟。但有些场景，比如历史查询、列表查询是可以的。如果读写分离是需要想清楚哪些场景要拆分到主库，哪些是拆分到从库的。会不会涉及事务问题。</p>
<p style="">数据归档，因为已经需要使用像sharding-jdbc这种中间件了。这种中间件不仅可以支持哈希散列算法，还可以按照时间散列的。所以可以直接在分表设计时就可以设计为_上半年下半年标识_哈希，这样只要有定时脚本数据表直接提前建好，自动实现数据归档，无需额外服务处理。比如现在已经是下半年。现在上半年只有读的流量，没有写的流量了。下半年的读写都很多。所以上半年数据属于冷数据，下半年数据属于热数据，实现了冷热分离。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">其他还有什么考虑点？</span></strong></p>
<p style="">数据库分库分表是个大动作，一般要提前很久规划，过程因为涉及数据一致性校验等环节，相对持续时间也很长。而这个过程是对业务进行梳理的绝佳机会。比如可以趁此机会将原本的数据库事务改成分布式事务，提升架构的性能和扩展性。</p>
<p style="">而架构师要自己或者带领团队成员做好各个方面的考虑。考虑问题要比leader全面，并具有前瞻性视野。</p>]]></description><guid isPermaLink="false">/archives/jia-gou-shi-ying-gai-zai-tuan-dui-zhong-fa-hui-zen-yang-de-zuo-yong</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:11:06 GMT</pubDate></item><item><title><![CDATA[JWT鉴权体系]]></title><link>https://wiki.dgfygk.com/archives/jwtjian-quan-ti-xi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=JWT%E9%89%B4%E6%9D%83%E4%BD%93%E7%B3%BB&amp;url=/archives/jwtjian-quan-ti-xi" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">引子</span></strong></p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-enmf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">对于业务的发展阶段，不同的人对0到100的定义差距很大。上图是我认知里的阶段划分。和很多人的认知相比，我认为的1不是系统搭建起来可用，而是系统已经获得了一定的认可，具备持续运营的条件；下一阶段是高速发展期，系统复杂性增加，业务量指数级增长，甚至应该是在行业处于领先地位才能叫做10；在100的阶段是做到了行业第一。毕竟不是第一怎么敢说给自己打100分呢？100+就像是小学生常规题满分之后的附加题一样，是要有超纲性的，需要在维持行业领头羊基础上孵化出新业务并取得了一定成绩的，比如谷歌，依托于行业翘楚的搜索引擎，孵化出Google Maps、Gmail。</p>
<p style="">在越往后的阶段，对安全上的要求也会越来越高。对接口安全来说，常考虑的是两个方面，一个是鉴权，一个是加密。鉴权要求高的场景下，常用的是两种体系：一种是OAuth体系，一种是JWT体系。这两种体系根据不同的安全等级，会使用其他安全技术来配合。比如OAuth2.0+openid connect。各种标准的RFC文档可在这个网站找到：https://datatracker.ietf.org/doc/html/rfc6749。我周末读了OAuth2.0和JWT两个文档，共花了5个小时的时间。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mmqv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">不管是哪种体系，鉴权实际上包含上图流程中的1、传输加密；2、身份认证；3、鉴权三个部分。今天咱们以JWT为例来说明这个体系怎样和其他鉴权方式配合使用。</p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">JWT体系举例</span></strong></p>
<p style="">开放平台的权限控制一般来说常使用CBAC。</p>
<p style="">基于声明的访问控制（Claim-Based Access Control, CBAC）：
 <br>
 CBAC 模型强调用户、资源和权限的声明性描述。用户、资源和权限都有一组声明（即属性），系统通过比较这些声明来决定用户是否有权访问资源。CBAC 模型提供了更加灵活和可扩展的权限管理，可以与其他安全模型（如 OAuth、SAML 等）集成。</p>
<p style="">假设一个开放平台提供以下三个等级的控制声明：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 200px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <th colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;font-weight: var(--base-text-weight-semibold, 600);">
     <p style="">安全等级</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;font-weight: var(--base-text-weight-semibold, 600);">
     <p style="">鉴权方式</p>
    </th>
   </tr>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">开放接口</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">不需要鉴权的接口</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-muted, var(--color-canvas-subtle));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">低安全接口</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">需要有效的API-KEY</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">高安全接口</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">需要有效的API-KEY和签名</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="">API-Key：API Key是一种简单的接口鉴权方式，平台分配一个API Key给合作方。合作方通过将API Key放在请求头或URL参数中，服务端验证API Key的合法性。API Key易于实现，但安全性较低，容易被攻击者盗取。</p>
<p style="">需要签名的鉴权方式一般使用的就是JWT。</p>
<p style="">SON Web Tokens (JWT)：JWT是一个开放标准（RFC 7519），它定义了一种简洁、自包含的方式用于通信双方之间以JSON对象的形式安全地传输信息。JWT常用于身份验证和信息交换场景。</p>
<p style="">实现JWT的步骤：</p>
<p style="">1、有效期声明</p>
<p style="">在JWT规范中，明确规定参数中需要带有效期的声明。比如以下参数timestamp字段就是这个声明的一种实现。</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 200px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
  </colgroup>
  <tbody>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <th colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;font-weight: var(--base-text-weight-semibold, 600);">
     <p style="">参数</p>
    </th>
    <th colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;font-weight: var(--base-text-weight-semibold, 600);">
     <p style="">取值</p>
    </th>
   </tr>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">symbol</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">BTCUSDT</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-muted, var(--color-canvas-subtle));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">side</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">SELL</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">type</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">LIMIT</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-muted, var(--color-canvas-subtle));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">timeInForce</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">GTC</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">quantity</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">1</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-muted, var(--color-canvas-subtle));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">price</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">0.2</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-default, var(--color-canvas-default));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">timestamp</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">1668481559918</p>
    </td>
   </tr>
   <tr style="background-color: var(--bgColor-muted, var(--color-canvas-subtle));border-top: 1px solid var(--borderColor-muted, var(--color-border-muted));">
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">recvWindow</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="padding: 6px 13px;">
     <p style="">5000</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style="">2、Payload</p>
<p style="">将参数列表排列成一个 string。用&nbsp;&amp;&nbsp;分隔每个参数。对于上述参数，签名 payload 如下所示：</p>
<ul>
 <li>
  <p style=""></p>
 </li>
</ul>
<pre><code>symbol=BTCUSDT&amp;side=SELL&amp;type=LIMIT&amp;timeInForce=GTC&amp;quantity=1&amp;price=0.2&amp;timestamp=1668481559918&amp;recvWindow=5000br</code></pre>
<p style="">3、计算签名</p>
<p style="">将签名有效负载编码为 ASCII 数据并使用带有 SHA-256 hash 函数的 RSASSA-PKCS1-v1_5 算法对 payload 进行签名。</p>
<p style="">这句话信息量比较大，用图来表示：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uaqc.png&amp;size=m" width="338px" height="471px" style="display: inline-block"></p>
<p style="">计算签名分三步：</p>
<ul>
 <li>
  <p style="">第一步：将签名有效负载编码为 ASCII 数据</p>
 </li>
 <li>
  <p style="">第二步：将待签名的 M 进行 Hash，JWT规范中建议使用HMAC SHA-256散列算法，得到的字符串，咱们用H代表</p>
 </li>
 <li>
  <p style="">第三步：将 H 进行 RSA 私钥加密。RSASSA-PKCS1-v1_5是一种RSA算法实现。</p>
 </li>
 <li>
  <p style="">第四步：JWT又分为JWS和JWE，它们的区别白话来讲JWS需要再将得到的字符串做一次base64，而JWE不做这一步。咱们通常都会使用JWS。</p>
  <p style=""></p>
 </li>
</ul>
<p style="">具体实现步骤可参考JWT的RFC规范文档实现部分：https://datatracker.ietf.org/doc/html/rfc7519#section-8。</p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">JWT体系总结</span></strong></p>
<p style="">JWT鉴权体系和Oauth体系一样，集成了很多身份认证、加密的技术，如：api key、HMAC、RSA。这些本身都可以单独使用，也可以组合使用。</p>
<p style=""><span style="font-size: mediumpx; color: rgb(25, 27, 31)">JWT为什么要组合这些技术呢？</span></p>
<p style=""><span style="font-size: mediumpx; color: rgb(25, 27, 31)">JWT 的优点包括它们的大小小、传输速度快，以及它们是自包含的，这意味着在 JWT 本身就包含了所有需要的信息，减少了需要查询数据库的次数。然而，它们也有安全性问题，例如如果密钥被泄露，那么签名的 JWT 可能会被篡改。因此，使用 JWT 时应该采取适当的安全措施，例如使用上面介绍的强密码学算法和确保密钥的安全存储。</span></p>]]></description><guid isPermaLink="false">/archives/jwtjian-quan-ti-xi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:09:21 GMT</pubDate></item><item><title><![CDATA[CURD系统怎样做出技术含量：微内核改造]]></title><link>https://wiki.dgfygk.com/archives/curdxi-tong-zen-yang-zuo-chu-ji-shu-han-liang-wei-nei-he-gai-zao</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=CURD%E7%B3%BB%E7%BB%9F%E6%80%8E%E6%A0%B7%E5%81%9A%E5%87%BA%E6%8A%80%E6%9C%AF%E5%90%AB%E9%87%8F%EF%BC%9A%E5%BE%AE%E5%86%85%E6%A0%B8%E6%94%B9%E9%80%A0&amp;url=/archives/curdxi-tong-zen-yang-zuo-chu-ji-shu-han-liang-wei-nei-he-gai-zao" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">建「中心」还是「平台」？</span></strong></p>
<p style="">1<span style="font-size: 16px">0年前在一个公司，公司里经常需要做线上活动。对开发来说，就是通过不同的活动页面进行注册、登录，之后会有一些个性化的小活动：领取小礼物、领取红包或者参加抽奖。
  <br></span></p>
<p style=""><span fontsize="" color=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jpcj.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<p style=""><span style="font-size: 16px">前端和后端疲于天天做各种换汤不换药的注册、登录。开发方式基本是把上次的代码拷贝一份，在此基础上做修改，来支撑新活动。大家就开会一起想解决办法解决重复开发的问题。大龄前端架构师说：「可以做工具自动生成注册、登录代码。」新生代的架构师说：「这个意义不是很大，应该做平台来解决。」</span><span fontsize="" color="">10年过去了，现在我再回头想想。觉得当初应该建「中心」来解决。</span></p>
<p style=""><span style="font-size: 16px">首先明确一下「平台」和「中心」的区别。中心是个点，平台是个面。一般来讲，平台更大。中心是为了解决复用的问题，</span><span fontsize="" color="">一般用在公司内部</span>进行统一开发、统一管理、统一维护。比如业务上有「活动中心」；技术上有「配置中心」。平台可以对内也可以对外，按咱们「编程一生」群友的说法：是SaaS的。比如各种开放平台，还有网联清算等业务平台，各种交易平台。一般需要一些配套：用户注册、登录、权限控制等。</p>
<p style=""><span style="font-size: 16px">下面截图出于对群友信息的保护，隐藏了昵称和头像。
  <br></span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-byyi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><span style="font-size: 16px">我们当时面临的问题，是要解决公司内部复用的问题，建立「中心」是合适的。当然，如果系统演进的好，以后把能力开发出去做成「平台」也未可知。但首先要是「中心」。</span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">用「微内核架构」解决问题</span></strong></p>
<p style=""><span style="font-size: 16px">我们当时遇到的问题，后端之所以每次都要参与开发是因为当时还是前后端不分离的架构。后端在注册、登录部分的修改主要是跳转的页面不同。现在都是前后端分离的架构了。后端只提供数据，流程之间的串联基本靠前端实现。所以后端只要有真正新功能的时候才需要开发，重复开发并不多。
  <br></span></p>
<p style=""><span style="font-size: 16px">前端对应每一个需求都是有一定的开发量的。但是对于这种重复工作多的，页面可以通过配置出来，而不用真正意义的开发。甚至可以通过低代码或者零代码的方式拖拖拽拽就把功能实现了。核心就是把原来的功能分成一个个小组件。
  <br></span></p>
<p style=""><span style="font-size: 16px">这听起来好像当下很多项目就是这么做的。怎样改造成微内核架构呢？
  <br></span></p>
<p style=""><span style="font-size: 16px">不需要再做什么了。这已经是一个微内核架构的项目了。不管对于前端还是后端，核心服务(内核)是注册、登录和做活动。具体产品和运营那边的奇思妙想，新的活动类型，可以视为插件。需要的时候开发出来插上，如果发现效果不好，要彻底弃用就拔下来。微小的核心服务+可插拔的插件，就是一个标准的微内核架构。</span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">受到的质疑</span></strong></p>
<p style=""><span style="font-size: 16px">之前和别人聊的时候，人家质疑时我说：你这样，安装插件的时候，服务需要重新发布，需要重启吗？</span></p>
<p style=""><span style="font-size: 16px">我说：需要开发，也需要重新发布上线。</span></p>
<p style=""><span style="font-size: 16px">人家说：需要重启就不是一个微内核的架构。
  <br></span></p>
<p style=""><span style="font-size: 16px">我心里知道他理解的不对，但一时没有想清楚他问题的本质，当时没有做任何的回应。现在想想应该这么来思考：</span></p>
<p style=""><span style="font-size: 16px">先看看标准的微内核架构代表：eclipse\Intelij idea这样的IDE(IDE,即Integrated Development Environment,是"集成开发环境"的英文缩写,可以辅助开发程序的应用软件&nbsp;)工具。如果需要的插件是现在应用市场上没有的，也是需要重新开发的吧？开发出来就要上线吧？上线的方式可能只是打个包放到外部可以访问的平台上。但是IDE要集成插件引入之后也需要重启IDE吧？连微内核架构的经典代表IDE都是需要重启的，所以从是否需要重启来作为是否是微内核架构的标准是有失偏颇的。</span></p>
<p style=""><span style="font-size: 16px">微内核的本质是核心逻辑是有限的，可以维持相对稳定。其他功能可以方便的集成和拆除。它是一种思想，符合这种思想的都是微内核架构。面试时或者其他场景，你完全可以大胆的去说。
  <br></span></p>]]></description><guid isPermaLink="false">/archives/curdxi-tong-zen-yang-zuo-chu-ji-shu-han-liang-wei-nei-he-gai-zao</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:07:38 GMT</pubDate></item><item><title><![CDATA[AKF扩展立方体和AKF可用性立方体]]></title><link>https://wiki.dgfygk.com/archives/akfkuo-zhan-li-fang-ti-he-akfke-yong-xing-li-fang-ti</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=AKF%E6%89%A9%E5%B1%95%E7%AB%8B%E6%96%B9%E4%BD%93%E5%92%8CAKF%E5%8F%AF%E7%94%A8%E6%80%A7%E7%AB%8B%E6%96%B9%E4%BD%93&amp;url=/archives/akfkuo-zhan-li-fang-ti-he-akfke-yong-xing-li-fang-ti" width="1" height="1" alt="" style="opacity:0;">
<p style="">很多人知道AKF扩展立方体是从《架构即未来》这本书开始。实际上akfpartners官方写过4篇关于AKF扩展立方体的文章，还有一篇介绍AKF可用性立方体。akfpartners官方在高可用、扩展性方面有很多专业技术文章，建议有空就翻翻看。</p>
<p style="">AKF扩展立方体和AKF可用性立方体都介绍了X轴、Y轴和Z轴。X轴核心是复制，Y轴核心是按业务拆分；Z轴核心是按算法拆分。AKF扩展立方体解决的问题是提供无限扩展性的解决方案；AKF可用性立方体解决的问题是提供无限高可用的解决方案。</p>
<p style="">AKF扩展立方体</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ocpl.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">X轴扩展是通过克隆和复制的方法来扩展。对应用来说，复制服务、负载均衡、浏览器存储session、对象缓存都是X轴扩展的例子；对数据库来说，数据库主从读写分离是典型的例子。</p>
<p style="">Y轴扩展是按业务来拆分，微服务就是一种Y轴扩展方式，DDD为扩展提供方式方法。扩展的大敌是瓶颈。所以微服务系统都要求将不同服务的数据库分开，来增强扩展性。</p>
<p style="">Z轴扩展是按算法规则扩展。对应用来说，单元化架构就是一种Z轴扩展方式；对数据库来说，分库分表就是一种Z轴扩展方式。</p>
<p style="">官方对三轴拆分的本质描述有一张图，清楚明白：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uqyh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">AKF可用性立方体</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-itnq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">AKF可用性立方体</p>
<p style="">X轴扩展可以大幅提高可用性。假如一个服务本身的可用性是4个9，根据&nbsp;（总时间-不可用时间）/总时间 的可用性公式，可用性可被提高到下面这么多9:</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-gjxa.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">Y轴扩展对可用性是起副作用的。因为业务拆分之后，模块之间的跨系统交互会增加，增加通信失败的风险。服务、数据扇出和服务、数据熔断是典型的反模式。服务扇出是说一个服务是个中心节点，和很多服务有调用关系；数据扇出是要得到数据需要从多处获取数据，也是一个中心。这样，一旦这个服务发生故障，和其有调用关系的都受到影响，被熔断。这些可以通过额外的处理：限流、降级等进行规避。</p>
<p style="">Z轴扩展可减小故障辐射半径，提高可用性。这种可用性手段又被称为舱壁模式或者泳道。被称为永不沉没的泰坦尼克，之所以叫永不沉没就是使用了舱壁，使用了16个舱壁，就算4个进水都不会有问题。设计上没有问题，据说是使用外包做了豆腐渣工程，导致了悲剧。</p>]]></description><guid isPermaLink="false">/archives/akfkuo-zhan-li-fang-ti-he-akfke-yong-xing-li-fang-ti</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:04:45 GMT</pubDate></item><item><title><![CDATA[怎样建设高性能、低延迟的系统？]]></title><link>https://wiki.dgfygk.com/archives/zen-yang-jian-she-gao-xing-neng-di-yan-chi-de-xi-tong</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%80%8E%E6%A0%B7%E5%BB%BA%E8%AE%BE%E9%AB%98%E6%80%A7%E8%83%BD%E3%80%81%E4%BD%8E%E5%BB%B6%E8%BF%9F%E7%9A%84%E7%B3%BB%E7%BB%9F%EF%BC%9F&amp;url=/archives/zen-yang-jian-she-gao-xing-neng-di-yan-chi-de-xi-tong" width="1" height="1" alt="" style="opacity:0;">
<p style="">实现高性能和低延迟系统涉及多个方面，包括硬件、软件、系统架构和算法优化等。以下是一些通用的方法：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-isut.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<ol>
 <li>
  <p style="">硬件优化</p>
 </li>
</ol>
<ul>
 <li>
  <p style=""><span fontsize="" color="">CPU</span>：选择具有高主频、多核心和多线程的CPU，以支持高并发的处理需求。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">内存</span>：使用高速的RAM（如DDR4或更高版本的内存），并确保足够的内存容量以支持系统的运行。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">存储</span>：使用低延迟的存储设备，如SSD（固态硬盘）替代传统的HDD（机械硬盘）。对于特别需要高性能的场景，可以使用NVMe SSD。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">网络</span>：选择高速和低延迟的网络设备，如使用千兆或万兆网卡，以及低延迟的交换机和路由器。</p>
 </li>
</ul>
<h3 style="" id="2.-%E8%BD%AF%E4%BB%B6%E4%BC%98%E5%8C%96">2. 软件优化</h3>
<h4 style="" id="%E4%BB%A3%E7%A0%81%E4%BC%98%E5%8C%96">代码优化</h4>
<ul>
 <li>
  <p style=""><span fontsize="" color="">高效算法</span>：选择时间复杂度和空间复杂度较低的算法。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">减少不必要的计算</span>：避免不必要的计算，例如缓存计算结果，避免重复计算。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">编程语言和模型</span>：选择高性能的编程语言（如C、C++或Go）和编程模型（如并发编程模型）。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">代码审查</span>：定期进行代码审查，确保代码的高效性和正确性。</p>
 </li>
</ul>
<h4 style="" id="%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E4%BC%98%E5%8C%96">数据处理优化</h4>
<ul>
 <li>
  <p style=""><span fontsize="" color="">数据库优化</span>：使用索引、分区、缓存等技术优化数据库查询性能。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">IO优化</span>：使用异步IO、批量IO等技术减少IO操作的延迟。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">数据结构</span>：选择适合的数据结构，如哈希表、红黑树等，以支持高效的查询和更新操作。</p>
 </li>
</ul>
<h4 style="" id="%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6">并发控制</h4>
<ul>
 <li>
  <p style=""><span fontsize="" color="">选择合适的并发模型</span>：如多线程、异步编程、事件驱动编程等。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">锁优化</span>：避免频繁的锁竞争，使用无锁数据结构或优化锁的使用方式。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">线程池</span>：使用线程池来管理和复用线程，减少线程的创建和销毁开销。</p>
 </li>
</ul>
<h3 style="" id="3.-%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E4%BC%98%E5%8C%96">3. 系统架构优化</h3>
<ul>
 <li>
  <p style=""><span fontsize="" color="">微服务架构</span>：将系统拆分为多个小的、独立的服务，每个服务专注于一个特定的功能，提高系统的可扩展性和可维护性。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">缓存</span>：使用缓存技术（如Redis、Memcached等）来减少对数据库的访问压力，提高系统的响应速度。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">负载均衡</span>：使用负载均衡技术来分发请求，避免单点故障和性能瓶颈。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">消息队列</span>：使用消息队列（如Kafka、RabbitMQ等）来解耦系统组件，提高系统的并发处理能力和稳定性。</p>
 </li>
</ul>
<h3 style="" id="4.-%E7%AE%97%E6%B3%95%E4%BC%98%E5%8C%96">4. 算法优化</h3>
<ul>
 <li>
  <p style=""><span fontsize="" color="">选择适当的算法</span>：根据问题的特点选择合适的算法，如排序、搜索、图算法等。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">优化算法参数</span>：调整算法参数以获取最佳性能。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">并行和分布式计算</span>：利用多核CPU、GPU或分布式计算资源来加速算法的执行。</p>
 </li>
</ul>
<h3 style="" id="5.-%E5%85%B6%E4%BB%96%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5">5. 其他优化策略</h3>
<ul>
 <li>
  <p style=""><span fontsize="" color="">性能监控和调优</span>：使用性能监控工具（如JProfiler、VisualVM等）来识别系统的性能瓶颈，并进行针对性的优化。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">预测和优化</span>：利用机器学习技术预测系统的负载和性能，提前进行资源分配和优化。</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="">测试</span>：进行充分的测试，包括单元测试、集成测试、性能测试等，确保系统的稳定性和性能。</p>
  <p style=""></p>
 </li>
</ul>
<p style="">综上所述，实现高性能和低延迟系统需要从多个方面进行综合优化。这些优化策略需要根据具体的应用场景和需求进行调整和完善。</p>]]></description><guid isPermaLink="false">/archives/zen-yang-jian-she-gao-xing-neng-di-yan-chi-de-xi-tong</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:03:50 GMT</pubDate></item><item><title><![CDATA[微服务架构中的拆分粒度决策]]></title><link>https://wiki.dgfygk.com/archives/wei-fu-wu-jia-gou-zhong-de-chai-fen-li-du-jue-ce</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%AD%E7%9A%84%E6%8B%86%E5%88%86%E7%B2%92%E5%BA%A6%E5%86%B3%E7%AD%96&amp;url=/archives/wei-fu-wu-jia-gou-zhong-de-chai-fen-li-du-jue-ce" width="1" height="1" alt="" style="opacity:0;">
<p style="">大家好！今天我来和大家分享一下微服务架构中的拆分粒度决策问题，希望能帮助大家更好地理解和应用微服务架构！</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cklu.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<h3 style="" id="%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF"><strong><span style="font-size: 20px">问题背景</span></strong></h3>
<p style="">在设计和实施微服务架构时，拆分粒度的决策非常重要。拆分得太细，会增加系统间通信和部署的复杂性；拆分得太大，会失去微服务的灵活性和独立性。所以，我们需要考虑哪些因素来确定拆分粒度呢？</p>
<h3 style="" id="%E9%80%9A%E7%94%A8%E7%BB%B4%E5%BA%A6"><strong><span style="font-size: 20px">通用维度</span></strong></h3>
<ul>
 <li>
  <p style="">业务边界：</p>
  <ul>
   <li>
    <p style="">将相关的业务功能放在一个微服务中，可以更好地实现独立开发、部署和维护。通过分析业务流程、功能依赖关系和数据模型，可以确定微服务的拆分粒度。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">团队组织：</p>
  <ul>
   <li>
    <p style="">将具有相似技术栈和业务背景的团队分配到相应的微服务中，可以提高开发效率和合作效果。拆分的粒度应该符合团队的能力和专长，避免出现开发难度过大或者重复开发的情况。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">性能需求：</p>
  <ul>
   <li>
    <p style="">根据系统的负载情况和响应时间要求，合理划分服务边界，将高负载的服务进行水平扩展，保证系统的性能和可用性。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">灵活性和耦合度：</p>
  <ul>
   <li>
    <p style="">微服务的设计应该遵循单一职责原则，每个微服务只负责一个特定的业务功能，避免功能耦合和依赖关系过于复杂。通过合理划分服务边界，可以实现微服务的独立变更和部署，提高系统的敏捷性和可维护性。</p>
    <p style=""></p>
   </li>
  </ul>
 </li>
</ul>
<h3 style="" id="%E6%88%90%E6%9C%AC%E7%BB%B4%E5%BA%A6"><strong><span style="font-size: 20px">成本维度</span></strong></h3>
<ul>
 <li>
  <p style="">资源占用：</p>
  <ul>
   <li>
    <p style="">如果使用虚拟机，成本高，因此可能会倾向于更粗的粒度以减少资源消耗。</p>
   </li>
   <li>
    <p style="">如果容器化做得好，资源利用率高，可以考虑更细的粒度。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">人力情况：</p>
  <ul>
   <li>
    <p style="">多人维护一个服务可能导致沟通成本上升。</p>
   </li>
   <li>
    <p style="">一人维护多个服务可能增加开发压力，降低效率。</p>
   </li>
   <li>
    <p style="">理想情况下，一个中级工程师维护一到两个服务可能是一个平衡点。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">变更成本：</p>
  <ul>
   <li>
    <p style="">如果两个服务经常一起变更，将它们分开可能会增加不必要的成本。</p>
    <p style=""></p>
   </li>
  </ul>
 </li>
</ul>
<h3 style="" id="%E8%B4%A8%E9%87%8F%E7%BB%B4%E5%BA%A6"><strong><span style="font-size: 20px">质量维度</span></strong></h3>
<ul>
 <li>
  <p style="">服务治理基础设施：</p>
  <ul>
   <li>
    <p style="">如果基础设施完善，如提供标准的RPC调用、链路追踪、监控等，可以支持更细的粒度。</p>
   </li>
   <li>
    <p style="">如果基础设施不足，更粗的粒度可能更有利于保证整体服务质量。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">性能消耗：</p>
  <ul>
   <li>
    <p style="">拆分服务会增加通信成本，需要评估这个成本是否可接受，并与预期收益相权衡。</p>
   </li>
   <li>
    <p style="">考虑是否有技术手段可以降低通信成本，例如使用高效的通信协议或中间件。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">部署速度：</p>
  <ul>
   <li>
    <p style="">太粗或太细的粒度都可能影响部署速度。需要找到一个平衡点，使得部署既快速又可靠。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">应用规模和业务量：</p>
  <ul>
   <li>
    <p style="">项目初期，建议采用较粗的粒度，随着项目的演进和业务量的增长，可以逐渐细化粒度。</p>
   </li>
   <li>
    <p style="">业务量达到一定程度后，可能需要拆分得很细以应对稳定性、复杂性等挑战。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">高可用要求：</p>
  <ul>
   <li>
    <p style="">根据不同服务的高可用要求，可能需要拆分成不同的服务以满足不同的部署策略，如核心应用的三地六机房部署，非核心应用的双机房部署。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style=""></p>
 </li>
</ul>
<p style="">综上所述，微服务的拆分粒度是一个权衡各种因素的决策过程。不同的项目、不同的阶段、不同的业务需求都可能影响这个决策。因此，在实际操作中，需要根据具体情况灵活调整和优化。</p>
<p style="">PS:本文是编程一生之前的文章节选，发送到大模型润色而成，所以准确的说：作者是AI，只可惜文生图的功能还不能满足要求，配图还要靠自己。</p>]]></description><guid isPermaLink="false">/archives/wei-fu-wu-jia-gou-zhong-de-chai-fen-li-du-jue-ce</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Mon, 23 Sep 2024 01:03:01 GMT</pubDate></item><item><title><![CDATA[深入探索：软件架构模式的精华总结]]></title><link>https://wiki.dgfygk.com/archives/shen-ru-tan-suo-ruan-jian-jia-gou-mo-shi-de-jing-hua-zong-jie</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%B7%B1%E5%85%A5%E6%8E%A2%E7%B4%A2%EF%BC%9A%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F%E7%9A%84%E7%B2%BE%E5%8D%8E%E6%80%BB%E7%BB%93&amp;url=/archives/shen-ru-tan-suo-ruan-jian-jia-gou-mo-shi-de-jing-hua-zong-jie" width="1" height="1" alt="" style="opacity:0;">
<p style=""><span style="font-size: 16px">Mark Richards写了一个50多页的小册子叫《软件架构模式》，里面介绍了五种架构：分层架构、事件驱动架构、微服务架构、微内核架构和基于空间的架构。其他的架构风格，我在之前的文章中都介绍过，简单说一下：基于空间的架构。</span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 0, 0)">基于空间的架构模式</span></strong></p>
<h1 style="" id="%E5%9F%BA%E4%BA%8E%E7%A9%BA%E9%97%B4%E7%9A%84%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F%E4%B9%9F%E5%8F%AB%E4%BA%91%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F(cloud-architecture-pattern)%EF%BC%8C%E6%98%AF%E8%AE%BE%E8%AE%A1%E7%94%A8%E6%9D%A5%E8%A7%A3%E5%86%B3%E6%89%A9%E5%B1%95%E6%80%A7%E5%92%8C%E5%B9%B6%E5%8F%91%E6%80%A7%E9%97%AE%E9%A2%98%E7%9A%84%E3%80%82%E4%BC%A0%E7%BB%9F%E6%9E%B6%E6%9E%84%E9%95%BF%E6%88%90%E4%B8%8B%E9%9D%A2%E8%BF%99%E6%A0%B7%EF%BC%8C%E6%8B%93%E6%89%91%E7%BB%93%E6%9E%84%E4%BB%8D%E5%91%88%E4%B8%89%E8%A7%92%E5%BD%A2%E5%BD%A2%E6%80%81%EF%BC%8C%E5%85%B6%E4%B8%AD%E6%9C%80%E5%AE%BD%E9%83%A8%E5%88%86%E4%B8%BAweb%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E6%9C%80%E5%AE%B9%E6%98%93%E8%BF%9B%E8%A1%8C%E8%A7%84%E6%A8%A1%E5%8C%96%EF%BC%8C%E6%97%A0%E7%8A%B6%E6%80%81%E6%9C%8D%E5%8A%A1%E6%B0%B4%E5%B9%B3%E6%89%A9%E5%AE%B9%E5%8D%B3%E5%8F%AF%EF%BC%89%EF%BC%8C%E8%80%8C%E6%9C%80%E7%AA%84%E9%83%A8%E5%88%86%E5%88%99%E4%B8%BA%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%88%E6%9C%80%E9%9A%BE%E8%BF%9B%E8%A1%8C%E8%A7%84%E6%A8%A1%E5%8C%96%EF%BC%89%E3%80%82">基于<span fontsize="" color="">空间的架构模式也叫云架构模式(cloud architecture pattern)，是设计用来解决扩展性和并发性问题的。传统架构长成下面这样，拓扑结构仍呈三角形形态，其中最宽部分为web服务器（最容易进行规模化，无状态服务水平扩容即可），而最窄部分则为数据库（最难进行规模化）。</span></h1>
<h1 style="text-align: center; " id=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wdvj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></h1>
<p style=""><span style="font-size: var(--articleFontsize)px">基于空间的架构风格通过将数据库从系统的事务处理中剥离，以解决应用程序扩展性的限制。因此，该架构被称为空间型架构。这个风格得名于计算机科学术语元组空间，即具有共享内存的多个并行处理器概念。通过使用在事务处理期间复制到内存中的数据网格来替代数据库，实现了高度可扩展性。应用程序数据保存在内存中，并在所有活动处理单元之间进行复制，并使用数据泵异步与后台数据库同步。随着用户负载增加和减少，可以动态启动和关闭处理单元，从而实现可变可伸缩性。由于系统的事务处理过程中没有涉及数据库，因此消除了数据库瓶颈，在应用程序内提供近乎无限的可扩展性。</span></p>
<h1 style="" id="-1"></h1>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vcnf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">听上去是解决高并发问题的利器。但是实际项目中使用的却很少，为什么呢？由于所有事务数据都存储在内存中，因此整体数据大小成为空间型架构的限制因素。试想一下将45TB的关系数据库放入内存中！鉴于该架构风格技术上的复杂性，如果预算紧张且时间有限，则不宜选择该架构。此外，在测试环境中实现非常高负载用户既昂贵又耗时，这使得难以测试应用程序在可扩展性方面表现如何。因此，该架构风格相对整体敏捷性（快速响应变化能力）较低。由于空间型架构始终是最终一致性的，在内存数据网格更新达到数据库之前可能需要很长时间。因此，对于那些需要系统之间高水平数据一致性的系统来说，并不适合考虑使用空间型架构。</p>
<p style=""><span fontsize="" color="">基于空间的架构模式</span>总结一下就是：</p>
<p style=""><span fontsize="" color="">核心思想：内存数据库、处理器与存储共享内存、异步持久化</span></p>
<p style=""><span fontsize="" color="">优势：性能高、扩展性好</span></p>
<p style=""><span fontsize="" color="">劣势：贵、复杂、响应变化能力差、数据一致性差(这些劣势排除了实际中95%以上的使用场景，所以简单了解即可)</span></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">更完整的分类方式</span></strong><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">咱们实际常使用的架构模式不止这五种。《金字塔原理》里提出，当大脑要处理的项目超过4个或5个时就要对它进行归类分组。今天咱们尝试将架构视角从整体到部分来归类，分成：宏观架构、局部架构、应用结构架构和用户界面架构。</span></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">宏观架构</span></strong><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">宏观架构一般应用于大规模企业的整体技术架构战略，一般建立在服务化基础之上。常见的架构模式有：中台架构、去中台化架构、Serverless 架构和微服务架构。</span></p>
<p style=""><strong><span fontsize="" color="rgb(255, 0, 0)" style="color: rgb(255, 0, 0)">局部架构</span></strong><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">局部架构一般应用于一个业务线或者一个团队，一般建立在宏观架构之下，有可能直接按照宏观架构来设计，或者遵从以下架构模式：单体架构、分层架构、事件驱动架构、面向空间架构、C/S架构、B/S架构、主从架构、面向服务架构和点对点架构。</span><span style="font-size: 16px">其他的架构风格，我在之前的文章中都介绍过，简单说一下：</span><span fontsize="" color="">点对点架构</span>。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 0, 0)">点对点架构</span></strong></p>
<p style="">点对点模式，即Peer-to-Peer模式，在这种模式中，单个组件被称为对等点。对等点可以作为客户端，从其他对等点请求服务，也可以作为服务器，为其他对等点提供服务。对等点可以充当客户端或服务器的角色，并<span style="font-size: var(--articleFontsize)px">且可以随时间动态地更改其角色。每个节点既可以从其他节点得到服务，也可以向其他节点提供服务。</span></p>
<p style="">有3种比较流行的组织结构，(1)DHT结构，这种结构多用于文件共享和作为底层结构用于流媒体传输。(2)树形结构，最初的树形结构多用于P2P流媒体直播。(3)网状结构，又叫无结构，为P2P提供了最大的容忍性、动态适应性，在流媒体直播和点播应用中<span style="font-size: var(--articleFontsize)px">取得了极大的成功。</span></p>
<p style="">点对点 (P2P) 架构有多种用途，包括：</p>
<ul>
 <li>
  <p style="">文件共享：P2P网络通常用于文件共享，因为它们允许用户直接相互共享文件，而不需要中央服务器。</p>
 </li>
 <li>
  <p style="">即时通讯：P2P网络可用于在用户之间实现即时通讯，使他们能够进行实时通信。</p>
 </li>
 <li>
  <p style="">区块链：例如比特币是一种 P2P 加密货币，无需中央货币当局即可运行。交易在去中心化的计算机网络上进行验证和记录，而不是集中管理。</p>
 </li>
</ul>
<p style="">
 <br>
 <strong><span style="font-size: 18px; color: rgb(255, 0, 0)">应用结构架构</span></strong>
</p>
<p style="">应用结构架构一般应用于单个可执行程序。<span fontsize="" color="">常见的架构模式有：微内核架构、CQRS命令查询责任隔离、事件溯源、管道过滤器架构、还有和用户界面相关的</span><span style="font-size: var(--articleFontsize)px">MVC、MVP、MVVM架构。</span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 0, 0)">总结</span></strong></p>
<p style="">这些架构的视角并不相同，所以会产生多种架构一起使用，并不冲突。另外，请特别注意：“<span fontsize="" color="">架构一般应用于”&nbsp;这几个字，实际上所谓架构模式，并没有那么模式化，更类似于架构风格，可以灵活应用。比如微内核架构，又叫插件化架构，是按照功能拆分，有一个主流程，其他功能做成插件来做插拔。这些插件可以是集成于一个可执行应用，比如Intelij的插件、谷歌浏览器的插件，也可以做成服务化插件独立部署，应用于局部架构。如果有人聊天的时候硬要给微内核架构上纲上线，说什么服务要重启的就不是微内核架构、独立部署的就不是微内核架构，完全可以怼回去：微内核是一种思想，核心是功能可插拔、可替换。怎么实现并不需要模式化的套路。</span></p>
<p style=""></p>
<p style="">预览时标签不可点</p>
<p style="">架构师28</p>
<p style="">架构师 · 目录</p>
<p style="">#架构师</p>
<p style="">上一篇在跨系统环境中实现数据一致性：关键步骤和策略下一篇微服务架构中的拆分粒度决策</p>]]></description><guid isPermaLink="false">/archives/shen-ru-tan-suo-ruan-jian-jia-gou-mo-shi-de-jing-hua-zong-jie</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 10:03:45 GMT</pubDate></item><item><title><![CDATA[在跨系统环境中实现数据一致性：关键步骤和策略]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9C%A8%E8%B7%A8%E7%B3%BB%E7%BB%9F%E7%8E%AF%E5%A2%83%E4%B8%AD%E5%AE%9E%E7%8E%B0%E6%95%B0%E6%8D%AE%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%9A%E5%85%B3%E9%94%AE%E6%AD%A5%E9%AA%A4%E5%92%8C%E7%AD%96%E7%95%A5&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style="line-height: 1.8">总结了五种大分类，供参考。其实个人觉得总结的不是很好，但调研业界的文章，也没有其他结构上、体系化上更有说服力的。先发出来给大家看看，日后改进的基础。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zywg.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: 1.8"><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">事务处理</span></strong></p>
<p style="line-height: 1.8">使用事务可以确保一组操作被视为一个单元，同时确保所有操作都成功或全部失败。如果其中一个操作失败，则整个事务将回滚并撤消所有已提交的更改。</p>
<p style="line-height: 1.8">事务可以分为ACID事务和分布式事务。分布式事务业界有分成两阶段提交2PC、三阶段提交3PC、TCC、可靠消息等手段。本质上我理解都是分阶段，前面阶段判断，判断可执行则实际进行执行，然后确认成功，不成功则通过取消、重试等手段尽最大努力补偿。</p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">锁定机制</span></strong></p>
<p style=""><span style="font-size: var(--articleFontsize)px">使用锁定机制可以防止多个用户同时对同一个记录进行修改，从而避免冲突和不一致性。在某些情况下，应该采用分布式锁定机制来协调不同系统之间的数据访问。</span></p>
<p style="line-height: 1.8"><span fontsize="" color="">分布式锁，相对单例模式下并发线程的锁，当需要控制多个实例上面的线程操作某个共享变量时，分布式锁显得尤为重要，当获取锁，才可以业务操作，否则等待。可以采用curator、redis等相关中间件，实现分布式锁。</span></p>
<p style="line-height: 1.8"><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">数据复制技术</span></strong></p>
<p style="line-height: 1.8">可以使用主从复制或双向复制等技术来确保源和目标系统之间的数据一致性。这种方法通常会选择其中一个节点作为主节点，并将更新写入到该节点上，在其他节点上进行读取。</p>
<p style="line-height: 1.8"><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">数据校验和补偿</span></strong></p>
<p style="">在进行跨系统或跨数据库间的数据同步时，应该对传输过程中的每个记录进行完整性<span style="font-size: var(--articleFontsize)px">检查和校验。如果发现错误，则应立即恢复原始状态并尝试重新发送。</span></p>
<p style="">经典的校验和补偿机制有：幂等、补单、信息流一致性比对、数据平衡性检查。</p>
<p style="line-height: 1.8"><span fontsize="" color="">幂等，就是当客户端发起功能重试时，N次操作，都跟第一次操作结果相同，例如查询就是天生的幂等；下单，如果客人重复提交下单，这时系统不能生成很多重复的订单，这就需要幂等的判断处理。</span></p>
<p style="">信息流一致性比对，举几个例子：内部对账和外部对账、上下游之间比对、和中间件比对、和异构数据比对(交易库和大数据采集的日志可以做大数据核对，定时核对交易笔数是否一致)</p>
<p style="">数据平衡性检查，常见的是资金流平衡性检查。比如支付金额-退款金额应该=结算金额，退款金额不能大于支付金额。</p>
<p style="line-height: 1.8"><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">可观测技术</span></strong></p>
<p style="line-height: 1.8">实时监控各个系统之间的数据同步状态，并记录所有操作以便后续分析故障原因。可以使用监控工具或者自定义脚本来实现这些功能。</p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:59:54 GMT</pubDate></item><item><title><![CDATA[红蓝攻防、红蓝对抗]]></title><link>https://wiki.dgfygk.com/archives/hong-lan-gong-fang-hong-lan-dui-kang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BA%A2%E8%93%9D%E6%94%BB%E9%98%B2%E3%80%81%E7%BA%A2%E8%93%9D%E5%AF%B9%E6%8A%97&amp;url=/archives/hong-lan-gong-fang-hong-lan-dui-kang" width="1" height="1" alt="" style="opacity:0;">
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-epjc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">在网络安全领域中，红队和蓝队是两个常用的术语，用来描述攻击者和防御者之间的对抗。红队代表攻击方，通过模拟真实的攻击手法来测试和评估系统的弱点，而蓝队代表防御方，负责检测和阻止潜在的攻击。</p>
<p style="">比如很多大公司每年都要参与护网行动。护网行动是以公安部牵头的，用以评估企事业单位的网络安全的活动。</p>
<p style="">守护好自身的系统和设施，极为重要。以下是红蓝对抗中守方的一些方法总结。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 0, 0)">针对所有员工</span></strong></p>
<ul>
 <li>
  <p style="">定期组织各种活动，加强安全意识，预防社工攻击。</p>
 </li>
 <li>
  <p style="">安装入侵防御系统IPS</p>
  <p style="">入侵防御系统(IPS: Intrusion Prevention System)是电脑网络安全设施，是对防病毒软件（Antivirus Programs）和防火墙(Packet Filter, Application Gateway)的补充。入侵防御系统(Intrusion-prevention system)是一部能够监视网络或网络设备的网络资料传输行为的计算机<span style="font-size: var(--articleFontsize)px">网络安全设备，能够及时的中断、调整或隔离一些不正常或是具有伤害性的网络资料传输行为。</span></p>
  <p style="">在ISO/OSI网络层次模型&nbsp;中，防火墙主要在第二到第四层起作用，它的作用在第四到第七层一般很微弱。而除病毒软件主要在第五到第七层起作用。为了弥补防火墙和除病毒软件二者在第四到第五层之间留下的空档。几年前，工业界已经有入侵侦查系统（IDS: Intrusion Detection System）投入使用。入侵侦查系统在发现异常情况后及时向网络安全管理人员或防火墙系统发出警报。可惜这时灾害往往已经形成。防卫机制最好应该是在危害形成之前先期起作用。随后应运而生的入侵响应系统(IRS: Intrusion Response Systems) 作为对入侵侦查系统的补充能够在发现入侵时，迅速作出反应，并自动采取阻止措施。而入侵预防系统则作为二者的进一步发展，汲取了二者的长处。</p>
  <p style=""></p>
 </li>
</ul>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 0, 0)">针对所有应用</span></strong></p>
<ul>
 <li>
  <p style="">漏洞修复：比如升级fastjson避免反序列化漏洞</p>
 </li>
 <li>
  <p style="">应用主机要安装HIDS</p>
  <p style="">HIDS全称是Host-based Intrusion Detection System，即基于主机型入侵检测系统。作为计算机系统的监视器和分析器，它并不作用于外部接口，而是专注于系统内部，监视系统全部或部分的动态的行为以及整个计算机系统的状态。HIDS运行依赖于这样一个原理：一个成功的入侵者一般而言都会留下他们入侵的痕迹。这样，计算机管理员就可以察觉到一些系统的修改，HIDS亦能检测并报告出检测结果。</p>
  <p style="">值得注意的是：HIDS是安装在应用主机上的，本身要占用一些资源，有可能影响应用本身的稳定性。要做好监控。</p>
 </li>
 <li>
  <p style=""><span style="font-size: var(--articleFontsize)px">严格控制和收敛使用的端口。</span></p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 0, 0)">针对边界系统</span></strong></p>
<ul>
 <li>
  <p style="">区域划分：对于专线和公网接入，有不同的物理隔离区域，相对应的安全策略也不同。外部流量先进隔离区，在此区域可以做流量的清洗，避免攻击直接渗透到内网，提升攻击成本。</p>
 </li>
 <li>
  <p style="">安装WAF</p>
  <p style="">Web应用防护系统（也称为：网站应用级入侵防御系统。英文：Web Application Firewall，简称：WAF）。Web应用防火墙会对HTTP的请求进行异常检测，拒绝不符合HTTP标准的请求。并且，它也可以只允许HTTP协议的部分选项通过，从而减少攻击的影响范围。甚至，一些Web应用防火墙还可以严格限定HTTP协议中那些过于松散或未被完全制定的选项。</p>
 </li>
 <li>
  <p style="">加强监控：排查所有状态码以4开头的请求确认是否为攻击请求。不是则通知运营与机构沟通整改，是的话立即隔离封禁</p>
 </li>
 <li>
  <p style="">程序上：定期梳理，加强校验，严格准入</p>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 0, 0)">其他说明</span></strong></p>
<p style="">实际沟通过程中，也要先了解清楚上下文。有时候别人说的红蓝攻防有可能指的是系统故障注入，不确定时可以先问清楚。十几年前还看到过两个人在那里聊的看上去很happy，但是两个人实际各说各的，并没有在一个维度。这个时代，这种事情不多见了，大家都专业了。实际上，很多公司首先考察一个人就是看他的沟通能力，所以会设置场景题。</p>]]></description><guid isPermaLink="false">/archives/hong-lan-gong-fang-hong-lan-dui-kang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:59:08 GMT</pubDate></item><item><title><![CDATA[思维训练-怎样设计一个MQ]]></title><link>https://wiki.dgfygk.com/archives/si-wei-xun-lian-zen-yang-she-ji-yi-ge-mq</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%80%9D%E7%BB%B4%E8%AE%AD%E7%BB%83-%E6%80%8E%E6%A0%B7%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AAMQ&amp;url=/archives/si-wei-xun-lian-zen-yang-she-ji-yi-ge-mq" width="1" height="1" alt="" style="opacity:0;">
<p style="">架构师需要做各种设计，要不断地提高自己的设计能力。这有没有方法可以训练呢？有的，就是看到什么、想到什么，就假设对面坐着产品经理，一起讨论怎么把它设计出来。比如怎样设计一个MQ</p>
<p style="">我：<span style="font-size: var(--articleFontsize)px">首先我确认一下需求。从功能性需求和非功能性需求两方面来看。先看功能性需求：要设计MQ，本身来说就要求：&nbsp;发送、接收和存储，存储是先进先出的。那还有其他的需求吗？比如我是只需要在程序逻辑中用JVM内实现，还是需要跨进程、跨平台？</span></p>
<p style="">产品经理：<span style="font-size: var(--articleFontsize)px">需要</span><span fontsize="" color="">跨进程、跨平台</span></p>
<p style="">我：那假设发送端是producer、接收端是consumer，存储端是broker。i那就需要解决broker与两端的通信问题。可以使用成熟的RPC框架，这样就不需要考虑 服务注册与发现、负载均衡和序列化方式的问题了。</p>
<p style=""><span style="font-size: var(--articleFontsize)px">先把最简单的架构图画出来：</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fjsv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">需要支持发布订阅吗？</p>
<p style="">产品经理：需要支持发布订阅</p>
<p style="">我：可以给发布的消息设置主题，将一类消息发布到同一个主题中，消费端来订阅相关主题的消息。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-zogv.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">再来考虑一下非功能需求。在高可用方面有什么要求吗？</p>
<p style="">产品经理：需要保证高可用</p>
<p style="">我：可以使用多副本模式增加复制因子(kafka MQ里有个主题复制因子的概念，其实就是副本数)来提高可用性，通过服务注册与发现、超时与重试、负载均衡、发送和消费时的ack来保证可用性。存储方面，对性能和可靠性有要求吗？</p>
<p style="">产品经理：有要求怎么办，没有要求怎么办呢？</p>
<p style="">我：如果不追求很高的性能可以使用数据库等方案。<span style="font-size: 16px; color: rgb(51, 51, 51)">可靠性要求不高的话直接用内存或者分布式缓存也可以。</span>追求性能的话，目前主流的方式是采用追加写日志顺序写盘+索引文件的方式。索引设计上可以考虑稀疏或者稠密索引（稠密索引是所有的数据条目都能通过索引找到，稀疏索引是数据分段出现在索引中，通过索引可以找到第一个数据的地址，再通过第一个数据找到想要的数据）。查找消息可以采用跳表或者二分查找等。还可以通过操作系统的页缓存和零拷贝等技术来提高操作系统的读写性能。</p>
<p style="">对吞吐量有什么要求？</p>
<p style="">产品经理：需要每秒处理500M的消息</p>
<p style="">我：kafka单节点处理能力在每秒100M左右，那就需要建立多个分区来进行水平扩展。如果没有特殊要求的话，生产端到分区，分区到消费端都可以采用轮询的策略来进行分区分配。</p>
<p style="">产品经理：有的消息，需要保证它的顺序，这个需要处理一下。</p>
<p style="">我：可以在生产端到分区的分配策略上，如果指定了分区就发到指定分区，如果指定了key可以按照key来进行hash分配，如果这两个都没指定再按照轮询来分配。这样，指定了分区的和指定了key的都会发到一个分区中，在同一个分区中的消息是有序的。</p>
<p style="text-align: center; "><strong><span style="font-size: 18px">总结</span></strong></p>
<p style="">按照这种思维方式思考下去，MQ的原理都可以思考地很明白，同时也提高了设计能力。思考的过程中还会将具体的用词在脑子过一遍，有些不清楚可以通过搜索学习来让自己的用词更加专业，从而达到一个总体的技术提升。</p>]]></description><guid isPermaLink="false">/archives/si-wei-xun-lian-zen-yang-she-ji-yi-ge-mq</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:57:53 GMT</pubDate></item><item><title><![CDATA[DDD、SOA、微服务和微内核]]></title><link>https://wiki.dgfygk.com/archives/ddd-soa-wei-fu-wu-he-wei-nei-he</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=DDD%E3%80%81SOA%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%92%8C%E5%BE%AE%E5%86%85%E6%A0%B8&amp;url=/archives/ddd-soa-wei-fu-wu-he-wei-nei-he" width="1" height="1" alt="" style="opacity:0;">
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">DDD、SOA、微服务和微内核，看到经常有人把这几个概念拿出来一起讲。事实上，DDD和其他三个不是一个维度的东西。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">DDD其实特别好理解，DDD就是领域来驱动设计嘛，是一种设计思想。很容易又和OOA、OOD和OOP来比较了。这个回头再说。
  <br></span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">SOA、微服务和微内核都是架构风格，DDD里能和他们三个放在一起比较的是四层架构和六边形架构。
  <br></span></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(0, 0, 0)">四层架构</span></strong><span style="font-size: 18px; color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">四层架构长这样：</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mhda.png&amp;size=m" width="587px" height="691px" style="display: inline-block"></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">分为用户接口层、应用层、领域层和基础层，四层架构目的是为了解耦，下层不依赖上层，从依赖关系上讲，四层架构的箭头是反过来的。</span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">目前这个架构，在现代系统中，通常用作项目工程模块的设计。就是说更传统的MVC逐渐被淘汰，目前主流就是这种四层架构。有的项目工程会先用限界上下文划分子域，再用四层架构。然后代码结构长这样：</span></p>
<pre><code>├─interfaces&nbsp;&nbsp;&nbsp;API接口层│&nbsp;  ├─dto &nbsp;&nbsp;&nbsp;&nbsp;视图模型,数据模型定义 vo/dto（大多数情況是一样的）│&nbsp;&nbsp;&nbsp;└─controller&nbsp;&nbsp;&nbsp;控制器，对外提供（Restful）接口│&nbsp;├─application&nbsp;&nbsp;&nbsp;应用层│&nbsp;&nbsp;&nbsp;├─service&nbsp;&nbsp;应用服务，非核心服务&nbsp;│&nbsp;&nbsp;&nbsp;└─***&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;others│&nbsp;├─domain&nbsp;&nbsp;&nbsp;领域层│&nbsp;&nbsp;&nbsp;├─entity&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;领域实体、聚合根，充血的领域模型&nbsp;│&nbsp;&nbsp;&nbsp;├─valueobject&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;领域值对象&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;└─service&nbsp;&nbsp;领域服务类，一些不能归属某个具体领域模型的行为│&nbsp;├─infrastructure&nbsp;&nbsp;基础设施层│&nbsp;&nbsp;&nbsp;├─po&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;持久化对象&nbsp;│&nbsp;&nbsp;&nbsp;├─repository&nbsp;仓储类，持久化接口&amp;实现，可与ORM映射框架结合│&nbsp;&nbsp;&nbsp;├─dao&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数据访问对象&nbsp;│&nbsp;&nbsp;&nbsp;├─client&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;feign等调用客户端&nbsp;└─&nbsp;&nbsp;└─factory&nbsp;&nbsp;&nbsp;&nbsp;工厂类，管理仓库，封装细节</code></pre>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">底层实体采用充血模型，有一些聚合，形成聚合根。与存储层的交互封装在仓库中，用DDD工厂来管理仓库。上层会将实体转换成贫血模型再暴露出去。你们项目的结构，架构师是不是这样定义的？</span></p>
<p style="text-align: center; "><strong><span style="font-size: 18px">六边形架构</span></strong></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">六边形架构很多文章作者把这个架构分成几层，这些都是加上了作者个人的理解。实际上六边形架构又叫洋葱形架构很简单，就是个端口适配器模式。外六边形是技术域，内六边形是业务域。这个常用于一些整体架构中。比如说基于一些基础组件做二次开发。真正的业务实现是组件实现的，有一些专门的维护人员。但是团队中往往会有更多成员在做封装接口暴露给公司内的用户，做高可用或者自动化运维这些通常所说的外层。这个外层就是六边形架构里技术域解决的问题。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mwqj.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(0, 0, 0)">SOA和微服务架构</span></strong><span style="font-size: 18px; color: rgb(0, 0, 0)">
  <br>
 </span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">SOA的出现是为了解决功能复用的问题，将一些共通的模块提取出来做成服务。这个我之前在开发中发现不好用，仅次于单体应用时各个模块引入一个common的jar包。因为这些共通的模块很可能适应不了各方面的需求，上层的需求总有一些差异。这样的情况下，这个共通模块就不太好维护了。
  <br></span></p>
<p style=""><span fontsize="" color="rgb(0, 0, 0)" style="color: rgb(0, 0, 0)">服务化的概念使得上世纪就已经产生的DDD得到了大家的关注。因为DDD解决了怎么服务化的问题，就是划分领域。微服务就是服务化之后划分粒度的问题了。
  <br></span></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(0, 0, 0)">微内核架构</span></strong></p>
<p style="">微内核架构又叫插件化架构，包含两类组件：核心系统（core system）和插件模块（plug-in modules）。核心系统负责和业务无关的通用功能，例如模块加载、模块间通信等；插件模块负责实现具体的业务逻辑，例如“学生信息管理”系统中的“手机号注册”功能。</p>
<p style="">核心系统（Core System）功能一般比较稳定，不会由于业务扩展而不断修改，插件模块需要根据业务功能的发展不断地扩展。微内核的架构本质是将变化部分封装在插件里，从而实现快速灵活扩展的目标，而同时又不影响整体系统的稳定。常见的例子有：Eclipse IDE、Spring、Dubbo。</p>
<p style="">我做过一个配置化项目。把业务流程划分成模块，会不断有新业务接入进来，很多模块都可以复用。比如业务A需要a\b\c三个模块，业务B需要a\c\d\e四个模块，那新业务进来只需要进行模块流程的配置。如果需要的模块没有，那就以插件的形式集成进来再进行配置。我们称这个服务采用的是微内核架构，目的就是要让大家把新功能的开发做插件式开发，避免对旧功能的影响，提高系统的稳定性。</p>]]></description><guid isPermaLink="false">/archives/ddd-soa-wei-fu-wu-he-wei-nei-he</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:55:57 GMT</pubDate></item><item><title><![CDATA[系统设计的11个考虑]]></title><link>https://wiki.dgfygk.com/archives/xi-tong-she-ji-de-11ge-kao-lu</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1%E7%9A%8411%E4%B8%AA%E8%80%83%E8%99%91&amp;url=/archives/xi-tong-she-ji-de-11ge-kao-lu" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="%E6%9C%89%E4%BA%86%E3%80%8A%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E7%9A%8411%E6%9D%A1%E5%8E%9F%E5%88%99%E3%80%8B%EF%BC%8C%E7%9C%9F%E6%AD%A3%E5%88%B0%E8%AE%BE%E8%AE%A1%E9%98%B6%E6%AE%B5%E8%BF%98%E6%9C%89%E5%8F%A6%E5%A4%9611%E4%B8%AA%E8%80%83%E8%99%91%E3%80%82"><span style="font-size: 17px">有了《</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247487034&amp;idx=1&amp;sn=76192e552b7a3ff6efcfc150e6becf2c&amp;chksm=fafde494cd8a6d82abed8d8372e667009cf1de7a2de54913f0c0c81ad7036478e0b9b20dd9fb&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">系统架构的11条原则</span></u></strong></a><span style="font-size: 17px">》，真正到设计阶段还有另外11个考虑。</span></h1>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">系统正确性</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑一：负负得正</span></strong></p>
<p style=""></p>
<p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">假如我们看到某个代码，明显有逻辑错误，想随手改改。你就要考虑一件事情：这段明显有问题的代码为什么在线上运行着没有人来报bug？有一种正常运行叫做【</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485646&amp;idx=1&amp;sn=865d300bd03db4ea45f5ed743a6cbb60&amp;chksm=fafde260cd8a6b7686cbfd5464401d5a834398a78d56926d2856672aab3fa60d99406412d99e&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">负负得正</span></u></strong></a><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">】。如果把错误的逻辑改对了反而可能引起问题。</span></p>
<p style=""></p>
<p style="">这种问题要避免最好的时机是初版设计和开发阶段就避免。除了设计阶段逻辑要清晰，代码要做好审查、加上单体测试等测试手段外，可以将中间结果用debug日志打印。建议自测阶段多用debug级别日志跑几遍，进行观察。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑二：终态设计</span></strong></p>
<p style=""></p>
<p style="">在分布式系统中，由于系统是分布在不同机器上的。还可能有一种状态叫：超时。成功、失败和超时是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484510&amp;idx=1&amp;sn=dbe4c622a7ef5d667f675420d5e4464c&amp;chksm=fafdeef0cd8a67e64981d80c89c2c0a8fcb36177e5e19dcde180bc1e9718b577dce4d5d2d937&amp;scene=21#wechat_redirect" target="_blank"><strong><u>分布式系统调用的三态</u></strong></a>。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iyop.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">超时不是终态，而是一种中间状态：最终有可能下游是成功了，也有可能是失败了。这时候我们需要在超时之后推定一种状态，推定成功或者失败。究竟是成功还是失败因功能而异。</p>
<p style=""></p>
<p style="">比如付款操作，需要推定失败。如果不知道是否成功就推定是成功的，那用户可能没有付款就拿到了商品或者享受了服务，商家就会资金损失。推定失败让用户再次支付，最终通过查询或者对账发现用户实际是支付成功的，可以再把钱给用户退回去，保证交易的公平性。</p>
<p style=""></p>
<p style="">退款恰恰相反，需要推定成功。告诉用户，钱退给你了。最终通过查询或者对账发现实际是退款失败了，可以系统重新发起退款，直到真正退成功为止。</p>
<p style=""></p>
<p style="">后台管理系统也很需要这种终态设计。比如发布系统，发布了一个功能，发布系统如果出现了问题，这次发布没有结束。用户可能没有办法进行下一次发布。这时候可以设置超时自动结束，防止未结束的流程始终在那里。</p>
<p style=""></p>
<p style=""><strong>考虑三：<span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">长尾效应</span></strong></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-eutf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">如上图，随便找了一个调用的耗时。从上面可以看到平均耗时13.9ms，百分之99的耗时在30ms内，最大耗时有488ms。那对于超过100ms的请求来说，是不是在30ms内还不返回就直接丢弃这个请求重新发起一个，新请求有99%的概率在30ms内返回结果，从时间上更划算？</p>
<p style=""></p>
<p style="">而之所以对同一个请求性能差距很大，原因很多，常见的有服务过载和队列过长。如果长尾处理不好，有可能上游判定超时，导致请求失败，影响正确性。需要系统处理好<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484362&amp;idx=1&amp;sn=741629424adc436b02af25e7a0667087&amp;chksm=fafde964cd8a6072aaf01d87d4391b9178d3cfac0eb88cc55590c8fb5be81d846fc4d70ef06e&amp;scene=21#wechat_redirect" target="_blank"><strong><u>超时和重试</u></strong></a>。</p>
<p style=""></p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">系统容量</span></strong></p>
<p style=""></p>
<p style=""><strong>考虑四：<span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">存储周期</span></strong></p>
<p style=""></p>
<p style="">数据库、应用系统的磁盘都是宝贵的资源。数据不能无限期存储不做清理。清理的周期是一个重要的考虑方面。因为这涉及对用户的承诺。</p>
<p style=""></p>
<p style="">对数据库来说，比如交易库数据半年清理一次。那就要跟用户说清楚半年以上的交易不允许退款。因为原交易已经不在数据库，而是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485331&amp;idx=1&amp;sn=afb38bdedcdd103fa7c800307a7344b1&amp;chksm=fafded3dcd8a642b1e92dd4ec673591c9e1b419c3b2a57b6fa9539c79839a39e733e24949933&amp;scene=21#wechat_redirect" target="_blank"><strong><u>归档</u></strong></a>到大数据了。</p>
<p style=""></p>
<p style="">对磁盘来说，如果应用日志是30天清理一次。那就要跟用户达成一致，非重大问题30天以上的不予追溯。为什么这里说重大问题呢，其实很多公司磁盘清理了，数据在hbase等大数据设备里还是有留存的。只是查询没有磁盘日志便利。</p>
<p style=""></p>
<h1 style="" id="%E8%80%83%E8%99%91%E4%BA%94%EF%BC%9Aakf%E6%89%A9%E5%B1%95"><strong>考虑五：<span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">AKF扩展</span></strong></h1>
<p style=""></p>
<h1 style="" id="akf%E6%89%A9%E5%B1%95%E7%AB%8B%E6%96%B9%E4%BD%93%EF%BC%88scalability-cube%EF%BC%89%EF%BC%8C%E6%98%AF%E3%80%8A%E6%9E%B6%E6%9E%84%E5%8D%B3%E6%9C%AA%E6%9D%A5%E3%80%8B%E4%B8%80%E4%B9%A6%E4%B8%AD%E6%8F%90%E5%87%BA%E7%9A%84%E5%8F%AF%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%EF%BC%8C%E8%BF%99%E4%B8%AA%E7%AB%8B%E6%96%B9%E4%BD%93%E6%9C%89%E4%B8%89%E4%B8%AA%E8%BD%B4%E7%BA%BF%EF%BC%8C%E6%AF%8F%E4%B8%AA%E8%BD%B4%E7%BA%BF%E6%8F%8F%E8%BF%B0%E6%89%A9%E5%B1%95%E6%80%A7%E7%9A%84%E4%B8%80%E4%B8%AA%E7%BB%B4%E5%BA%A6%EF%BC%9A"><strong>AKF扩展立方体（Scalability Cube）</strong><span style="font-size: 17px">，是《</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485553&amp;idx=1&amp;sn=945e46d81bef62f35ac447895d99cdd2&amp;chksm=fafde2dfcd8a6bc905cd0e9e50a9e51247a6d53f614a948b89209341a55877893a3fcba295dd&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">架构即未来</span></u></strong></a><span style="font-size: 17px">》一书中提出的可扩展模型，这个立方体有三个轴线，每个轴线描述扩展性的一个维度：</span></h1>
<p style="">X轴 —— 代表无差别的克隆服务和数据，工作可以很均匀的分散在不同的服务实例上；</p>
<p style="">Y轴 —— 关注应用中职责的划分，比如数据类型，交易执行类型的划分；</p>
<p style="">Z轴 —— 关注服务和数据的优先级划分，如分地域划分。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-iotn.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">白话来说：X轴拆分就是通过加机器水平拆分，就是横向扩展；Y轴拆分就是按业务逻辑垂直拆分；Z轴拆分就是按照算法进行分片，这个算法比如按地域，不同地域访问不同的分片或者服务。</p>
<p style=""></p>
<p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">举个例子，比如一般公司的</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485868&amp;idx=1&amp;sn=843459f44e37003814c7a9b44bf721ec&amp;chksm=fafde302cd8a6a142123716eef9228df519285d9d1b94b6a135fb701adf8cf826a570298f497&amp;scene=21#wechat_redirect" target="_blank"><strong><u>redis集群</u></strong></a><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">会有一个团队来进行统一维护。redis集群有主有从，数据都是一样的，多副本容灾，这是X轴水平的拆分。一个公司很多业务，redis团队会对不同的业务提供不同的集群，这是Y轴垂直拆分；集群内部数据会通过sharding做分片，这是Z轴算法拆分。</span></p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">系统运维</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑六：服务自治</span></strong></p>
<p style=""></p>
<p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">当一个服务的逻辑单元由自身的领域边界内所控制，不受</span><span fontsize="" color="">其他外界条件的影响（外界条件带有不可预测性），且运行环境是自身可控，完全自给自足，我们认为这个</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483946&amp;idx=1&amp;sn=d3090a80bc7f91b83cd374ac7ebcfd69&amp;chksm=fafde884cd8a61928be65568456a993fae4ef35304476fe6a87b047c1da3ce2e2e79458935e4&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">服务是自治</span></u></strong></a><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">的。</span></p>
<p style=""></p>
<p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">在系统设计时，要考虑服务上线后，对于问题要自感知、自修复、自优化、自运维及自安全。</span></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑七：应急预案</span></strong></p>
<p style=""></p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485806&amp;idx=1&amp;sn=267b1a6980afc82695441072a2d9af60&amp;chksm=fafde3c0cd8a6ad62a541e3a62fda77234ea15c683c1acc9115731cf0c5643baa2de10b1adaa&amp;scene=21#wechat_redirect" target="_blank"><strong><u>SOP</u></strong></a>（Standard Operating Procedure三个单词中首字母的大写 ）即标准作业程序，就是将某一事件的标准操作步骤和要求以统一的格式描述出来，用来指导和规范日常的工作。</p>
<p style=""></p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486431&amp;idx=1&amp;sn=c6bb35981521d01846c7a02517f5d281&amp;chksm=fafde171cd8a68677f5ead2228b27d009ff60746adf77861ec63e951fed3e0ba7cbb665a64f9&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">EOP</span></u></strong></a><span fontsize="" color="">（Emergency Operating Procedure三个单词中首字母的大写 ）即应急操作流程，用于规范应急操作过程中的流程及操作步骤。确保人员可以迅速启动，确保有序、有效</span>地<span fontsize="" color="">组织实施各项应对措施。</span></p>
<p style=""></p>
<p style="">这两种操作流程需要平时就整理好，避免紧急情况下思虑不周导致操作时的二次故障。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑八：故障隔离</span></strong></p>
<p style=""></p>
<p style="">隔离是行之有效的故障规避措施。有以下隔离手段。</p>
<p style=""></p>
<ul>
 <li>
  <p style="">领域拆分隔离方面</p>
  <ul>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486632&amp;idx=1&amp;sn=7b443147ff6b4d10ab0c09ee6e1e1d73&amp;chksm=fafde606cd8a6f109870d9ad2c47cc42497de5a0f18d84bcdb174d3bf13e49efe8b6bf1a27d2&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">ACL防止损坏层</span></u></strong></a></p>
   </li>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247487033&amp;idx=2&amp;sn=6f2744a4e09457c707f6fe638cac230a&amp;chksm=fafde497cd8a6d813ddc3c1e13befcd75143b0a62a9eac22386e6cd4f7dbfd6184eb6ca9bd6f&amp;scene=21#wechat_redirect" target="_blank"><strong><u>有界上下文</u></strong></a></p>
   </li>
   <li>
    <p style="">提炼核心、支撑和通用域</p>
   </li>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483674&amp;idx=1&amp;sn=77f06af636c2b25fb182d4ea667bc6c7&amp;chksm=fafdebb4cd8a62a2c298ac51236c3f8a052f7be315834ddb4b6954eb0b0a400ce923a7b20ae8&amp;scene=21#wechat_redirect" target="_blank"><strong><u>分层架构</u></strong></a></p>
   </li>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484092&amp;idx=1&amp;sn=99082fb559f866a7c7ae3612250d9535&amp;chksm=fafde812cd8a6104614342c2b0785fedb8dedfef033ff5f1b9a0f8f6bbd33de3ba31ed94d6f6&amp;scene=21#wechat_redirect" target="_blank"><strong><u>CRUD增删改查简单架构</u></strong></a></p>
   </li>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485928&amp;idx=1&amp;sn=d60b63b554e5d1b054f273e898ed12c5&amp;chksm=fafde346cd8a6a50fbf7d2d4b3eafc36ba356e161251504203465effcf187dc5368cff9f04b9&amp;scene=21#wechat_redirect" target="_blank"><strong><u>CQRS命令查询隔离</u></strong></a></p>
   </li>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485188&amp;idx=1&amp;sn=3618568ba3bf9c396195d8e2c4e6723d&amp;chksm=fafdedaacd8a64bca74024452fb88a56ff99aa6bcd9ae841fca1b91cbff40d4dd78698b09dcc&amp;scene=21#wechat_redirect" target="_blank"><strong><u>依赖消弱控</u></strong></a></p>
    <p style=""></p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">服务部署隔离方面</p>
  <ul>
   <li>
    <p style="">环境拆分</p>
   </li>
   <li>
    <p style="">机房隔离</p>
   </li>
   <li>
    <p style="">通道隔离</p>
   </li>
   <li>
    <p style="">单元化</p>
   </li>
   <li>
    <p style="">泳道</p>
   </li>
   <li>
    <p style="">热点隔离</p>
   </li>
   <li>
    <p style="">读写隔离</p>
   </li>
   <li>
    <p style="">容器隔离</p>
   </li>
   <li>
    <p style="">拆库拆表</p>
   </li>
   <li>
    <p style="">动静隔离</p>
   </li>
   <li>
    <p style="">非核心流量隔离</p>
    <p style=""></p>
   </li>
  </ul>
 </li>
 <li>
  <p style="">服务间交互隔离方面</p>
  <ul>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485315&amp;idx=1&amp;sn=41e61beaa78b2840633adb3b8cec380a&amp;chksm=fafded2dcd8a643b46d38bfdb81b9441091afc5ec5b3d385342b6fb20b307480c03ff3b5f604&amp;scene=21#wechat_redirect" target="_blank"><strong><u>超时熔断</u></strong></a></p>
   </li>
   <li>
    <p style="">失败率超限降级</p>
   </li>
   <li>
    <p style="">服务内资源隔离方面</p>
   </li>
   <li>
    <p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485474&amp;idx=1&amp;sn=df22866d7a0b096e7169a5bef81a3058&amp;chksm=fafde28ccd8a6b9a0157fb4e2600a71c8a5db6db7eea37dfdbd6f25e6d3b38b6ab92c4d1f9e3&amp;scene=21#wechat_redirect" target="_blank"><strong><u>线程池隔离</u></strong></a></p>
   </li>
   <li>
    <p style="">信号量隔离</p>
   </li>
  </ul>
 </li>
</ul>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑九：风险巡检</span></strong></p>
<p style=""></p>
<p style="">核心系统稳定之后，更多的从边缘进行优化，避免影响核心系统的稳定。风险巡检是优化的重要方面。常见的有以下内容。</p>
<p style=""></p>
<ul>
 <li>
  <p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">请求系统错误统计
    <br></span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">请求业务错误</span><span fontsize="" color="">统计</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">请求内部错误</span><span fontsize="" color="">统计</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">慢查询</span><span fontsize="" color="">统计</span>巡检</p>
 </li>
 <li>
  <p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">数据一致性</span><span fontsize="" color="">巡检</span></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="rgb(34, 34, 34)" style="color: rgb(34, 34, 34)">流程执行情况巡检
    <br></span></p>
 </li>
</ul>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">审计和安全</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑十：合法合规</span></strong></p>
<p style=""></p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484103&amp;idx=1&amp;sn=2b77a63f5e03e41a555df833d5686ec8&amp;chksm=fafde869cd8a617f9ade7e54d5b3d22882de7da9dfe3164cd1f9551a3a9162dced482d1860a9&amp;scene=21#wechat_redirect" target="_blank"><strong><u>合法合规</u></strong></a>是企业生死存亡的大事。近年来，由于政策影响，很多教育机构面临了巨大的危机甚至倒闭。</p>
<p style=""></p>
<p style="">对于金融类企业，太多合规操作。比如：行业要求金融交易类系统不能与其他系统混合部署；平台没有清结算资质可能面临二清问题。提到资质，不得不说说金融牌照。在我国，需要审批的金融牌照主要包括银行、信托、保险、券商、期货、基金、基金子公司、基金销售、金融租赁、小额贷款、第三方支付牌照、典当12种。业务的牌照要对口。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">考虑十一：严格准入</span></strong></p>
<p style=""></p>
<p style="">做需求有个常识，对于用户输入的每个字段都需要和产品经理讨论一下：什么<span style="font-size: 17px">类型、长度多少、允许的字符集范围、格式是否合法。这么做一方面是设计问题，包括产品设计、数据库设计，还有一部分是安全问题：一个数值型的字段肯定比一个粗放的文本型字段被攻击的可能性小，起码不会传到后端之后被当成脚本被执行。</span></p>
<p style=""></p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484379&amp;idx=1&amp;sn=012608c46885969e2b6b6dd5fcf1d70a&amp;chksm=fafde975cd8a606321a8cc42caa24501df999aa53453943173e6a972c5425ba99252a895dd6e&amp;scene=21#wechat_redirect" target="_blank"><strong><span style="font-size: 17px">操作合理性准入</span></strong></a><span style="font-size: 17px">也是考虑的重要方面。比如一个普通用户不能编辑另一个用户的个人信息。比如i申请公司服务器，一次申请1万台机器。比如流量准入，一台机器以超快的速度频繁访问一个网站的资讯信息，就可能不是真实用户操作而是爬虫。以上都会对系统安全性产生极大的影响。</span></p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">一张图总结今天的内容：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-uwyi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/xi-tong-she-ji-de-11ge-kao-lu</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:50:55 GMT</pubDate></item><item><title><![CDATA[系统架构的11条原则]]></title><link>https://wiki.dgfygk.com/archives/xi-tong-jia-gou-de-11tiao-yuan-ze</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E7%9A%8411%E6%9D%A1%E5%8E%9F%E5%88%99&amp;url=/archives/xi-tong-jia-gou-de-11tiao-yuan-ze" width="1" height="1" alt="" style="opacity:0;">
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则五：拥抱变化</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<h1 style="" id="%E9%87%8D%E8%A7%86%E6%9E%B6%E6%9E%84%E6%89%A9%E5%B1%95%E6%80%A7%E5%92%8C%E5%8F%AF%E8%BF%90%E7%BB%B4%E6%80%A7%E3%80%82%E6%97%A0%E7%8A%B6%E6%80%81%E7%9A%84%E7%B3%BB%E7%BB%9F%E7%9A%84%E6%98%AF%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%E5%92%8C%E7%9B%B4%E6%8E%A5%E7%9A%84%E3%80%82%E4%BB%BB%E4%BD%95%E6%97%B6%E5%80%99%E9%83%BD%E8%A6%81%E8%80%83%E8%99%91%E8%BF%99%E4%B8%80%E7%82%B9%EF%BC%8C%E4%B8%8D%E8%A6%81%E6%90%9E%E4%B8%AA%E4%B8%8D%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%EF%BC%8C%E6%9C%89%E7%8A%B6%E6%80%81%E7%9A%84%E4%B8%9C%E4%B8%9C%E5%87%BA%E6%9D%A5%E3%80%82%E5%90%A6%E5%88%99%EF%BC%8C%E4%B8%80%E6%97%A6%E9%9C%80%E8%A6%81%E6%94%B9%E5%8F%98%EF%BC%8C%E6%88%90%E6%9C%AC%E5%BE%88%E9%AB%98%E3%80%82"><span style="font-size: 17px">重视架构扩展性和可运维性。</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484368&amp;idx=1&amp;sn=b3c8d41b98ecf2b976d0b025c0322055&amp;chksm=fafde97ecd8a6068cc4d25f2766261870a67baa4cbf9dc0d4920302fcf09e74ee682025efabc&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">无状态的系统</span></u></strong></a><span style="font-size: 17px">的是可扩展的和直接的。任何时候都要考虑这一点，不要搞个不可扩展的，有状态的东东出来。否则，一旦需要改变，成本很高。</span></h1>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则六：简单即正义</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 17px">解析：</span></p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485195&amp;idx=1&amp;sn=9aa72718cdcc24d872f0a89cf63ea34f&amp;chksm=fafdeda5cd8a64b3a55f4de3fdc48d2ee396074d851f686b70a7e679dbcf26862348a79182eb&amp;scene=21#wechat_redirect" target="_blank"><strong><u>简单即正义</u></strong></a>的另一种说法叫做KISS。KISS(Keep it simple,sutpid)保持每件事情都尽可能的简单。用最简单的解决方案来解决问题。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则七：尽量自动化</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style="">人力成本既慢又贵，还有经常不断的人工失误。如果不能降低人力成本，反而需要更多的人，那么这个架构设计一定是失败的。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">稳定性原则</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则八：依赖最简</span></strong></p>
<p style=""></p>
<p style="">解释：</p>
<p style="">依赖原则是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484076&amp;idx=1&amp;sn=0c2b291a31ed53e9c8af06d131aabdd4&amp;chksm=fafde802cd8a6114430a5c1eb8609481059341089ba2c4113012764578152570b6149780e3d5&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">去除依赖、弱化依赖、控制依赖</span></u></strong></a>。多一个依赖多一分风险。能不依赖则不依赖，能异步弱依赖不要同步强依赖。实在不能弱依赖的，比如必须要调用加密存储来获取数据库的密码，不然无法连接数据库，可以控制获取密码在服务启动时进行，如果获取不到则服务启动失败，因为现在都是集群部署，一台无法启动不影响整体提供服务。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则九：不作不死</span></strong></p>
<p style=""></p>
<p style="">解释：</p>
<p style="">尽可能的做较少的功能。当有疑问的时候，就不要去做，甚至干掉。很多功能从来不会被使用。最多留个扩展点就够了。</p>
<p style="">等到有人提出再说（除非是影响核心流程，否则就等到需要的时候再去做）。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则十：容灾容错</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485188&amp;idx=1&amp;sn=3618568ba3bf9c396195d8e2c4e6723d&amp;chksm=fafdedaacd8a64bca74024452fb88a56ff99aa6bcd9ae841fca1b91cbff40d4dd78698b09dcc&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">Everything fails!</span>&nbsp;</u></strong></a>人都是要死的，机器都是要坏的。<span fontsize="" color="">如果一件事情有可能发生则在生产环境中一定会发生，架构中要做好容错设计。</span></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">原则十一：用成熟的技术</span></strong></p>
<p style=""></p>
<p style="">解析：</p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484324&amp;idx=1&amp;sn=78986320566babe59cc2c0da81c7f5b6&amp;chksm=fafde90acd8a601ceff0bb501a0c1f6cd10b5408d3e4fed43d9e6c8605f800297abead8abdf5&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">不要给别人的技术当小白鼠</span></u></strong></a>，不要因技术本身的问题影响系统的稳定。尽可能的使用红利大的主流技术，而不要自己发明轮子，更不要魔改。在技术选型上，千万不要被——“你看某个公司也在用这个技术”，或是一些在论坛上看到的一些程序员吐槽技术的观点（没有任何的数据，只有自己的喜好）来决定自己的技术，还是看看主流大多数公司实际在用的技术栈，会更靠谱一些。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">一张图总结今天的内容：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vsdt.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/xi-tong-jia-gou-de-11tiao-yuan-ze</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:46:31 GMT</pubDate></item><item><title><![CDATA[四种常用的微服务架构拆分方式]]></title><link>https://wiki.dgfygk.com/archives/si-zhong-chang-yong-de-wei-fu-wu-jia-gou-chai-fen-fang-shi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%9B%9B%E7%A7%8D%E5%B8%B8%E7%94%A8%E7%9A%84%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E6%8B%86%E5%88%86%E6%96%B9%E5%BC%8F&amp;url=/archives/si-zhong-chang-yong-de-wei-fu-wu-jia-gou-chai-fen-fang-shi" width="1" height="1" alt="" style="opacity:0;">
<p style="">微服务架构并无标准架构，不然什么架构师大会也不会各个系统架构百花齐放了。虽然没有固定的套路，却有一些经验，今天就来做一个总结。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于角色拆分</span></strong></p>
<p style=""></p>
<p style="">这种拆分方式常见于基础设施以及其PaaS层的架构，比如<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485181&amp;idx=1&amp;sn=df4568317a038fcff3b77cd66d0b7bb6&amp;chksm=fafdec53cd8a654573d4210e755fac7c95b5145aa8a5c76cb3df64478a44da0446945ef04c19&amp;scene=21#wechat_redirect" target="_blank"><strong><u>服务治理</u></strong></a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484514&amp;idx=1&amp;sn=5e841cfbfdd405168c08368d7a78dbe2&amp;chksm=fafdeecccd8a67da2f520388da9d13cb72b476e0b372d6e105b66d333ec70e1a17b013763d1c&amp;scene=21#wechat_redirect" target="_blank"><strong><u>k8s</u></strong></a>、<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484948&amp;idx=1&amp;sn=5e5e32ebde19bfb3803e697b6ddf4ef4&amp;chksm=fafdecbacd8a65ac45ac2fabfbba02c70a9ea556e4449991b55a3cd3d9223c636af5f1edd947&amp;scene=21#wechat_redirect" target="_blank"><strong><u>kafka</u></strong></a>。所谓基础组件的PaaS层是说，基础设施本身主要作为基础设施使用，是IaaS层。但是基础设施本身需要维护功能，需要增删改查等运维操作。业界基于开源做的二次开发也着重在做这方面的工作。</p>
<p style=""></p>
<p style="">下面以kafka做说明。因为要上升到PaaS层，下图基于美团对kafka的二次开发封装，产品名叫<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483874&amp;idx=2&amp;sn=786c19686a3f33ef6e6e13c5f6c961ef&amp;chksm=fafdeb4ccd8a625a1d8b34eeb9d55ad4d9530ee548c1cffdeb49dff4eeeb287c193bacf1eae3&amp;scene=21#wechat_redirect" target="_blank"><strong><u>mafka</u></strong></a>。</p>
<p style=""></p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-sgiw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">咱们直接看★标注的部分mafka-manager，这个就是运维操作统一管理端。充当的就是管理者的角色。再看看kafka其他组件的名称：生产者( producer )、消费者( consumer )、经纪人( broker )。核查员( monitor )。架构的组织都是根据角色来来划分的。</p>
<p style=""></p>
<p style="">为什么我要将mafka-manager用<span fontsize="" color="">★标注</span>呢。因为不管是基础设施还是别的，以产品化的观点，需要对外提供一个完整的产品体验。完整的产品包含什么呢？统一的产品外观、统一的接口定义、统一的服务运营。</p>
<p style=""></p>
<p style="">我们要接入mafka，虽然最终程序里要用的是生产者、消费者这些，但是第一步都要在mafka-manager对应的界面上申请。<span fontsize="" color="">mafka-manager</span>类似于网关入口的角色。业界有专门把这些接口服务抽象出来叫做api网关。</p>
<p style=""></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247483841&amp;idx=1&amp;sn=c236d6829a2cce8ad56236bf75085bbf&amp;chksm=fafdeb6fcd8a62794646fd34a37c0532c92bba41d7664badc4db18162b141d0c8285e5d29a52&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span fontsize="" color="">k8s</span></u></strong></a>架构中，api网关这个概念更加明显一些。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-arvn.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">kubectl是k8s的控制台命令交互界面、web UI是浏览器交互界面，不同的交互界面会走统一的api server。这里api server就是api网关服务。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于可扩展性拆分</span></strong></p>
<p style=""></p>
<h1 style="" id="%E9%A6%96%E5%85%88%E6%9D%A5%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%8Bakf%E6%89%A9%E5%B1%95%E7%AB%8B%E6%96%B9%E4%BD%93%E3%80%82"><span style="font-size: 17px">首先来了解一下AKF扩展立方体。</span></h1>
<h1 style="" id=""></h1>
<h1 style="" id="akf%E6%89%A9%E5%B1%95%E7%AB%8B%E6%96%B9%E4%BD%93%EF%BC%88scalability-cube%EF%BC%89%EF%BC%8C%E6%98%AF%E3%80%8A%E6%9E%B6%E6%9E%84%E5%8D%B3%E6%9C%AA%E6%9D%A5%E3%80%8B%E4%B8%80%E4%B9%A6%E4%B8%AD%E6%8F%90%E5%87%BA%E7%9A%84%E5%8F%AF%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%EF%BC%8C%E8%BF%99%E4%B8%AA%E7%AB%8B%E6%96%B9%E4%BD%93%E6%9C%89%E4%B8%89%E4%B8%AA%E8%BD%B4%E7%BA%BF%EF%BC%8C%E6%AF%8F%E4%B8%AA%E8%BD%B4%E7%BA%BF%E6%8F%8F%E8%BF%B0%E6%89%A9%E5%B1%95%E6%80%A7%E7%9A%84%E4%B8%80%E4%B8%AA%E7%BB%B4%E5%BA%A6%EF%BC%9A"><strong>AKF扩展立方体（Scalability Cube）</strong><span style="font-size: 17px">，是《</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485553&amp;idx=1&amp;sn=945e46d81bef62f35ac447895d99cdd2&amp;chksm=fafde2dfcd8a6bc905cd0e9e50a9e51247a6d53f614a948b89209341a55877893a3fcba295dd&amp;scene=21#wechat_redirect" target="_blank"><strong><u><span style="font-size: 17px">架构即未来</span></u></strong></a><span style="font-size: 17px">》一书中提出的可扩展模型，这个立方体有三个轴线，每个轴线描述扩展性的一个维度：</span></h1>
<p style="">X轴 —— 代表无差别的克隆服务和数据，工作可以很均匀的分散在不同的服务实例上；</p>
<p style="">Y轴 —— 关注应用中职责的划分，比如数据类型，交易执行类型的划分；</p>
<p style="">Z轴 —— 关注服务和数据的优先级划分，如分地域划分。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mwlg.webp&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">白话来说：X轴拆分就是通过加机器水平拆分；Y轴拆分就是按业务逻辑垂直拆分；Z轴拆分就是按照算法进行分片，这个算法比如按地域，不同地域访问不同的分片或者服务。</p>
<p style=""></p>
<p style="">举个例子，比如一般公司的<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485868&amp;idx=1&amp;sn=843459f44e37003814c7a9b44bf721ec&amp;chksm=fafde302cd8a6a142123716eef9228df519285d9d1b94b6a135fb701adf8cf826a570298f497&amp;scene=21#wechat_redirect" target="_blank"><strong><u>redis集群</u></strong></a>会有一个团队来进行统一维护。redis集群有主有从，数据都是一样的，多副本容灾，这是X轴水平的拆分。一个公司很多业务，redis团队会对不同的业务提供不同的集群，这是Y轴垂直拆分；集群内部数据会通过sharding做分片，这是Z轴算法拆分。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于稳定性拆分</span></strong></p>
<p style=""></p>
<p style="">在业务架构中，通常会通过核心模块的划分或者主次链路的划分来进行微服务拆分。</p>
<p style=""></p>
<p style="">在《<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485742&amp;idx=1&amp;sn=b3fa976a6bc5ec30c3f4b7b93d60711f&amp;chksm=fafde380cd8a6a968c2b8204ec1e5ebd48625b2ba51714f581d358007d69ba0e2e80223434ba&amp;scene=21#wechat_redirect" target="_blank"><strong><u>三平面分离架构</u></strong></a>》中，我提到过分离出<span fontsize="" color="">控制平面</span>、<span fontsize="" color="">数据平面</span>和管理平面。这本质上也是通过核心模块划分来进行拆分的一种方式。控制平台一般是核心链路，核心数据作为控制逻辑的一部分可以通过本地缓存等措施弱依赖于数据平面。管理平台是后台管理等，用于增删改查，人工操作时才用，其他时间挂掉都没关系。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">基于资源需求拆分</span></strong></p>
<p style=""></p>
<p style="">根据性能需求来进行拆分。简单来说就是访问量特别大，访问频率特别高的业务，又要保证高效的响应能力，这些业务对性能的要求特别高。比如积分竞拍、低价秒杀、限量抢购。</p>
<p style=""></p>
<p style="">我们要识别出某些超高并发量的业务，尽可能把这部分业务独立拆分出来。这么做的原因非常简单，一个保证满足高性能业务需求，另一个保证业务的独立性，不互相影响。&nbsp;</p>
<p style=""></p>
<p style="">类似积分竞拍、超低价秒杀、限量抢购，对瞬间峰值和计算性能要求是非常高的。这部分的业务如果跟其他通用业务放在一块，一个是可能互相影响，比如某个链路阻塞，会导致雪崩沿调用链向上传递。</p>
<p style=""></p>
<p style="">另外一个是如果多个业务耦合在一块，发布频率变高、服务扩缩容变难、维护复杂度变高。如下图例子所示，订单服务是一个性能要求高的服务，一般会单独拆分。</p>
<p style=""></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fqie.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<h1 style="" id="-1"></h1>
<p style="text-align: center; "><strong><span style="font-size: 18px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style=""><span style="font-size: 17px">咱们实际工作中，通常会发现一种拆分方式和另一种拆分方式并不冲突。一个完整的架构也不只使用了一种拆分方式。</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486632&amp;idx=1&amp;sn=7b443147ff6b4d10ab0c09ee6e1e1d73&amp;chksm=fafde606cd8a6f109870d9ad2c47cc42497de5a0f18d84bcdb174d3bf13e49efe8b6bf1a27d2&amp;scene=21#wechat_redirect" target="_blank"><u><span style="font-size: 17px">《</span></u><strong><u><span style="font-size: 17px">领域驱动设计(DDD)中简单易用的10种技巧</span></u></strong><u><span style="font-size: 17px">》</span></u></a><span style="font-size: 17px">也可以配合来使用。</span></p>
<p style=""><span style="font-size: 17px">
  <br>
 </span></p>
<p style=""><span style="font-size: 17px">本文提到的api网关严格上不是业务划分时的一个模块。业界通常把api网关作为一个基础设施。如果在架构图中包含了api网关通常是下图所示：
  <br></span></p>
<p style=""><span style="font-size: 17px"><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-fjwu.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<p style=""><span style="font-size: 17px">
  <br>
 </span></p>
<p style="">上图中的架构既包含了业务逻辑的业务划分，也有配置中心、注册中心这样的技术划分。总体上来说是一个技术架构图。而api网关本身也更多被归为是技术概念，而不是业务概念。</p>
<p style=""></p>
<p style="">在《<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486894&amp;idx=1&amp;sn=344b221975495bc6e4a2246fe3e0a848&amp;chksm=fafde700cd8a6e16e06e78397e2eb2e229d60335d9a8760072eff23f86fc1a88c1748a271f41&amp;scene=21#wechat_redirect" target="_blank"><strong><u>尤娜系统的第一次飞行中换引擎的架构垂直拆分改造</u></strong></a>》中，首先就拆分出来了api网关层，这说明尤娜要下一盘大棋，最终对外会提供一个统一的产品。</p>
<h1 style="" id="-2"></h1>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/si-zhong-chang-yong-de-wei-fu-wu-jia-gou-chai-fen-fang-shi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:33:27 GMT</pubDate></item><item><title><![CDATA[【真实案例】程序设计的陷阱-警惕大块数据]]></title><link>https://wiki.dgfygk.com/archives/zhen-shi-an-li-cheng-xu-she-ji-de-xian-jing-jing-ti-da-kuai-shu-ju</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E7%9C%9F%E5%AE%9E%E6%A1%88%E4%BE%8B%E3%80%91%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E7%9A%84%E9%99%B7%E9%98%B1-%E8%AD%A6%E6%83%95%E5%A4%A7%E5%9D%97%E6%95%B0%E6%8D%AE&amp;url=/archives/zhen-shi-an-li-cheng-xu-she-ji-de-xian-jing-jing-ti-da-kuai-shu-ju" width="1" height="1" alt="" style="opacity:0;">
<p style=""><span fontsize="" color="">无论是架构还是程序的设计，往大的方面说，逃不过</span><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">分治、分类</span></strong><span fontsize="" color="">的范畴。微服务是一种分治的思想；service mesh是一种分治的思想；G1是一种分治的思想；大数据是一种分类的思想；领域划分是一种分类的思想……</span></p>
<p style=""></p>
<p style="">数据大了会遇到问题，比如经典的redis的大key问题。遇到这类问题，主流解决方案是两个。</p>
<p style=""></p>
<p style="">一个是限制，比如超过多大就不处理了，只提示错误。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-adjx.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="">另外一个是分治，将数据分片处理。</p>
<p style="text-align: center; "></p>
<p style="">如果这两个都没做？</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bbwc.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">案例一：请求超时了</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">问题</span></strong></p>
<p style=""><span style="font-size: 16px">首先回顾</span><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485729&amp;idx=1&amp;sn=325d273617410e48e3e8c1d8a103421c&amp;chksm=fafde38fcd8a6a99b1c79a1b6bfd7d29cb925be129c2ad2608f28b7e93fb414384c5838568bd&amp;scene=21#wechat_redirect" target="_blank"><span style="font-size: 16px">《近期做的稳定性建设总结》</span></a><span style="font-size: 16px">里的一个案例：收到一个可用内存不足10%的告警，经过了两个小时才自动恢复到87%，所在的机器出现一笔请求超时。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">现象</span></strong></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">1、对业务进行了解发现开始出现问题的时间点，有收到外部MQ发过来的大块数据。虽然大块数据做了分片，但是每个分片还是有几M大并且下发条数多，集中下发。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">2、分别在问题机器和其他相同应用的不同机器上执行top命令，观察到问题机器和其他机器上jvm内存占用差不多并且稳定。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">3、执行free -h命令观察到机器可用内存7.6G，cache/buffer下降到1G以下。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">4、</span><span fontsize="" color="">收到外部MQ发过来的</span><span style="font-size: 16px">大块数据</span>时间段网卡占用情况从平时0.5M以下飙升到20M/S。</p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">5、</span><span fontsize="" color="">出现一笔请求超时正在下发</span><span style="font-size: 16px">大块数据</span>的高峰期，请求方发请求到机器接收到请求时间间隔是几毫秒。收到请求后机器会将请求转发给MQ，用了3秒多。而请求方设置了5秒超时。
 <br></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">6、所有环节cpu使用率都很低。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">7、20天前在另外一台机器上也发生了内存升高到90%以上的现象。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 16px; color: rgb(255, 41, 65)">总结来说：</span></strong><span style="font-size: 16px">MQ集中接收10M内的大块数据，JVM内存占用正常，操作系统内存占用升高，网卡流量20M/S。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">分析</span></strong></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">此问题的根本原因是外部短时间内下发大量大块数据(受节假日影响当天的大块数据略高于其他天)，从根本上治理需要和外部(大佬级别，我们完全没有主动权)一起进行架构方面的调整，不现实也没有必要用此牛刀。
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">表现为虚机层面内存资源不足，影响了IO速率。因为高峰IO 20M/S，针对现在的千兆网卡、万兆网卡来说，并不大。问题并不在网络通道上，而在机器本身。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">我写网络通信和linux操作系统系列也有一段时间了，大家可以用这个实际问题来小试牛刀。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486658&amp;idx=1&amp;sn=0067e6d161c0acac5cf20fda6231f692&amp;chksm=fafde66ccd8a6f7a62114ebf73963d09253cb94bb8d47acea955f4b9bfacc37dfcb1ff22e4a5&amp;scene=21#wechat_redirect" target="_blank">《深入浅出操作系统的零拷贝》</a>中，我发起了一个投票：<span style="font-size: 16px">使用零拷贝最少要进行几次拷贝？</span></p>
<p style=""><span fontsize="" color="">36%的人答对了，是2次。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">零拷贝如果只是从磁盘发送到网络，最低可以只用2次DMA拷贝，也就是内核空间拷贝。但是实际情况是大多数场景，数据都需要回到用户空间进行数据处理，所以都需要3次以上。加上数据往往进来还要出去。所以10M的大块数据处理过程占用的内存是数倍于10M的。</span></p>
<p style=""><span style="font-size: 16px">所以最终我们提申请进行了纵向扩容。纵向扩容说白了就是增加物理内存。有人就要问了是不是扩机器数量也能解决问题。这种处理方式大概是能治病但是不一定对症。意思可以理解为吃广谱抗菌药来治疗感冒，有一定帮助但不是针对性的处理。因为扩机器是减少了请求数从而减少了内存占用量。但是万一有个大请求就是把内存飚高了呢？并且扩内存实施成本更低，不需要程序的发布。就是低峰期暂停程序，插个内存条。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="">经过半年多的验证，超时再没有发生过，说明我们的处理方式是对症的。</p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">案例二：内存达到95%</span></strong></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">问题</span></strong></p>
<p style="">生产环境有个不太核心的服务，共三台机器，一台机器内存达到95%。这个服务分成定时任务和实时请求处理两部分，其实定时任务流量占比居多，实时请求很少。所以我们将这台机器隔离掉定时任务。也就是说定时任务只走另外两台。目前内存稳定在95%，但是内存达到这么高的原因需要找到。</p>
<p style="">服务的定时任务部分是调用RPC接口获取数据，之后RPC或者http请求发送到别处。每次处理的数据大小在几K。实时请求部分有个是处理文件上传的，文件的大小不固定，几M，几十M都是有可能的。这种请求一般情况下1天十笔，但是不固定。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">现象</span></strong></p>
<p style="">1、使用pmap -x java程序进程号 命令，发现大量anon块，举例如下：</p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">b7f0a000 8 rwx-- 00000000b7f0a000 000:00000 [ anon ]</span>
 <br>
 <span style="font-size: 16px; color: rgb(77, 77, 77)">b7f20000 8 rwx-- 00000000b7f20000 000:00000 [ anon ]</span></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px; color: rgb(77, 77, 77)">2、这个服务除了内存使用达到95%的这台机器，另外一台达到了93%，还有一台89%。备用机房的机器(无定时任务，也不接受实时请求)内存占用基本都是86%。</span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">分析</span></strong></p>
<p style="">用pmap -x 查到的anon，中文叫匿名块。是通过malloc或mmap分配的“大”块。Java直接申请的堆内存之外的块就是堆外内存了。我查了代码，这个服务不涉及到数据库操作，没有使用guava等直接内存的。最有可能产生堆外内存的地方是<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486564&amp;idx=2&amp;sn=7ebfa62b34c22dd6c8170e67784d43dd&amp;chksm=fafde6cacd8a6fdc4bded6a9bebe05bbac49cea08e06deb20d4a6d7ea1fe26864088b8857f83&amp;scene=21#wechat_redirect" target="_blank">nio</a>(使用了netty、jetty)。</p>
<p style="">这个问题本来不是我在查，另外两个同事在做。但是一直没有结论，我就进行了介入。同事一直的思路是要找到内存泄露的地方。我分析可能不是内存泄露，因为最近几个月内存没有再增长。</p>
<p style="">我任务运行的这半年内，有段时间可能有人批量上传大文件。<span fontsize="" color="">文件上传到哪台服务器是随机的，占用内存高的机器很有可能是当时处理的文件大或者多。因为过去了快半年了，日志都清理了，我没有办法找到直接的证据。</span></p>
<p style="">同事还是想排查内存泄露问题。给出两个理由：第一，把定时任务停掉的机器最近内存稳定不再增长了。第二，他查了最近的日志，文件大小都在几M，而内存高的机器要内存使用高出了几百M。</p>
<p style="">我的答复：内存不再增长与定时任务停掉未必是因果。也可能是最近再也没有如此大块或大量的文件上传。另外，程序没有做限制，最近只有几M未必几个月前没有更的文件上传。另外，案例一里也说了，如果文件大于十几兆，其使用的内存要多出好几倍。</p>
<p style="">同事觉得有道理，决定下周进行测试环境模拟验证，结果究竟如何呢？咱们来猜一猜。</p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">如果你的服务要处理大数据，但是又不想使用分治引入程序的复杂性，那么在申请机器的时候，要增加足够的内存冗余！</p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">有的朋友会觉得这个处理方式并不高大上。牛逼的处理方式是排查到内核层，调整个linux参数啥的。</span><strong><span style="font-size: 16px; color: rgb(255, 41, 65)">不推荐！</span></strong><span style="font-size: 16px">原因如下：</span></p>
<p style=""><span style="font-size: 16px">现在也算比较流行的一个词叫做：不可变服务器。</span><span fontsize="" color="rgb(77, 77, 77)" style="color: rgb(77, 77, 77)">“传统”的部署方式中，对系统的改动都会呈现在服务器上，从而增加了风险。采用不可变部署方式，</span>服务器都是公司统一标准化的，那么变更只要考虑应用程序的发布，而应用程序一旦发布也不允许改变，就成了不可变服务。不可变性可以增加系统的稳定性。在一个规模比较大的公司，就算可以通过技术解决资源的问题，从整体角度，还是通用方案更加可取。</p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style=""><span style="font-size: 16px">另外补充一个小知识：
  <br></span></p>
<p style=""><span style="font-size: 16px">
  <br>
 </span></p>
<p style="">使用pmap -x 命令查看内存地址的时候要注意起始地址：</p>
<p style="">64位的linux系统，对于虚拟地址空间的划分，将0x0000,0000,0000,0000 – 0x0000,7fff,ffff,f000这128T地址用于用户空间；而0xffff,8000,0000,0000以上的128T为系统空间地址。</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/zhen-shi-an-li-cheng-xu-she-ji-de-xian-jing-jing-ti-da-kuai-shu-ju</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:30:37 GMT</pubDate></item><item><title><![CDATA[小公司比较吃亏的两道微服务面试题]]></title><link>https://wiki.dgfygk.com/archives/xiao-gong-si-bi-jiao-chi-yu-de-liang-dao-wei-fu-wu-mian-shi-ti</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E5%B0%8F%E5%85%AC%E5%8F%B8%E6%AF%94%E8%BE%83%E5%90%83%E4%BA%8F%E7%9A%84%E4%B8%A4%E9%81%93%E5%BE%AE%E6%9C%8D%E5%8A%A1%E9%9D%A2%E8%AF%95%E9%A2%98&amp;url=/archives/xiao-gong-si-bi-jiao-chi-yu-de-liang-dao-wei-fu-wu-mian-shi-ti" width="1" height="1" alt="" style="opacity:0;">
<p style="">其实选择工作的时候，很多技术牛人都会选择一些小而美的公司，技术全面，能够以一个更全面的视角看整个公司的运作，人和人之间的相处也很简单。但是，有两道微服务的面试题，小公司的朋友们会比较吃亏。</p>
<p style=""></p>
<p style="">题一：你们上下游通信用的是什么协议？</p>
<p style="">题二：你们服务拆分成了几个子服务？</p>
<p style=""></p>
<p style="">为什么吃亏，咱们来分析一下。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">题一：你们上下游通信用的是什么协议？</span></strong></p>
<p style=""></p>
<p style="">在一些小公司，可能直接用的是http协议。如果直接这样回答面试官。面试官下一个问题就有点难度了：“为什么用HTTP协议呢？”</p>
<p style=""></p>
<p style="">这时候心里那个苦啊：“我也想用dubbo这样的RPC框架啊。但是公司非要用http，我自己又做不了主。但我总不能这么说吧”
 <br>
 <br>
 最好的回答是真实的回答，但是要回答到本质。首先从面试官问题的本质出发，面试官实际的考察点是：服务通信方式RPC与REST的区别与怎么选取。</p>
<p style=""></p>
<p style="">要是我，我这么回答：我们公司的基础设施还不是很完善，没有类似Dubbo这样的服务治理工具。所以我们还是使用基于http协议的REST调用。如果将来我们公司有了类似Dubbo这样的服务治理工具，并且足够稳定。内部之间的调用还是用RPC调用更为合适。</p>
<p style=""></p>
<p style="">因为RPC与REST相比较，主要的区别如下：</p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jibq.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">对于公司内部的使用，理想的情况是有标准的服务化能力和高性能，更适合RPC。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">题二：你们服务拆分成了几个子服务？</span></strong></p>
<p style=""></p>
<p style="">在一些小公司，可能一个项目就是一个微信小程序，用户量几万的样子。这时候前后端分离，后端一个单体应用就足够。如果直接这样回答面试官。面试官会怀疑你对微服务完全没有经验。</p>
<p style=""></p>
<p style="">这时候，首先还是要事实就是。做人诚信是根本。那要怎么回答呢？要是我，我这么回答。</p>
<p style=""></p>
<p style="">目前综合项目本身的复杂度、业务量、<span style="font-size: 16px; color: rgb(64, 64, 64)">成本和沟通上考量</span>，目前我们项目采用的单体架构。将来，我们业务量上来，也可能会按领域拆分，毕竟<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">架构不是设计出来的，而是演进而来的</span></strong>。</p>
<p style=""></p>
<p style="">虽然我自身所做的项目简单，但是如果不局限我自身做的，我对整体也有一定了解，我就说说我了解的内容吧。</p>
<p style=""></p>
<p style="">根据<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">康威定律</span></strong>，公司的组织架构设计等价于组织间的沟通结构，也极大的反应了公司的系统架构。我在一个小公司，所有人员加起来就是个<strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">2个披萨</span></strong>(国外的大，一个披萨大概够6个人吃)的团队，开发测试运营都在一起。做的事情白话一点说就是个公众号小程序。</p>
<p style=""></p>
<p style="">我们的项目想要工作，借助了微信开放平台，同时我们的服务器是放阿里云上的。如果把微信开放平台和阿里云纳入到架构中，是下面的样子（这时候如果是现场面试，我建议拿起白板笔边画边说）：</p>
<p style="text-align: center; "></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hnxm.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">图画的比较粗糙，意思到了就可以了。反正我没有在面试中卡过谁画画水平不行。其实面试者在图中维护的只是核心服务部分。熔断、限流和降级都是在核心服务内部使用hystrix或者直接使用spring cloud来完成的。服务注册发现如题一所说因为是使用http协议，实际上是例如nginx之类的负载均衡器来完成。图中也涉及OAuth2，这是微服务安全的主流实现方式。</p>
<p style=""></p>
<p style="">上面这张就把面试官的注意引入了Spring Cloud等一个个具体的技术问题，和自己所在的平台关系不大了。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485928&amp;idx=1&amp;sn=d60b63b554e5d1b054f273e898ed12c5&amp;chksm=fafde346cd8a6a50fbf7d2d4b3eafc36ba356e161251504203465effcf187dc5368cff9f04b9&amp;scene=21#wechat_redirect" target="_blank">《CURD系统怎么做出技术含量--怎样引导面试》</a>里，我说过其实面试官希望面试者来引导面试，将自己的特长能力充分展现出来。面试者如果可以洞悉面试官的心里，说明自己的格局是够的。</p>
<p style=""></p>
<p style="">最后临场做了一首打油诗送给大家：</p>
<p style=""></p>
<p style="text-align: center; ">胸中有丘壑，</p>
<p style="text-align: center; ">对答如有神。</p>
<p style="text-align: center; ">惊艳面试官，</p>
<p style="text-align: center; ">事业上青云。</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/xiao-gong-si-bi-jiao-chi-yu-de-liang-dao-wei-fu-wu-mian-shi-ti</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:26:53 GMT</pubDate></item><item><title><![CDATA[业务系统调研方法论]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%9A%E5%8A%A1%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%A0%94%E6%96%B9%E6%B3%95%E8%AE%BA&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style="">今早收到邀请写一篇业务系统调研方法论的文章，因为这个方法一直就在头脑中，一直在用。所以动笔直接写了。</p>
<p style=""></p>
<p style="">业务系统调研，调研的内容有两个：</p>
<p style="">1、做成什么样</p>
<p style="">2、怎么做</p>
<p style=""></p>
<p style="">把事情想清楚了，完成一份完整的调研报告，整个调研流程前后都需要做工作。所以完整的调研流程要分为5个步骤：</p>
<p style="">1、设立目标</p>
<p style="">2、梳理清楚现状</p>
<p style="">3、调研要做成什么样</p>
<p style="">4、调研怎么做</p>
<p style="">5、形成方案</p>
<p style=""></p>
<p style=""></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">设立目标和梳理清楚现状</span></strong></p>
<p style=""></p>
<p style="">对业务系统的调研，需要在设立好目标和梳理清楚现状的前提下开展。几年前有次开会，评审一个技术方案。但是做方案的小伙做的调研：调研部分与实际需求相关度不高。</p>
<p style=""></p>
<p style="">小伙是刚毕业的，觉得这个调研有难度，很难从业界找到相同问题的方案。但是领导给了一个中肯的建议：这个问题是个常规问题，如果没有找到相关度高的内容，那应该是你的搜索关键词不对。而关键词不对的根源就是没有梳理清楚要调研的问题及本质。</p>
<p style=""></p>
<p style="">当然，光梳理清楚目标和现状还不够。</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">做成什么样</span></strong></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">第一步：术语</span></strong></p>
<p style=""></p>
<p style="">术语是进入一个业务领域的有效抓手。我们举个例子来说明一下术语的价值。</p>
<p style=""></p>
<p style="">在我的认知当中，不管是组织架构、业务架构、技术架构。有一个原则就是先划分领域，清晰领域边界。各个领域之间的关系都是在领域层解决。而数据存储只是支撑。举个通俗点的例子：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-jvkv.jpg&amp;size=m" width="524px" height="524px" style="display: inline-block"></p>
<p style="">一个好的设计好比一把凳子，领域是凳面，数据存储是凳子腿，它们之间靠领域联系在一起。数据存储是支撑，最好和其他部分解耦开。这种简约的风格易于维护和扩展。</p>
<p style=""></p>
<p style="">但是我们leader告诉我，数据存储自成体系。通过存储可以清楚掌握领域的全貌。就是说他心中的凳子大概是长这个样子：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qdoh.jpg&amp;size=m" width="499px" height="499px" style="display: inline-block"></p>
<p style="">我觉得这个观点的形成应该和领导工作时间长，从oracle数据库时代一路走来的历程有关。但是他心中的整个体系全貌我不清楚，我不清楚就没有发言权。</p>
<p style=""></p>
<p style="">为了走进他的世界，我围绕的是他口中的一个术语：三户模型。这是电信、银行、电商等领域常用的一个术语。三户是指用户、客户和账户。</p>
<p style=""></p>
<p style="">三户：</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;用户：单个人</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;客户：个人或集体就是服务的对象</p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;账户：客户使用服务的ID，一般指：护照，身份证或者其他账户</p>
<p style=""></p>
<p style="">三户模型是根据营销模型转向“以客户为中心”理念而产生的结果，客户的需求成为支撑系统信息模型不断趋于完善的主要驱动力。与客户这个实体概念关联最为紧密的概念就是用户和帐户，这三者之间的关系应该是一个相互关联但又是独立的三个实体，这种关联只是一个归属和映射的关系，而三个实体本身是相互独立的，分别是体现完全不同的几个域的信息，客户是体现了社会域的信息，用户体现了业务域的信息，帐户体现的是资金域的信息。</p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">三户模型是贯穿于整个业务运营支撑系统中的核心模型之一。</span></strong></p>
<p style=""></p>
<p style="">当我调研到上面红字的这句话，我突然明白：我们leader的整个上下文是围绕着运营和运维数据来展开的。数据有体系才能体系化运营。通过对这个术语的检查、学习和思考，我慢慢走进了另外一个领域。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">第二步：搜索</span></strong></p>
<p style=""></p>
<p style="">术语是入门，是确保搜索出自己想要信息的基础。有了这个基础，我们就可以看一个业务系统在业界是怎么做的。调研的首选是相关领域的头部机构，比如在电商领域，一般会调研亚马逊、阿里等的优秀实践。</p>
<p style=""></p>
<p style="">但是业务系统做成了什么样子和怎么做的，网上的资源非常有限。很多公司会特意派人去参加一些线下技术沙龙之类的。是不是想到了疫情前经常这个开发者大会、那个meeting up那些怀念的活动。</p>
<p style="">
 <br>
 不过大家不要对这种大会期望过高，也不要觉得参加了一天的沙龙感觉也没收获什么，是不是因为自己太菜。其实首先，从目的出发，这些上台演讲的专家都是带着目的来的，一般是品牌宣传或者做其他的推广。所以他们的重点是展示自己的实力，一般不会轻易吐露是怎么实现的。所以如果你恰好要做或者想过类似的产品，他们可以激发一些“做成什么样”的思路，不会给你怎么做的思路。
</p>
<p style=""></p>
<p style="text-align: center; "><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">怎么做</span></strong></p>
<p style=""></p>
<p style="">下面对怎么做提供两种调研方法。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">访谈法</span></strong></p>
<p style=""></p>
<p style="">很多公司招聘人才是会从一些头部公司有比例的招人的。为什么嘞。因为这些聪明的公司要的不仅仅是这个人，还有这个人的人脉。</p>
<p style=""></p>
<p style="">公司的技术方案除了一些开源项目，其他不会对外公开。假设我们已经了解到了XX公司有一款产品做得不错，也很符合现在自身的需求，想了解怎么做的。怎么办呢？找找XX公司的同事打听打听，正式的说法叫做访谈。</p>
<p style=""></p>
<p style="">不过大家也不要被我误导。访谈法有正规的访谈流程。<span style="font-size: 16px">访谈法的一般步骤是：</span></p>
<p style="text-align: start; ; line-height: 28px">
 <br>
 <span style="font-size: 17px">①设计访谈提纲。</span>
</p>
<p style="text-align: start; ; line-height: 28px"><span style="font-size: 17px">②.恰当进行提问。</span></p>
<p style="text-align: start; ; line-height: 28px"><span style="font-size: 17px">③准确捕捉信息，及时收集有关资料。</span></p>
<p style="text-align: start; ; line-height: 28px"><span style="font-size: 17px">④适当地作出回应。</span></p>
<p style="text-align: start; ; line-height: 28px"><span fontsize="" color="">⑤及时</span>做<span fontsize="" color="">好访谈记录。</span></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">登高类比法</span></strong></p>
<p style=""></p>
<p style="">我在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486299&amp;idx=1&amp;sn=34ea00ad39901a51331bfd95dee0c641&amp;chksm=fafde1f5cd8a68e31c7b8d5fa5b21fc70f5e49663f4cd6e6ea0a0639696556bc197b97ea241b&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">应用程序怎样划分模块？</span>》</a>和<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486507&amp;idx=1&amp;sn=2bd6916ba35babe2bd85da53800377a6&amp;chksm=fafde685cd8a6f934c7cd38a8ee3fe010ed18f47e57ec658bb72ef423d4978f9c262b37c37dd&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">Java无垃圾稳态设计</span>》</a>里都提到过登高类比法。</p>
<p style=""></p>
<p style=""><span fontsize="" color="">登高类比法是先检索最为相似的场景以得出结论。如果检索结果不够时再扩大范围直到足以得出结论。比如设计一个IP分配方法，检索时先检索IP分配，在网上比较权威的大概只有DHCP动态主机配置了。但是我们需要的不是动态，可以退而检索资源分配。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style="text-align: center; "><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style=""></p>
<p style=""><span fontsize="" color="">完整的调研流程的最后一步是形成方案，也就是一个总结的过程。我去年很大一块工作就是在撰写一个公司级的调查研究报告。虽然写的不好，但是过程还是有很大收获的。</span></p>
<p style=""><span fontsize="" color="">
  <br>
 </span></p>
<p style=""><span fontsize="" color="">好了，划重点：三户模型、访谈法、登高类比法。你学会了吗？</span></p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><pubDate>Sat, 21 Sep 2024 09:25:23 GMT</pubDate></item><item><title><![CDATA[热点和秒杀来临前要做的5件事]]></title><link>https://wiki.dgfygk.com/archives/re-dian-he-miao-sha-lai-lin-qian-yao-zuo-de-5jian-shi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%83%AD%E7%82%B9%E5%92%8C%E7%A7%92%E6%9D%80%E6%9D%A5%E4%B8%B4%E5%89%8D%E8%A6%81%E5%81%9A%E7%9A%845%E4%BB%B6%E4%BA%8B&amp;url=/archives/re-dian-he-miao-sha-lai-lin-qian-yao-zuo-de-5jian-shi" width="1" height="1" alt="" style="opacity:0;">
<p style="">记得当年《甄嬛传》热播，调用了我们团队的媒体资讯接口。接口被调用挂了。当时虽然我不负责那一块，只是目睹了当时大家在临场解决问题的紧张一幕。但是这件事在我心里埋下了种子，从此追求高可用、高稳定成为职业发展的方向。</p>
<p style=""></p>
<p style="">今天咱们就来聊一聊热点和秒杀前要做的5件事。这5件事是按时间顺序排列出场。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">应急演练</span></strong></p>
<p style=""></p>
<p style=""><a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484340&amp;idx=1&amp;sn=66ea4273b461fd994d3ce1186c82722a&amp;chksm=fafde91acd8a600c47fbc8dab508027745d5e9fd37f722575fae2151c7c1098fad731aa28085&amp;scene=21#wechat_redirect" target="_blank"><span fontsize="" color="">无论对一个软件系统运行原理掌握得多么彻底，也不能阻止人犯意外错误。--玛格丽特教授</span></a></p>
<p style=""></p>
<p style="">应急演练可以定期举行。前提是对各种意外情况，提前准备好了预案。预案在演练过程中，可以发现系统问题、检验相关人员SOP/EOP的操作熟练情况。</p>
<p style=""></p>
<p style="">举个例子：支付系统由于下游银行通道能力参差，需要制定关闭XX银行通道的SOP，并进行演练。</p>
<p style=""></p>
<p style="">应急演练一般要求级别较高的人员进行组织，确保其对突发情况有一定的应变权利，同时也能敏锐的发现新问题，确保效果。应急演练我个人总结要分为4个步骤，实际上也是标准的<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486113&amp;idx=1&amp;sn=f3623b2cf257aa37ea83768fd4325762&amp;chksm=fafde00fcd8a691928c66f7e484ab2b7f6fa7797dc32833a7f777bb4ebde58c010750f13c118&amp;scene=21#wechat_redirect" target="_blank">PDCA方法</a>的步骤：</p>
<p style=""></p>
<p style="">1、提前通知，确保参加人员掌握了需要的知识。同时同步到应急演练的目标、相关文档、时间、地点、参加人员</p>
<p style="">2、演练开始时第一步是告知大家演练的目标、流程和检查项目，比如过程中会检查大家对SOP/EOP的操作熟练情况，如果不熟练则会进行通报。</p>
<p style="">3、按流程步骤进行演练</p>
<p style="">4、进行演练总结，制定改进计划</p>
<p style=""></p>
<p style="">在我实际进行过的演练中，上面提到的第二步做的不是很好。可能是作为一项例行事务，演练组织人员习惯于这项工作，认为没有必要重复说明。实际上我认为这是最重要的一步。因为第一，每次演练可能会有一些新人加入，他们不了解背景。第二，大家最后可能只记得演练的一个目标，把优化流程、找问题这些事情忽视了，演练效果大打折扣。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">SOP/EOP</span></strong></p>
<p style=""></p>
<p style="">SOP（Standard Operating Procedure三个单词中首字母的大写 ）即标准作业程序，就是将某一事件的标准操作步骤和要求以统一的格式描述出来，用来指导和规范日常的工作。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-cgxw.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">EOP（Emergency Operating Procedure三个单词中首字母的大写 ）即应急操作流程，用于规范应急操作过程中的流程及操作步骤。确保人员可以迅速启动，确保有序、有效的组织实施各项应对措施。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">全链路压测</span></strong></p>
<p style=""></p>
<p style="">全链路压测是一个很好的数据说话的方法，下面要介绍的扩容和降级都要依赖于压测的结果进行。同时，它也是提前发现系统问题的有效手段。
 <br></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">挡板压测/联合压测/封版压测</span></strong></p>
<p style=""></p>
<p style="">挡板压测</p>
<p style="">挡板压测就是链路上需要与外部交互的地方使用mock模拟来进行压测。比如微信支付，要模拟使用银行卡支付，不能每次压测都先冲几个亿来做测试吧。这时候可以模拟银行侧的返回值进行模拟。这个环节主要用于发现内部问题。</p>
<p style=""></p>
<p style="">联合压测</p>
<p style="">联合压测就是真的和交互的外部机构沟通好一起压测。如果测试微信支付，还真需要提前冲几个亿来做测试。当然，测试完可以退款。这个环节有个重要职责是验证合作方能力，比如合作方有三个银行，一个银行并发量不够，那大促时就少路由一些请求给它。</p>
<p style=""></p>
<p style="">封版压测</p>
<p style="">封版压测其实和前面两个压测不在同一维度。封版压测既可以是挡板压测，也可以是联合压测。目的在于测试系统稳定性。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">重启服务</span></strong></p>
<p style=""></p>
<p style="">Java服务如果长时间不发布，没有任何bug的情况下也会内存缓慢增长。因为JVM申请了的内存，只要进程不死就不会释放。虽然咱们JVM参数里指定了堆的大小和每个线程占用的大小。但是程序运行过程中还会不可避免的申请很多堆外内存。比如数据库操作就会产生很多堆外内存。我负责的服务就发生过一个服务近1年没有任何发布升级，内存比刚重启后一周增长5%的情况。</p>
<p style=""></p>
<p style="">除了内存，重启还能避免很多慢性问题在一个关键点爆发。所以也热点和秒杀保护的一个重要举措之一。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(255, 41, 65)">VIRT/RES/SHR</span></strong></p>
<p style=""></p>
<p style="">这里既然提到了JVM内存，那就顺便说一下怎么观察。常见的是使用top命令。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hoij.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">关键列的数据含义如下：</p>
<p style=""></p>
<p style="">VIRT(虚拟内存)</p>
<p style="">1、进程“需要的”虚拟内存大小，包括进程使用的库、代码、数据，以及malloc、new分配的堆空间和分配的栈空间等；</p>
<p style="">2、假如进程新申请10MB的内存，但实际只使用了1MB，那么它会增长10MB，而不是实际的1MB使用量。</p>
<p style="">3、VIRT = SWAP + RES</p>
<p style=""></p>
<p style="">RES(常驻内存)</p>
<p style="">1、进程当前使用的内存大小，包括使用中的malloc、new分配的堆空间和分配的栈空间，但不包括swap out量；</p>
<p style="">2、包含其他进程的共享；</p>
<p style="">3、如果申请10MB的内存，实际使用1MB，它只增长1MB，与VIRT相反；</p>
<p style="">4、关于库占用内存的情况，它只统计加载的库文件所占内存大小。</p>
<p style="">5、RES = CODE + DATA</p>
<p style=""></p>
<p style="">SHR(共享内存)</p>
<p style="">1、除了自身进程的共享内存，也包括其他进程的共享内存；</p>
<p style="">2、虽然进程只使用了几个共享库的函数，但它包含了整个共享库的大小；</p>
<p style="">3、计算某个进程所占的物理内存大小公式：RES – SHR；</p>
<p style="">4、swap out后，它将会降下来。</p>
<p style=""></p>
<p style="">它们之间的关系用一张图来表示就是</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bini.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">扩容</span></strong></p>
<p style=""></p>
<p style="">微博历史上曾发生过许多次服务器崩溃的情况。这种情况大多可以通过提前扩容解决的。但是一直停留在扩容状态经费上有问题：一台服务器每年成本按3万来算，100台就是300W。热点流量带来的收益并没有高。</p>
<p style=""></p>
<p style="">近几年容器技术突飞猛进，对于热点和秒杀等场景，有的公司已经做到了弹性伸缩。就是根据流量情况动态调整服务的集群机器数。公司还可以和云厂商合作，让厂商提供动态扩容能力。</p>
<p style=""></p>
<p style="">对于大公司而言，其实像阿里云、腾讯云这种云服务器的成本要比自己管理服务器成本要高。当然最贵的是亚马逊云，这是题外话。像微博这种临时扩容场景，服务器费用收取是按秒来计费的。</p>
<p style=""></p>
<p style="">微博在【马蓉事件】中，有次马蓉说希望给自己来次专访，微博提前租了阿里云机器。结果没有提供任何实质性证据，也没有什么流量。气的微博团队@马蓉让她结服务器的账。</p>
<p style=""></p>
<p style="">不管怎么说，很多热点事件来临前，提前扩容是可以解决问题的。所以，容器技术的一个重要指标就是创建容器的耗时，有的团队已经做到30s可以创建一个容器。意思是点击申请容器到容器上的业务镜像启动提供服务只需要30s！</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">降级</span></strong></p>
<p style=""></p>
<p style="">写文章要注意逻辑闭环。回到开头“当年《甄嬛传》热播，调用了我们团队的媒体资讯接口。接口被调用挂了”这个问题。</p>
<p style=""></p>
<p style="">当时最大的问题是媒体资讯核心服务上有获取视频和专辑两个主要接口。视频咨询内容很小。就是下图所示内容简介这些视频的主要信息。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-vrwo.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">而专辑如下图所示的花絮呀、正片呀所有的这些主要内容简介打包在一起。我记得当年《甄嬛传》一个专辑包含了几千个视频信息？</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-oqna.png&amp;size=m" width="722px" height="639px" style="display: inline-block"></p>
<p style=""></p>
<p style="">当时服务器就是因为调用专辑给调挂了。当时正确的做法应该是降级专辑，只返回单个视频信息。当然了，在架构上，这个量级是要拆分服务的，这是题外话。</p>
<p style=""></p>
<p style="">而在热点秒杀来临时，降级一些非核心服务，比如：定时服务、批量程序等。以节省带宽、线程等资源给核心服务。据我了解，一般大公司秒杀之前都要走一套标准的降级流程。</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/re-dian-he-miao-sha-lai-lin-qian-yao-zuo-de-5jian-shi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:14:42 GMT</pubDate></item><item><title><![CDATA[网络通信之Session的历史血脉]]></title><link>https://wiki.dgfygk.com/archives/wang-luo-tong-xin-zhi-sessionde-li-shi-xie-mai</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1%E4%B9%8BSession%E7%9A%84%E5%8E%86%E5%8F%B2%E8%A1%80%E8%84%89&amp;url=/archives/wang-luo-tong-xin-zhi-sessionde-li-shi-xie-mai" width="1" height="1" alt="" style="opacity:0;">
<p style="">Session经常被大家翻译为【会话】，在一个应用内部设计架构中，它通常指进程内保存的状态数据或者属性。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">2007年</span></strong></p>
<p style="">2007年我的本科毕业设计里，用了很多JSP里嵌Java代码的操作。比如登录是这么写的：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-hoim.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">运行效果是这样的：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-flcy.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">对的，我就是用这么原始的技术成功的混到了毕业文凭。</p>
<p style="">我周围的男同学技术比我要好一些，他们的用法也比我要高级，前端JSP里没有调用Java代码，有专门的后端来处理。</p>
<pre><code>HttpSession&nbsp;hs = request.getSession();PrintWriter&nbsp;pw&nbsp;=&nbsp;response.getWriter();String&nbsp;msg&nbsp;=&nbsp;request.getParameter("ming");pw.println(msg);pw.println(hs.getId());</code></pre>
<p style="">这两种方式本质上就是使用Java Servlet配合http协议标准来实现的。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">HttpSession</span></strong></p>
<p style="">服务器会为每一个用户 创建一个独立的HttpSession</p>
<p style=""></p>
<p style=""><strong>原理</strong></p>
<p style="">当用户第一次访问Servlet时,服务器端会给用户创建一个独立的Session</p>
<p style="">并且生成一个SessionID,这个SessionID在响应浏览器的时候会被装进cookie中,从而被保存到浏览器中</p>
<p style="">当用户再一次访问Servlet时,请求中会携带着cookie中的SessionID去访问</p>
<p style="">对于Servlet而言。服务器会根据这个SessionID去查看是否有对应的Session对象，有就拿出来使用;没有就创建一个Session(相当于用户第一次访问)。换句话说：若Servlet是客户端访问的第一个WEB应用的资源，则只有调用了request.getSession()或request.getSession(true) 才会创建HttpSession对象。</p>
<p style=""></p>
<p style=""><strong>域的范围</strong></p>
<p style="">&nbsp; &nbsp; Context域 &gt; Session域 &gt; Request域</p>
<p style="">&nbsp; &nbsp; Session域 只要会话不结束就会存在 但是Session有默认的存活时间(30分钟)</p>
<p style=""></p>
<p style=""><strong>注意点</strong></p>
<p style="">1&gt;这时候的Session是跨线程的。</p>
<p style="">2&gt;一个Session的概念需要包括特定的客户端，特定的服务器端以及不中断的操作时间。A用户和C服务器建立连接时所处的Session同B用户和C服务器建立连接时所处的Session是两个不同的Session。基于这个注意点，多数线上服务都是采用集群或者在扩展性上支持了集群，所以现在基本上没有人使用HttpSession了，当然只是做个毕设还是可以的。</p>
<p style=""></p>
<p style=""><strong>总结</strong></p>
<p style="">通过HttpSession主要是获取其中保存的状态数据或者属性，是一种状态的保持。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px">2015年</span></strong></p>
<p style="">2015年我做了一个PHP的项目，发现在PHP里Session那个好用。其实PHP里的Session和Java Servlet的Session原理基本相同。在集群情况下也是会失效的。但是为什么都过去8年了，Java中基本已经淘汰了这种用法，在PHP里还在用呢？</p>
<p style=""></p>
<p style="">我个人理解那就是PHP语言的本身适用范围了。PHP本身适用于简单轻量的小型系统，这种系统有些就是单服务器在运行的。对可用性要求也不是特别高，出了问题可以通过立即重启或者启用备份服务器解决。Session里的信息如用户信息丢了用户重新登录就好了。</p>
<p style=""></p>
<p style=""><strong>总结</strong></p>
<p style="">通过PHP里Session和HttpSession一样，主要是获取其中保存的状态数据或者属性，是一种状态的保持。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">2016年</span></strong></p>
<p style="">当时做项目用到ActiveMQ，它实现了JMS标准。先看一段简单代码：</p>
<pre><code>public&nbsp;class&nbsp;JMSProducer&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;public static void main(String[] args) {        //连接工厂        ConnectionFactory connectionFactory;        //连接        Connection connection  = null;        //会话，接受或者发送消息的线程        Session session;        //消息的目的地        Destination destination;        //消息生产者        MessageProducer messageProducer;        //实例化工厂        connectionFactory = new ActiveMQConnectionFactory(ConnectionConstants.BROKENURL);        try {            //获取连接            connection = connectionFactory.createConnection(ConnectionConstants.USERNAME, ConnectionConstants.PASSWORD);            //启动连接            connection.start();            //创建session&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//参数1：1、true：支持事务&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//为true时：paramB的值忽略， acknowledgment mode被jms服务器设置为SESSION_TRANSACTED 。&nbsp;　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 2、false：不支持事务&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;为false时：paramB的值可为Session.AUTO_ACKNOWLEDGE、Session.CLIENT_ACKNOWLEDGE、DUPS_OK_ACKNOWLEDGE其中一个,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 为Session.SESSION_TRANSACTED时会报错!            //参数2：Session.AUTO_ACKNOWLEDGE为自动确认，客户端发送和接收消息不需要做额外的工作。异常也会确认消息，应该是在执行之前确认的            //Session.CLIENT_ACKNOWLEDGE为客户端确认。客户端接收到消息后，必须调用javax.jms.Message的acknowledge方法。jms服务器才会删除消息。可以在失败的            //时候不确认消息,不确认的话不会移出队列，一直存在，下次启动继续接受。接收消息的连接不断开，其他的消费者也不会接受（正常情况下队列模式不存在其他消费者）            //DUPS_OK_ACKNOWLEDGE允许副本的确认模式。一旦接收方应用程序的方法调用从处理消息处返回，会话对象就会确认消息的接收；而且允许重复确认。在需要考虑资源使用时，这种模式非常有效。            session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);            //创建一个消息队列            destination = session.createQueue("firstDemo");            //创建消息生成这            messageProducer = session.createProducer(destination);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//创建消息            TextMessage message = session.createTextMessage("ACTIVEMQ 生产者 生产消息，这是第"+i+"次生产");            //发出消息            messageProducer.send(message);
            session.commit();        } catch (JMSException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }&nbsp;&nbsp;&nbsp;&nbsp;}}</code></pre>
<p style="">注意在上面的connection连接中创建了session。在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486186&amp;idx=1&amp;sn=f21e17b21beb4c99cef0e076071ac859&amp;chksm=fafde044cd8a69526aa3ec9edbe71ea5aa41de2ef236494201dc2f20701ed8abc6be1a72344f&amp;scene=21#wechat_redirect" target="_blank">《深入理解MQ生产端的底层通信过程-理解channel》</a>的<strong>MQ中怎么使用Channel</strong>那一节，<span style="font-size: 16px">RabbitMQ的生产端&nbsp;</span>connection.createChannel()。对比之下，情不自禁就要问了，这里的Channel等价于Session?</p>
<p style=""></p>
<p style="">自然不是啦，针对他们的区别。知乎上有个神回复：“Session是会话，比如打电话，从拨号到挂断这就是一个Session；Channel是通道,我的理解是比如打电话时，Channel表示是使用联通信号或者是移动信号或者是电信信号。”</p>
<p style=""></p>
<p style="">Channel是文件的读取等操作的抽象，而Session我理解是连接中状态数据的保持或者复用。Channel在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247486186&amp;idx=1&amp;sn=f21e17b21beb4c99cef0e076071ac859&amp;chksm=fafde044cd8a69526aa3ec9edbe71ea5aa41de2ef236494201dc2f20701ed8abc6be1a72344f&amp;scene=21#wechat_redirect" target="_blank">《深入理解MQ生产端的底层通信过程-理解channel》</a>里讲过了。那Session这块怎么理解呢？</p>
<p style=""></p>
<p style="">在上面JMSProducer的代码中，我可写了大段的注释。这些注释可不是白写的。我们列举一下在上面的Session中显式的保存了哪些东西：</p>
<ul>
 <li>
  <p style="">是否支持事务</p>
 </li>
 <li>
  <p style="">客户端收到消息是否自动确认</p>
 </li>
 <li>
  <p style="">一个消息队列<span style="font-size: 14px; color: rgb(221, 17, 68)">firstDemo</span></p>
 </li>
 <li>
  <p style="">一个可以发消息给<span style="font-size: 14px; color: rgb(221, 17, 68)">firstDemo</span>的生产者</p>
 </li>
 <li>
  <p style="">一个消息内容</p>
 </li>
</ul>
<p style="">一般我们每次发消息时，消息内容会需要新建，但是生产者、消息队列、是否支持事务和客户端收到消息是否自动确认这些都可以复用。幸好人家设计的时候也设计了Session，每次直接用就可以了。</p>
<p style=""></p>
<p style="">总结：MQ中的Session是连接中状态数据的保持或者复用</p>
<p style=""></p>
<p style=""><strong>2020年</strong></p>
<p style="">项目原因我研究了mybatis的源码，本来都计划为了满足项目需求自己重写一个mybatis了，后来重新规划设计没有做。在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485597&amp;idx=1&amp;sn=555efa9b4b4041a29c4816772b0c1f2c&amp;chksm=fafde233cd8a6b25f5a98a025df7917cb0d27f81e9efece21d4ce0a1f43155598b4108626172&amp;scene=21#wechat_redirect" target="_blank">《<span style="font-size: 16px">mybatis的本质和原理</span>》</a>里我手撕了一个简易mybatis，里面有提到SqlSession。作用类似于一个 JDBC 中的 Connection 对象的代理，代表着一个连接资源的启用，它的作用有 3 个：</p>
<ul>
 <li>
  <p style="">获取 Mapper 接口。</p>
 </li>
 <li>
  <p style="">发送 SQL 给数据库。</p>
 </li>
 <li>
  <p style="">控制数据库事务。</p>
 </li>
</ul>
<p style="">本质上我理解也是连接中状态数据的保持或者复用。一段代码感受一下：</p>
<pre><code>//定义 SqlSessionSqlSession sqlSession = null;try {    // 打开 SqlSession 会话    sqlSession = SqlSessionFactory.openSession();    // some code...    sqlSession.commit();    // 提交事务} catch (IOException e) {    sqlSession.rollback();  // 回滚事务}finally{    // 在 finally 语句中确保资源被顺利关闭    if(sqlSession != null){        sqlSession.close();    }}</code></pre>
<p style="">上面在<span style="font-size: 14px">sqlSession</span>对象中进行了事务的提交和回滚操作，非常类似于咱们在navicate等mysql客户端界面上的一个会话终端的操作。</p>
<p style="">在这同一个session下，因为就好像之前在HttpSession 登录有效期内用户的操作一样，只是界面不同。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">本文从个人的时间经历，分析了Session演进的血缘关系。这十几年的演进中，Session的概念越来越泛化，但是本质上还是保存的状态数据或者属性。</p>]]></description><guid isPermaLink="false">/archives/wang-luo-tong-xin-zhi-sessionde-li-shi-xie-mai</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:11:25 GMT</pubDate></item><item><title><![CDATA[架构师三大难-领域划分问题]]></title><link>https://wiki.dgfygk.com/archives/wei-ming-ming-wen-zhang</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B8%89%E5%A4%A7%E9%9A%BE-%E9%A2%86%E5%9F%9F%E5%88%92%E5%88%86%E9%97%AE%E9%A2%98&amp;url=/archives/wei-ming-ming-wen-zhang" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px">引子</span></strong></p>
<p style="">在<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485868&amp;idx=1&amp;sn=843459f44e37003814c7a9b44bf721ec&amp;chksm=fafde302cd8a6a142123716eef9228df519285d9d1b94b6a135fb701adf8cf826a570298f497&amp;scene=21#wechat_redirect" target="_blank">《架构师之路-redis集群解析》</a>提到：提出有水平的问题、做出有水平的总结和建议、做出有水平的回答 是架构师面临的三大难。</p>
<p style="">天天开会，最怕开会。开会十分钟，准备半天功。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-kguq.gif&amp;size=m" width="475px" height="475px" style="display: inline-block"></p>
<p style="">下面是围绕这三大难展开的故事。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">情景-领域划分问题</span></strong></p>
<p style="">几年前的一天，在一个会上，完全不相关的团队人员在进行我们系统的架构评审。由于他们对我的系统不了解，提的问题多是针对架构师个人能力上的。</p>
<p style="">我在介绍的时候提到：“根据系统的特点，按照角色划分领域的同时结合现有人员情况划分了下面几个应用……”，然后我被打断了，有人提问说：“应用是按领域划分还是按照人员划分？”</p>
<p style="text-align: center; ">针对这种埋坑的问题，选项有A和B，那更合理的答案一般是C。<img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-eyaw.jpg&amp;size=m" width="549px" height="549px" style="display: inline-block"></p>
<p style="">翻译提问者的问题其实是在问：“不是都是用领域划分领域吗？按照人员划分的方法不对吧？”</p>
<p style="">先来分析一下，我顺着提问者的话说会怎样：“模块需要按照领域划分，模块是分层级的。人员划分决定的是独立部署单位的粒度，在实际项目中应该综合考虑。”</p>
<p style="">这样虽然回答了提问者的问题，但是提问者很显然有知识上的盲区，需要我给他解惑的地方：“究竟要怎样划分应用？”而我的回答没有给出他完整的答案，他会继续找一些回答的漏洞来细化问题，比如：“资源预算不算做考虑粒度的因素吗？” 假定我每次顺着他的思路来，整个回答过程就没有清晰的结构了。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-dkij.png&amp;size=m" width="639px" height="639px" style="display: inline-block"></p>
<p style="">所以我需要直接针对他本质的问题展开回答，以下是回答内容：</p>
<p style="">在这次介绍的系统中，最主要的依据是按照领域来划分模块，同时根据资源和人员等情况来决定独立部署的应用模块的粒度。</p>
<p style="">但是在其他的系统中，根据不同的系统特点，模块或者应用的划分上，考虑侧重点会有所不同。我举几个例子。</p>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">示例一(管道过滤器模式)</span></strong></p>
<p style="">比如工作流类的系统，从总体架构上采用的是管道过滤器模式</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-xflc.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">如上图，在这种系统中主要有两种角色，一种是管理者角色，负责把其他模块组织串联起来，整体对外提供服务。记得之前做个这样的项目，管理者角色的模块系统名叫captain（当时大家都以漫威英雄人物命名，captain对应美国队长）。其他模块都是一个个过滤器。是否要将每个过滤器独立应用部署，还是主要根据人力和资源来定。只要设计清晰，将来人力和资源有调整，或者随着业务的发展，对稳定性有个更好的要求，可能会需要根据可用性做一个隔离。高SLA和低SLA的单独部署，高SLA的多地区多机房部署。</p>
<p style=""><strong>示例二(三平面分离模式)</strong></p>
<p style="">比如三平面分离架构系统，详情可参考我之前的文章<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485742&amp;idx=1&amp;sn=b3fa976a6bc5ec30c3f4b7b93d60711f&amp;chksm=fafde380cd8a6a968c2b8204ec1e5ebd48625b2ba51714f581d358007d69ba0e2e80223434ba&amp;scene=21#wechat_redirect" target="_blank">《三平面分离架构》</a>。简单来说分成最核心的流程控制平面、次核心的组件支撑平面和SLA只要求两个9的管理运维平面。如下图：</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-ehns.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">所以领域划分时这三个平面要边界分明，三个平面可用性级别不同，资源分配也不同。比如最核心的流程控制平台日志存储要90天，其他可能需要30天；流程控制平面可能需每笔请求开始和结束打日志，而其他服务只需要异常时打日志；流程控制平面和组件支撑平面需要四地八中心高可用部署，而管理运维平台只需要两机房容灾。所以核心是要将三个平面分开以分配不同的资源。</p>
<p style=""><strong>示例三(异步处理模式)</strong></p>
<p style="">有些应用整体是实现一个大职责，但是被中间件分成了两个部分。比如有个服务是异步处理模式。所谓异步处理模式是将一个执行耗时长的流程分成两个阶段。比如退款操作。用户提交一个退款请求，先会收到一个实时通知：“您的退款请求已经收到，退款会于1~2个工作日内到账。”之后系统会将这个退款请求扔到MQ中，慢慢来消费处理。</p>
<p style="">这种模式的服务，根据实际资源等情况可以分成两个独立部署的系统，或者合在一个应用里既作为MQ的生产者又作为MQ的消费者。</p>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nfge.jpg&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""></p>
<p style=""><strong><span fontsize="" color="rgb(216, 79, 169)" style="color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">如果观察到别人总是就细节进行追问，这时候可以先把思路跳出来弄清楚他的本质问题是什么。</p>]]></description><guid isPermaLink="false">/archives/wei-ming-ming-wen-zhang</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:03:05 GMT</pubDate></item><item><title><![CDATA[一个http请求进来都经过了什么(2021版)]]></title><link>https://wiki.dgfygk.com/archives/yi-ge-httpqing-qiu-jin-lai-du-jing-guo-liao-shi-me-2021ban</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E4%B8%80%E4%B8%AAhttp%E8%AF%B7%E6%B1%82%E8%BF%9B%E6%9D%A5%E9%83%BD%E7%BB%8F%E8%BF%87%E4%BA%86%E4%BB%80%E4%B9%88%282021%E7%89%88%29&amp;url=/archives/yi-ge-httpqing-qiu-jin-lai-du-jing-guo-liao-shi-me-2021ban" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></p>
<p style="">四年前我写过一篇文章<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247484226&amp;idx=2&amp;sn=cb8df0d23713a8842140963fa2522c56&amp;chksm=fafde9eccd8a60fa7a9ac71d9819da3b16e2995e368a3e5f7404ab202743904f380efa591e53&amp;scene=21#wechat_redirect" target="_blank">《一个请求过来都经过了什么》</a>，这个问题是每隔一段时间我就要重新梳理一遍的。因为每个阶段接触的信息不同，看到的想到的也不同。今天来比较一下与4年前认知上有什么变化。</p>
<p style=""></p>
<h1 style="text-align: start; " id="%E6%8F%90%E5%87%BA%E9%97%AE%E9%A2%98"><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">提出问题</span></strong></h1>
<p style="">高技术含量开放性面试题驾到：<span style="font-size: 16px">”请描述一个请求过来到响应完成都做了什么，越详细越好。”&nbsp;</span></p>
<p style=""></p>
<h1 style="text-align: start; " id="%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98"><strong><span style="font-size: 20px">解决问题</span></strong></h1>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">设立场景</span></strong></p>
<p style="">假设我在超市买了我喜爱的经典搭配：烤肠+酸奶。然后我就微信扫码付款了。付款时序图大体是这样的：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-mgrf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看的眼晕没关系，和主题关系不大。咱们zoom in（把镜头拉近，只看一部分）一下：</p>
<p style="">1.1返带订单ID的URL到微信的处理，我不是腾讯的哈，他们内部怎么做的我不知道，这里只是举个例子。</p>
<p style=""><strong><span fontsize="" color="rgb(0, 209, 0)" style="color: rgb(0, 209, 0)">链路处理流程</span></strong></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-plsz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">如上图所示，请求从外网过来，到达一个机房。首先要经过外网DMZ区，DMZ区可以简单理解为隔离区。</p>
<ul>
 <li>
  <p style="">接入路由器：它是专线或者互联网的统一入口。</p>
 </li>
 <li>
  <p style="">接入交换机：这是一个容灾设备，它可以连接到其他机房的线路，如果本机房线路发生故障，可以绕行其他机房接入进来。</p>
 </li>
 <li>
  <p style="">防火墙：有两个作用。一个是外网地址到内网地址的转换；另外一个是防火墙安全策略，进行信息的过滤。</p>
 </li>
</ul>
<p style="">通过防火墙后，会进入内网DMZ，这个也是一个隔离区。在这个区域，一般会设置蜜罐。蜜罐技术本质上是一种对攻击方进行欺骗的技术，通过布置一些作为诱饵的主机、网络服务或者信息，诱使攻击方对它们实施攻击，从而可以对攻击行为进行捕获和分析，了解攻击方所使用的工具与方法，推测攻击意图和动机，能够让防御方清晰地了解他们所面对的安全威胁，并通过技术和管理手段来增强实际系统的安全防护能力。</p>
<ul>
 <li>
  <p style="">核心交换机：一般公司里会有生产环境、测试环境和办公环境。一般都是走了一个交换机设备将其分离开。</p>
 </li>
 <li>
  <p style="">防火墙：这里的防火墙是策略的加强。生产环境、测试环境和办公环境的防火策略不同。之后可以部署一些与外部通信的设备，比如MQ。</p>
  <p style="">生产云spine交换机和生产云leaf交换机这种架构叫做脊叶网络，是目前公司普遍使用的一种架构。spine是脊梁，就是主干的意思。leaf是叶子的意思，就是分支了。一般一台spine交换机下面会对应几十组leaf交换机，一起进行流量分发。</p>
 </li>
 <li>
  <p style="">F5是负载均衡设备，同时它还可以进行https卸载，就是https转http。关于https可以参考我之前的文章<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485862&amp;idx=1&amp;sn=414f61b537094b14dab2682a343700e9&amp;chksm=fafde308cd8a6a1e993ed8774cef1c8dc1233761a17b27395b89e3d2c74b0908e0971aeedf9a&amp;scene=21#wechat_redirect" target="_blank">《架构师之路-https的底层原理》</a>，普遍评价说是比较好理解。</p>
  <p style="">F5设备怎么卖，处理能力怎么样，我还专门查了一下，截图如下：</p>
 </li>
</ul>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qxsw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;咱们看看最贵的：</p>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 1034px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 934px">
  </colgroup>
  <tbody>
   <tr style="height: 60px;">
    <td colspan="2" rowspan="1" colwidth="100,934" style="padding: 8px 20px;font-size: 16px;line-height: 34px;border-bottom-color: rgb(245, 245, 245);color: rgb(51, 51, 51);font-weight: bold;background: rgb(250, 250, 250);">
     <p style=""><strong>产品概述</strong></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="100" style="padding: 8px 20px;font-size: 14px;line-height: 24px;border-bottom-color: rgb(245, 245, 245);color: rgb(51, 51, 51);width: 160px;text-align: left;font-weight: normal;border-right-color: rgb(245, 245, 245);">
     <p style="">设备类型</p>
    </th>
    <td colspan="1" rowspan="1" colwidth="934" style="padding: 8px 20px;font-size: 14px;line-height: 24px;border-bottom-color: rgb(245, 245, 245);color: rgb(153, 153, 153);">
     <p style=""><span fontsize="" color="">负载均衡器</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="100" style="padding: 8px 20px;font-size: 14px;line-height: 24px;border-bottom-color: rgb(245, 245, 245);color: rgb(51, 51, 51);width: 160px;text-align: left;font-weight: normal;border-right-color: rgb(245, 245, 245);">
     <p style="">硬件配置</p>
    </th>
    <td colspan="1" rowspan="1" colwidth="934" style="padding: 8px 20px;font-size: 14px;line-height: 24px;border-bottom-color: rgb(245, 245, 245);color: rgb(153, 153, 153);">
     <p style=""><span fontsize="" color="">处理器：英特尔四核Xeon处理器（共8个超线程逻辑处理器内核
       <br>内存：32GB
       <br>硬盘：400GB SSD
       <br>接口：标配8个万兆/千兆位光纤端口，最大32个
       <br>硬件SSL：标配4000/Blade，最大10000 TPS(2K keys)，9Gbps批量加密
       <br>硬件DDoS保护：每秒40M SYN cookies
       <br>高度：4U
       <br>服务器缓存：缺省提供内存仿真cache，提供静态页面加速，降低后台服务器压力
       <br>电源：标配两个交流100-240 VAC（800W），50/60Hz自动测距，每插槽电流10A（最大），直流电源（可选）</span></p>
    </td>
   </tr>
   <tr style="height: 60px;">
    <th colspan="1" rowspan="1" colwidth="100" style="padding: 8px 20px;font-size: 14px;line-height: 24px;border-bottom-color: rgb(245, 245, 245);color: rgb(51, 51, 51);width: 160px;text-align: left;font-weight: normal;border-right-color: rgb(245, 245, 245);">
     <p style="">性能应用概述</p>
    </th>
    <td colspan="1" rowspan="1" colwidth="934" style="padding: 8px 20px;font-size: 14px;line-height: 24px;border-bottom-color: rgb(245, 245, 245);color: rgb(153, 153, 153);">
     <p style=""><span fontsize="" color="">每秒L7请求数：1M
       <br>每秒L4连接数：400K
       <br>每秒L4 HTTP请求数：7M
       <br>最大L4并发连接数：24M
       <br>L4吞吐量：40Gbps
       <br>L7吞吐量：18Gbps
       <br>最大软件压缩：10Gbps
       <br>软件架构：64位TMOS
       <br>虚拟化支持：每个刀片8个，最大4个刀片
       <br>支持的虚拟服务器数量 VIP：无限制
       <br>Real Server：无限制
       <br>IP Version：支持ipv4 和ipv6
       <br>协议层保护：提供HTTP（S）,FTP(S)，SMTP等协议层安全
       <br>安全的管理：可以通过HTTPS、SSH进行安全的远程管理，本地可以通过CONSOLE终端进行管理</span></p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<p style=""><strong><span style="font-size: 18px; color: rgb(0, 209, 0)">其他处理流程</span></strong></p>
<p style="">从链路处理流程，在公司的软件架构地图上再点一下zoom in，理论上要到达公司的整体架构了。但是公司的整体架构和组织架构之间密不可分，通常是业务架构而不是技术架构。</p>
<p style="">一个http请求要经过很多个部门甚至事业群的处理才会再传送到外部机构那边。外部机构下游链条也很长，比如支付请求从微信二维码开始到达腾讯内网处理之后，至少要再经过网联(支付监管)再达到银行侧，从银行处理之后原路返回。</p>
<p style="">再zoom in一下，只看一个部门所做的事情，对一个支付请求来说，至少要经过渠道、交易核心和支付网关三层，在一个大公司，这往往是三个团队，一个大组十几个人的样子。</p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">渠道</span></strong></p>
<p style="">渠道是指业务渠道，比如智能终端设备比如扫码枪、小白盒和二维码会分属不同渠道。它们可能会由不同的应用系统来承建。</p>
<p style="">上面说的只能终端设备很多朋友可能不知道名字，但是一般都在超市等地方见过。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nphx.jpg&amp;size=m" width="296px" height="296px" style="display: inline-block"></p>
<p style="">上面是一个扫码枪的图片，生活中我在小卖店见的比较多。我买了东西放到柜台上结账，收银员就会拿出扫码枪对着我的商品条码扫一下，他们就能看着电脑告诉我付多少钱。这个是用来创建商品订单的，和支付没有关系，一般需要另外扫码或现金支付。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-drsh.png&amp;size=m" width="508px" height="316px" style="display: inline-block"></p>
<p style=""></p>
<p style="">盒子设备我在大超市见的多一些，这个是真正的支付设备。商品订单已经生成好了，支付费用也已经汇总好了，拿自己的付款码一扫就可以支付。这种方式的好处是支持离线。就是网络不好也能支付成功，等待网络恢复再进行真正的扣款。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-rrgq.jpg&amp;size=m" width="368px" height="499px" style="display: inline-block"></p>
<p style="">上面这个是大家最熟悉的扫码付款。来来来，欢迎大家扫描上面二维码发起支付，熟悉一下整个付款流程。扫码付款对于扫描的人来说，扫描时解析的数据只有商家的信息没有付款人的信息，所以需要经过互联网几次交互才能支付成功，商家才会放你离开。</p>
<p style="">综上所述，每种支付渠道支付流程都就有不少差异化信息，所以渠道侧经常需要快速迭代业务。</p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">交易核心</span></strong></p>
<p style="">不管何种渠道，它们最终都需要落单，就是把支付订单统一存储，最终进行清算。交易核心的数据一般需要提供多个部门使用，比如清算、营销、会员、风控。</p>
<p style=""><strong><span fontsize="" color="rgb(172, 57, 255)" style="color: rgb(172, 57, 255)">支付网关</span></strong></p>
<p style="">支付网关就相当于是业务架构的防腐层，一般公司都提供多种支付方式，也叫支付渠道。比如同时支持微信、支付宝、余额支付、银行卡。不同支付渠道提供的接口不同，需要网关层做一个统一适配。</p>
<p style="">再zoom in一下，到一个团队，也一般会有几个或者几十个应用系统构成。一个应用的部署架构有两地三中心或者双机房容灾的部署。一个应用web容器内也请求分发和请求处理两种大类的线程。请求处理线程的处理流程在做详细设计的时候一般需要画出活动图来描述处理流程，举例如下：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-wnjj.png&amp;size=m" width="927px" height="1720px" style="display: inline-block"></p>]]></description><guid isPermaLink="false">/archives/yi-ge-httpqing-qiu-jin-lai-du-jing-guo-liao-shi-me-2021ban</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 09:01:58 GMT</pubDate></item><item><title><![CDATA[架构师之路-redis集群解析]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-shi-zhi-lu-redisji-qun-jie-xi</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B9%8B%E8%B7%AF-redis%E9%9B%86%E7%BE%A4%E8%A7%A3%E6%9E%90&amp;url=/archives/jia-gou-shi-zhi-lu-redisji-qun-jie-xi" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="%E5%BC%95%E5%AD%90"><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></h1>
<p style=""><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-nrnl.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">上篇<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485862&amp;idx=1&amp;sn=414f61b537094b14dab2682a343700e9&amp;chksm=fafde308cd8a6a1e993ed8774cef1c8dc1233761a17b27395b89e3d2c74b0908e0971aeedf9a&amp;scene=21#wechat_redirect" target="_blank">《架构师之路-https底层原理》</a>里我提到了上面的整体视图，文章也介绍了想要真正能在工作中及时正确解决问题的基本功：原理理解透彻。今天以redis集群解析为例介绍一个及时敏锐的发现问题的基本功：深入分析。</p>
<p style="">我认为达到深入分析有三个步骤：</p>
<p style="">第一步，深入理解</p>
<p style="">第二步，学以致用</p>
<p style="">第三步，千人千问</p>
<p style="">第一步redis集群各种原理介绍的人也很多；第二步很多人实际项目中大概也对redis集群不陌生；所以本文主要讲第三步：千人千问。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">提出问题</span></strong></p>
<p style="">"redis集群使用时有什么注意事项？"这是之前有段时间我面试喜欢问的一个问题。我的一个观点是作为redis集群的使用方而不是开发者首先要做的是用好。通过这个问题确定候选人用好了，再去挖掘他是通过了解了怎么用好的。所以我通常不会一开始就问一些中间件的原理，而是先从使用者的角度提出问题。</p>
<p style="">下面列举了6条代表性的回答：</p>
<p style="">1&gt;防止集中失效</p>
<p style="">2&gt;单线程执行，注意不要卡住</p>
<p style="">3&gt;注意客户端和服务端的版本匹配</p>
<p style="">4&gt;分片要保持流量均匀</p>
<p style="">5&gt;注意超时时间配置</p>
<p style="">6&gt;当内存缓存用，推荐删除代替更新</p>
<p style=""></p>
<p style="">每条问题前面加上个为什么，就引出了6条新的问题。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">解决问题</span></strong></p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q1：为什么要防止缓存集中失效？</span></strong></p>
<p style="">A：缓存穿透、缓存集中失效和缓存雪崩并称为缓存世界的三大问题。先来总结理解一下这三个概念。这三个概念都是建立在缓存的一大作用就是对后端存储，比如mysql的保护。缓存没有保护住mysql，一个或一些到mysql了，那就是穿透；一个时间点缓存数据没有了，打到mysql了就是集中失效；缓存完全丧失了保护mysql的能力，请求全打到mysql了，就是雪崩。</p>
<p style="">所以防止缓存集中失效是对后端存储的保护。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q2：为什么单线程执行，注意不要卡住？</span></strong></p>
<p style="">A：卡住换个专业点的词就是阻塞嘛。什么叫阻塞呢，一辆小轿车A在单行道跑，遇到前面一辆车B停了，那A就被阻塞了。如果A和B都在高速单行道上跑，A开了160迈、B开了140迈。就算高速的允许最高速度是120迈(咱就当路过的是没有摄像头的路段，这俩车肆无忌惮)，那A是不是还是被B给阻塞了呢？所以卡住造成的最直接影响就是快的快不起来，因为单线程不能绕行嘛。</p>
<p style="">有人说不是可以多开几个redis嘛。是滴，但是客户端分请求使用的是crc16，根本不会先探测哪个服务端比较空闲呀。所以后面来的总会被阻塞。</p>
<p style="">注意不要卡住还有个大家更常听到的名字：“避免大key问题”。其实我刚听到这个名字的时候是觉得很奇怪的。因为避免大key实际上是要避免key所对应的value不要太大。我之前一直觉得这个名字取的不对。那应该叫“避免大value问题”。后来想想这确实是正宗的中国话。比如张三的媳妇，人家都怎样叫呢？一般都是张三媳妇、张三家的。因为他家主要是张三出来抛头露面。那redis取值也一样，一般是先知道key，从key取value。用这个key取出来的数据大，就是大key问题啦。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q3：如果不注意客户端和服务端版本匹配会引发什么问题？</span></strong></p>
<p style="">A：先来思考客户端做了什么事情。我理解它就做了两件事：第一是使用RESP（Redis自定义的序列化协议）传输客户端命令并返回结果。第二是为了做第一件事，因为Redis集群是直连服务端模式，所以计算命令要落在哪个节点、哪个哈希槽上也是客户端来做的，我就称为选节点吧。</p>
<p style="">其实要回答客户端和服务端不匹配会引发什么问题，正规的方法应该是查看客户端版本升级都做过什么更新。</p>
<p style="">一般升级会做的是客户端依赖的jar包变了。这个可能会引起程序启动错误，但是这个往往启动成功了就不会再有问题，和服务端版本没有直接关系。</p>
<p style="">十年前还在用memcache的时候，发生过一次升级客户端版本，因为算法发生了变化，所以导致缓存全部不命中的问题。Redis最近的算法一直是crc16。如果不存在分布式算法不兼容问题的话，下一个要考虑的是大迭代是Redis3.0版本，支持了集群，集群模式是必须要匹配的。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q4：为什么分片要保持流量均匀？</span></strong></p>
<p style="">A：要提分片先来回忆一下redis集群的发展史，从单机版到主从版，后来有了大家可能很耳熟的哨兵模式。哨兵模式就是给主从增加一个监控，发现主节点挂了自动把从节点升级为主节点，有了故障自动迁移的功能。但是直到哨兵时代都只有一个主节点，也就是处理写请求的节点，不能称之为真正的集群。这也是很无奈的事情，一旦多个节点写一份数据，就涉及到数据一致性的问题。</p>
<p style="">一个蜂巢只能有一个蜂后，多出来一个，蜜蜂们就不会正常提供采蜜服务了，都打架去了。但是分成两个蜂巢呢？秩序就会恢复。所以现在的集群基本都是分片的原理。之前主从和哨兵的经验不能废弃，加上分片。redis集群就是将一个完整服务数据分成几份，每份都带着从节点，故障时可自动转移的一个整体。之前在《<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485567&amp;idx=1&amp;sn=0644277d2c046890a14442830a4ed0f3&amp;chksm=fafde2d1cd8a6bc70307efef696654f89074c2219f824af72effe5eb027cee983b364e4279c8&amp;scene=21#wechat_redirect" target="_blank">Redis集群搭建采坑总结</a><span style="font-size: 16px">》里讲过，1个节点的集群会有问题，最少需要3主3从也就是6个redis进程。3个主方便在1个挂掉之后重新选主。</span></p>
<p style="">梳理了这个之后，分片保持流量均匀这件事也很容易了。就是Q2的问题，均匀更不容易阻塞嘛。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q5：为什么要注意超时时间配置？</span></strong></p>
<p style="">A：提到redis的key的过期时间，首先想到的是redis的术语中，带过期时间的key又叫volatile key，就是不稳定key。怎么不稳定呢？就相当key这个对象有value和过期时间2个属性。过期时间这个属性1s改变一次(redis领域内时间都是以秒为单位)，一直在变，当然不稳定。</p>
<p style="">如果把过期时间理解为key的一个属性，那也很好理解：对其进行del、set命令时过期时间也会删除；rename会把过期时间传给新的key；incr、lpush、hset等命令改变的是key的存储容器，没有改变key这个对象本身所以不会影响过期时间。</p>
<p style="">值得注意的是persist命令就是持久化保存的意思，将不稳定变成稳定，过期时间也自动删除了。</p>
<p style="">Redis在服务端有过期策略，但是对客户端是不感知的。客户端访问过期的就是一个表现，访问不到了。实际上服务端是有两种策略配合使用，一个是惰性删除，就是访问的时候发现过期了，就直接删除了；另一个策略会定期去删除，这个是为了防止一个过期的key总是不被访问到，还占着资源不释放。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">Q6：为什么当内存缓存用，推荐删除代替更新？</span></strong></p>
<p style="">A：一般大家出于数据一致性的考虑，会选择删除代替更新。这都是基于更新一定要更新数据库的固有思路。并发场景下，A的值1先被更新到数据库再更新缓存时，又来了一个更新请求把A的值更新为2。如果这时候执行更新为1的服务器性能不好或者网络传输速度比更新为2的慢，导致2在数据库是最新值，而设置为1的后更新了缓存。缓存就和数据库不一致了。</p>
<p style="">但只是记住删除代替更新不太够。如果先删除缓存再更新数据库，其他请求可能会把数据库老的值再加载到缓存中。记得之前有人介绍缓存还有三大种模式：Cache-Aside、Read-Through/Write-Through、Write-Behind。</p>
<p style="">Cache-Aside就是先更新数据库再删除缓存数据，可以避免上面提到的持续脏读的问题，顶多就是更新数据库的那一小段时间有更新延迟可见。我们给Cache-Aside起个中文名，叫经典模式。</p>
<p style="">Read-Through/Write-Through就是数据以缓存为准，数据库的操作是缓存发起的。Read-Through是在读数据时发现缓存过期了，那缓存自己去数据库加载新的数据，读数据还是读取缓存值。Write-Through写数据时调用方只负责写缓存，缓存自己去同步更新数据库。Read-Through/Write-Through一般配合使用。</p>
<p style="">Write-Behind和Write-Through的区别是虽然都是是写数据时调用方只负责写缓存，但是Write-Behind缓存自己去异步更新数据库。</p>
<p style="">因为Read-Through/Write-Through、Write-Behind都是以缓存为准，缓存不可靠，所以还是推荐经典模式。</p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/jia-gou-shi-zhi-lu-redisji-qun-jie-xi</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 08:59:49 GMT</pubDate></item><item><title><![CDATA[架构师之路-https底层原理]]></title><link>https://wiki.dgfygk.com/archives/jia-gou-shi-zhi-lu-httpsdi-ceng-yuan-li</link><description><![CDATA[<img src="https://wiki.dgfygk.com/plugins/feed/assets/telemetry.gif?title=%E6%9E%B6%E6%9E%84%E5%B8%88%E4%B9%8B%E8%B7%AF-https%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86&amp;url=/archives/jia-gou-shi-zhi-lu-httpsdi-ceng-yuan-li" width="1" height="1" alt="" style="opacity:0;">
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">引子</span></strong></p>
<p style="">先说说我对架构师的理解。从业务能力上，需要的是发现问题和解决问题的能力；从团队建设上，需要的是能培养团队的业务能力；从项目管理上，把控好整个项目和软件产品的全生命周期。</p>
<p style="">我搜索了一下架构师的培训课程，讲什么的也有。实际上是Java从入门到精通的课也冠名架构师课程；讲持久层框架的也冠名架构师课程；讲组件、中间件的也冠名架构师课程；讲分布式系统设计的也冠名架构师课程。个人意见哈，内容其实没有什么问题，但是课程开始之前至少让大家有个整体的视图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qhqb.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">熟悉https等常用技术的底层原理，可以增强解决实际问题的能力，作出正确的决策。举个例子：</p>
<p style="">接到安全团队的指令，要求各个团队梳理域名是否存在证书将要到期的安全风险。怎么来给团队下发具体任务呢？这个问题在文章最后总结并回答。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">简单入门</span></strong></p>
<p style="">先来回答下面两个问题：</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">1、大家都知道使用https协议传输默认使用443端口，那什么情况下必须使用443，什么情况下不使用443？</span></strong></p>
<p style="">大家想一下端口的本质是什么。其实端口有两种，一种是物理端口，比如集线器、路由器的端口。大学的时候记得有门课叫集成电路，里面有讲到过。另一种是逻辑端口，是TCP/IP协议的端口，也常叫网络端口。本质上一个端口是用来绑定服务的，那服务器用监听什么端口，客户端就要向哪个端口发起请求。我们自己也可以写个简单的例子，写过这个例子应该很容易就能明白。<a href="http://mp.weixin.qq.com/s?__biz=MzUzNjAxODg4MQ==&amp;mid=2247485527&amp;idx=1&amp;sn=21413445605b9b2b9f883ba7b446c64c&amp;chksm=fafde2f9cd8a6bef39582ee0075fcb846bf5c320dbddb0b4f0363a88f42d760109acf8ae198f&amp;scene=21#wechat_redirect" target="_blank">《懂了！国际算法体系对称算法DES原理》</a>也有完整的例子。</p>
<p style="">简单点说服务端可以自己决定什么端口提供服务。但是呢，大家各自为政，记忆起来麻烦也不便于端口管理。所以逻辑端口也进行了划分。其中0~1023是公认端口，比如80就默认是http的端口、443就是https的端口。这样约定好了，就不需要特殊来记了。所以一些对全人类开发的公网网站，就是用的默认端口。但是你自己或者公司内部用的或者和合作方商量好的，也可以用其他端口，只要服务提供方认就行。</p>
<p style=""><strong><span fontsize="" color="rgb(255, 41, 65)" style="color: rgb(255, 41, 65)">2、使用443的端口的同时，80端口还会用吗？</span></strong></p>
<p style="">这就是涉及到https的本质了。https本质是http协议上加了一个安全层。安全层做了一些处理，http要做的事情一点也没有减少。就相当于在药粒外加了一层胶囊，本质上起作用的还是药粒呀；快递送来的海鲜放在保鲜箱里还加了保鲜袋本质还是买的海鲜呀。所以当然要用啦。</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-yrop.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">安全层原理</span></strong></p>
<p style="">既然https协议本质只是http上加了安全层，本文就主要将安全层是怎么实现的。当然要靠加密。加密又分成对称加密和非对称加密。要使用对称加密，客户端和服务端要有一个相同的秘钥，这个秘钥怎么能安全的传输就成了问题。非对称加密是公钥只能用来加密，私钥只能用来解密。那服务端自己存私钥，客户端来请求的时候，服务端把公钥发给它就可以了。</p>
<p style="">那有的朋友就问了，那别的客户端来请求是不是也拿到的是同一个公钥呀，信息就不安全了呀。没错，同一个服务端给所有的客户端返回的是相同的公钥。但是威胁网络安全的黑客需要是截获信息，不是公钥。信息发送方用公钥把信息加密了，黑客没有私钥解不开。他能拿到公钥和密文，想解开那就只能暴力破解，把所有语言文字的组合用公钥试一下看看和截获到的密文是不是一样的。如果用常用的RSA2048的话，要80年。</p>
<p style="">总之，非对称加密是比较安全的，但是它慢。通常加解密速度是对称加密的几百倍，信息大了速度还会快速下降。所以https协议是用非对称加密沟通一个对称秘钥。真正的信息就用对称秘钥来加解密了。</p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">CA证书</span></strong></p>
<p style="">很多人都听过网络抓包吧。抓包的原理和黑客截获信息差不多，都是自己作为一个代理。客户端B想访问C网站，黑客D发现了。D就伪装成了C把自己的公钥发给了B。B发请求“我是B，我的提货卡号码是XBXB”到网站C时，黑客D截获了这个信息，用自己的私钥解开，之后把数据篡改成“我是D，我的提货卡号码是XBXB”发给网站C。于是本来B的东西就被D领走了。抓包也是这种充当代理的原理。</p>
<p style="">为了防止这一问题。客户端就需要在收到公钥的时候做一下判断，信任的公钥才使用。于是出现了一些大家都很认可的组织，它们可以颁发公钥。</p>
<p style=""><span fontsize="" color="">打个比方，在唐朝，贺知章贺老可谓是少有的文坛官场双得意的代表，“二月春风”没少光顾他家。从7品官做到3品大员，是皇帝的老铁，为国服务50年后退休皇帝了还送豪宅，后来还被追封礼部尚书。《长安十二时辰》里那个80多的老头何监就是影射的贺知章，因为他退休前的官职就是监事，人称贺监。不少诗坛大家就是受了他的推荐才与皇帝建立了通信成为了公务员。所以，贺监还有一个身份是“大唐最佳星探”。</span></p>
<p style=""><span fontsize="" color="">这里贺监就是大家都认可的组织，在贺监80多岁的时候，40多岁的李白找到贺监，请求他给自己签发一份公钥。贺监就用自己的私钥给他做了签发写了推荐信，不仅如此，还金龟换酒传为佳话。皇帝想选公务员，李白拿出了贺监的推荐信。皇帝一看，确实是贺监的笔迹，马上就给李白封了公务员。</span></p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-qedh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这里提一下，很多时候https抓包可以成功，原理是客户端很多时候并不验证证书的合法性。有个选项check=false。那至于哪些是认证机构，这个是系统预装的。可以在IE浏览器的internet选项里查到预装的证书。在中国，很多金融机构，它们的认证机构是央行。这些证书都是有有效期的，有的三年一签发、有的五年一签发。</p>
<p style="">上面图中有显示，证书除了公钥信息，还有数字签名信息。数字签名当然需要有信息来进行签名，具体信息就是我们在生成密钥时需要输入的那些，如下图：</p>
<p style="text-align: center; "><img src="https://wiki.dgfygk.com/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F640-bari.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style=""><strong><span style="font-size: 20px; color: rgb(216, 79, 169)">总结</span></strong></p>
<p style="">回答开头的问题：</p>
<p style="">接到安全团队的指令，要求各个团队梳理域名是否存在证书将要到期的安全风险。怎么来给团队下发具体任务呢？</p>
<p style="">1、要梳理的是提供https服务的服务端，只有服务端才会需要找认证机构签发证书</p>
<p style="">2、只有需要外部认证的证书有过期的概念，自己签发的可以更新也可以不更新。可以通过向服务端发请求的获取到证书，查看证书是谁签发的、有效期是哪天</p>
<p style="">架构师经常面临着需要做出正确的决策，比如连续几天团队都在执行生产程序发布，但是后面几天大家有纪律涣散的趋势，这时候应该敏锐的察觉到并及时指出</p>]]></description><guid isPermaLink="false">/archives/jia-gou-shi-zhi-lu-httpsdi-ceng-yuan-li</guid><dc:creator>黄健</dc:creator><category>默认分类</category><pubDate>Sat, 21 Sep 2024 08:58:21 GMT</pubDate></item></channel></rss>